diff --git a/apps/web/src/screens/PersonDetail.tsx b/apps/web/src/screens/PersonDetail.tsx index 484808b..8db13c4 100644 --- a/apps/web/src/screens/PersonDetail.tsx +++ b/apps/web/src/screens/PersonDetail.tsx @@ -5,12 +5,14 @@ import { MarkdownView } from '@/components/MarkdownView'; import { StageBadge } from '@/components/StageBadge'; import { TagChip } from '@/components/TagChip'; import { PersonAvatar } from '@/components/PersonAvatar'; +import { useAuth } from '@/hooks/useAuth'; import { api, ApiError } from '@/lib/api'; import { formatMonthYear, formatRelativeTime } from '@/lib/time'; export function PersonDetail() { const params = useParams(); const slug = params['slug']!; + const { person: viewer } = useAuth(); const personQ = useQuery({ queryKey: ['person', slug], @@ -37,6 +39,7 @@ export function PersonDetail() { } const person = personQ.data!.data; + const isSelf = viewer !== null && viewer.slug === person.slug; const allTags = [...person.tags.tech, ...person.tags.topic]; // Memberships sorted: maintainer desc, joinedAt desc @@ -193,6 +196,13 @@ export function PersonDetail() {

{formatMonthYear(person.createdAt)}

+ {isSelf && ( +
+ + Manage account + +
+ )} ); diff --git a/apps/web/tests/PersonDetail.test.tsx b/apps/web/tests/PersonDetail.test.tsx index 18f69ea..e5d2642 100644 --- a/apps/web/tests/PersonDetail.test.tsx +++ b/apps/web/tests/PersonDetail.test.tsx @@ -99,4 +99,54 @@ describe('PersonDetail Contact sidebar', () => { expect(mailto).toHaveAttribute('href', 'mailto:jane@example.com'); }); }); + + // #113 — "Manage account" link, self only + it('shows a "Manage account" link to /account when viewing your own profile', async () => { + vi.spyOn(globalThis, 'fetch').mockImplementation(((input: string) => { + if (input.startsWith('/api/auth/me')) { + return Promise.resolve( + new Response( + JSON.stringify( + mockOk({ + person: { id: BASE_PERSON.id, slug: 'jane-doe', fullName: 'Jane Doe', accountLevel: 'user', avatarUrl: null }, + accountLevel: 'user', + }), + ), + { status: 200, headers: { 'content-type': 'application/json' } }, + ), + ); + } + if (input.startsWith('/api/people/jane-doe')) { + return Promise.resolve(new Response(JSON.stringify(mockOk(BASE_PERSON)), { status: 200, headers: { 'content-type': 'application/json' } })); + } + return Promise.resolve(new Response(null, { status: 404 })); + }) as typeof fetch); + renderScreen( + + + } /> + + , + { initialEntries: ['/members/jane-doe'] }, + ); + await waitFor(() => { + expect(screen.getByRole('link', { name: /manage account/i })).toHaveAttribute('href', '/account'); + }); + }); + + it('does not show "Manage account" for anonymous viewers', async () => { + vi.spyOn(globalThis, 'fetch').mockImplementation(makeFetchMock(BASE_PERSON)); + renderScreen( + + + } /> + + , + { initialEntries: ['/members/jane-doe'] }, + ); + await waitFor(() => { + expect(screen.getByRole('heading', { name: 'Jane Doe', level: 1 })).toBeInTheDocument(); + }); + expect(screen.queryByRole('link', { name: /manage account/i })).not.toBeInTheDocument(); + }); });