48d538f94f
Adds the bulk-delete half of #384 — admins can select multiple events from the list and delete them in one batch, gated by re-entering their password. ## Why password confirmation Bulk delete is destructive and irreversible (cascades across 5 DB tables and 3 filesystem paths per event). Re-entering the password matches the pattern already used by /auth/admin/change-password and makes accidental clicks much harder than a plain "type DELETE to confirm" — the muscle-memory required to type your real password is a stronger gate than typing a literal word. ## Changes ### Backend (adminEvents.js) - Extracted the per-event cascade-delete logic into a module-private `deleteEventCascade(eventId, adminContext)` helper. The DELETE /:id route now calls it instead of inlining 60 lines of cascade — same behaviour, no drift between the per-event and bulk paths. - New `POST /admin/events/bulk-delete`. Body: `{ eventIds, password }`. Permission: `events.delete`. - Validates `eventIds` array length (1–100) and that each id is an integer. The 100-cap keeps request time bounded; the per-event cascade touches DB + filesystem so 1000 events at once would risk timing out the request. - Verifies `password` against the calling admin's bcrypt hash via `bcrypt.compare()` (same as /auth/admin/change-password). Wrong password → 401 `{ error, code: 'INVALID_PASSWORD' }` and no events are touched. - Loops via `deleteEventCascade`, returns `{ results: { successful, failed } }` with the same shape as /bulk-archive so the frontend can show partial-failure feedback. - Logs `bulk_delete_completed` activity with totals. ### Frontend - `events.service.ts`: `bulkDeleteEvents(eventIds, password)`. - New `BulkDeleteModal.tsx`. Red/destructive variant of the bulk-archive modal: - Lists the events to be deleted (so the admin can verify). - Password input with show/hide toggle, autofocus, Enter-to-submit. - Inline `passwordError` prop surfaces the 401 INVALID_PASSWORD response without losing the modal state — admin can retry without re-typing the event list. - "Processing" state replaces the form with a spinner + "Deleting N events. This may take a few minutes — please don't close this window." (i18n) so admins know not to abandon the page during a slow operation. - `EventsListPage.tsx`: "Delete Selected" button next to "Archive Selected" in the bulk-actions bar (red-styled to signal danger), bulkDeleteMutation that maps the 401 to the modal's inline error and any other failure to a generic toast. ### i18n 12 new keys under `events.bulkDelete.*` in all 5 locales (en/de/nl/pt/ru): title, warning, password label/placeholder/help, submit, processing, incorrectPassword, successAll, successPartial, errorGeneric, plus `events.deleteSelected` for the button. Hand- written for de; nl/pt/ru should get a native-speaker pass at some point but read naturally. ### Verified - `npx tsc --noEmit` clean - `npx eslint` clean on every touched file (4 pre-existing errors in adminEvents.js for unused vars unrelated to this PR) - All 5 locale JSON files parse cleanly - `node -e "require('./src/routes/adminEvents')"` loads the module Closes the bulk-delete half of #384. The Photos-column half lands separately in PR #387.