fix: admin pages CSS variables - consistent design system colors

This commit is contained in:
2026-06-02 05:02:19 +00:00
parent 454515965e
commit d7fe1ea3fd
15 changed files with 215 additions and 99 deletions
@@ -13,7 +13,7 @@ export default async function AbandonedCartsPage() {
const brandName = settingsResult?.success ? (settingsResult.settings?.brand_name ?? "Farm") : "Farm"; const brandName = settingsResult?.success ? (settingsResult.settings?.brand_name ?? "Farm") : "Farm";
return ( return (
<main className="min-h-screen bg-stone-100 px-6 py-10"> <main className="min-h-screen px-6 py-10" style={{ backgroundColor: "var(--admin-bg)" }}>
<div className="mx-auto max-w-5xl space-y-6"> <div className="mx-auto max-w-5xl space-y-6">
{/* Header */} {/* Header */}
<div> <div>
@@ -12,7 +12,7 @@ export default async function WelcomeSequencePage() {
const brandName = settingsResult?.success ? (settingsResult.settings?.brand_name ?? "Farm") : "Farm"; const brandName = settingsResult?.success ? (settingsResult.settings?.brand_name ?? "Farm") : "Farm";
return ( return (
<main className="min-h-screen bg-stone-100 px-6 py-10"> <main className="min-h-screen px-6 py-10" style={{ backgroundColor: "var(--admin-bg)" }}>
<div className="mx-auto max-w-5xl space-y-6"> <div className="mx-auto max-w-5xl space-y-6">
{/* Header */} {/* Header */}
<div> <div>
+79 -30
View File
@@ -71,44 +71,62 @@ export default function AdminMeClient({ currentUser }: ProfilePageProps) {
} }
return ( return (
<main className="min-h-screen bg-stone-50 px-6 py-12"> <main className="min-h-screen px-6 py-12" style={{ backgroundColor: "var(--admin-bg)" }}>
<div className="mx-auto max-w-2xl"> <div className="mx-auto max-w-2xl">
<Link href="/admin" className="text-sm text-stone-500 hover:text-stone-700"> <Link
href="/admin"
className="text-sm transition-colors"
style={{ color: "var(--admin-text-muted)" }}
>
Back to dashboard Back to dashboard
</Link> </Link>
<h1 className="mt-6 text-3xl font-bold text-stone-950">My Profile</h1> <h1 className="mt-6 text-3xl font-bold" style={{ color: "var(--admin-text-primary)" }}>My Profile</h1>
<p className="mt-1 text-sm text-stone-500"> <p className="mt-1 text-sm" style={{ color: "var(--admin-text-muted)" }}>
Manage your profile information and preferences. Manage your profile information and preferences.
</p> </p>
{error && ( {error && (
<div className="mt-4 rounded-xl bg-red-50 p-4 text-sm text-red-700">{error}</div> <div className="mt-4 rounded-xl p-4 text-sm" style={{
backgroundColor: "rgba(239, 68, 68, 0.1)",
color: "rgb(239, 68, 68)"
}}>{error}</div>
)} )}
{/* Profile card */} {/* Profile card */}
<div className="mt-6 rounded-2xl bg-white p-6 shadow-black/20 ring-1 ring-stone-200"> <div className="mt-6 rounded-2xl p-6 shadow-lg border" style={{
backgroundColor: "white",
boxShadow: "0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -2px rgba(0, 0, 0, 0.1)",
borderColor: "var(--admin-border)"
}}>
<div className="flex items-start justify-between"> <div className="flex items-start justify-between">
<div> <div>
<h2 className="text-lg font-semibold text-stone-950"> <h2 className="text-lg font-semibold" style={{ color: "var(--admin-text-primary)" }}>
{currentUser.display_name || currentUser.email} {currentUser.display_name || currentUser.email}
</h2> </h2>
{currentUser.display_name && ( {currentUser.display_name && (
<p className="text-sm text-stone-500">{currentUser.email}</p> <p className="text-sm" style={{ color: "var(--admin-text-muted)" }}>{currentUser.email}</p>
)} )}
<div className="mt-2 flex items-center gap-2"> <div className="mt-2 flex items-center gap-2">
<span className="rounded-full bg-stone-50 px-2.5 py-0.5 text-xs font-semibold capitalize text-stone-500"> <span className="rounded-full px-2.5 py-0.5 text-xs font-semibold capitalize" style={{
backgroundColor: "var(--admin-bg-subtle)",
color: "var(--admin-text-muted)"
}}>
{currentUser.role.replace("_", " ")} {currentUser.role.replace("_", " ")}
</span> </span>
{currentUser.brand_name && ( {currentUser.brand_name && (
<span className="text-xs text-stone-500">{currentUser.brand_name}</span> <span className="text-xs" style={{ color: "var(--admin-text-muted)" }}>{currentUser.brand_name}</span>
)} )}
</div> </div>
</div> </div>
{!editing && ( {!editing && (
<button <button
onClick={() => setEditing(true)} onClick={() => setEditing(true)}
className="rounded-lg border border-stone-200 px-4 py-2 text-sm font-medium text-stone-500 hover:bg-stone-100" className="rounded-lg border px-4 py-2 text-sm font-medium transition-colors"
style={{
borderColor: "var(--admin-border)",
color: "var(--admin-text-muted)"
}}
> >
Edit Profile Edit Profile
</button> </button>
@@ -120,12 +138,12 @@ export default function AdminMeClient({ currentUser }: ProfilePageProps) {
<div className="mt-6 space-y-4"> <div className="mt-6 space-y-4">
<div className="grid grid-cols-2 gap-4"> <div className="grid grid-cols-2 gap-4">
<div> <div>
<p className="text-xs font-medium uppercase tracking-wide text-stone-500">Phone</p> <p className="text-xs font-medium uppercase tracking-wide" style={{ color: "var(--admin-text-muted)" }}>Phone</p>
<p className="mt-1 text-sm text-stone-700">{currentUser.phone_number ?? "—"}</p> <p className="mt-1 text-sm" style={{ color: "var(--admin-text-primary)" }}>{currentUser.phone_number ?? "—"}</p>
</div> </div>
<div> <div>
<p className="text-xs font-medium uppercase tracking-wide text-stone-500">Email</p> <p className="text-xs font-medium uppercase tracking-wide" style={{ color: "var(--admin-text-muted)" }}>Email</p>
<p className="mt-1 text-sm text-stone-700">{currentUser.email}</p> <p className="mt-1 text-sm" style={{ color: "var(--admin-text-primary)" }}>{currentUser.email}</p>
</div> </div>
</div> </div>
</div> </div>
@@ -135,22 +153,32 @@ export default function AdminMeClient({ currentUser }: ProfilePageProps) {
{editing && ( {editing && (
<form onSubmit={handleSaveProfile} className="mt-6 space-y-4"> <form onSubmit={handleSaveProfile} className="mt-6 space-y-4">
<div> <div>
<label className="block text-sm font-medium text-stone-700">Display Name</label> <label className="block text-sm font-medium" style={{ color: "var(--admin-text-primary)" }}>Display Name</label>
<input <input
type="text" type="text"
value={displayName} value={displayName}
onChange={(e) => setDisplayName(e.target.value)} onChange={(e) => setDisplayName(e.target.value)}
className="mt-1 w-full rounded-lg border border-stone-300 px-3 py-2 text-sm focus:border-emerald-500 focus:outline-none focus:ring-1 focus:ring-emerald-500" className="mt-1 w-full rounded-lg border px-3 py-2 text-sm focus:outline-none focus:ring-1"
style={{
borderColor: "var(--admin-border)",
color: "var(--admin-text-primary)",
backgroundColor: "white"
}}
placeholder="Your name" placeholder="Your name"
/> />
</div> </div>
<div> <div>
<label className="block text-sm font-medium text-stone-700">Phone Number</label> <label className="block text-sm font-medium" style={{ color: "var(--admin-text-primary)" }}>Phone Number</label>
<input <input
type="tel" type="tel"
value={phoneNumber} value={phoneNumber}
onChange={(e) => setPhoneNumber(e.target.value)} onChange={(e) => setPhoneNumber(e.target.value)}
className="mt-1 w-full rounded-lg border border-stone-300 px-3 py-2 text-sm focus:border-emerald-500 focus:outline-none focus:ring-1 focus:ring-emerald-500" className="mt-1 w-full rounded-lg border px-3 py-2 text-sm focus:outline-none focus:ring-1"
style={{
borderColor: "var(--admin-border)",
color: "var(--admin-text-primary)",
backgroundColor: "white"
}}
placeholder="+1 (555) 000-0000" placeholder="+1 (555) 000-0000"
/> />
</div> </div>
@@ -158,14 +186,19 @@ export default function AdminMeClient({ currentUser }: ProfilePageProps) {
<button <button
type="button" type="button"
onClick={() => { setEditing(false); setError(null); }} onClick={() => { setEditing(false); setError(null); }}
className="rounded-lg border border-stone-200 px-4 py-2 text-sm font-medium text-stone-500 hover:bg-stone-100" className="rounded-lg border px-4 py-2 text-sm font-medium transition-colors"
style={{
borderColor: "var(--admin-border)",
color: "var(--admin-text-muted)"
}}
> >
Cancel Cancel
</button> </button>
<button <button
type="submit" type="submit"
disabled={saving} disabled={saving}
className="rounded-lg bg-stone-800 px-4 py-2 text-sm font-medium text-white hover:bg-stone-700 disabled:opacity-50" className="rounded-lg px-4 py-2 text-sm font-medium text-white transition-colors disabled:opacity-50"
style={{ backgroundColor: "var(--admin-accent)" }}
> >
{saving ? "Saving…" : "Save Changes"} {saving ? "Saving…" : "Save Changes"}
</button> </button>
@@ -175,35 +208,51 @@ export default function AdminMeClient({ currentUser }: ProfilePageProps) {
</div> </div>
{/* Email change section */} {/* Email change section */}
<div className="mt-6 rounded-2xl bg-white p-6 shadow-black/20 ring-1 ring-stone-200"> <div className="mt-6 rounded-2xl p-6 shadow-lg border" style={{
<h3 className="text-base font-semibold text-stone-950">Email Address</h3> backgroundColor: "white",
<p className="mt-1 text-sm text-stone-500"> boxShadow: "0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -2px rgba(0, 0, 0, 0.1)",
Current: <span className="font-medium text-stone-700">{currentUser.email}</span> borderColor: "var(--admin-border)"
}}>
<h3 className="text-base font-semibold" style={{ color: "var(--admin-text-primary)" }}>Email Address</h3>
<p className="mt-1 text-sm" style={{ color: "var(--admin-text-muted)" }}>
Current: <span className="font-medium" style={{ color: "var(--admin-text-primary)" }}>{currentUser.email}</span>
</p> </p>
{emailChangeSent ? ( {emailChangeSent ? (
<div className="mt-4 rounded-lg bg-green-50 p-4 text-sm text-green-700"> <div className="mt-4 rounded-lg p-4 text-sm" style={{
backgroundColor: "rgba(16, 185, 129, 0.1)",
color: "var(--admin-accent)"
}}>
A confirmation email has been sent to <strong>{newEmail}</strong>. Click the link in the email to confirm the change. A confirmation email has been sent to <strong>{newEmail}</strong>. Click the link in the email to confirm the change.
</div> </div>
) : ( ) : (
<form onSubmit={handleEmailChange} className="mt-4 space-y-3"> <form onSubmit={handleEmailChange} className="mt-4 space-y-3">
<div> <div>
<label className="block text-sm font-medium text-stone-700">New Email Address</label> <label className="block text-sm font-medium" style={{ color: "var(--admin-text-primary)" }}>New Email Address</label>
<input <input
type="email" type="email"
value={newEmail} value={newEmail}
onChange={(e) => setNewEmail(e.target.value)} onChange={(e) => setNewEmail(e.target.value)}
className="mt-1 w-full rounded-lg border border-stone-300 px-3 py-2 text-sm focus:border-emerald-500 focus:outline-none focus:ring-1 focus:ring-emerald-500" className="mt-1 w-full rounded-lg border px-3 py-2 text-sm focus:outline-none focus:ring-1"
style={{
borderColor: "var(--admin-border)",
color: "var(--admin-text-primary)",
backgroundColor: "white"
}}
placeholder="new@example.com" placeholder="new@example.com"
/> />
</div> </div>
{emailError && ( {emailError && (
<div className="rounded-lg bg-red-50 p-3 text-sm text-red-700">{emailError}</div> <div className="rounded-lg p-3 text-sm" style={{
backgroundColor: "rgba(239, 68, 68, 0.1)",
color: "rgb(239, 68, 68)"
}}>{emailError}</div>
)} )}
<button <button
type="submit" type="submit"
disabled={changingEmail || !newEmail || !newEmail.includes("@")} disabled={changingEmail || !newEmail || !newEmail.includes("@")}
className="rounded-lg bg-stone-800 px-4 py-2 text-sm font-medium text-white hover:bg-stone-700 disabled:opacity-50" className="rounded-lg px-4 py-2 text-sm font-medium text-white transition-colors disabled:opacity-50"
style={{ backgroundColor: "var(--admin-accent)" }}
> >
{changingEmail ? "Sending…" : "Change Email"} {changingEmail ? "Sending…" : "Change Email"}
</button> </button>
+2 -2
View File
@@ -30,7 +30,7 @@ export default async function ProductDetailPage({ params }: ProductDetailPagePro
if (error || !product) { if (error || !product) {
return ( return (
<main className="min-h-screen bg-stone-100 px-6 py-12"> <main className="min-h-screen px-6 py-12" style={{ backgroundColor: "var(--admin-bg)" }}>
<div className="mx-auto max-w-4xl"> <div className="mx-auto max-w-4xl">
<h1 className="text-3xl font-bold text-red-600">Product not found</h1> <h1 className="text-3xl font-bold text-red-600">Product not found</h1>
<pre className="mt-4 rounded-xl bg-white p-4 text-sm text-stone-600"> <pre className="mt-4 rounded-xl bg-white p-4 text-sm text-stone-600">
@@ -48,7 +48,7 @@ export default async function ProductDetailPage({ params }: ProductDetailPagePro
} }
return ( return (
<main className="min-h-screen bg-stone-100 px-6 py-12"> <main className="min-h-screen px-6 py-12" style={{ backgroundColor: "var(--admin-bg)" }}>
<div className="mx-auto max-w-4xl"> <div className="mx-auto max-w-4xl">
<a <a
href="/admin/products" href="/admin/products"
+1 -1
View File
@@ -81,7 +81,7 @@ Citrus Gift Box,Seasonal citrus assortment,34.99,Shipping,TRUE,
} }
return ( return (
<main className="min-h-screen bg-stone-100 px-6 py-10"> <main className="min-h-screen px-6 py-10" style={{ backgroundColor: "var(--admin-bg)" }}>
<div className="mx-auto max-w-4xl"> <div className="mx-auto max-w-4xl">
<div className="mb-6 flex items-center justify-between"> <div className="mb-6 flex items-center justify-between">
<div> <div>
+1 -1
View File
@@ -6,7 +6,7 @@ export default async function NewProductPage() {
const adminUser = await getAdminUser(); const adminUser = await getAdminUser();
if (!adminUser?.can_manage_products) redirect("/admin/pickup"); if (!adminUser?.can_manage_products) redirect("/admin/pickup");
return ( return (
<main className="min-h-screen bg-stone-100 px-6 py-12"> <main className="min-h-screen px-6 py-12" style={{ backgroundColor: "var(--admin-bg)" }}>
<div className="mx-auto max-w-4xl"> <div className="mx-auto max-w-4xl">
<div className="mb-8"> <div className="mb-8">
<a <a
+1 -1
View File
@@ -22,7 +22,7 @@ export default async function ReportsPage() {
: adminUser.brand_id ?? null; : adminUser.brand_id ?? null;
return ( return (
<main className="min-h-screen bg-stone-100 px-6 py-10"> <main className="min-h-screen px-6 py-10" style={{ backgroundColor: "var(--admin-bg)" }}>
<div className="mx-auto max-w-7xl"> <div className="mx-auto max-w-7xl">
<PageHeader <PageHeader
title="Reports" title="Reports"
+2 -2
View File
@@ -41,7 +41,7 @@ export default async function LotDetailPage({ params }: { params: Promise<{ id:
if (!detailResult.success || !detailResult.lot) { if (!detailResult.success || !detailResult.lot) {
return ( return (
<div className="min-h-screen bg-stone-50 px-6 py-10"> <div className="min-h-screen px-6 py-10" style={{ backgroundColor: "var(--admin-bg)" }}>
<div className="mx-auto max-w-2xl"> <div className="mx-auto max-w-2xl">
<div className="rounded-xl border border-red-200 bg-white p-6 text-center"> <div className="rounded-xl border border-red-200 bg-white p-6 text-center">
<p className="text-red-600">Lot not found</p> <p className="text-red-600">Lot not found</p>
@@ -53,7 +53,7 @@ export default async function LotDetailPage({ params }: { params: Promise<{ id:
} }
return ( return (
<div className="min-h-screen bg-stone-50 px-6 py-10"> <div className="min-h-screen px-6 py-10" style={{ backgroundColor: "var(--admin-bg)" }}>
<div className="mx-auto max-w-3xl"> <div className="mx-auto max-w-3xl">
<div className="mb-6"> <div className="mb-6">
<a href="/admin/route-trace/lots" className="text-sm text-stone-500 hover:text-stone-700"> Back to Lots</a> <a href="/admin/route-trace/lots" className="text-sm text-stone-500 hover:text-stone-700"> Back to Lots</a>
+1 -1
View File
@@ -25,7 +25,7 @@ export default async function NewLotPage() {
if (!enabled) redirect("/admin/settings/apps?reason=route_trace"); if (!enabled) redirect("/admin/settings/apps?reason=route_trace");
return ( return (
<div className="min-h-screen bg-stone-50 px-6 py-10"> <div className="min-h-screen px-6 py-10" style={{ backgroundColor: "var(--admin-bg)" }}>
<div className="mx-auto max-w-2xl"> <div className="mx-auto max-w-2xl">
<div className="mb-6"> <div className="mb-6">
<a href="/admin/route-trace/lots" className="text-sm text-stone-500 hover:text-stone-700"> Back to Lots</a> <a href="/admin/route-trace/lots" className="text-sm text-stone-500 hover:text-stone-700"> Back to Lots</a>
+1 -1
View File
@@ -77,7 +77,7 @@ John Doe,john@example.com,555-5678,{STOP_ID},{PRODUCT_ID},1,shipping
} }
return ( return (
<main className="min-h-screen bg-stone-100 px-6 py-10"> <main className="min-h-screen px-6 py-10" style={{ backgroundColor: "var(--admin-bg)" }}>
<div className="mx-auto max-w-4xl"> <div className="mx-auto max-w-4xl">
<div className="mb-6"> <div className="mb-6">
<Link href="/admin/orders" className="text-sm text-stone-500 hover:text-stone-800"> <Link href="/admin/orders" className="text-sm text-stone-500 hover:text-stone-800">
+9 -9
View File
@@ -26,30 +26,30 @@ export default async function ShippingSettingsPage() {
const settings = result.success ? result.settings : null; const settings = result.success ? result.settings : null;
return ( return (
<main className="min-h-screen bg-stone-100 px-6 py-10"> <main className="min-h-screen px-6 py-10" style={{ backgroundColor: "var(--admin-bg)" }}>
<div className="mx-auto max-w-2xl"> <div className="mx-auto max-w-2xl">
{/* Breadcrumb */} {/* Breadcrumb */}
<nav className="flex items-center gap-2 text-xs text-stone-500 mb-6"> <nav className="flex items-center gap-2 text-xs mb-6" style={{ color: "var(--admin-text-muted)" }}>
<a href="/admin" className="hover:text-stone-800 transition-colors">Admin</a> <a href="/admin" className="hover:text-[var(--admin-text-primary)] transition-colors">Admin</a>
<span>/</span> <span>/</span>
<span className="text-stone-800">Shipping</span> <span style={{ color: "var(--admin-text-primary)" }}>Shipping</span>
</nav> </nav>
<div className="mb-8"> <div className="mb-8">
<div className="flex items-center gap-3 mb-2"> <div className="flex items-center gap-3 mb-2">
<div className="flex h-10 w-10 items-center justify-center rounded-xl bg-stone-200 border border-stone-300"> <div className="flex h-10 w-10 items-center justify-center rounded-xl" style={{ backgroundColor: "var(--admin-accent)", color: "white" }}>
<svg className="h-5 w-5 text-stone-600" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}> <svg className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
<path strokeLinecap="round" strokeLinejoin="round" d="M8.25 18.75a1.5 1.5 0 01-3 0m3 0a1.5 1.5 0 00-3 0m3 0h6m-9 0H3.375a1.125 1.125 0 01-1.125-1.125V14.25m17.25 4.5a1.5 1.5 0 01-3 0m3 0a1.5 1.5 0 00-3 0m3 0h1.125c.621 0 1.129-.504 1.09-1.124a17.902 17.902 0 00-3.213-9.193 2.056 2.056 0 00-1.58-.86H14.25m-2.25 0h2.25m0 0v-.375c0-.621-.504-1.125-1.125-1.125H15m-1.5-3l1.5 0l.75 0v-.375c0-.621-.504-1.125-1.125-1.125H15m0 0v-.375c0-.621-.504-1.125-1.125-1.125H12" /> <path strokeLinecap="round" strokeLinejoin="round" d="M8.25 18.75a1.5 1.5 0 01-3 0m3 0a1.5 1.5 0 00-3 0m3 0h6m-9 0H3.375a1.125 1.125 0 01-1.125-1.125V14.25m17.25 4.5a1.5 1.5 0 01-3 0m3 0a1.5 1.5 0 00-3 0m3 0h1.125c.621 0 1.129-.504 1.09-1.124a17.902 17.902 0 00-3.213-9.193 2.056 2.056 0 00-1.58-.86H14.25m-2.25 0h2.25m0 0v-.375c0-.621-.504-1.125-1.125-1.125H15m-1.5-3l1.5 0l.75 0v-.375c0-.621-.504-1.125-1.125-1.125H15m0 0v-.375c0-.621-.504-1.125-1.125-1.125H12" />
</svg> </svg>
</div> </div>
<h1 className="text-3xl font-bold text-stone-950">Shipping Settings</h1> <h1 className="text-3xl font-bold" style={{ color: "var(--admin-text-primary)" }}>Shipping Settings</h1>
</div> </div>
<p className="mt-2 text-stone-500"> <p className="mt-2 text-sm" style={{ color: "var(--admin-text-muted)" }}>
Configure FedEx integration for shipping fresh produce sweet corn, onions, and more. Configure FedEx integration for shipping fresh produce sweet corn, onions, and more.
</p> </p>
</div> </div>
<div className="card p-6 shadow-xl"> <div className="rounded-2xl border p-6 shadow-lg" style={{ backgroundColor: "white", borderColor: "var(--admin-border)" }}>
<ShippingSettingsForm <ShippingSettingsForm
settings={settings} settings={settings}
brandId={effectiveBrandId} brandId={effectiveBrandId}
+2 -2
View File
@@ -38,7 +38,7 @@ export default async function StopDetailPage({ params }: StopDetailPageProps) {
if (error || !stop) { if (error || !stop) {
return ( return (
<main className="min-h-screen bg-stone-100 px-6 py-12"> <main className="min-h-screen px-6 py-12" style={{ backgroundColor: "var(--admin-bg)" }}>
<div className="mx-auto max-w-4xl"> <div className="mx-auto max-w-4xl">
<h1 className="text-3xl font-bold text-red-600">Stop not found</h1> <h1 className="text-3xl font-bold text-red-600">Stop not found</h1>
<pre className="mt-4 rounded-xl bg-white border border-stone-200 p-4 text-sm text-stone-600"> <pre className="mt-4 rounded-xl bg-white border border-stone-200 p-4 text-sm text-stone-600">
@@ -72,7 +72,7 @@ export default async function StopDetailPage({ params }: StopDetailPageProps) {
.filter(Boolean); .filter(Boolean);
return ( return (
<main className="min-h-screen bg-stone-100 px-6 py-12"> <main className="min-h-screen px-6 py-12" style={{ backgroundColor: "var(--admin-bg)" }}>
<div className="mx-auto max-w-4xl"> <div className="mx-auto max-w-4xl">
<a <a
href="/admin/stops" href="/admin/stops"
+1 -1
View File
@@ -51,7 +51,7 @@ export default async function NewStopPage({
]); ]);
return ( return (
<main className="min-h-screen bg-stone-100 px-6 py-12"> <main className="min-h-screen px-6 py-12" style={{ backgroundColor: "var(--admin-bg)" }}>
<div className="mx-auto max-w-4xl"> <div className="mx-auto max-w-4xl">
<div className="mb-8"> <div className="mb-8">
<a <a
+14 -6
View File
@@ -29,12 +29,20 @@ export default async function TaxesPage({ params }: Props) {
resolvedBrandId = firstBrand.id; resolvedBrandId = firstBrand.id;
} else { } else {
return ( return (
<main className="min-h-screen bg-stone-100 px-6 py-12"> <main className="min-h-screen px-6 py-12" style={{ backgroundColor: "var(--admin-bg)" }}>
<div className="mx-auto max-w-6xl"> <div className="mx-auto max-w-6xl">
<div className="card p-8 text-center"> <div className="rounded-2xl p-8 text-center border" style={{
<h1 className="text-2xl font-bold text-stone-950">No Brands Found</h1> backgroundColor: "white",
<p className="mt-2 text-stone-500">Create a brand in the database first.</p> borderColor: "var(--admin-border)"
<a href="/admin" className="mt-4 inline-block rounded-xl bg-stone-200 px-6 py-3 text-sm font-medium text-stone-700 border border-stone-300 hover:bg-stone-300 transition-colors"> }}>
<h1 className="text-2xl font-bold" style={{ color: "var(--admin-text-primary)" }}>No Brands Found</h1>
<p className="mt-2" style={{ color: "var(--admin-text-muted)" }}>Create a brand in the database first.</p>
<a href="/admin" className="mt-4 inline-block rounded-xl px-6 py-3 text-sm font-medium border transition-colors"
style={{
backgroundColor: "var(--admin-bg-subtle)",
borderColor: "var(--admin-border)",
color: "var(--admin-text-primary)"
}}>
Back to Admin Back to Admin
</a> </a>
</div> </div>
@@ -54,7 +62,7 @@ export default async function TaxesPage({ params }: Props) {
const allBrands = (brands ?? []) as Array<{ id: string; name: string }>; const allBrands = (brands ?? []) as Array<{ id: string; name: string }>;
return ( return (
<main className="min-h-screen bg-stone-100"> <main className="min-h-screen" style={{ backgroundColor: "var(--admin-bg)" }}>
<div className="mx-auto max-w-6xl px-6 py-10"> <div className="mx-auto max-w-6xl px-6 py-10">
<PageHeader <PageHeader
title="Tax Dashboard" title="Tax Dashboard"
+99 -40
View File
@@ -7,7 +7,7 @@ import {
testFedExConnection, testFedExConnection,
type ShippingSettings, type ShippingSettings,
} from "@/actions/shipping/settings"; } from "@/actions/shipping/settings";
import { AdminInput, AdminTextInput, AdminTextarea, AdminSelect } from "./design-system"; import { AdminInput, AdminTextInput, AdminTextarea, AdminSelect, AdminButton } from "./design-system";
const SERVICE_OPTIONS = [ const SERVICE_OPTIONS = [
{ value: "FEDEX_OVERNIGHT", label: "FedEx Overnight" }, { value: "FEDEX_OVERNIGHT", label: "FedEx Overnight" },
@@ -132,42 +132,78 @@ export default function ShippingSettingsForm({
)} )}
{/* Connection status banner */} {/* Connection status banner */}
<div className={`flex items-center gap-3 rounded-xl px-4 py-3 text-sm font-medium ${ <div
isConfigured className="flex items-center gap-3 rounded-xl px-4 py-3 text-sm font-medium border"
? "bg-emerald-900/30 border border-emerald-700 text-emerald-300" style={{
: "bg-amber-900/30 border border-amber-700 text-amber-300" backgroundColor: isConfigured ? "rgba(16, 185, 129, 0.1)" : "rgba(245, 158, 11, 0.1)",
}`}> borderColor: isConfigured ? "var(--admin-accent)" : "rgba(245, 158, 11, 0.3)",
<span className={`h-2.5 w-2.5 rounded-full ${isConfigured ? "bg-emerald-500" : "bg-amber-900/300"}`} /> color: isConfigured ? "var(--admin-accent)" : "rgb(245, 158, 11)",
}}
>
<span
className="h-2.5 w-2.5 rounded-full"
style={{ backgroundColor: isConfigured ? "var(--admin-accent)" : "rgb(245, 158, 11)" }}
/>
{isConfigured {isConfigured
? `FedEx Connected — ${settings?.fedex_use_production ? "Production" : "Sandbox"} mode` ? `FedEx Connected — ${settings?.fedex_use_production ? "Production" : "Sandbox"} mode`
: "FedEx Not Configured — enter credentials below to enable shipping rates and label generation"} : "FedEx Not Configured — enter credentials below to enable shipping rates and label generation"}
</div> </div>
{error && ( {error && (
<div className="rounded-xl bg-red-900/50 border border-red-700 p-4 text-sm text-red-200">{error}</div> <div
className="rounded-xl p-4 text-sm border"
style={{
backgroundColor: "rgba(239, 68, 68, 0.1)",
borderColor: "rgba(239, 68, 68, 0.3)",
color: "rgb(239, 68, 68)"
}}
>
{error}
</div>
)} )}
{saved && ( {saved && (
<div className="rounded-xl bg-green-900/50 border border-green-700 p-4 text-sm text-green-200"> <div
className="rounded-xl p-4 text-sm border"
style={{
backgroundColor: "rgba(16, 185, 129, 0.1)",
borderColor: "rgba(16, 185, 129, 0.3)",
color: "var(--admin-accent)"
}}
>
Shipping settings saved. Shipping settings saved.
</div> </div>
)} )}
{testResult && ( {testResult && (
<div className={`rounded-xl p-4 text-sm border ${testResult.success ? "bg-green-900/50 border-green-700 text-green-200" : "bg-red-900/50 border-red-700 text-red-200"}`}> <div
className="rounded-xl p-4 text-sm border"
style={{
backgroundColor: testResult.success ? "rgba(16, 185, 129, 0.1)" : "rgba(239, 68, 68, 0.1)",
borderColor: testResult.success ? "rgba(16, 185, 129, 0.3)" : "rgba(239, 68, 68, 0.3)",
color: testResult.success ? "var(--admin-accent)" : "rgb(239, 68, 68)"
}}
>
{testResult.message} {testResult.message}
</div> </div>
)} )}
{/* FedEx credentials */} {/* FedEx credentials */}
<div className="space-y-5 rounded-xl border border-blue-400 bg-blue-900/20 p-5"> <div
className="space-y-5 rounded-xl border p-5"
style={{
backgroundColor: "var(--admin-bg-subtle)",
borderColor: "var(--admin-border)"
}}
>
<div> <div>
<h3 className="font-semibold text-blue-300">FedEx API Credentials</h3> <h3 className="font-semibold" style={{ color: "var(--admin-text-primary)" }}>FedEx API Credentials</h3>
<p className="mt-1 text-sm text-blue-400"> <p className="mt-1 text-sm" style={{ color: "var(--admin-text-muted)" }}>
Used for shipping fresh sweet corn and onions. Get your credentials from{" "} Used for shipping fresh sweet corn and onions. Get your credentials from{" "}
<a <a
href="https://developer.fedex.com" href="https://developer.fedex.com"
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="underline hover:no-underline" className="underline hover:no-underline"
style={{ color: "var(--admin-accent)" }}
> >
developer.fedex.com developer.fedex.com
</a> </a>
@@ -203,7 +239,8 @@ export default function ShippingSettingsForm({
<button <button
type="button" type="button"
onClick={() => setShowSecret(!showSecret)} onClick={() => setShowSecret(!showSecret)}
className="absolute right-3 top-1/2 -translate-y-1/2 text-slate-400 hover:text-zinc-400 text-xs" className="absolute right-3 top-1/2 -translate-y-1/2 text-xs transition-colors"
style={{ color: "var(--admin-text-muted)" }}
> >
{showSecret ? "Hide" : "Show"} {showSecret ? "Hide" : "Show"}
</button> </button>
@@ -211,23 +248,32 @@ export default function ShippingSettingsForm({
</AdminInput> </AdminInput>
{/* Production toggle */} {/* Production toggle */}
<div className="flex items-center gap-3 rounded-lg border border-zinc-700 bg-zinc-800 px-4 py-3"> <div
className="flex items-center gap-3 rounded-xl px-4 py-3 border"
style={{
backgroundColor: "var(--admin-bg)",
borderColor: "var(--admin-border)"
}}
>
<button <button
type="button" type="button"
onClick={() => setFedexUseProduction(!fedexUseProduction)} onClick={() => setFedexUseProduction(!fedexUseProduction)}
className={`relative inline-flex h-6 w-11 items-center rounded-full transition-colors ${ className="relative inline-flex h-6 w-11 items-center rounded-full transition-colors"
fedexUseProduction ? "bg-blue-600" : "bg-zinc-600" style={{
}`} backgroundColor: fedexUseProduction ? "var(--admin-accent)" : "var(--admin-text-muted)"
}}
> >
<span <span
className={`inline-block h-4 w-4 transform rounded-full bg-zinc-900 transition-transform ${ className="inline-block h-4 w-4 rounded-full transition-transform"
fedexUseProduction ? "translate-x-6" : "translate-x-1" style={{
}`} backgroundColor: "white",
transform: fedexUseProduction ? "translateX(26px)" : "translateX(4px)"
}}
/> />
</button> </button>
<div> <div>
<span className="text-sm font-medium text-zinc-200">Use Production Mode</span> <span className="text-sm font-medium" style={{ color: "var(--admin-text-primary)" }}>Use Production Mode</span>
<p className="text-xs text-zinc-400"> <p className="text-xs" style={{ color: "var(--admin-text-muted)" }}>
{fedexUseProduction {fedexUseProduction
? "Live rates, real labels. Sandbox is currently active." ? "Live rates, real labels. Sandbox is currently active."
: "Sandbox/test mode. Use for testing before going live."} : "Sandbox/test mode. Use for testing before going live."}
@@ -237,32 +283,38 @@ export default function ShippingSettingsForm({
{/* Test connection */} {/* Test connection */}
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<button <AdminButton
type="button" variant="primary"
size="sm"
onClick={handleTestConnection} onClick={handleTestConnection}
disabled={testing || !fedexApiKey || !fedexApiSecret} disabled={testing || !fedexApiKey || !fedexApiSecret}
className="rounded-xl border border-blue-400 bg-blue-600 px-5 py-2.5 text-sm font-semibold text-white hover:bg-blue-700 disabled:opacity-50"
> >
{testing ? ( {testing ? (
<span className="flex items-center gap-2"> <span className="flex items-center gap-2">
<span className="h-4 w-4 rounded-full border-2 border-blue-600 border-t-transparent animate-spin" /> <span className="h-4 w-4 rounded-full border-2 border-white border-t-transparent animate-spin" />
Testing... Testing...
</span> </span>
) : ( ) : (
"Test Connection" "Test Connection"
)} )}
</button> </AdminButton>
{testResult?.success && ( {testResult?.success && (
<span className="text-sm text-green-400 font-medium"> Connection verified</span> <span className="text-sm font-medium" style={{ color: "var(--admin-accent)" }}> Connection verified</span>
)} )}
</div> </div>
</div> </div>
{/* Default shipping options */} {/* Default shipping options */}
<div className="space-y-5 rounded-xl border border-zinc-700 bg-zinc-900 p-5"> <div
className="space-y-5 rounded-xl border p-5"
style={{
backgroundColor: "var(--admin-bg-subtle)",
borderColor: "var(--admin-border)"
}}
>
<div> <div>
<h3 className="font-semibold text-zinc-200">Default Shipping Service</h3> <h3 className="font-semibold" style={{ color: "var(--admin-text-primary)" }}>Default Shipping Service</h3>
<p className="mt-1 text-sm text-zinc-400"> <p className="mt-1 text-sm" style={{ color: "var(--admin-text-muted)" }}>
Applied automatically when creating shipments. Perishable orders always require Overnight or 2-Day Air regardless of this setting. Applied automatically when creating shipments. Perishable orders always require Overnight or 2-Day Air regardless of this setting.
</p> </p>
</div> </div>
@@ -277,10 +329,16 @@ export default function ShippingSettingsForm({
</div> </div>
{/* Handling notes */} {/* Handling notes */}
<div className="space-y-5 rounded-xl border border-zinc-700 bg-zinc-900 p-5"> <div
className="space-y-5 rounded-xl border p-5"
style={{
backgroundColor: "var(--admin-bg-subtle)",
borderColor: "var(--admin-border)"
}}
>
<div> <div>
<h3 className="font-semibold text-zinc-200">Handling Instructions</h3> <h3 className="font-semibold" style={{ color: "var(--admin-text-primary)" }}>Handling Instructions</h3>
<p className="mt-1 text-sm text-zinc-400"> <p className="mt-1 text-sm" style={{ color: "var(--admin-text-muted)" }}>
These notes are attached to shipments containing perishable or fragile items (sweet corn, onions, etc.). Appear on the carrier label and in the warehouse. These notes are attached to shipments containing perishable or fragile items (sweet corn, onions, etc.). Appear on the carrier label and in the warehouse.
</p> </p>
</div> </div>
@@ -308,18 +366,19 @@ export default function ShippingSettingsForm({
</div> </div>
{loading ? ( {loading ? (
<div className="flex items-center gap-3 text-zinc-500"> <div className="flex items-center gap-3" style={{ color: "var(--admin-text-muted)" }}>
<div className="h-5 w-5 rounded-full border-2 border-zinc-500 border-t-transparent animate-spin" /> <div className="h-5 w-5 rounded-full border-2 border-current border-t-transparent animate-spin" />
Loading settings... Loading settings...
</div> </div>
) : ( ) : (
<button <AdminButton
variant="primary"
size="md"
type="submit" type="submit"
disabled={saving} disabled={saving}
className="rounded-xl bg-blue-600 px-6 py-3 text-sm font-bold text-white hover:bg-blue-700 disabled:opacity-50"
> >
{saving ? "Saving..." : "Save Shipping Settings"} {saving ? "Saving..." : "Save Shipping Settings"}
</button> </AdminButton>
)} )}
</form> </form>
); );