unfied style
This commit is contained in:
@@ -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">
|
||||
|
||||
@@ -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} />
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user