style: unify marketplace color system across all pages (light + dark)

Apply a consistent design token set across every public page:
- Page bg: stone-50 / dark:stone-950
- Cards/surfaces: white / dark:stone-900 (via .card class)
- Primary text: stone-900 / dark:stone-100
- Secondary text: stone-600 / dark:stone-400
- Muted/label: stone-500 / dark:stone-500, stone-400 / dark:stone-500
- Accent kicker: amber-700 / dark:amber-400
- Image placeholder: stone-100 / dark:stone-800
- Primary button: stone-900 white / dark:white dark:stone-900
- Amber button: amber-500 white / dark:amber-400 dark:stone-900
- Status available: emerald-100 emerald-700 / dark:emerald-950/50 dark:emerald-400
- Status unavailable: rose-100 rose-700 / dark:rose-950/50 dark:rose-400

Also: remove dead medium-theme CSS overrides from globals.css,
add dark:[color-scheme:dark] to date/time inputs so browser
native pickers adapt, and unify amber accent to amber-400 in dark.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
root
2026-05-23 03:06:46 -04:00
parent b82a5d4633
commit fd10d0db6a
11 changed files with 185 additions and 309 deletions
@@ -23,13 +23,8 @@ function toUtcDateTime(date: string, time: string) {
return `${date}T${time || '10:00'}:00.000Z`
}
function inputClassName(hasLeadingIcon = true) {
return `w-full border-0 bg-white px-4 py-4 text-sm text-stone-900 outline-none placeholder:text-stone-400 ${hasLeadingIcon ? 'pl-2' : ''}`
}
function selectClassName() {
return 'w-full border-0 bg-white py-4 text-sm text-stone-900 outline-none'
}
const inputBase = 'w-full border-0 bg-white dark:bg-stone-800 px-4 py-4 text-sm text-stone-900 dark:text-stone-100 outline-none placeholder:text-stone-400 dark:placeholder:text-stone-500'
const selectBase = 'w-full border-0 bg-white dark:bg-stone-800 py-4 text-sm text-stone-900 dark:text-stone-100 outline-none'
export default function ExploreSearchForm({
pickupLocation,
@@ -103,17 +98,17 @@ export default function ExploreSearchForm({
}
return (
<div className="mt-8 rounded-[1.75rem] bg-white p-5 text-stone-900 shadow-[0_24px_80px_rgba(0,0,0,0.16)]">
<h2 className="text-3xl font-black tracking-tight">{dict.searchTitle}</h2>
<div className="mt-8 rounded-[1.75rem] bg-white dark:bg-stone-900 p-5 text-stone-900 dark:text-stone-100 shadow-[0_24px_80px_rgba(0,0,0,0.16)]">
<h2 className="text-3xl font-black tracking-tight text-stone-900 dark:text-stone-100">{dict.searchTitle}</h2>
<form onSubmit={handleSubmit} className="mt-5 space-y-4">
<div className="grid gap-px overflow-hidden rounded-[1.35rem] border border-stone-200 bg-stone-200 md:grid-cols-2">
<label className="flex items-center gap-3 bg-white px-4">
<MapPin className="h-5 w-5 text-stone-300" />
<div className="grid gap-px overflow-hidden rounded-[1.35rem] border border-stone-200 dark:border-stone-700 bg-stone-200 dark:bg-stone-700 md:grid-cols-2">
<label className="flex items-center gap-3 bg-white dark:bg-stone-800 px-4">
<MapPin className="h-5 w-5 text-stone-300 dark:text-stone-500" />
<select
name="pickupLocation"
value={filters.pickupLocation}
onChange={(event) => updateFilter('pickupLocation', event.target.value)}
className={selectClassName()}
className={selectBase}
>
<option value="">{dict.pickupLocation}</option>
{cityOptions.map((city) => (
@@ -121,13 +116,13 @@ export default function ExploreSearchForm({
))}
</select>
</label>
<label className="flex items-center gap-3 bg-white px-4">
<MapPin className="h-5 w-5 text-stone-300" />
<label className="flex items-center gap-3 bg-white dark:bg-stone-800 px-4">
<MapPin className="h-5 w-5 text-stone-300 dark:text-stone-500" />
<select
name="dropoffLocation"
value={filters.dropoffLocation}
onChange={(event) => updateFilter('dropoffLocation', event.target.value)}
className={selectClassName()}
className={selectBase}
>
<option value="">{dict.dropoffLocation}</option>
{cityOptions.map((city) => (
@@ -137,82 +132,82 @@ export default function ExploreSearchForm({
</label>
</div>
<div className="grid gap-px overflow-hidden rounded-[1.35rem] border border-stone-200 bg-stone-200 md:grid-cols-4">
<label className="flex items-center gap-3 bg-white px-4">
<CalendarDays className="h-5 w-5 text-stone-500" />
<div className="grid gap-px overflow-hidden rounded-[1.35rem] border border-stone-200 dark:border-stone-700 bg-stone-200 dark:bg-stone-700 md:grid-cols-4">
<label className="flex items-center gap-3 bg-white dark:bg-stone-800 px-4">
<CalendarDays className="h-5 w-5 text-stone-500 dark:text-stone-400" />
<div className="flex w-full flex-col py-3">
<span className="text-xs font-semibold uppercase tracking-[0.14em] text-stone-400">{dict.pickupDate}</span>
<span className="text-xs font-semibold uppercase tracking-[0.14em] text-stone-400 dark:text-stone-500">{dict.pickupDate}</span>
<input
name="pickupDate"
type="date"
value={filters.pickupDate}
min={new Date().toISOString().slice(0, 10)}
onChange={(event) => updateFilter('pickupDate', event.target.value)}
className="border-0 bg-transparent px-0 py-0 text-sm text-stone-900 outline-none"
className="border-0 bg-transparent px-0 py-0 text-sm text-stone-900 dark:text-stone-100 outline-none [color-scheme:light] dark:[color-scheme:dark]"
/>
</div>
</label>
<label className="flex items-center gap-3 bg-white px-4">
<Clock3 className="h-5 w-5 text-stone-500" />
<label className="flex items-center gap-3 bg-white dark:bg-stone-800 px-4">
<Clock3 className="h-5 w-5 text-stone-500 dark:text-stone-400" />
<div className="flex w-full flex-col py-3">
<span className="text-xs font-semibold uppercase tracking-[0.14em] text-stone-400">{dict.hour}</span>
<span className="text-xs font-semibold uppercase tracking-[0.14em] text-stone-400 dark:text-stone-500">{dict.hour}</span>
<input
name="pickupTime"
type="time"
value={filters.pickupTime}
onChange={(event) => updateFilter('pickupTime', event.target.value)}
className="border-0 bg-transparent px-0 py-0 text-sm text-stone-900 outline-none"
className="border-0 bg-transparent px-0 py-0 text-sm text-stone-900 dark:text-stone-100 outline-none [color-scheme:light] dark:[color-scheme:dark]"
/>
</div>
</label>
<label className="flex items-center gap-3 bg-white px-4">
<CalendarDays className="h-5 w-5 text-stone-500" />
<label className="flex items-center gap-3 bg-white dark:bg-stone-800 px-4">
<CalendarDays className="h-5 w-5 text-stone-500 dark:text-stone-400" />
<div className="flex w-full flex-col py-3">
<span className="text-xs font-semibold uppercase tracking-[0.14em] text-stone-400">{dict.returnDate}</span>
<span className="text-xs font-semibold uppercase tracking-[0.14em] text-stone-400 dark:text-stone-500">{dict.returnDate}</span>
<input
name="returnDate"
type="date"
value={filters.returnDate}
min={filters.pickupDate || new Date().toISOString().slice(0, 10)}
onChange={(event) => updateFilter('returnDate', event.target.value)}
className="border-0 bg-transparent px-0 py-0 text-sm text-stone-900 outline-none"
className="border-0 bg-transparent px-0 py-0 text-sm text-stone-900 dark:text-stone-100 outline-none [color-scheme:light] dark:[color-scheme:dark]"
/>
</div>
</label>
<label className="flex items-center gap-3 bg-white px-4">
<Clock3 className="h-5 w-5 text-stone-500" />
<label className="flex items-center gap-3 bg-white dark:bg-stone-800 px-4">
<Clock3 className="h-5 w-5 text-stone-500 dark:text-stone-400" />
<div className="flex w-full flex-col py-3">
<span className="text-xs font-semibold uppercase tracking-[0.14em] text-stone-400">{dict.hour}</span>
<span className="text-xs font-semibold uppercase tracking-[0.14em] text-stone-400 dark:text-stone-500">{dict.hour}</span>
<input
name="returnTime"
type="time"
value={filters.returnTime}
onChange={(event) => updateFilter('returnTime', event.target.value)}
className="border-0 bg-transparent px-0 py-0 text-sm text-stone-900 outline-none"
className="border-0 bg-transparent px-0 py-0 text-sm text-stone-900 dark:text-stone-100 outline-none [color-scheme:light] dark:[color-scheme:dark]"
/>
</div>
</label>
</div>
<div className="relative z-10 grid gap-px overflow-visible rounded-[1.35rem] border border-stone-200 bg-stone-200 lg:grid-cols-[1.1fr_1.1fr_1.35fr]">
<label className="flex items-center gap-3 rounded-t-[1.35rem] bg-white px-4 lg:rounded-l-[1.35rem] lg:rounded-tr-none">
<TicketPercent className="h-5 w-5 text-stone-300" />
<div className="relative z-10 grid gap-px overflow-visible rounded-[1.35rem] border border-stone-200 dark:border-stone-700 bg-stone-200 dark:bg-stone-700 lg:grid-cols-[1.1fr_1.1fr_1.35fr]">
<label className="flex items-center gap-3 rounded-t-[1.35rem] bg-white dark:bg-stone-800 px-4 lg:rounded-l-[1.35rem] lg:rounded-tr-none">
<TicketPercent className="h-5 w-5 text-stone-300 dark:text-stone-500" />
<input
name="promoCode"
value={filters.promoCode}
onChange={(event) => updateFilter('promoCode', event.target.value)}
placeholder={dict.promoCode}
className={inputClassName()}
className={`${inputBase} pl-2`}
/>
</label>
<label className="flex items-center justify-between gap-3 bg-white px-4 py-3">
<label className="flex items-center justify-between gap-3 bg-white dark:bg-stone-800 px-4 py-3">
<div className="flex min-w-0 items-center gap-3">
<span className="text-sm font-semibold text-stone-600">{dict.myAge}</span>
<span className="text-sm font-semibold text-stone-600 dark:text-stone-300">{dict.myAge}</span>
<select
name="driverAge"
value={filters.driverAge}
onChange={(event) => updateFilter('driverAge', event.target.value)}
className="border-0 bg-transparent text-sm font-semibold text-stone-900 outline-none"
className="border-0 bg-transparent text-sm font-semibold text-stone-900 dark:text-stone-100 outline-none"
>
{ageOptions.map((option) => (
<option key={option} value={option}>{option}</option>
@@ -228,21 +223,21 @@ export default function ExploreSearchForm({
type="button"
aria-label={dict.ageDescription}
onClick={() => setAgeHelpOpen((open) => !open)}
className="text-stone-400 transition hover:text-stone-600"
className="text-stone-400 dark:text-stone-500 transition hover:text-stone-600 dark:hover:text-stone-300"
>
<Info className="h-5 w-5" />
</button>
{ageHelpOpen ? (
<div className="absolute bottom-[calc(100%+0.85rem)] right-0 z-30 w-72 rounded-2xl border border-[#ffc400] bg-white px-4 py-3 text-left text-sm leading-6 text-stone-700 shadow-[0_16px_40px_rgba(0,0,0,0.14)]">
<div className="absolute bottom-[calc(100%+0.85rem)] right-0 z-30 w-72 rounded-2xl border border-amber-300 dark:border-amber-600 bg-white dark:bg-stone-900 px-4 py-3 text-left text-sm leading-6 text-stone-700 dark:text-stone-200 shadow-[0_16px_40px_rgba(0,0,0,0.14)]">
{dict.ageDescription}
<span className="absolute right-6 top-full h-3 w-3 -translate-y-1/2 rotate-45 border-b border-r border-[#ffc400] bg-white" />
<span className="absolute right-6 top-full h-3 w-3 -translate-y-1/2 rotate-45 border-b border-r border-amber-300 dark:border-amber-600 bg-white dark:bg-stone-900" />
</div>
) : null}
</div>
</label>
<button
type="submit"
className="min-h-[76px] rounded-b-[1.35rem] bg-stone-900 px-6 text-sm font-black uppercase tracking-[0.08em] text-white transition hover:bg-stone-700 lg:rounded-b-none lg:rounded-r-[1.35rem]"
className="min-h-[76px] rounded-b-[1.35rem] bg-stone-900 px-6 text-sm font-black uppercase tracking-[0.08em] text-white transition hover:bg-stone-700 dark:bg-amber-400 dark:text-stone-900 dark:hover:bg-amber-300 lg:rounded-b-none lg:rounded-r-[1.35rem]"
>
{dict.search}
</button>
@@ -59,13 +59,13 @@ export default function ExploreVehicleGrid({ vehicles, dict }: { vehicles: Vehic
return (
<section>
<div className="flex items-center justify-between">
<h2 className="text-2xl font-bold text-stone-900">{dict.availableVehicles}</h2>
<p className="text-sm text-stone-500">{vehicles.length} {dict.listings}</p>
<h2 className="text-2xl font-bold text-stone-900 dark:text-stone-100">{dict.availableVehicles}</h2>
<p className="text-sm text-stone-500 dark:text-stone-400">{vehicles.length} {dict.listings}</p>
</div>
<div className="mt-4 grid gap-5 md:grid-cols-2 xl:grid-cols-3">
{vehicles.map((vehicle) => (
<article key={vehicle.id} className="card overflow-hidden">
<div className="aspect-[16/10] bg-stone-100">
<div className="aspect-[16/10] bg-stone-100 dark:bg-stone-800">
{vehicle.photos[0] ? (
// eslint-disable-next-line @next/next/no-img-element
<img src={vehicle.photos[0]} alt={`${vehicle.make} ${vehicle.model}`} className="h-full w-full object-cover" />
@@ -74,27 +74,27 @@ export default function ExploreVehicleGrid({ vehicles, dict }: { vehicles: Vehic
<div className="p-5">
<div className="flex items-start justify-between gap-4">
<div>
<p className="text-xs uppercase tracking-[0.16em] text-stone-500">{vehicle.company.brand?.displayName ?? dict.rentalCompany}</p>
<h3 className="mt-2 text-xl font-bold text-stone-900">{vehicle.make} {vehicle.model}</h3>
<p className="mt-1 text-sm text-stone-500">{vehicle.year} · {vehicle.category}</p>
<p className="text-xs uppercase tracking-[0.16em] text-stone-500 dark:text-stone-400">{vehicle.company.brand?.displayName ?? dict.rentalCompany}</p>
<h3 className="mt-2 text-xl font-bold text-stone-900 dark:text-stone-100">{vehicle.make} {vehicle.model}</h3>
<p className="mt-1 text-sm text-stone-500 dark:text-stone-400">{vehicle.year} · {vehicle.category}</p>
</div>
<span className={`rounded-full px-3 py-1 text-xs font-semibold ${vehicle.availability === false ? 'bg-rose-100 text-rose-700' : 'bg-emerald-100 text-emerald-700'}`}>
<span className={`shrink-0 rounded-full px-3 py-1 text-xs font-semibold ${vehicle.availability === false ? 'bg-rose-100 text-rose-700 dark:bg-rose-950/50 dark:text-rose-400' : 'bg-emerald-100 text-emerald-700 dark:bg-emerald-950/50 dark:text-emerald-400'}`}>
{getStatusCopy(vehicle, dict)}
</span>
</div>
{vehicle.availability === false && (
<p className="mt-3 text-sm text-stone-500">
<p className="mt-3 text-sm text-stone-500 dark:text-stone-400">
{vehicle.nextAvailableAt ? `${dict.availableFrom} ${formatAvailabilityDate(vehicle.nextAvailableAt)}` : dict.unavailableNoDate}
</p>
)}
<div className="mt-5 flex items-end justify-between">
<div>
<p className="text-sm text-stone-500">{dict.from}</p>
<p className="text-2xl font-black text-stone-900">{formatCents(vehicle.dailyRate)}</p>
<p className="text-sm text-stone-500 dark:text-stone-400">{dict.from}</p>
<p className="text-2xl font-black text-stone-900 dark:text-stone-100">{formatCents(vehicle.dailyRate)}</p>
</div>
<Link
href={`/explore/${vehicle.company.slug}/vehicles/${vehicle.id}`}
className="rounded-full bg-stone-900 px-5 py-2.5 text-sm font-semibold text-white transition hover:bg-stone-700"
className="rounded-full bg-stone-900 px-5 py-2.5 text-sm font-semibold text-white transition hover:bg-stone-700 dark:bg-white dark:text-stone-900 dark:hover:bg-stone-100"
>
{dict.viewVehicle}
</Link>
@@ -103,7 +103,7 @@ export default function ExploreVehicleGrid({ vehicles, dict }: { vehicles: Vehic
</article>
))}
{vehicles.length === 0 && (
<div className="card p-6 text-sm text-stone-500 md:col-span-2 xl:col-span-3">{dict.vehicleUnavailable}</div>
<div className="card p-6 text-sm text-stone-500 dark:text-stone-400 md:col-span-2 xl:col-span-3">{dict.vehicleUnavailable}</div>
)}
</div>
</section>
@@ -45,7 +45,7 @@ export default async function CompanyProfilePage({ params }: { params: { slug: s
unavailable: 'Marketplace indisponible',
unavailableTitle: "Les détails de l'entreprise sont temporairement indisponibles.",
unavailableBody: "L'API marketplace fonctionne, mais la base de données n'est pas accessible dans cet environnement local.",
backToExplore: 'Retour à lexploration',
backToExplore: "Retour à l'exploration",
partner: 'Partenaire marketplace',
vehicles: 'véhicules',
offers: 'offres actives',
@@ -82,14 +82,14 @@ export default async function CompanyProfilePage({ params }: { params: { slug: s
if (!company) {
return (
<main className="min-h-screen bg-stone-50 py-10">
<main className="min-h-screen bg-stone-50 dark:bg-stone-950 py-10">
<div className="shell">
<section className="card p-8">
<p className="text-sm uppercase tracking-[0.18em] text-amber-700">{dict.unavailable}</p>
<h1 className="mt-3 text-3xl font-black text-stone-900">{dict.unavailableTitle}</h1>
<p className="mt-3 text-stone-600">{dict.unavailableBody}</p>
<p className="text-sm uppercase tracking-[0.18em] text-amber-700 dark:text-amber-400">{dict.unavailable}</p>
<h1 className="mt-3 text-3xl font-black text-stone-900 dark:text-stone-100">{dict.unavailableTitle}</h1>
<p className="mt-3 text-stone-600 dark:text-stone-400">{dict.unavailableBody}</p>
<div className="mt-6">
<Link href="/explore" className="rounded-full bg-stone-900 px-5 py-2.5 text-sm font-semibold text-white">{dict.backToExplore}</Link>
<Link href="/explore" className="rounded-full bg-stone-900 px-5 py-2.5 text-sm font-semibold text-white transition hover:bg-stone-700 dark:bg-white dark:text-stone-900 dark:hover:bg-stone-100">{dict.backToExplore}</Link>
</div>
</section>
</div>
@@ -98,13 +98,13 @@ export default async function CompanyProfilePage({ params }: { params: { slug: s
}
return (
<main className="min-h-screen bg-stone-50 py-10">
<main className="min-h-screen bg-stone-50 dark:bg-stone-950 py-10">
<div className="shell space-y-8">
<section className="card p-8">
<p className="text-sm uppercase tracking-[0.18em] text-amber-700">{company.brand?.publicCity ?? dict.partner}</p>
<h1 className="mt-3 text-4xl font-black text-stone-900">{company.brand?.displayName ?? company.name}</h1>
<p className="mt-3 text-stone-600">{company.brand?.publicCountry ?? ''}</p>
<div className="mt-6 flex flex-wrap gap-3 text-sm text-stone-500">
<p className="text-sm uppercase tracking-[0.18em] text-amber-700 dark:text-amber-400">{company.brand?.publicCity ?? dict.partner}</p>
<h1 className="mt-3 text-4xl font-black text-stone-900 dark:text-stone-100">{company.brand?.displayName ?? company.name}</h1>
<p className="mt-3 text-stone-600 dark:text-stone-400">{company.brand?.publicCountry ?? ''}</p>
<div className="mt-6 flex flex-wrap gap-3 text-sm text-stone-500 dark:text-stone-400">
<span>{company.vehicles.length} {dict.vehicles}</span>
<span>{company.offers.length} {dict.offers}</span>
<span>{dict.rating} {company.brand?.marketplaceRating?.toFixed(1) ?? dict.new}</span>
@@ -112,41 +112,41 @@ export default async function CompanyProfilePage({ params }: { params: { slug: s
</section>
<section>
<h2 className="text-2xl font-bold text-stone-900">{dict.currentOffers}</h2>
<h2 className="text-2xl font-bold text-stone-900 dark:text-stone-100">{dict.currentOffers}</h2>
<div className="mt-4 grid gap-4 md:grid-cols-2">
{company.offers.map((offer) => (
<div key={offer.id} className="card p-5">
<p className="text-sm font-semibold text-stone-900">{offer.title}</p>
<p className="mt-2 text-3xl font-black text-amber-700">{offer.discountValue}%</p>
<p className="mt-2 text-sm text-stone-500">{dict.validUntil} {new Date(offer.validUntil).toLocaleDateString()}</p>
<p className="text-sm font-semibold text-stone-900 dark:text-stone-100">{offer.title}</p>
<p className="mt-2 text-3xl font-black text-amber-700 dark:text-amber-400">{offer.discountValue}%</p>
<p className="mt-2 text-sm text-stone-500 dark:text-stone-400">{dict.validUntil} {new Date(offer.validUntil).toLocaleDateString()}</p>
</div>
))}
{company.offers.length === 0 && <div className="card p-6 text-sm text-stone-500">{dict.noOffers}</div>}
{company.offers.length === 0 && <div className="card p-6 text-sm text-stone-500 dark:text-stone-400">{dict.noOffers}</div>}
</div>
</section>
<section>
<h2 className="text-2xl font-bold text-stone-900">{dict.fleet}</h2>
<h2 className="text-2xl font-bold text-stone-900 dark:text-stone-100">{dict.fleet}</h2>
<div className="mt-4 grid gap-5 md:grid-cols-2 xl:grid-cols-3">
{company.vehicles.map((vehicle) => (
<article key={vehicle.id} className="card overflow-hidden">
<div className="aspect-[16/10] bg-stone-100">
<div className="aspect-[16/10] bg-stone-100 dark:bg-stone-800">
{vehicle.photos[0] ? (
// eslint-disable-next-line @next/next/no-img-element
<img src={vehicle.photos[0]} alt={`${vehicle.make} ${vehicle.model}`} className="h-full w-full object-cover" />
) : null}
</div>
<div className="p-5">
<h3 className="text-xl font-bold text-stone-900">{vehicle.make} {vehicle.model}</h3>
<p className="mt-1 text-sm text-stone-500">{vehicle.category}</p>
<h3 className="text-xl font-bold text-stone-900 dark:text-stone-100">{vehicle.make} {vehicle.model}</h3>
<p className="mt-1 text-sm text-stone-500 dark:text-stone-400">{vehicle.category}</p>
{!vehicle.availability && (
<p className="mt-2 text-sm text-stone-500">
<p className="mt-2 text-sm text-stone-500 dark:text-stone-400">
{vehicle.nextAvailableAt ? `${dict.availableFrom} ${new Date(vehicle.nextAvailableAt).toLocaleDateString('en-GB', { year: 'numeric', month: 'long', day: 'numeric' })}` : dict.unavailableNoDate}
</p>
)}
<div className="mt-4 flex items-end justify-between">
<p className="text-xl font-black text-stone-900">{formatCurrency(vehicle.dailyRate, 'MAD')}</p>
<Link href={`/explore/${params.slug}/vehicles/${vehicle.id}`} className="rounded-full bg-stone-900 px-5 py-2.5 text-sm font-semibold text-white">{dict.viewVehicle}</Link>
<p className="text-xl font-black text-stone-900 dark:text-stone-100">{formatCurrency(vehicle.dailyRate, 'MAD')}</p>
<Link href={`/explore/${params.slug}/vehicles/${vehicle.id}`} className="rounded-full bg-stone-900 px-5 py-2.5 text-sm font-semibold text-white transition hover:bg-stone-700 dark:bg-white dark:text-stone-900 dark:hover:bg-stone-100">{dict.viewVehicle}</Link>
</div>
</div>
</article>
@@ -107,15 +107,15 @@ export default async function VehicleDetailPage({ params }: { params: { slug: st
if (!vehicle) {
return (
<main className="min-h-screen bg-stone-50 py-10">
<main className="min-h-screen bg-stone-50 dark:bg-stone-950 py-10">
<div className="shell">
<section className="card p-8">
<p className="text-sm uppercase tracking-[0.18em] text-amber-700">{dict.unavailable}</p>
<p className="text-sm uppercase tracking-[0.18em] text-amber-700 dark:text-amber-400">{dict.unavailable}</p>
<h1 className="mt-3 text-3xl font-black text-stone-900 dark:text-stone-100">{dict.unavailableTitle}</h1>
<p className="mt-3 text-stone-600 dark:text-stone-400">{dict.unavailableBody}</p>
<div className="mt-6 flex gap-3">
<Link href={`/explore/${params.slug}`} className="rounded-full bg-stone-900 px-5 py-2.5 text-sm font-semibold text-white">{dict.backToFleet}</Link>
<Link href="/explore" className="rounded-full border border-stone-300 px-5 py-2.5 text-sm font-semibold text-stone-700 dark:border-stone-600 dark:text-stone-300">{dict.backToExplore}</Link>
<Link href={`/explore/${params.slug}`} className="rounded-full bg-stone-900 px-5 py-2.5 text-sm font-semibold text-white transition hover:bg-stone-700 dark:bg-white dark:text-stone-900 dark:hover:bg-stone-100">{dict.backToFleet}</Link>
<Link href="/explore" className="rounded-full border border-stone-200 px-5 py-2.5 text-sm font-semibold text-stone-700 transition hover:border-stone-400 dark:border-stone-700 dark:text-stone-300 dark:hover:border-stone-500">{dict.backToExplore}</Link>
</div>
</section>
</div>
@@ -237,9 +237,9 @@ export default async function ExplorePage({ searchParams }: { searchParams?: Rec
).sort((left, right) => left.localeCompare(right))
return (
<main className="min-h-screen bg-stone-50 py-10">
<main className="min-h-screen bg-stone-50 dark:bg-stone-950 py-10">
<div className="shell space-y-10">
<section className="rounded-[2rem] bg-[linear-gradient(135deg,#1c1917,#57534e)] px-8 py-12 text-white">
<section className="rounded-[2rem] bg-[linear-gradient(135deg,#1c1917,#57534e)] dark:bg-[linear-gradient(135deg,#0c0a09,#292524)] px-8 py-12 text-white">
<p className="text-sm uppercase tracking-[0.2em] text-amber-300">{dict.kicker}</p>
<h1 className="mt-4 text-4xl font-black tracking-tight">{dict.title}</h1>
<p className="mt-4 max-w-2xl text-stone-300">{dict.body}</p>
@@ -259,29 +259,29 @@ export default async function ExplorePage({ searchParams }: { searchParams?: Rec
<section>
<div className="flex items-center justify-between">
<h2 className="text-2xl font-bold text-stone-900">{dict.currentDeals}</h2>
<p className="text-sm text-stone-500">{dict.featuredOffers}</p>
<h2 className="text-2xl font-bold text-stone-900 dark:text-stone-100">{dict.currentDeals}</h2>
<p className="text-sm text-stone-500 dark:text-stone-400">{dict.featuredOffers}</p>
</div>
<div className="mt-4 grid gap-4 md:grid-cols-2 xl:grid-cols-4">
{offers.map((offer) => (
<Link key={offer.id} href={`/explore/${offer.company.brand?.subdomain ?? ''}#offers`} className="card p-5 hover:border-amber-300 transition-colors">
<p className="text-xs uppercase tracking-[0.18em] text-amber-700">{offer.company.brand?.displayName ?? dict.company}</p>
<h3 className="mt-3 text-lg font-semibold text-stone-900">{offer.title}</h3>
<p className="mt-3 text-3xl font-black text-stone-900">{offer.discountValue}%</p>
<p className="mt-2 text-sm text-stone-500">{dict.validUntil} {new Date(offer.validUntil).toLocaleDateString()}</p>
<Link key={offer.id} href={`/explore/${offer.company.brand?.subdomain ?? ''}#offers`} className="card p-5 hover:border-amber-300 dark:hover:border-amber-500 transition-colors">
<p className="text-xs uppercase tracking-[0.18em] text-amber-700 dark:text-amber-400">{offer.company.brand?.displayName ?? dict.company}</p>
<h3 className="mt-3 text-lg font-semibold text-stone-900 dark:text-stone-100">{offer.title}</h3>
<p className="mt-3 text-3xl font-black text-stone-900 dark:text-stone-100">{offer.discountValue}%</p>
<p className="mt-2 text-sm text-stone-500 dark:text-stone-400">{dict.validUntil} {new Date(offer.validUntil).toLocaleDateString()}</p>
</Link>
))}
{offers.length === 0 && <div className="card p-6 text-sm text-stone-500 md:col-span-2 xl:col-span-4">{dict.offersUnavailable}</div>}
{offers.length === 0 && <div className="card p-6 text-sm text-stone-500 dark:text-stone-400 md:col-span-2 xl:col-span-4">{dict.offersUnavailable}</div>}
</div>
</section>
<ExploreVehicleGrid vehicles={vehicles} dict={dict} />
<section>
<h2 className="text-2xl font-bold text-stone-900">{dict.browseByCategory}</h2>
<h2 className="text-2xl font-bold text-stone-900 dark:text-stone-100">{dict.browseByCategory}</h2>
<div className="mt-4 flex flex-wrap gap-3">
{dict.categories.map((categoryName, index) => (
<Link key={CATEGORY_VALUES[index] ?? categoryName} href={`/explore?category=${CATEGORY_VALUES[index]}`} className="rounded-full border border-stone-300 bg-white px-4 py-2 text-sm font-semibold text-stone-700">
<Link key={CATEGORY_VALUES[index] ?? categoryName} href={`/explore?category=${CATEGORY_VALUES[index]}`} className="rounded-full border border-stone-200 bg-white px-4 py-2 text-sm font-semibold text-stone-700 transition hover:border-stone-400 dark:border-stone-700 dark:bg-stone-900 dark:text-stone-300 dark:hover:border-stone-500">
{categoryName}
</Link>
))}
@@ -290,17 +290,17 @@ export default async function ExplorePage({ searchParams }: { searchParams?: Rec
<section>
<div className="flex items-center justify-between">
<h2 className="text-2xl font-bold text-stone-900">{dict.browseByCompany}</h2>
<p className="text-sm text-stone-500">{dict.marketplacePartners}</p>
<h2 className="text-2xl font-bold text-stone-900 dark:text-stone-100">{dict.browseByCompany}</h2>
<p className="text-sm text-stone-500 dark:text-stone-400">{dict.marketplacePartners}</p>
</div>
<div className="mt-4 grid gap-4 md:grid-cols-2 xl:grid-cols-4">
{companies.map((company) => (
<Link key={company.id} href={`/explore/${company.brand?.subdomain ?? ''}`} className="card p-5 hover:border-amber-300 transition-colors">
<p className="text-xs uppercase tracking-[0.18em] text-stone-500">{company.brand?.publicCity ?? dict.marketplacePartner}</p>
<h3 className="mt-3 text-lg font-semibold text-stone-900">{company.brand?.displayName ?? dict.rentalCompany}</h3>
<p className="mt-2 text-sm text-stone-500">{dict.rating} {company.brand?.marketplaceRating?.toFixed(1) ?? dict.new}</p>
<p className="mt-1 text-sm text-stone-500">{company._count.vehicles} {dict.publishedVehicles}</p>
<span className="mt-4 inline-flex rounded-full bg-stone-900 px-4 py-2 text-sm font-semibold text-white">{dict.viewFleet}</span>
<Link key={company.id} href={`/explore/${company.brand?.subdomain ?? ''}`} className="card p-5 hover:border-amber-300 dark:hover:border-amber-500 transition-colors">
<p className="text-xs uppercase tracking-[0.18em] text-stone-500 dark:text-stone-400">{company.brand?.publicCity ?? dict.marketplacePartner}</p>
<h3 className="mt-3 text-lg font-semibold text-stone-900 dark:text-stone-100">{company.brand?.displayName ?? dict.rentalCompany}</h3>
<p className="mt-2 text-sm text-stone-500 dark:text-stone-400">{dict.rating} {company.brand?.marketplaceRating?.toFixed(1) ?? dict.new}</p>
<p className="mt-1 text-sm text-stone-500 dark:text-stone-400">{company._count.vehicles} {dict.publishedVehicles}</p>
<span className="mt-4 inline-flex rounded-full bg-stone-900 px-4 py-2 text-sm font-semibold text-white dark:bg-white dark:text-stone-900">{dict.viewFleet}</span>
</Link>
))}
</div>
@@ -6,7 +6,7 @@ const copy = {
en: {
kicker: 'Features',
title: 'Operations, marketplace discovery, and branded booking.',
body: 'RentalDriveGo combines company-only operations with a public marketplace that sends renters to each companys own payment flow.',
body: 'RentalDriveGo combines company-only operations with a public marketplace that sends renters to each company's own payment flow.',
cards: [
['Private dashboard', 'Fleet, reservations, CRM, team permissions, billing, and reports stay isolated per company.'],
['Marketplace discovery', 'Published vehicles and public offers appear on `/explore` for cross-company browsing.'],
@@ -19,9 +19,9 @@ const copy = {
fr: {
kicker: 'Fonctionnalités',
title: 'Opérations, découverte marketplace et réservation de marque.',
body: 'RentalDriveGo combine des opérations privées pour lentreprise avec une marketplace publique qui redirige les clients vers le parcours de paiement propre à chaque société.',
body: "RentalDriveGo combine des opérations privées pour l'entreprise avec une marketplace publique qui redirige les clients vers le parcours de paiement propre à chaque société.",
cards: [
['Tableau de bord privé', 'Flotte, réservations, CRM, permissions déquipe, facturation et rapports restent isolés par entreprise.'],
['Tableau de bord privé', "Flotte, réservations, CRM, permissions d'équipe, facturation et rapports restent isolés par entreprise."],
['Découverte marketplace', 'Les véhicules publiés et les offres publiques apparaissent sur `/explore` pour une navigation multi-entreprises.'],
['Site de réservation de marque', 'Chaque entreprise dispose de son propre sous-domaine les clients finalisent la réservation et le paiement.'],
['Flexibilité de paiement', 'Utilisez AmanPay ou PayPal pour les abonnements et pour les paiements côté entreprise.'],
@@ -49,19 +49,19 @@ export default function FeaturesPage() {
const dict = copy[language]
return (
<main className="min-h-screen bg-stone-50">
<main className="min-h-screen bg-stone-50 dark:bg-stone-950">
<div className="shell space-y-12 py-16">
<div className="max-w-3xl">
<p className="text-sm font-semibold uppercase tracking-[0.24em] text-amber-700">{dict.kicker}</p>
<h1 className="mt-4 text-5xl font-black tracking-tight text-stone-900">{dict.title}</h1>
<p className="mt-5 text-lg leading-8 text-stone-600">{dict.body}</p>
<p className="text-sm font-semibold uppercase tracking-[0.24em] text-amber-700 dark:text-amber-400">{dict.kicker}</p>
<h1 className="mt-4 text-5xl font-black tracking-tight text-stone-900 dark:text-stone-100">{dict.title}</h1>
<p className="mt-5 text-lg leading-8 text-stone-600 dark:text-stone-400">{dict.body}</p>
</div>
<div className="grid gap-6 md:grid-cols-2 xl:grid-cols-3">
{dict.cards.map(([title, body]) => (
<article key={title} className="card p-8">
<h2 className="text-xl font-black text-stone-900">{title}</h2>
<p className="mt-4 text-sm leading-7 text-stone-600">{body}</p>
<h2 className="text-xl font-black text-stone-900 dark:text-stone-100">{title}</h2>
<p className="mt-4 text-sm leading-7 text-stone-600 dark:text-stone-400">{body}</p>
</article>
))}
</div>
@@ -92,7 +92,7 @@ const copy = {
perMonth: '/mois',
billedAs: 'Facturé à',
getStarted: 'Commencer',
footer: 'Toutes les formules incluent un essai gratuit de 14 jours. Aucune carte bancaire nest requise.',
footer: "Toutes les formules incluent un essai gratuit de 14 jours. Aucune carte bancaire n'est requise.",
},
ar: {
monthly: 'شهري',
@@ -127,27 +127,27 @@ export default function PricingClient() {
const [billing, setBilling] = useState<Billing>('monthly')
const dashboardUrl = resolveBrowserAppUrl(process.env.NEXT_PUBLIC_DASHBOARD_URL ?? 'http://localhost:3001/dashboard')
const savings = annualSavingsPct('STARTER', currency) // same % for all plans
const savings = annualSavingsPct('STARTER', currency)
return (
<div className="space-y-10">
{/* Toggles */}
<div className="flex flex-col items-center gap-6 sm:flex-row sm:justify-center">
{/* Billing toggle */}
<div className="flex items-center gap-1 rounded-full border border-stone-200 bg-white p-1 shadow-sm">
<div className="flex items-center gap-1 rounded-full border border-stone-200 bg-white p-1 shadow-sm dark:border-stone-700 dark:bg-stone-900">
{(['monthly', 'annual'] as Billing[]).map((b) => (
<button
key={b}
onClick={() => setBilling(b)}
className={`relative rounded-full px-5 py-2 text-sm font-semibold transition-colors ${
billing === b
? 'bg-stone-900 text-white'
: 'text-stone-600 hover:text-stone-900'
? 'bg-stone-900 text-white dark:bg-white dark:text-stone-900'
: 'text-stone-600 hover:text-stone-900 dark:text-stone-400 dark:hover:text-stone-100'
}`}
>
{b === 'monthly' ? dict.monthly : dict.annual}
{b === 'annual' && billing !== 'annual' && (
<span className="ml-2 rounded-full bg-amber-100 px-2 py-0.5 text-[10px] font-bold text-amber-700">
<span className="ml-2 rounded-full bg-amber-100 px-2 py-0.5 text-[10px] font-bold text-amber-700 dark:bg-amber-950/50 dark:text-amber-400">
-{savings}%
</span>
)}
@@ -156,15 +156,15 @@ export default function PricingClient() {
</div>
{/* Currency toggle */}
<div className="flex items-center gap-1 rounded-full border border-stone-200 bg-white p-1 shadow-sm">
<div className="flex items-center gap-1 rounded-full border border-stone-200 bg-white p-1 shadow-sm dark:border-stone-700 dark:bg-stone-900">
{(['MAD', 'USD', 'EUR'] as Currency[]).map((c) => (
<button
key={c}
onClick={() => setCurrency(c)}
className={`rounded-full px-4 py-2 text-sm font-semibold transition-colors ${
currency === c
? 'bg-amber-500 text-white'
: 'text-stone-600 hover:text-stone-900'
? 'bg-amber-500 text-white dark:bg-amber-400 dark:text-stone-900'
: 'text-stone-600 hover:text-stone-900 dark:text-stone-400 dark:hover:text-stone-100'
}`}
>
{c}
@@ -174,7 +174,7 @@ export default function PricingClient() {
</div>
{billing === 'annual' && (
<p className="text-center text-sm text-amber-700 font-medium">
<p className="text-center text-sm font-medium text-amber-700 dark:text-amber-400">
{dict.youSave} {savings}% {dict.withAnnual}
</p>
)}
@@ -193,34 +193,34 @@ export default function PricingClient() {
key={plan.key}
className={`card relative flex flex-col overflow-hidden ${
plan.highlight
? 'border-amber-400 ring-2 ring-amber-400 ring-offset-2'
? 'border-amber-400 ring-2 ring-amber-400 ring-offset-2 dark:ring-offset-stone-950'
: ''
}`}
>
{plan.highlight && (
<div className="bg-amber-500 py-1.5 text-center text-xs font-bold uppercase tracking-widest text-white">
<div className="bg-amber-500 dark:bg-amber-400 py-1.5 text-center text-xs font-bold uppercase tracking-widest text-white dark:text-stone-900">
{dict.mostPopular}
</div>
)}
<div className="flex flex-1 flex-col p-8">
<p className="text-xs font-bold uppercase tracking-[0.2em] text-amber-700">
<p className="text-xs font-bold uppercase tracking-[0.2em] text-amber-700 dark:text-amber-400">
{plan.name}
</p>
<p className="mt-1 text-sm text-stone-500">{plan.tagline}</p>
<p className="mt-1 text-sm text-stone-500 dark:text-stone-400">{plan.tagline}</p>
<div className="mt-6">
<div className="flex items-end gap-1">
<span className="text-4xl font-black text-stone-900">
<span className="text-4xl font-black text-stone-900 dark:text-stone-100">
{currency === 'MAD' ? displayPrice : `${sym}${displayPrice}`}
</span>
{currency === 'MAD' && (
<span className="mb-1 text-lg font-semibold text-stone-500">{sym}</span>
<span className="mb-1 text-lg font-semibold text-stone-500 dark:text-stone-400">{sym}</span>
)}
<span className="mb-1 text-sm text-stone-400">{dict.perMonth}</span>
<span className="mb-1 text-sm text-stone-400 dark:text-stone-500">{dict.perMonth}</span>
</div>
{billing === 'annual' && (
<p className="mt-1 text-xs text-stone-400">
<p className="mt-1 text-xs text-stone-400 dark:text-stone-500">
{dict.billedAs}{' '}
{currency === 'MAD'
? `${price.annual} ${sym}`
@@ -232,9 +232,9 @@ export default function PricingClient() {
<ul className="mt-8 flex-1 space-y-3">
{plan.features.map((f) => (
<li key={f} className="flex items-start gap-2.5 text-sm text-stone-700">
<li key={f} className="flex items-start gap-2.5 text-sm text-stone-700 dark:text-stone-300">
<svg
className="mt-0.5 h-4 w-4 shrink-0 text-amber-500"
className="mt-0.5 h-4 w-4 shrink-0 text-amber-500 dark:text-amber-400"
fill="none"
stroke="currentColor"
strokeWidth={2.5}
@@ -251,8 +251,8 @@ export default function PricingClient() {
href={`${dashboardUrl}/sign-up?plan=${plan.key}&billing=${billing.toUpperCase()}&currency=${currency}`}
className={`mt-10 block rounded-full px-6 py-3 text-center text-sm font-semibold transition-colors ${
plan.highlight
? 'bg-amber-500 text-white hover:bg-amber-600'
: 'bg-stone-900 text-white hover:bg-stone-700'
? 'bg-amber-500 text-white hover:bg-amber-600 dark:bg-amber-400 dark:text-stone-900 dark:hover:bg-amber-300'
: 'bg-stone-900 text-white hover:bg-stone-700 dark:bg-white dark:text-stone-900 dark:hover:bg-stone-100'
}`}
>
{dict.getStarted}
@@ -264,7 +264,7 @@ export default function PricingClient() {
</div>
{/* Footer note */}
<p className="text-center text-sm text-stone-400">{dict.footer}</p>
<p className="text-center text-sm text-stone-400 dark:text-stone-500">{dict.footer}</p>
</div>
)
}
@@ -23,8 +23,8 @@ const copy = {
faq: 'Questions fréquentes',
items: [
['Puis-je changer de formule plus tard ?', 'Oui. Vous pouvez passer à une formule supérieure ou inférieure à tout moment. Les changements prennent effet au cycle de facturation suivant.'],
['Quest-ce qui compte comme véhicule ?', 'Tout véhicule ajouté à votre tableau de bord de flotte, quil soit actif ou non.'],
['Y a-t-il des frais dinstallation ?', 'Aucun frais dinstallation. Vous payez uniquement labonnement mensuel ou annuel.'],
["Qu'est-ce qui compte comme véhicule ?", "Tout véhicule ajouté à votre tableau de bord de flotte, qu'il soit actif ou non."],
["Y a-t-il des frais d'installation ?", "Aucun frais d'installation. Vous payez uniquement l'abonnement mensuel ou annuel."],
['Comment fonctionne la visibilité marketplace ?', 'Toutes les formules payantes incluent une présence publique sur RentalDriveGo. Les clients découvrent votre flotte puis sont redirigés vers votre site de réservation.'],
],
},
@@ -47,24 +47,24 @@ export default function PricingPageContent() {
const dict = copy[language]
return (
<main className="min-h-screen bg-[radial-gradient(circle_at_top,#fef3c7,transparent_30%),linear-gradient(180deg,#fafaf9,white)]">
<main className="min-h-screen bg-stone-50 dark:bg-stone-950">
<div className="shell py-20">
<div className="mx-auto max-w-2xl text-center">
<p className="text-sm font-semibold uppercase tracking-[0.24em] text-amber-700">{dict.kicker}</p>
<h1 className="mt-4 text-5xl font-black tracking-tight text-stone-900">{dict.title}</h1>
<p className="mt-5 text-lg leading-8 text-stone-600">{dict.body}</p>
<p className="text-sm font-semibold uppercase tracking-[0.24em] text-amber-700 dark:text-amber-400">{dict.kicker}</p>
<h1 className="mt-4 text-5xl font-black tracking-tight text-stone-900 dark:text-stone-100">{dict.title}</h1>
<p className="mt-5 text-lg leading-8 text-stone-600 dark:text-stone-400">{dict.body}</p>
</div>
<div className="mt-16">
<PricingClient />
</div>
<div className="mx-auto mt-24 max-w-3xl divide-y divide-stone-200">
<h2 className="pb-8 text-2xl font-bold text-stone-900">{dict.faq}</h2>
<div className="mx-auto mt-24 max-w-3xl divide-y divide-stone-200 dark:divide-stone-800">
<h2 className="pb-8 text-2xl font-bold text-stone-900 dark:text-stone-100">{dict.faq}</h2>
{dict.items.map(([q, a]) => (
<div key={q} className="py-6">
<p className="font-semibold text-stone-900">{q}</p>
<p className="mt-2 text-sm leading-6 text-stone-600">{a}</p>
<p className="font-semibold text-stone-900 dark:text-stone-100">{q}</p>
<p className="mt-2 text-sm leading-6 text-stone-600 dark:text-stone-400">{a}</p>
</div>
))}
</div>
@@ -18,7 +18,7 @@ function StarRating({ value, onChange, label }: { value: number; onChange: (v: n
const [hovered, setHovered] = useState(0)
return (
<div>
<p className="text-sm font-medium text-stone-700 mb-1.5">{label}</p>
<p className="mb-1.5 text-sm font-medium text-stone-700 dark:text-stone-300">{label}</p>
<div className="flex gap-1">
{[1, 2, 3, 4, 5].map((n) => (
<button
@@ -34,7 +34,7 @@ function StarRating({ value, onChange, label }: { value: number; onChange: (v: n
className={`h-8 w-8 transition-colors ${
n <= (hovered || value)
? 'fill-amber-400 text-amber-400'
: 'fill-stone-200 text-stone-200'
: 'fill-stone-200 text-stone-200 dark:fill-stone-700 dark:text-stone-700'
}`}
/>
</button>
@@ -101,18 +101,18 @@ function ReviewPageContent() {
if (loadState === 'loading') {
return (
<main className="flex min-h-screen items-center justify-center bg-stone-50 p-6">
<p className="text-stone-500 text-sm">Loading</p>
<main className="flex min-h-screen items-center justify-center bg-stone-50 dark:bg-stone-950 p-6">
<p className="text-sm text-stone-500 dark:text-stone-400">Loading</p>
</main>
)
}
if (loadState === 'invalid') {
return (
<main className="flex min-h-screen items-center justify-center bg-stone-50 p-6">
<div className="card max-w-md w-full p-8 text-center space-y-3">
<p className="text-2xl font-black text-stone-900">Link not found</p>
<p className="text-stone-500 text-sm">This review link is invalid or has already been used.</p>
<main className="flex min-h-screen items-center justify-center bg-stone-50 dark:bg-stone-950 p-6">
<div className="card w-full max-w-md space-y-3 p-8 text-center">
<p className="text-2xl font-black text-stone-900 dark:text-stone-100">Link not found</p>
<p className="text-sm text-stone-500 dark:text-stone-400">This review link is invalid or has already been used.</p>
</div>
</main>
)
@@ -120,13 +120,13 @@ function ReviewPageContent() {
if (loadState === 'already_reviewed') {
return (
<main className="flex min-h-screen items-center justify-center bg-stone-50 p-6">
<div className="card max-w-md w-full p-8 text-center space-y-3">
<div className="mx-auto flex h-14 w-14 items-center justify-center rounded-full bg-emerald-100">
<svg className="h-7 w-7 text-emerald-600" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}><path strokeLinecap="round" strokeLinejoin="round" d="M5 13l4 4L19 7" /></svg>
<main className="flex min-h-screen items-center justify-center bg-stone-50 dark:bg-stone-950 p-6">
<div className="card w-full max-w-md space-y-3 p-8 text-center">
<div className="mx-auto flex h-14 w-14 items-center justify-center rounded-full bg-emerald-100 dark:bg-emerald-950/50">
<svg className="h-7 w-7 text-emerald-600 dark:text-emerald-400" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}><path strokeLinecap="round" strokeLinejoin="round" d="M5 13l4 4L19 7" /></svg>
</div>
<p className="text-xl font-bold text-stone-900">Already reviewed</p>
<p className="text-stone-500 text-sm">You have already submitted a review for this rental. Thank you!</p>
<p className="text-xl font-bold text-stone-900 dark:text-stone-100">Already reviewed</p>
<p className="text-sm text-stone-500 dark:text-stone-400">You have already submitted a review for this rental. Thank you!</p>
</div>
</main>
)
@@ -134,60 +134,60 @@ function ReviewPageContent() {
if (submitState === 'done') {
return (
<main className="flex min-h-screen items-center justify-center bg-stone-50 p-6">
<div className="card max-w-md w-full p-8 text-center space-y-4">
<div className="mx-auto flex h-16 w-16 items-center justify-center rounded-full bg-emerald-100">
<svg className="h-8 w-8 text-emerald-600" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}><path strokeLinecap="round" strokeLinejoin="round" d="M5 13l4 4L19 7" /></svg>
<main className="flex min-h-screen items-center justify-center bg-stone-50 dark:bg-stone-950 p-6">
<div className="card w-full max-w-md space-y-4 p-8 text-center">
<div className="mx-auto flex h-16 w-16 items-center justify-center rounded-full bg-emerald-100 dark:bg-emerald-950/50">
<svg className="h-8 w-8 text-emerald-600 dark:text-emerald-400" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}><path strokeLinecap="round" strokeLinejoin="round" d="M5 13l4 4L19 7" /></svg>
</div>
<h1 className="text-2xl font-black text-stone-900">Thank you!</h1>
<p className="text-stone-500">Your review has been submitted and will be visible on {info?.companyName}'s profile.</p>
<h1 className="text-2xl font-black text-stone-900 dark:text-stone-100">Thank you!</h1>
<p className="text-stone-500 dark:text-stone-400">Your review has been submitted and will be visible on {info?.companyName}'s profile.</p>
</div>
</main>
)
}
return (
<main className="flex min-h-screen items-center justify-center bg-stone-50 p-6">
<div className="card max-w-lg w-full overflow-hidden">
<main className="flex min-h-screen items-center justify-center bg-stone-50 dark:bg-stone-950 p-6">
<div className="card w-full max-w-lg overflow-hidden">
{/* Header */}
<div className="flex items-center gap-4 border-b border-stone-100 p-6">
<div className="flex items-center gap-4 border-b border-stone-200 dark:border-stone-800 p-6">
{info?.vehiclePhoto ? (
// eslint-disable-next-line @next/next/no-img-element
<img src={info.vehiclePhoto} alt={info.vehicle} className="h-16 w-24 rounded-xl object-cover flex-shrink-0" />
<img src={info.vehiclePhoto} alt={info.vehicle} className="h-16 w-24 flex-shrink-0 rounded-xl object-cover" />
) : (
<div className="h-16 w-24 rounded-xl bg-stone-100 flex-shrink-0" />
<div className="h-16 w-24 flex-shrink-0 rounded-xl bg-stone-100 dark:bg-stone-800" />
)}
<div className="min-w-0">
<p className="text-xs uppercase tracking-widest text-stone-400 truncate">{info?.companyName}</p>
<h1 className="mt-0.5 text-lg font-bold text-stone-900 truncate">{info?.vehicle}</h1>
<p className="mt-0.5 text-sm text-stone-500">How was your experience?</p>
<p className="truncate text-xs uppercase tracking-widest text-stone-400 dark:text-stone-500">{info?.companyName}</p>
<h1 className="mt-0.5 truncate text-lg font-bold text-stone-900 dark:text-stone-100">{info?.vehicle}</h1>
<p className="mt-0.5 text-sm text-stone-500 dark:text-stone-400">How was your experience?</p>
</div>
</div>
<form onSubmit={handleSubmit} className="p-6 space-y-6">
<form onSubmit={handleSubmit} className="space-y-6 p-6">
<StarRating value={overall} onChange={setOverall} label="Overall experience *" />
<StarRating value={vehicle} onChange={setVehicle} label="Vehicle condition (optional)" />
<StarRating value={service} onChange={setService} label="Customer service (optional)" />
<div>
<label className="block text-sm font-medium text-stone-700 mb-1.5">Comments (optional)</label>
<label className="mb-1.5 block text-sm font-medium text-stone-700 dark:text-stone-300">Comments (optional)</label>
<textarea
rows={4}
value={comment}
onChange={(e) => setComment(e.target.value)}
placeholder="Tell us about your rental experience…"
className="w-full rounded-xl border border-stone-200 px-4 py-3 text-sm focus:outline-none focus:ring-2 focus:ring-stone-900 resize-none"
className="w-full resize-none rounded-xl border border-stone-200 dark:border-stone-700 bg-white dark:bg-stone-900 px-4 py-3 text-sm text-stone-900 dark:text-stone-100 placeholder:text-stone-400 dark:placeholder:text-stone-500 focus:outline-none focus:ring-2 focus:ring-stone-900 dark:focus:ring-stone-400"
/>
</div>
{errorMsg && (
<p className="rounded-xl bg-rose-50 px-4 py-3 text-sm text-rose-700">{errorMsg}</p>
<p className="rounded-xl bg-rose-50 dark:bg-rose-950/40 px-4 py-3 text-sm text-rose-700 dark:text-rose-400">{errorMsg}</p>
)}
<button
type="submit"
disabled={submitState === 'submitting'}
className="w-full rounded-full bg-stone-900 py-3 text-sm font-semibold text-white transition-colors hover:bg-stone-700 disabled:opacity-60"
className="w-full rounded-full bg-stone-900 py-3 text-sm font-semibold text-white transition-colors hover:bg-stone-700 disabled:opacity-60 dark:bg-white dark:text-stone-900 dark:hover:bg-stone-100"
>
{submitState === 'submitting' ? 'Submitting' : 'Submit review'}
</button>
@@ -201,8 +201,8 @@ export default function ReviewPage() {
return (
<Suspense
fallback={
<main className="flex min-h-screen items-center justify-center bg-stone-50 p-6">
<p className="text-stone-500 text-sm">Loading</p>
<main className="flex min-h-screen items-center justify-center bg-stone-50 dark:bg-stone-950 p-6">
<p className="text-sm text-stone-500 dark:text-stone-400">Loading</p>
</main>
}
>
-119
View File
@@ -14,122 +14,3 @@ body {
@apply rounded-2xl border border-stone-200 bg-white shadow-sm transition-colors dark:border-stone-800 dark:bg-stone-900;
}
body[data-theme='medium'] {
background-color: rgb(41 37 36);
color: rgb(231 229 228);
}
body[data-theme='medium'] .dark\:bg-stone-950 {
background-color: rgb(41 37 36);
}
body[data-theme='medium'] .dark\:bg-stone-950\/80 {
background-color: rgb(41 37 36 / 0.8);
}
body[data-theme='medium'] .dark\:bg-stone-950\/95 {
background-color: rgb(41 37 36 / 0.95);
}
body[data-theme='medium'] .dark\:bg-stone-950\/50 {
background-color: rgb(68 64 60 / 0.5);
}
body[data-theme='medium'] .dark\:bg-stone-950\/40 {
background-color: rgb(68 64 60 / 0.4);
}
body[data-theme='medium'] .dark\:bg-stone-950\/30 {
background-color: rgb(68 64 60 / 0.3);
}
body[data-theme='medium'] .dark\:bg-stone-900,
body[data-theme='medium'] .dark\:bg-stone-900\/80,
body[data-theme='medium'] .dark\:bg-stone-900\/70,
body[data-theme='medium'] .dark\:bg-stone-900\/60 {
background-color: rgb(68 64 60 / 0.85);
}
body[data-theme='medium'] .dark\:bg-stone-800 {
background-color: rgb(87 83 78);
}
body[data-theme='medium'] .dark\:bg-amber-400 {
background-color: rgb(251 191 36);
}
body[data-theme='medium'] .dark\:border-stone-800 {
border-color: rgb(120 113 108);
}
body[data-theme='medium'] .dark\:border-stone-700,
body[data-theme='medium'] .dark\:border-white\/5 {
border-color: rgb(87 83 78);
}
body[data-theme='medium'] .dark\:border-amber-300 {
border-color: rgb(252 211 77);
}
body[data-theme='medium'] .dark\:text-stone-50,
body[data-theme='medium'] .dark\:text-stone-100,
body[data-theme='medium'] .dark\:text-white {
color: rgb(250 250 249);
}
body[data-theme='medium'] .dark\:text-stone-200 {
color: rgb(231 229 228);
}
body[data-theme='medium'] .dark\:text-stone-300,
body[data-theme='medium'] .dark\:text-stone-400 {
color: rgb(214 211 209);
}
body[data-theme='medium'] .dark\:text-stone-500,
body[data-theme='medium'] .dark\:text-stone-700,
body[data-theme='medium'] .dark\:text-stone-800 {
color: rgb(168 162 158);
}
body[data-theme='medium'] .dark\:text-stone-950 {
color: rgb(28 25 23);
}
body[data-theme='medium'] .dark\:text-slate-100 {
color: rgb(250 250 249);
}
body[data-theme='medium'] .dark\:text-stone-600 {
color: rgb(168 162 158);
}
body[data-theme='medium'] .dark\:text-amber-300 {
color: rgb(252 211 77);
}
body[data-theme='medium'] .dark\:text-sky-400 {
color: rgb(125 211 252);
}
body[data-theme='medium'] .dark\:hover\:bg-stone-800:hover,
body[data-theme='medium'] .dark\:hover\:bg-stone-900:hover {
background-color: rgb(28 25 23);
}
body[data-theme='medium'] .dark\:hover\:bg-amber-300:hover {
background-color: rgb(252 211 77);
}
body[data-theme='medium'] .dark\:hover\:text-stone-100:hover,
body[data-theme='medium'] .dark\:hover\:text-white:hover {
color: rgb(250 250 249);
}
body[data-theme='medium'] .dark\:hover\:border-stone-200:hover {
border-color: rgb(214 211 209);
}
body[data-theme='medium'] .dark\:bg-\[\#111111\] {
background-color: rgb(28 25 23);
}
@@ -12,7 +12,7 @@ const pageMeta = {
fr: {
kicker: 'Informations du pied de page',
cta: 'Besoin de plus de détails ?',
support: 'Contactez léquipe RentalDriveGo via nos canaux officiels pour toute demande commerciale, support ou partenariat.',
support: "Contactez l'équipe RentalDriveGo via nos canaux officiels pour toute demande commerciale, support ou partenariat.",
},
ar: {
kicker: 'معلومات التذييل',
@@ -28,30 +28,30 @@ export default function FooterContentPage({ slug }: { slug: FooterPageSlug }) {
const isArabic = language === 'ar'
return (
<main className="min-h-screen bg-stone-50">
<main className="min-h-screen bg-stone-50 dark:bg-stone-950">
<div className="mx-auto max-w-4xl px-4 py-16 sm:px-6 lg:px-8">
<section className="overflow-hidden rounded-[2rem] border border-stone-200 bg-white shadow-[0_30px_80px_rgba(12,18,28,0.08)]">
<div className="bg-[linear-gradient(135deg,#f8fafc,white_45%,#fef3c7)] px-6 py-10 sm:px-10 sm:py-14">
<p className="text-sm font-semibold uppercase tracking-[0.24em] text-amber-700">
<section className="overflow-hidden rounded-[2rem] border border-stone-200 dark:border-stone-800 bg-white dark:bg-stone-900 shadow-[0_30px_80px_rgba(12,18,28,0.08)]">
<div className="bg-stone-50 dark:bg-stone-900 px-6 py-10 sm:px-10 sm:py-14 border-b border-stone-100 dark:border-stone-800">
<p className="text-sm font-semibold uppercase tracking-[0.24em] text-amber-700 dark:text-amber-400">
{meta.kicker}
</p>
<h1 className="mt-4 text-4xl font-black tracking-tight text-stone-900 sm:text-5xl">
<h1 className="mt-4 text-4xl font-black tracking-tight text-stone-900 dark:text-stone-100 sm:text-5xl">
{content.title}
</h1>
</div>
<div className={`space-y-6 px-6 py-10 sm:px-10 sm:py-12 ${isArabic ? 'text-right' : 'text-left'}`}>
{content.paragraphs.map((paragraph) => (
<p key={paragraph} className="text-lg leading-8 text-stone-700">
<p key={paragraph} className="text-lg leading-8 text-stone-700 dark:text-stone-300">
{paragraph}
</p>
))}
<div className="rounded-3xl border border-amber-200 bg-amber-50 px-6 py-5 text-stone-700">
<p className="text-sm font-semibold uppercase tracking-[0.18em] text-amber-800">
<div className="rounded-3xl border border-amber-200 dark:border-amber-800 bg-amber-50 dark:bg-amber-950/30 px-6 py-5">
<p className="text-sm font-semibold uppercase tracking-[0.18em] text-amber-800 dark:text-amber-400">
{meta.cta}
</p>
<p className="mt-2 text-base leading-7">{meta.support}</p>
<p className="mt-2 text-base leading-7 text-stone-700 dark:text-stone-300">{meta.support}</p>
</div>
</div>
</section>