@realiizlabs/admin 0.7.0 → 0.7.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/auth/index.cjs +7 -2
- package/dist/auth/index.cjs.map +1 -1
- package/dist/auth/index.d.cts +12 -3
- package/dist/auth/index.d.ts +12 -3
- package/dist/auth/index.js +7 -2
- package/dist/auth/index.js.map +1 -1
- package/dist/forms-ui/index.cjs +5 -5
- package/dist/forms-ui/index.cjs.map +1 -1
- package/dist/forms-ui/index.d.cts +9 -1
- package/dist/forms-ui/index.d.ts +9 -1
- package/dist/forms-ui/index.js +5 -5
- package/dist/forms-ui/index.js.map +1 -1
- package/package.json +1 -1
package/dist/auth/index.cjs
CHANGED
|
@@ -6,12 +6,17 @@ var supabaseJs = require('@supabase/supabase-js');
|
|
|
6
6
|
|
|
7
7
|
// src/auth/index.ts
|
|
8
8
|
function createIdentityClient(ctx) {
|
|
9
|
+
const store = ctx.cookies;
|
|
9
10
|
return ssr.createServerClient(ctx.url, ctx.anonKey, {
|
|
10
11
|
cookies: {
|
|
11
|
-
getAll: () =>
|
|
12
|
+
getAll: () => store.getAll(),
|
|
12
13
|
setAll: (list) => {
|
|
13
14
|
try {
|
|
14
|
-
|
|
15
|
+
if (typeof store.setAll === "function") {
|
|
16
|
+
void store.setAll(list);
|
|
17
|
+
} else if (typeof store.set === "function") {
|
|
18
|
+
for (const c of list) store.set(c.name, c.value, c.options);
|
|
19
|
+
}
|
|
15
20
|
} catch {
|
|
16
21
|
}
|
|
17
22
|
}
|
package/dist/auth/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/auth/client.ts","../../src/auth/session.ts","../../src/auth/errors.ts","../../src/auth/types.ts","../../src/auth/roles.ts","../../src/auth/callback.ts","../../src/auth/signout.ts","../../src/auth/service.ts"],"names":["createServerClient","createClient"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * createIdentityClient — a Supabase client bound to the request's cookies.\n *\n * Built per request from arguments. A Next 16 route does:\n *\n * const store = await cookies();\n * const client = createIdentityClient({ url, anonKey, cookies: store });\n *\n * No `next` import here: the CookieStore interface is exactly what\n * `cookies()` returns and exactly what @supabase/ssr accepts.\n */\n\nimport { createServerClient } from \"@supabase/ssr\";\nimport type { SupabaseClient } from \"@supabase/supabase-js\";\nimport type { IdentityContext } from \"./types\";\n\nexport type IdentityClient = SupabaseClient;\n\nexport function createIdentityClient(ctx: IdentityContext): IdentityClient {\n return createServerClient(ctx.url, ctx.anonKey, {\n cookies: {\n getAll: () => ctx.cookies.getAll(),\n setAll: (list) => {\n // Server Components can't set cookies; @supabase/ssr documents that\n // swallowing the failure there is correct because middleware/route\n // handlers refresh the session instead.\n try {\n void ctx.cookies.setAll?.(list);\n } catch {\n /* read-only cookie store */\n }\n },\n },\n });\n}\n","/**\n * getCurrentUser — who is signed in, verified against the auth server.\n *\n * `getUser()` round-trips to Supabase and validates the JWT; `getSession()`\n * only decodes the cookie and is not trustworthy on the server. Always this.\n */\n\nimport type { IdentityClient } from \"./client\";\nimport type { IdentityUser } from \"./types\";\n\nexport async function getCurrentUser(client: IdentityClient): Promise<IdentityUser | null> {\n const { data, error } = await client.auth.getUser();\n if (error || !data.user) return null;\n return { id: data.user.id, email: data.user.email ?? null };\n}\n","/** Typed errors so a route can map them to 401 / 403 without string matching. */\n\nexport class IdentityError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"IdentityError\";\n }\n}\n\n/** No session — send them to sign in. Maps to 401. */\nexport class UnauthenticatedError extends IdentityError {\n constructor() {\n super(\"Not signed in\");\n this.name = \"UnauthenticatedError\";\n }\n}\n\n/** Signed in, but not allowed to do this here. Maps to 403. */\nexport class ForbiddenError extends IdentityError {\n readonly siteId: string;\n readonly required: string;\n readonly actual: string | null;\n\n constructor(siteId: string, required: string, actual: string | null) {\n super(`Requires ${required} on site \"${siteId}\" (you are ${actual ?? \"not a member\"})`);\n this.name = \"ForbiddenError\";\n this.siteId = siteId;\n this.required = required;\n this.actual = actual;\n }\n}\n","/**\n * Types for the identity layer. All config arrives as arguments — never env.\n */\n\n/** Matches @supabase/ssr's CookieMethodsServer, so `next/headers` cookies() plugs in. */\nexport interface CookieStore {\n getAll(): { name: string; value: string }[] | Promise<{ name: string; value: string }[]>;\n setAll?(cookies: { name: string; value: string; options?: Record<string, unknown> }[]): void | Promise<void>;\n}\n\n/** Enough to read the signed-in user with the anon key + their cookies. */\nexport interface IdentityContext {\n url: string;\n anonKey: string;\n cookies: CookieStore;\n}\n\n/** Server-only. The service-role key bypasses RLS; the module checks roles in code first. */\nexport interface ServiceContext {\n url: string;\n serviceRoleKey: string;\n}\n\nexport type SiteRole = \"staff\" | \"owner\" | \"editor\";\n\nexport const ROLE_RANK: Record<SiteRole, number> = { editor: 1, owner: 2, staff: 3 };\n\nexport interface IdentityUser {\n id: string;\n email: string | null;\n}\n\nexport interface SiteMembership {\n userId: string;\n siteId: string;\n role: \"owner\" | \"editor\";\n}\n","/**\n * Role resolution and the request gate.\n *\n * Two selects on the caller's OWN rows — both permitted by RLS with the anon\n * key, so this never needs the service role. Staff wins over any site role.\n */\n\nimport type { IdentityClient } from \"./client\";\nimport { ForbiddenError, UnauthenticatedError } from \"./errors\";\nimport { getCurrentUser } from \"./session\";\nimport { ROLE_RANK, type IdentityUser, type SiteRole } from \"./types\";\n\nexport async function getSiteRole(client: IdentityClient, siteId: string, userId?: string): Promise<SiteRole | null> {\n const uid = userId ?? (await getCurrentUser(client))?.id;\n if (!uid) return null;\n\n const [staff, member] = await Promise.all([\n client.from(\"staff\").select(\"user_id\").eq(\"user_id\", uid).maybeSingle(),\n client.from(\"site_users\").select(\"role\").eq(\"user_id\", uid).eq(\"site_id\", siteId).maybeSingle(),\n ]);\n\n if (staff.error) throw staff.error;\n if (member.error) throw member.error;\n\n if (staff.data) return \"staff\";\n const role = (member.data as { role?: string } | null)?.role;\n return role === \"owner\" || role === \"editor\" ? role : null;\n}\n\nexport function roleAtLeast(actual: SiteRole | null, minimum: SiteRole): boolean {\n return actual !== null && ROLE_RANK[actual] >= ROLE_RANK[minimum];\n}\n\nexport interface RequireOptions {\n /** Default \"editor\" — any member of the site. */\n minimumRole?: SiteRole;\n}\n\n/**\n * The gate. Throws UnauthenticatedError (→ sign in) or ForbiddenError (→ 403);\n * otherwise returns the user and their effective role for this site.\n */\nexport async function requireSiteUser(\n client: IdentityClient,\n siteId: string,\n opts: RequireOptions = {},\n): Promise<{ user: IdentityUser; role: SiteRole }> {\n const user = await getCurrentUser(client);\n if (!user) throw new UnauthenticatedError();\n\n const minimum = opts.minimumRole ?? \"editor\";\n const role = await getSiteRole(client, siteId, user.id);\n if (!roleAtLeast(role, minimum)) throw new ForbiddenError(siteId, minimum, role);\n\n return { user, role: role as SiteRole };\n}\n","/**\n * handleAuthCallback — the emailed magic link lands here with ?code=…&next=…\n *\n * Exchanges the PKCE code for a session (cookies are written through the\n * adapter the client was built with) and says where to send the user. The\n * `next` parameter is only honoured when it is a plain same-origin path —\n * \"//evil.example\" and \"https://…\" fall back, which closes the open-redirect\n * a naive startsWith(\"/\") check leaves open.\n */\n\nimport type { IdentityClient } from \"./client\";\n\nexport type CallbackResult =\n | { ok: true; redirectTo: string }\n | { ok: false; reason: \"missing_code\" | \"exchange_failed\"; message?: string };\n\nexport interface CallbackOptions {\n /** Where to go when `next` is absent or unsafe. Default \"/admin\". */\n fallbackPath?: string;\n}\n\nexport function safeNextPath(next: string | null | undefined, fallback: string): string {\n if (!next) return fallback;\n // Exactly one leading slash, then not another slash or backslash.\n if (!/^\\/(?![/\\\\])/.test(next)) return fallback;\n // No scheme smuggling or CR/LF.\n if (/[\\r\\n]/.test(next) || /^\\/[^?#]*:\\/\\//.test(next)) return fallback;\n return next;\n}\n\nexport async function handleAuthCallback(\n client: IdentityClient,\n requestUrl: string | URL,\n opts: CallbackOptions = {},\n): Promise<CallbackResult> {\n const fallback = opts.fallbackPath ?? \"/admin\";\n const url = typeof requestUrl === \"string\" ? new URL(requestUrl, \"http://placeholder.invalid\") : requestUrl;\n const code = url.searchParams.get(\"code\");\n if (!code) return { ok: false, reason: \"missing_code\" };\n\n const { error } = await client.auth.exchangeCodeForSession(code);\n if (error) return { ok: false, reason: \"exchange_failed\", message: error.message };\n\n return { ok: true, redirectTo: safeNextPath(url.searchParams.get(\"next\"), fallback) };\n}\n","/** signOut — clears this browser's session. The caller redirects afterwards. */\n\nimport type { IdentityClient } from \"./client\";\n\nexport async function signOut(client: IdentityClient): Promise<void> {\n const { error } = await client.auth.signOut({ scope: \"local\" });\n if (error) throw error;\n}\n","/**\n * The only file that touches the service-role key. Invite and remove.\n *\n * Authorization is checked HERE, in code, before the privileged client does\n * anything — RLS doesn't apply to the service role, so this is the guard:\n * staff → may grant owner or editor on any site\n * owner → may grant editor on their own site only\n * editor → may not invite\n *\n * `actor` is the caller's already-resolved identity (from requireSiteUser on\n * the anon client). Its role is re-resolved with the service client so a\n * caller can't lie about it.\n */\n\nimport { createClient, type SupabaseClient } from \"@supabase/supabase-js\";\nimport { ForbiddenError, IdentityError } from \"./errors\";\nimport type { ServiceContext, SiteRole } from \"./types\";\n\nexport type ServiceClient = SupabaseClient;\n\nexport function createServiceClient(ctx: ServiceContext): ServiceClient {\n return createClient(ctx.url, ctx.serviceRoleKey, {\n auth: { persistSession: false, autoRefreshToken: false },\n });\n}\n\nexport interface InviteInput {\n email: string;\n siteId: string;\n role: \"owner\" | \"editor\";\n /** The signed-in user performing the invite. */\n actor: { id: string };\n /** Where the invite email's link lands — the site's /admin/auth/callback. */\n redirectTo: string;\n}\n\nexport interface InviteResult {\n userId: string;\n siteId: string;\n role: \"owner\" | \"editor\";\n /** false when the auth user already existed and only the membership was added. */\n invitedByEmail: boolean;\n}\n\nasync function resolveActorRole(svc: ServiceClient, actorId: string, siteId: string): Promise<SiteRole | null> {\n const staff = await svc.from(\"staff\").select(\"user_id\").eq(\"user_id\", actorId).maybeSingle();\n if (staff.error) throw staff.error;\n if (staff.data) return \"staff\";\n const m = await svc.from(\"site_users\").select(\"role\").eq(\"user_id\", actorId).eq(\"site_id\", siteId).maybeSingle();\n if (m.error) throw m.error;\n const role = (m.data as { role?: string } | null)?.role;\n return role === \"owner\" || role === \"editor\" ? role : null;\n}\n\nfunction assertMayGrant(actorRole: SiteRole | null, siteId: string, granting: \"owner\" | \"editor\"): void {\n if (actorRole === \"staff\") return;\n if (actorRole === \"owner\" && granting === \"editor\") return;\n throw new ForbiddenError(siteId, granting === \"owner\" ? \"staff\" : \"owner\", actorRole);\n}\n\nexport async function inviteUser(ctx: ServiceContext, input: InviteInput): Promise<InviteResult> {\n const svc = createServiceClient(ctx);\n const actorRole = await resolveActorRole(svc, input.actor.id, input.siteId);\n assertMayGrant(actorRole, input.siteId, input.role);\n\n const email = input.email.trim().toLowerCase();\n let userId: string | undefined;\n let invitedByEmail = false;\n\n const invite = await svc.auth.admin.inviteUserByEmail(email, {\n redirectTo: input.redirectTo,\n data: { site_id: input.siteId, role: input.role },\n });\n\n if (!invite.error) {\n userId = invite.data.user.id;\n invitedByEmail = true;\n } else if (/already|exists|registered/i.test(invite.error.message)) {\n // Existing auth user (works for another site, or is staff): just add the row.\n userId = await findUserIdByEmail(svc, email);\n if (!userId) throw new IdentityError(`Could not find existing user ${email}`);\n } else {\n throw new IdentityError(`Invite failed: ${invite.error.message}`);\n }\n\n const upsert = await svc\n .from(\"site_users\")\n .upsert({ user_id: userId, site_id: input.siteId, role: input.role, invited_by: input.actor.id }, { onConflict: \"user_id,site_id\" });\n if (upsert.error) throw upsert.error;\n\n return { userId, siteId: input.siteId, role: input.role, invitedByEmail };\n}\n\nasync function findUserIdByEmail(svc: ServiceClient, email: string): Promise<string | undefined> {\n // listUsers is paged; identity holds tens of users, not thousands.\n for (let page = 1; page <= 20; page++) {\n const { data, error } = await svc.auth.admin.listUsers({ page, perPage: 200 });\n if (error) throw error;\n const hit = data.users.find((u) => u.email?.toLowerCase() === email);\n if (hit) return hit.id;\n if (data.users.length < 200) break;\n }\n return undefined;\n}\n\nexport interface RemoveInput {\n userId: string;\n siteId: string;\n actor: { id: string };\n}\n\n/** Removes the membership only. The auth user survives (they may belong to other sites). */\nexport async function removeSiteUser(ctx: ServiceContext, input: RemoveInput): Promise<void> {\n const svc = createServiceClient(ctx);\n const actorRole = await resolveActorRole(svc, input.actor.id, input.siteId);\n\n const target = await svc.from(\"site_users\").select(\"role\").eq(\"user_id\", input.userId).eq(\"site_id\", input.siteId).maybeSingle();\n if (target.error) throw target.error;\n const targetRole = ((target.data as { role?: string } | null)?.role ?? \"editor\") as \"owner\" | \"editor\";\n assertMayGrant(actorRole, input.siteId, targetRole);\n\n const del = await svc.from(\"site_users\").delete().eq(\"user_id\", input.userId).eq(\"site_id\", input.siteId);\n if (del.error) throw del.error;\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/auth/client.ts","../../src/auth/session.ts","../../src/auth/errors.ts","../../src/auth/types.ts","../../src/auth/roles.ts","../../src/auth/callback.ts","../../src/auth/signout.ts","../../src/auth/service.ts"],"names":["createServerClient","createClient"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * createIdentityClient — a Supabase client bound to the request's cookies.\n *\n * Built per request from arguments. A Next 16 route does:\n *\n * const store = await cookies();\n * const client = createIdentityClient({ url, anonKey, cookies: store });\n *\n * No `next` import here. The CookieStore interface accepts either shape a\n * host might hand us: @supabase/ssr's { getAll, setAll }, or Next's\n * cookies() store, which has { getAll, set } — the session cookies are\n * written through whichever exists. (Observed 2026-09-08: with only setAll\n * supported, Next's store silently wrote nothing and every sign-in bounced.)\n */\n\nimport { createServerClient } from \"@supabase/ssr\";\nimport type { SupabaseClient } from \"@supabase/supabase-js\";\nimport type { IdentityContext } from \"./types\";\n\nexport type IdentityClient = SupabaseClient;\n\ntype CookieToSet = { name: string; value: string; options?: Record<string, unknown> };\n\nexport function createIdentityClient(ctx: IdentityContext): IdentityClient {\n const store = ctx.cookies;\n return createServerClient(ctx.url, ctx.anonKey, {\n cookies: {\n getAll: () => store.getAll(),\n setAll: (list: CookieToSet[]) => {\n // Server Components can't set cookies; @supabase/ssr documents that\n // swallowing the failure there is correct — middleware / route\n // handlers refresh the session instead.\n try {\n if (typeof store.setAll === \"function\") {\n void store.setAll(list);\n } else if (typeof store.set === \"function\") {\n for (const c of list) store.set(c.name, c.value, c.options);\n }\n } catch {\n /* read-only cookie store */\n }\n },\n },\n });\n}\n","/**\n * getCurrentUser — who is signed in, verified against the auth server.\n *\n * `getUser()` round-trips to Supabase and validates the JWT; `getSession()`\n * only decodes the cookie and is not trustworthy on the server. Always this.\n */\n\nimport type { IdentityClient } from \"./client\";\nimport type { IdentityUser } from \"./types\";\n\nexport async function getCurrentUser(client: IdentityClient): Promise<IdentityUser | null> {\n const { data, error } = await client.auth.getUser();\n if (error || !data.user) return null;\n return { id: data.user.id, email: data.user.email ?? null };\n}\n","/** Typed errors so a route can map them to 401 / 403 without string matching. */\n\nexport class IdentityError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"IdentityError\";\n }\n}\n\n/** No session — send them to sign in. Maps to 401. */\nexport class UnauthenticatedError extends IdentityError {\n constructor() {\n super(\"Not signed in\");\n this.name = \"UnauthenticatedError\";\n }\n}\n\n/** Signed in, but not allowed to do this here. Maps to 403. */\nexport class ForbiddenError extends IdentityError {\n readonly siteId: string;\n readonly required: string;\n readonly actual: string | null;\n\n constructor(siteId: string, required: string, actual: string | null) {\n super(`Requires ${required} on site \"${siteId}\" (you are ${actual ?? \"not a member\"})`);\n this.name = \"ForbiddenError\";\n this.siteId = siteId;\n this.required = required;\n this.actual = actual;\n }\n}\n","/**\n * Types for the identity layer. All config arrives as arguments — never env.\n */\n\n/**\n * Either cookie-store shape a host can hand us:\n * - @supabase/ssr's CookieMethodsServer: { getAll, setAll }\n * - Next's `await cookies()` store: { getAll, set }\n * `setAll` is preferred when present; otherwise each cookie goes through `set`.\n */\nexport interface CookieStore {\n getAll(): { name: string; value: string }[] | Promise<{ name: string; value: string }[]>;\n setAll?(cookies: { name: string; value: string; options?: Record<string, unknown> }[]): void | Promise<void>;\n set?(name: string, value: string, options?: Record<string, unknown>): unknown;\n}\n\n/** Enough to read the signed-in user with the anon key + their cookies. */\nexport interface IdentityContext {\n url: string;\n anonKey: string;\n cookies: CookieStore;\n}\n\n/** Server-only. The service-role key bypasses RLS; the module checks roles in code first. */\nexport interface ServiceContext {\n url: string;\n serviceRoleKey: string;\n}\n\nexport type SiteRole = \"staff\" | \"owner\" | \"editor\";\n\nexport const ROLE_RANK: Record<SiteRole, number> = { editor: 1, owner: 2, staff: 3 };\n\nexport interface IdentityUser {\n id: string;\n email: string | null;\n}\n\nexport interface SiteMembership {\n userId: string;\n siteId: string;\n role: \"owner\" | \"editor\";\n}\n","/**\n * Role resolution and the request gate.\n *\n * Two selects on the caller's OWN rows — both permitted by RLS with the anon\n * key, so this never needs the service role. Staff wins over any site role.\n */\n\nimport type { IdentityClient } from \"./client\";\nimport { ForbiddenError, UnauthenticatedError } from \"./errors\";\nimport { getCurrentUser } from \"./session\";\nimport { ROLE_RANK, type IdentityUser, type SiteRole } from \"./types\";\n\nexport async function getSiteRole(client: IdentityClient, siteId: string, userId?: string): Promise<SiteRole | null> {\n const uid = userId ?? (await getCurrentUser(client))?.id;\n if (!uid) return null;\n\n const [staff, member] = await Promise.all([\n client.from(\"staff\").select(\"user_id\").eq(\"user_id\", uid).maybeSingle(),\n client.from(\"site_users\").select(\"role\").eq(\"user_id\", uid).eq(\"site_id\", siteId).maybeSingle(),\n ]);\n\n if (staff.error) throw staff.error;\n if (member.error) throw member.error;\n\n if (staff.data) return \"staff\";\n const role = (member.data as { role?: string } | null)?.role;\n return role === \"owner\" || role === \"editor\" ? role : null;\n}\n\nexport function roleAtLeast(actual: SiteRole | null, minimum: SiteRole): boolean {\n return actual !== null && ROLE_RANK[actual] >= ROLE_RANK[minimum];\n}\n\nexport interface RequireOptions {\n /** Default \"editor\" — any member of the site. */\n minimumRole?: SiteRole;\n}\n\n/**\n * The gate. Throws UnauthenticatedError (→ sign in) or ForbiddenError (→ 403);\n * otherwise returns the user and their effective role for this site.\n */\nexport async function requireSiteUser(\n client: IdentityClient,\n siteId: string,\n opts: RequireOptions = {},\n): Promise<{ user: IdentityUser; role: SiteRole }> {\n const user = await getCurrentUser(client);\n if (!user) throw new UnauthenticatedError();\n\n const minimum = opts.minimumRole ?? \"editor\";\n const role = await getSiteRole(client, siteId, user.id);\n if (!roleAtLeast(role, minimum)) throw new ForbiddenError(siteId, minimum, role);\n\n return { user, role: role as SiteRole };\n}\n","/**\n * handleAuthCallback — the emailed magic link lands here with ?code=…&next=…\n *\n * Exchanges the PKCE code for a session (cookies are written through the\n * adapter the client was built with) and says where to send the user. The\n * `next` parameter is only honoured when it is a plain same-origin path —\n * \"//evil.example\" and \"https://…\" fall back, which closes the open-redirect\n * a naive startsWith(\"/\") check leaves open.\n */\n\nimport type { IdentityClient } from \"./client\";\n\nexport type CallbackResult =\n | { ok: true; redirectTo: string }\n | { ok: false; reason: \"missing_code\" | \"exchange_failed\"; message?: string };\n\nexport interface CallbackOptions {\n /** Where to go when `next` is absent or unsafe. Default \"/admin\". */\n fallbackPath?: string;\n}\n\nexport function safeNextPath(next: string | null | undefined, fallback: string): string {\n if (!next) return fallback;\n // Exactly one leading slash, then not another slash or backslash.\n if (!/^\\/(?![/\\\\])/.test(next)) return fallback;\n // No scheme smuggling or CR/LF.\n if (/[\\r\\n]/.test(next) || /^\\/[^?#]*:\\/\\//.test(next)) return fallback;\n return next;\n}\n\nexport async function handleAuthCallback(\n client: IdentityClient,\n requestUrl: string | URL,\n opts: CallbackOptions = {},\n): Promise<CallbackResult> {\n const fallback = opts.fallbackPath ?? \"/admin\";\n const url = typeof requestUrl === \"string\" ? new URL(requestUrl, \"http://placeholder.invalid\") : requestUrl;\n const code = url.searchParams.get(\"code\");\n if (!code) return { ok: false, reason: \"missing_code\" };\n\n const { error } = await client.auth.exchangeCodeForSession(code);\n if (error) return { ok: false, reason: \"exchange_failed\", message: error.message };\n\n return { ok: true, redirectTo: safeNextPath(url.searchParams.get(\"next\"), fallback) };\n}\n","/** signOut — clears this browser's session. The caller redirects afterwards. */\n\nimport type { IdentityClient } from \"./client\";\n\nexport async function signOut(client: IdentityClient): Promise<void> {\n const { error } = await client.auth.signOut({ scope: \"local\" });\n if (error) throw error;\n}\n","/**\n * The only file that touches the service-role key. Invite and remove.\n *\n * Authorization is checked HERE, in code, before the privileged client does\n * anything — RLS doesn't apply to the service role, so this is the guard:\n * staff → may grant owner or editor on any site\n * owner → may grant editor on their own site only\n * editor → may not invite\n *\n * `actor` is the caller's already-resolved identity (from requireSiteUser on\n * the anon client). Its role is re-resolved with the service client so a\n * caller can't lie about it.\n */\n\nimport { createClient, type SupabaseClient } from \"@supabase/supabase-js\";\nimport { ForbiddenError, IdentityError } from \"./errors\";\nimport type { ServiceContext, SiteRole } from \"./types\";\n\nexport type ServiceClient = SupabaseClient;\n\nexport function createServiceClient(ctx: ServiceContext): ServiceClient {\n return createClient(ctx.url, ctx.serviceRoleKey, {\n auth: { persistSession: false, autoRefreshToken: false },\n });\n}\n\nexport interface InviteInput {\n email: string;\n siteId: string;\n role: \"owner\" | \"editor\";\n /** The signed-in user performing the invite. */\n actor: { id: string };\n /** Where the invite email's link lands — the site's /admin/auth/callback. */\n redirectTo: string;\n}\n\nexport interface InviteResult {\n userId: string;\n siteId: string;\n role: \"owner\" | \"editor\";\n /** false when the auth user already existed and only the membership was added. */\n invitedByEmail: boolean;\n}\n\nasync function resolveActorRole(svc: ServiceClient, actorId: string, siteId: string): Promise<SiteRole | null> {\n const staff = await svc.from(\"staff\").select(\"user_id\").eq(\"user_id\", actorId).maybeSingle();\n if (staff.error) throw staff.error;\n if (staff.data) return \"staff\";\n const m = await svc.from(\"site_users\").select(\"role\").eq(\"user_id\", actorId).eq(\"site_id\", siteId).maybeSingle();\n if (m.error) throw m.error;\n const role = (m.data as { role?: string } | null)?.role;\n return role === \"owner\" || role === \"editor\" ? role : null;\n}\n\nfunction assertMayGrant(actorRole: SiteRole | null, siteId: string, granting: \"owner\" | \"editor\"): void {\n if (actorRole === \"staff\") return;\n if (actorRole === \"owner\" && granting === \"editor\") return;\n throw new ForbiddenError(siteId, granting === \"owner\" ? \"staff\" : \"owner\", actorRole);\n}\n\nexport async function inviteUser(ctx: ServiceContext, input: InviteInput): Promise<InviteResult> {\n const svc = createServiceClient(ctx);\n const actorRole = await resolveActorRole(svc, input.actor.id, input.siteId);\n assertMayGrant(actorRole, input.siteId, input.role);\n\n const email = input.email.trim().toLowerCase();\n let userId: string | undefined;\n let invitedByEmail = false;\n\n const invite = await svc.auth.admin.inviteUserByEmail(email, {\n redirectTo: input.redirectTo,\n data: { site_id: input.siteId, role: input.role },\n });\n\n if (!invite.error) {\n userId = invite.data.user.id;\n invitedByEmail = true;\n } else if (/already|exists|registered/i.test(invite.error.message)) {\n // Existing auth user (works for another site, or is staff): just add the row.\n userId = await findUserIdByEmail(svc, email);\n if (!userId) throw new IdentityError(`Could not find existing user ${email}`);\n } else {\n throw new IdentityError(`Invite failed: ${invite.error.message}`);\n }\n\n const upsert = await svc\n .from(\"site_users\")\n .upsert({ user_id: userId, site_id: input.siteId, role: input.role, invited_by: input.actor.id }, { onConflict: \"user_id,site_id\" });\n if (upsert.error) throw upsert.error;\n\n return { userId, siteId: input.siteId, role: input.role, invitedByEmail };\n}\n\nasync function findUserIdByEmail(svc: ServiceClient, email: string): Promise<string | undefined> {\n // listUsers is paged; identity holds tens of users, not thousands.\n for (let page = 1; page <= 20; page++) {\n const { data, error } = await svc.auth.admin.listUsers({ page, perPage: 200 });\n if (error) throw error;\n const hit = data.users.find((u) => u.email?.toLowerCase() === email);\n if (hit) return hit.id;\n if (data.users.length < 200) break;\n }\n return undefined;\n}\n\nexport interface RemoveInput {\n userId: string;\n siteId: string;\n actor: { id: string };\n}\n\n/** Removes the membership only. The auth user survives (they may belong to other sites). */\nexport async function removeSiteUser(ctx: ServiceContext, input: RemoveInput): Promise<void> {\n const svc = createServiceClient(ctx);\n const actorRole = await resolveActorRole(svc, input.actor.id, input.siteId);\n\n const target = await svc.from(\"site_users\").select(\"role\").eq(\"user_id\", input.userId).eq(\"site_id\", input.siteId).maybeSingle();\n if (target.error) throw target.error;\n const targetRole = ((target.data as { role?: string } | null)?.role ?? \"editor\") as \"owner\" | \"editor\";\n assertMayGrant(actorRole, input.siteId, targetRole);\n\n const del = await svc.from(\"site_users\").delete().eq(\"user_id\", input.userId).eq(\"site_id\", input.siteId);\n if (del.error) throw del.error;\n}\n"]}
|
package/dist/auth/index.d.cts
CHANGED
|
@@ -3,7 +3,12 @@ import { SupabaseClient } from '@supabase/supabase-js';
|
|
|
3
3
|
/**
|
|
4
4
|
* Types for the identity layer. All config arrives as arguments — never env.
|
|
5
5
|
*/
|
|
6
|
-
/**
|
|
6
|
+
/**
|
|
7
|
+
* Either cookie-store shape a host can hand us:
|
|
8
|
+
* - @supabase/ssr's CookieMethodsServer: { getAll, setAll }
|
|
9
|
+
* - Next's `await cookies()` store: { getAll, set }
|
|
10
|
+
* `setAll` is preferred when present; otherwise each cookie goes through `set`.
|
|
11
|
+
*/
|
|
7
12
|
interface CookieStore {
|
|
8
13
|
getAll(): {
|
|
9
14
|
name: string;
|
|
@@ -17,6 +22,7 @@ interface CookieStore {
|
|
|
17
22
|
value: string;
|
|
18
23
|
options?: Record<string, unknown>;
|
|
19
24
|
}[]): void | Promise<void>;
|
|
25
|
+
set?(name: string, value: string, options?: Record<string, unknown>): unknown;
|
|
20
26
|
}
|
|
21
27
|
/** Enough to read the signed-in user with the anon key + their cookies. */
|
|
22
28
|
interface IdentityContext {
|
|
@@ -49,8 +55,11 @@ interface SiteMembership {
|
|
|
49
55
|
* const store = await cookies();
|
|
50
56
|
* const client = createIdentityClient({ url, anonKey, cookies: store });
|
|
51
57
|
*
|
|
52
|
-
* No `next` import here
|
|
53
|
-
*
|
|
58
|
+
* No `next` import here. The CookieStore interface accepts either shape a
|
|
59
|
+
* host might hand us: @supabase/ssr's { getAll, setAll }, or Next's
|
|
60
|
+
* cookies() store, which has { getAll, set } — the session cookies are
|
|
61
|
+
* written through whichever exists. (Observed 2026-09-08: with only setAll
|
|
62
|
+
* supported, Next's store silently wrote nothing and every sign-in bounced.)
|
|
54
63
|
*/
|
|
55
64
|
|
|
56
65
|
type IdentityClient = SupabaseClient;
|
package/dist/auth/index.d.ts
CHANGED
|
@@ -3,7 +3,12 @@ import { SupabaseClient } from '@supabase/supabase-js';
|
|
|
3
3
|
/**
|
|
4
4
|
* Types for the identity layer. All config arrives as arguments — never env.
|
|
5
5
|
*/
|
|
6
|
-
/**
|
|
6
|
+
/**
|
|
7
|
+
* Either cookie-store shape a host can hand us:
|
|
8
|
+
* - @supabase/ssr's CookieMethodsServer: { getAll, setAll }
|
|
9
|
+
* - Next's `await cookies()` store: { getAll, set }
|
|
10
|
+
* `setAll` is preferred when present; otherwise each cookie goes through `set`.
|
|
11
|
+
*/
|
|
7
12
|
interface CookieStore {
|
|
8
13
|
getAll(): {
|
|
9
14
|
name: string;
|
|
@@ -17,6 +22,7 @@ interface CookieStore {
|
|
|
17
22
|
value: string;
|
|
18
23
|
options?: Record<string, unknown>;
|
|
19
24
|
}[]): void | Promise<void>;
|
|
25
|
+
set?(name: string, value: string, options?: Record<string, unknown>): unknown;
|
|
20
26
|
}
|
|
21
27
|
/** Enough to read the signed-in user with the anon key + their cookies. */
|
|
22
28
|
interface IdentityContext {
|
|
@@ -49,8 +55,11 @@ interface SiteMembership {
|
|
|
49
55
|
* const store = await cookies();
|
|
50
56
|
* const client = createIdentityClient({ url, anonKey, cookies: store });
|
|
51
57
|
*
|
|
52
|
-
* No `next` import here
|
|
53
|
-
*
|
|
58
|
+
* No `next` import here. The CookieStore interface accepts either shape a
|
|
59
|
+
* host might hand us: @supabase/ssr's { getAll, setAll }, or Next's
|
|
60
|
+
* cookies() store, which has { getAll, set } — the session cookies are
|
|
61
|
+
* written through whichever exists. (Observed 2026-09-08: with only setAll
|
|
62
|
+
* supported, Next's store silently wrote nothing and every sign-in bounced.)
|
|
54
63
|
*/
|
|
55
64
|
|
|
56
65
|
type IdentityClient = SupabaseClient;
|
package/dist/auth/index.js
CHANGED
|
@@ -4,12 +4,17 @@ import { createClient } from '@supabase/supabase-js';
|
|
|
4
4
|
|
|
5
5
|
// src/auth/index.ts
|
|
6
6
|
function createIdentityClient(ctx) {
|
|
7
|
+
const store = ctx.cookies;
|
|
7
8
|
return createServerClient(ctx.url, ctx.anonKey, {
|
|
8
9
|
cookies: {
|
|
9
|
-
getAll: () =>
|
|
10
|
+
getAll: () => store.getAll(),
|
|
10
11
|
setAll: (list) => {
|
|
11
12
|
try {
|
|
12
|
-
|
|
13
|
+
if (typeof store.setAll === "function") {
|
|
14
|
+
void store.setAll(list);
|
|
15
|
+
} else if (typeof store.set === "function") {
|
|
16
|
+
for (const c of list) store.set(c.name, c.value, c.options);
|
|
17
|
+
}
|
|
13
18
|
} catch {
|
|
14
19
|
}
|
|
15
20
|
}
|
package/dist/auth/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/auth/client.ts","../../src/auth/session.ts","../../src/auth/errors.ts","../../src/auth/types.ts","../../src/auth/roles.ts","../../src/auth/callback.ts","../../src/auth/signout.ts","../../src/auth/service.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * createIdentityClient — a Supabase client bound to the request's cookies.\n *\n * Built per request from arguments. A Next 16 route does:\n *\n * const store = await cookies();\n * const client = createIdentityClient({ url, anonKey, cookies: store });\n *\n * No `next` import here: the CookieStore interface is exactly what\n * `cookies()` returns and exactly what @supabase/ssr accepts.\n */\n\nimport { createServerClient } from \"@supabase/ssr\";\nimport type { SupabaseClient } from \"@supabase/supabase-js\";\nimport type { IdentityContext } from \"./types\";\n\nexport type IdentityClient = SupabaseClient;\n\nexport function createIdentityClient(ctx: IdentityContext): IdentityClient {\n return createServerClient(ctx.url, ctx.anonKey, {\n cookies: {\n getAll: () => ctx.cookies.getAll(),\n setAll: (list) => {\n // Server Components can't set cookies; @supabase/ssr documents that\n // swallowing the failure there is correct because middleware/route\n // handlers refresh the session instead.\n try {\n void ctx.cookies.setAll?.(list);\n } catch {\n /* read-only cookie store */\n }\n },\n },\n });\n}\n","/**\n * getCurrentUser — who is signed in, verified against the auth server.\n *\n * `getUser()` round-trips to Supabase and validates the JWT; `getSession()`\n * only decodes the cookie and is not trustworthy on the server. Always this.\n */\n\nimport type { IdentityClient } from \"./client\";\nimport type { IdentityUser } from \"./types\";\n\nexport async function getCurrentUser(client: IdentityClient): Promise<IdentityUser | null> {\n const { data, error } = await client.auth.getUser();\n if (error || !data.user) return null;\n return { id: data.user.id, email: data.user.email ?? null };\n}\n","/** Typed errors so a route can map them to 401 / 403 without string matching. */\n\nexport class IdentityError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"IdentityError\";\n }\n}\n\n/** No session — send them to sign in. Maps to 401. */\nexport class UnauthenticatedError extends IdentityError {\n constructor() {\n super(\"Not signed in\");\n this.name = \"UnauthenticatedError\";\n }\n}\n\n/** Signed in, but not allowed to do this here. Maps to 403. */\nexport class ForbiddenError extends IdentityError {\n readonly siteId: string;\n readonly required: string;\n readonly actual: string | null;\n\n constructor(siteId: string, required: string, actual: string | null) {\n super(`Requires ${required} on site \"${siteId}\" (you are ${actual ?? \"not a member\"})`);\n this.name = \"ForbiddenError\";\n this.siteId = siteId;\n this.required = required;\n this.actual = actual;\n }\n}\n","/**\n * Types for the identity layer. All config arrives as arguments — never env.\n */\n\n/** Matches @supabase/ssr's CookieMethodsServer, so `next/headers` cookies() plugs in. */\nexport interface CookieStore {\n getAll(): { name: string; value: string }[] | Promise<{ name: string; value: string }[]>;\n setAll?(cookies: { name: string; value: string; options?: Record<string, unknown> }[]): void | Promise<void>;\n}\n\n/** Enough to read the signed-in user with the anon key + their cookies. */\nexport interface IdentityContext {\n url: string;\n anonKey: string;\n cookies: CookieStore;\n}\n\n/** Server-only. The service-role key bypasses RLS; the module checks roles in code first. */\nexport interface ServiceContext {\n url: string;\n serviceRoleKey: string;\n}\n\nexport type SiteRole = \"staff\" | \"owner\" | \"editor\";\n\nexport const ROLE_RANK: Record<SiteRole, number> = { editor: 1, owner: 2, staff: 3 };\n\nexport interface IdentityUser {\n id: string;\n email: string | null;\n}\n\nexport interface SiteMembership {\n userId: string;\n siteId: string;\n role: \"owner\" | \"editor\";\n}\n","/**\n * Role resolution and the request gate.\n *\n * Two selects on the caller's OWN rows — both permitted by RLS with the anon\n * key, so this never needs the service role. Staff wins over any site role.\n */\n\nimport type { IdentityClient } from \"./client\";\nimport { ForbiddenError, UnauthenticatedError } from \"./errors\";\nimport { getCurrentUser } from \"./session\";\nimport { ROLE_RANK, type IdentityUser, type SiteRole } from \"./types\";\n\nexport async function getSiteRole(client: IdentityClient, siteId: string, userId?: string): Promise<SiteRole | null> {\n const uid = userId ?? (await getCurrentUser(client))?.id;\n if (!uid) return null;\n\n const [staff, member] = await Promise.all([\n client.from(\"staff\").select(\"user_id\").eq(\"user_id\", uid).maybeSingle(),\n client.from(\"site_users\").select(\"role\").eq(\"user_id\", uid).eq(\"site_id\", siteId).maybeSingle(),\n ]);\n\n if (staff.error) throw staff.error;\n if (member.error) throw member.error;\n\n if (staff.data) return \"staff\";\n const role = (member.data as { role?: string } | null)?.role;\n return role === \"owner\" || role === \"editor\" ? role : null;\n}\n\nexport function roleAtLeast(actual: SiteRole | null, minimum: SiteRole): boolean {\n return actual !== null && ROLE_RANK[actual] >= ROLE_RANK[minimum];\n}\n\nexport interface RequireOptions {\n /** Default \"editor\" — any member of the site. */\n minimumRole?: SiteRole;\n}\n\n/**\n * The gate. Throws UnauthenticatedError (→ sign in) or ForbiddenError (→ 403);\n * otherwise returns the user and their effective role for this site.\n */\nexport async function requireSiteUser(\n client: IdentityClient,\n siteId: string,\n opts: RequireOptions = {},\n): Promise<{ user: IdentityUser; role: SiteRole }> {\n const user = await getCurrentUser(client);\n if (!user) throw new UnauthenticatedError();\n\n const minimum = opts.minimumRole ?? \"editor\";\n const role = await getSiteRole(client, siteId, user.id);\n if (!roleAtLeast(role, minimum)) throw new ForbiddenError(siteId, minimum, role);\n\n return { user, role: role as SiteRole };\n}\n","/**\n * handleAuthCallback — the emailed magic link lands here with ?code=…&next=…\n *\n * Exchanges the PKCE code for a session (cookies are written through the\n * adapter the client was built with) and says where to send the user. The\n * `next` parameter is only honoured when it is a plain same-origin path —\n * \"//evil.example\" and \"https://…\" fall back, which closes the open-redirect\n * a naive startsWith(\"/\") check leaves open.\n */\n\nimport type { IdentityClient } from \"./client\";\n\nexport type CallbackResult =\n | { ok: true; redirectTo: string }\n | { ok: false; reason: \"missing_code\" | \"exchange_failed\"; message?: string };\n\nexport interface CallbackOptions {\n /** Where to go when `next` is absent or unsafe. Default \"/admin\". */\n fallbackPath?: string;\n}\n\nexport function safeNextPath(next: string | null | undefined, fallback: string): string {\n if (!next) return fallback;\n // Exactly one leading slash, then not another slash or backslash.\n if (!/^\\/(?![/\\\\])/.test(next)) return fallback;\n // No scheme smuggling or CR/LF.\n if (/[\\r\\n]/.test(next) || /^\\/[^?#]*:\\/\\//.test(next)) return fallback;\n return next;\n}\n\nexport async function handleAuthCallback(\n client: IdentityClient,\n requestUrl: string | URL,\n opts: CallbackOptions = {},\n): Promise<CallbackResult> {\n const fallback = opts.fallbackPath ?? \"/admin\";\n const url = typeof requestUrl === \"string\" ? new URL(requestUrl, \"http://placeholder.invalid\") : requestUrl;\n const code = url.searchParams.get(\"code\");\n if (!code) return { ok: false, reason: \"missing_code\" };\n\n const { error } = await client.auth.exchangeCodeForSession(code);\n if (error) return { ok: false, reason: \"exchange_failed\", message: error.message };\n\n return { ok: true, redirectTo: safeNextPath(url.searchParams.get(\"next\"), fallback) };\n}\n","/** signOut — clears this browser's session. The caller redirects afterwards. */\n\nimport type { IdentityClient } from \"./client\";\n\nexport async function signOut(client: IdentityClient): Promise<void> {\n const { error } = await client.auth.signOut({ scope: \"local\" });\n if (error) throw error;\n}\n","/**\n * The only file that touches the service-role key. Invite and remove.\n *\n * Authorization is checked HERE, in code, before the privileged client does\n * anything — RLS doesn't apply to the service role, so this is the guard:\n * staff → may grant owner or editor on any site\n * owner → may grant editor on their own site only\n * editor → may not invite\n *\n * `actor` is the caller's already-resolved identity (from requireSiteUser on\n * the anon client). Its role is re-resolved with the service client so a\n * caller can't lie about it.\n */\n\nimport { createClient, type SupabaseClient } from \"@supabase/supabase-js\";\nimport { ForbiddenError, IdentityError } from \"./errors\";\nimport type { ServiceContext, SiteRole } from \"./types\";\n\nexport type ServiceClient = SupabaseClient;\n\nexport function createServiceClient(ctx: ServiceContext): ServiceClient {\n return createClient(ctx.url, ctx.serviceRoleKey, {\n auth: { persistSession: false, autoRefreshToken: false },\n });\n}\n\nexport interface InviteInput {\n email: string;\n siteId: string;\n role: \"owner\" | \"editor\";\n /** The signed-in user performing the invite. */\n actor: { id: string };\n /** Where the invite email's link lands — the site's /admin/auth/callback. */\n redirectTo: string;\n}\n\nexport interface InviteResult {\n userId: string;\n siteId: string;\n role: \"owner\" | \"editor\";\n /** false when the auth user already existed and only the membership was added. */\n invitedByEmail: boolean;\n}\n\nasync function resolveActorRole(svc: ServiceClient, actorId: string, siteId: string): Promise<SiteRole | null> {\n const staff = await svc.from(\"staff\").select(\"user_id\").eq(\"user_id\", actorId).maybeSingle();\n if (staff.error) throw staff.error;\n if (staff.data) return \"staff\";\n const m = await svc.from(\"site_users\").select(\"role\").eq(\"user_id\", actorId).eq(\"site_id\", siteId).maybeSingle();\n if (m.error) throw m.error;\n const role = (m.data as { role?: string } | null)?.role;\n return role === \"owner\" || role === \"editor\" ? role : null;\n}\n\nfunction assertMayGrant(actorRole: SiteRole | null, siteId: string, granting: \"owner\" | \"editor\"): void {\n if (actorRole === \"staff\") return;\n if (actorRole === \"owner\" && granting === \"editor\") return;\n throw new ForbiddenError(siteId, granting === \"owner\" ? \"staff\" : \"owner\", actorRole);\n}\n\nexport async function inviteUser(ctx: ServiceContext, input: InviteInput): Promise<InviteResult> {\n const svc = createServiceClient(ctx);\n const actorRole = await resolveActorRole(svc, input.actor.id, input.siteId);\n assertMayGrant(actorRole, input.siteId, input.role);\n\n const email = input.email.trim().toLowerCase();\n let userId: string | undefined;\n let invitedByEmail = false;\n\n const invite = await svc.auth.admin.inviteUserByEmail(email, {\n redirectTo: input.redirectTo,\n data: { site_id: input.siteId, role: input.role },\n });\n\n if (!invite.error) {\n userId = invite.data.user.id;\n invitedByEmail = true;\n } else if (/already|exists|registered/i.test(invite.error.message)) {\n // Existing auth user (works for another site, or is staff): just add the row.\n userId = await findUserIdByEmail(svc, email);\n if (!userId) throw new IdentityError(`Could not find existing user ${email}`);\n } else {\n throw new IdentityError(`Invite failed: ${invite.error.message}`);\n }\n\n const upsert = await svc\n .from(\"site_users\")\n .upsert({ user_id: userId, site_id: input.siteId, role: input.role, invited_by: input.actor.id }, { onConflict: \"user_id,site_id\" });\n if (upsert.error) throw upsert.error;\n\n return { userId, siteId: input.siteId, role: input.role, invitedByEmail };\n}\n\nasync function findUserIdByEmail(svc: ServiceClient, email: string): Promise<string | undefined> {\n // listUsers is paged; identity holds tens of users, not thousands.\n for (let page = 1; page <= 20; page++) {\n const { data, error } = await svc.auth.admin.listUsers({ page, perPage: 200 });\n if (error) throw error;\n const hit = data.users.find((u) => u.email?.toLowerCase() === email);\n if (hit) return hit.id;\n if (data.users.length < 200) break;\n }\n return undefined;\n}\n\nexport interface RemoveInput {\n userId: string;\n siteId: string;\n actor: { id: string };\n}\n\n/** Removes the membership only. The auth user survives (they may belong to other sites). */\nexport async function removeSiteUser(ctx: ServiceContext, input: RemoveInput): Promise<void> {\n const svc = createServiceClient(ctx);\n const actorRole = await resolveActorRole(svc, input.actor.id, input.siteId);\n\n const target = await svc.from(\"site_users\").select(\"role\").eq(\"user_id\", input.userId).eq(\"site_id\", input.siteId).maybeSingle();\n if (target.error) throw target.error;\n const targetRole = ((target.data as { role?: string } | null)?.role ?? \"editor\") as \"owner\" | \"editor\";\n assertMayGrant(actorRole, input.siteId, targetRole);\n\n const del = await svc.from(\"site_users\").delete().eq(\"user_id\", input.userId).eq(\"site_id\", input.siteId);\n if (del.error) throw del.error;\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/auth/client.ts","../../src/auth/session.ts","../../src/auth/errors.ts","../../src/auth/types.ts","../../src/auth/roles.ts","../../src/auth/callback.ts","../../src/auth/signout.ts","../../src/auth/service.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * createIdentityClient — a Supabase client bound to the request's cookies.\n *\n * Built per request from arguments. A Next 16 route does:\n *\n * const store = await cookies();\n * const client = createIdentityClient({ url, anonKey, cookies: store });\n *\n * No `next` import here. The CookieStore interface accepts either shape a\n * host might hand us: @supabase/ssr's { getAll, setAll }, or Next's\n * cookies() store, which has { getAll, set } — the session cookies are\n * written through whichever exists. (Observed 2026-09-08: with only setAll\n * supported, Next's store silently wrote nothing and every sign-in bounced.)\n */\n\nimport { createServerClient } from \"@supabase/ssr\";\nimport type { SupabaseClient } from \"@supabase/supabase-js\";\nimport type { IdentityContext } from \"./types\";\n\nexport type IdentityClient = SupabaseClient;\n\ntype CookieToSet = { name: string; value: string; options?: Record<string, unknown> };\n\nexport function createIdentityClient(ctx: IdentityContext): IdentityClient {\n const store = ctx.cookies;\n return createServerClient(ctx.url, ctx.anonKey, {\n cookies: {\n getAll: () => store.getAll(),\n setAll: (list: CookieToSet[]) => {\n // Server Components can't set cookies; @supabase/ssr documents that\n // swallowing the failure there is correct — middleware / route\n // handlers refresh the session instead.\n try {\n if (typeof store.setAll === \"function\") {\n void store.setAll(list);\n } else if (typeof store.set === \"function\") {\n for (const c of list) store.set(c.name, c.value, c.options);\n }\n } catch {\n /* read-only cookie store */\n }\n },\n },\n });\n}\n","/**\n * getCurrentUser — who is signed in, verified against the auth server.\n *\n * `getUser()` round-trips to Supabase and validates the JWT; `getSession()`\n * only decodes the cookie and is not trustworthy on the server. Always this.\n */\n\nimport type { IdentityClient } from \"./client\";\nimport type { IdentityUser } from \"./types\";\n\nexport async function getCurrentUser(client: IdentityClient): Promise<IdentityUser | null> {\n const { data, error } = await client.auth.getUser();\n if (error || !data.user) return null;\n return { id: data.user.id, email: data.user.email ?? null };\n}\n","/** Typed errors so a route can map them to 401 / 403 without string matching. */\n\nexport class IdentityError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"IdentityError\";\n }\n}\n\n/** No session — send them to sign in. Maps to 401. */\nexport class UnauthenticatedError extends IdentityError {\n constructor() {\n super(\"Not signed in\");\n this.name = \"UnauthenticatedError\";\n }\n}\n\n/** Signed in, but not allowed to do this here. Maps to 403. */\nexport class ForbiddenError extends IdentityError {\n readonly siteId: string;\n readonly required: string;\n readonly actual: string | null;\n\n constructor(siteId: string, required: string, actual: string | null) {\n super(`Requires ${required} on site \"${siteId}\" (you are ${actual ?? \"not a member\"})`);\n this.name = \"ForbiddenError\";\n this.siteId = siteId;\n this.required = required;\n this.actual = actual;\n }\n}\n","/**\n * Types for the identity layer. All config arrives as arguments — never env.\n */\n\n/**\n * Either cookie-store shape a host can hand us:\n * - @supabase/ssr's CookieMethodsServer: { getAll, setAll }\n * - Next's `await cookies()` store: { getAll, set }\n * `setAll` is preferred when present; otherwise each cookie goes through `set`.\n */\nexport interface CookieStore {\n getAll(): { name: string; value: string }[] | Promise<{ name: string; value: string }[]>;\n setAll?(cookies: { name: string; value: string; options?: Record<string, unknown> }[]): void | Promise<void>;\n set?(name: string, value: string, options?: Record<string, unknown>): unknown;\n}\n\n/** Enough to read the signed-in user with the anon key + their cookies. */\nexport interface IdentityContext {\n url: string;\n anonKey: string;\n cookies: CookieStore;\n}\n\n/** Server-only. The service-role key bypasses RLS; the module checks roles in code first. */\nexport interface ServiceContext {\n url: string;\n serviceRoleKey: string;\n}\n\nexport type SiteRole = \"staff\" | \"owner\" | \"editor\";\n\nexport const ROLE_RANK: Record<SiteRole, number> = { editor: 1, owner: 2, staff: 3 };\n\nexport interface IdentityUser {\n id: string;\n email: string | null;\n}\n\nexport interface SiteMembership {\n userId: string;\n siteId: string;\n role: \"owner\" | \"editor\";\n}\n","/**\n * Role resolution and the request gate.\n *\n * Two selects on the caller's OWN rows — both permitted by RLS with the anon\n * key, so this never needs the service role. Staff wins over any site role.\n */\n\nimport type { IdentityClient } from \"./client\";\nimport { ForbiddenError, UnauthenticatedError } from \"./errors\";\nimport { getCurrentUser } from \"./session\";\nimport { ROLE_RANK, type IdentityUser, type SiteRole } from \"./types\";\n\nexport async function getSiteRole(client: IdentityClient, siteId: string, userId?: string): Promise<SiteRole | null> {\n const uid = userId ?? (await getCurrentUser(client))?.id;\n if (!uid) return null;\n\n const [staff, member] = await Promise.all([\n client.from(\"staff\").select(\"user_id\").eq(\"user_id\", uid).maybeSingle(),\n client.from(\"site_users\").select(\"role\").eq(\"user_id\", uid).eq(\"site_id\", siteId).maybeSingle(),\n ]);\n\n if (staff.error) throw staff.error;\n if (member.error) throw member.error;\n\n if (staff.data) return \"staff\";\n const role = (member.data as { role?: string } | null)?.role;\n return role === \"owner\" || role === \"editor\" ? role : null;\n}\n\nexport function roleAtLeast(actual: SiteRole | null, minimum: SiteRole): boolean {\n return actual !== null && ROLE_RANK[actual] >= ROLE_RANK[minimum];\n}\n\nexport interface RequireOptions {\n /** Default \"editor\" — any member of the site. */\n minimumRole?: SiteRole;\n}\n\n/**\n * The gate. Throws UnauthenticatedError (→ sign in) or ForbiddenError (→ 403);\n * otherwise returns the user and their effective role for this site.\n */\nexport async function requireSiteUser(\n client: IdentityClient,\n siteId: string,\n opts: RequireOptions = {},\n): Promise<{ user: IdentityUser; role: SiteRole }> {\n const user = await getCurrentUser(client);\n if (!user) throw new UnauthenticatedError();\n\n const minimum = opts.minimumRole ?? \"editor\";\n const role = await getSiteRole(client, siteId, user.id);\n if (!roleAtLeast(role, minimum)) throw new ForbiddenError(siteId, minimum, role);\n\n return { user, role: role as SiteRole };\n}\n","/**\n * handleAuthCallback — the emailed magic link lands here with ?code=…&next=…\n *\n * Exchanges the PKCE code for a session (cookies are written through the\n * adapter the client was built with) and says where to send the user. The\n * `next` parameter is only honoured when it is a plain same-origin path —\n * \"//evil.example\" and \"https://…\" fall back, which closes the open-redirect\n * a naive startsWith(\"/\") check leaves open.\n */\n\nimport type { IdentityClient } from \"./client\";\n\nexport type CallbackResult =\n | { ok: true; redirectTo: string }\n | { ok: false; reason: \"missing_code\" | \"exchange_failed\"; message?: string };\n\nexport interface CallbackOptions {\n /** Where to go when `next` is absent or unsafe. Default \"/admin\". */\n fallbackPath?: string;\n}\n\nexport function safeNextPath(next: string | null | undefined, fallback: string): string {\n if (!next) return fallback;\n // Exactly one leading slash, then not another slash or backslash.\n if (!/^\\/(?![/\\\\])/.test(next)) return fallback;\n // No scheme smuggling or CR/LF.\n if (/[\\r\\n]/.test(next) || /^\\/[^?#]*:\\/\\//.test(next)) return fallback;\n return next;\n}\n\nexport async function handleAuthCallback(\n client: IdentityClient,\n requestUrl: string | URL,\n opts: CallbackOptions = {},\n): Promise<CallbackResult> {\n const fallback = opts.fallbackPath ?? \"/admin\";\n const url = typeof requestUrl === \"string\" ? new URL(requestUrl, \"http://placeholder.invalid\") : requestUrl;\n const code = url.searchParams.get(\"code\");\n if (!code) return { ok: false, reason: \"missing_code\" };\n\n const { error } = await client.auth.exchangeCodeForSession(code);\n if (error) return { ok: false, reason: \"exchange_failed\", message: error.message };\n\n return { ok: true, redirectTo: safeNextPath(url.searchParams.get(\"next\"), fallback) };\n}\n","/** signOut — clears this browser's session. The caller redirects afterwards. */\n\nimport type { IdentityClient } from \"./client\";\n\nexport async function signOut(client: IdentityClient): Promise<void> {\n const { error } = await client.auth.signOut({ scope: \"local\" });\n if (error) throw error;\n}\n","/**\n * The only file that touches the service-role key. Invite and remove.\n *\n * Authorization is checked HERE, in code, before the privileged client does\n * anything — RLS doesn't apply to the service role, so this is the guard:\n * staff → may grant owner or editor on any site\n * owner → may grant editor on their own site only\n * editor → may not invite\n *\n * `actor` is the caller's already-resolved identity (from requireSiteUser on\n * the anon client). Its role is re-resolved with the service client so a\n * caller can't lie about it.\n */\n\nimport { createClient, type SupabaseClient } from \"@supabase/supabase-js\";\nimport { ForbiddenError, IdentityError } from \"./errors\";\nimport type { ServiceContext, SiteRole } from \"./types\";\n\nexport type ServiceClient = SupabaseClient;\n\nexport function createServiceClient(ctx: ServiceContext): ServiceClient {\n return createClient(ctx.url, ctx.serviceRoleKey, {\n auth: { persistSession: false, autoRefreshToken: false },\n });\n}\n\nexport interface InviteInput {\n email: string;\n siteId: string;\n role: \"owner\" | \"editor\";\n /** The signed-in user performing the invite. */\n actor: { id: string };\n /** Where the invite email's link lands — the site's /admin/auth/callback. */\n redirectTo: string;\n}\n\nexport interface InviteResult {\n userId: string;\n siteId: string;\n role: \"owner\" | \"editor\";\n /** false when the auth user already existed and only the membership was added. */\n invitedByEmail: boolean;\n}\n\nasync function resolveActorRole(svc: ServiceClient, actorId: string, siteId: string): Promise<SiteRole | null> {\n const staff = await svc.from(\"staff\").select(\"user_id\").eq(\"user_id\", actorId).maybeSingle();\n if (staff.error) throw staff.error;\n if (staff.data) return \"staff\";\n const m = await svc.from(\"site_users\").select(\"role\").eq(\"user_id\", actorId).eq(\"site_id\", siteId).maybeSingle();\n if (m.error) throw m.error;\n const role = (m.data as { role?: string } | null)?.role;\n return role === \"owner\" || role === \"editor\" ? role : null;\n}\n\nfunction assertMayGrant(actorRole: SiteRole | null, siteId: string, granting: \"owner\" | \"editor\"): void {\n if (actorRole === \"staff\") return;\n if (actorRole === \"owner\" && granting === \"editor\") return;\n throw new ForbiddenError(siteId, granting === \"owner\" ? \"staff\" : \"owner\", actorRole);\n}\n\nexport async function inviteUser(ctx: ServiceContext, input: InviteInput): Promise<InviteResult> {\n const svc = createServiceClient(ctx);\n const actorRole = await resolveActorRole(svc, input.actor.id, input.siteId);\n assertMayGrant(actorRole, input.siteId, input.role);\n\n const email = input.email.trim().toLowerCase();\n let userId: string | undefined;\n let invitedByEmail = false;\n\n const invite = await svc.auth.admin.inviteUserByEmail(email, {\n redirectTo: input.redirectTo,\n data: { site_id: input.siteId, role: input.role },\n });\n\n if (!invite.error) {\n userId = invite.data.user.id;\n invitedByEmail = true;\n } else if (/already|exists|registered/i.test(invite.error.message)) {\n // Existing auth user (works for another site, or is staff): just add the row.\n userId = await findUserIdByEmail(svc, email);\n if (!userId) throw new IdentityError(`Could not find existing user ${email}`);\n } else {\n throw new IdentityError(`Invite failed: ${invite.error.message}`);\n }\n\n const upsert = await svc\n .from(\"site_users\")\n .upsert({ user_id: userId, site_id: input.siteId, role: input.role, invited_by: input.actor.id }, { onConflict: \"user_id,site_id\" });\n if (upsert.error) throw upsert.error;\n\n return { userId, siteId: input.siteId, role: input.role, invitedByEmail };\n}\n\nasync function findUserIdByEmail(svc: ServiceClient, email: string): Promise<string | undefined> {\n // listUsers is paged; identity holds tens of users, not thousands.\n for (let page = 1; page <= 20; page++) {\n const { data, error } = await svc.auth.admin.listUsers({ page, perPage: 200 });\n if (error) throw error;\n const hit = data.users.find((u) => u.email?.toLowerCase() === email);\n if (hit) return hit.id;\n if (data.users.length < 200) break;\n }\n return undefined;\n}\n\nexport interface RemoveInput {\n userId: string;\n siteId: string;\n actor: { id: string };\n}\n\n/** Removes the membership only. The auth user survives (they may belong to other sites). */\nexport async function removeSiteUser(ctx: ServiceContext, input: RemoveInput): Promise<void> {\n const svc = createServiceClient(ctx);\n const actorRole = await resolveActorRole(svc, input.actor.id, input.siteId);\n\n const target = await svc.from(\"site_users\").select(\"role\").eq(\"user_id\", input.userId).eq(\"site_id\", input.siteId).maybeSingle();\n if (target.error) throw target.error;\n const targetRole = ((target.data as { role?: string } | null)?.role ?? \"editor\") as \"owner\" | \"editor\";\n assertMayGrant(actorRole, input.siteId, targetRole);\n\n const del = await svc.from(\"site_users\").delete().eq(\"user_id\", input.userId).eq(\"site_id\", input.siteId);\n if (del.error) throw del.error;\n}\n"]}
|
package/dist/forms-ui/index.cjs
CHANGED
|
@@ -480,7 +480,7 @@ function toFrontmatter(specs, state, hiddenValues) {
|
|
|
480
480
|
}
|
|
481
481
|
return { frontmatter, files };
|
|
482
482
|
}
|
|
483
|
-
function ContentForm({ entry, initialValues = {}, initialBody = "", onSubmit, onCancel, submitLabel = "Save", className }) {
|
|
483
|
+
function ContentForm({ entry, initialValues = {}, initialBody = "", onSubmit, onCancel, submitLabel = "Save", className, showBody = true, bodyLabel = "Content" }) {
|
|
484
484
|
const formId = react.useId();
|
|
485
485
|
const specs = react.useMemo(() => walkSchema(entry.schema, entry), [entry]);
|
|
486
486
|
const visible = react.useMemo(() => specs.filter((s) => s.role !== "hidden" && s.role !== "body"), [specs]);
|
|
@@ -556,9 +556,9 @@ function ContentForm({ entry, initialValues = {}, initialBody = "", onSubmit, on
|
|
|
556
556
|
s.name
|
|
557
557
|
))
|
|
558
558
|
] }),
|
|
559
|
-
bodySpec && /* @__PURE__ */ jsxRuntime.jsxs("fieldset", { children: [
|
|
560
|
-
/* @__PURE__ */ jsxRuntime.jsx("legend", { children: bodySpec
|
|
561
|
-
/* @__PURE__ */ jsxRuntime.jsx("label", { className: "realiiz-form__label", htmlFor: `${formId}-body`, children: bodySpec
|
|
559
|
+
(showBody || bodySpec) && /* @__PURE__ */ jsxRuntime.jsxs("fieldset", { children: [
|
|
560
|
+
/* @__PURE__ */ jsxRuntime.jsx("legend", { children: bodySpec?.label ?? bodyLabel }),
|
|
561
|
+
/* @__PURE__ */ jsxRuntime.jsx("label", { className: "realiiz-form__label", htmlFor: `${formId}-body`, children: bodySpec?.label ?? bodyLabel }),
|
|
562
562
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
563
563
|
"textarea",
|
|
564
564
|
{
|
|
@@ -566,7 +566,7 @@ function ContentForm({ entry, initialValues = {}, initialBody = "", onSubmit, on
|
|
|
566
566
|
name: "body",
|
|
567
567
|
className: "realiiz-form__control realiiz-form__control--body",
|
|
568
568
|
value: body,
|
|
569
|
-
placeholder: bodySpec.
|
|
569
|
+
placeholder: bodySpec?.placeholder ?? "Write in Markdown. Headings with ##, links as [text](url).",
|
|
570
570
|
onChange: (e) => setBody(e.target.value)
|
|
571
571
|
}
|
|
572
572
|
)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/forms/internals.ts","../../src/forms/checks.ts","../../src/forms/unwrap.ts","../../src/forms/inputs.ts","../../src/forms/labels.ts","../../src/forms/walk.ts","../../src/forms-ui/controls.tsx","../../src/forms-ui/Field.tsx","../../src/forms-ui/issues.ts","../../src/forms-ui/styles.ts","../../src/forms-ui/values.ts","../../src/forms-ui/ContentForm.tsx"],"names":["jsx","jsxs","Fragment","useId","useMemo","useState","useEffect","useCallback"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * The slice of zod 4's `_zod.def` surface this module reads, typed loosely.\n *\n * Tested against zod 4.5.4. These are semi-public internals (the v4 \"core\"\n * shape), stable across 4.x so far but not covered by semver promises — hence\n * the peer-dep range and the fixture tests. No zod value is imported here.\n */\n\nexport interface ZodCheckDef {\n check?: string;\n format?: string;\n minimum?: number;\n maximum?: number;\n length?: number;\n value?: number;\n inclusive?: boolean;\n pattern?: RegExp;\n error?: string | ((issue: unknown) => string | { message?: string } | undefined);\n}\n\nexport interface ZodDef {\n type: string;\n shape?: Record<string, ZodNode>;\n innerType?: ZodNode;\n in?: ZodNode;\n out?: ZodNode;\n element?: ZodNode;\n entries?: Record<string, string | number>;\n checks?: Array<{ _zod: { def: ZodCheckDef } }>;\n coerce?: boolean;\n}\n\nexport interface ZodNode {\n _zod: { def: ZodDef };\n description?: string;\n}\n\n/** Narrow an unknown schema to the internal node shape, or throw a clear error. */\nexport function asNode(schema: unknown, what = \"schema\"): ZodNode {\n const n = schema as Partial<ZodNode> | null;\n if (!n || typeof n !== \"object\" || !n._zod?.def?.type) {\n throw new TypeError(`walkSchema: ${what} is not a zod 4 schema`);\n }\n return n as ZodNode;\n}\n\n/** Resolve a check's error to a plain string, if it has one. */\nexport function checkMessage(def: ZodCheckDef): string | undefined {\n const e = def.error;\n if (!e) return undefined;\n if (typeof e === \"string\") return e;\n const out = e({});\n if (typeof out === \"string\") return out;\n return out?.message;\n}\n","/**\n * checks[] → constraints + messages.\n *\n * Messages are keyed PER CHECK. The spike's walker wrote `out.message = …` on\n * every check, so a field with min and max kept only the max message and\n * showed the wrong error for a too-short value. Here each check kind gets its\n * own key; a renderer maps a failed issue to its message by that key.\n *\n * Unknown check kinds (e.g. a per-field `.refine`, which is `custom`) are kept\n * as a message when they carry one and otherwise skipped — never fatal.\n */\n\nimport type { Constraints, Messages } from \"./types\";\nimport type { ZodNode } from \"./internals\";\nimport { checkMessage } from \"./internals\";\n\nexport function readChecks(node: ZodNode): { constraints: Constraints; messages: Messages } {\n const constraints: Constraints = {};\n const messages: Messages = {};\n\n for (const c of node._zod.def.checks ?? []) {\n const d = c._zod.def;\n const msg = checkMessage(d);\n let key: string | undefined;\n\n switch (d.check) {\n case \"min_length\":\n constraints.min = d.minimum;\n key = \"min_length\";\n break;\n case \"max_length\":\n constraints.max = d.maximum;\n key = \"max_length\";\n break;\n case \"length_equals\":\n constraints.exact = d.length;\n key = \"length_equals\";\n break;\n case \"greater_than\":\n constraints.min = d.inclusive ? d.value : (d.value ?? 0) + 1;\n key = \"greater_than\";\n break;\n case \"less_than\":\n constraints.max = d.inclusive ? d.value : (d.value ?? 0) - 1;\n key = \"less_than\";\n break;\n case \"string_format\":\n if (d.format === \"regex\" && d.pattern) {\n constraints.pattern = d.pattern.source;\n constraints.format = \"regex\";\n key = \"regex\";\n } else if (d.format === \"url\" || d.format === \"email\") {\n constraints.format = d.format;\n key = d.format;\n } else {\n key = d.format;\n }\n break;\n case \"number_format\":\n if (d.format === \"safeint\" || d.format === \"int32\") {\n constraints.format = \"int\";\n key = \"int\";\n }\n break;\n case \"custom\":\n key = \"custom\";\n break;\n default:\n key = d.check;\n }\n\n if (key && msg) messages[key] = msg;\n }\n\n return { constraints, messages };\n}\n","/**\n * Peel wrapper types off a field until the real type is reached.\n *\n * optional / nullable / default → the field is not required.\n * pipe → follow `out` (older 4.x wrapped z.coerce.date in a pipe; 4.5 marks\n * the date itself with `coerce: true` — both land on the same node here).\n */\n\nimport type { ZodNode } from \"./internals\";\n\nexport interface Unwrapped {\n node: ZodNode;\n type: string;\n required: boolean;\n}\n\nexport function unwrap(start: ZodNode): Unwrapped {\n let node = start;\n let required = true;\n // Bounded so a malformed schema can't loop forever.\n for (let i = 0; i < 16; i++) {\n const def = node._zod.def;\n if (def.type === \"optional\" || def.type === \"nullable\" || def.type === \"default\") {\n required = false;\n if (!def.innerType) break;\n node = def.innerType;\n } else if (def.type === \"pipe\") {\n const next = def.out ?? def.in;\n if (!next) break;\n node = next;\n } else {\n break;\n }\n }\n return { node, type: node._zod.def.type, required };\n}\n","/**\n * zod type → InputKind, plus the extras some kinds carry (enum options, the\n * array item's constraints).\n *\n * Long strings become textareas by a simple rule: a max over 120 chars. That\n * is a heuristic, and the registry can't override it today — if a site needs\n * to, that is a one-line change here, not a per-site branch.\n */\n\nimport type { Constraints, InputKind, Messages } from \"./types\";\nimport type { ZodNode } from \"./internals\";\nimport { readChecks } from \"./checks\";\nimport { unwrap } from \"./unwrap\";\n\nconst TEXTAREA_MIN_MAX = 120;\n\nexport interface InputInfo {\n input: InputKind;\n constraints: Constraints;\n messages: Messages;\n options?: string[];\n}\n\nexport function inputFor(node: ZodNode): InputInfo {\n const def = node._zod.def;\n const { constraints, messages } = readChecks(node);\n\n switch (def.type) {\n case \"string\": {\n if (constraints.format === \"url\") return { input: \"url\", constraints, messages };\n const long = (constraints.max ?? 0) > TEXTAREA_MIN_MAX;\n return { input: long ? \"textarea\" : \"text\", constraints, messages };\n }\n case \"date\":\n return { input: \"date\", constraints, messages };\n case \"number\":\n case \"int\":\n return { input: \"number\", constraints, messages };\n case \"boolean\":\n return { input: \"boolean\", constraints, messages };\n case \"enum\":\n case \"literal\": {\n const options = Object.values(def.entries ?? {}).map(String);\n return { input: \"select\", constraints, messages, options };\n }\n case \"array\": {\n if (def.element) {\n const item = unwrap(def.element);\n const inner = readChecks(item.node);\n constraints.item = inner;\n }\n return { input: \"tags\", constraints, messages };\n }\n default:\n // Anything unrecognised (object, union, …) is presented as text so the\n // value can at least be carried; a renderer may choose to treat it as hidden.\n return { input: \"text\", constraints, messages };\n }\n}\n","/**\n * Labels and placeholders.\n *\n * Precedence (decided 2026-09-08):\n * label: registry.labels[name] → .meta({ label }) → .describe() → titlecase(name)\n * placeholder: registry.placeholders[name] → .meta({ placeholder }) → undefined\n *\n * The registry tier exists for schemas that can't be edited yet (5degrees\n * until its repo transfer). The meta tier is the intended home — one source of\n * truth beside the validation rules. Titlecase is the floor, and it is naive on\n * purpose: an acronym coming out as \"Rsvp url\" is the signal that a real label\n * is missing.\n *\n * Meta is read from zod's global registry via the schema's own `meta()` /\n * `description` accessors, so no zod value is imported here.\n */\n\nimport type { ContentTypeEntry } from \"./types\";\n\ninterface MetaBearing {\n meta?: () => Record<string, unknown> | undefined;\n description?: string;\n}\n\ninterface FieldMeta {\n label?: string;\n placeholder?: string;\n description?: string;\n}\n\nexport function readMeta(schema: unknown): FieldMeta {\n const s = schema as MetaBearing;\n const raw = typeof s.meta === \"function\" ? (s.meta() ?? {}) : {};\n const pick = (k: keyof FieldMeta) => (typeof raw[k] === \"string\" ? (raw[k] as string) : undefined);\n return {\n label: pick(\"label\"),\n placeholder: pick(\"placeholder\"),\n description: pick(\"description\") ?? (typeof s.description === \"string\" ? s.description : undefined),\n };\n}\n\nexport function titlecase(name: string): string {\n const words = name\n .replace(/([a-z0-9])([A-Z])/g, \"$1 $2\")\n .replace(/[_-]+/g, \" \")\n .toLowerCase()\n .trim();\n return words.charAt(0).toUpperCase() + words.slice(1);\n}\n\nexport function labelFor(name: string, schema: unknown, entry?: Pick<ContentTypeEntry, \"labels\">): string {\n const fromRegistry = entry?.labels?.[name];\n if (fromRegistry) return fromRegistry;\n const meta = readMeta(schema);\n return meta.label ?? meta.description ?? titlecase(name);\n}\n\nexport function placeholderFor(\n name: string,\n schema: unknown,\n entry?: Pick<ContentTypeEntry, \"placeholders\">,\n): string | undefined {\n return entry?.placeholders?.[name] ?? readMeta(schema).placeholder;\n}\n","/**\n * walkSchema — a zod object schema (+ optional registry entry) → FieldSpec[].\n *\n * Synchronous and pure. Reads the schema's shape and checks; never parses a\n * value, never calls an options resolver, never imports zod at runtime.\n *\n * Roles from the registry override the schema-derived input kind: a field the\n * entry names as an image is `role: \"image\"` even though zod sees a string.\n * Hidden fields are still emitted so a renderer can carry their values through.\n */\n\nimport type { ContentTypeEntry, FieldRole, FieldSpec } from \"./types\";\nimport type { ZodNode } from \"./internals\";\nimport { asNode } from \"./internals\";\nimport { inputFor } from \"./inputs\";\nimport { labelFor, placeholderFor } from \"./labels\";\nimport { unwrap } from \"./unwrap\";\n\ntype Entry = Partial<Omit<ContentTypeEntry, \"schema\" | \"filename\" | \"id\" | \"label\" | \"folder\">>;\n\nexport function walkSchema(schema: unknown, entry: Entry = {}): FieldSpec[] {\n const root = asNode(schema);\n const shape = shapeOf(root);\n const names = Object.keys(shape);\n\n const specs = names.map((name): FieldSpec => {\n const raw = shape[name];\n const { node, required } = unwrap(raw);\n const info = inputFor(node);\n const role = roleFor(name, entry);\n\n return {\n name,\n label: labelFor(name, raw, entry),\n input: role === \"image\" ? \"image\" : info.input,\n required,\n constraints: info.constraints,\n messages: info.messages,\n placeholder: placeholderFor(name, raw, entry),\n order: 0,\n role,\n options: entry.options?.[name] ?? info.options,\n };\n });\n\n return applyOrder(specs, entry.order ?? []);\n}\n\n/** `.strict()` and object-level `.refine()` both leave `shape` on the object def. */\nfunction shapeOf(root: ZodNode): Record<string, ZodNode> {\n let node = root;\n for (let i = 0; i < 8 && !node._zod.def.shape; i++) {\n const def = node._zod.def;\n const next = def.innerType ?? def.in;\n if (!next) break;\n node = next;\n }\n const shape = node._zod.def.shape;\n if (!shape) throw new TypeError(`walkSchema: expected an object schema, got \"${root._zod.def.type}\"`);\n return shape;\n}\n\nfunction roleFor(name: string, entry: Entry): FieldRole {\n if (entry.hidden?.includes(name)) return \"hidden\";\n if (entry.body === name) return \"body\";\n if (entry.images?.includes(name)) return \"image\";\n return \"field\";\n}\n\n/** Named fields first in the given order; the rest keep schema key order. */\nfunction applyOrder(specs: FieldSpec[], order: string[]): FieldSpec[] {\n const rank = new Map(order.map((n, i) => [n, i]));\n const named = specs.filter((s) => rank.has(s.name)).sort((a, b) => rank.get(a.name)! - rank.get(b.name)!);\n const rest = specs.filter((s) => !rank.has(s.name));\n return [...named, ...rest].map((s, i) => ({ ...s, order: i }));\n}\n","/**\n * One control per InputKind. Dumb: value in, change out. Field.tsx owns the\n * label, helper row and error; ContentForm owns state.\n */\n\nimport type { FieldSpec } from \"../forms\";\nimport type { ControlValue } from \"./values\";\n\nexport interface ControlProps {\n id: string;\n spec: FieldSpec;\n value: ControlValue;\n invalid: boolean;\n options?: string[] | \"loading\";\n onChange: (v: ControlValue) => void;\n onBlur: () => void;\n}\n\nexport function Control(p: ControlProps) {\n const cls = [\"realiiz-form__control\", p.invalid && \"realiiz-form__control--invalid\"].filter(Boolean).join(\" \");\n const common = {\n id: p.id,\n name: p.spec.name,\n \"aria-invalid\": p.invalid || undefined,\n \"aria-describedby\": p.invalid ? `${p.id}-error` : undefined,\n onBlur: p.onBlur,\n } as const;\n const str = typeof p.value === \"string\" ? p.value : \"\";\n\n if (p.options === \"loading\") {\n return <div className={cls} aria-busy=\"true\" style={{ color: \"var(--_muted)\" }}>Loading options…</div>;\n }\n\n if (p.options || p.spec.input === \"select\") {\n return (\n <select {...common} className={cls} value={str} onChange={(e) => p.onChange(e.target.value)}>\n {!p.spec.required && <option value=\"\">—</option>}\n {(p.options ?? []).map((o) => (\n <option key={o} value={o}>{o}</option>\n ))}\n </select>\n );\n }\n\n switch (p.spec.input) {\n case \"textarea\":\n return (\n <textarea {...common} className={`${cls} realiiz-form__control--textarea`} value={str}\n placeholder={p.spec.placeholder} onChange={(e) => p.onChange(e.target.value)} />\n );\n case \"boolean\":\n return (\n <input {...common} type=\"checkbox\" checked={p.value === true} onChange={(e) => p.onChange(e.target.checked)} />\n );\n case \"image\":\n return (\n <input {...common} type=\"file\" accept=\"image/*\" className={cls}\n onChange={(e) => p.onChange(e.target.files?.[0] ?? null)} />\n );\n case \"date\":\n return <input {...common} type=\"date\" className={cls} value={str} onChange={(e) => p.onChange(e.target.value)} />;\n case \"number\":\n return (\n <input {...common} type=\"number\" inputMode=\"numeric\" className={cls} value={str}\n min={p.spec.constraints.min} max={p.spec.constraints.max}\n step={p.spec.constraints.format === \"int\" ? 1 : undefined}\n placeholder={p.spec.placeholder} onChange={(e) => p.onChange(e.target.value)} />\n );\n case \"url\":\n return (\n <input {...common} type=\"url\" className={cls} value={str} placeholder={p.spec.placeholder ?? \"https://\"}\n onChange={(e) => p.onChange(e.target.value)} />\n );\n case \"tags\":\n return (\n <input {...common} type=\"text\" className={cls} value={str}\n placeholder={p.spec.placeholder ?? \"one, two, three\"} onChange={(e) => p.onChange(e.target.value)} />\n );\n default:\n return (\n <input {...common} type=\"text\" className={cls} value={str} placeholder={p.spec.placeholder}\n maxLength={p.spec.constraints.max} onChange={(e) => p.onChange(e.target.value)} />\n );\n }\n}\n","/**\n * Field anatomy: label (+ required marker) → control → helper row (character\n * count when max exists) → error text. The error sits under the control,\n * never in a tooltip — the audience is a non-technical client.\n */\n\nimport type { FieldSpec } from \"../forms\";\nimport type { FieldIssue } from \"./issues\";\nimport { Control } from \"./controls\";\nimport type { ControlValue } from \"./values\";\n\nexport interface FieldProps {\n formId: string;\n spec: FieldSpec;\n value: ControlValue;\n current?: string;\n issues: FieldIssue[];\n options?: string[] | \"loading\";\n onChange: (v: ControlValue) => void;\n onBlur: () => void;\n}\n\nexport function Field(p: FieldProps) {\n const id = `${p.formId}-${p.spec.name}`;\n const invalid = p.issues.length > 0;\n const max = p.spec.constraints.max;\n const showCount = typeof max === \"number\" && (p.spec.input === \"text\" || p.spec.input === \"textarea\");\n const len = typeof p.value === \"string\" ? p.value.length : 0;\n const isCheck = p.spec.input === \"boolean\";\n\n const control = (\n <Control id={id} spec={p.spec} value={p.value} invalid={invalid} options={p.options} onChange={p.onChange} onBlur={p.onBlur} />\n );\n\n return (\n <div className={`realiiz-form__field realiiz-form__field--${p.spec.input}`} data-field={p.spec.name}>\n {isCheck ? (\n <label className=\"realiiz-form__check\" htmlFor={id}>\n {control}\n <span>{p.spec.label}{p.spec.required && <span className=\"realiiz-form__required\" aria-hidden=\"true\">*</span>}</span>\n </label>\n ) : (\n <>\n <label className=\"realiiz-form__label\" htmlFor={id}>\n {p.spec.label}\n {p.spec.required && <span className=\"realiiz-form__required\" aria-hidden=\"true\">*</span>}\n </label>\n {control}\n </>\n )}\n {p.spec.input === \"image\" && p.current && !(p.value instanceof File) && (\n <div className=\"realiiz-form__current\">Current: {p.current}</div>\n )}\n {showCount && (\n <div className=\"realiiz-form__helper\">\n <span className=\"realiiz-form__count\">{len} / {max}</span>\n </div>\n )}\n {invalid && (\n <div className=\"realiiz-form__error\" id={`${id}-error`}>\n {p.issues.map((i, n) => <div key={n}>{i.message}</div>)}\n </div>\n )}\n </div>\n );\n}\n","/**\n * zod issue → { field, key, message }. Pure.\n *\n * The key matches FieldSpec.messages so the schema's own per-check wording is\n * what the client reads. Observed on zod 4.5.4 (see the ADMIN-02b brief):\n * too_small / too_big origin string|array → min_length / max_length\n * origin number → greater_than / less_than\n * when the spec has `exact` → length_equals\n * invalid_format format regex|url|… → that format\n * invalid_type + format safeint (from .int()) → int\n * custom → custom\n * unrecognized_keys path [] , keys: [...] → form-level\n */\n\nimport type { FieldSpec } from \"../forms\";\n\nexport interface FieldIssue {\n /** null for form-level issues. */\n field: string | null;\n key: string;\n message: string;\n}\n\ninterface ZodIssueLike {\n code: string;\n path: PropertyKey[];\n message: string;\n origin?: string;\n format?: string;\n keys?: string[];\n}\n\nexport function mapIssues(issues: ZodIssueLike[], specs: FieldSpec[]): FieldIssue[] {\n const byName = new Map(specs.map((s) => [s.name, s]));\n return issues.map((issue) => {\n const field = typeof issue.path[0] === \"string\" ? issue.path[0] : null;\n const spec = field ? byName.get(field) : undefined;\n const key = keyFor(issue, spec);\n const message =\n (spec?.messages[key] ?? (issue.code === \"unrecognized_keys\" ? unknownKeysMessage(issue) : undefined)) ??\n issue.message;\n return { field, key, message };\n });\n}\n\nfunction keyFor(issue: ZodIssueLike, spec?: FieldSpec): string {\n const numeric = issue.origin === \"number\" || issue.origin === \"int\";\n switch (issue.code) {\n case \"too_small\":\n if (spec?.constraints.exact !== undefined) return \"length_equals\";\n return numeric ? \"greater_than\" : \"min_length\";\n case \"too_big\":\n if (spec?.constraints.exact !== undefined) return \"length_equals\";\n return numeric ? \"less_than\" : \"max_length\";\n case \"invalid_format\":\n return issue.format ?? \"format\";\n case \"invalid_type\":\n return issue.format === \"safeint\" || issue.format === \"int32\" ? \"int\" : \"invalid_type\";\n default:\n return issue.code;\n }\n}\n\nfunction unknownKeysMessage(issue: ZodIssueLike): string {\n const keys = issue.keys ?? [];\n return `This file has ${keys.length === 1 ? \"a field\" : \"fields\"} the site does not recognise: ${keys.join(\", \")}. It was probably edited by hand — remove or rename ${keys.length === 1 ? \"it\" : \"them\"} to continue.`;\n}\n\n/** Group by field for rendering. */\nexport function groupIssues(list: FieldIssue[]): { byField: Map<string, FieldIssue[]>; formLevel: FieldIssue[] } {\n const byField = new Map<string, FieldIssue[]>();\n const formLevel: FieldIssue[] = [];\n for (const i of list) {\n if (i.field === null) formLevel.push(i);\n else byField.set(i.field, [...(byField.get(i.field) ?? []), i]);\n }\n return { byField, formLevel };\n}\n","/**\n * Theming hooks, same convention as BookingWidget: a root class, BEM elements,\n * and --realiiz-form-* custom properties with fallbacks so the form is usable\n * with zero host CSS and fully themeable with a dozen variables.\n */\n\nexport const FORM_CSS = `\n.realiiz-form{\n --_fg:var(--realiiz-form-fg,#111);\n --_muted:var(--realiiz-form-muted,#666);\n --_border:var(--realiiz-form-border,#cfcfcf);\n --_accent:var(--realiiz-form-accent,#111);\n --_accent-fg:var(--realiiz-form-accent-fg,#fff);\n --_danger:var(--realiiz-form-danger,#b3261e);\n --_radius:var(--realiiz-form-radius,6px);\n --_font:var(--realiiz-form-font,system-ui,-apple-system,\"Segoe UI\",Roboto,sans-serif);\n --_ring:var(--realiiz-form-focus-ring,0 0 0 3px rgba(0,0,0,.15));\n box-sizing:border-box;max-width:100%;color:var(--_fg);font-family:var(--_font);font-size:15px;line-height:1.4;\n}\n.realiiz-form *,.realiiz-form *::before,.realiiz-form *::after{box-sizing:inherit}\n.realiiz-form fieldset{border:0;margin:0 0 20px;padding:0;min-width:0}\n.realiiz-form legend{font-weight:600;font-size:13px;letter-spacing:.02em;text-transform:uppercase;color:var(--_muted);margin-bottom:12px;padding:0}\n.realiiz-form__field{display:block;margin:0 0 16px}\n.realiiz-form__label{display:block;font-weight:500;margin-bottom:6px}\n.realiiz-form__required{color:var(--_danger);margin-left:3px}\n.realiiz-form__control{display:block;width:100%;max-width:100%;font:inherit;color:inherit;background:#fff;border:1px solid var(--_border);border-radius:var(--_radius);padding:9px 11px;min-height:40px}\n.realiiz-form__control:focus{outline:none;border-color:var(--_accent);box-shadow:var(--_ring)}\n.realiiz-form__control--textarea{min-height:110px;resize:vertical}\n.realiiz-form__control--body{min-height:280px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:14px}\n.realiiz-form__control--invalid{border-color:var(--_danger)}\n.realiiz-form__check{display:flex;align-items:center;gap:8px;font-weight:500}\n.realiiz-form__check input{width:18px;height:18px;margin:0}\n.realiiz-form__helper{display:flex;justify-content:space-between;gap:12px;font-size:12px;color:var(--_muted);margin-top:4px;min-height:1em}\n.realiiz-form__count{margin-left:auto;font-variant-numeric:tabular-nums}\n.realiiz-form__error{color:var(--_danger);font-size:13px;margin-top:4px}\n.realiiz-form__current{font-size:12px;color:var(--_muted);margin-top:4px;word-break:break-all}\n.realiiz-form__alert{border:1px solid var(--_danger);border-radius:var(--_radius);padding:12px 14px;margin:0 0 20px;color:var(--_danger)}\n.realiiz-form__alert ul{margin:6px 0 0;padding-left:18px}\n.realiiz-form__actions{display:flex;gap:10px;justify-content:flex-end;flex-wrap:wrap;margin-top:8px}\n.realiiz-form__button{font:inherit;font-weight:600;border-radius:var(--_radius);padding:10px 16px;border:1px solid var(--_border);background:#fff;color:var(--_fg);cursor:pointer;min-height:40px}\n.realiiz-form__button--primary{background:var(--_accent);color:var(--_accent-fg);border-color:var(--_accent)}\n.realiiz-form__button:disabled{opacity:.6;cursor:default}\n`;\n","/**\n * Control strings ↔ frontmatter values. Pure.\n *\n * Controls hold strings (or booleans / Files). The schema wants typed values.\n * `toControl` seeds a control from an existing frontmatter value — including\n * a Date from a previously-parsed file — and `toValue` assembles what goes to\n * safeParse. Anything the schema then coerces (dates) is the schema's business.\n */\n\nimport type { FieldSpec } from \"../forms\";\n\nexport type ControlValue = string | boolean | File | null;\nexport type FormState = Record<string, ControlValue>;\n\n/** Stand-in path for an image chosen but not yet uploaded — replaced by ADMIN-06. */\nexport const PENDING_UPLOAD_PREFIX = \"pending-upload/\";\n\nexport function toControl(spec: FieldSpec, raw: unknown): ControlValue {\n if (raw === undefined || raw === null) return spec.input === \"boolean\" ? false : \"\";\n switch (spec.input) {\n case \"boolean\":\n return Boolean(raw);\n case \"date\":\n return raw instanceof Date ? raw.toISOString().slice(0, 10) : String(raw);\n case \"tags\":\n return Array.isArray(raw) ? raw.join(\", \") : String(raw);\n default:\n return String(raw);\n }\n}\n\n/** Build the object that goes to schema.safeParse. Empty optional fields are omitted. */\nexport function toFrontmatter(\n specs: FieldSpec[],\n state: FormState,\n hiddenValues: Record<string, unknown>,\n): { frontmatter: Record<string, unknown>; files: Record<string, File> } {\n const frontmatter: Record<string, unknown> = { ...hiddenValues };\n const files: Record<string, File> = {};\n\n for (const spec of specs) {\n if (spec.role === \"hidden\" || spec.role === \"body\") continue;\n const v = state[spec.name];\n\n if (spec.input === \"boolean\") {\n if (v === true) frontmatter[spec.name] = true;\n else if (spec.required) frontmatter[spec.name] = false;\n continue;\n }\n if (v instanceof File) {\n files[spec.name] = v;\n frontmatter[spec.name] = PENDING_UPLOAD_PREFIX + v.name;\n continue;\n }\n const s = typeof v === \"string\" ? v.trim() : \"\";\n if (s === \"\") {\n // Required fields are sent empty so the schema's own \"is required\" message fires.\n if (spec.required) frontmatter[spec.name] = spec.input === \"tags\" ? [] : \"\";\n continue;\n }\n switch (spec.input) {\n case \"number\": {\n const n = Number(s);\n frontmatter[spec.name] = Number.isNaN(n) ? s : n;\n break;\n }\n case \"tags\":\n frontmatter[spec.name] = s.split(\",\").map((t) => t.trim()).filter(Boolean);\n break;\n default:\n frontmatter[spec.name] = s;\n }\n }\n return { frontmatter, files };\n}\n","/**\n * ContentForm — a ContentTypeEntry in, validated { frontmatter, body, files } out.\n *\n * Validation runs on submit. After the first failed submit it also re-runs on\n * blur, so the client watches the error clear as they fix it. Before that\n * first submit the form stays quiet — nagging someone who hasn't finished\n * typing is the wrong default for this audience.\n *\n * The schema is the only judge. This component never decides what is valid;\n * it assembles values, calls entry.schema.safeParse, and maps issues back to\n * fields with the schema's own messages.\n */\n\nimport { useCallback, useEffect, useId, useMemo, useState, type FormEvent } from \"react\";\nimport type { ContentTypeEntry, FieldSpec } from \"../forms\";\nimport { walkSchema } from \"../forms\";\nimport { Field } from \"./Field\";\nimport { groupIssues, mapIssues, type FieldIssue } from \"./issues\";\nimport { FORM_CSS } from \"./styles\";\nimport { toControl, toFrontmatter, type ControlValue, type FormState } from \"./values\";\n\nexport interface ContentFormSubmission {\n frontmatter: Record<string, unknown>;\n body: string;\n files: Record<string, File>;\n}\n\nexport interface ContentFormProps {\n entry: ContentTypeEntry;\n initialValues?: Record<string, unknown>;\n initialBody?: string;\n onSubmit: (s: ContentFormSubmission) => void | Promise<void>;\n onCancel?: () => void;\n submitLabel?: string;\n className?: string;\n}\n\ntype SafeParse = { success: true; data: unknown } | { success: false; error: { issues: Parameters<typeof mapIssues>[0] } };\n\nexport function ContentForm({ entry, initialValues = {}, initialBody = \"\", onSubmit, onCancel, submitLabel = \"Save\", className }: ContentFormProps) {\n const formId = useId();\n const specs = useMemo(() => walkSchema(entry.schema, entry), [entry]);\n const visible = useMemo(() => specs.filter((s) => s.role !== \"hidden\" && s.role !== \"body\"), [specs]);\n const bodySpec = specs.find((s) => s.role === \"body\");\n\n const [state, setState] = useState<FormState>(() => Object.fromEntries(specs.map((s) => [s.name, toControl(s, initialValues[s.name])])));\n const [body, setBody] = useState(initialBody);\n const [issues, setIssues] = useState<FieldIssue[]>([]);\n const [attempted, setAttempted] = useState(false);\n const [submitting, setSubmitting] = useState(false);\n const [options, setOptions] = useState<Record<string, string[] | \"loading\">>({});\n\n // Hidden fields (and any initial key with no spec) ride through untouched.\n const hiddenValues = useMemo(() => {\n const shown = new Set(visible.map((s) => s.name));\n if (bodySpec) shown.add(bodySpec.name);\n return Object.fromEntries(Object.entries(initialValues).filter(([k]) => !shown.has(k)));\n }, [initialValues, visible, bodySpec]);\n\n // Resolve function option sources once, on mount.\n useEffect(() => {\n let live = true;\n for (const s of visible) {\n if (typeof s.options !== \"function\") continue;\n setOptions((o) => ({ ...o, [s.name]: \"loading\" }));\n Promise.resolve(s.options()).then((list) => {\n if (live) setOptions((o) => ({ ...o, [s.name]: list }));\n });\n }\n return () => {\n live = false;\n };\n }, [visible]);\n\n const validate = useCallback((): SafeParse => {\n const { frontmatter } = toFrontmatter(specs, state, hiddenValues);\n const result = (entry.schema as { safeParse: (v: unknown) => SafeParse }).safeParse(frontmatter);\n setIssues(result.success ? [] : mapIssues(result.error.issues, specs));\n return result;\n }, [entry.schema, specs, state, hiddenValues]);\n\n const handleSubmit = async (e: FormEvent) => {\n e.preventDefault();\n if (submitting) return;\n setAttempted(true);\n const result = validate();\n if (!result.success) return;\n const { files } = toFrontmatter(specs, state, hiddenValues);\n setSubmitting(true);\n try {\n await onSubmit({ frontmatter: result.data as Record<string, unknown>, body, files });\n } finally {\n setSubmitting(false);\n }\n };\n\n const { byField, formLevel } = groupIssues(issues);\n const optionsFor = (s: FieldSpec) => options[s.name] ?? (Array.isArray(s.options) ? s.options : undefined);\n\n return (\n <form className={[\"realiiz-form\", className].filter(Boolean).join(\" \")} onSubmit={handleSubmit} noValidate data-realiiz-content-form={entry.id}>\n <style>{FORM_CSS}</style>\n\n {formLevel.length > 0 && (\n <div className=\"realiiz-form__alert\" role=\"alert\">\n <strong>This can't be saved yet.</strong>\n <ul>{formLevel.map((i, n) => <li key={n}>{i.message}</li>)}</ul>\n </div>\n )}\n\n <fieldset>\n <legend>{entry.label}</legend>\n {visible.map((s) => (\n <Field key={s.name} formId={formId} spec={s} value={state[s.name]} issues={byField.get(s.name) ?? []}\n current={typeof initialValues[s.name] === \"string\" ? (initialValues[s.name] as string) : undefined}\n options={optionsFor(s)}\n onChange={(v: ControlValue) => setState((st) => ({ ...st, [s.name]: v }))}\n onBlur={() => { if (attempted) validate(); }} />\n ))}\n </fieldset>\n\n {bodySpec && (\n <fieldset>\n <legend>{bodySpec.label}</legend>\n <label className=\"realiiz-form__label\" htmlFor={`${formId}-body`}>{bodySpec.label}</label>\n <textarea id={`${formId}-body`} name=\"body\" className=\"realiiz-form__control realiiz-form__control--body\"\n value={body} placeholder={bodySpec.placeholder} onChange={(e) => setBody(e.target.value)} />\n </fieldset>\n )}\n\n <div className=\"realiiz-form__actions\">\n {onCancel && <button type=\"button\" className=\"realiiz-form__button\" onClick={onCancel}>Cancel</button>}\n <button type=\"submit\" className=\"realiiz-form__button realiiz-form__button--primary\" disabled={submitting}>\n {submitting ? `${submitLabel}…` : submitLabel}\n </button>\n </div>\n </form>\n );\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/forms/internals.ts","../../src/forms/checks.ts","../../src/forms/unwrap.ts","../../src/forms/inputs.ts","../../src/forms/labels.ts","../../src/forms/walk.ts","../../src/forms-ui/controls.tsx","../../src/forms-ui/Field.tsx","../../src/forms-ui/issues.ts","../../src/forms-ui/styles.ts","../../src/forms-ui/values.ts","../../src/forms-ui/ContentForm.tsx"],"names":["jsx","jsxs","Fragment","useId","useMemo","useState","useEffect","useCallback"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * The slice of zod 4's `_zod.def` surface this module reads, typed loosely.\n *\n * Tested against zod 4.5.4. These are semi-public internals (the v4 \"core\"\n * shape), stable across 4.x so far but not covered by semver promises — hence\n * the peer-dep range and the fixture tests. No zod value is imported here.\n */\n\nexport interface ZodCheckDef {\n check?: string;\n format?: string;\n minimum?: number;\n maximum?: number;\n length?: number;\n value?: number;\n inclusive?: boolean;\n pattern?: RegExp;\n error?: string | ((issue: unknown) => string | { message?: string } | undefined);\n}\n\nexport interface ZodDef {\n type: string;\n shape?: Record<string, ZodNode>;\n innerType?: ZodNode;\n in?: ZodNode;\n out?: ZodNode;\n element?: ZodNode;\n entries?: Record<string, string | number>;\n checks?: Array<{ _zod: { def: ZodCheckDef } }>;\n coerce?: boolean;\n}\n\nexport interface ZodNode {\n _zod: { def: ZodDef };\n description?: string;\n}\n\n/** Narrow an unknown schema to the internal node shape, or throw a clear error. */\nexport function asNode(schema: unknown, what = \"schema\"): ZodNode {\n const n = schema as Partial<ZodNode> | null;\n if (!n || typeof n !== \"object\" || !n._zod?.def?.type) {\n throw new TypeError(`walkSchema: ${what} is not a zod 4 schema`);\n }\n return n as ZodNode;\n}\n\n/** Resolve a check's error to a plain string, if it has one. */\nexport function checkMessage(def: ZodCheckDef): string | undefined {\n const e = def.error;\n if (!e) return undefined;\n if (typeof e === \"string\") return e;\n const out = e({});\n if (typeof out === \"string\") return out;\n return out?.message;\n}\n","/**\n * checks[] → constraints + messages.\n *\n * Messages are keyed PER CHECK. The spike's walker wrote `out.message = …` on\n * every check, so a field with min and max kept only the max message and\n * showed the wrong error for a too-short value. Here each check kind gets its\n * own key; a renderer maps a failed issue to its message by that key.\n *\n * Unknown check kinds (e.g. a per-field `.refine`, which is `custom`) are kept\n * as a message when they carry one and otherwise skipped — never fatal.\n */\n\nimport type { Constraints, Messages } from \"./types\";\nimport type { ZodNode } from \"./internals\";\nimport { checkMessage } from \"./internals\";\n\nexport function readChecks(node: ZodNode): { constraints: Constraints; messages: Messages } {\n const constraints: Constraints = {};\n const messages: Messages = {};\n\n for (const c of node._zod.def.checks ?? []) {\n const d = c._zod.def;\n const msg = checkMessage(d);\n let key: string | undefined;\n\n switch (d.check) {\n case \"min_length\":\n constraints.min = d.minimum;\n key = \"min_length\";\n break;\n case \"max_length\":\n constraints.max = d.maximum;\n key = \"max_length\";\n break;\n case \"length_equals\":\n constraints.exact = d.length;\n key = \"length_equals\";\n break;\n case \"greater_than\":\n constraints.min = d.inclusive ? d.value : (d.value ?? 0) + 1;\n key = \"greater_than\";\n break;\n case \"less_than\":\n constraints.max = d.inclusive ? d.value : (d.value ?? 0) - 1;\n key = \"less_than\";\n break;\n case \"string_format\":\n if (d.format === \"regex\" && d.pattern) {\n constraints.pattern = d.pattern.source;\n constraints.format = \"regex\";\n key = \"regex\";\n } else if (d.format === \"url\" || d.format === \"email\") {\n constraints.format = d.format;\n key = d.format;\n } else {\n key = d.format;\n }\n break;\n case \"number_format\":\n if (d.format === \"safeint\" || d.format === \"int32\") {\n constraints.format = \"int\";\n key = \"int\";\n }\n break;\n case \"custom\":\n key = \"custom\";\n break;\n default:\n key = d.check;\n }\n\n if (key && msg) messages[key] = msg;\n }\n\n return { constraints, messages };\n}\n","/**\n * Peel wrapper types off a field until the real type is reached.\n *\n * optional / nullable / default → the field is not required.\n * pipe → follow `out` (older 4.x wrapped z.coerce.date in a pipe; 4.5 marks\n * the date itself with `coerce: true` — both land on the same node here).\n */\n\nimport type { ZodNode } from \"./internals\";\n\nexport interface Unwrapped {\n node: ZodNode;\n type: string;\n required: boolean;\n}\n\nexport function unwrap(start: ZodNode): Unwrapped {\n let node = start;\n let required = true;\n // Bounded so a malformed schema can't loop forever.\n for (let i = 0; i < 16; i++) {\n const def = node._zod.def;\n if (def.type === \"optional\" || def.type === \"nullable\" || def.type === \"default\") {\n required = false;\n if (!def.innerType) break;\n node = def.innerType;\n } else if (def.type === \"pipe\") {\n const next = def.out ?? def.in;\n if (!next) break;\n node = next;\n } else {\n break;\n }\n }\n return { node, type: node._zod.def.type, required };\n}\n","/**\n * zod type → InputKind, plus the extras some kinds carry (enum options, the\n * array item's constraints).\n *\n * Long strings become textareas by a simple rule: a max over 120 chars. That\n * is a heuristic, and the registry can't override it today — if a site needs\n * to, that is a one-line change here, not a per-site branch.\n */\n\nimport type { Constraints, InputKind, Messages } from \"./types\";\nimport type { ZodNode } from \"./internals\";\nimport { readChecks } from \"./checks\";\nimport { unwrap } from \"./unwrap\";\n\nconst TEXTAREA_MIN_MAX = 120;\n\nexport interface InputInfo {\n input: InputKind;\n constraints: Constraints;\n messages: Messages;\n options?: string[];\n}\n\nexport function inputFor(node: ZodNode): InputInfo {\n const def = node._zod.def;\n const { constraints, messages } = readChecks(node);\n\n switch (def.type) {\n case \"string\": {\n if (constraints.format === \"url\") return { input: \"url\", constraints, messages };\n const long = (constraints.max ?? 0) > TEXTAREA_MIN_MAX;\n return { input: long ? \"textarea\" : \"text\", constraints, messages };\n }\n case \"date\":\n return { input: \"date\", constraints, messages };\n case \"number\":\n case \"int\":\n return { input: \"number\", constraints, messages };\n case \"boolean\":\n return { input: \"boolean\", constraints, messages };\n case \"enum\":\n case \"literal\": {\n const options = Object.values(def.entries ?? {}).map(String);\n return { input: \"select\", constraints, messages, options };\n }\n case \"array\": {\n if (def.element) {\n const item = unwrap(def.element);\n const inner = readChecks(item.node);\n constraints.item = inner;\n }\n return { input: \"tags\", constraints, messages };\n }\n default:\n // Anything unrecognised (object, union, …) is presented as text so the\n // value can at least be carried; a renderer may choose to treat it as hidden.\n return { input: \"text\", constraints, messages };\n }\n}\n","/**\n * Labels and placeholders.\n *\n * Precedence (decided 2026-09-08):\n * label: registry.labels[name] → .meta({ label }) → .describe() → titlecase(name)\n * placeholder: registry.placeholders[name] → .meta({ placeholder }) → undefined\n *\n * The registry tier exists for schemas that can't be edited yet (5degrees\n * until its repo transfer). The meta tier is the intended home — one source of\n * truth beside the validation rules. Titlecase is the floor, and it is naive on\n * purpose: an acronym coming out as \"Rsvp url\" is the signal that a real label\n * is missing.\n *\n * Meta is read from zod's global registry via the schema's own `meta()` /\n * `description` accessors, so no zod value is imported here.\n */\n\nimport type { ContentTypeEntry } from \"./types\";\n\ninterface MetaBearing {\n meta?: () => Record<string, unknown> | undefined;\n description?: string;\n}\n\ninterface FieldMeta {\n label?: string;\n placeholder?: string;\n description?: string;\n}\n\nexport function readMeta(schema: unknown): FieldMeta {\n const s = schema as MetaBearing;\n const raw = typeof s.meta === \"function\" ? (s.meta() ?? {}) : {};\n const pick = (k: keyof FieldMeta) => (typeof raw[k] === \"string\" ? (raw[k] as string) : undefined);\n return {\n label: pick(\"label\"),\n placeholder: pick(\"placeholder\"),\n description: pick(\"description\") ?? (typeof s.description === \"string\" ? s.description : undefined),\n };\n}\n\nexport function titlecase(name: string): string {\n const words = name\n .replace(/([a-z0-9])([A-Z])/g, \"$1 $2\")\n .replace(/[_-]+/g, \" \")\n .toLowerCase()\n .trim();\n return words.charAt(0).toUpperCase() + words.slice(1);\n}\n\nexport function labelFor(name: string, schema: unknown, entry?: Pick<ContentTypeEntry, \"labels\">): string {\n const fromRegistry = entry?.labels?.[name];\n if (fromRegistry) return fromRegistry;\n const meta = readMeta(schema);\n return meta.label ?? meta.description ?? titlecase(name);\n}\n\nexport function placeholderFor(\n name: string,\n schema: unknown,\n entry?: Pick<ContentTypeEntry, \"placeholders\">,\n): string | undefined {\n return entry?.placeholders?.[name] ?? readMeta(schema).placeholder;\n}\n","/**\n * walkSchema — a zod object schema (+ optional registry entry) → FieldSpec[].\n *\n * Synchronous and pure. Reads the schema's shape and checks; never parses a\n * value, never calls an options resolver, never imports zod at runtime.\n *\n * Roles from the registry override the schema-derived input kind: a field the\n * entry names as an image is `role: \"image\"` even though zod sees a string.\n * Hidden fields are still emitted so a renderer can carry their values through.\n */\n\nimport type { ContentTypeEntry, FieldRole, FieldSpec } from \"./types\";\nimport type { ZodNode } from \"./internals\";\nimport { asNode } from \"./internals\";\nimport { inputFor } from \"./inputs\";\nimport { labelFor, placeholderFor } from \"./labels\";\nimport { unwrap } from \"./unwrap\";\n\ntype Entry = Partial<Omit<ContentTypeEntry, \"schema\" | \"filename\" | \"id\" | \"label\" | \"folder\">>;\n\nexport function walkSchema(schema: unknown, entry: Entry = {}): FieldSpec[] {\n const root = asNode(schema);\n const shape = shapeOf(root);\n const names = Object.keys(shape);\n\n const specs = names.map((name): FieldSpec => {\n const raw = shape[name];\n const { node, required } = unwrap(raw);\n const info = inputFor(node);\n const role = roleFor(name, entry);\n\n return {\n name,\n label: labelFor(name, raw, entry),\n input: role === \"image\" ? \"image\" : info.input,\n required,\n constraints: info.constraints,\n messages: info.messages,\n placeholder: placeholderFor(name, raw, entry),\n order: 0,\n role,\n options: entry.options?.[name] ?? info.options,\n };\n });\n\n return applyOrder(specs, entry.order ?? []);\n}\n\n/** `.strict()` and object-level `.refine()` both leave `shape` on the object def. */\nfunction shapeOf(root: ZodNode): Record<string, ZodNode> {\n let node = root;\n for (let i = 0; i < 8 && !node._zod.def.shape; i++) {\n const def = node._zod.def;\n const next = def.innerType ?? def.in;\n if (!next) break;\n node = next;\n }\n const shape = node._zod.def.shape;\n if (!shape) throw new TypeError(`walkSchema: expected an object schema, got \"${root._zod.def.type}\"`);\n return shape;\n}\n\nfunction roleFor(name: string, entry: Entry): FieldRole {\n if (entry.hidden?.includes(name)) return \"hidden\";\n if (entry.body === name) return \"body\";\n if (entry.images?.includes(name)) return \"image\";\n return \"field\";\n}\n\n/** Named fields first in the given order; the rest keep schema key order. */\nfunction applyOrder(specs: FieldSpec[], order: string[]): FieldSpec[] {\n const rank = new Map(order.map((n, i) => [n, i]));\n const named = specs.filter((s) => rank.has(s.name)).sort((a, b) => rank.get(a.name)! - rank.get(b.name)!);\n const rest = specs.filter((s) => !rank.has(s.name));\n return [...named, ...rest].map((s, i) => ({ ...s, order: i }));\n}\n","/**\n * One control per InputKind. Dumb: value in, change out. Field.tsx owns the\n * label, helper row and error; ContentForm owns state.\n */\n\nimport type { FieldSpec } from \"../forms\";\nimport type { ControlValue } from \"./values\";\n\nexport interface ControlProps {\n id: string;\n spec: FieldSpec;\n value: ControlValue;\n invalid: boolean;\n options?: string[] | \"loading\";\n onChange: (v: ControlValue) => void;\n onBlur: () => void;\n}\n\nexport function Control(p: ControlProps) {\n const cls = [\"realiiz-form__control\", p.invalid && \"realiiz-form__control--invalid\"].filter(Boolean).join(\" \");\n const common = {\n id: p.id,\n name: p.spec.name,\n \"aria-invalid\": p.invalid || undefined,\n \"aria-describedby\": p.invalid ? `${p.id}-error` : undefined,\n onBlur: p.onBlur,\n } as const;\n const str = typeof p.value === \"string\" ? p.value : \"\";\n\n if (p.options === \"loading\") {\n return <div className={cls} aria-busy=\"true\" style={{ color: \"var(--_muted)\" }}>Loading options…</div>;\n }\n\n if (p.options || p.spec.input === \"select\") {\n return (\n <select {...common} className={cls} value={str} onChange={(e) => p.onChange(e.target.value)}>\n {!p.spec.required && <option value=\"\">—</option>}\n {(p.options ?? []).map((o) => (\n <option key={o} value={o}>{o}</option>\n ))}\n </select>\n );\n }\n\n switch (p.spec.input) {\n case \"textarea\":\n return (\n <textarea {...common} className={`${cls} realiiz-form__control--textarea`} value={str}\n placeholder={p.spec.placeholder} onChange={(e) => p.onChange(e.target.value)} />\n );\n case \"boolean\":\n return (\n <input {...common} type=\"checkbox\" checked={p.value === true} onChange={(e) => p.onChange(e.target.checked)} />\n );\n case \"image\":\n return (\n <input {...common} type=\"file\" accept=\"image/*\" className={cls}\n onChange={(e) => p.onChange(e.target.files?.[0] ?? null)} />\n );\n case \"date\":\n return <input {...common} type=\"date\" className={cls} value={str} onChange={(e) => p.onChange(e.target.value)} />;\n case \"number\":\n return (\n <input {...common} type=\"number\" inputMode=\"numeric\" className={cls} value={str}\n min={p.spec.constraints.min} max={p.spec.constraints.max}\n step={p.spec.constraints.format === \"int\" ? 1 : undefined}\n placeholder={p.spec.placeholder} onChange={(e) => p.onChange(e.target.value)} />\n );\n case \"url\":\n return (\n <input {...common} type=\"url\" className={cls} value={str} placeholder={p.spec.placeholder ?? \"https://\"}\n onChange={(e) => p.onChange(e.target.value)} />\n );\n case \"tags\":\n return (\n <input {...common} type=\"text\" className={cls} value={str}\n placeholder={p.spec.placeholder ?? \"one, two, three\"} onChange={(e) => p.onChange(e.target.value)} />\n );\n default:\n return (\n <input {...common} type=\"text\" className={cls} value={str} placeholder={p.spec.placeholder}\n maxLength={p.spec.constraints.max} onChange={(e) => p.onChange(e.target.value)} />\n );\n }\n}\n","/**\n * Field anatomy: label (+ required marker) → control → helper row (character\n * count when max exists) → error text. The error sits under the control,\n * never in a tooltip — the audience is a non-technical client.\n */\n\nimport type { FieldSpec } from \"../forms\";\nimport type { FieldIssue } from \"./issues\";\nimport { Control } from \"./controls\";\nimport type { ControlValue } from \"./values\";\n\nexport interface FieldProps {\n formId: string;\n spec: FieldSpec;\n value: ControlValue;\n current?: string;\n issues: FieldIssue[];\n options?: string[] | \"loading\";\n onChange: (v: ControlValue) => void;\n onBlur: () => void;\n}\n\nexport function Field(p: FieldProps) {\n const id = `${p.formId}-${p.spec.name}`;\n const invalid = p.issues.length > 0;\n const max = p.spec.constraints.max;\n const showCount = typeof max === \"number\" && (p.spec.input === \"text\" || p.spec.input === \"textarea\");\n const len = typeof p.value === \"string\" ? p.value.length : 0;\n const isCheck = p.spec.input === \"boolean\";\n\n const control = (\n <Control id={id} spec={p.spec} value={p.value} invalid={invalid} options={p.options} onChange={p.onChange} onBlur={p.onBlur} />\n );\n\n return (\n <div className={`realiiz-form__field realiiz-form__field--${p.spec.input}`} data-field={p.spec.name}>\n {isCheck ? (\n <label className=\"realiiz-form__check\" htmlFor={id}>\n {control}\n <span>{p.spec.label}{p.spec.required && <span className=\"realiiz-form__required\" aria-hidden=\"true\">*</span>}</span>\n </label>\n ) : (\n <>\n <label className=\"realiiz-form__label\" htmlFor={id}>\n {p.spec.label}\n {p.spec.required && <span className=\"realiiz-form__required\" aria-hidden=\"true\">*</span>}\n </label>\n {control}\n </>\n )}\n {p.spec.input === \"image\" && p.current && !(p.value instanceof File) && (\n <div className=\"realiiz-form__current\">Current: {p.current}</div>\n )}\n {showCount && (\n <div className=\"realiiz-form__helper\">\n <span className=\"realiiz-form__count\">{len} / {max}</span>\n </div>\n )}\n {invalid && (\n <div className=\"realiiz-form__error\" id={`${id}-error`}>\n {p.issues.map((i, n) => <div key={n}>{i.message}</div>)}\n </div>\n )}\n </div>\n );\n}\n","/**\n * zod issue → { field, key, message }. Pure.\n *\n * The key matches FieldSpec.messages so the schema's own per-check wording is\n * what the client reads. Observed on zod 4.5.4 (see the ADMIN-02b brief):\n * too_small / too_big origin string|array → min_length / max_length\n * origin number → greater_than / less_than\n * when the spec has `exact` → length_equals\n * invalid_format format regex|url|… → that format\n * invalid_type + format safeint (from .int()) → int\n * custom → custom\n * unrecognized_keys path [] , keys: [...] → form-level\n */\n\nimport type { FieldSpec } from \"../forms\";\n\nexport interface FieldIssue {\n /** null for form-level issues. */\n field: string | null;\n key: string;\n message: string;\n}\n\ninterface ZodIssueLike {\n code: string;\n path: PropertyKey[];\n message: string;\n origin?: string;\n format?: string;\n keys?: string[];\n}\n\nexport function mapIssues(issues: ZodIssueLike[], specs: FieldSpec[]): FieldIssue[] {\n const byName = new Map(specs.map((s) => [s.name, s]));\n return issues.map((issue) => {\n const field = typeof issue.path[0] === \"string\" ? issue.path[0] : null;\n const spec = field ? byName.get(field) : undefined;\n const key = keyFor(issue, spec);\n const message =\n (spec?.messages[key] ?? (issue.code === \"unrecognized_keys\" ? unknownKeysMessage(issue) : undefined)) ??\n issue.message;\n return { field, key, message };\n });\n}\n\nfunction keyFor(issue: ZodIssueLike, spec?: FieldSpec): string {\n const numeric = issue.origin === \"number\" || issue.origin === \"int\";\n switch (issue.code) {\n case \"too_small\":\n if (spec?.constraints.exact !== undefined) return \"length_equals\";\n return numeric ? \"greater_than\" : \"min_length\";\n case \"too_big\":\n if (spec?.constraints.exact !== undefined) return \"length_equals\";\n return numeric ? \"less_than\" : \"max_length\";\n case \"invalid_format\":\n return issue.format ?? \"format\";\n case \"invalid_type\":\n return issue.format === \"safeint\" || issue.format === \"int32\" ? \"int\" : \"invalid_type\";\n default:\n return issue.code;\n }\n}\n\nfunction unknownKeysMessage(issue: ZodIssueLike): string {\n const keys = issue.keys ?? [];\n return `This file has ${keys.length === 1 ? \"a field\" : \"fields\"} the site does not recognise: ${keys.join(\", \")}. It was probably edited by hand — remove or rename ${keys.length === 1 ? \"it\" : \"them\"} to continue.`;\n}\n\n/** Group by field for rendering. */\nexport function groupIssues(list: FieldIssue[]): { byField: Map<string, FieldIssue[]>; formLevel: FieldIssue[] } {\n const byField = new Map<string, FieldIssue[]>();\n const formLevel: FieldIssue[] = [];\n for (const i of list) {\n if (i.field === null) formLevel.push(i);\n else byField.set(i.field, [...(byField.get(i.field) ?? []), i]);\n }\n return { byField, formLevel };\n}\n","/**\n * Theming hooks, same convention as BookingWidget: a root class, BEM elements,\n * and --realiiz-form-* custom properties with fallbacks so the form is usable\n * with zero host CSS and fully themeable with a dozen variables.\n */\n\nexport const FORM_CSS = `\n.realiiz-form{\n --_fg:var(--realiiz-form-fg,#111);\n --_muted:var(--realiiz-form-muted,#666);\n --_border:var(--realiiz-form-border,#cfcfcf);\n --_accent:var(--realiiz-form-accent,#111);\n --_accent-fg:var(--realiiz-form-accent-fg,#fff);\n --_danger:var(--realiiz-form-danger,#b3261e);\n --_radius:var(--realiiz-form-radius,6px);\n --_font:var(--realiiz-form-font,system-ui,-apple-system,\"Segoe UI\",Roboto,sans-serif);\n --_ring:var(--realiiz-form-focus-ring,0 0 0 3px rgba(0,0,0,.15));\n box-sizing:border-box;max-width:100%;color:var(--_fg);font-family:var(--_font);font-size:15px;line-height:1.4;\n}\n.realiiz-form *,.realiiz-form *::before,.realiiz-form *::after{box-sizing:inherit}\n.realiiz-form fieldset{border:0;margin:0 0 20px;padding:0;min-width:0}\n.realiiz-form legend{font-weight:600;font-size:13px;letter-spacing:.02em;text-transform:uppercase;color:var(--_muted);margin-bottom:12px;padding:0}\n.realiiz-form__field{display:block;margin:0 0 16px}\n.realiiz-form__label{display:block;font-weight:500;margin-bottom:6px}\n.realiiz-form__required{color:var(--_danger);margin-left:3px}\n.realiiz-form__control{display:block;width:100%;max-width:100%;font:inherit;color:inherit;background:#fff;border:1px solid var(--_border);border-radius:var(--_radius);padding:9px 11px;min-height:40px}\n.realiiz-form__control:focus{outline:none;border-color:var(--_accent);box-shadow:var(--_ring)}\n.realiiz-form__control--textarea{min-height:110px;resize:vertical}\n.realiiz-form__control--body{min-height:280px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:14px}\n.realiiz-form__control--invalid{border-color:var(--_danger)}\n.realiiz-form__check{display:flex;align-items:center;gap:8px;font-weight:500}\n.realiiz-form__check input{width:18px;height:18px;margin:0}\n.realiiz-form__helper{display:flex;justify-content:space-between;gap:12px;font-size:12px;color:var(--_muted);margin-top:4px;min-height:1em}\n.realiiz-form__count{margin-left:auto;font-variant-numeric:tabular-nums}\n.realiiz-form__error{color:var(--_danger);font-size:13px;margin-top:4px}\n.realiiz-form__current{font-size:12px;color:var(--_muted);margin-top:4px;word-break:break-all}\n.realiiz-form__alert{border:1px solid var(--_danger);border-radius:var(--_radius);padding:12px 14px;margin:0 0 20px;color:var(--_danger)}\n.realiiz-form__alert ul{margin:6px 0 0;padding-left:18px}\n.realiiz-form__actions{display:flex;gap:10px;justify-content:flex-end;flex-wrap:wrap;margin-top:8px}\n.realiiz-form__button{font:inherit;font-weight:600;border-radius:var(--_radius);padding:10px 16px;border:1px solid var(--_border);background:#fff;color:var(--_fg);cursor:pointer;min-height:40px}\n.realiiz-form__button--primary{background:var(--_accent);color:var(--_accent-fg);border-color:var(--_accent)}\n.realiiz-form__button:disabled{opacity:.6;cursor:default}\n`;\n","/**\n * Control strings ↔ frontmatter values. Pure.\n *\n * Controls hold strings (or booleans / Files). The schema wants typed values.\n * `toControl` seeds a control from an existing frontmatter value — including\n * a Date from a previously-parsed file — and `toValue` assembles what goes to\n * safeParse. Anything the schema then coerces (dates) is the schema's business.\n */\n\nimport type { FieldSpec } from \"../forms\";\n\nexport type ControlValue = string | boolean | File | null;\nexport type FormState = Record<string, ControlValue>;\n\n/** Stand-in path for an image chosen but not yet uploaded — replaced by ADMIN-06. */\nexport const PENDING_UPLOAD_PREFIX = \"pending-upload/\";\n\nexport function toControl(spec: FieldSpec, raw: unknown): ControlValue {\n if (raw === undefined || raw === null) return spec.input === \"boolean\" ? false : \"\";\n switch (spec.input) {\n case \"boolean\":\n return Boolean(raw);\n case \"date\":\n return raw instanceof Date ? raw.toISOString().slice(0, 10) : String(raw);\n case \"tags\":\n return Array.isArray(raw) ? raw.join(\", \") : String(raw);\n default:\n return String(raw);\n }\n}\n\n/** Build the object that goes to schema.safeParse. Empty optional fields are omitted. */\nexport function toFrontmatter(\n specs: FieldSpec[],\n state: FormState,\n hiddenValues: Record<string, unknown>,\n): { frontmatter: Record<string, unknown>; files: Record<string, File> } {\n const frontmatter: Record<string, unknown> = { ...hiddenValues };\n const files: Record<string, File> = {};\n\n for (const spec of specs) {\n if (spec.role === \"hidden\" || spec.role === \"body\") continue;\n const v = state[spec.name];\n\n if (spec.input === \"boolean\") {\n if (v === true) frontmatter[spec.name] = true;\n else if (spec.required) frontmatter[spec.name] = false;\n continue;\n }\n if (v instanceof File) {\n files[spec.name] = v;\n frontmatter[spec.name] = PENDING_UPLOAD_PREFIX + v.name;\n continue;\n }\n const s = typeof v === \"string\" ? v.trim() : \"\";\n if (s === \"\") {\n // Required fields are sent empty so the schema's own \"is required\" message fires.\n if (spec.required) frontmatter[spec.name] = spec.input === \"tags\" ? [] : \"\";\n continue;\n }\n switch (spec.input) {\n case \"number\": {\n const n = Number(s);\n frontmatter[spec.name] = Number.isNaN(n) ? s : n;\n break;\n }\n case \"tags\":\n frontmatter[spec.name] = s.split(\",\").map((t) => t.trim()).filter(Boolean);\n break;\n default:\n frontmatter[spec.name] = s;\n }\n }\n return { frontmatter, files };\n}\n","/**\n * ContentForm — a ContentTypeEntry in, validated { frontmatter, body, files } out.\n *\n * Validation runs on submit. After the first failed submit it also re-runs on\n * blur, so the client watches the error clear as they fix it. Before that\n * first submit the form stays quiet — nagging someone who hasn't finished\n * typing is the wrong default for this audience.\n *\n * The schema is the only judge. This component never decides what is valid;\n * it assembles values, calls entry.schema.safeParse, and maps issues back to\n * fields with the schema's own messages.\n */\n\nimport { useCallback, useEffect, useId, useMemo, useState, type FormEvent } from \"react\";\nimport type { ContentTypeEntry, FieldSpec } from \"../forms\";\nimport { walkSchema } from \"../forms\";\nimport { Field } from \"./Field\";\nimport { groupIssues, mapIssues, type FieldIssue } from \"./issues\";\nimport { FORM_CSS } from \"./styles\";\nimport { toControl, toFrontmatter, type ControlValue, type FormState } from \"./values\";\n\nexport interface ContentFormSubmission {\n frontmatter: Record<string, unknown>;\n body: string;\n files: Record<string, File>;\n}\n\nexport interface ContentFormProps {\n entry: ContentTypeEntry;\n initialValues?: Record<string, unknown>;\n initialBody?: string;\n onSubmit: (s: ContentFormSubmission) => void | Promise<void>;\n onCancel?: () => void;\n submitLabel?: string;\n className?: string;\n /**\n * Show the MDX body textarea (the content below the frontmatter). Default\n * true — most types have a body. Pass false for frontmatter-only types such\n * as venues. Independent of entry.body, which names a frontmatter FIELD\n * that should be edited as long text.\n */\n showBody?: boolean;\n bodyLabel?: string;\n}\n\ntype SafeParse = { success: true; data: unknown } | { success: false; error: { issues: Parameters<typeof mapIssues>[0] } };\n\nexport function ContentForm({ entry, initialValues = {}, initialBody = \"\", onSubmit, onCancel, submitLabel = \"Save\", className, showBody = true, bodyLabel = \"Content\" }: ContentFormProps) {\n const formId = useId();\n const specs = useMemo(() => walkSchema(entry.schema, entry), [entry]);\n const visible = useMemo(() => specs.filter((s) => s.role !== \"hidden\" && s.role !== \"body\"), [specs]);\n const bodySpec = specs.find((s) => s.role === \"body\");\n\n const [state, setState] = useState<FormState>(() => Object.fromEntries(specs.map((s) => [s.name, toControl(s, initialValues[s.name])])));\n const [body, setBody] = useState(initialBody);\n const [issues, setIssues] = useState<FieldIssue[]>([]);\n const [attempted, setAttempted] = useState(false);\n const [submitting, setSubmitting] = useState(false);\n const [options, setOptions] = useState<Record<string, string[] | \"loading\">>({});\n\n // Hidden fields (and any initial key with no spec) ride through untouched.\n const hiddenValues = useMemo(() => {\n const shown = new Set(visible.map((s) => s.name));\n if (bodySpec) shown.add(bodySpec.name);\n return Object.fromEntries(Object.entries(initialValues).filter(([k]) => !shown.has(k)));\n }, [initialValues, visible, bodySpec]);\n\n // Resolve function option sources once, on mount.\n useEffect(() => {\n let live = true;\n for (const s of visible) {\n if (typeof s.options !== \"function\") continue;\n setOptions((o) => ({ ...o, [s.name]: \"loading\" }));\n Promise.resolve(s.options()).then((list) => {\n if (live) setOptions((o) => ({ ...o, [s.name]: list }));\n });\n }\n return () => {\n live = false;\n };\n }, [visible]);\n\n const validate = useCallback((): SafeParse => {\n const { frontmatter } = toFrontmatter(specs, state, hiddenValues);\n const result = (entry.schema as { safeParse: (v: unknown) => SafeParse }).safeParse(frontmatter);\n setIssues(result.success ? [] : mapIssues(result.error.issues, specs));\n return result;\n }, [entry.schema, specs, state, hiddenValues]);\n\n const handleSubmit = async (e: FormEvent) => {\n e.preventDefault();\n if (submitting) return;\n setAttempted(true);\n const result = validate();\n if (!result.success) return;\n const { files } = toFrontmatter(specs, state, hiddenValues);\n setSubmitting(true);\n try {\n await onSubmit({ frontmatter: result.data as Record<string, unknown>, body, files });\n } finally {\n setSubmitting(false);\n }\n };\n\n const { byField, formLevel } = groupIssues(issues);\n const optionsFor = (s: FieldSpec) => options[s.name] ?? (Array.isArray(s.options) ? s.options : undefined);\n\n return (\n <form className={[\"realiiz-form\", className].filter(Boolean).join(\" \")} onSubmit={handleSubmit} noValidate data-realiiz-content-form={entry.id}>\n <style>{FORM_CSS}</style>\n\n {formLevel.length > 0 && (\n <div className=\"realiiz-form__alert\" role=\"alert\">\n <strong>This can't be saved yet.</strong>\n <ul>{formLevel.map((i, n) => <li key={n}>{i.message}</li>)}</ul>\n </div>\n )}\n\n <fieldset>\n <legend>{entry.label}</legend>\n {visible.map((s) => (\n <Field key={s.name} formId={formId} spec={s} value={state[s.name]} issues={byField.get(s.name) ?? []}\n current={typeof initialValues[s.name] === \"string\" ? (initialValues[s.name] as string) : undefined}\n options={optionsFor(s)}\n onChange={(v: ControlValue) => setState((st) => ({ ...st, [s.name]: v }))}\n onBlur={() => { if (attempted) validate(); }} />\n ))}\n </fieldset>\n\n {(showBody || bodySpec) && (\n <fieldset>\n <legend>{bodySpec?.label ?? bodyLabel}</legend>\n <label className=\"realiiz-form__label\" htmlFor={`${formId}-body`}>{bodySpec?.label ?? bodyLabel}</label>\n <textarea id={`${formId}-body`} name=\"body\" className=\"realiiz-form__control realiiz-form__control--body\"\n value={body} placeholder={bodySpec?.placeholder ?? \"Write in Markdown. Headings with ##, links as [text](url).\"}\n onChange={(e) => setBody(e.target.value)} />\n </fieldset>\n )}\n\n <div className=\"realiiz-form__actions\">\n {onCancel && <button type=\"button\" className=\"realiiz-form__button\" onClick={onCancel}>Cancel</button>}\n <button type=\"submit\" className=\"realiiz-form__button realiiz-form__button--primary\" disabled={submitting}>\n {submitting ? `${submitLabel}…` : submitLabel}\n </button>\n </div>\n </form>\n );\n}\n"]}
|
|
@@ -15,8 +15,16 @@ interface ContentFormProps {
|
|
|
15
15
|
onCancel?: () => void;
|
|
16
16
|
submitLabel?: string;
|
|
17
17
|
className?: string;
|
|
18
|
+
/**
|
|
19
|
+
* Show the MDX body textarea (the content below the frontmatter). Default
|
|
20
|
+
* true — most types have a body. Pass false for frontmatter-only types such
|
|
21
|
+
* as venues. Independent of entry.body, which names a frontmatter FIELD
|
|
22
|
+
* that should be edited as long text.
|
|
23
|
+
*/
|
|
24
|
+
showBody?: boolean;
|
|
25
|
+
bodyLabel?: string;
|
|
18
26
|
}
|
|
19
|
-
declare function ContentForm({ entry, initialValues, initialBody, onSubmit, onCancel, submitLabel, className }: ContentFormProps): react.JSX.Element;
|
|
27
|
+
declare function ContentForm({ entry, initialValues, initialBody, onSubmit, onCancel, submitLabel, className, showBody, bodyLabel }: ContentFormProps): react.JSX.Element;
|
|
20
28
|
|
|
21
29
|
/**
|
|
22
30
|
* Control strings ↔ frontmatter values. Pure.
|
package/dist/forms-ui/index.d.ts
CHANGED
|
@@ -15,8 +15,16 @@ interface ContentFormProps {
|
|
|
15
15
|
onCancel?: () => void;
|
|
16
16
|
submitLabel?: string;
|
|
17
17
|
className?: string;
|
|
18
|
+
/**
|
|
19
|
+
* Show the MDX body textarea (the content below the frontmatter). Default
|
|
20
|
+
* true — most types have a body. Pass false for frontmatter-only types such
|
|
21
|
+
* as venues. Independent of entry.body, which names a frontmatter FIELD
|
|
22
|
+
* that should be edited as long text.
|
|
23
|
+
*/
|
|
24
|
+
showBody?: boolean;
|
|
25
|
+
bodyLabel?: string;
|
|
18
26
|
}
|
|
19
|
-
declare function ContentForm({ entry, initialValues, initialBody, onSubmit, onCancel, submitLabel, className }: ContentFormProps): react.JSX.Element;
|
|
27
|
+
declare function ContentForm({ entry, initialValues, initialBody, onSubmit, onCancel, submitLabel, className, showBody, bodyLabel }: ContentFormProps): react.JSX.Element;
|
|
20
28
|
|
|
21
29
|
/**
|
|
22
30
|
* Control strings ↔ frontmatter values. Pure.
|
package/dist/forms-ui/index.js
CHANGED
|
@@ -233,7 +233,7 @@ function toFrontmatter(specs, state, hiddenValues) {
|
|
|
233
233
|
}
|
|
234
234
|
return { frontmatter, files };
|
|
235
235
|
}
|
|
236
|
-
function ContentForm({ entry, initialValues = {}, initialBody = "", onSubmit, onCancel, submitLabel = "Save", className }) {
|
|
236
|
+
function ContentForm({ entry, initialValues = {}, initialBody = "", onSubmit, onCancel, submitLabel = "Save", className, showBody = true, bodyLabel = "Content" }) {
|
|
237
237
|
const formId = useId();
|
|
238
238
|
const specs = useMemo(() => walkSchema(entry.schema, entry), [entry]);
|
|
239
239
|
const visible = useMemo(() => specs.filter((s) => s.role !== "hidden" && s.role !== "body"), [specs]);
|
|
@@ -309,9 +309,9 @@ function ContentForm({ entry, initialValues = {}, initialBody = "", onSubmit, on
|
|
|
309
309
|
s.name
|
|
310
310
|
))
|
|
311
311
|
] }),
|
|
312
|
-
bodySpec && /* @__PURE__ */ jsxs("fieldset", { children: [
|
|
313
|
-
/* @__PURE__ */ jsx("legend", { children: bodySpec
|
|
314
|
-
/* @__PURE__ */ jsx("label", { className: "realiiz-form__label", htmlFor: `${formId}-body`, children: bodySpec
|
|
312
|
+
(showBody || bodySpec) && /* @__PURE__ */ jsxs("fieldset", { children: [
|
|
313
|
+
/* @__PURE__ */ jsx("legend", { children: bodySpec?.label ?? bodyLabel }),
|
|
314
|
+
/* @__PURE__ */ jsx("label", { className: "realiiz-form__label", htmlFor: `${formId}-body`, children: bodySpec?.label ?? bodyLabel }),
|
|
315
315
|
/* @__PURE__ */ jsx(
|
|
316
316
|
"textarea",
|
|
317
317
|
{
|
|
@@ -319,7 +319,7 @@ function ContentForm({ entry, initialValues = {}, initialBody = "", onSubmit, on
|
|
|
319
319
|
name: "body",
|
|
320
320
|
className: "realiiz-form__control realiiz-form__control--body",
|
|
321
321
|
value: body,
|
|
322
|
-
placeholder: bodySpec.
|
|
322
|
+
placeholder: bodySpec?.placeholder ?? "Write in Markdown. Headings with ##, links as [text](url).",
|
|
323
323
|
onChange: (e) => setBody(e.target.value)
|
|
324
324
|
}
|
|
325
325
|
)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/forms-ui/controls.tsx","../../src/forms-ui/Field.tsx","../../src/forms-ui/issues.ts","../../src/forms-ui/values.ts","../../src/forms-ui/ContentForm.tsx"],"names":["jsx","jsxs"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * One control per InputKind. Dumb: value in, change out. Field.tsx owns the\n * label, helper row and error; ContentForm owns state.\n */\n\nimport type { FieldSpec } from \"../forms\";\nimport type { ControlValue } from \"./values\";\n\nexport interface ControlProps {\n id: string;\n spec: FieldSpec;\n value: ControlValue;\n invalid: boolean;\n options?: string[] | \"loading\";\n onChange: (v: ControlValue) => void;\n onBlur: () => void;\n}\n\nexport function Control(p: ControlProps) {\n const cls = [\"realiiz-form__control\", p.invalid && \"realiiz-form__control--invalid\"].filter(Boolean).join(\" \");\n const common = {\n id: p.id,\n name: p.spec.name,\n \"aria-invalid\": p.invalid || undefined,\n \"aria-describedby\": p.invalid ? `${p.id}-error` : undefined,\n onBlur: p.onBlur,\n } as const;\n const str = typeof p.value === \"string\" ? p.value : \"\";\n\n if (p.options === \"loading\") {\n return <div className={cls} aria-busy=\"true\" style={{ color: \"var(--_muted)\" }}>Loading options…</div>;\n }\n\n if (p.options || p.spec.input === \"select\") {\n return (\n <select {...common} className={cls} value={str} onChange={(e) => p.onChange(e.target.value)}>\n {!p.spec.required && <option value=\"\">—</option>}\n {(p.options ?? []).map((o) => (\n <option key={o} value={o}>{o}</option>\n ))}\n </select>\n );\n }\n\n switch (p.spec.input) {\n case \"textarea\":\n return (\n <textarea {...common} className={`${cls} realiiz-form__control--textarea`} value={str}\n placeholder={p.spec.placeholder} onChange={(e) => p.onChange(e.target.value)} />\n );\n case \"boolean\":\n return (\n <input {...common} type=\"checkbox\" checked={p.value === true} onChange={(e) => p.onChange(e.target.checked)} />\n );\n case \"image\":\n return (\n <input {...common} type=\"file\" accept=\"image/*\" className={cls}\n onChange={(e) => p.onChange(e.target.files?.[0] ?? null)} />\n );\n case \"date\":\n return <input {...common} type=\"date\" className={cls} value={str} onChange={(e) => p.onChange(e.target.value)} />;\n case \"number\":\n return (\n <input {...common} type=\"number\" inputMode=\"numeric\" className={cls} value={str}\n min={p.spec.constraints.min} max={p.spec.constraints.max}\n step={p.spec.constraints.format === \"int\" ? 1 : undefined}\n placeholder={p.spec.placeholder} onChange={(e) => p.onChange(e.target.value)} />\n );\n case \"url\":\n return (\n <input {...common} type=\"url\" className={cls} value={str} placeholder={p.spec.placeholder ?? \"https://\"}\n onChange={(e) => p.onChange(e.target.value)} />\n );\n case \"tags\":\n return (\n <input {...common} type=\"text\" className={cls} value={str}\n placeholder={p.spec.placeholder ?? \"one, two, three\"} onChange={(e) => p.onChange(e.target.value)} />\n );\n default:\n return (\n <input {...common} type=\"text\" className={cls} value={str} placeholder={p.spec.placeholder}\n maxLength={p.spec.constraints.max} onChange={(e) => p.onChange(e.target.value)} />\n );\n }\n}\n","/**\n * Field anatomy: label (+ required marker) → control → helper row (character\n * count when max exists) → error text. The error sits under the control,\n * never in a tooltip — the audience is a non-technical client.\n */\n\nimport type { FieldSpec } from \"../forms\";\nimport type { FieldIssue } from \"./issues\";\nimport { Control } from \"./controls\";\nimport type { ControlValue } from \"./values\";\n\nexport interface FieldProps {\n formId: string;\n spec: FieldSpec;\n value: ControlValue;\n current?: string;\n issues: FieldIssue[];\n options?: string[] | \"loading\";\n onChange: (v: ControlValue) => void;\n onBlur: () => void;\n}\n\nexport function Field(p: FieldProps) {\n const id = `${p.formId}-${p.spec.name}`;\n const invalid = p.issues.length > 0;\n const max = p.spec.constraints.max;\n const showCount = typeof max === \"number\" && (p.spec.input === \"text\" || p.spec.input === \"textarea\");\n const len = typeof p.value === \"string\" ? p.value.length : 0;\n const isCheck = p.spec.input === \"boolean\";\n\n const control = (\n <Control id={id} spec={p.spec} value={p.value} invalid={invalid} options={p.options} onChange={p.onChange} onBlur={p.onBlur} />\n );\n\n return (\n <div className={`realiiz-form__field realiiz-form__field--${p.spec.input}`} data-field={p.spec.name}>\n {isCheck ? (\n <label className=\"realiiz-form__check\" htmlFor={id}>\n {control}\n <span>{p.spec.label}{p.spec.required && <span className=\"realiiz-form__required\" aria-hidden=\"true\">*</span>}</span>\n </label>\n ) : (\n <>\n <label className=\"realiiz-form__label\" htmlFor={id}>\n {p.spec.label}\n {p.spec.required && <span className=\"realiiz-form__required\" aria-hidden=\"true\">*</span>}\n </label>\n {control}\n </>\n )}\n {p.spec.input === \"image\" && p.current && !(p.value instanceof File) && (\n <div className=\"realiiz-form__current\">Current: {p.current}</div>\n )}\n {showCount && (\n <div className=\"realiiz-form__helper\">\n <span className=\"realiiz-form__count\">{len} / {max}</span>\n </div>\n )}\n {invalid && (\n <div className=\"realiiz-form__error\" id={`${id}-error`}>\n {p.issues.map((i, n) => <div key={n}>{i.message}</div>)}\n </div>\n )}\n </div>\n );\n}\n","/**\n * zod issue → { field, key, message }. Pure.\n *\n * The key matches FieldSpec.messages so the schema's own per-check wording is\n * what the client reads. Observed on zod 4.5.4 (see the ADMIN-02b brief):\n * too_small / too_big origin string|array → min_length / max_length\n * origin number → greater_than / less_than\n * when the spec has `exact` → length_equals\n * invalid_format format regex|url|… → that format\n * invalid_type + format safeint (from .int()) → int\n * custom → custom\n * unrecognized_keys path [] , keys: [...] → form-level\n */\n\nimport type { FieldSpec } from \"../forms\";\n\nexport interface FieldIssue {\n /** null for form-level issues. */\n field: string | null;\n key: string;\n message: string;\n}\n\ninterface ZodIssueLike {\n code: string;\n path: PropertyKey[];\n message: string;\n origin?: string;\n format?: string;\n keys?: string[];\n}\n\nexport function mapIssues(issues: ZodIssueLike[], specs: FieldSpec[]): FieldIssue[] {\n const byName = new Map(specs.map((s) => [s.name, s]));\n return issues.map((issue) => {\n const field = typeof issue.path[0] === \"string\" ? issue.path[0] : null;\n const spec = field ? byName.get(field) : undefined;\n const key = keyFor(issue, spec);\n const message =\n (spec?.messages[key] ?? (issue.code === \"unrecognized_keys\" ? unknownKeysMessage(issue) : undefined)) ??\n issue.message;\n return { field, key, message };\n });\n}\n\nfunction keyFor(issue: ZodIssueLike, spec?: FieldSpec): string {\n const numeric = issue.origin === \"number\" || issue.origin === \"int\";\n switch (issue.code) {\n case \"too_small\":\n if (spec?.constraints.exact !== undefined) return \"length_equals\";\n return numeric ? \"greater_than\" : \"min_length\";\n case \"too_big\":\n if (spec?.constraints.exact !== undefined) return \"length_equals\";\n return numeric ? \"less_than\" : \"max_length\";\n case \"invalid_format\":\n return issue.format ?? \"format\";\n case \"invalid_type\":\n return issue.format === \"safeint\" || issue.format === \"int32\" ? \"int\" : \"invalid_type\";\n default:\n return issue.code;\n }\n}\n\nfunction unknownKeysMessage(issue: ZodIssueLike): string {\n const keys = issue.keys ?? [];\n return `This file has ${keys.length === 1 ? \"a field\" : \"fields\"} the site does not recognise: ${keys.join(\", \")}. It was probably edited by hand — remove or rename ${keys.length === 1 ? \"it\" : \"them\"} to continue.`;\n}\n\n/** Group by field for rendering. */\nexport function groupIssues(list: FieldIssue[]): { byField: Map<string, FieldIssue[]>; formLevel: FieldIssue[] } {\n const byField = new Map<string, FieldIssue[]>();\n const formLevel: FieldIssue[] = [];\n for (const i of list) {\n if (i.field === null) formLevel.push(i);\n else byField.set(i.field, [...(byField.get(i.field) ?? []), i]);\n }\n return { byField, formLevel };\n}\n","/**\n * Control strings ↔ frontmatter values. Pure.\n *\n * Controls hold strings (or booleans / Files). The schema wants typed values.\n * `toControl` seeds a control from an existing frontmatter value — including\n * a Date from a previously-parsed file — and `toValue` assembles what goes to\n * safeParse. Anything the schema then coerces (dates) is the schema's business.\n */\n\nimport type { FieldSpec } from \"../forms\";\n\nexport type ControlValue = string | boolean | File | null;\nexport type FormState = Record<string, ControlValue>;\n\n/** Stand-in path for an image chosen but not yet uploaded — replaced by ADMIN-06. */\nexport const PENDING_UPLOAD_PREFIX = \"pending-upload/\";\n\nexport function toControl(spec: FieldSpec, raw: unknown): ControlValue {\n if (raw === undefined || raw === null) return spec.input === \"boolean\" ? false : \"\";\n switch (spec.input) {\n case \"boolean\":\n return Boolean(raw);\n case \"date\":\n return raw instanceof Date ? raw.toISOString().slice(0, 10) : String(raw);\n case \"tags\":\n return Array.isArray(raw) ? raw.join(\", \") : String(raw);\n default:\n return String(raw);\n }\n}\n\n/** Build the object that goes to schema.safeParse. Empty optional fields are omitted. */\nexport function toFrontmatter(\n specs: FieldSpec[],\n state: FormState,\n hiddenValues: Record<string, unknown>,\n): { frontmatter: Record<string, unknown>; files: Record<string, File> } {\n const frontmatter: Record<string, unknown> = { ...hiddenValues };\n const files: Record<string, File> = {};\n\n for (const spec of specs) {\n if (spec.role === \"hidden\" || spec.role === \"body\") continue;\n const v = state[spec.name];\n\n if (spec.input === \"boolean\") {\n if (v === true) frontmatter[spec.name] = true;\n else if (spec.required) frontmatter[spec.name] = false;\n continue;\n }\n if (v instanceof File) {\n files[spec.name] = v;\n frontmatter[spec.name] = PENDING_UPLOAD_PREFIX + v.name;\n continue;\n }\n const s = typeof v === \"string\" ? v.trim() : \"\";\n if (s === \"\") {\n // Required fields are sent empty so the schema's own \"is required\" message fires.\n if (spec.required) frontmatter[spec.name] = spec.input === \"tags\" ? [] : \"\";\n continue;\n }\n switch (spec.input) {\n case \"number\": {\n const n = Number(s);\n frontmatter[spec.name] = Number.isNaN(n) ? s : n;\n break;\n }\n case \"tags\":\n frontmatter[spec.name] = s.split(\",\").map((t) => t.trim()).filter(Boolean);\n break;\n default:\n frontmatter[spec.name] = s;\n }\n }\n return { frontmatter, files };\n}\n","/**\n * ContentForm — a ContentTypeEntry in, validated { frontmatter, body, files } out.\n *\n * Validation runs on submit. After the first failed submit it also re-runs on\n * blur, so the client watches the error clear as they fix it. Before that\n * first submit the form stays quiet — nagging someone who hasn't finished\n * typing is the wrong default for this audience.\n *\n * The schema is the only judge. This component never decides what is valid;\n * it assembles values, calls entry.schema.safeParse, and maps issues back to\n * fields with the schema's own messages.\n */\n\nimport { useCallback, useEffect, useId, useMemo, useState, type FormEvent } from \"react\";\nimport type { ContentTypeEntry, FieldSpec } from \"../forms\";\nimport { walkSchema } from \"../forms\";\nimport { Field } from \"./Field\";\nimport { groupIssues, mapIssues, type FieldIssue } from \"./issues\";\nimport { FORM_CSS } from \"./styles\";\nimport { toControl, toFrontmatter, type ControlValue, type FormState } from \"./values\";\n\nexport interface ContentFormSubmission {\n frontmatter: Record<string, unknown>;\n body: string;\n files: Record<string, File>;\n}\n\nexport interface ContentFormProps {\n entry: ContentTypeEntry;\n initialValues?: Record<string, unknown>;\n initialBody?: string;\n onSubmit: (s: ContentFormSubmission) => void | Promise<void>;\n onCancel?: () => void;\n submitLabel?: string;\n className?: string;\n}\n\ntype SafeParse = { success: true; data: unknown } | { success: false; error: { issues: Parameters<typeof mapIssues>[0] } };\n\nexport function ContentForm({ entry, initialValues = {}, initialBody = \"\", onSubmit, onCancel, submitLabel = \"Save\", className }: ContentFormProps) {\n const formId = useId();\n const specs = useMemo(() => walkSchema(entry.schema, entry), [entry]);\n const visible = useMemo(() => specs.filter((s) => s.role !== \"hidden\" && s.role !== \"body\"), [specs]);\n const bodySpec = specs.find((s) => s.role === \"body\");\n\n const [state, setState] = useState<FormState>(() => Object.fromEntries(specs.map((s) => [s.name, toControl(s, initialValues[s.name])])));\n const [body, setBody] = useState(initialBody);\n const [issues, setIssues] = useState<FieldIssue[]>([]);\n const [attempted, setAttempted] = useState(false);\n const [submitting, setSubmitting] = useState(false);\n const [options, setOptions] = useState<Record<string, string[] | \"loading\">>({});\n\n // Hidden fields (and any initial key with no spec) ride through untouched.\n const hiddenValues = useMemo(() => {\n const shown = new Set(visible.map((s) => s.name));\n if (bodySpec) shown.add(bodySpec.name);\n return Object.fromEntries(Object.entries(initialValues).filter(([k]) => !shown.has(k)));\n }, [initialValues, visible, bodySpec]);\n\n // Resolve function option sources once, on mount.\n useEffect(() => {\n let live = true;\n for (const s of visible) {\n if (typeof s.options !== \"function\") continue;\n setOptions((o) => ({ ...o, [s.name]: \"loading\" }));\n Promise.resolve(s.options()).then((list) => {\n if (live) setOptions((o) => ({ ...o, [s.name]: list }));\n });\n }\n return () => {\n live = false;\n };\n }, [visible]);\n\n const validate = useCallback((): SafeParse => {\n const { frontmatter } = toFrontmatter(specs, state, hiddenValues);\n const result = (entry.schema as { safeParse: (v: unknown) => SafeParse }).safeParse(frontmatter);\n setIssues(result.success ? [] : mapIssues(result.error.issues, specs));\n return result;\n }, [entry.schema, specs, state, hiddenValues]);\n\n const handleSubmit = async (e: FormEvent) => {\n e.preventDefault();\n if (submitting) return;\n setAttempted(true);\n const result = validate();\n if (!result.success) return;\n const { files } = toFrontmatter(specs, state, hiddenValues);\n setSubmitting(true);\n try {\n await onSubmit({ frontmatter: result.data as Record<string, unknown>, body, files });\n } finally {\n setSubmitting(false);\n }\n };\n\n const { byField, formLevel } = groupIssues(issues);\n const optionsFor = (s: FieldSpec) => options[s.name] ?? (Array.isArray(s.options) ? s.options : undefined);\n\n return (\n <form className={[\"realiiz-form\", className].filter(Boolean).join(\" \")} onSubmit={handleSubmit} noValidate data-realiiz-content-form={entry.id}>\n <style>{FORM_CSS}</style>\n\n {formLevel.length > 0 && (\n <div className=\"realiiz-form__alert\" role=\"alert\">\n <strong>This can't be saved yet.</strong>\n <ul>{formLevel.map((i, n) => <li key={n}>{i.message}</li>)}</ul>\n </div>\n )}\n\n <fieldset>\n <legend>{entry.label}</legend>\n {visible.map((s) => (\n <Field key={s.name} formId={formId} spec={s} value={state[s.name]} issues={byField.get(s.name) ?? []}\n current={typeof initialValues[s.name] === \"string\" ? (initialValues[s.name] as string) : undefined}\n options={optionsFor(s)}\n onChange={(v: ControlValue) => setState((st) => ({ ...st, [s.name]: v }))}\n onBlur={() => { if (attempted) validate(); }} />\n ))}\n </fieldset>\n\n {bodySpec && (\n <fieldset>\n <legend>{bodySpec.label}</legend>\n <label className=\"realiiz-form__label\" htmlFor={`${formId}-body`}>{bodySpec.label}</label>\n <textarea id={`${formId}-body`} name=\"body\" className=\"realiiz-form__control realiiz-form__control--body\"\n value={body} placeholder={bodySpec.placeholder} onChange={(e) => setBody(e.target.value)} />\n </fieldset>\n )}\n\n <div className=\"realiiz-form__actions\">\n {onCancel && <button type=\"button\" className=\"realiiz-form__button\" onClick={onCancel}>Cancel</button>}\n <button type=\"submit\" className=\"realiiz-form__button realiiz-form__button--primary\" disabled={submitting}>\n {submitting ? `${submitLabel}…` : submitLabel}\n </button>\n </div>\n </form>\n );\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/forms-ui/controls.tsx","../../src/forms-ui/Field.tsx","../../src/forms-ui/issues.ts","../../src/forms-ui/values.ts","../../src/forms-ui/ContentForm.tsx"],"names":["jsx","jsxs"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * One control per InputKind. Dumb: value in, change out. Field.tsx owns the\n * label, helper row and error; ContentForm owns state.\n */\n\nimport type { FieldSpec } from \"../forms\";\nimport type { ControlValue } from \"./values\";\n\nexport interface ControlProps {\n id: string;\n spec: FieldSpec;\n value: ControlValue;\n invalid: boolean;\n options?: string[] | \"loading\";\n onChange: (v: ControlValue) => void;\n onBlur: () => void;\n}\n\nexport function Control(p: ControlProps) {\n const cls = [\"realiiz-form__control\", p.invalid && \"realiiz-form__control--invalid\"].filter(Boolean).join(\" \");\n const common = {\n id: p.id,\n name: p.spec.name,\n \"aria-invalid\": p.invalid || undefined,\n \"aria-describedby\": p.invalid ? `${p.id}-error` : undefined,\n onBlur: p.onBlur,\n } as const;\n const str = typeof p.value === \"string\" ? p.value : \"\";\n\n if (p.options === \"loading\") {\n return <div className={cls} aria-busy=\"true\" style={{ color: \"var(--_muted)\" }}>Loading options…</div>;\n }\n\n if (p.options || p.spec.input === \"select\") {\n return (\n <select {...common} className={cls} value={str} onChange={(e) => p.onChange(e.target.value)}>\n {!p.spec.required && <option value=\"\">—</option>}\n {(p.options ?? []).map((o) => (\n <option key={o} value={o}>{o}</option>\n ))}\n </select>\n );\n }\n\n switch (p.spec.input) {\n case \"textarea\":\n return (\n <textarea {...common} className={`${cls} realiiz-form__control--textarea`} value={str}\n placeholder={p.spec.placeholder} onChange={(e) => p.onChange(e.target.value)} />\n );\n case \"boolean\":\n return (\n <input {...common} type=\"checkbox\" checked={p.value === true} onChange={(e) => p.onChange(e.target.checked)} />\n );\n case \"image\":\n return (\n <input {...common} type=\"file\" accept=\"image/*\" className={cls}\n onChange={(e) => p.onChange(e.target.files?.[0] ?? null)} />\n );\n case \"date\":\n return <input {...common} type=\"date\" className={cls} value={str} onChange={(e) => p.onChange(e.target.value)} />;\n case \"number\":\n return (\n <input {...common} type=\"number\" inputMode=\"numeric\" className={cls} value={str}\n min={p.spec.constraints.min} max={p.spec.constraints.max}\n step={p.spec.constraints.format === \"int\" ? 1 : undefined}\n placeholder={p.spec.placeholder} onChange={(e) => p.onChange(e.target.value)} />\n );\n case \"url\":\n return (\n <input {...common} type=\"url\" className={cls} value={str} placeholder={p.spec.placeholder ?? \"https://\"}\n onChange={(e) => p.onChange(e.target.value)} />\n );\n case \"tags\":\n return (\n <input {...common} type=\"text\" className={cls} value={str}\n placeholder={p.spec.placeholder ?? \"one, two, three\"} onChange={(e) => p.onChange(e.target.value)} />\n );\n default:\n return (\n <input {...common} type=\"text\" className={cls} value={str} placeholder={p.spec.placeholder}\n maxLength={p.spec.constraints.max} onChange={(e) => p.onChange(e.target.value)} />\n );\n }\n}\n","/**\n * Field anatomy: label (+ required marker) → control → helper row (character\n * count when max exists) → error text. The error sits under the control,\n * never in a tooltip — the audience is a non-technical client.\n */\n\nimport type { FieldSpec } from \"../forms\";\nimport type { FieldIssue } from \"./issues\";\nimport { Control } from \"./controls\";\nimport type { ControlValue } from \"./values\";\n\nexport interface FieldProps {\n formId: string;\n spec: FieldSpec;\n value: ControlValue;\n current?: string;\n issues: FieldIssue[];\n options?: string[] | \"loading\";\n onChange: (v: ControlValue) => void;\n onBlur: () => void;\n}\n\nexport function Field(p: FieldProps) {\n const id = `${p.formId}-${p.spec.name}`;\n const invalid = p.issues.length > 0;\n const max = p.spec.constraints.max;\n const showCount = typeof max === \"number\" && (p.spec.input === \"text\" || p.spec.input === \"textarea\");\n const len = typeof p.value === \"string\" ? p.value.length : 0;\n const isCheck = p.spec.input === \"boolean\";\n\n const control = (\n <Control id={id} spec={p.spec} value={p.value} invalid={invalid} options={p.options} onChange={p.onChange} onBlur={p.onBlur} />\n );\n\n return (\n <div className={`realiiz-form__field realiiz-form__field--${p.spec.input}`} data-field={p.spec.name}>\n {isCheck ? (\n <label className=\"realiiz-form__check\" htmlFor={id}>\n {control}\n <span>{p.spec.label}{p.spec.required && <span className=\"realiiz-form__required\" aria-hidden=\"true\">*</span>}</span>\n </label>\n ) : (\n <>\n <label className=\"realiiz-form__label\" htmlFor={id}>\n {p.spec.label}\n {p.spec.required && <span className=\"realiiz-form__required\" aria-hidden=\"true\">*</span>}\n </label>\n {control}\n </>\n )}\n {p.spec.input === \"image\" && p.current && !(p.value instanceof File) && (\n <div className=\"realiiz-form__current\">Current: {p.current}</div>\n )}\n {showCount && (\n <div className=\"realiiz-form__helper\">\n <span className=\"realiiz-form__count\">{len} / {max}</span>\n </div>\n )}\n {invalid && (\n <div className=\"realiiz-form__error\" id={`${id}-error`}>\n {p.issues.map((i, n) => <div key={n}>{i.message}</div>)}\n </div>\n )}\n </div>\n );\n}\n","/**\n * zod issue → { field, key, message }. Pure.\n *\n * The key matches FieldSpec.messages so the schema's own per-check wording is\n * what the client reads. Observed on zod 4.5.4 (see the ADMIN-02b brief):\n * too_small / too_big origin string|array → min_length / max_length\n * origin number → greater_than / less_than\n * when the spec has `exact` → length_equals\n * invalid_format format regex|url|… → that format\n * invalid_type + format safeint (from .int()) → int\n * custom → custom\n * unrecognized_keys path [] , keys: [...] → form-level\n */\n\nimport type { FieldSpec } from \"../forms\";\n\nexport interface FieldIssue {\n /** null for form-level issues. */\n field: string | null;\n key: string;\n message: string;\n}\n\ninterface ZodIssueLike {\n code: string;\n path: PropertyKey[];\n message: string;\n origin?: string;\n format?: string;\n keys?: string[];\n}\n\nexport function mapIssues(issues: ZodIssueLike[], specs: FieldSpec[]): FieldIssue[] {\n const byName = new Map(specs.map((s) => [s.name, s]));\n return issues.map((issue) => {\n const field = typeof issue.path[0] === \"string\" ? issue.path[0] : null;\n const spec = field ? byName.get(field) : undefined;\n const key = keyFor(issue, spec);\n const message =\n (spec?.messages[key] ?? (issue.code === \"unrecognized_keys\" ? unknownKeysMessage(issue) : undefined)) ??\n issue.message;\n return { field, key, message };\n });\n}\n\nfunction keyFor(issue: ZodIssueLike, spec?: FieldSpec): string {\n const numeric = issue.origin === \"number\" || issue.origin === \"int\";\n switch (issue.code) {\n case \"too_small\":\n if (spec?.constraints.exact !== undefined) return \"length_equals\";\n return numeric ? \"greater_than\" : \"min_length\";\n case \"too_big\":\n if (spec?.constraints.exact !== undefined) return \"length_equals\";\n return numeric ? \"less_than\" : \"max_length\";\n case \"invalid_format\":\n return issue.format ?? \"format\";\n case \"invalid_type\":\n return issue.format === \"safeint\" || issue.format === \"int32\" ? \"int\" : \"invalid_type\";\n default:\n return issue.code;\n }\n}\n\nfunction unknownKeysMessage(issue: ZodIssueLike): string {\n const keys = issue.keys ?? [];\n return `This file has ${keys.length === 1 ? \"a field\" : \"fields\"} the site does not recognise: ${keys.join(\", \")}. It was probably edited by hand — remove or rename ${keys.length === 1 ? \"it\" : \"them\"} to continue.`;\n}\n\n/** Group by field for rendering. */\nexport function groupIssues(list: FieldIssue[]): { byField: Map<string, FieldIssue[]>; formLevel: FieldIssue[] } {\n const byField = new Map<string, FieldIssue[]>();\n const formLevel: FieldIssue[] = [];\n for (const i of list) {\n if (i.field === null) formLevel.push(i);\n else byField.set(i.field, [...(byField.get(i.field) ?? []), i]);\n }\n return { byField, formLevel };\n}\n","/**\n * Control strings ↔ frontmatter values. Pure.\n *\n * Controls hold strings (or booleans / Files). The schema wants typed values.\n * `toControl` seeds a control from an existing frontmatter value — including\n * a Date from a previously-parsed file — and `toValue` assembles what goes to\n * safeParse. Anything the schema then coerces (dates) is the schema's business.\n */\n\nimport type { FieldSpec } from \"../forms\";\n\nexport type ControlValue = string | boolean | File | null;\nexport type FormState = Record<string, ControlValue>;\n\n/** Stand-in path for an image chosen but not yet uploaded — replaced by ADMIN-06. */\nexport const PENDING_UPLOAD_PREFIX = \"pending-upload/\";\n\nexport function toControl(spec: FieldSpec, raw: unknown): ControlValue {\n if (raw === undefined || raw === null) return spec.input === \"boolean\" ? false : \"\";\n switch (spec.input) {\n case \"boolean\":\n return Boolean(raw);\n case \"date\":\n return raw instanceof Date ? raw.toISOString().slice(0, 10) : String(raw);\n case \"tags\":\n return Array.isArray(raw) ? raw.join(\", \") : String(raw);\n default:\n return String(raw);\n }\n}\n\n/** Build the object that goes to schema.safeParse. Empty optional fields are omitted. */\nexport function toFrontmatter(\n specs: FieldSpec[],\n state: FormState,\n hiddenValues: Record<string, unknown>,\n): { frontmatter: Record<string, unknown>; files: Record<string, File> } {\n const frontmatter: Record<string, unknown> = { ...hiddenValues };\n const files: Record<string, File> = {};\n\n for (const spec of specs) {\n if (spec.role === \"hidden\" || spec.role === \"body\") continue;\n const v = state[spec.name];\n\n if (spec.input === \"boolean\") {\n if (v === true) frontmatter[spec.name] = true;\n else if (spec.required) frontmatter[spec.name] = false;\n continue;\n }\n if (v instanceof File) {\n files[spec.name] = v;\n frontmatter[spec.name] = PENDING_UPLOAD_PREFIX + v.name;\n continue;\n }\n const s = typeof v === \"string\" ? v.trim() : \"\";\n if (s === \"\") {\n // Required fields are sent empty so the schema's own \"is required\" message fires.\n if (spec.required) frontmatter[spec.name] = spec.input === \"tags\" ? [] : \"\";\n continue;\n }\n switch (spec.input) {\n case \"number\": {\n const n = Number(s);\n frontmatter[spec.name] = Number.isNaN(n) ? s : n;\n break;\n }\n case \"tags\":\n frontmatter[spec.name] = s.split(\",\").map((t) => t.trim()).filter(Boolean);\n break;\n default:\n frontmatter[spec.name] = s;\n }\n }\n return { frontmatter, files };\n}\n","/**\n * ContentForm — a ContentTypeEntry in, validated { frontmatter, body, files } out.\n *\n * Validation runs on submit. After the first failed submit it also re-runs on\n * blur, so the client watches the error clear as they fix it. Before that\n * first submit the form stays quiet — nagging someone who hasn't finished\n * typing is the wrong default for this audience.\n *\n * The schema is the only judge. This component never decides what is valid;\n * it assembles values, calls entry.schema.safeParse, and maps issues back to\n * fields with the schema's own messages.\n */\n\nimport { useCallback, useEffect, useId, useMemo, useState, type FormEvent } from \"react\";\nimport type { ContentTypeEntry, FieldSpec } from \"../forms\";\nimport { walkSchema } from \"../forms\";\nimport { Field } from \"./Field\";\nimport { groupIssues, mapIssues, type FieldIssue } from \"./issues\";\nimport { FORM_CSS } from \"./styles\";\nimport { toControl, toFrontmatter, type ControlValue, type FormState } from \"./values\";\n\nexport interface ContentFormSubmission {\n frontmatter: Record<string, unknown>;\n body: string;\n files: Record<string, File>;\n}\n\nexport interface ContentFormProps {\n entry: ContentTypeEntry;\n initialValues?: Record<string, unknown>;\n initialBody?: string;\n onSubmit: (s: ContentFormSubmission) => void | Promise<void>;\n onCancel?: () => void;\n submitLabel?: string;\n className?: string;\n /**\n * Show the MDX body textarea (the content below the frontmatter). Default\n * true — most types have a body. Pass false for frontmatter-only types such\n * as venues. Independent of entry.body, which names a frontmatter FIELD\n * that should be edited as long text.\n */\n showBody?: boolean;\n bodyLabel?: string;\n}\n\ntype SafeParse = { success: true; data: unknown } | { success: false; error: { issues: Parameters<typeof mapIssues>[0] } };\n\nexport function ContentForm({ entry, initialValues = {}, initialBody = \"\", onSubmit, onCancel, submitLabel = \"Save\", className, showBody = true, bodyLabel = \"Content\" }: ContentFormProps) {\n const formId = useId();\n const specs = useMemo(() => walkSchema(entry.schema, entry), [entry]);\n const visible = useMemo(() => specs.filter((s) => s.role !== \"hidden\" && s.role !== \"body\"), [specs]);\n const bodySpec = specs.find((s) => s.role === \"body\");\n\n const [state, setState] = useState<FormState>(() => Object.fromEntries(specs.map((s) => [s.name, toControl(s, initialValues[s.name])])));\n const [body, setBody] = useState(initialBody);\n const [issues, setIssues] = useState<FieldIssue[]>([]);\n const [attempted, setAttempted] = useState(false);\n const [submitting, setSubmitting] = useState(false);\n const [options, setOptions] = useState<Record<string, string[] | \"loading\">>({});\n\n // Hidden fields (and any initial key with no spec) ride through untouched.\n const hiddenValues = useMemo(() => {\n const shown = new Set(visible.map((s) => s.name));\n if (bodySpec) shown.add(bodySpec.name);\n return Object.fromEntries(Object.entries(initialValues).filter(([k]) => !shown.has(k)));\n }, [initialValues, visible, bodySpec]);\n\n // Resolve function option sources once, on mount.\n useEffect(() => {\n let live = true;\n for (const s of visible) {\n if (typeof s.options !== \"function\") continue;\n setOptions((o) => ({ ...o, [s.name]: \"loading\" }));\n Promise.resolve(s.options()).then((list) => {\n if (live) setOptions((o) => ({ ...o, [s.name]: list }));\n });\n }\n return () => {\n live = false;\n };\n }, [visible]);\n\n const validate = useCallback((): SafeParse => {\n const { frontmatter } = toFrontmatter(specs, state, hiddenValues);\n const result = (entry.schema as { safeParse: (v: unknown) => SafeParse }).safeParse(frontmatter);\n setIssues(result.success ? [] : mapIssues(result.error.issues, specs));\n return result;\n }, [entry.schema, specs, state, hiddenValues]);\n\n const handleSubmit = async (e: FormEvent) => {\n e.preventDefault();\n if (submitting) return;\n setAttempted(true);\n const result = validate();\n if (!result.success) return;\n const { files } = toFrontmatter(specs, state, hiddenValues);\n setSubmitting(true);\n try {\n await onSubmit({ frontmatter: result.data as Record<string, unknown>, body, files });\n } finally {\n setSubmitting(false);\n }\n };\n\n const { byField, formLevel } = groupIssues(issues);\n const optionsFor = (s: FieldSpec) => options[s.name] ?? (Array.isArray(s.options) ? s.options : undefined);\n\n return (\n <form className={[\"realiiz-form\", className].filter(Boolean).join(\" \")} onSubmit={handleSubmit} noValidate data-realiiz-content-form={entry.id}>\n <style>{FORM_CSS}</style>\n\n {formLevel.length > 0 && (\n <div className=\"realiiz-form__alert\" role=\"alert\">\n <strong>This can't be saved yet.</strong>\n <ul>{formLevel.map((i, n) => <li key={n}>{i.message}</li>)}</ul>\n </div>\n )}\n\n <fieldset>\n <legend>{entry.label}</legend>\n {visible.map((s) => (\n <Field key={s.name} formId={formId} spec={s} value={state[s.name]} issues={byField.get(s.name) ?? []}\n current={typeof initialValues[s.name] === \"string\" ? (initialValues[s.name] as string) : undefined}\n options={optionsFor(s)}\n onChange={(v: ControlValue) => setState((st) => ({ ...st, [s.name]: v }))}\n onBlur={() => { if (attempted) validate(); }} />\n ))}\n </fieldset>\n\n {(showBody || bodySpec) && (\n <fieldset>\n <legend>{bodySpec?.label ?? bodyLabel}</legend>\n <label className=\"realiiz-form__label\" htmlFor={`${formId}-body`}>{bodySpec?.label ?? bodyLabel}</label>\n <textarea id={`${formId}-body`} name=\"body\" className=\"realiiz-form__control realiiz-form__control--body\"\n value={body} placeholder={bodySpec?.placeholder ?? \"Write in Markdown. Headings with ##, links as [text](url).\"}\n onChange={(e) => setBody(e.target.value)} />\n </fieldset>\n )}\n\n <div className=\"realiiz-form__actions\">\n {onCancel && <button type=\"button\" className=\"realiiz-form__button\" onClick={onCancel}>Cancel</button>}\n <button type=\"submit\" className=\"realiiz-form__button realiiz-form__button--primary\" disabled={submitting}>\n {submitting ? `${submitLabel}…` : submitLabel}\n </button>\n </div>\n </form>\n );\n}\n"]}
|