diff --git a/client/src/components/Trips/TripFormModal.test.tsx b/client/src/components/Trips/TripFormModal.test.tsx index ad8346253..ce298903a 100644 --- a/client/src/components/Trips/TripFormModal.test.tsx +++ b/client/src/components/Trips/TripFormModal.test.tsx @@ -854,14 +854,13 @@ describe('TripFormModal', () => { render(); const zone = screen.getByRole('button', { name: /Add cover image/i }); + // Dragging over highlights the zone in the user's accent. This used to be + // four handlers writing element.style, which is how a hard-coded indigo + // outlived the switch to a configurable accent. fireEvent.dragOver(zone); - expect(zone.style.borderColor).toBe('rgb(99, 102, 241)'); + expect(zone.className).toContain('border-accent'); fireEvent.dragLeave(zone); - expect(zone.style.borderColor).toBe('rgb(229, 231, 235)'); - fireEvent.mouseEnter(zone); - expect(zone.style.color).toBe('rgb(107, 114, 128)'); - fireEvent.mouseLeave(zone); - expect(zone.style.color).toBe('rgb(156, 163, 175)'); + expect(zone.className).not.toContain('border-accent'); fireEvent.drop(zone, { dataTransfer: { files: [new File(['x'], 'notes.txt', { type: 'text/plain' })] } }); expect(createObjectURL).not.toHaveBeenCalled(); diff --git a/client/src/components/Trips/TripFormModal.tsx b/client/src/components/Trips/TripFormModal.tsx index a49d4cc09..d3068bdd9 100644 --- a/client/src/components/Trips/TripFormModal.tsx +++ b/client/src/components/Trips/TripFormModal.tsx @@ -70,6 +70,10 @@ export default function TripFormModal({ isOpen, onClose, onSave, trip, onCoverUp const [coverSearchResults, setCoverSearchResults] = useState([]) const [coverSearchError, setCoverSearchError] = useState('') const [searchingCover, setSearchingCover] = useState(false) + // Drives the drop zone's hover look. Used to be four handlers writing + // element.style directly, which is how the indigo dragover colour survived + // the move to a configurable accent. + const [coverDragActive, setCoverDragActive] = useState(false) const [allUsers, setAllUsers] = useState<{ id: number; username: string }[]>([]) const [selectedMembers, setSelectedMembers] = useState([]) const [existingMembers, setExistingMembers] = useState<{ id: number; username: string }[]>([]) @@ -335,39 +339,51 @@ export default function TripFormModal({ isOpen, onClose, onSave, trip, onCoverUp return next }) - const inputCls = "w-full px-3 py-2.5 border border-slate-200 rounded-lg text-slate-900 placeholder-slate-400 focus:outline-none focus:ring-2 focus:ring-slate-300 focus:border-transparent text-sm" + /* The dashboard's shapes, in the app's own tokens. + `.trek-dash` deliberately scopes its palette to that page, so none of its + variables are reachable from here — what carries over is the geometry: + generous radii, grouped panels instead of a single stack of labelled + fields, and overline captions rather than sentence-case labels. */ + const inputCls = "w-full px-3.5 py-2.5 border border-edge rounded-xl bg-surface-input text-content placeholder:text-content-faint focus:outline-none focus:ring-2 focus:ring-accent/30 focus:border-accent text-body transition-shadow" + const labelCls = "flex items-center gap-1 text-caption font-semibold uppercase tracking-[0.14em] text-content-faint mb-2" + const ghostBtnCls = "px-4 py-2.5 text-body font-medium text-content-secondary hover:text-content border border-edge rounded-xl hover:bg-surface-hover transition-colors" + const primaryBtnCls = "px-5 py-2.5 text-body font-medium bg-accent hover:bg-accent-hover disabled:opacity-50 text-accent-text rounded-xl shadow-card transition-colors flex items-center gap-2" + /* Two columns once there is room for them: the form had grown to eight stacked + blocks and the create button sat a full screen below the title. Left is what + the trip looks like, right is when and with whom. One column below md. */ + const columnCls = "flex flex-col gap-4" + const panelCls = "rounded-2xl border border-edge bg-surface-secondary p-4 space-y-3.5" return ( {pendingDateShift ? ( <> - ) : ( <> - - @@ -378,57 +394,66 @@ export default function TripFormModal({ isOpen, onClose, onSave, trip, onCoverUp {pendingDateShift && (
{error && ( -
{error}
+
{error}
)} -

{t('dashboard.dateShiftIntro')}

+

{t('dashboard.dateShiftIntro')}

{([ { mode: 'keep_bookings' as DateShiftMode, label: t('dashboard.dateShiftKeepBookings'), desc: t('dashboard.dateShiftKeepBookingsDesc') }, { mode: 'shift_all' as DateShiftMode, label: t('dashboard.dateShiftAll'), desc: t('dashboard.dateShiftAllDesc') }, ]).map(({ mode, label, desc }) => ( ))} -

{t('dashboard.dateShiftHint')}

+

{t('dashboard.dateShiftHint')}

)}
{error && ( -
{error}
+
{error}
)} +
+ + {/* Left column — what the trip is: its picture, its name, what it is about. */} +
+ {/* Cover image — gated by trip_cover_upload permission */} - {canUploadCover &&
- - + {canUploadCover &&
+ + {coverPreview ? ( -
- -
+
+ +
+ {/* Chrome sitting on top of a photo, so it is deliberately dark in + both themes rather than following the surface tokens. */} -
) : ( )} @@ -442,12 +467,12 @@ export default function TripFormModal({ isOpen, onClose, onSave, trip, onCoverUp className={inputCls} />
- {coverSearchError &&

{coverSearchError}

} + {coverSearchError &&

{coverSearchError}

} {coverSearchResults.length > 0 && (
{coverSearchResults.map(photo => ( @@ -456,11 +481,11 @@ export default function TripFormModal({ isOpen, onClose, onSave, trip, onCoverUp key={photo.id} onClick={() => handleUnsplashSelect(photo)} aria-label={t('dashboard.useUnsplashPhoto', { photographer: photo.photographer || 'Unsplash' })} - className={`relative h-20 overflow-hidden rounded-lg border transition-colors ${coverPreview === photo.url ? 'border-slate-900 ring-2 ring-slate-900/20' : 'border-slate-200 hover:border-slate-400'}`} + className={`relative h-20 overflow-hidden rounded-xl border transition-colors ${coverPreview === photo.url ? 'border-accent ring-2 ring-accent/20' : 'border-edge hover:border-content-faint'}`} > {photo.description {photo.photographer && ( - + {photo.photographer} )} @@ -470,31 +495,40 @@ export default function TripFormModal({ isOpen, onClose, onSave, trip, onCoverUp )}
} -
- - canEditTrip && update('title', e.target.value)} - required readOnly={!canEditTrip} placeholder={t('dashboard.tripTitlePlaceholder')} className={inputCls} /> -
- -
- -