feat(20-02): add updateMemberProfile fetcher + AdminMember.isAdmin

- Add isAdmin: boolean to AdminMember interface (after color, before
  hasCredential) — feeds the Phase 20 editor toggle initial state (D-02)
- Add updateMemberProfile(memberId, body) fetcher: PATCH /api/admin/members/:id,
  credentials:include, redirect:manual, JSON body
- Maps 401/opaqueredirect → SessionExpiredError (existing convention)
- Maps 409/422 → Error('last-admin') sentinel (D-03 last-admin guard)
- Maps other non-ok → generic error
- All 50 tests pass; eslint + prettier + tsc --noEmit exit 0

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Lucas Berger
2026-06-18 17:20:13 -04:00
co-authored by Claude Sonnet 4.6
parent 18da7e9476
commit 5bcd8180c1
2 changed files with 33 additions and 4 deletions
+2 -4
View File
@@ -801,15 +801,13 @@ describe('updateMemberProfile — URL + verb contract (Phase 20, Plan 20-02)', (
const { updateMemberProfile, SessionExpiredError } = await import('./client.js');
await expect(updateMemberProfile(7, { displayName: 'X' })).rejects.toSatisfy(
(e: unknown) =>
e instanceof Error &&
!(e instanceof SessionExpiredError) &&
e.message !== 'last-admin',
e instanceof Error && !(e instanceof SessionExpiredError) && e.message !== 'last-admin',
);
});
});
describe('AdminMember.isAdmin field (Phase 20, Plan 20-02)', () => {
it('AdminMember interface has isAdmin: boolean (compile-time type check via runtime shape)', async () => {
it('AdminMember interface has isAdmin: boolean (compile-time type check via runtime shape)', () => {
// Construct a conforming object — TypeScript will error at compile time if
// isAdmin is missing from the AdminMember interface (caught by tsc --noEmit).
const member: import('./client.js').AdminMember = {