fix(19): CR-02 send initialPassword + map 409 conflict in fetchCreateMember

This commit is contained in:
Lucas Berger
2026-06-17 20:15:58 -04:00
parent 1688f229e0
commit 93c47b38aa
+17 -4
View File
@@ -168,10 +168,14 @@ export async function fetchChangePassword(body: {
* POST /api/admin/members — create a new local member account (Phase 19, Surface 11A).
* Admin-only; server enforces requireAdmin.
*
* Request contract: the server's createMemberSchema requires
* { displayName, username, initialPassword }
* (see apps/api/src/routes/admin.ts). The caller-facing `password` field is mapped to
* `initialPassword` here so the request validates server-side.
*
* Status codes:
* 409 → username already taken
* 422 → validation failure (short password / mismatch)
* other non-ok → generic error
* 409 → username already taken (throws Error with message 'conflict')
* other non-ok → generic error (throws Error('server'))
*/
export async function fetchCreateMember(body: {
displayName: string;
@@ -183,10 +187,19 @@ export async function fetchCreateMember(body: {
headers: { 'Content-Type': 'application/json' },
credentials: 'include',
redirect: 'manual',
body: JSON.stringify(body),
// CR-02: the server expects `initialPassword`, not `password`. Send the field it
// validates against — otherwise Zod rejects every create with a generic 400.
body: JSON.stringify({
displayName: body.displayName,
username: body.username,
initialPassword: body.password,
}),
});
if (res.type === 'opaqueredirect' || res.status === 401) throw new SessionExpiredError();
// 409 → username conflict. The server returns { error: 'Username already in use' } (no
// `code` field), so map the status to the 'conflict' sentinel the AdminPage handler expects.
if (res.status === 409) throw new Error('conflict');
if (!res.ok) {
const detail = (await res.json().catch(() => ({}))) as { code?: string };
throw new Error(detail.code ?? 'server');