unfied style

This commit is contained in:
root
2026-05-23 04:35:47 -04:00
parent fd10d0db6a
commit 07fba16ff2
57 changed files with 565 additions and 263 deletions
@@ -4,7 +4,7 @@ import { DashboardLanguageSwitcher, DashboardThemeSwitcher } from '@/components/
export default function DashboardLayout({ children }: { children: React.ReactNode }) {
return (
<div className="flex min-h-screen bg-slate-50 transition-colors dark:bg-slate-950 print:block print:min-h-0 print:bg-white">
<div className="flex min-h-screen bg-[linear-gradient(180deg,#f8f5ef_0%,#f4efe6_28%,#fffdf8_58%,#ffffff_100%)] transition-colors dark:bg-[linear-gradient(180deg,#14110f_0%,#17120d_35%,#0c0a09_100%)] print:block print:min-h-0 print:bg-white">
<div className="print:hidden">
<Sidebar />
</div>
@@ -13,9 +13,9 @@ export default function DashboardLayout({ children }: { children: React.ReactNod
<TopBar />
</div>
<main className="flex-1 overflow-y-auto p-6 print:p-0 print:overflow-visible">{children}</main>
<footer className="border-t border-stone-200 bg-white/90 px-6 py-4 backdrop-blur-md transition-colors dark:border-slate-800 dark:bg-slate-950/90 print:hidden">
<footer className="border-t border-stone-200/80 bg-white/72 px-6 py-4 backdrop-blur-xl transition-colors dark:border-stone-800 dark:bg-stone-950/72 print:hidden">
<div className="flex flex-col items-center justify-between gap-3 lg:flex-row">
<p className="text-xs font-medium uppercase tracking-[0.16em] text-stone-400 dark:text-slate-500">
<p className="text-xs font-medium uppercase tracking-[0.16em] text-stone-400 dark:text-stone-500">
Workspace preferences
</p>
<div className="flex flex-col items-center gap-3 sm:flex-row">
+72 -22
View File
@@ -25,7 +25,9 @@ html.dark {
}
body {
@apply bg-slate-50 text-slate-900 antialiased transition-colors dark:bg-slate-950 dark:text-slate-100;
@apply text-stone-900 antialiased transition-colors dark:text-stone-100;
background-image:
linear-gradient(180deg, #f8f5ef 0%, #f4efe6 28%, #fffdf8 58%, #ffffff 100%);
}
h1, h2, h3, h4, h5, h6 {
@@ -33,49 +35,61 @@ html.dark {
}
}
html.dark body {
background-image:
linear-gradient(180deg, #14110f 0%, #17120d 35%, #0c0a09 100%);
}
@layer components {
.btn-primary {
@apply inline-flex items-center gap-2 px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white text-sm font-medium rounded-lg transition-colors disabled:opacity-50 disabled:cursor-not-allowed;
@apply inline-flex items-center gap-2 rounded-full bg-stone-950 px-5 py-2.5 text-sm font-semibold text-white transition hover:bg-stone-800 disabled:cursor-not-allowed disabled:opacity-50 dark:bg-amber-400 dark:text-stone-950 dark:hover:bg-amber-300;
}
.btn-secondary {
@apply inline-flex items-center gap-2 rounded-lg border border-slate-200 bg-white px-4 py-2 text-sm font-medium text-slate-700 transition-colors hover:bg-slate-50 dark:border-slate-700 dark:bg-slate-900 dark:text-slate-200 dark:hover:bg-slate-800;
@apply inline-flex items-center gap-2 rounded-full border border-stone-300 bg-white/85 px-5 py-2.5 text-sm font-semibold text-stone-700 transition hover:border-stone-950 hover:text-stone-950 dark:border-stone-700 dark:bg-stone-950/30 dark:text-stone-200 dark:hover:border-stone-200 dark:hover:text-white;
}
.btn-danger {
@apply inline-flex items-center gap-2 px-4 py-2 bg-red-600 hover:bg-red-700 text-white text-sm font-medium rounded-lg transition-colors disabled:opacity-50;
@apply inline-flex items-center gap-2 rounded-full bg-red-600 px-5 py-2.5 text-sm font-semibold text-white transition hover:bg-red-700 disabled:opacity-50;
}
.input-field {
@apply w-full rounded-lg border border-slate-200 bg-white px-3 py-2 text-sm text-slate-900 transition-colors placeholder:text-slate-400 focus:border-transparent focus:outline-none focus:ring-2 focus:ring-blue-500 dark:border-slate-700 dark:bg-slate-900 dark:text-slate-100 dark:placeholder:text-slate-500;
@apply w-full rounded-2xl border border-stone-200 bg-white px-4 py-3 text-sm text-stone-900 transition-colors 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;
}
.card {
@apply rounded-xl border border-slate-200 bg-white shadow-sm transition-colors dark:border-slate-800 dark:bg-slate-900;
@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 .card {
border-color: rgb(41 37 36 / 0.96);
background-color: rgb(28 25 23 / 0.72);
}
.badge-green {
@apply inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-green-100 text-green-700;
@apply inline-flex items-center rounded-full bg-emerald-100 px-2.5 py-0.5 text-xs font-medium text-emerald-700 dark:bg-emerald-950/40 dark:text-emerald-300;
}
.badge-blue {
@apply inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-blue-100 text-blue-700;
@apply inline-flex items-center rounded-full bg-amber-100 px-2.5 py-0.5 text-xs font-medium text-amber-700 dark:bg-amber-950/40 dark:text-amber-300;
}
.badge-amber {
@apply inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-amber-100 text-amber-700;
@apply inline-flex items-center rounded-full bg-orange-100 px-2.5 py-0.5 text-xs font-medium text-orange-700 dark:bg-orange-950/40 dark:text-orange-300;
}
.badge-red {
@apply inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-red-100 text-red-700;
@apply inline-flex items-center rounded-full bg-red-100 px-2.5 py-0.5 text-xs font-medium text-red-700 dark:bg-red-950/40 dark:text-red-300;
}
.badge-gray {
@apply inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-slate-100 text-slate-600;
@apply inline-flex items-center rounded-full bg-stone-100 px-2.5 py-0.5 text-xs font-medium text-stone-600 dark:bg-stone-800 dark:text-stone-300;
}
.badge-purple {
@apply inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-purple-100 text-purple-700;
@apply inline-flex items-center rounded-full bg-teal-100 px-2.5 py-0.5 text-xs font-medium text-teal-700 dark:bg-teal-950/40 dark:text-teal-300;
}
}
@@ -92,46 +106,82 @@ html.dark {
@layer utilities {
.dark .bg-slate-50 {
background-color: rgb(2 6 23);
background-color: rgb(12 10 9);
}
.dark .bg-white {
background-color: rgb(15 23 42);
background-color: rgb(28 25 23);
}
.dark .bg-white\/90 {
background-color: rgb(15 23 42 / 0.9);
background-color: rgb(28 25 23 / 0.9);
}
.dark .border-slate-200,
.dark .border-stone-200 {
border-color: rgb(30 41 59);
border-color: rgb(41 37 36);
}
.dark .text-slate-900 {
color: rgb(241 245 249);
color: rgb(250 250 249);
}
.dark .text-slate-700 {
color: rgb(203 213 225);
color: rgb(231 229 228);
}
.dark .text-slate-600,
.dark .text-stone-500 {
color: rgb(148 163 184);
color: rgb(168 162 158);
}
.dark .text-slate-500,
.dark .text-slate-400,
.dark .text-stone-400 {
color: rgb(100 116 139);
color: rgb(120 113 108);
}
.dark .hover\:bg-slate-100:hover {
background-color: rgb(30 41 59);
background-color: rgb(41 37 36);
}
.dark .hover\:text-slate-900:hover {
color: rgb(248 250 252);
color: rgb(250 250 249);
}
.bg-blue-600 {
background-color: rgb(217 119 6);
}
.hover\:bg-blue-700:hover {
background-color: rgb(180 83 9);
}
.bg-blue-500 {
background-color: rgb(245 158 11);
}
.bg-blue-50 {
background-color: rgb(255 251 235);
}
.text-blue-600 {
color: rgb(180 83 9);
}
.text-blue-800 {
color: rgb(154 52 18);
}
.border-blue-200 {
border-color: rgb(253 230 138);
}
.ring-blue-400 {
--tw-ring-color: rgb(251 191 36 / 0.6);
}
.focus\:ring-blue-500:focus {
--tw-ring-color: rgb(217 119 6 / 0.45);
}
}
@@ -141,14 +141,17 @@ export default function SignInPageClient({ embedded = false }: { embedded?: bool
width={104}
height={104}
priority
className="h-24 w-24 rounded-[1.75rem] border border-blue-100 bg-white p-1.5 shadow-sm transition-colors dark:border-slate-700 dark:bg-slate-900"
className="h-24 w-24 rounded-[1.75rem] border border-stone-200/80 bg-white/85 p-1.5 shadow-sm transition-colors dark:border-stone-700 dark:bg-stone-900/80"
/>
</a>
</div>
<h1 className="mt-4 text-3xl font-black tracking-tight text-slate-900 dark:text-slate-100">
<p className="mt-5 text-xs font-bold uppercase tracking-[0.28em] text-amber-700 dark:text-amber-300">
RentalDriveGo
</p>
<h1 className="mt-3 text-3xl font-black tracking-[-0.03em] text-stone-950 dark:text-stone-50">
{dict.brandName}
</h1>
<p className="mt-2 text-sm text-slate-500 dark:text-slate-400">
<p className="mt-2 text-sm text-stone-600 dark:text-stone-300">
{dict.subtitle}
</p>
<div className="mt-4 flex items-center justify-center gap-2">
@@ -157,10 +160,10 @@ export default function SignInPageClient({ embedded = false }: { embedded?: bool
key={lang}
type="button"
onClick={() => setLanguage(lang)}
className={`rounded-full px-3 py-1 text-xs font-semibold uppercase tracking-wider transition-colors text-blue-600 dark:text-amber-400 ${
className={`rounded-full px-3 py-1 text-xs font-semibold uppercase tracking-wider transition-colors text-amber-700 dark:text-amber-300 ${
language === lang
? 'ring-1 ring-current opacity-100'
: 'opacity-40 hover:opacity-80'
: 'opacity-45 hover:opacity-80'
}`}
>
{lang}
@@ -169,7 +172,7 @@ export default function SignInPageClient({ embedded = false }: { embedded?: bool
</div>
</div>
<section className="rounded-[2rem] border border-slate-200 bg-white p-8 shadow-sm transition-colors dark:border-slate-800 dark:bg-slate-900/90">
<section className="rounded-[2rem] border border-stone-200/80 bg-white/82 p-8 shadow-[0_30px_80px_rgba(28,25,23,0.08)] backdrop-blur transition-colors dark:border-stone-800 dark:bg-stone-900/78 dark:shadow-[0_30px_80px_rgba(0,0,0,0.26)]">
<LocalSignInForm dict={dict} />
</section>
</div>
@@ -305,7 +308,7 @@ function LocalSignInForm({
return (
<>
{error ? (
<div className="mb-5 rounded-2xl border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700">
<div className="mb-5 rounded-[1.5rem] border border-red-200/80 bg-red-50/90 px-4 py-3 text-sm text-red-700 dark:border-red-900/60 dark:bg-red-950/40 dark:text-red-300">
{error}
</div>
) : null}
@@ -313,19 +316,19 @@ function LocalSignInForm({
{step === 'credentials' ? (
<form onSubmit={handleCredentials} className="space-y-5">
<div>
<label className="mb-1.5 block text-sm font-medium text-slate-700">{dict.email}</label>
<label className="mb-1.5 block text-sm font-medium text-stone-700 dark:text-stone-200">{dict.email}</label>
<input
type="email"
required
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="owner@company.com"
className="input-field"
className="w-full rounded-2xl border border-stone-200 bg-white px-4 py-3 text-sm text-stone-900 transition-colors 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>
<div>
<label className="mb-1.5 block text-sm font-medium text-slate-700">{dict.password}</label>
<label className="mb-1.5 block text-sm font-medium text-stone-700 dark:text-stone-200">{dict.password}</label>
<div className="relative">
<input
type={showPassword ? 'text' : 'password'}
@@ -333,12 +336,12 @@ function LocalSignInForm({
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="••••••••"
className="input-field pr-10"
className="w-full rounded-2xl border border-stone-200 bg-white px-4 py-3 pr-10 text-sm text-stone-900 transition-colors 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"
/>
<button
type="button"
onClick={() => setShowPassword((v) => !v)}
className="absolute inset-y-0 right-3 flex items-center text-slate-400 hover:text-slate-600"
className="absolute inset-y-0 right-3 flex items-center text-stone-400 hover:text-stone-700 dark:text-stone-500 dark:hover:text-stone-200"
tabIndex={-1}
>
{showPassword ? (
@@ -358,25 +361,25 @@ function LocalSignInForm({
<button
type="submit"
disabled={loading}
className="btn-primary w-full justify-center disabled:cursor-not-allowed disabled:opacity-60"
className="inline-flex w-full justify-center rounded-full bg-stone-950 px-6 py-3 text-sm font-semibold text-white transition hover:bg-stone-800 disabled:cursor-not-allowed disabled:opacity-60 dark:bg-amber-400 dark:text-stone-950 dark:hover:bg-amber-300"
>
{loading ? dict.signingIn : dict.signIn}
</button>
<div className="text-center">
<Link href="/forgot-password" className="text-sm text-slate-500 underline decoration-slate-300 underline-offset-4 hover:text-slate-700">
<Link href="/forgot-password" className="text-sm text-stone-500 underline decoration-stone-300 underline-offset-4 hover:text-stone-900 dark:text-stone-400 dark:decoration-stone-600 dark:hover:text-stone-100">
{dict.forgotPassword}
</Link>
</div>
</form>
) : (
<form onSubmit={handleTotp} className="space-y-5">
<div className="rounded-2xl border border-slate-200 bg-slate-50 px-4 py-4 text-center text-sm text-slate-600">
<div className="rounded-[1.5rem] border border-stone-200/80 bg-stone-50/90 px-4 py-4 text-center text-sm text-stone-600 dark:border-stone-700 dark:bg-stone-950/50 dark:text-stone-300">
{dict.enterCode}
</div>
<div>
<label className="mb-1.5 block text-sm font-medium text-slate-700">{dict.authCode}</label>
<label className="mb-1.5 block text-sm font-medium text-stone-700 dark:text-stone-200">{dict.authCode}</label>
<input
type="text"
inputMode="numeric"
@@ -386,14 +389,14 @@ function LocalSignInForm({
value={totpCode}
onChange={(e) => setTotpCode(e.target.value.replace(/\D/g, ''))}
placeholder="000000"
className="input-field text-center text-xl tracking-[0.45em]"
className="w-full rounded-2xl border border-stone-200 bg-white px-4 py-3 text-center text-xl tracking-[0.45em] text-stone-900 transition-colors 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="btn-primary w-full justify-center disabled:cursor-not-allowed disabled:opacity-60"
className="inline-flex w-full justify-center rounded-full bg-stone-950 px-6 py-3 text-sm font-semibold text-white transition hover:bg-stone-800 disabled:cursor-not-allowed disabled:opacity-60 dark:bg-amber-400 dark:text-stone-950 dark:hover:bg-amber-300"
>
{loading ? dict.verifying : dict.verify}
</button>
@@ -405,7 +408,7 @@ function LocalSignInForm({
setTotpCode('')
setError(null)
}}
className="w-full text-sm text-slate-500 hover:text-slate-700"
className="w-full text-sm text-stone-500 hover:text-stone-900 dark:text-stone-400 dark:hover:text-stone-100"
>
{dict.back}
</button>
@@ -1,3 +1,5 @@
import { headers } from 'next/headers'
import { redirect } from 'next/navigation'
import SignInPageClient from './SignInPageClient'
export default async function SignInPage({
@@ -8,5 +10,27 @@ export default async function SignInPage({
const params = await searchParams
const embedded = params.embedded === '1'
if (!embedded) {
const requestHeaders = headers()
const host = requestHeaders.get('host')
const proto = requestHeaders.get('x-forwarded-proto') ?? 'http'
const query = new URLSearchParams()
for (const [key, value] of Object.entries(params)) {
if (value === undefined) continue
if (Array.isArray(value)) {
for (const item of value) query.append(key, item)
} else {
query.set(key, value)
}
}
const destination = host
? `${proto}://${host}/sign-in${query.size ? `?${query.toString()}` : ''}`
: `${process.env.NEXT_PUBLIC_MARKETPLACE_URL ?? 'http://localhost:3000'}/sign-in${query.size ? `?${query.toString()}` : ''}`
redirect(destination)
}
return <SignInPageClient embedded={embedded} />
}
+7 -10
View File
@@ -1513,8 +1513,8 @@ export function DashboardLanguageSwitcher() {
}
return (
<div className="flex items-center gap-1 rounded-full border border-slate-200 bg-white px-2 py-1 shadow-sm transition-colors dark:border-slate-700 dark:bg-slate-900">
<span className="px-2 text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-500">
<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 DashboardLanguage[]).map((value) => {
@@ -1526,8 +1526,8 @@ export function DashboardLanguageSwitcher() {
onClick={() => handleLanguageChange(value)}
className={`rounded-full px-3 py-1.5 text-xs font-semibold transition ${
active
? 'bg-slate-900 text-white dark:bg-slate-100 dark:text-slate-950'
: 'text-slate-600 hover:bg-slate-100 dark:text-slate-300 dark:hover:bg-slate-800'
? '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.toUpperCase()}
@@ -1549,10 +1549,7 @@ export function DashboardThemeSwitcher() {
if (embedded) return null
return (
<div className="flex items-center gap-1 rounded-full border border-slate-200 bg-white px-2 py-1 shadow-sm transition-colors dark:border-slate-700 dark:bg-slate-900">
<span className="px-2 text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-500 dark:text-slate-400">
{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 DashboardTheme[]).map((value) => {
const active = value === theme
return (
@@ -1562,8 +1559,8 @@ export function DashboardThemeSwitcher() {
onClick={() => setTheme(value)}
className={`rounded-full px-3 py-1.5 text-xs font-semibold transition ${
active
? 'bg-slate-900 text-white dark:bg-slate-100 dark:text-slate-950'
: 'text-slate-600 hover:bg-slate-100 dark:text-slate-300 dark:hover:bg-slate-800'
? '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}
@@ -105,7 +105,7 @@ export default function PublicFooter() {
}, [])
return (
<footer className="border-t border-stone-200 bg-white/90 px-4 py-8 text-stone-600 transition-colors dark:border-white/5 dark:bg-[#111111] dark:text-stone-300">
<footer className="border-t border-stone-200/80 bg-white/72 px-4 py-8 text-stone-600 backdrop-blur-xl transition-colors dark:border-stone-800 dark:bg-stone-950/72 dark:text-stone-300">
<div className="mx-auto flex max-w-7xl flex-col items-center gap-5 px-0 text-center sm:px-2 lg:px-0">
<nav className="flex flex-wrap items-center justify-center gap-y-3 text-sm">
{footerContent.primary.map((item, index) => (
@@ -129,7 +129,7 @@ export default function PublicFooter() {
<button
type="button"
onClick={() => setLocaleMenuOpen((open) => !open)}
className="inline-flex items-center gap-2 text-sky-600 transition hover:text-sky-700 dark:text-sky-400 dark:hover:text-sky-300"
className="inline-flex items-center gap-2 text-amber-700 transition hover:text-stone-950 dark:text-amber-300 dark:hover:text-stone-100"
aria-expanded={localeMenuOpen}
aria-haspopup="menu"
>
@@ -35,7 +35,6 @@ export default function PublicHeader() {
createAccount: 'Create Agency Space',
theme: 'Theme',
light: 'Light',
medium: 'Medium',
dark: 'Dark',
},
fr: {
@@ -45,7 +44,6 @@ export default function PublicHeader() {
createAccount: 'Créer un espace dagence',
theme: 'Mode',
light: 'Clair',
medium: 'Moyen',
dark: 'Sombre',
},
ar: {
@@ -55,13 +53,11 @@ export default function PublicHeader() {
createAccount: 'إنشاء مساحة الوكالة',
theme: 'الوضع',
light: 'فاتح',
medium: 'متوسط',
dark: 'داكن',
},
}[language]
const themeOptions = [
{ value: 'light' as const, label: dict.light },
{ value: 'medium' as const, label: dict.medium },
{ value: 'dark' as const, label: dict.dark },
]
const currentTheme = themeOptions.find((option) => option.value === theme) ?? themeOptions[0]
@@ -101,7 +97,7 @@ export default function PublicHeader() {
const redirect = searchParams.get('redirect')
if (next) signInParams.set('next', next)
if (redirect) signInParams.set('redirect', redirect)
const signInHref = `/sign-in?${signInParams.toString()}`
const signInHref = `${marketplaceUrl}/sign-in?${signInParams.toString()}`
const signUpParams = new URLSearchParams()
signUpParams.set('lang', language)
@@ -109,7 +105,7 @@ export default function PublicHeader() {
const signUpHref = `/sign-up?${signUpParams.toString()}`
return (
<header className="sticky top-0 z-30 border-b border-stone-200 bg-white shadow-[0_10px_30px_rgba(15,23,42,0.06)] transition-colors dark:border-stone-800 dark:bg-stone-950 dark:shadow-[0_10px_30px_rgba(0,0,0,0.28)]">
<header className="sticky top-0 z-30 border-b border-stone-200/80 bg-white/78 backdrop-blur-xl shadow-[0_10px_30px_rgba(15,23,42,0.06)] transition-colors dark:border-stone-800 dark:bg-stone-950/76 dark:shadow-[0_10px_30px_rgba(0,0,0,0.28)]">
<div className="mx-auto flex max-w-7xl flex-col gap-1.5 px-4 py-1.5 sm:px-6 lg:flex-row lg:items-center lg:justify-between lg:gap-3 lg:px-8 lg:py-2">
<a href={marketplaceUrl} className="flex items-center gap-1.5 text-[11px] font-bold uppercase tracking-[0.14em] text-stone-900 dark:text-stone-100 sm:text-sm sm:tracking-[0.2em]">
<Image
@@ -131,9 +127,9 @@ export default function PublicHeader() {
<a href={`${marketplaceUrl}/explore`} className="rounded-full px-2.5 py-1 text-[12px] font-medium text-stone-600 transition hover:bg-stone-100 hover:text-stone-900 dark:text-stone-300 dark:hover:bg-stone-800 dark:hover:text-stone-100 sm:px-4 sm:py-2 sm:text-sm">
{dict.marketplace}
</a>
<Link href={signInHref} className="rounded-full px-2.5 py-1 text-[12px] font-medium text-stone-600 transition hover:bg-stone-100 hover:text-stone-900 dark:text-stone-300 dark:hover:bg-stone-800 dark:hover:text-stone-100 sm:px-4 sm:py-2 sm:text-sm">
<a href={signInHref} className="rounded-full px-2.5 py-1 text-[12px] font-medium text-stone-600 transition hover:bg-stone-100 hover:text-stone-900 dark:text-stone-300 dark:hover:bg-stone-800 dark:hover:text-stone-100 sm:px-4 sm:py-2 sm:text-sm">
{dict.signIn}
</Link>
</a>
<Link href={signUpHref} className="ml-1 rounded-full bg-stone-900 px-3 py-1 text-[12px] font-semibold text-white transition hover:bg-stone-700 dark:bg-amber-400 dark:text-stone-950 dark:hover:bg-amber-300 sm:ml-2 sm:px-5 sm:py-2 sm:text-sm">
{dict.createAccount}
</Link>
@@ -176,13 +172,10 @@ export default function PublicHeader() {
<button
type="button"
onClick={toggleThemeMenu}
className="inline-flex items-center gap-1.5 rounded-full px-1.5 py-1 transition hover:bg-stone-100 dark:hover:bg-stone-800 sm:gap-2 sm:px-2 sm:py-1.5"
className="inline-flex items-center gap-1.5 rounded-full px-2 py-1 transition hover:bg-stone-100 dark:hover:bg-stone-800 sm:px-2.5 sm:py-1.5"
aria-expanded={themeMenuOpen}
aria-haspopup="menu"
>
<span className="px-1.5 text-[10px] font-semibold uppercase tracking-[0.16em] text-stone-500 dark:text-stone-400 sm:px-2 sm:text-[11px] sm:tracking-[0.2em]">
{dict.theme}
</span>
<span className="rounded-full bg-stone-900 px-3 py-1 text-[11px] font-semibold text-white dark:bg-amber-400 dark:text-stone-950 sm:px-3.5 sm:py-1.5 sm:text-xs">
{currentTheme.label}
</span>
@@ -11,7 +11,7 @@ export default function PublicShell({
embedded?: boolean
}) {
return (
<div className="flex min-h-screen flex-col bg-[radial-gradient(circle_at_top,#dbeafe,transparent_35%),linear-gradient(180deg,#f8fafc,white)] transition-colors dark:bg-[radial-gradient(circle_at_top,rgba(37,99,235,0.18),transparent_35%),linear-gradient(180deg,#020617,#0f172a)] dark:text-slate-100">
<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">
{embedded ? null : <PublicHeader />}
<div className="flex flex-1 flex-col">{children}</div>
{embedded ? null : <PublicFooter />}
@@ -180,7 +180,7 @@ export default function Sidebar() {
onClick={() => setOpen(true)}
aria-label="Open sidebar"
className={[
'fixed top-1/2 z-50 -translate-y-1/2 flex items-center justify-center w-6 h-14 bg-slate-900 text-white shadow-lg lg:hidden',
'fixed top-1/2 z-50 flex h-14 w-6 -translate-y-1/2 items-center justify-center bg-stone-950 text-white shadow-lg lg:hidden',
isRtl ? 'right-0 rounded-l-lg' : 'left-0 rounded-r-lg',
].join(' ')}
>
@@ -190,19 +190,19 @@ export default function Sidebar() {
<aside
className={[
'fixed inset-y-0 z-40 flex w-64 flex-col bg-slate-900 transition-transform duration-300',
'fixed inset-y-0 z-40 flex w-64 flex-col border-r border-stone-800/80 bg-stone-950/94 backdrop-blur-xl transition-transform duration-300',
isRtl ? 'right-0' : 'left-0',
'lg:translate-x-0',
open ? 'translate-x-0' : (isRtl ? 'translate-x-full' : '-translate-x-full'),
].join(' ')}
>
<a href={marketplaceUrl} target="_top" className="flex items-center gap-3 border-b border-slate-800 px-6 py-5">
<div className="flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-lg bg-blue-500 overflow-hidden">
<a href={marketplaceUrl} target="_top" className="flex items-center gap-3 border-b border-stone-800/80 px-6 py-5">
<div className="flex h-8 w-8 flex-shrink-0 items-center justify-center overflow-hidden rounded-lg bg-amber-400">
{brand?.logoUrl ? (
// eslint-disable-next-line @next/next/no-img-element
<img src={brand.logoUrl} alt={brand.displayName} className="h-8 w-8 object-cover" />
) : (
<Car className="h-4 w-4 text-white" />
<Car className="h-4 w-4 text-stone-950" />
)}
</div>
<span className="truncate text-sm font-bold tracking-wide text-white">
@@ -219,8 +219,8 @@ export default function Sidebar() {
key={item.href}
href={item.href}
className={[
'flex items-center gap-3 rounded-lg px-3 py-2.5 text-sm font-medium transition-colors',
active ? 'bg-blue-600 text-white' : 'text-slate-400 hover:bg-slate-800 hover:text-white',
'flex items-center gap-3 rounded-2xl px-3 py-2.5 text-sm font-medium transition-colors',
active ? 'bg-amber-400 text-stone-950' : 'text-stone-400 hover:bg-stone-800 hover:text-white',
].join(' ')}
>
<Icon className="h-4 w-4 flex-shrink-0" />
@@ -230,19 +230,19 @@ export default function Sidebar() {
})}
</nav>
<div className="border-t border-slate-800 px-3 py-4">
<div className="flex items-center gap-3 rounded-lg px-3 py-2">
<div className="flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-full bg-blue-500 text-xs font-semibold text-white">
<div className="border-t border-stone-800/80 px-3 py-4">
<div className="flex items-center gap-3 rounded-2xl px-3 py-2">
<div className="flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-full bg-amber-400 text-xs font-semibold text-stone-950">
{user.initials}
</div>
<div className="min-w-0 flex-1">
<p className="truncate text-sm font-medium text-white">{user.displayName}</p>
<p className="truncate text-xs text-slate-400">{user.subtitle}</p>
<p className="truncate text-xs text-stone-400">{user.subtitle}</p>
</div>
</div>
<button
onClick={signOut}
className="mt-2 flex w-full items-center gap-3 rounded-lg px-3 py-2 text-sm text-slate-400 transition-colors hover:bg-slate-800 hover:text-white"
className="mt-2 flex w-full items-center gap-3 rounded-2xl px-3 py-2 text-sm text-stone-400 transition-colors hover:bg-stone-800 hover:text-white"
>
<LogOut className="h-4 w-4" />
{dict.signOut}
@@ -254,7 +254,7 @@ export default function Sidebar() {
onClick={() => setOpen(false)}
aria-label="Close sidebar"
className={[
'absolute top-1/2 z-10 -translate-y-1/2 flex items-center justify-center w-5 h-14 bg-slate-900 text-white shadow-lg lg:hidden',
'absolute top-1/2 z-10 flex h-14 w-5 -translate-y-1/2 items-center justify-center bg-stone-950 text-white shadow-lg lg:hidden',
isRtl ? '-left-5 rounded-l-lg' : '-right-5 rounded-r-lg',
].join(' ')}
>
+12 -12
View File
@@ -143,14 +143,14 @@ export default function TopBar() {
}
return (
<header className="flex h-16 items-center justify-between border-b border-slate-200 bg-white px-6 transition-colors dark:border-slate-800 dark:bg-slate-950">
<h1 className="text-lg font-semibold text-slate-900 dark:text-slate-100">{title}</h1>
<header className="flex h-16 items-center justify-between border-b border-stone-200/80 bg-white/78 px-6 backdrop-blur-xl transition-colors dark:border-stone-800 dark:bg-stone-950/76">
<h1 className="text-lg font-semibold text-stone-950 dark:text-stone-50">{title}</h1>
<div className="flex items-center gap-3">
<div className="relative">
<button
onClick={() => setShowNotifs(!showNotifs)}
className="relative rounded-lg p-2 text-slate-500 transition-colors hover:bg-slate-100 hover:text-slate-700 dark:text-slate-400 dark:hover:bg-slate-800 dark:hover:text-slate-100"
className="relative rounded-2xl p-2 text-stone-500 transition-colors hover:bg-stone-100 hover:text-stone-700 dark:text-stone-400 dark:hover:bg-stone-800 dark:hover:text-stone-100"
>
<Bell className="h-5 w-5" />
{unreadCount > 0 ? (
@@ -161,22 +161,22 @@ export default function TopBar() {
</button>
{showNotifs ? (
<div className="absolute right-0 top-full z-50 mt-2 w-80 rounded-xl border border-slate-200 bg-white p-4 shadow-lg dark:border-slate-700 dark:bg-slate-900">
<div className="absolute right-0 top-full z-50 mt-2 w-80 rounded-[1.75rem] border border-stone-200/80 bg-white/95 p-4 shadow-[0_20px_60px_rgba(0,0,0,0.12)] backdrop-blur dark:border-stone-800 dark:bg-stone-950/95 dark:shadow-[0_20px_60px_rgba(0,0,0,0.35)]">
<div className="mb-2 flex items-center justify-between gap-2">
<p className="text-sm font-medium text-slate-900 dark:text-slate-100">{dict.notifications}</p>
<p className="text-sm font-medium text-stone-950 dark:text-stone-100">{dict.notifications}</p>
<button
type="button"
onClick={() => openNotificationsInbox()}
className="text-xs font-medium text-blue-600 hover:text-blue-700"
className="text-xs font-medium text-amber-700 hover:text-stone-950 dark:text-amber-300 dark:hover:text-white"
>
View all
</button>
</div>
{loadingNotifs ? (
<p className="text-sm text-slate-500 dark:text-slate-400">Loading</p>
<p className="text-sm text-stone-500 dark:text-stone-400">Loading</p>
) : notifications.length === 0 ? (
<p className="text-sm text-slate-500 dark:text-slate-400">
<p className="text-sm text-stone-500 dark:text-stone-400">
{unreadCount === 0 ? dict.noNewNotifications : dict.unreadNotifications(unreadCount)}
</p>
) : (
@@ -187,14 +187,14 @@ export default function TopBar() {
type="button"
onClick={() => openNotificationsInbox(notification.id)}
className={[
'w-full rounded-lg px-3 py-2 text-left transition-colors hover:bg-slate-100 dark:hover:bg-slate-800',
'w-full rounded-2xl px-3 py-2 text-left transition-colors hover:bg-stone-100 dark:hover:bg-stone-800',
notification.readAt ? 'opacity-80' : '',
].join(' ')}
>
<p className="truncate text-sm font-medium text-slate-900 dark:text-slate-100">
<p className="truncate text-sm font-medium text-stone-950 dark:text-stone-100">
{notification.title}
</p>
<p className="truncate text-xs text-slate-500 dark:text-slate-400">
<p className="truncate text-xs text-stone-500 dark:text-stone-400">
{notification.body}
</p>
</button>
@@ -205,7 +205,7 @@ export default function TopBar() {
) : null}
</div>
<div className="flex h-8 w-8 items-center justify-center rounded-full bg-blue-500 text-xs font-semibold text-white">
<div className="flex h-8 w-8 items-center justify-center rounded-full bg-amber-400 text-xs font-semibold text-stone-950">
{userInitials}
</div>
</div>
@@ -16,8 +16,8 @@ export default function StatCard({
change,
vsLastMonthLabel = 'vs last month',
icon: Icon,
iconColor = 'text-blue-600',
iconBg = 'bg-blue-50',
iconColor = 'text-amber-700 dark:text-amber-300',
iconBg = 'bg-amber-50 dark:bg-amber-950/30',
}: StatCardProps) {
const isPositive = change !== undefined && change >= 0
const isNegative = change !== undefined && change < 0
@@ -26,24 +26,24 @@ export default function StatCard({
<div className="card p-6">
<div className="flex items-start justify-between">
<div>
<p className="text-sm text-slate-500 font-medium">{title}</p>
<p className="text-2xl font-bold text-slate-900 mt-1">{value}</p>
<p className="text-sm font-medium text-stone-500 dark:text-stone-400">{title}</p>
<p className="mt-1 text-2xl font-bold text-stone-950 dark:text-stone-50">{value}</p>
{change !== undefined && (
<div className="flex items-center gap-1 mt-2">
<div className="mt-2 flex items-center gap-1">
<span
className={[
'text-xs font-medium',
isPositive ? 'text-green-600' : isNegative ? 'text-red-600' : 'text-slate-500',
isPositive ? 'text-emerald-600 dark:text-emerald-300' : isNegative ? 'text-red-600 dark:text-red-300' : 'text-stone-500 dark:text-stone-400',
].join(' ')}
>
{isPositive ? '+' : ''}{change.toFixed(1)}%
</span>
<span className="text-xs text-slate-400">{vsLastMonthLabel}</span>
<span className="text-xs text-stone-400 dark:text-stone-500">{vsLastMonthLabel}</span>
</div>
)}
</div>
<div className={`w-12 h-12 rounded-xl ${iconBg} flex items-center justify-center flex-shrink-0`}>
<Icon className={`w-6 h-6 ${iconColor}`} />
<div className={`flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-[1.25rem] ${iconBg}`}>
<Icon className={`h-6 w-6 ${iconColor}`} />
</div>
</div>
</div>
+1 -1
View File
@@ -14,5 +14,5 @@ export const marketplaceUrl = toAppBase(
)
export const adminUrl = toAppBase(
resolveBrowserAppUrl(process.env.NEXT_PUBLIC_ADMIN_URL ?? 'http://localhost:3002/admin'),
resolveBrowserAppUrl(process.env.NEXT_PUBLIC_ADMIN_URL ?? 'http://localhost:3000/admin'),
)