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 && (
+
+ )}
);
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();
+ });
});