diff --git a/frontend/src/pages/admin/EventDetailsPage.tsx b/frontend/src/pages/admin/EventDetailsPage.tsx
index 5c5b3823..d9c58e40 100644
--- a/frontend/src/pages/admin/EventDetailsPage.tsx
+++ b/frontend/src/pages/admin/EventDetailsPage.tsx
@@ -5,7 +5,7 @@ import { useTranslation } from 'react-i18next';
import { toast } from 'react-toastify';
import { useLocalizedDate } from '../../hooks/useLocalizedDate';
-import { Loading } from '../../components/common';
+import { Button, Card, Loading } from '../../components/common';
import { PasswordResetModal, PublishGalleryDialog, SendGalleryEmailDialog, DuplicateEventDialog, EventRenameDialog, AdminGuestsList } from '../../components/admin';
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { eventsService } from '../../services/events.service';
@@ -101,7 +101,7 @@ export const EventDetailsPage: React.FC = () => {
});
// Fetch event details
- const { data: event, isLoading: eventLoading, refetch: refetchEvent } = useQuery({
+ const { data: event, isLoading: eventLoading, isError: eventError, refetch: refetchEvent } = useQuery({
queryKey: ['admin-event', id],
queryFn: () => eventsService.getEvent(parseInt(id!)),
enabled: !!id,
@@ -324,7 +324,7 @@ export const EventDetailsPage: React.FC = () => {
},
});
- if (eventLoading || !event) {
+ if (eventLoading) {
return (
@@ -332,6 +332,19 @@ export const EventDetailsPage: React.FC = () => {
);
}
+ // A 404 (or any settled failure) leaves `event` undefined forever — without
+ // this branch the spinner above never resolved (QA 7.02).
+ if (eventError || !event) {
+ return (
+
+ {t('events.notFound', 'Event not found')}
+
+
+ );
+ }
+
const expiresAtDate = safeParseDate(event.expires_at);
// Timestamp comparison, not truncated whole days (#909): the old
// differenceInDays <= 0 marked events "expired" up to 24h early.
diff --git a/frontend/src/pages/admin/__tests__/eventDetailsNotFound.test.tsx b/frontend/src/pages/admin/__tests__/eventDetailsNotFound.test.tsx
new file mode 100644
index 00000000..621f7cdb
--- /dev/null
+++ b/frontend/src/pages/admin/__tests__/eventDetailsNotFound.test.tsx
@@ -0,0 +1,84 @@
+/**
+ * /admin/events/:id hung on the spinner forever for a nonexistent id
+ * (QA 7.02). The backend returns a clean 404, but the page gated on
+ * `eventLoading || !event`, so once the query settled `event` stayed
+ * undefined and the condition never went false.
+ */
+import React from 'react';
+import { describe, it, expect, vi } from 'vitest';
+import { render, screen, waitFor } from '@testing-library/react';
+import { MemoryRouter, Route, Routes } from 'react-router-dom';
+import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
+
+vi.mock('react-i18next', async () => {
+ const actual = await vi.importActual
('react-i18next');
+ return {
+ ...actual,
+ useTranslation: () => ({
+ t: (k: string, fb?: unknown) => (typeof fb === 'string' ? fb : k),
+ i18n: { language: 'en' },
+ }),
+ };
+});
+
+vi.mock('react-toastify', () => ({ toast: { success: vi.fn(), error: vi.fn(), info: vi.fn() } }));
+
+const getEvent = vi.fn();
+vi.mock('../../../services/events.service', () => ({
+ eventsService: {
+ getEvent: (...args: unknown[]) => getEvent(...args),
+ updateEvent: vi.fn(),
+ deleteEvent: vi.fn(),
+ extendExpiration: vi.fn(),
+ duplicateEvent: vi.fn(),
+ resetPassword: vi.fn(),
+ publishEvent: vi.fn(),
+ renameEvent: vi.fn(),
+ },
+}));
+
+vi.mock('../../../hooks/usePublicSettings', () => ({
+ PUBLIC_SETTINGS_QUERY_KEY: ['public-settings'],
+ usePublicSettings: () => ({ data: {} }),
+}));
+
+vi.mock('../../../contexts/FeatureFlagsContext', () => ({
+ useFeatureFlags: () => ({ flags: {}, isLoading: false }),
+ useFeatureEnabled: () => false,
+}));
+
+vi.mock('../../../contexts/PermissionsContext', () => ({
+ usePermissions: () => ({ hasAnyPermission: () => true, hasPermission: () => true, isLoading: false }),
+}));
+
+import { EventDetailsPage } from '../EventDetailsPage';
+
+function renderPage() {
+ const qc = new QueryClient({ defaultOptions: { queries: { retry: false } } });
+ return render(
+
+
+
+ } />
+ events list } />
+
+
+
+ );
+}
+
+describe('EventDetailsPage 404 handling (QA 7.02)', () => {
+ it('renders a not-found state instead of spinning forever when the event 404s', async () => {
+ getEvent.mockRejectedValue({ response: { status: 404, data: { error: 'Event not found' } } });
+
+ renderPage();
+
+ expect(screen.getByText('events.loadingEventDetails')).toBeInTheDocument();
+
+ await waitFor(() => {
+ expect(screen.getByText('Event not found')).toBeInTheDocument();
+ });
+ expect(screen.queryByText('events.loadingEventDetails')).not.toBeInTheDocument();
+ expect(screen.getByRole('button', { name: 'events.backToEvents' })).toBeInTheDocument();
+ });
+});