diff --git a/src/components/__tests__/FollowButton.test.tsx b/src/components/__tests__/FollowButton.test.tsx
new file mode 100644
index 00000000..002529ee
--- /dev/null
+++ b/src/components/__tests__/FollowButton.test.tsx
@@ -0,0 +1 @@
+import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import { describe, it, expect, vi } from 'vitest';
import FollowButton from '../common/FollowButton';
describe('FollowButton', () => {
it('renders Follow when not following', () => {
render();
expect(screen.getByTestId('follow-button')).toHaveTextContent('Follow');
});
it('renders Following when following', () => {
render();
expect(screen.getByTestId('follow-button')).toHaveTextContent('Following');
});
it('calls onFollow with correct address when not following', async () => {
const onFollow = vi.fn().mockResolvedValue(undefined);
render();
fireEvent.click(screen.getByTestId('follow-button'));
await waitFor(() => expect(onFollow).toHaveBeenCalledWith('0x123'));
});
it('calls onUnfollow when already following', async () => {
const onUnfollow = vi.fn().mockResolvedValue(undefined);
render();
fireEvent.click(screen.getByTestId('follow-button'));
await waitFor(() => expect(onUnfollow).toHaveBeenCalled());
});
it('disables button and shows loading while pending', async () => {
const onFollow = vi.fn().mockImplementation(() => new Promise(resolve => setTimeout(resolve, 100)));
render();
fireEvent.click(screen.getByTestId('follow-button'));
expect(screen.getByTestId('follow-button')).toBeDisabled();
expect(screen.getByTestId('follow-button')).toHaveTextContent('Loading...');
await waitFor(() => expect(onFollow).toHaveBeenCalled());
});
});
\ No newline at end of file
diff --git a/src/components/common/FollowButton.tsx b/src/components/common/FollowButton.tsx
new file mode 100644
index 00000000..8d88d30d
--- /dev/null
+++ b/src/components/common/FollowButton.tsx
@@ -0,0 +1,42 @@
+import { useState } from 'react';
+import { Button } from '@/components/ui/button';
+
+interface FollowButtonProps {
+ creatorAddress: string;
+ isFollowing: boolean;
+ onFollow: (creatorAddress: string) => Promise;
+ onUnfollow: (creatorAddress: string) => Promise;
+}
+
+export default function FollowButton({
+ creatorAddress,
+ isFollowing,
+ onFollow,
+ onUnfollow,
+}: FollowButtonProps) {
+ const [loading, setLoading] = useState(false);
+
+ const handleClick = async () => {
+ setLoading(true);
+ try {
+ if (isFollowing) {
+ await onUnfollow(creatorAddress);
+ } else {
+ await onFollow(creatorAddress);
+ }
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ return (
+
+ );
+}
\ No newline at end of file