fix(frontend): finish the token migration — raw grey classes -> theme tokens
UI_REDESIGN chunk 4 was never finished: 39 app/component files still used raw Tailwind greys, kept readable in light mode only by the !important override sheet. In dark mode text-gray-600 is ~2.3:1 on surface, and the light override maps it to #94a3b8 (~2.6:1 on white) — unreadable in both. Mechanical mapping (886 classes): gray-100/200 -> ink, 300/400 -> ink-2, 500/600/700 -> ink-muted, bg 950/900/800 -> page/surface/raised, border 800 -> line, 700/600 -> line-strong, plus hover/divide/placeholder. Alpha suffixes kept (tokens are color-mix, so /60 works). bg-gray-700, text-white and marketing pages left alone. text-[9px] -> text-[10px]. Frontend unverified: no Node on the authoring box (#207). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
3968b49b80
commit
bf206334e3
@@ -141,7 +141,7 @@ function PlaceSearch({
|
||||
onChange={(e) => setQuery(e.target.value)}
|
||||
onKeyDown={(e) => e.key === "Enter" && (e.preventDefault(), search())}
|
||||
placeholder="Search a place…"
|
||||
className="flex-1 bg-gray-800 border border-gray-700 rounded-lg px-3 py-2 text-white text-sm focus:outline-none focus:border-indigo-500"
|
||||
className="flex-1 bg-raised border border-line-strong rounded-lg px-3 py-2 text-white text-sm focus:outline-none focus:border-indigo-500"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
@@ -153,7 +153,7 @@ function PlaceSearch({
|
||||
</button>
|
||||
</div>
|
||||
{results.length > 0 && (
|
||||
<div className="bg-gray-800 border border-gray-700 rounded-lg overflow-hidden divide-y divide-gray-700">
|
||||
<div className="bg-raised border border-line-strong rounded-lg overflow-hidden divide-y divide-line-strong">
|
||||
{results.map((p) => (
|
||||
<button
|
||||
key={p.place_id}
|
||||
@@ -162,7 +162,7 @@ function PlaceSearch({
|
||||
className="w-full text-left px-3 py-2 hover:bg-gray-700 transition-colors"
|
||||
>
|
||||
<p className="text-white text-sm font-medium">{p.name}</p>
|
||||
<p className="text-gray-400 text-xs">{p.address}</p>
|
||||
<p className="text-ink-2 text-xs">{p.address}</p>
|
||||
{p.rating && (
|
||||
<p className="text-yellow-400 text-xs">★ {p.rating}</p>
|
||||
)}
|
||||
@@ -240,60 +240,60 @@ function AddEventModal({
|
||||
<div className="fixed inset-0 bg-black/70 flex items-center justify-center z-50 p-4">
|
||||
<form
|
||||
onSubmit={handleSubmit}
|
||||
className="bg-gray-900 border border-gray-700 rounded-xl p-6 w-full max-w-lg space-y-4 max-h-[90vh] overflow-y-auto"
|
||||
className="bg-surface border border-line-strong rounded-xl p-6 w-full max-w-lg space-y-4 max-h-[90vh] overflow-y-auto"
|
||||
>
|
||||
<h2 className="text-white font-bold text-lg">{isEdit ? "Edit Event" : "Add Event"}</h2>
|
||||
|
||||
<div>
|
||||
<label className="text-xs text-gray-400 block mb-1">Title</label>
|
||||
<label className="text-xs text-ink-2 block mb-1">Title</label>
|
||||
<input
|
||||
required value={title} onChange={(e) => setTitle(e.target.value)}
|
||||
placeholder="Dinner at the bar"
|
||||
className="w-full bg-gray-800 border border-gray-700 rounded-lg px-3 py-2 text-white text-sm focus:outline-none focus:border-indigo-500"
|
||||
className="w-full bg-raised border border-line-strong rounded-lg px-3 py-2 text-white text-sm focus:outline-none focus:border-indigo-500"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-3 gap-3">
|
||||
<div>
|
||||
<label className="text-xs text-gray-400 block mb-1">Date</label>
|
||||
<label className="text-xs text-ink-2 block mb-1">Date</label>
|
||||
<input
|
||||
required type="date"
|
||||
min={trip.start_date} max={trip.end_date}
|
||||
value={date} onChange={(e) => setDate(e.target.value)}
|
||||
className="w-full bg-gray-800 border border-gray-700 rounded-lg px-3 py-2 text-white text-sm focus:outline-none focus:border-indigo-500"
|
||||
className="w-full bg-raised border border-line-strong rounded-lg px-3 py-2 text-white text-sm focus:outline-none focus:border-indigo-500"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-xs text-gray-400 block mb-1">Start</label>
|
||||
<label className="text-xs text-ink-2 block mb-1">Start</label>
|
||||
<input
|
||||
type="time" value={startTime} onChange={(e) => setStartTime(e.target.value)}
|
||||
className="w-full bg-gray-800 border border-gray-700 rounded-lg px-3 py-2 text-white text-sm focus:outline-none focus:border-indigo-500"
|
||||
className="w-full bg-raised border border-line-strong rounded-lg px-3 py-2 text-white text-sm focus:outline-none focus:border-indigo-500"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-xs text-gray-400 block mb-1">End</label>
|
||||
<label className="text-xs text-ink-2 block mb-1">End</label>
|
||||
<input
|
||||
type="time" value={endTime} onChange={(e) => setEndTime(e.target.value)}
|
||||
min={startTime}
|
||||
className="w-full bg-gray-800 border border-gray-700 rounded-lg px-3 py-2 text-white text-sm focus:outline-none focus:border-indigo-500"
|
||||
className="w-full bg-raised border border-line-strong rounded-lg px-3 py-2 text-white text-sm focus:outline-none focus:border-indigo-500"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="text-xs text-gray-400 block mb-1">Find a place</label>
|
||||
<label className="text-xs text-ink-2 block mb-1">Find a place</label>
|
||||
<PlaceSearch near={trip.location} onSelect={handlePlaceSelect} />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="text-xs text-gray-400 block mb-1">
|
||||
<label className="text-xs text-ink-2 block mb-1">
|
||||
Location{" "}
|
||||
<span className="text-gray-600">(auto-filled from search, or type manually)</span>
|
||||
<span className="text-ink-muted">(auto-filled from search, or type manually)</span>
|
||||
</label>
|
||||
<input
|
||||
value={location} onChange={(e) => setLocation(e.target.value)}
|
||||
placeholder={`Inherits: ${trip.location}`}
|
||||
className="w-full bg-gray-800 border border-gray-700 rounded-lg px-3 py-2 text-white text-sm focus:outline-none focus:border-indigo-500"
|
||||
className="w-full bg-raised border border-line-strong rounded-lg px-3 py-2 text-white text-sm focus:outline-none focus:border-indigo-500"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -307,16 +307,16 @@ function AddEventModal({
|
||||
)}
|
||||
|
||||
<div>
|
||||
<label className="text-xs text-gray-400 block mb-1">Notes (optional)</label>
|
||||
<label className="text-xs text-ink-2 block mb-1">Notes (optional)</label>
|
||||
<textarea
|
||||
value={notes} onChange={(e) => setNotes(e.target.value)} rows={2}
|
||||
className="w-full bg-gray-800 border border-gray-700 rounded-lg px-3 py-2 text-white text-sm focus:outline-none resize-none"
|
||||
className="w-full bg-raised border border-line-strong rounded-lg px-3 py-2 text-white text-sm focus:outline-none resize-none"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{availableTags.length > 0 && (
|
||||
<div>
|
||||
<label className="text-xs text-gray-400 block mb-1">Tags</label>
|
||||
<label className="text-xs text-ink-2 block mb-1">Tags</label>
|
||||
<div className="flex flex-wrap gap-1.5">
|
||||
{availableTags.map((tag) => {
|
||||
const active = tags.includes(tag);
|
||||
@@ -326,7 +326,7 @@ function AddEventModal({
|
||||
type="button"
|
||||
onClick={() => setTags((prev) => active ? prev.filter((t) => t !== tag) : [...prev, tag])}
|
||||
className={`text-xs rounded-full px-2.5 py-1 border transition-colors ${
|
||||
active ? tagColor(tag, availableTags) : "bg-gray-800 text-gray-500 border-gray-700 hover:border-gray-600 hover:text-gray-400"
|
||||
active ? tagColor(tag, availableTags) : "bg-raised text-ink-muted border-line-strong hover:border-line-strong hover:text-ink-2"
|
||||
}`}
|
||||
>
|
||||
{tag}
|
||||
@@ -340,7 +340,7 @@ function AddEventModal({
|
||||
{error && <p className="text-red-400 text-xs">{error}</p>}
|
||||
|
||||
<div className="flex gap-3 justify-end pt-1">
|
||||
<button type="button" onClick={onClose} className="text-sm text-gray-400 hover:text-gray-200 px-4 py-2">
|
||||
<button type="button" onClick={onClose} className="text-sm text-ink-2 hover:text-ink px-4 py-2">
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
@@ -389,7 +389,7 @@ function DayTimeline({
|
||||
|
||||
if (timed.length === 0 && untimed.length === 0) {
|
||||
return (
|
||||
<p className="text-gray-600 text-sm font-mono py-8 text-center">
|
||||
<p className="text-ink-muted text-sm font-mono py-8 text-center">
|
||||
No events on this day yet.
|
||||
</p>
|
||||
);
|
||||
@@ -424,10 +424,10 @@ function DayTimeline({
|
||||
style={{ top: (h * 60 - rangeStart) * PX_PER_MIN }}
|
||||
className="absolute inset-x-0 flex items-center pointer-events-none"
|
||||
>
|
||||
<span className="w-14 text-right pr-3 text-xs text-gray-600 font-mono shrink-0 -translate-y-2">
|
||||
<span className="w-14 text-right pr-3 text-xs text-ink-muted font-mono shrink-0 -translate-y-2">
|
||||
{h === 0 ? "12 AM" : h < 12 ? `${h} AM` : h === 12 ? "12 PM" : `${h - 12} PM`}
|
||||
</span>
|
||||
<div className="flex-1 border-t border-gray-800/70" />
|
||||
<div className="flex-1 border-t border-line/70" />
|
||||
</div>
|
||||
))}
|
||||
|
||||
@@ -444,7 +444,7 @@ function DayTimeline({
|
||||
{e.end_time && (
|
||||
<div
|
||||
style={{ top, height, left: 60, right: 0 }}
|
||||
className="absolute bg-gray-800/30 border-l-2 border-gray-600/30 z-0 pointer-events-none"
|
||||
className="absolute bg-raised/30 border-l-2 border-line-strong/30 z-0 pointer-events-none"
|
||||
/>
|
||||
)}
|
||||
{/* Dashed marker line + label */}
|
||||
@@ -452,15 +452,15 @@ function DayTimeline({
|
||||
style={{ top, left: 60, right: 0 }}
|
||||
className="absolute z-10 flex items-center gap-2"
|
||||
>
|
||||
<div className="flex-1 border-t border-dashed border-gray-600/40" />
|
||||
<span className="text-gray-500 text-[10px] font-mono shrink-0 pr-1">
|
||||
<div className="flex-1 border-t border-dashed border-line-strong/40" />
|
||||
<span className="text-ink-muted text-[10px] font-mono shrink-0 pr-1">
|
||||
{fmtTime(e.start_time)}
|
||||
</span>
|
||||
<span className="text-gray-500 text-[10px] truncate max-w-[120px] shrink-0">{e.title}</span>
|
||||
<span className="text-ink-muted text-[10px] truncate max-w-[120px] shrink-0">{e.title}</span>
|
||||
{isAdmin && (
|
||||
<div className="flex gap-1 opacity-0 group-hover:opacity-100 transition-opacity shrink-0">
|
||||
<button onClick={() => onEdit(e)} className="text-gray-600 hover:text-indigo-400 text-[10px]">Edit</button>
|
||||
<button onClick={() => onDelete(e.event_id)} className="text-gray-600 hover:text-red-400 text-xs leading-none">×</button>
|
||||
<button onClick={() => onEdit(e)} className="text-ink-muted hover:text-indigo-400 text-[10px]">Edit</button>
|
||||
<button onClick={() => onDelete(e.event_id)} className="text-ink-muted hover:text-red-400 text-xs leading-none">×</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -498,10 +498,10 @@ function DayTimeline({
|
||||
</p>
|
||||
)}
|
||||
{height >= 60 && !e.location_inherited && e.location && (
|
||||
<p className="text-gray-500 text-xs mt-0.5 truncate">{e.location}</p>
|
||||
<p className="text-ink-muted text-xs mt-0.5 truncate">{e.location}</p>
|
||||
)}
|
||||
{height >= 60 && e.notes && (
|
||||
<p className="text-gray-600 text-xs mt-0.5 italic truncate">{e.notes}</p>
|
||||
<p className="text-ink-muted text-xs mt-0.5 italic truncate">{e.notes}</p>
|
||||
)}
|
||||
{e.tags?.length > 0 && (
|
||||
<div className="flex flex-wrap gap-1 mt-1">
|
||||
@@ -525,13 +525,13 @@ function DayTimeline({
|
||||
<>
|
||||
<button
|
||||
onClick={() => onEdit(e)}
|
||||
className="text-gray-600 hover:text-indigo-400 text-xs opacity-0 group-hover:opacity-100 transition-opacity"
|
||||
className="text-ink-muted hover:text-indigo-400 text-xs opacity-0 group-hover:opacity-100 transition-opacity"
|
||||
>
|
||||
Edit
|
||||
</button>
|
||||
<button
|
||||
onClick={() => onDelete(e.event_id)}
|
||||
className="text-gray-600 hover:text-red-400 text-base leading-none opacity-0 group-hover:opacity-100 transition-opacity"
|
||||
className="text-ink-muted hover:text-red-400 text-base leading-none opacity-0 group-hover:opacity-100 transition-opacity"
|
||||
>
|
||||
×
|
||||
</button>
|
||||
@@ -543,9 +543,9 @@ function DayTimeline({
|
||||
{drive && (
|
||||
<div
|
||||
style={{ top: top + height + 2, left: 60 }}
|
||||
className="absolute text-xs text-gray-600 font-mono flex items-center gap-1 z-20"
|
||||
className="absolute text-xs text-ink-muted font-mono flex items-center gap-1 z-20"
|
||||
>
|
||||
<span className="text-gray-700">↓</span> {drive} drive
|
||||
<span className="text-ink-muted">↓</span> {drive} drive
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -557,18 +557,18 @@ function DayTimeline({
|
||||
{/* Untimed / all-day events */}
|
||||
{untimed.length > 0 && (
|
||||
<div className="space-y-1.5">
|
||||
<p className="text-xs font-mono text-gray-600 uppercase tracking-wider">Unscheduled</p>
|
||||
<p className="text-xs font-mono text-ink-muted uppercase tracking-wider">Unscheduled</p>
|
||||
{untimed.map((e) => (
|
||||
<div
|
||||
key={e.event_id}
|
||||
className="bg-gray-900 border border-gray-800 rounded-lg px-4 py-2.5 flex items-center justify-between group"
|
||||
className="bg-surface border border-line rounded-lg px-4 py-2.5 flex items-center justify-between group"
|
||||
>
|
||||
<div className="min-w-0">
|
||||
<p className="text-white text-sm">{e.title}</p>
|
||||
{!e.location_inherited && e.location && (
|
||||
<p className="text-gray-500 text-xs mt-0.5">{e.location}</p>
|
||||
<p className="text-ink-muted text-xs mt-0.5">{e.location}</p>
|
||||
)}
|
||||
{e.notes && <p className="text-gray-600 text-xs italic mt-0.5">{e.notes}</p>}
|
||||
{e.notes && <p className="text-ink-muted text-xs italic mt-0.5">{e.notes}</p>}
|
||||
{e.tags?.length > 0 && (
|
||||
<div className="flex flex-wrap gap-1 mt-1">
|
||||
{e.tags.map((t) => <TagPill key={t} tag={t} availableTags={availableTags} />)}
|
||||
@@ -585,11 +585,11 @@ function DayTimeline({
|
||||
{isAdmin && (
|
||||
<>
|
||||
<button onClick={() => onEdit(e)}
|
||||
className="text-xs text-gray-600 hover:text-indigo-400 opacity-0 group-hover:opacity-100 transition-opacity">
|
||||
className="text-xs text-ink-muted hover:text-indigo-400 opacity-0 group-hover:opacity-100 transition-opacity">
|
||||
Edit
|
||||
</button>
|
||||
<button onClick={() => onDelete(e.event_id)}
|
||||
className="text-gray-600 hover:text-red-400 text-sm opacity-0 group-hover:opacity-100 transition-opacity">
|
||||
className="text-ink-muted hover:text-red-400 text-sm opacity-0 group-hover:opacity-100 transition-opacity">
|
||||
×
|
||||
</button>
|
||||
</>
|
||||
@@ -728,17 +728,17 @@ function AssistantPanel({
|
||||
return (
|
||||
<div className="flex flex-col h-full">
|
||||
{/* Header */}
|
||||
<div className="px-4 py-3 border-b border-gray-800 shrink-0 flex items-start justify-between gap-2">
|
||||
<div className="px-4 py-3 border-b border-line shrink-0 flex items-start justify-between gap-2">
|
||||
<div>
|
||||
<p className="text-white text-sm font-semibold">Trip Assistant</p>
|
||||
<p className="text-gray-500 text-xs mt-0.5">
|
||||
<p className="text-ink-muted text-xs mt-0.5">
|
||||
Tell me what you want to do — I can search places and suggest events.
|
||||
</p>
|
||||
</div>
|
||||
{messages.length > 0 && (
|
||||
<button
|
||||
onClick={clearChat}
|
||||
className="text-xs text-gray-600 hover:text-gray-400 transition-colors shrink-0 mt-0.5"
|
||||
className="text-xs text-ink-muted hover:text-ink-2 transition-colors shrink-0 mt-0.5"
|
||||
>
|
||||
Clear
|
||||
</button>
|
||||
@@ -758,7 +758,7 @@ function AssistantPanel({
|
||||
<button
|
||||
key={prompt}
|
||||
onClick={() => { setInput(prompt); }}
|
||||
className="w-full text-left bg-gray-800/60 hover:bg-gray-800 border border-gray-700/50 rounded-lg px-3 py-2 text-gray-400 text-xs transition-colors"
|
||||
className="w-full text-left bg-raised/60 hover:bg-raised border border-line-strong/50 rounded-lg px-3 py-2 text-ink-2 text-xs transition-colors"
|
||||
>
|
||||
{prompt}
|
||||
</button>
|
||||
@@ -772,7 +772,7 @@ function AssistantPanel({
|
||||
className={`rounded-xl px-3 py-2 text-sm max-w-[90%] ${
|
||||
msg.role === "user"
|
||||
? "bg-indigo-600 text-white"
|
||||
: "bg-gray-800 text-gray-200"
|
||||
: "bg-raised text-ink"
|
||||
}`}
|
||||
>
|
||||
{msg.role === "user" ? msg.content : (
|
||||
@@ -804,11 +804,11 @@ function AssistantPanel({
|
||||
className={`w-full border rounded-xl p-3 space-y-1.5 transition-all ${
|
||||
s.added
|
||||
? "border-green-800/50 bg-green-950/30"
|
||||
: "border-gray-700 bg-gray-900"
|
||||
: "border-line-strong bg-surface"
|
||||
}`}
|
||||
>
|
||||
<p className="text-white text-sm font-semibold">{s.title}</p>
|
||||
<div className="text-xs text-gray-400 space-y-0.5">
|
||||
<div className="text-xs text-ink-2 space-y-0.5">
|
||||
{s.date && (
|
||||
<p>
|
||||
{fmtDate(s.date)}
|
||||
@@ -817,7 +817,7 @@ function AssistantPanel({
|
||||
</p>
|
||||
)}
|
||||
{s.location && <p className="truncate">{s.location}</p>}
|
||||
{s.notes && <p className="text-gray-500 italic">{s.notes}</p>}
|
||||
{s.notes && <p className="text-ink-muted italic">{s.notes}</p>}
|
||||
{s.tags && s.tags.length > 0 && (
|
||||
<div className="flex flex-wrap gap-1 pt-0.5">
|
||||
{s.tags.map((t) => <TagPill key={t} tag={t} availableTags={trip.available_tags ?? []} />)}
|
||||
@@ -840,7 +840,7 @@ function AssistantPanel({
|
||||
</button>
|
||||
<button
|
||||
onClick={() => dismissSuggestion(msg.id, idx)}
|
||||
className="px-3 text-gray-500 hover:text-gray-300 text-xs transition-colors"
|
||||
className="px-3 text-ink-muted hover:text-ink text-xs transition-colors"
|
||||
>
|
||||
Skip
|
||||
</button>
|
||||
@@ -856,7 +856,7 @@ function AssistantPanel({
|
||||
|
||||
{loading && (
|
||||
<div className="flex items-start">
|
||||
<div className="bg-gray-800 rounded-xl px-3 py-2 text-gray-400 text-sm">
|
||||
<div className="bg-raised rounded-xl px-3 py-2 text-ink-2 text-sm">
|
||||
<span className="animate-pulse">Thinking…</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -865,7 +865,7 @@ function AssistantPanel({
|
||||
</div>
|
||||
|
||||
{/* Input */}
|
||||
<div className="px-3 py-3 border-t border-gray-800 shrink-0">
|
||||
<div className="px-3 py-3 border-t border-line shrink-0">
|
||||
<div className="flex gap-2">
|
||||
<textarea
|
||||
ref={inputRef}
|
||||
@@ -875,7 +875,7 @@ function AssistantPanel({
|
||||
placeholder="What do you want to do?"
|
||||
disabled={loading}
|
||||
rows={1}
|
||||
className="flex-1 bg-gray-800 border border-gray-700 rounded-lg px-3 py-2 text-white text-sm focus:outline-none focus:border-indigo-500 disabled:opacity-50 resize-none overflow-hidden"
|
||||
className="flex-1 bg-raised border border-line-strong rounded-lg px-3 py-2 text-white text-sm focus:outline-none focus:border-indigo-500 disabled:opacity-50 resize-none overflow-hidden"
|
||||
/>
|
||||
<button
|
||||
onClick={send}
|
||||
@@ -1055,8 +1055,8 @@ export default function TripDetailPage() {
|
||||
setTrip((prev) => prev ? { ...prev, overlap_tags: overlap } : prev);
|
||||
}
|
||||
|
||||
if (loading) return <p className="text-gray-500 text-sm font-mono">Loading…</p>;
|
||||
if (!trip) return <p className="text-gray-500 text-sm font-mono">Trip not found.</p>;
|
||||
if (loading) return <p className="text-ink-muted text-sm font-mono">Loading…</p>;
|
||||
if (!trip) return <p className="text-ink-muted text-sm font-mono">Trip not found.</p>;
|
||||
|
||||
const days = dateRange(trip.start_date, trip.end_date);
|
||||
const attendees = Object.values(trip.attendees ?? {});
|
||||
@@ -1070,7 +1070,7 @@ export default function TripDetailPage() {
|
||||
<div className="space-y-4">
|
||||
<button
|
||||
onClick={() => router.push("/trips")}
|
||||
className="text-sm font-mono text-gray-500 hover:text-gray-300 transition-colors"
|
||||
className="text-sm font-mono text-ink-muted hover:text-ink transition-colors"
|
||||
>
|
||||
← Trips
|
||||
</button>
|
||||
@@ -1078,7 +1078,7 @@ export default function TripDetailPage() {
|
||||
<div className="flex items-start justify-between gap-4 flex-wrap">
|
||||
<div className="space-y-1">
|
||||
<h1 className="text-white text-2xl font-bold">{trip.name}</h1>
|
||||
<div className="flex flex-wrap items-center gap-x-3 gap-y-1 text-sm text-gray-400">
|
||||
<div className="flex flex-wrap items-center gap-x-3 gap-y-1 text-sm text-ink-2">
|
||||
<span>{trip.location}</span>
|
||||
{trip.maps_link && (
|
||||
<a href={trip.maps_link} target="_blank" rel="noopener noreferrer"
|
||||
@@ -1086,11 +1086,11 @@ export default function TripDetailPage() {
|
||||
Maps
|
||||
</a>
|
||||
)}
|
||||
<span className="text-gray-700">•</span>
|
||||
<span className="text-ink-muted">•</span>
|
||||
<span className="font-mono text-xs">{fmtDate(trip.start_date)} — {fmtDate(trip.end_date)}</span>
|
||||
{attendees.length > 0 && (
|
||||
<>
|
||||
<span className="text-gray-700">•</span>
|
||||
<span className="text-ink-muted">•</span>
|
||||
<span className="text-xs">{attendees.join(", ")}</span>
|
||||
</>
|
||||
)}
|
||||
@@ -1114,7 +1114,7 @@ export default function TripDetailPage() {
|
||||
</button>
|
||||
<button
|
||||
onClick={handleToggleVisibility}
|
||||
className="text-xs text-gray-500 hover:text-gray-300 transition-colors border border-gray-700 rounded-lg px-3 py-2"
|
||||
className="text-xs text-ink-muted hover:text-ink transition-colors border border-line-strong rounded-lg px-3 py-2"
|
||||
>
|
||||
{trip.visibility === "private" ? "Make public" : "Make private"}
|
||||
</button>
|
||||
@@ -1159,9 +1159,9 @@ export default function TripDetailPage() {
|
||||
onChange={(e) => setTagInput(e.target.value)}
|
||||
onKeyDown={(e) => e.key === "Enter" && (e.preventDefault(), handleAddTag())}
|
||||
placeholder="Add tag…"
|
||||
className="bg-transparent border border-gray-700 rounded-full px-2.5 py-0.5 text-xs text-gray-400 placeholder-gray-600 focus:outline-none focus:border-gray-500 w-24"
|
||||
className="bg-transparent border border-line-strong rounded-full px-2.5 py-0.5 text-xs text-ink-2 placeholder-ink-muted focus:outline-none focus:border-gray-500 w-24"
|
||||
/>
|
||||
<button onClick={handleAddTag} className="text-xs text-gray-500 hover:text-gray-300 transition-colors">+</button>
|
||||
<button onClick={handleAddTag} className="text-xs text-ink-muted hover:text-ink transition-colors">+</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -1170,13 +1170,13 @@ export default function TripDetailPage() {
|
||||
{/* Invite management — admin only, only when private */}
|
||||
{isAdmin && trip.visibility === "private" && (
|
||||
<div className="space-y-2">
|
||||
<p className="text-xs text-gray-500 uppercase tracking-wider font-mono">Invited</p>
|
||||
<p className="text-xs text-ink-muted uppercase tracking-wider font-mono">Invited</p>
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
{(trip.invited_discord_ids ?? []).length === 0 && (
|
||||
<span className="text-xs text-gray-600">No invites yet</span>
|
||||
<span className="text-xs text-ink-muted">No invites yet</span>
|
||||
)}
|
||||
{(trip.invited_discord_ids ?? []).map((discordId) => (
|
||||
<span key={discordId} className="inline-flex items-center gap-1 text-xs bg-gray-800 border border-gray-700 rounded-full px-2.5 py-0.5 text-gray-300">
|
||||
<span key={discordId} className="inline-flex items-center gap-1 text-xs bg-raised border border-line-strong rounded-full px-2.5 py-0.5 text-ink-2">
|
||||
<span className="font-mono">{discordId}</span>
|
||||
<button
|
||||
onClick={() => handleRevokeInvite(discordId)}
|
||||
@@ -1192,9 +1192,9 @@ export default function TripDetailPage() {
|
||||
onChange={(e) => setInviteInput(e.target.value)}
|
||||
onKeyDown={(e) => e.key === "Enter" && (e.preventDefault(), handleInvite())}
|
||||
placeholder="Discord user ID…"
|
||||
className="bg-transparent border border-gray-700 rounded-full px-2.5 py-0.5 text-xs text-gray-400 placeholder-gray-600 focus:outline-none focus:border-gray-500 w-36"
|
||||
className="bg-transparent border border-line-strong rounded-full px-2.5 py-0.5 text-xs text-ink-2 placeholder-ink-muted focus:outline-none focus:border-gray-500 w-36"
|
||||
/>
|
||||
<button onClick={handleInvite} className="text-xs text-gray-500 hover:text-gray-300 transition-colors">+</button>
|
||||
<button onClick={handleInvite} className="text-xs text-ink-muted hover:text-ink transition-colors">+</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1218,13 +1218,13 @@ export default function TripDetailPage() {
|
||||
className={`flex flex-col items-center px-3 py-2 rounded-xl text-xs transition-colors relative ${
|
||||
day === selectedDay
|
||||
? "bg-indigo-600 text-white"
|
||||
: "bg-gray-900 border border-gray-800 text-gray-400 hover:border-gray-700 hover:text-gray-200"
|
||||
: "bg-surface border border-line text-ink-2 hover:border-line-strong hover:text-ink"
|
||||
}`}
|
||||
>
|
||||
<span className="font-mono font-bold">{short}</span>
|
||||
<span className="font-mono">{date}</span>
|
||||
{count > 0 && (
|
||||
<span className={`text-xs mt-0.5 ${day === selectedDay ? "text-indigo-200" : "text-gray-600"}`}>
|
||||
<span className={`text-xs mt-0.5 ${day === selectedDay ? "text-indigo-200" : "text-ink-muted"}`}>
|
||||
{count}
|
||||
</span>
|
||||
)}
|
||||
@@ -1238,7 +1238,7 @@ export default function TripDetailPage() {
|
||||
|
||||
{/* Selected day */}
|
||||
<div className="space-y-4">
|
||||
<p className="text-sm font-mono text-gray-400 uppercase tracking-wider">
|
||||
<p className="text-sm font-mono text-ink-2 uppercase tracking-wider">
|
||||
{fmtDayHeading(selectedDay)}
|
||||
</p>
|
||||
<DayTimeline
|
||||
@@ -1254,7 +1254,7 @@ export default function TripDetailPage() {
|
||||
</div>
|
||||
|
||||
{/* Right: AI Assistant — sticky */}
|
||||
<div className="w-80 xl:w-96 shrink-0 sticky top-20 h-[calc(100vh-240px)] flex flex-col bg-gray-900 border border-gray-800 rounded-xl overflow-hidden">
|
||||
<div className="w-80 xl:w-96 shrink-0 sticky top-20 h-[calc(100vh-240px)] flex flex-col bg-surface border border-line rounded-xl overflow-hidden">
|
||||
<AssistantPanel trip={trip} onAddEvent={handleAssistantAddEvent} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -29,25 +29,25 @@ function TripCard({ trip, isAdmin, onDelete }: {
|
||||
|
||||
return (
|
||||
<div
|
||||
className="bg-gray-900 border border-gray-800 rounded-xl p-5 cursor-pointer hover:border-gray-700 transition-colors"
|
||||
className="bg-surface border border-line rounded-xl p-5 cursor-pointer hover:border-line-strong transition-colors"
|
||||
onClick={() => router.push(`/trips/${trip.trip_id}`)}
|
||||
>
|
||||
<div className="flex items-start justify-between gap-4">
|
||||
<div className="min-w-0">
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
<span className={`text-xs font-mono px-2 py-0.5 rounded-full ${
|
||||
upcoming ? "bg-indigo-900 text-indigo-300" : "bg-gray-800 text-gray-500"
|
||||
upcoming ? "bg-indigo-900 text-indigo-300" : "bg-raised text-ink-muted"
|
||||
}`}>
|
||||
{upcoming ? "Upcoming" : "Past"}
|
||||
</span>
|
||||
</div>
|
||||
<h3 className="text-white font-semibold text-sm leading-snug">{trip.name}</h3>
|
||||
<p className="text-gray-400 text-xs mt-1">{trip.location}</p>
|
||||
<p className="text-gray-500 text-xs font-mono mt-1">
|
||||
<p className="text-ink-2 text-xs mt-1">{trip.location}</p>
|
||||
<p className="text-ink-muted text-xs font-mono mt-1">
|
||||
{fmtDate(trip.start_date)} — {fmtDate(trip.end_date)}
|
||||
</p>
|
||||
{attendeeCount > 0 && (
|
||||
<p className="text-gray-500 text-xs mt-1">
|
||||
<p className="text-ink-muted text-xs mt-1">
|
||||
{attendeeCount} going
|
||||
</p>
|
||||
)}
|
||||
@@ -102,59 +102,59 @@ function CreateModal({ onClose, onCreate }: {
|
||||
<div className="fixed inset-0 bg-black/60 flex items-center justify-center z-50 p-4">
|
||||
<form
|
||||
onSubmit={handleSubmit}
|
||||
className="bg-gray-900 border border-gray-700 rounded-xl p-6 w-full max-w-md space-y-4 max-h-[90vh] overflow-y-auto"
|
||||
className="bg-surface border border-line-strong rounded-xl p-6 w-full max-w-md space-y-4 max-h-[90vh] overflow-y-auto"
|
||||
>
|
||||
<h2 className="text-white font-bold">New Trip</h2>
|
||||
|
||||
<div>
|
||||
<label className="text-xs text-gray-400 block mb-1">Name</label>
|
||||
<label className="text-xs text-ink-2 block mb-1">Name</label>
|
||||
<input
|
||||
required value={name} onChange={(e) => setName(e.target.value)}
|
||||
placeholder="Road trip to Nashville"
|
||||
className="w-full bg-gray-800 border border-gray-700 rounded-lg px-3 py-2 text-white text-sm focus:outline-none focus:border-indigo-500"
|
||||
className="w-full bg-raised border border-line-strong rounded-lg px-3 py-2 text-white text-sm focus:outline-none focus:border-indigo-500"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="text-xs text-gray-400 block mb-1">Location</label>
|
||||
<label className="text-xs text-ink-2 block mb-1">Location</label>
|
||||
<input
|
||||
required value={location} onChange={(e) => setLocation(e.target.value)}
|
||||
placeholder="Nashville, TN"
|
||||
className="w-full bg-gray-800 border border-gray-700 rounded-lg px-3 py-2 text-white text-sm focus:outline-none focus:border-indigo-500"
|
||||
className="w-full bg-raised border border-line-strong rounded-lg px-3 py-2 text-white text-sm focus:outline-none focus:border-indigo-500"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<div>
|
||||
<label className="text-xs text-gray-400 block mb-1">Start date</label>
|
||||
<label className="text-xs text-ink-2 block mb-1">Start date</label>
|
||||
<input
|
||||
required type="date" value={start} onChange={(e) => setStart(e.target.value)}
|
||||
className="w-full bg-gray-800 border border-gray-700 rounded-lg px-3 py-2 text-white text-sm focus:outline-none focus:border-indigo-500"
|
||||
className="w-full bg-raised border border-line-strong rounded-lg px-3 py-2 text-white text-sm focus:outline-none focus:border-indigo-500"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-xs text-gray-400 block mb-1">End date</label>
|
||||
<label className="text-xs text-ink-2 block mb-1">End date</label>
|
||||
<input
|
||||
required type="date" value={end} onChange={(e) => setEnd(e.target.value)}
|
||||
min={start}
|
||||
className="w-full bg-gray-800 border border-gray-700 rounded-lg px-3 py-2 text-white text-sm focus:outline-none focus:border-indigo-500"
|
||||
className="w-full bg-raised border border-line-strong rounded-lg px-3 py-2 text-white text-sm focus:outline-none focus:border-indigo-500"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="text-xs text-gray-400 block mb-1">Google Maps link (optional)</label>
|
||||
<label className="text-xs text-ink-2 block mb-1">Google Maps link (optional)</label>
|
||||
<input
|
||||
type="url" value={mapsLink} onChange={(e) => setMapsLink(e.target.value)}
|
||||
placeholder="https://maps.google.com/…"
|
||||
className="w-full bg-gray-800 border border-gray-700 rounded-lg px-3 py-2 text-white text-sm focus:outline-none focus:border-indigo-500"
|
||||
className="w-full bg-raised border border-line-strong rounded-lg px-3 py-2 text-white text-sm focus:outline-none focus:border-indigo-500"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{error && <p className="text-red-400 text-xs">{error}</p>}
|
||||
|
||||
<div className="flex gap-3 justify-end">
|
||||
<button type="button" onClick={onClose} className="text-sm text-gray-400 hover:text-gray-200 px-4 py-2">
|
||||
<button type="button" onClick={onClose} className="text-sm text-ink-2 hover:text-ink px-4 py-2">
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
@@ -198,12 +198,12 @@ export default function TripsPage() {
|
||||
</div>
|
||||
|
||||
{loading ? (
|
||||
<p className="text-gray-500 text-sm font-mono">Loading…</p>
|
||||
<p className="text-ink-muted text-sm font-mono">Loading…</p>
|
||||
) : (
|
||||
<>
|
||||
{upcoming.length > 0 && (
|
||||
<section>
|
||||
<h2 className="text-sm font-mono text-gray-400 uppercase tracking-wider mb-3">Upcoming</h2>
|
||||
<h2 className="text-sm font-mono text-ink-2 uppercase tracking-wider mb-3">Upcoming</h2>
|
||||
<div className="space-y-3">
|
||||
{upcoming.map((t) => (
|
||||
<TripCard key={t.trip_id} trip={t} isAdmin={isAdmin} onDelete={handleDelete} />
|
||||
@@ -214,7 +214,7 @@ export default function TripsPage() {
|
||||
|
||||
{past.length > 0 && (
|
||||
<section>
|
||||
<h2 className="text-sm font-mono text-gray-400 uppercase tracking-wider mb-3">Past</h2>
|
||||
<h2 className="text-sm font-mono text-ink-2 uppercase tracking-wider mb-3">Past</h2>
|
||||
<div className="space-y-3">
|
||||
{past.map((t) => (
|
||||
<TripCard key={t.trip_id} trip={t} isAdmin={isAdmin} onDelete={handleDelete} />
|
||||
@@ -224,7 +224,7 @@ export default function TripsPage() {
|
||||
)}
|
||||
|
||||
{trips.length === 0 && (
|
||||
<p className="text-gray-600 text-sm font-mono">No trips yet.</p>
|
||||
<p className="text-ink-muted text-sm font-mono">No trips yet.</p>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user