diff --git a/client/src/pages/DashboardPage.test.tsx b/client/src/pages/DashboardPage.test.tsx
index d87279808..88f93cc49 100644
--- a/client/src/pages/DashboardPage.test.tsx
+++ b/client/src/pages/DashboardPage.test.tsx
@@ -1,5 +1,5 @@
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
-import { render, screen, waitFor } from '../../tests/helpers/render';
+import { render, screen, waitFor, within } from '../../tests/helpers/render';
import userEvent from '@testing-library/user-event';
import { http, HttpResponse } from 'msw';
import { server } from '../../tests/helpers/msw/server';
@@ -943,4 +943,61 @@ describe('DashboardPage', () => {
expect(useSettingsStore.getState().settings.dashboard_timezones).toBeUndefined();
});
});
+
+ describe('FE-PAGE-DASH-035: A trip the hero fell back to still appears in the grid (#1706)', () => {
+ const onlyTrips = (trips: unknown[]) =>
+ server.use(
+ http.get('/api/trips', ({ request }) => {
+ const url = new URL(request.url);
+ if (url.searchParams.get('archived')) return HttpResponse.json({ trips: [] });
+ return HttpResponse.json({ trips });
+ }),
+ );
+ // The grid, not the hero — the hero renders the same title and would mask the bug.
+ const grid = () => document.querySelector('.trips') as HTMLElement;
+
+ it('lists a finished trip under Completed when it is the only one', async () => {
+ onlyTrips([buildTrip({ title: 'Lisbon 2025', start_date: '2025-05-01', end_date: '2025-05-08' })]);
+ const user = userEvent.setup();
+ render();
+
+ await user.click(screen.getByText('Completed'));
+
+ await waitFor(() => expect(within(grid()).getByText('Lisbon 2025')).toBeInTheDocument());
+ });
+
+ it('lists a dateless trip under Planned when it is the only one', async () => {
+ onlyTrips([buildTrip({ title: 'Someday Iceland', start_date: null, end_date: null })]);
+ render();
+
+ await waitFor(() => expect(within(grid()).getByText('Someday Iceland')).toBeInTheDocument());
+ expect(screen.queryByText('No trips yet')).not.toBeInTheDocument();
+ });
+
+ it('does not claim "No trips yet" when the only trip is simply finished', async () => {
+ onlyTrips([buildTrip({ title: 'Lisbon 2025', start_date: '2025-05-01', end_date: '2025-05-08' })]);
+ render();
+
+ await waitFor(() => expect(screen.getAllByText('Lisbon 2025').length).toBeGreaterThan(0));
+ expect(screen.queryByText('No trips yet')).not.toBeInTheDocument();
+ });
+
+ it('still says "No trips yet" for an account with no trips at all', async () => {
+ onlyTrips([]);
+ render();
+
+ await waitFor(() => expect(screen.getByText('No trips yet')).toBeInTheDocument());
+ });
+
+ it('still keeps a running trip out of the grid, so the hero does not double up', async () => {
+ onlyTrips([
+ buildTrip({ title: 'Paris Adventure', start_date: '2026-07-01', end_date: '2026-07-10' }),
+ buildTrip({ title: 'Lisbon 2025', start_date: '2025-05-01', end_date: '2025-05-08' }),
+ ]);
+ render();
+
+ await waitFor(() => expect(screen.getAllByText('Paris Adventure').length).toBeGreaterThan(0));
+ expect(within(grid()).queryByText('Paris Adventure')).not.toBeInTheDocument();
+ });
+ });
});
diff --git a/client/src/pages/DashboardPage.tsx b/client/src/pages/DashboardPage.tsx
index 1120ba024..04f6a4279 100644
--- a/client/src/pages/DashboardPage.tsx
+++ b/client/src/pages/DashboardPage.tsx
@@ -220,7 +220,10 @@ function DashboardPageDesktop(): React.ReactElement {
/>
)}
- {gridTrips.length === 0 && tripFilter === 'planned' && !isLoading && !loadError && (
+ {/* "No trips yet" only when there really are none — a user whose trips are
+ all finished has a hero, and telling them to create their first trip is
+ simply wrong (#1706). Same condition the mobile dashboard already uses. */}
+ {gridTrips.length === 0 && !spotlight && tripFilter === 'planned' && !isLoading && !loadError && (
diff --git a/client/src/pages/dashboard/useDashboard.ts b/client/src/pages/dashboard/useDashboard.ts
index c0223b59a..478ec9b89 100644
--- a/client/src/pages/dashboard/useDashboard.ts
+++ b/client/src/pages/dashboard/useDashboard.ts
@@ -93,11 +93,17 @@ export function useDashboard() {
}
const today = new Date().toISOString().split('T')[0]
- const spotlight = trips.find(t => t.start_date && t.end_date && t.start_date <= today && t.end_date >= today)
+ // A trip the hero features on its own merit: one that is running, else the next
+ // one coming up. Only that one is taken out of the grid below, so the same trip
+ // isn't shown twice.
+ const featured = trips.find(t => t.start_date && t.end_date && t.start_date <= today && t.end_date >= today)
|| trips.find(t => t.start_date && t.start_date >= today)
- || trips[0]
|| null
- const rest = spotlight ? trips.filter(t => t.id !== spotlight.id) : trips
+ // With neither, the hero still shows something rather than sitting empty — but
+ // that trip is only borrowed for the header and must stay in the grid, or a user
+ // whose trips are all finished (or undated) sees "No trips yet". #1706
+ const spotlight = featured || trips[0] || null
+ const rest = featured ? trips.filter(t => t.id !== featured.id) : trips
// Pull the spotlight trip's members + places so the boarding pass can show
// real buddies and place thumbnails instead of placeholders.