feat(notifications): surface guest activity in the admin bell (#849)
* feat(notifications): surface guest activity in the admin bell (#746) Favorites already reached activity_logs (feedbackService), but gallery opens and downloads only landed in access_logs — invisible in the notification bell. Now: - gallery_opened on the guest photo-list route, debounced in-memory to one notification per event per 6h (the endpoint fires per page load; per-hit notifications would spam the bell). Slideshow traffic stays excluded, matching the analytics exclusion. - gallery_downloaded on all four download paths (streamed + pre-zipped + presigned download-all, download-selected) with scope metadata. - Frontend: locale entries for galleryOpened/galleryDownloaded (and photoFavorite, which previously fell through to the generic 'system activity' line) in all 8 languages — resolved via the existing smart camelCase fallback, no switch cases needed. Distinct bell icons per type. * fix(notifications): single-photo download activity + render per-type bell icons (codex review of #849) - The per-photo Save route (GET /:slug/download/:photoId) only wrote to access_logs — the most common download path never reached the bell. Now emits gallery_downloaded with scope 'single', debounced to one notification per event per hour: a guest saving 30 photos is one signal, not thirty (exact counts stay in access_logs/analytics). - getNotificationStyle's icon names were dead — AdminHeader hard-coded <Bell> for every row. Added an icon map so gallery opens (Eye), downloads (Download), favorites (Heart) and the pre-existing style names render their intended icons. * fix(notifications): notify after successful delivery, complete the icon map (codex review of #849, round 2) - Single-photo notification now fires on res 'finish' with status < 400: emitting up-front logged downloads that then 404ed/failed AND burned the 1h debounce window against the next real download. - Icon map completed over every name getNotificationStyle returns (grep-verified) — settings/user/mail/etc. styles render their declared icons instead of falling back to Bell. Deliberately NOT taken from the review: DB-backed debounce state for multi-worker deployments. The backend's current deployment contract is single-process (no PM2 cluster in-repo; multi-replica explicitly parked in #799 — chunked-upload/session state is process-local for the same reason). Worst case under a future multi-worker setup is N notifications per window, which degrades, not breaks; a shared-store debounce belongs to the #799 phase-3 work. * fix(notifications): attribute client sessions, log cached-ZIP after finish, add Trash2 icon (codex review of #849, round 3) - gallery_opened/gallery_downloaded now carry the real actor: client sessions (accessLevel 'client') are recorded as 'customer' instead of being mislabeled 'guest' — #746 explicitly covers client activity, so they are attributed, not excluded. - Cached-ZIP streaming path logs on res 'finish' (< 400) like the single-photo path — piping is not delivery. The presigned-redirect and on-the-fly-archiver paths keep their existing timing (redirect handoff / post-finalize). - Trash2 added to the icon map (customer_erased, bulk_delete_completed no longer fall back to Bell — the grep that built the map missed the digit in the name). * fix(notifications): dashboard formatting, portal dedup, actor-aware wording, archiver finish-hooks (codex review of #849, confirmation round) - activity_logs feed TWO surfaces: the dashboard's Recent Activity used admin.activities.<type> keys that didn't exist, rendering raw identifiers — added gallery_opened/gallery_downloaded entries in all 8 locales. - Customer-portal opens already log customer_event_access at the access-token mint; the ensuing /photos call no longer double-notifies (client sessions surface via downloads only). - gallery_downloaded formatting is actor-aware: customer sessions render 'Customer downloaded…' (new galleryDownloadedCustomer key ×8) instead of 'A guest…'. - Both on-the-fly ZIP paths (download-all fallback + download-selected) notify on res 'finish' < 400 — archive.finalize() ends Archiver's input, not the HTTP transfer. * fix(notifications): key customer dedup/attribution on portal provenance, neutral favorite wording (codex review of #849, final round) The previous dedup was inverted: portal-minted tokens carry via:'customer' but NO accessLevel (they run as guest), while PIN-client logins carry accessLevel:'client' and log nothing else. So PIN clients' only open signal was suppressed while portal opens still double- notified and portal downloads read as guest activity. verifyGalleryAccess now surfaces req.viaCustomer; gallery_opened dedups on THAT (portal only), and galleryActor treats via-customer OR accessLevel-client as 'customer'. photoFavorite wording is actor-neutral across all 8 locales — feedbackService logs favorites without an actor, so claiming 'a guest' was wrong for customer favorites.
This commit is contained in:
@@ -1,6 +1,17 @@
|
||||
import React, { useState, useRef, useEffect } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { Menu, User, LogOut, Settings, Bell, Lock, CheckCircle, Trash2, Sun, Moon, Globe, ChevronDown } from 'lucide-react';
|
||||
import { Menu, User, LogOut, Settings, Bell, Lock, CheckCircle, Trash2, Sun, Moon, Globe, ChevronDown, Eye, Download, Heart, Calendar, Image, Archive, AlertCircle, Clock, Database, FileText, Folder, Key, Mail, Tag, ToggleRight, UserCog, Webhook } from 'lucide-react';
|
||||
|
||||
// getNotificationStyle returns an icon NAME — map the ones we render to
|
||||
// components; anything unmapped keeps the Bell (codex review of #849:
|
||||
// the styles existed but every row hard-coded <Bell>).
|
||||
// Complete over getNotificationStyle's icon names (grep-verified) so every
|
||||
// style renders its declared icon (codex review of #849 round 2).
|
||||
const NOTIFICATION_ICONS: Record<string, React.ComponentType<{ className?: string }>> = {
|
||||
Bell, Eye, Download, Heart, Calendar, Image, Archive, AlertCircle, Clock, Lock,
|
||||
CheckCircle, Database, FileText, Folder, Globe, Key, LogOut, Mail, Settings,
|
||||
Tag, ToggleRight, Trash2, User, UserCog, Webhook,
|
||||
};
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useLocalizedDate } from '../../hooks/useLocalizedDate';
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
@@ -350,7 +361,10 @@ export const AdminHeader: React.FC<AdminHeaderProps> = ({ onMenuClick }) => {
|
||||
>
|
||||
<div className="flex items-start gap-3">
|
||||
<div className={`mt-0.5 ${style.color}`}>
|
||||
<Bell className="w-4 h-4" />
|
||||
{(() => {
|
||||
const Icon = NOTIFICATION_ICONS[style.icon] || Bell;
|
||||
return <Icon className="w-4 h-4" />;
|
||||
})()}
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="text-sm text-neutral-900 dark:text-neutral-100">
|
||||
|
||||
Reference in New Issue
Block a user