'use client' // apps/web/src/app/login/page.tsx // D3RO-VOICE Canonical Login Page — Precision Voice Intelligence Design // Design Reference: docs/v3/designs/login.html import { useRouter } from 'next/navigation' import { useEffect, useState } from 'react' import { d3roPalette } from '@d3ro/ui/theme' import { Box, Stack, Alert } from '@mui/material' import { useI18n } from '@d3ro/i18n' import { getSupabaseBrowserClient, isSupabaseConfigured } from '@/lib/supabase-browser' import { useAuth } from '@/components/providers/auth-provider' export default function LoginPage(): React.ReactElement { const router = useRouter() const { user, loading } = useAuth() const { t } = useI18n() const [error, setError] = useState(null) const [signingIn, setSigningIn] = useState(false) const [email, setEmail] = useState('') const [password, setPassword] = useState('') const configured = isSupabaseConfigured() useEffect(() => { if (!loading && user) { router.replace('/dashboard') } }, [loading, user, router]) async function handleOAuth(provider: 'google' | 'github' | 'apple'): Promise { if (!configured) { setError('Supabase 환경변수가 설정되지 않았습니다.') return } setError(null) setSigningIn(true) try { const supabase = getSupabaseBrowserClient() const redirectTo = `${window.location.origin}/auth/callback` const { error: err } = await supabase.auth.signInWithOAuth({ provider: provider === 'apple' ? 'apple' : provider, options: { redirectTo } }) if (err) { setError(err.message) setSigningIn(false) } } catch (e) { setError(e instanceof Error ? e.message : 'Unknown error') setSigningIn(false) } } async function handleEmailSignIn(e: React.FormEvent): Promise { e.preventDefault() if (!configured) { setError('Supabase 환경변수가 설정되지 않았습니다.') return } if (!email || !password) return setError(null) setSigningIn(true) try { const supabase = getSupabaseBrowserClient() const { error: err } = await supabase.auth.signInWithPassword({ email, password }) if (err) { setError(err.message) setSigningIn(false) } else { router.replace('/dashboard') } } catch (e) { setError(e instanceof Error ? e.message : 'Unknown error') setSigningIn(false) } } return ( {/* Header & LED Indicators */} D3RO-VOICE {t('login.subtitle') ?? 'Precision Voice Intelligence'} {error && ( {error} )} {/* OAuth Buttons */} {/* Google Button */} void handleOAuth('google')} sx={{ width: '100%', bgcolor: d3roPalette.bg.card, border: '1px solid var(--d3-border-default)', borderRadius: '12px', py: 1.6, px: 2, display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 1.5, color: d3roPalette.text.primary, fontSize: '14px', fontWeight: 500, cursor: signingIn ? 'not-allowed' : 'pointer', transition: 'all 0.15s ease', '&:hover': { bgcolor: d3roPalette.bg.elevated, borderColor: d3roPalette.border.strong }, '&:active': { transform: 'scale(0.99)' } }} > {t('login.google') ?? 'Google로 로그인'} {/* Apple Button */} void handleOAuth('apple')} sx={{ width: '100%', bgcolor: d3roPalette.bg.card, border: '1px solid var(--d3-border-default)', borderRadius: '12px', py: 1.6, px: 2, display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 1.5, color: d3roPalette.text.primary, fontSize: '14px', fontWeight: 500, cursor: signingIn ? 'not-allowed' : 'pointer', transition: 'all 0.15s ease', '&:hover': { bgcolor: d3roPalette.bg.elevated, borderColor: d3roPalette.border.strong }, '&:active': { transform: 'scale(0.99)' } }} > Apple로 로그인 {/* GitHub Button */} void handleOAuth('github')} sx={{ width: '100%', bgcolor: d3roPalette.bg.card, border: '1px solid var(--d3-border-default)', borderRadius: '12px', py: 1.6, px: 2, display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 1.5, color: d3roPalette.text.primary, fontSize: '14px', fontWeight: 500, cursor: signingIn ? 'not-allowed' : 'pointer', transition: 'all 0.15s ease', '&:hover': { bgcolor: d3roPalette.bg.elevated, borderColor: d3roPalette.border.strong }, '&:active': { transform: 'scale(0.99)' } }} > {t('login.github') ?? 'GitHub로 로그인'} {/* Divider */} {t('login.or') ?? '또는'} {/* Email & Password Form */} EMAIL ) => setEmail(e.target.value)} sx={{ width: '100%', bgcolor: d3roPalette.bg.app, border: '1px solid var(--d3-border-default)', borderRadius: '12px', py: 1.6, px: 2, fontSize: '14px', color: d3roPalette.text.primary, outline: 'none', boxSizing: 'border-box', transition: 'border-color 0.15s ease', '&:focus': { borderColor: d3roPalette.accent.main }, '&::placeholder': { color: 'color-mix(in srgb, var(--d3-text-label) 40%, transparent)' } }} /> PASSWORD ) => setPassword(e.target.value)} sx={{ width: '100%', bgcolor: d3roPalette.bg.app, border: '1px solid var(--d3-border-default)', borderRadius: '12px', py: 1.6, px: 2, fontSize: '14px', color: d3roPalette.text.primary, outline: 'none', boxSizing: 'border-box', transition: 'border-color 0.15s ease', '&:focus': { borderColor: d3roPalette.accent.main }, '&::placeholder': { color: 'color-mix(in srgb, var(--d3-text-label) 40%, transparent)' } }} /> {/* Primary Orange CTA Button */} {signingIn ? '로그인 중...' : '로그인'} {/* Footer */} 계정이 없으신가요?{' '} 회원가입 ) }