fix notification and add billing page and contract
This commit is contained in:
@@ -1,22 +1,33 @@
|
||||
'use client'
|
||||
|
||||
import Image from 'next/image'
|
||||
import Link from 'next/link'
|
||||
import { useState } from 'react'
|
||||
import { useRouter, useSearchParams } from 'next/navigation'
|
||||
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_TOKEN_KEY } from '@/lib/api'
|
||||
import { API_BASE, EMPLOYEE_PROFILE_KEY, EMPLOYEE_TOKEN_KEY } from '@/lib/api'
|
||||
|
||||
function notifyParent(message: Record<string, unknown>) {
|
||||
if (typeof window === 'undefined' || window.parent === window) return
|
||||
window.parent.postMessage(message, '*')
|
||||
}
|
||||
|
||||
export default function SignInPage() {
|
||||
const { language } = useDashboardI18n()
|
||||
const { language, theme, setLanguage, setTheme } = useDashboardI18n()
|
||||
const router = useRouter()
|
||||
const pathname = usePathname()
|
||||
const searchParams = useSearchParams()
|
||||
const portal = searchParams.get('portal') === 'admin' ? 'admin' : 'workspace'
|
||||
const requestedPortal = searchParams.get('portal') === 'admin' ? 'admin' : 'workspace'
|
||||
const [portal, setPortal] = useState<'admin' | 'workspace'>(requestedPortal)
|
||||
const requestedLanguage = searchParams.get('lang')
|
||||
const requestedTheme = searchParams.get('theme')
|
||||
const dict = {
|
||||
en: {
|
||||
workspace: 'Sign in',
|
||||
admin: 'Admin sign in',
|
||||
subtitle: 'Enter your credentials to access your workspace.',
|
||||
adminSubtitle: 'Use the same sign-in page for platform admin access.',
|
||||
title: 'Sign in',
|
||||
workspaceTab: 'Company Workspace',
|
||||
platformTab: 'Platform',
|
||||
subtitle: 'Choose where you want to sign in, then enter your credentials.',
|
||||
email: 'Email',
|
||||
password: 'Password',
|
||||
signIn: 'Sign in',
|
||||
@@ -34,10 +45,10 @@ export default function SignInPage() {
|
||||
unexpectedError: 'Something went wrong. Please try again.',
|
||||
},
|
||||
fr: {
|
||||
workspace: 'Connexion',
|
||||
admin: 'Connexion admin',
|
||||
subtitle: 'Saisissez vos identifiants pour accéder à votre espace.',
|
||||
adminSubtitle: 'Utilisez cette page unique pour accéder à la plateforme admin.',
|
||||
title: 'Connexion',
|
||||
workspaceTab: 'Espace',
|
||||
platformTab: 'Plateforme',
|
||||
subtitle: 'Choisissez votre accès puis saisissez vos identifiants.',
|
||||
email: 'Email',
|
||||
password: 'Mot de passe',
|
||||
signIn: 'Connexion',
|
||||
@@ -55,10 +66,10 @@ export default function SignInPage() {
|
||||
unexpectedError: 'Une erreur est survenue. Veuillez réessayer.',
|
||||
},
|
||||
ar: {
|
||||
workspace: 'تسجيل الدخول',
|
||||
admin: 'دخول الإدارة',
|
||||
subtitle: 'أدخل بياناتك للوصول إلى مساحة العمل.',
|
||||
adminSubtitle: 'استخدم صفحة الدخول نفسها للوصول إلى لوحة الإدارة.',
|
||||
title: 'تسجيل الدخول',
|
||||
workspaceTab: 'المساحة',
|
||||
platformTab: 'المنصة',
|
||||
subtitle: 'اختر جهة الدخول ثم أدخل بياناتك.',
|
||||
email: 'البريد الإلكتروني',
|
||||
password: 'كلمة المرور',
|
||||
signIn: 'تسجيل الدخول',
|
||||
@@ -76,33 +87,132 @@ export default function SignInPage() {
|
||||
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',
|
||||
toggleWrap: 'border-slate-200 bg-slate-50',
|
||||
activeToggle: 'bg-slate-900 text-white shadow-sm',
|
||||
inactiveToggle: 'text-slate-600 hover:bg-white hover:text-slate-900',
|
||||
},
|
||||
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',
|
||||
toggleWrap: 'border-slate-300 bg-slate-200/80',
|
||||
activeToggle: 'bg-amber-400 text-slate-950 shadow-sm',
|
||||
inactiveToggle: 'text-slate-700 hover:bg-white/85 hover:text-slate-950',
|
||||
},
|
||||
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',
|
||||
toggleWrap: 'border-slate-700 bg-slate-950/80',
|
||||
activeToggle: 'bg-amber-400 text-slate-950 shadow-sm',
|
||||
inactiveToggle: 'text-slate-300 hover:bg-slate-800 hover:text-white',
|
||||
},
|
||||
}[theme]
|
||||
|
||||
useEffect(() => {
|
||||
setPortal(requestedPortal)
|
||||
}, [requestedPortal])
|
||||
|
||||
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 = `${pathname}${searchParams.toString() ? `?${searchParams.toString()}` : ''}`
|
||||
notifyParent({ type: 'rentaldrivego:embedded-path', path: currentPath })
|
||||
}, [pathname, searchParams])
|
||||
|
||||
function handlePortalChange(nextPortal: 'admin' | 'workspace') {
|
||||
setPortal(nextPortal)
|
||||
const nextParams = new URLSearchParams(searchParams.toString())
|
||||
|
||||
if (nextPortal === 'admin') {
|
||||
nextParams.set('portal', 'admin')
|
||||
} else {
|
||||
nextParams.delete('portal')
|
||||
}
|
||||
|
||||
const nextSearch = nextParams.toString()
|
||||
router.replace(nextSearch ? `${pathname}?${nextSearch}` : pathname, { scroll: false })
|
||||
}
|
||||
|
||||
return (
|
||||
<main className="flex min-h-screen items-center justify-center bg-[radial-gradient(circle_at_top,#dbeafe,transparent_35%),linear-gradient(180deg,#f8fafc,white)] px-4 py-12">
|
||||
<main className={`flex min-h-screen items-center justify-center px-4 py-12 transition-colors ${themeStyles.main}`}>
|
||||
<div className="w-full max-w-md">
|
||||
<div className="mb-8 text-center">
|
||||
<Link href={marketplaceUrl} className="text-xs font-semibold uppercase tracking-[0.24em] text-blue-600">
|
||||
<div className="flex justify-center">
|
||||
<Image
|
||||
src="/rentalcardrive.png"
|
||||
alt="RentalDriveGo"
|
||||
width={104}
|
||||
height={104}
|
||||
className={`h-24 w-24 rounded-[1.75rem] border p-1.5 transition-colors ${themeStyles.logoFrame}`}
|
||||
/>
|
||||
</div>
|
||||
<Link href={marketplaceUrl} className={`text-xs font-semibold uppercase tracking-[0.24em] transition-colors ${themeStyles.brand}`}>
|
||||
RentalDriveGo
|
||||
</Link>
|
||||
<h1 className="mt-3 text-3xl font-black tracking-tight text-slate-900">
|
||||
{portal === 'admin' ? dict.admin : dict.workspace}
|
||||
<h1 className={`mt-3 text-3xl font-black tracking-tight transition-colors ${themeStyles.title}`}>
|
||||
{dict.title}
|
||||
</h1>
|
||||
<p className="mt-2 text-sm text-slate-500">
|
||||
{portal === 'admin' ? dict.adminSubtitle : dict.subtitle}
|
||||
<p className={`mt-2 text-sm transition-colors ${themeStyles.subtitle}`}>
|
||||
{dict.subtitle}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<section className="rounded-[2rem] border border-slate-200 bg-white p-8 shadow-sm">
|
||||
<LocalSignInForm dict={dict} portal={portal} />
|
||||
<section className={`rounded-[2rem] border p-8 transition-colors ${themeStyles.card}`}>
|
||||
<div className={`mb-6 grid grid-cols-2 gap-2 rounded-full border p-1 transition-colors ${themeStyles.toggleWrap}`}>
|
||||
{([
|
||||
{ value: 'workspace', label: dict.workspaceTab },
|
||||
{ value: 'admin', label: dict.platformTab },
|
||||
] as const).map((option) => {
|
||||
const active = option.value === portal
|
||||
|
||||
{portal !== 'admin' ? (
|
||||
<div className="mt-6 border-t border-slate-200 pt-6 text-sm text-slate-500">
|
||||
{dict.newAccount}
|
||||
<Link href="/sign-up" className="ml-1 font-semibold text-slate-900 underline decoration-slate-300 underline-offset-4">
|
||||
{dict.createWorkspace}
|
||||
</Link>
|
||||
</div>
|
||||
) : null}
|
||||
return (
|
||||
<button
|
||||
key={option.value}
|
||||
type="button"
|
||||
onClick={() => handlePortalChange(option.value)}
|
||||
className={`rounded-full px-4 py-2 text-sm font-semibold transition ${
|
||||
active
|
||||
? themeStyles.activeToggle
|
||||
: themeStyles.inactiveToggle
|
||||
}`}
|
||||
>
|
||||
{option.label}
|
||||
</button>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
<LocalSignInForm dict={dict} portal={portal} />
|
||||
</section>
|
||||
</div>
|
||||
</main>
|
||||
@@ -140,6 +250,12 @@ function LocalSignInForm({ dict, portal }: {
|
||||
const employeeRedirect = searchParams.get('redirect') || '/dashboard'
|
||||
const forgotPasswordHref = portal === 'admin' ? `${adminUrl}/forgot-password` : '/forgot-password'
|
||||
|
||||
useEffect(() => {
|
||||
setStep('credentials')
|
||||
setTotpCode('')
|
||||
setError(null)
|
||||
}, [portal])
|
||||
|
||||
function redirectAdmin(token: string) {
|
||||
const hash = new URLSearchParams({ token, next: adminNext }).toString()
|
||||
window.location.href = `${adminUrl}/auth-redirect#${hash}`
|
||||
@@ -183,7 +299,12 @@ function LocalSignInForm({ dict, portal }: {
|
||||
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))
|
||||
}
|
||||
document.cookie = `employee_token=${token}; path=/; max-age=28800; samesite=lax`
|
||||
window.dispatchEvent(new CustomEvent('rentaldrivego:auth-changed'))
|
||||
notifyParent({ type: 'rentaldrivego:employee-login', path: employeeRedirect })
|
||||
router.push(employeeRedirect)
|
||||
return
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user