'use client'; import { useState } from 'react'; import { signIn } from 'next-auth/react'; import { useRouter, useSearchParams } from 'next/navigation'; import Link from 'next/link'; import { useTranslations } from 'next-intl'; interface Props { hasOidc: boolean; oidcLabel: string; hasGoogle: boolean; hasGitHub: boolean; } export default function LoginClient({ hasOidc, oidcLabel, hasGoogle, hasGitHub }: Props) { const t = useTranslations('auth.login'); const router = useRouter(); const params = useSearchParams(); const callbackUrl = params.get('callbackUrl') ?? '/'; const resetSuccess = params.get('reset') === '1'; const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); const [error, setError] = useState(null); const [loading, setLoading] = useState(false); const handleCredentials = async (e: React.FormEvent) => { e.preventDefault(); setLoading(true); setError(null); const res = await signIn('credentials', { email, password, redirect: false, }); setLoading(false); if (res?.error) { setError(t('error')); } else { await migrateThenRedirect(callbackUrl, router); } }; return (

{t('title')}

{resetSuccess && (

{t('resetSuccess')}

)}
setEmail(e.target.value)} required autoComplete="email" style={styles.input} /> setPassword(e.target.value)} required autoComplete="current-password" style={styles.input} /> {error &&

{error}

} {t('forgotPassword')}
{(hasGoogle || hasGitHub || hasOidc) &&
{t('or')}
}
{hasGoogle && ( )} {hasGitHub && ( )} {hasOidc && ( )}

{t('noAccount')}{' '} {t('register')}

); } async function migrateThenRedirect(callbackUrl: string, router: ReturnType) { const anonId = sessionStorage.getItem('curio_uid'); if (anonId) { try { await fetch('/api/auth/migrate-session', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ anonUserId: anonId }), }); sessionStorage.removeItem('curio_uid'); } catch { // non-blocking } } // Sync saved locale preference to cookie so UI reflects user's language try { const localeRes = await fetch('/api/user/locale'); if (localeRes.ok) { const { locale } = (await localeRes.json()) as { locale: string }; document.cookie = `NEXT_LOCALE=${locale};path=/;max-age=${60 * 60 * 24 * 365};samesite=lax`; } } catch { // non-blocking } router.refresh(); router.push(callbackUrl); } const styles = { page: { display: 'flex', flexDirection: 'column' as const, alignItems: 'center', minHeight: '100svh', padding: 'var(--space-8)', paddingTop: 'var(--space-16)', gap: 'var(--space-4)', }, heading: { fontFamily: 'var(--font-display)', fontSize: '1.5rem', fontWeight: 500, color: 'var(--color-ink)', marginBottom: 'var(--space-4)', }, form: { width: '100%', maxWidth: '26rem', display: 'flex', flexDirection: 'column' as const, gap: 'var(--space-2)', }, label: { fontFamily: 'var(--font-display)', fontSize: '0.875rem', fontWeight: 500, color: 'var(--color-ink)', marginTop: 'var(--space-3)', }, input: { width: '100%', padding: 'var(--space-3) var(--space-4)', background: 'var(--color-surface)', border: '1px solid var(--color-border)', borderRadius: '4px', fontFamily: 'var(--font-body)', fontSize: '1rem', color: 'var(--color-ink)', outline: 'none', }, error: { color: 'var(--color-misconception)', fontSize: '0.875rem', margin: 0, }, btn: { marginTop: 'var(--space-4)', padding: 'var(--space-3) var(--space-6)', background: 'var(--color-accent)', color: 'var(--color-on-accent)', border: 'none', borderRadius: '4px', fontFamily: 'var(--font-display)', fontSize: '0.9375rem', fontWeight: 500, cursor: 'pointer', }, divider: { width: '100%', maxWidth: '26rem', display: 'flex', alignItems: 'center', gap: 'var(--space-3)', color: 'var(--color-ink-faint)', fontSize: '0.875rem', marginTop: 'var(--space-2)', }, oauthGroup: { width: '100%', maxWidth: '26rem', display: 'flex', flexDirection: 'column' as const, gap: 'var(--space-2)', }, oauthBtn: { width: '100%', padding: 'var(--space-3) var(--space-4)', background: 'var(--color-surface)', border: '1px solid var(--color-border)', borderRadius: '4px', fontFamily: 'var(--font-display)', fontSize: '0.9375rem', color: 'var(--color-ink)', cursor: 'pointer', }, footer: { fontSize: '0.875rem', color: 'var(--color-ink-muted)', marginTop: 'var(--space-4)', }, link: { color: 'var(--color-accent)', textDecoration: 'none', }, forgotLink: { color: 'var(--color-ink-muted)', fontSize: '0.8125rem', textDecoration: 'none', textAlign: 'right' as const, marginTop: 'var(--space-1)', }, success: { width: '100%', maxWidth: '26rem', padding: 'var(--space-3) var(--space-4)', background: 'var(--color-mastered-subtle)', color: 'var(--color-mastered)', borderRadius: '4px', fontSize: '0.875rem', margin: 0, }, } as const;