2026-05-13 20:55:20 -04:00

142 lines
3.6 KiB
TypeScript

import { z } from "zod";
import {
getCurrentAuthRedirect,
getOAuthSignedQuery,
} from "@/lib/auth-redirect";
import { isHostedClientAuthMode } from "@/lib/auth-mode";
import {
getFieldError as getSharedFieldError,
getFormError as getSharedFormError,
} from "@/client/lib/forms";
export const authRedirectSearchSchema = z.object({
redirect: z.string().optional(),
});
export function useAuthPageState(redirect: string | undefined) {
const redirectTo = getCurrentAuthRedirect(redirect);
const oauthQuery =
typeof window !== "undefined"
? getOAuthSignedQuery(window.location.search)
: null;
const isHostedMode = isHostedClientAuthMode();
return {
redirectTo,
oauthQuery,
isHostedMode,
};
}
export function getFieldError(errors: readonly unknown[]) {
return getSharedFieldError(errors);
}
export function getFormError(error: unknown) {
return getSharedFormError(error);
}
export function AuthMethodChooser({
googleLabel,
emailLabel = "Continue with email",
isBusy,
disabled,
onContinueWithGoogle,
onContinueWithEmail,
}: {
googleLabel: string;
emailLabel?: string;
isBusy?: boolean;
disabled?: boolean;
onContinueWithGoogle: () => void;
onContinueWithEmail: () => void;
}) {
return (
<div className="space-y-3">
<button
type="button"
className="btn w-full bg-white text-neutral border border-base-content/15 hover:bg-base-100 hover:border-base-content/25 disabled:bg-base-300"
onClick={onContinueWithGoogle}
disabled={disabled || isBusy}
>
<GoogleLogo />
{isBusy ? "Opening Google..." : googleLabel}
</button>
<button
type="button"
className="btn btn-soft w-full"
onClick={onContinueWithEmail}
disabled={disabled || isBusy}
>
{emailLabel}
</button>
</div>
);
}
function GoogleLogo() {
return (
<svg aria-hidden="true" viewBox="0 0 18 18" className="size-4 shrink-0">
<path
fill="#4285F4"
d="M17.64 9.2c0-.64-.06-1.25-.16-1.84H9v3.48h4.84a4.14 4.14 0 0 1-1.8 2.72v2.26h2.92c1.7-1.57 2.68-3.88 2.68-6.62Z"
/>
<path
fill="#34A853"
d="M9 18c2.43 0 4.47-.8 5.96-2.18l-2.92-2.26c-.8.54-1.84.86-3.04.86-2.34 0-4.33-1.58-5.04-3.72H.94v2.34A9 9 0 0 0 9 18Z"
/>
<path
fill="#FBBC05"
d="M3.96 10.7A5.4 5.4 0 0 1 3.68 9c0-.59.1-1.16.28-1.7V4.96H.94A9 9 0 0 0 0 9c0 1.45.34 2.82.94 4.04l3.02-2.34Z"
/>
<path
fill="#EA4335"
d="M9 3.58c1.32 0 2.5.45 3.44 1.35l2.58-2.58A8.64 8.64 0 0 0 9 0 9 9 0 0 0 .94 4.96L3.96 7.3C4.67 5.16 6.66 3.58 9 3.58Z"
/>
</svg>
);
}
export function AuthPageCard({
title,
helperText,
children,
footer,
}: {
title: string;
helperText?: string;
children: React.ReactNode;
footer?: React.ReactNode;
}) {
return (
<div className="w-full max-w-xs space-y-6">
<div className="text-center space-y-3">
<img
src="/transparent-logo.png"
alt="OpenSEO"
className="mx-auto size-10 rounded-lg"
/>
<div>
<h1 className="text-xl font-semibold">{title}</h1>
{helperText ? (
<p className="text-sm text-base-content/60 mt-1">{helperText}</p>
) : null}
</div>
</div>
{children}
{footer ? <div className="text-center">{footer}</div> : null}
</div>
);
}
export function AuthPageShell({ children }: { children: React.ReactNode }) {
return (
<div className="min-h-[100dvh] flex flex-col items-center justify-center p-4 bg-base-200">
{children}
</div>
);
}