30 lines · 1.4 KB
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<string, unknown> | 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;
}