+
+
{spotlight && (
@@ -304,10 +323,10 @@ export default function DashboardPage(): React.ReactElement {
-
@@ -395,45 +414,36 @@ function BoardingPassHero({ trip, bundle, locale, onOpen, onEdit, onCopy, onArch
onEdit: () => void; onCopy: () => void; onArchive: () => void; onDelete: () => void
}): React.ReactElement {
const { t } = useTranslation()
+ const mobile = useIsMobile()
const stop = (e: React.MouseEvent, fn: () => void) => { e.stopPropagation(); fn() }
const status = getTripStatus(trip)
const start = splitDate(trip.start_date, locale)
const end = splitDate(trip.end_date, locale)
- const dayCount = trip.day_count || (trip.start_date && trip.end_date
- ? Math.round((new Date(trip.end_date).getTime() - new Date(trip.start_date).getTime()) / MS_PER_DAY) + 1
- : null)
+ // Countdown cell — plain text in the same style as the trip-dates cell:
+ // days remaining while the trip runs, days until departure before it starts.
const until = daysUntil(trip.start_date)
const ongoing = status === 'ongoing'
- let ringFraction = 0
+ let countdownTop = ''
let countdownNumber = ''
let countdownLabel = ''
if (ongoing && trip.end_date) {
const todayMid = new Date(); todayMid.setHours(0, 0, 0, 0)
const endMid = new Date(trip.end_date + 'T00:00:00')
const daysLeft = Math.max(0, Math.round((endMid.getTime() - todayMid.getTime()) / MS_PER_DAY))
- // Ring tracks progress through the trip; the number shows what's left.
- if (trip.start_date && dayCount) {
- const elapsed = Math.round((Date.now() - new Date(trip.start_date + 'T00:00:00').getTime()) / MS_PER_DAY) + 1
- ringFraction = Math.min(1, Math.max(0.04, elapsed / dayCount))
- } else {
- ringFraction = 0.5
- }
+ countdownTop = t('dashboard.status.ongoing')
countdownNumber = String(daysLeft)
countdownLabel = daysLeft === 0 ? t('dashboard.hero.lastDay') : daysLeft === 1 ? t('dashboard.hero.dayLeft') : t('dashboard.hero.daysLeft')
} else if (until !== null && until >= 0) {
- // Closer trips fill more of the ring (1-year horizon).
- ringFraction = Math.min(1, Math.max(0.04, 1 - until / 365))
+ countdownTop = t('dashboard.hero.startsIn')
countdownNumber = String(until)
- countdownLabel = until === 1 ? t('dashboard.hero.dayLeft') : t('dashboard.hero.daysLeft')
+ countdownLabel = until === 1 ? t('dashboard.hero.dayUnitOne') : t('dashboard.hero.dayUnitMany')
}
- const RING_LEN = 170
- const dashOffset = RING_LEN * (1 - ringFraction)
const members = bundle?.members || []
- const places = (bundle?.places || []).filter(p => p.image_url)
+ const places = bundle?.places || []
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 || places.length
const badge = status === 'ongoing' ? t('dashboard.hero.badgeLive')
: status === 'today' ? t('dashboard.hero.badgeToday')
@@ -441,7 +451,61 @@ function BoardingPassHero({ trip, bundle, locale, onOpen, onEdit, onCopy, onArch
: status === 'future' ? t('dashboard.hero.badgeNext')
: t('dashboard.hero.badgeRecent')
+ const passCells = (
+ <>
+
+
{t('dashboard.members')}
+
+ {members.slice(0, 4).map((m, i) => (
+ m.avatar_url
+ ?

+ :
{initials(m.username)}
+ ))}
+ {members.length > 4 &&
+{members.length - 4}
}
+ {members.length === 0 &&
{initials(trip.owner_username)}
}
+
+
{buddyCount === 1 ? t('dashboard.hero.travelerOne', { count: buddyCount }) : t('dashboard.hero.travelerMany', { count: buddyCount })}
+
+
+
+
{t('dashboard.hero.tripDates')}
+
+ {start ?
+ :
}
+
+ {end ?
+ :
}
+
+
+
+
+ {countdownNumber && (
+ <>
+
{countdownTop}
+
{countdownNumber}
+
{countdownLabel}
+ >
+ )}
+
+
+
+
{t('dashboard.places')}
+
+ {places.slice(0, 3).map(p => (
+
+ ))}
+ {places.length === 0 &&
}
+ {places.length > 3 &&
+{places.length - 3}
}
+
+
{placeCount === 1 ? t('dashboard.hero.destinationOne', { count: placeCount }) : t('dashboard.hero.destinationMany', { count: placeCount })}
+
+ >
+ )
+
return (
+ <>
{trip.cover_image
?
@@ -454,10 +518,10 @@ function BoardingPassHero({ trip, bundle, locale, onOpen, onEdit, onCopy, onArch
{badge}