export const EMAIL_UNVERIFIED = "email_unverified"; export const EMAIL_UNVERIFIED_MESSAGE = "Verify your email address before making changes. Check your inbox for the link, or resend it from Settings."; /** Where the resend button lives; the error detail and the banner both link here. */ export const VERIFY_EMAIL_PATH = "/settings"; const HTTP_FORBIDDEN = 403; /** Whether a thrown server error (server function or JSON API) is the unverified-email refusal. */ export function isEmailUnverifiedError(error: unknown): boolean { if (!(error instanceof Error)) return false; const { status, detail } = error as Error & { status?: number; detail?: Record | null }; return status === HTTP_FORBIDDEN && detail?.code === EMAIL_UNVERIFIED; } /** Site admins and viewers counted as verified (see toViewer) may write and pay. */ export function canWriteAsViewer(viewer: { isAdmin: boolean; emailVerified: boolean } | null | undefined): boolean { return Boolean(viewer && (viewer.isAdmin || viewer.emailVerified)); } /** * The refusal text for a write by this viewer, or null when it may write. Deploy, scratch and CI job tokens * resolve to no viewer: they act for their own repository and only a verified admin can mint them. */ export function writeRefusal(viewer: { isAdmin: boolean; emailVerified: boolean } | null): string | null { return viewer && !canWriteAsViewer(viewer) ? EMAIL_UNVERIFIED_MESSAGE : null; }