feat(admin): apply design system to Stops pages

- Stops list, new, and detail pages use new PageHeader with Operations
  eyebrow, Stops & Routes title, and editorial subtitle.
- StopsCalendarClient: replace hardcoded amber (#f59e0b) and brown (#92400e)
  draft markers with --admin-warning / --admin-warning-soft tokens. Calendar
  structure unchanged (drag/click/edit preserved).
- Add/Edit/StopDetail modals: replace hardcoded red rgba with --admin-danger-soft
  and color-mix derived borders.
- StopsLocationsTabs: swap hardcoded emerald-* for --admin-primary /
  --admin-primary-soft tokens.
- StopMessagingForm: align with design system (ha-field, ha-eyebrow, tokens).
- StopProductAssignment error surface: tokens instead of red rgba.
- StopsHeaderActions already tokenized via AdminButton; no change.

TS clean. Calendar functionality unchanged.
This commit is contained in:
Tyler
2026-06-17 00:31:33 -06:00
parent 6c0a282765
commit 685a1269a4
10 changed files with 304 additions and 219 deletions
+42 -28
View File
@@ -3,6 +3,7 @@
import { useState } from "react";
import { getStopPendingCustomers, type StopCustomer } from "@/actions/stops/get-stop-customers";
import { sendStopBlast } from "@/actions/communications/stop-blast";
import { AdminButton } from "@/components/admin/design-system";
const quickMessages = [
{ label: "Truck running late", value: "Heads up — the truck is running about 15 minutes behind schedule. Thanks for your patience!" },
@@ -84,10 +85,10 @@ export default function StopMessagingForm({
return (
<div className="space-y-6">
<div>
<h2 className="text-2xl font-bold text-zinc-100">
<h2 className="ha-display text-2xl text-[var(--admin-text-primary)]">
Send Message
</h2>
<p className="mt-1 text-zinc-400">
<p className="mt-1 text-sm text-[var(--admin-text-muted)]">
Notify all customers with pending pickups at this stop.
</p>
</div>
@@ -96,18 +97,21 @@ export default function StopMessagingForm({
<button
onClick={loadCustomers}
disabled={loading}
className="w-full rounded-xl border-2 border-dashed border-zinc-600 px-6 py-4 text-lg font-medium text-zinc-500 disabled:opacity-50"
className="w-full rounded-xl border-2 border-dashed border-[var(--admin-border)] px-6 py-4 text-lg font-medium text-[var(--admin-text-secondary)] hover:border-[var(--admin-primary)] hover:text-[var(--admin-primary)] transition-colors disabled:opacity-50"
>
{loading ? "Loading customers..." : "Load Pending Customers"}
</button>
) : (
<>
{customers.length === 0 ? (
<div className="rounded-xl bg-slate-50 p-6 text-center text-zinc-500">
<div className="rounded-xl bg-[var(--admin-bg-subtle)] p-6 text-center text-[var(--admin-text-muted)]">
No pending orders for this stop yet.
</div>
) : (
<div className="rounded-xl bg-green-900/30 p-4 text-sm text-green-400">
<div
className="rounded-xl p-4 text-sm text-[var(--admin-success)]"
style={{ background: "var(--admin-success-soft)" }}
>
{customers.length} pending order{customers.length !== 1 ? "s" : ""} found
{recipients.length !== customers.length && (
<span> {recipients.length} with contact info</span>
@@ -117,21 +121,21 @@ export default function StopMessagingForm({
{/* Channel */}
<div>
<label className="mb-2 block text-sm font-medium text-zinc-300">
Send via
<label className="ha-field-label mb-2">
<span>Send via</span>
</label>
<div className="flex gap-3">
<div className="flex gap-2">
{(["sms", "email", "both"] as const).map((ch) => (
<button
key={ch}
onClick={() => setChannel(ch)}
className={`flex-1 rounded-xl px-4 py-3 text-sm font-medium transition-colors ${
channel === ch
? "bg-slate-900 text-white"
: "bg-zinc-950 text-zinc-400 hover:bg-slate-200"
? "bg-[var(--admin-primary)] text-white"
: "bg-[var(--admin-bg-subtle)] text-[var(--admin-text-secondary)] hover:bg-[var(--admin-primary-soft)] hover:text-[var(--admin-primary)]"
}`}
>
{ch === "sms" ? "📱 SMS" : ch === "email" ? "✉️ Email" : "📱+✉️ Both"}
{ch === "sms" ? "SMS" : ch === "email" ? "Email" : "Both"}
</button>
))}
</div>
@@ -139,8 +143,8 @@ export default function StopMessagingForm({
{/* Quick messages */}
<div>
<label className="mb-2 block text-sm font-medium text-zinc-300">
Quick messages
<label className="ha-field-label mb-2">
<span>Quick messages</span>
</label>
<div className="flex flex-wrap gap-2">
{quickMessages.map((qm) => (
@@ -149,8 +153,8 @@ export default function StopMessagingForm({
onClick={() => applyQuickMessage(qm.value)}
className={`rounded-full px-3 py-1 text-sm font-medium transition-colors ${
message === qm.value
? "bg-slate-900 text-white"
: "bg-zinc-950 text-zinc-400 hover:bg-slate-200"
? "bg-[var(--admin-primary)] text-white"
: "bg-[var(--admin-bg-subtle)] text-[var(--admin-text-secondary)] hover:bg-[var(--admin-primary-soft)] hover:text-[var(--admin-primary)]"
}`}
>
{qm.label}
@@ -161,8 +165,8 @@ export default function StopMessagingForm({
{/* Message preview */}
<div>
<label className="mb-2 block text-sm font-medium text-zinc-300">
Message
<label className="ha-field-label mb-2">
<span>Message</span>
</label>
<textarea
value={message}
@@ -171,37 +175,44 @@ export default function StopMessagingForm({
setCustomMessage(e.target.value);
}}
rows={4}
className="w-full rounded-xl border border-zinc-600 px-4 py-3 text-base outline-none focus:border-slate-900"
className="ha-field-textarea"
placeholder="Type your message..."
/>
<p className="mt-1 text-xs text-slate-400">
<p className="mt-1 text-xs text-[var(--admin-text-muted)] tabular-nums">
{message.length} characters
</p>
</div>
{error && (
<div className="rounded-xl bg-red-900/30 p-4 text-red-400 text-sm">
<div
role="alert"
className="rounded-xl p-4 text-sm text-[var(--admin-danger)]"
style={{ background: "var(--admin-danger-soft)" }}
>
{error}
</div>
)}
{sent > 0 && (
<div className="rounded-xl bg-green-900/30 p-4 text-green-400 text-sm">
<div
className="rounded-xl p-4 text-sm text-[var(--admin-success)]"
style={{ background: "var(--admin-success-soft)" }}
>
Message sent to {sent} customer{sent !== 1 ? "s" : ""}!
</div>
)}
{/* Recipients */}
{recipients.length > 0 && message && (
<div className="rounded-xl border border-zinc-800 p-4">
<p className="mb-3 text-sm font-medium text-zinc-300">
<div className="rounded-xl border border-[var(--admin-border)] bg-white p-4">
<p className="mb-3 text-sm font-medium text-[var(--admin-text-secondary)]">
Recipients ({recipients.length}):
</p>
<div className="space-y-2">
{recipients.map((c) => (
<div key={c.id} className="flex justify-between text-sm">
<span className="text-zinc-300">{c.customer_name}</span>
<span className="text-slate-400">
<span className="text-[var(--admin-text-primary)]">{c.customer_name}</span>
<span className="text-[var(--admin-text-muted)] tabular-nums">
{c.customer_phone ?? c.customer_email ?? "no contact"}
</span>
</div>
@@ -210,15 +221,18 @@ export default function StopMessagingForm({
</div>
)}
<button
<AdminButton
variant="primary"
size="lg"
onClick={handleSend}
disabled={!message || recipients.length === 0 || sending}
className="w-full rounded-xl bg-slate-900 px-6 py-4 text-lg font-bold text-white disabled:opacity-50"
isLoading={sending}
className="w-full"
>
{sending
? "Sending..."
: `Send to ${recipients.length} customer${recipients.length !== 1 ? "s" : ""}`}
</button>
</AdminButton>
</>
)}
</div>