refactor(dashboard): replace hardcoded strings with i18n keys

Hero, atlas row, trip cards, filters, currency and timezone widgets now resolve all visible copy through t() instead of hardcoded English/German.
This commit is contained in:
Maurice
2026-05-26 23:25:51 +02:00
parent 69b699c9bf
commit 4ff4435f8b
+51 -45
View File
@@ -300,9 +300,9 @@ export default function DashboardPage(): React.ReactElement {
<h3 className="sec-title">{t('dashboard.title')}</h3> <h3 className="sec-title">{t('dashboard.title')}</h3>
<div className="sec-tools"> <div className="sec-tools">
<div className="seg"> <div className="seg">
<button className={tripFilter === 'planned' ? 'on' : ''} onClick={() => setTripFilter('planned')}>Planned</button> <button className={tripFilter === 'planned' ? 'on' : ''} onClick={() => setTripFilter('planned')}>{t('dashboard.filter.planned')}</button>
<button className={tripFilter === 'archive' ? 'on' : ''} onClick={() => setTripFilter('archive')}>Archive</button> <button className={tripFilter === 'archive' ? 'on' : ''} onClick={() => setTripFilter('archive')}>{t('dashboard.archive')}</button>
<button className={tripFilter === 'completed' ? 'on' : ''} onClick={() => setTripFilter('completed')}>Completed</button> <button className={tripFilter === 'completed' ? 'on' : ''} onClick={() => setTripFilter('completed')}>{t('dashboard.mobile.completed')}</button>
</div> </div>
<button className="tool-action" aria-label="Toggle view" onClick={toggleViewMode} style={{ width: 38, height: 38, borderRadius: 11 }}> <button className="tool-action" aria-label="Toggle view" onClick={toggleViewMode} style={{ width: 38, height: 38, borderRadius: 11 }}>
{viewMode === 'grid' ? <List size={17} /> : <LayoutGrid size={17} />} {viewMode === 'grid' ? <List size={17} /> : <LayoutGrid size={17} />}
@@ -394,6 +394,7 @@ function BoardingPassHero({ trip, bundle, locale, onOpen, onEdit, onCopy, onArch
trip: DashboardTrip; bundle: HeroBundle | null; locale: string; onOpen: () => void trip: DashboardTrip; bundle: HeroBundle | null; locale: string; onOpen: () => void
onEdit: () => void; onCopy: () => void; onArchive: () => void; onDelete: () => void onEdit: () => void; onCopy: () => void; onArchive: () => void; onDelete: () => void
}): React.ReactElement { }): React.ReactElement {
const { t } = useTranslation()
const stop = (e: React.MouseEvent, fn: () => void) => { e.stopPropagation(); fn() } const stop = (e: React.MouseEvent, fn: () => void) => { e.stopPropagation(); fn() }
const status = getTripStatus(trip) const status = getTripStatus(trip)
const start = splitDate(trip.start_date, locale) const start = splitDate(trip.start_date, locale)
@@ -419,12 +420,12 @@ function BoardingPassHero({ trip, bundle, locale, onOpen, onEdit, onCopy, onArch
ringFraction = 0.5 ringFraction = 0.5
} }
countdownNumber = String(daysLeft) countdownNumber = String(daysLeft)
countdownLabel = daysLeft === 0 ? 'Last day' : daysLeft === 1 ? 'Day left' : 'Days left' countdownLabel = daysLeft === 0 ? t('dashboard.hero.lastDay') : daysLeft === 1 ? t('dashboard.hero.dayLeft') : t('dashboard.hero.daysLeft')
} else if (until !== null && until >= 0) { } else if (until !== null && until >= 0) {
// Closer trips fill more of the ring (1-year horizon). // Closer trips fill more of the ring (1-year horizon).
ringFraction = Math.min(1, Math.max(0.04, 1 - until / 365)) ringFraction = Math.min(1, Math.max(0.04, 1 - until / 365))
countdownNumber = String(until) countdownNumber = String(until)
countdownLabel = until === 1 ? 'Day left' : 'Days left' countdownLabel = until === 1 ? t('dashboard.hero.dayLeft') : t('dashboard.hero.daysLeft')
} }
const RING_LEN = 170 const RING_LEN = 170
const dashOffset = RING_LEN * (1 - ringFraction) const dashOffset = RING_LEN * (1 - ringFraction)
@@ -434,11 +435,11 @@ function BoardingPassHero({ trip, bundle, locale, onOpen, onEdit, onCopy, onArch
const buddyCount = trip.shared_count != null ? trip.shared_count + 1 : members.length const buddyCount = trip.shared_count != null ? trip.shared_count + 1 : members.length
const placeCount = trip.place_count || (bundle?.places.length ?? 0) const placeCount = trip.place_count || (bundle?.places.length ?? 0)
const badge = status === 'ongoing' ? 'LIVE NOW' const badge = status === 'ongoing' ? t('dashboard.hero.badgeLive')
: status === 'today' ? 'STARTS TODAY' : status === 'today' ? t('dashboard.hero.badgeToday')
: status === 'tomorrow' ? 'TOMORROW' : status === 'tomorrow' ? t('dashboard.hero.badgeTomorrow')
: status === 'future' ? 'UP NEXT' : status === 'future' ? t('dashboard.hero.badgeNext')
: 'RECENT' : t('dashboard.hero.badgeRecent')
return ( return (
<section className="hero-trip" onClick={onOpen}> <section className="hero-trip" onClick={onOpen}>
@@ -466,7 +467,7 @@ function BoardingPassHero({ trip, bundle, locale, onOpen, onEdit, onCopy, onArch
<div className="hero-pass" onClick={(e) => { e.stopPropagation(); onOpen() }}> <div className="hero-pass" onClick={(e) => { e.stopPropagation(); onOpen() }}>
<div className="pass-cell buddies"> <div className="pass-cell buddies">
<div className="pass-label">Buddies</div> <div className="pass-label">{t('dashboard.members')}</div>
<div className="buddies-avatars"> <div className="buddies-avatars">
{members.slice(0, 4).map((m, i) => ( {members.slice(0, 4).map((m, i) => (
m.avatar_url m.avatar_url
@@ -476,11 +477,11 @@ function BoardingPassHero({ trip, bundle, locale, onOpen, onEdit, onCopy, onArch
{members.length > 4 && <div className="buddy-more">+{members.length - 4}</div>} {members.length > 4 && <div className="buddy-more">+{members.length - 4}</div>}
{members.length === 0 && <div className="buddy-avatar" style={{ background: buddyColor(0) }}>{initials(trip.owner_username)}</div>} {members.length === 0 && <div className="buddy-avatar" style={{ background: buddyColor(0) }}>{initials(trip.owner_username)}</div>}
</div> </div>
<div className="pass-sub">{buddyCount} {buddyCount === 1 ? 'traveler' : 'travelers'}</div> <div className="pass-sub">{buddyCount === 1 ? t('dashboard.hero.travelerOne', { count: buddyCount }) : t('dashboard.hero.travelerMany', { count: buddyCount })}</div>
</div> </div>
<div className="pass-cell dates-combined"> <div className="pass-cell dates-combined">
<div className="pass-label">Trip dates</div> <div className="pass-label">{t('dashboard.hero.tripDates')}</div>
<div className="dates-row"> <div className="dates-row">
{start ? <div className="date-block"><div className="date-num mono">{start.d}</div><div className="date-month">{start.m}</div></div> {start ? <div className="date-block"><div className="date-num mono">{start.d}</div><div className="date-month">{start.m}</div></div>
: <div className="date-block"><div className="date-num"></div></div>} : <div className="date-block"><div className="date-num"></div></div>}
@@ -488,7 +489,7 @@ function BoardingPassHero({ trip, bundle, locale, onOpen, onEdit, onCopy, onArch
{end ? <div className="date-block"><div className="date-num mono">{end.d}</div><div className="date-month">{end.m}</div></div> {end ? <div className="date-block"><div className="date-num mono">{end.d}</div><div className="date-month">{end.m}</div></div>
: <div className="date-block"><div className="date-num"></div></div>} : <div className="date-block"><div className="date-num"></div></div>}
</div> </div>
<div className="pass-sub">{dayCount ? `${dayCount} ${dayCount === 1 ? 'day' : 'days'}` : 'No dates set'}</div> <div className="pass-sub">{dayCount ? `${dayCount} ${dayCount === 1 ? t('dashboard.hero.dayUnitOne') : t('dashboard.hero.dayUnitMany')}` : t('dashboard.hero.noDates')}</div>
</div> </div>
<div className="pass-cell countdown"> <div className="pass-cell countdown">
@@ -511,7 +512,7 @@ function BoardingPassHero({ trip, bundle, locale, onOpen, onEdit, onCopy, onArch
</div> </div>
<div className="pass-cell places"> <div className="pass-cell places">
<div className="pass-label">Places</div> <div className="pass-label">{t('dashboard.places')}</div>
<div className="places-preview"> <div className="places-preview">
{places.slice(0, 4).map(p => ( {places.slice(0, 4).map(p => (
<img key={p.id} className="place-thumb" src={p.image_url as string} alt={p.name} /> <img key={p.id} className="place-thumb" src={p.image_url as string} alt={p.name} />
@@ -519,7 +520,7 @@ function BoardingPassHero({ trip, bundle, locale, onOpen, onEdit, onCopy, onArch
{places.length === 0 && <div className="place-more"><MapPin size={15} /></div>} {places.length === 0 && <div className="place-more"><MapPin size={15} /></div>}
{placeCount > 4 && <div className="place-more">+{placeCount - 4}</div>} {placeCount > 4 && <div className="place-more">+{placeCount - 4}</div>}
</div> </div>
<div className="pass-sub">{placeCount} {placeCount === 1 ? 'destination' : 'destinations'}</div> <div className="pass-sub">{placeCount === 1 ? t('dashboard.hero.destinationOne', { count: placeCount }) : t('dashboard.hero.destinationMany', { count: placeCount })}</div>
</div> </div>
</div> </div>
</div> </div>
@@ -529,6 +530,7 @@ function BoardingPassHero({ trip, bundle, locale, onOpen, onEdit, onCopy, onArch
// ── Atlas / stats row ──────────────────────────────────────────────────────── // ── Atlas / stats row ────────────────────────────────────────────────────────
function AtlasStats({ stats }: { stats: TravelStats | null }): React.ReactElement { function AtlasStats({ stats }: { stats: TravelStats | null }): React.ReactElement {
const { t } = useTranslation()
const countries = stats?.countries || [] const countries = stats?.countries || []
const distanceKm = stats?.totalDistanceKm || 0 const distanceKm = stats?.totalDistanceKm || 0
const distanceText = distanceKm >= 1000 ? `${(distanceKm / 1000).toFixed(1)}k` : String(distanceKm) const distanceText = distanceKm >= 1000 ? `${(distanceKm / 1000).toFixed(1)}k` : String(distanceKm)
@@ -537,8 +539,8 @@ function AtlasStats({ stats }: { stats: TravelStats | null }): React.ReactElemen
return ( return (
<section className="atlas"> <section className="atlas">
<div className="atlas-card passport"> <div className="atlas-card passport">
<div className="label">Atlas · Countries visited</div> <div className="label">{t('dashboard.atlas.countriesVisited')}</div>
<div className="value mono">{countries.length} <span className="unit" style={{ color: 'oklch(1 0 0 / .55)' }}>of 195</span></div> <div className="value mono">{countries.length} <span className="unit" style={{ color: 'oklch(1 0 0 / .55)' }}>{t('dashboard.atlas.ofTotal', { total: 195 })}</span></div>
<div className="passport-flags"> <div className="passport-flags">
{countries.slice(0, 5).map((c, i) => ( {countries.slice(0, 5).map((c, i) => (
<span key={i} className="flag" title={c}> <span key={i} className="flag" title={c}>
@@ -551,27 +553,27 @@ function AtlasStats({ stats }: { stats: TravelStats | null }): React.ReactElemen
</div> </div>
<div className="atlas-card"> <div className="atlas-card">
<div className="label">Trips total</div> <div className="label">{t('dashboard.atlas.tripsTotal')}</div>
<div className="value mono">{stats?.totalTrips ?? 0}</div> <div className="value mono">{stats?.totalTrips ?? 0}</div>
<div className="delta">{stats?.totalPlaces ?? 0} places mapped</div> <div className="delta">{t('dashboard.atlas.placesMapped', { count: stats?.totalPlaces ?? 0 })}</div>
<svg className="spark" width="80" height="36" viewBox="0 0 80 36"> <svg className="spark" width="80" height="36" viewBox="0 0 80 36">
<polyline points="0,30 12,26 22,28 32,18 44,22 56,10 68,14 80,4" fill="none" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" /> <polyline points="0,30 12,26 22,28 32,18 44,22 56,10 68,14 80,4" fill="none" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
</svg> </svg>
</div> </div>
<div className="atlas-card"> <div className="atlas-card">
<div className="label">Days traveled</div> <div className="label">{t('dashboard.atlas.daysTraveled')}</div>
<div className="value mono">{stats?.totalDays ?? 0} <span className="unit">days</span></div> <div className="value mono">{stats?.totalDays ?? 0} <span className="unit">{t('dashboard.atlas.daysUnit')}</span></div>
<div className="delta">across all trips</div> <div className="delta">{t('dashboard.atlas.acrossAllTrips')}</div>
<svg className="spark" width="80" height="36" viewBox="0 0 80 36"> <svg className="spark" width="80" height="36" viewBox="0 0 80 36">
<path d="M0 30 Q10 24 20 26 T40 20 T60 14 T80 10" fill="none" strokeWidth="2" strokeLinecap="round" /> <path d="M0 30 Q10 24 20 26 T40 20 T60 14 T80 10" fill="none" strokeWidth="2" strokeLinecap="round" />
</svg> </svg>
</div> </div>
<div className="atlas-card"> <div className="atlas-card">
<div className="label">Distance flown</div> <div className="label">{t('dashboard.atlas.distanceFlown')}</div>
<div className="value mono">{distanceText} <span className="unit">km</span></div> <div className="value mono">{distanceText} <span className="unit">{t('dashboard.atlas.kmUnit')}</span></div>
<div className="delta"> {equatorTimes}× around the equator</div> <div className="delta">{t('dashboard.atlas.aroundEquator', { count: equatorTimes })}</div>
<svg className="spark" width="80" height="36" viewBox="0 0 80 36"> <svg className="spark" width="80" height="36" viewBox="0 0 80 36">
<circle cx="40" cy="18" r="14" fill="none" stroke="oklch(0.88 0.01 70)" strokeWidth="2" /> <circle cx="40" cy="18" r="14" fill="none" stroke="oklch(0.88 0.01 70)" strokeWidth="2" />
<circle cx="40" cy="18" r="14" fill="none" strokeWidth="2" strokeDasharray="58 88" strokeLinecap="round" transform="rotate(-90 40 18)" /> <circle cx="40" cy="18" r="14" fill="none" strokeWidth="2" strokeDasharray="58 88" strokeLinecap="round" transform="rotate(-90 40 18)" />
@@ -586,18 +588,19 @@ function TripCard({ trip, locale, onOpen, onEdit, onCopy, onArchive, onDelete }:
trip: DashboardTrip; locale: string; onOpen: () => void trip: DashboardTrip; locale: string; onOpen: () => void
onEdit: () => void; onCopy: () => void; onArchive: () => void; onDelete: () => void onEdit: () => void; onCopy: () => void; onArchive: () => void; onDelete: () => void
}): React.ReactElement { }): React.ReactElement {
const { t } = useTranslation()
const status = getTripStatus(trip) const status = getTripStatus(trip)
const start = splitDate(trip.start_date, locale) const start = splitDate(trip.start_date, locale)
const end = splitDate(trip.end_date, locale) const end = splitDate(trip.end_date, locale)
const until = daysUntil(trip.start_date) const until = daysUntil(trip.start_date)
const statusClass = status === 'ongoing' ? '' : status === 'past' ? 'completed' : status === 'future' || status === 'today' || status === 'tomorrow' ? 'upcoming' : 'idea' const statusClass = status === 'ongoing' ? '' : status === 'past' ? 'completed' : status === 'future' || status === 'today' || status === 'tomorrow' ? 'upcoming' : 'idea'
const statusLabel = status === 'ongoing' ? 'Live now' const statusLabel = status === 'ongoing' ? t('dashboard.mobile.liveNow')
: status === 'today' ? 'Today' : status === 'today' ? t('dashboard.status.today')
: status === 'tomorrow' ? 'Tomorrow' : status === 'tomorrow' ? t('dashboard.status.tomorrow')
: status === 'future' && until !== null ? (until > 60 ? `In ${Math.round(until / 30)} months` : `In ${until} days`) : status === 'future' && until !== null ? (until > 60 ? t('dashboard.mobile.inMonths', { count: Math.round(until / 30) }) : t('dashboard.mobile.inDays', { count: until }))
: status === 'past' ? 'Completed' : status === 'past' ? t('dashboard.mobile.completed')
: 'Idea' : t('dashboard.card.idea')
const stop = (e: React.MouseEvent, fn: () => void) => { e.stopPropagation(); fn() } const stop = (e: React.MouseEvent, fn: () => void) => { e.stopPropagation(); fn() }
@@ -626,12 +629,12 @@ function TripCard({ trip, locale, onOpen, onEdit, onCopy, onArchive, onDelete }:
<span className="date-arrow"><ArrowRight size={11} /></span> <span className="date-arrow"><ArrowRight size={11} /></span>
<span className="date-num">{end.m} {end.d}</span> <span className="date-num">{end.m} {end.d}</span>
</> </>
) : <span>No dates set</span>} ) : <span>{t('dashboard.hero.noDates')}</span>}
</div> </div>
<div className="trip-meta" style={{ gridTemplateColumns: 'repeat(3, 1fr)' }}> <div className="trip-meta" style={{ gridTemplateColumns: 'repeat(3, 1fr)' }}>
<div><span className="n mono">{trip.day_count ?? 0}</span><span className="k">Days</span></div> <div><span className="n mono">{trip.day_count ?? 0}</span><span className="k">{t('dashboard.days')}</span></div>
<div><span className="n mono">{trip.place_count ?? 0}</span><span className="k">Places</span></div> <div><span className="n mono">{trip.place_count ?? 0}</span><span className="k">{t('dashboard.places')}</span></div>
<div><span className="n mono">{trip.shared_count ?? 0}</span><span className="k">{trip.shared_count === 1 ? 'Buddy' : 'Buddies'}</span></div> <div><span className="n mono">{trip.shared_count ?? 0}</span><span className="k">{trip.shared_count === 1 ? t('dashboard.card.buddyOne') : t('dashboard.members')}</span></div>
</div> </div>
</div> </div>
</article> </article>
@@ -642,6 +645,7 @@ function TripCard({ trip, locale, onOpen, onEdit, onCopy, onArchive, onDelete }:
const FX_FALLBACK = ['EUR', 'USD', 'GBP', 'CHF', 'JPY', 'CAD', 'AUD', 'CNY', 'SEK', 'NOK', 'DKK', 'PLN', 'CZK', 'HUF', 'TRY', 'THB', 'INR', 'BRL', 'MXN', 'ZAR'] const FX_FALLBACK = ['EUR', 'USD', 'GBP', 'CHF', 'JPY', 'CAD', 'AUD', 'CNY', 'SEK', 'NOK', 'DKK', 'PLN', 'CZK', 'HUF', 'TRY', 'THB', 'INR', 'BRL', 'MXN', 'ZAR']
function CurrencyTool(): React.ReactElement { function CurrencyTool(): React.ReactElement {
const { t } = useTranslation()
const [from, setFrom] = useState(() => localStorage.getItem('trek_fx_from') || 'EUR') const [from, setFrom] = useState(() => localStorage.getItem('trek_fx_from') || 'EUR')
const [to, setTo] = useState(() => localStorage.getItem('trek_fx_to') || 'USD') const [to, setTo] = useState(() => localStorage.getItem('trek_fx_to') || 'USD')
const [amount, setAmount] = useState('100') const [amount, setAmount] = useState('100')
@@ -667,24 +671,24 @@ function CurrencyTool(): React.ReactElement {
return ( return (
<div className="tool"> <div className="tool">
<div className="tool-head"> <div className="tool-head">
<div className="tool-title"><RefreshCw size={14} /> Currency</div> <div className="tool-title"><RefreshCw size={14} /> {t('dashboard.currency')}</div>
<button className="tool-action" aria-label="Refresh" onClick={fetchRate}><RefreshCw size={14} /></button> <button className="tool-action" aria-label="Refresh" onClick={fetchRate}><RefreshCw size={14} /></button>
</div> </div>
<div className="fx-input"> <div className="fx-input">
<div className="fx-field"> <div className="fx-field">
<div className="lbl">From</div> <div className="lbl">{t('dashboard.fx.from')}</div>
<input className="amt mono" value={amount} onChange={e => setAmount(e.target.value)} inputMode="decimal" /> <input className="amt mono" value={amount} onChange={e => setAmount(e.target.value)} inputMode="decimal" />
<CustomSelect value={from} onChange={setFrom} options={ccyOptions} searchable size="sm" style={{ marginTop: 6 }} /> <CustomSelect value={from} onChange={setFrom} options={ccyOptions} searchable size="sm" style={{ marginTop: 6 }} />
</div> </div>
<button className="fx-swap" aria-label="Swap" onClick={swap}><ArrowRightLeft size={14} /></button> <button className="fx-swap" aria-label="Swap" onClick={swap}><ArrowRightLeft size={14} /></button>
<div className="fx-field"> <div className="fx-field">
<div className="lbl">To</div> <div className="lbl">{t('dashboard.fx.to')}</div>
<input className="amt mono" value={converted != null ? converted.toFixed(2) : '—'} readOnly /> <input className="amt mono" value={converted != null ? converted.toFixed(2) : '—'} readOnly />
<CustomSelect value={to} onChange={setTo} options={ccyOptions} searchable size="sm" style={{ marginTop: 6 }} /> <CustomSelect value={to} onChange={setTo} options={ccyOptions} searchable size="sm" style={{ marginTop: 6 }} />
</div> </div>
</div> </div>
<div className="fx-rate"> <div className="fx-rate">
<span>{rate != null ? `1 ${from} = ${rate.toFixed(4)} ${to}` : 'Rate unavailable'}</span> <span>{rate != null ? `1 ${from} = ${rate.toFixed(4)} ${to}` : t('dashboard.fx.unavailable')}</span>
</div> </div>
</div> </div>
) )
@@ -707,6 +711,7 @@ function shortZone(tz: string): string {
} }
function TimezoneTool({ locale }: { locale: string }): React.ReactElement { function TimezoneTool({ locale }: { locale: string }): React.ReactElement {
const { t } = useTranslation()
const home = Intl.DateTimeFormat().resolvedOptions().timeZone const home = Intl.DateTimeFormat().resolvedOptions().timeZone
const [now, setNow] = useState(() => new Date()) const [now, setNow] = useState(() => new Date())
const [zones, setZones] = useState<string[]>(() => { const [zones, setZones] = useState<string[]>(() => {
@@ -747,14 +752,14 @@ function TimezoneTool({ locale }: { locale: string }): React.ReactElement {
return ( return (
<div className="tool"> <div className="tool">
<div className="tool-head"> <div className="tool-head">
<div className="tool-title"><Clock size={14} /> Timezones</div> <div className="tool-title"><Clock size={14} /> {t('dashboard.timezone')}</div>
<button className="tool-action" aria-label="Add timezone" onClick={() => setAdding(a => !a)}> <button className="tool-action" aria-label="Add timezone" onClick={() => setAdding(a => !a)}>
{adding ? <X size={14} /> : <Plus size={14} />} {adding ? <X size={14} /> : <Plus size={14} />}
</button> </button>
</div> </div>
{adding && ( {adding && (
<div style={{ marginBottom: 14 }}> <div style={{ marginBottom: 14 }}>
<CustomSelect value="" onChange={addZone} options={tzOptions} searchable size="sm" placeholder="Zeitzone suchen…" /> <CustomSelect value="" onChange={addZone} options={tzOptions} searchable size="sm" placeholder={t('dashboard.tz.searchPlaceholder')} />
</div> </div>
)} )}
<div className="tz-list"> <div className="tz-list">
@@ -770,7 +775,7 @@ function TimezoneTool({ locale }: { locale: string }): React.ReactElement {
</div> </div>
))} ))}
{zones.length === 0 && ( {zones.length === 0 && (
<div className="tz-empty">Noch keine weiteren Zeitzonen über + hinzufügen</div> <div className="tz-empty">{t('dashboard.tz.empty')}</div>
)} )}
</div> </div>
</div> </div>
@@ -781,13 +786,14 @@ function TimezoneTool({ locale }: { locale: string }): React.ReactElement {
function UpcomingTool({ items, locale, onOpen }: { function UpcomingTool({ items, locale, onOpen }: {
items: UpcomingReservation[]; locale: string; onOpen: (tripId: number) => void items: UpcomingReservation[]; locale: string; onOpen: (tripId: number) => void
}): React.ReactElement { }): React.ReactElement {
const { t } = useTranslation()
return ( return (
<div className="tool"> <div className="tool">
<div className="tool-head"> <div className="tool-head">
<div className="tool-title"><Calendar size={14} /> Upcoming reservations</div> <div className="tool-title"><Calendar size={14} /> {t('dashboard.upcoming.title')}</div>
</div> </div>
{items.length === 0 ? ( {items.length === 0 ? (
<div style={{ fontSize: 13, color: 'var(--ink-3)' }}>Nothing booked yet.</div> <div style={{ fontSize: 13, color: 'var(--ink-3)' }}>{t('dashboard.upcoming.empty')}</div>
) : ( ) : (
<div className="upc-list"> <div className="upc-list">
{items.map(r => { {items.map(r => {