unfied style
This commit is contained in:
@@ -11,7 +11,7 @@ import {
|
||||
import { resolveBrowserAppUrl } from '@/lib/appUrls'
|
||||
|
||||
function buildUnifiedLoginUrl(nextPath: string) {
|
||||
const dashboardUrl = resolveBrowserAppUrl(process.env.NEXT_PUBLIC_DASHBOARD_URL ?? 'http://localhost:3001/dashboard')
|
||||
const dashboardUrl = resolveBrowserAppUrl(process.env.NEXT_PUBLIC_DASHBOARD_URL ?? 'http://localhost:3000/dashboard')
|
||||
const params = new URLSearchParams({
|
||||
portal: 'admin',
|
||||
next: nextPath || '/dashboard',
|
||||
@@ -50,18 +50,18 @@ export default function AdminDashboardLayout({ children }: { children: React.Rea
|
||||
|
||||
if (!ready) {
|
||||
return (
|
||||
<div className="flex h-screen items-center justify-center bg-zinc-950">
|
||||
<div className="h-6 w-6 rounded-full border-2 border-emerald-500 border-t-transparent animate-spin" aria-label={dict.loading} />
|
||||
<div className="flex h-screen items-center justify-center bg-[linear-gradient(180deg,#f8f5ef_0%,#f4efe6_28%,#fffdf8_58%,#ffffff_100%)] dark:bg-[linear-gradient(180deg,#14110f_0%,#17120d_35%,#0c0a09_100%)]">
|
||||
<div className="h-6 w-6 animate-spin rounded-full border-2 border-amber-500 border-t-transparent" aria-label={dict.loading} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex h-screen bg-zinc-950 text-zinc-100 transition-colors">
|
||||
<aside className="w-60 flex-shrink-0 flex flex-col border-r border-zinc-800 bg-zinc-900 transition-colors">
|
||||
<div className="flex h-screen bg-[linear-gradient(180deg,#f8f5ef_0%,#f4efe6_28%,#fffdf8_58%,#ffffff_100%)] text-stone-900 transition-colors dark:bg-[linear-gradient(180deg,#14110f_0%,#17120d_35%,#0c0a09_100%)] dark:text-stone-100">
|
||||
<aside className="flex w-60 flex-shrink-0 flex-col border-r border-stone-200/80 bg-white/78 backdrop-blur-xl transition-colors dark:border-stone-800 dark:bg-stone-950/78">
|
||||
<Link href="/" className="block px-5 py-6">
|
||||
<p className="text-xs font-semibold uppercase tracking-[0.2em] text-emerald-400">{dict.admin}</p>
|
||||
<p className="mt-0.5 text-sm font-semibold text-zinc-100">RentalDriveGo</p>
|
||||
<p className="text-xs font-semibold uppercase tracking-[0.2em] text-amber-700 dark:text-amber-300">{dict.admin}</p>
|
||||
<p className="mt-0.5 text-sm font-semibold text-stone-950 dark:text-stone-100">RentalDriveGo</p>
|
||||
</Link>
|
||||
<nav className="flex-1 px-3 space-y-0.5">
|
||||
{navLinks.map((link) => {
|
||||
@@ -71,7 +71,7 @@ export default function AdminDashboardLayout({ children }: { children: React.Rea
|
||||
key={link.href}
|
||||
href={link.href}
|
||||
className={`flex items-center gap-3 px-3 py-2.5 rounded-xl text-sm font-medium transition-colors ${
|
||||
active ? 'bg-zinc-800 text-zinc-100' : 'text-zinc-400 hover:text-zinc-200 hover:bg-zinc-800/50'
|
||||
active ? 'bg-stone-900 text-white dark:bg-amber-400 dark:text-stone-950' : 'text-stone-500 hover:text-stone-950 hover:bg-stone-100 dark:text-stone-400 dark:hover:text-white dark:hover:bg-stone-800'
|
||||
}`}
|
||||
>
|
||||
<svg className="h-4 w-4 flex-shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
|
||||
@@ -85,20 +85,20 @@ export default function AdminDashboardLayout({ children }: { children: React.Rea
|
||||
<div className="px-3 py-4">
|
||||
<button
|
||||
onClick={handleLogout}
|
||||
className="flex w-full items-center gap-3 px-3 py-2.5 rounded-xl text-sm font-medium text-zinc-500 transition-colors hover:bg-zinc-800/50 hover:text-red-400"
|
||||
className="flex w-full items-center gap-3 rounded-xl px-3 py-2.5 text-sm font-medium text-stone-500 transition-colors hover:bg-stone-100 hover:text-red-500 dark:text-stone-400 dark:hover:bg-stone-800 dark:hover:text-red-300"
|
||||
>
|
||||
<svg className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1" />
|
||||
</svg>
|
||||
{dict.logout}
|
||||
</button>
|
||||
<div className="mt-4 flex flex-col items-center gap-3 border-t border-zinc-800 pt-4">
|
||||
<div className="mt-4 flex flex-col items-center gap-3 border-t border-stone-200/80 pt-4 dark:border-stone-800">
|
||||
<AdminLanguageSwitcher />
|
||||
<AdminThemeSwitcher />
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
<main className="flex-1 overflow-y-auto bg-zinc-950 transition-colors">{children}</main>
|
||||
<main className="flex-1 overflow-y-auto transition-colors">{children}</main>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ export default function AdminForgotPasswordPage() {
|
||||
<main className="flex flex-1 items-center justify-center px-4">
|
||||
<div className="w-full max-w-md">
|
||||
<div className="mb-8 text-center">
|
||||
<Link href="/" className="text-xs font-semibold uppercase tracking-[0.2em] text-emerald-400">RentalDriveGo</Link>
|
||||
<Link href="/" className="text-xs font-semibold uppercase tracking-[0.2em] text-amber-700 dark:text-amber-300">RentalDriveGo</Link>
|
||||
<h1 className="mt-3 text-3xl font-black tracking-tight">Forgot password</h1>
|
||||
<p className="mt-1 text-sm text-zinc-400">Enter your admin email to receive a reset link.</p>
|
||||
</div>
|
||||
@@ -57,20 +57,20 @@ export default function AdminForgotPasswordPage() {
|
||||
<div className="rounded-xl border border-red-900/50 bg-red-950/50 px-4 py-3 text-sm text-red-400">{error}</div>
|
||||
)}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-zinc-300 mb-1.5">Email</label>
|
||||
<label className="mb-1.5 block text-sm font-medium text-zinc-300">Email</label>
|
||||
<input
|
||||
type="email"
|
||||
required
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
placeholder="admin@rentaldrivego.com"
|
||||
className="w-full px-3 py-2.5 rounded-xl bg-zinc-800 border border-zinc-700 text-zinc-100 placeholder:text-zinc-500 text-sm focus:outline-none focus:ring-2 focus:ring-emerald-500 focus:border-transparent"
|
||||
className="w-full rounded-2xl border border-stone-200 bg-white px-4 py-3 text-sm text-stone-900 placeholder:text-stone-400 focus:border-transparent focus:outline-none focus:ring-2 focus:ring-amber-500 dark:border-stone-700 dark:bg-stone-950/80 dark:text-stone-100 dark:placeholder:text-stone-500"
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
type="submit"
|
||||
disabled={loading}
|
||||
className="w-full py-2.5 px-4 rounded-xl bg-emerald-600 hover:bg-emerald-500 text-white text-sm font-semibold transition-colors disabled:opacity-50"
|
||||
className="inline-flex w-full justify-center rounded-full bg-stone-900 px-6 py-3 text-sm font-semibold text-white transition hover:bg-stone-700 disabled:opacity-50 dark:bg-amber-400 dark:text-stone-950 dark:hover:bg-amber-300"
|
||||
>
|
||||
{loading ? 'Sending…' : 'Send reset link'}
|
||||
</button>
|
||||
|
||||
+109
-13
@@ -11,7 +11,9 @@ html.dark {
|
||||
}
|
||||
|
||||
body {
|
||||
@apply bg-zinc-950 text-zinc-50 antialiased transition-colors;
|
||||
@apply text-stone-900 antialiased transition-colors dark:text-stone-100;
|
||||
background-image:
|
||||
linear-gradient(180deg, #f8f5ef 0%, #f4efe6 28%, #fffdf8 58%, #ffffff 100%);
|
||||
}
|
||||
|
||||
.shell {
|
||||
@@ -19,17 +21,111 @@ body {
|
||||
}
|
||||
|
||||
.panel {
|
||||
@apply rounded-2xl border border-zinc-800 bg-zinc-900 shadow-sm;
|
||||
@apply rounded-[1.75rem] border shadow-[0_30px_80px_rgba(28,25,23,0.08)] backdrop-blur transition-colors dark:shadow-[0_30px_80px_rgba(0,0,0,0.26)];
|
||||
border-color: rgb(231 229 228 / 0.8);
|
||||
background-color: rgb(255 255 255 / 0.82);
|
||||
}
|
||||
|
||||
html.dark body {
|
||||
background-image:
|
||||
linear-gradient(180deg, #14110f 0%, #17120d 35%, #0c0a09 100%);
|
||||
}
|
||||
|
||||
html.dark .panel {
|
||||
border-color: rgb(41 37 36 / 0.96);
|
||||
background-color: rgb(28 25 23 / 0.72);
|
||||
}
|
||||
|
||||
@layer utilities {
|
||||
.light body {
|
||||
background-color: rgb(248 250 252);
|
||||
color: rgb(15 23 42);
|
||||
color: rgb(28 25 23);
|
||||
}
|
||||
|
||||
.bg-zinc-950 {
|
||||
background-color: rgb(12 10 9);
|
||||
}
|
||||
|
||||
.bg-zinc-950\/90 {
|
||||
background-color: rgb(28 25 23 / 0.9);
|
||||
}
|
||||
|
||||
.bg-zinc-900 {
|
||||
background-color: rgb(28 25 23);
|
||||
}
|
||||
|
||||
.bg-zinc-800,
|
||||
.bg-zinc-800\/50 {
|
||||
background-color: rgb(41 37 36);
|
||||
}
|
||||
|
||||
.border-zinc-800,
|
||||
.border-zinc-700 {
|
||||
border-color: rgb(68 64 60);
|
||||
}
|
||||
|
||||
.text-zinc-100,
|
||||
.text-zinc-200 {
|
||||
color: rgb(250 250 249);
|
||||
}
|
||||
|
||||
.text-zinc-500 {
|
||||
color: rgb(168 162 158);
|
||||
}
|
||||
|
||||
.text-zinc-400,
|
||||
.text-zinc-300 {
|
||||
color: rgb(120 113 108);
|
||||
}
|
||||
|
||||
.hover\:bg-zinc-800:hover,
|
||||
.hover\:bg-zinc-800\/50:hover {
|
||||
background-color: rgb(68 64 60);
|
||||
}
|
||||
|
||||
.hover\:text-zinc-200:hover {
|
||||
color: rgb(250 250 249);
|
||||
}
|
||||
|
||||
.text-emerald-400 {
|
||||
color: rgb(252 211 77);
|
||||
}
|
||||
|
||||
.bg-emerald-600 {
|
||||
background-color: rgb(28 25 23);
|
||||
}
|
||||
|
||||
.hover\:bg-emerald-500:hover {
|
||||
background-color: rgb(68 64 60);
|
||||
}
|
||||
|
||||
.dark .bg-emerald-600 {
|
||||
background-color: rgb(251 191 36);
|
||||
}
|
||||
|
||||
.dark .hover\:bg-emerald-500:hover {
|
||||
background-color: rgb(252 211 77);
|
||||
}
|
||||
|
||||
.bg-emerald-900\/30,
|
||||
.bg-emerald-950\/40,
|
||||
.bg-emerald-950\/50 {
|
||||
background-color: rgb(120 53 15 / 0.32);
|
||||
}
|
||||
|
||||
.border-emerald-500 {
|
||||
border-color: rgb(245 158 11);
|
||||
}
|
||||
|
||||
.focus\:ring-emerald-500:focus {
|
||||
--tw-ring-color: rgb(217 119 6 / 0.45);
|
||||
}
|
||||
|
||||
.hover\:bg-emerald-900\/50:hover {
|
||||
background-color: rgb(146 64 14 / 0.42);
|
||||
}
|
||||
|
||||
.light .bg-zinc-950 {
|
||||
background-color: rgb(248 250 252);
|
||||
background-color: rgb(255 253 248);
|
||||
}
|
||||
|
||||
.light .bg-zinc-950\/90 {
|
||||
@@ -37,39 +133,39 @@ body {
|
||||
}
|
||||
|
||||
.light .bg-zinc-900 {
|
||||
background-color: rgb(255 255 255);
|
||||
background-color: rgb(255 255 255 / 0.82);
|
||||
}
|
||||
|
||||
.light .bg-zinc-800,
|
||||
.light .bg-zinc-800\/50 {
|
||||
background-color: rgb(241 245 249);
|
||||
background-color: rgb(245 245 244);
|
||||
}
|
||||
|
||||
.light .border-zinc-800,
|
||||
.light .border-zinc-700 {
|
||||
border-color: rgb(226 232 240);
|
||||
border-color: rgb(231 229 228);
|
||||
}
|
||||
|
||||
.light .text-zinc-100,
|
||||
.light .text-zinc-200 {
|
||||
color: rgb(15 23 42);
|
||||
color: rgb(28 25 23);
|
||||
}
|
||||
|
||||
.light .text-zinc-500 {
|
||||
color: rgb(100 116 139);
|
||||
color: rgb(120 113 108);
|
||||
}
|
||||
|
||||
.light .text-zinc-400,
|
||||
.light .text-zinc-300 {
|
||||
color: rgb(71 85 105);
|
||||
color: rgb(87 83 78);
|
||||
}
|
||||
|
||||
.light .hover\:bg-zinc-800:hover,
|
||||
.light .hover\:bg-zinc-800\/50:hover {
|
||||
background-color: rgb(241 245 249);
|
||||
background-color: rgb(245 245 244);
|
||||
}
|
||||
|
||||
.light .hover\:text-zinc-200:hover {
|
||||
color: rgb(15 23 42);
|
||||
color: rgb(28 25 23);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,12 +9,12 @@ export const metadata: Metadata = {
|
||||
|
||||
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<html lang="en" className="dark" suppressHydrationWarning>
|
||||
<html lang="en" className="light" suppressHydrationWarning>
|
||||
<head>
|
||||
<script
|
||||
dangerouslySetInnerHTML={{
|
||||
__html:
|
||||
"(function(){try{var theme=localStorage.getItem('admin-theme');if(theme!=='light'&&theme!=='dark'){theme=window.matchMedia('(prefers-color-scheme: dark)').matches?'dark':'light'}document.documentElement.classList.remove('light','dark');document.documentElement.classList.add(theme);document.documentElement.style.colorScheme=theme}catch(e){}})();",
|
||||
"(function(){try{var m=document.cookie.match(/(?:^|; )rentaldrivego-theme=([^;]+)/);var theme=m?decodeURIComponent(m[1]):(localStorage.getItem('rentaldrivego-theme')||localStorage.getItem('admin-theme'));if(theme!=='light'&&theme!=='dark'){theme=window.matchMedia('(prefers-color-scheme: dark)').matches?'dark':'light'}document.documentElement.classList.remove('light','dark');document.documentElement.classList.add(theme);document.documentElement.style.colorScheme=theme;document.body&&document.body.setAttribute('data-theme',theme)}catch(e){}})();",
|
||||
}}
|
||||
/>
|
||||
</head>
|
||||
|
||||
@@ -5,7 +5,7 @@ import { resolveServerAppUrl } from '@/lib/appUrls'
|
||||
export default function AdminLoginPage() {
|
||||
const requestHeaders = headers()
|
||||
const dashboardUrl = resolveServerAppUrl(
|
||||
process.env.NEXT_PUBLIC_DASHBOARD_URL ?? 'http://localhost:3001/dashboard',
|
||||
process.env.NEXT_PUBLIC_DASHBOARD_URL ?? 'http://localhost:3000/dashboard',
|
||||
requestHeaders.get('host'),
|
||||
requestHeaders.get('x-forwarded-proto'),
|
||||
)
|
||||
|
||||
@@ -77,7 +77,7 @@ function AdminResetPasswordContent() {
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => router.push('/login')}
|
||||
className="mt-2 w-full py-2.5 px-4 rounded-xl bg-emerald-600 hover:bg-emerald-500 text-white text-sm font-semibold transition-colors"
|
||||
className="mt-2 inline-flex w-full justify-center rounded-full bg-stone-900 px-6 py-3 text-sm font-semibold text-white transition hover:bg-stone-700 dark:bg-amber-400 dark:text-stone-950 dark:hover:bg-amber-300"
|
||||
>
|
||||
Sign in
|
||||
</button>
|
||||
@@ -93,7 +93,7 @@ function AdminResetPasswordContent() {
|
||||
<div className="rounded-xl border border-red-900/50 bg-red-950/50 px-4 py-3 text-sm text-red-400">{error}</div>
|
||||
)}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-zinc-300 mb-1.5">New password</label>
|
||||
<label className="mb-1.5 block text-sm font-medium text-zinc-300">New password</label>
|
||||
<input
|
||||
type="password"
|
||||
required
|
||||
@@ -101,11 +101,11 @@ function AdminResetPasswordContent() {
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
placeholder="••••••••"
|
||||
className="w-full px-3 py-2.5 rounded-xl bg-zinc-800 border border-zinc-700 text-zinc-100 text-sm focus:outline-none focus:ring-2 focus:ring-emerald-500 focus:border-transparent"
|
||||
className="w-full rounded-2xl border border-stone-200 bg-white px-4 py-3 text-sm text-stone-900 focus:border-transparent focus:outline-none focus:ring-2 focus:ring-amber-500 dark:border-stone-700 dark:bg-stone-950/80 dark:text-stone-100"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-zinc-300 mb-1.5">Confirm password</label>
|
||||
<label className="mb-1.5 block text-sm font-medium text-zinc-300">Confirm password</label>
|
||||
<input
|
||||
type="password"
|
||||
required
|
||||
@@ -113,13 +113,13 @@ function AdminResetPasswordContent() {
|
||||
value={confirm}
|
||||
onChange={(e) => setConfirm(e.target.value)}
|
||||
placeholder="••••••••"
|
||||
className="w-full px-3 py-2.5 rounded-xl bg-zinc-800 border border-zinc-700 text-zinc-100 text-sm focus:outline-none focus:ring-2 focus:ring-emerald-500 focus:border-transparent"
|
||||
className="w-full rounded-2xl border border-stone-200 bg-white px-4 py-3 text-sm text-stone-900 focus:border-transparent focus:outline-none focus:ring-2 focus:ring-amber-500 dark:border-stone-700 dark:bg-stone-950/80 dark:text-stone-100"
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
type="submit"
|
||||
disabled={loading}
|
||||
className="w-full py-2.5 px-4 rounded-xl bg-emerald-600 hover:bg-emerald-500 text-white text-sm font-semibold transition-colors disabled:opacity-50"
|
||||
className="inline-flex w-full justify-center rounded-full bg-stone-900 px-6 py-3 text-sm font-semibold text-white transition hover:bg-stone-700 disabled:opacity-50 dark:bg-amber-400 dark:text-stone-950 dark:hover:bg-amber-300"
|
||||
>
|
||||
{loading ? 'Resetting…' : 'Reset password'}
|
||||
</button>
|
||||
@@ -140,7 +140,7 @@ function AdminResetShell({ children }: { children: React.ReactNode }) {
|
||||
<main className="flex flex-1 items-center justify-center px-4">
|
||||
<div className="w-full max-w-md">
|
||||
<div className="mb-8 text-center">
|
||||
<Link href="/" className="text-xs font-semibold uppercase tracking-[0.2em] text-emerald-400">RentalDriveGo</Link>
|
||||
<Link href="/" className="text-xs font-semibold uppercase tracking-[0.2em] text-amber-700 dark:text-amber-300">RentalDriveGo</Link>
|
||||
<h1 className="mt-3 text-3xl font-black tracking-tight">Reset password</h1>
|
||||
</div>
|
||||
<div className="panel p-8">{children}</div>
|
||||
|
||||
@@ -87,6 +87,8 @@ const languageMeta = {
|
||||
ar: { flag: '🇲🇦', shortLabel: 'AR' },
|
||||
} as const
|
||||
|
||||
const SHARED_THEME_KEY = 'rentaldrivego-theme'
|
||||
|
||||
type AdminI18nContext = {
|
||||
language: AdminLanguage
|
||||
setLanguage: (value: AdminLanguage) => void
|
||||
@@ -99,14 +101,14 @@ const Context = createContext<AdminI18nContext | null>(null)
|
||||
|
||||
export function AdminI18nProvider({ children }: { children: React.ReactNode }) {
|
||||
const [language, setLanguage] = useState<AdminLanguage>('en')
|
||||
const [theme, setTheme] = useState<AdminTheme>('dark')
|
||||
const [theme, setTheme] = useState<AdminTheme>('light')
|
||||
// Skip the very first write so we don't overwrite a stored preference with
|
||||
// the default 'en' value before the hydration read-effect has applied it.
|
||||
const skipFirstLangWrite = useRef(true)
|
||||
|
||||
useEffect(() => {
|
||||
const stored = window.localStorage.getItem('admin-language')
|
||||
const storedTheme = window.localStorage.getItem('admin-theme')
|
||||
const storedTheme = window.localStorage.getItem(SHARED_THEME_KEY) ?? window.localStorage.getItem('admin-theme')
|
||||
if (stored === 'en' || stored === 'fr' || stored === 'ar') {
|
||||
setLanguage(stored)
|
||||
}
|
||||
@@ -136,6 +138,8 @@ export function AdminI18nProvider({ children }: { children: React.ReactNode }) {
|
||||
document.documentElement.classList.add(theme)
|
||||
document.documentElement.style.colorScheme = theme
|
||||
document.body.dataset.theme = theme
|
||||
document.cookie = `${SHARED_THEME_KEY}=${encodeURIComponent(theme)}; path=/; max-age=31536000; SameSite=Lax`
|
||||
window.localStorage.setItem(SHARED_THEME_KEY, theme)
|
||||
window.localStorage.setItem('admin-theme', theme)
|
||||
}, [theme])
|
||||
|
||||
@@ -163,8 +167,8 @@ export function AdminLanguageSwitcher() {
|
||||
if (embedded) return null
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-1 rounded-full border border-zinc-700 bg-zinc-900 px-2 py-1 shadow-sm transition-colors">
|
||||
<span className="px-2 text-[11px] font-semibold uppercase tracking-[0.16em] text-zinc-500">{dict.language}</span>
|
||||
<div className="flex items-center gap-1 rounded-full border border-stone-200/80 bg-white/95 px-2 py-1 shadow-sm transition-colors dark:border-stone-700 dark:bg-stone-900/85">
|
||||
<span className="px-2 text-[11px] font-semibold uppercase tracking-[0.16em] text-stone-500 dark:text-stone-400">{dict.language}</span>
|
||||
{(['en', 'fr', 'ar'] as AdminLanguage[]).map((value) => {
|
||||
const active = value === language
|
||||
const meta = languageMeta[value]
|
||||
@@ -174,7 +178,7 @@ export function AdminLanguageSwitcher() {
|
||||
type="button"
|
||||
onClick={() => setLanguage(value)}
|
||||
className={`rounded-full px-3 py-1.5 text-xs font-semibold transition ${
|
||||
active ? 'bg-zinc-100 text-zinc-950' : 'text-zinc-300 hover:bg-zinc-800'
|
||||
active ? 'bg-stone-900 text-white dark:bg-amber-400 dark:text-stone-950' : 'text-stone-600 hover:bg-stone-100 dark:text-stone-300 dark:hover:bg-stone-800'
|
||||
}`}
|
||||
>
|
||||
<span className="inline-flex items-center gap-1.5">
|
||||
@@ -199,10 +203,7 @@ export function AdminThemeSwitcher() {
|
||||
if (embedded) return null
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-1 rounded-full border border-zinc-700 bg-zinc-900 px-2 py-1 shadow-sm transition-colors">
|
||||
<span className="px-2 text-[11px] font-semibold uppercase tracking-[0.16em] text-zinc-500">
|
||||
{dict.theme}
|
||||
</span>
|
||||
<div className="flex items-center gap-1 rounded-full border border-stone-200/80 bg-white/95 px-2 py-1 shadow-sm transition-colors dark:border-stone-700 dark:bg-stone-900/85">
|
||||
{(['light', 'dark'] as AdminTheme[]).map((value) => {
|
||||
const active = value === theme
|
||||
return (
|
||||
@@ -211,7 +212,7 @@ export function AdminThemeSwitcher() {
|
||||
type="button"
|
||||
onClick={() => setTheme(value)}
|
||||
className={`rounded-full px-3 py-1.5 text-xs font-semibold transition ${
|
||||
active ? 'bg-zinc-100 text-zinc-950' : 'text-zinc-300 hover:bg-zinc-800'
|
||||
active ? 'bg-stone-900 text-white dark:bg-amber-400 dark:text-stone-950' : 'text-stone-600 hover:bg-stone-100 dark:text-stone-300 dark:hover:bg-stone-800'
|
||||
}`}
|
||||
>
|
||||
{value === 'light' ? dict.light : dict.dark}
|
||||
|
||||
@@ -28,11 +28,11 @@ export default function PublicFooter() {
|
||||
}[language]
|
||||
|
||||
return (
|
||||
<footer className="border-t border-stone-200 bg-white/90 px-4 py-4 backdrop-blur-md transition-colors dark:border-zinc-800 dark:bg-zinc-950/90">
|
||||
<footer className="border-t border-stone-200/80 bg-white/72 px-4 py-4 backdrop-blur-xl transition-colors dark:border-stone-800 dark:bg-stone-950/72">
|
||||
<div className="mx-auto flex max-w-6xl flex-col items-center justify-between gap-3 lg:flex-row">
|
||||
<div className="flex items-center gap-3 text-xs font-medium uppercase tracking-[0.16em] text-stone-400 dark:text-zinc-500">
|
||||
<div className="flex items-center gap-3 text-xs font-medium uppercase tracking-[0.16em] text-stone-400 dark:text-stone-500">
|
||||
<p>{dict.preferences}</p>
|
||||
<span className="inline-flex items-center gap-1 rounded-full border border-zinc-700 bg-zinc-900 px-2.5 py-1 text-zinc-200">
|
||||
<span className="inline-flex items-center gap-1 rounded-full border border-stone-200/80 bg-white/95 px-2.5 py-1 text-stone-700 dark:border-stone-700 dark:bg-stone-900/85 dark:text-stone-200">
|
||||
<span aria-hidden="true">{currentLanguage.flag}</span>
|
||||
<span>{currentLanguage.shortLabel}</span>
|
||||
</span>
|
||||
|
||||
@@ -28,18 +28,18 @@ export default function PublicHeader() {
|
||||
}[language]
|
||||
|
||||
return (
|
||||
<header className="sticky top-0 z-30 border-b border-zinc-800 bg-zinc-950/90 backdrop-blur-md transition-colors">
|
||||
<header className="sticky top-0 z-30 border-b border-stone-200/80 bg-white/78 backdrop-blur-xl transition-colors dark:border-stone-800 dark:bg-stone-950/76">
|
||||
<div className="mx-auto flex max-w-6xl items-center justify-between gap-4 px-4 py-4">
|
||||
<Link href="/" className="flex items-center gap-3">
|
||||
<span className="text-xs font-semibold uppercase tracking-[0.24em] text-emerald-400">RentalDriveGo</span>
|
||||
<span className="hidden text-sm font-semibold text-zinc-400 sm:inline">{dict.admin}</span>
|
||||
<span className="text-xs font-semibold uppercase tracking-[0.24em] text-amber-700 dark:text-amber-300">RentalDriveGo</span>
|
||||
<span className="hidden text-sm font-semibold text-stone-500 dark:text-stone-400 sm:inline">{dict.admin}</span>
|
||||
</Link>
|
||||
<nav className="flex items-center gap-2">
|
||||
<span className="inline-flex min-w-[3.5rem] items-center justify-center gap-1 rounded-full border border-zinc-700 bg-zinc-900 px-2.5 py-2 text-xs font-semibold text-zinc-200">
|
||||
<span className="inline-flex min-w-[3.5rem] items-center justify-center gap-1 rounded-full border border-stone-200/80 bg-white/95 px-2.5 py-2 text-xs font-semibold text-stone-700 dark:border-stone-700 dark:bg-stone-900/85 dark:text-stone-200">
|
||||
<span aria-hidden="true">{currentLanguage.flag}</span>
|
||||
<span>{currentLanguage.shortLabel}</span>
|
||||
</span>
|
||||
<Link href="/login" className="rounded-full bg-zinc-100 px-4 py-2 text-sm font-semibold text-zinc-950 transition hover:bg-zinc-300">
|
||||
<Link href="/login" className="rounded-full bg-stone-900 px-4 py-2 text-sm font-semibold text-white transition hover:bg-stone-700 dark:bg-amber-400 dark:text-stone-950 dark:hover:bg-amber-300">
|
||||
{dict.signIn}
|
||||
</Link>
|
||||
</nav>
|
||||
|
||||
@@ -5,7 +5,7 @@ import PublicHeader from '@/components/PublicHeader'
|
||||
|
||||
export default function PublicShell({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<div className="flex min-h-screen flex-col bg-zinc-950 text-zinc-100 transition-colors">
|
||||
<div className="flex min-h-screen flex-col bg-[linear-gradient(180deg,#f8f5ef_0%,#f4efe6_28%,#fffdf8_58%,#ffffff_100%)] text-stone-900 transition-colors dark:bg-[linear-gradient(180deg,#14110f_0%,#17120d_35%,#0c0a09_100%)] dark:text-stone-100">
|
||||
<PublicHeader />
|
||||
<div className="flex flex-1 flex-col">{children}</div>
|
||||
<PublicFooter />
|
||||
|
||||
Reference in New Issue
Block a user