'use client' import Image from 'next/image' import Link from 'next/link' import { useEffect, useState } from 'react' import { usePathname, useRouter, useSearchParams } from 'next/navigation' import { useDashboardI18n } from '@/components/I18nProvider' import { adminUrl, marketplaceUrl } from '@/lib/urls' import { API_BASE, EMPLOYEE_PROFILE_KEY, EMPLOYEE_TOKEN_KEY } from '@/lib/api' const DASHBOARD_LOGO_SRC = '/dashboard/rentalcardrive.png' function notifyParent(message: Record) { if (typeof window === 'undefined' || window.parent === window) return window.parent.postMessage(message, '*') } export default function SignInPage() { const { language, theme, setLanguage, setTheme } = useDashboardI18n() const pathname = usePathname() const searchParams = useSearchParams() const requestedLanguage = searchParams.get('lang') const requestedTheme = searchParams.get('theme') const dict = { en: { title: 'Sign in', subtitle: 'Enter your credentials to access your account.', email: 'Email', password: 'Password', signIn: 'Sign in', signingIn: 'Signing in…', verify: 'Verify code', verifying: 'Verifying…', authCode: 'Authentication code', enterCode: 'Enter the 6-digit code from your authenticator app.', back: 'Back to credentials', forgotPassword: 'Forgot your password?', invalidCredentials: 'Invalid email or password.', passwordNotSet: 'No password set yet. Check your invitation email or use "Forgot your password?"', unexpectedError: 'Something went wrong. Please try again.', }, fr: { title: 'Connexion', subtitle: 'Saisissez vos identifiants pour accéder à votre compte.', email: 'Email', password: 'Mot de passe', signIn: 'Connexion', signingIn: 'Connexion…', verify: 'Vérifier le code', verifying: 'Vérification…', authCode: 'Code d\'authentification', enterCode: 'Entrez le code à 6 chiffres de votre application d\'authentification.', back: 'Retour aux identifiants', forgotPassword: 'Mot de passe oublié ?', invalidCredentials: 'Email ou mot de passe invalide.', passwordNotSet: 'Aucun mot de passe défini. Vérifiez votre e-mail d\'invitation ou utilisez « Mot de passe oublié ? »', unexpectedError: 'Une erreur est survenue. Veuillez réessayer.', }, ar: { title: 'تسجيل الدخول', subtitle: 'أدخل بياناتك للوصول إلى حسابك.', email: 'البريد الإلكتروني', password: 'كلمة المرور', signIn: 'تسجيل الدخول', signingIn: 'جارٍ تسجيل الدخول…', verify: 'تحقق من الرمز', verifying: 'جارٍ التحقق…', authCode: 'رمز المصادقة', enterCode: 'أدخل الرمز المكون من 6 أرقام من تطبيق المصادقة.', back: 'العودة إلى بيانات الدخول', forgotPassword: 'نسيت كلمة المرور؟', invalidCredentials: 'البريد الإلكتروني أو كلمة المرور غير صحيحة.', passwordNotSet: 'لم يتم تعيين كلمة مرور بعد. تحقق من بريد الدعوة أو استخدم "نسيت كلمة المرور؟"', unexpectedError: 'حدث خطأ ما. يرجى المحاولة مرة أخرى.', }, }[language] const themeStyles = { light: { main: 'bg-[radial-gradient(circle_at_top,#dbeafe,transparent_35%),linear-gradient(180deg,#f8fafc,white)]', logoFrame: 'border-blue-100 bg-white shadow-sm', brand: 'text-blue-600', title: 'text-slate-900', subtitle: 'text-slate-500', card: 'border-slate-200 bg-white shadow-sm', }, medium: { main: 'bg-[radial-gradient(circle_at_top,rgba(148,163,184,0.32),transparent_35%),linear-gradient(180deg,#d7dee8,#f3f4f6)]', logoFrame: 'border-slate-300 bg-white/90 shadow-[0_12px_30px_rgba(71,85,105,0.18)]', brand: 'text-slate-700', title: 'text-slate-900', subtitle: 'text-slate-600', card: 'border-slate-300/80 bg-white/88 shadow-[0_18px_50px_rgba(51,65,85,0.14)] backdrop-blur', }, dark: { main: 'bg-[radial-gradient(circle_at_top,rgba(37,99,235,0.2),transparent_35%),linear-gradient(180deg,#020617,#0f172a)]', logoFrame: 'border-slate-700 bg-slate-900 shadow-[0_12px_30px_rgba(2,6,23,0.45)]', brand: 'text-amber-400', title: 'text-slate-100', subtitle: 'text-slate-400', card: 'border-slate-800 bg-slate-900/92 shadow-[0_18px_50px_rgba(2,6,23,0.45)] backdrop-blur', }, }[theme] useEffect(() => { if ( (requestedLanguage === 'en' || requestedLanguage === 'fr' || requestedLanguage === 'ar') && requestedLanguage !== language ) { setLanguage(requestedLanguage) } }, [language, requestedLanguage, setLanguage]) useEffect(() => { if ( (requestedTheme === 'light' || requestedTheme === 'medium' || requestedTheme === 'dark') && requestedTheme !== theme ) { setTheme(requestedTheme) } }, [requestedTheme, setTheme, theme]) useEffect(() => { const currentPath = window.location.pathname + (window.location.search || '') notifyParent({ type: 'rentaldrivego:embedded-path', path: currentPath }) }, [pathname, searchParams]) return (
RentalDriveGo
RentalDriveGo

{dict.title}

{dict.subtitle}

) } function LocalSignInForm({ dict }: { dict: { email: string password: string signIn: string signingIn: string verify: string verifying: string authCode: string enterCode: string back: string forgotPassword: string invalidCredentials: string passwordNotSet: string unexpectedError: string } }) { const router = useRouter() const searchParams = useSearchParams() const { setLanguage } = useDashboardI18n() const [email, setEmail] = useState('') const [password, setPassword] = useState('') const [totpCode, setTotpCode] = useState('') const [step, setStep] = useState<'credentials' | 'totp'>('credentials') const [showPassword, setShowPassword] = useState(false) const [loading, setLoading] = useState(false) const [error, setError] = useState(null) const adminNext = searchParams.get('next') || '/dashboard' const employeeRedirect = searchParams.get('redirect') || '/dashboard' function redirectAdmin(token: string) { const hash = new URLSearchParams({ token, next: adminNext }).toString() window.location.href = `${adminUrl}/auth-redirect#${hash}` } async function handleCredentials(e: React.FormEvent) { e.preventDefault() setLoading(true) setError(null) try { // Try employee login first const empRes = await fetch(`${API_BASE}/auth/employee/login`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email, password }), }) const empJson = await empRes.json() if (empRes.ok && empJson?.data?.token) { const token = empJson.data.token localStorage.setItem(EMPLOYEE_TOKEN_KEY, token) if (empJson?.data?.employee) { localStorage.setItem(EMPLOYEE_PROFILE_KEY, JSON.stringify(empJson.data.employee)) // Apply the employee's stored language preference immediately so the // dashboard renders in the correct language before any read-effect runs. const prefLang = empJson.data.employee?.preferredLanguage if (prefLang === 'en' || prefLang === 'fr' || prefLang === 'ar') { setLanguage(prefLang) document.cookie = `rentaldrivego-language=${prefLang}; path=/; max-age=31536000; samesite=lax` } } document.cookie = `employee_token=${token}; path=/; max-age=28800; samesite=lax` window.dispatchEvent(new CustomEvent('rentaldrivego:auth-changed')) notifyParent({ type: 'rentaldrivego:employee-login', path: '/dashboard' + employeeRedirect }) router.push(employeeRedirect) return } if (empJson?.error === 'password_not_set') { setError(dict.passwordNotSet) return } // Fall back to admin login const adminRes = await fetch(`${API_BASE}/admin/auth/login`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email, password }), }) const adminJson = await adminRes.json() if (adminRes.ok && adminJson?.data?.token) { redirectAdmin(adminJson.data.token) return } if (adminRes.status === 401 && adminJson?.error === 'totp_required') { setStep('totp') return } setError(dict.invalidCredentials) } catch { setError(dict.unexpectedError) } finally { setLoading(false) } } async function handleTotp(e: React.FormEvent) { e.preventDefault() setLoading(true) setError(null) try { const adminRes = await fetch(`${API_BASE}/admin/auth/login`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email, password, totpCode }), }) const adminJson = await adminRes.json() if (adminRes.ok && adminJson?.data?.token) { redirectAdmin(adminJson.data.token) return } setError(dict.invalidCredentials) } catch { setError(dict.unexpectedError) } finally { setLoading(false) } } return ( <> {error ? (
{error}
) : null} {step === 'credentials' ? (
setEmail(e.target.value)} placeholder="owner@company.com" className="input-field" />
setPassword(e.target.value)} placeholder="••••••••" className="input-field pr-10" />
{dict.forgotPassword}
) : (
{dict.enterCode}
setTotpCode(e.target.value.replace(/\D/g, ''))} placeholder="000000" className="input-field text-center text-xl tracking-[0.45em]" />
)} ) }