Spaces:
Sleeping
Sleeping
File size: 4,842 Bytes
cd99321 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 | import React from 'react'
import { Bell, CheckCheck, Trash2 } from 'lucide-react'
import { useTranslation } from '../i18n'
import PageShell from '../components/Layout/PageShell'
import { Spinner } from '../components/shared/Spinner'
import InAppNotificationItem from '../components/Notifications/InAppNotificationItem.tsx'
import { useInAppNotifications } from './inAppNotifications/useInAppNotifications'
export default function InAppNotificationsPage(): React.ReactElement {
const { t } = useTranslation()
// Page = wiring container: store, filter, fetch + infinite scroll live in the hook.
const {
notifications, unreadCount, total, isLoading, hasMore,
unreadOnly, setUnreadOnly, loaderRef, displayed,
markAllRead, deleteAll,
} = useInAppNotifications()
return (
<PageShell background="var(--bg-primary)">
<div className="max-w-2xl mx-auto px-4 py-8">
{/* Header */}
<div className="flex items-center justify-between mb-6">
<div>
<h1 className="text-xl font-semibold text-content">
{t('notifications.title')}
{unreadCount > 0 && (
<span className="ml-2 px-2 py-0.5 rounded-full text-xs font-medium align-middle inline-flex items-center justify-center bg-content text-surface">
{unreadCount}
</span>
)}
</h1>
<p className="text-sm mt-0.5 text-content-muted">
{total} {total === 1 ? 'notification' : 'notifications'}
</p>
</div>
{/* Bulk actions */}
<div className="flex items-center gap-2">
{unreadCount > 0 && (
<button
onClick={markAllRead}
className="flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-sm transition-colors text-content-secondary bg-surface-hover"
onMouseEnter={e => e.currentTarget.style.background = 'var(--bg-tertiary)'}
onMouseLeave={e => e.currentTarget.style.background = 'var(--bg-hover)'}
>
<CheckCheck className="w-4 h-4" />
<span className="hidden sm:inline">{t('notifications.markAllRead')}</span>
</button>
)}
{notifications.length > 0 && (
<button
onClick={deleteAll}
className="flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-sm transition-colors text-red-500 hover:bg-red-500/10"
>
<Trash2 className="w-4 h-4" />
<span className="hidden sm:inline">{t('notifications.deleteAll')}</span>
</button>
)}
</div>
</div>
{/* Filter toggle */}
<div className="flex gap-2 mb-4">
<button
onClick={() => setUnreadOnly(false)}
className={`px-3 py-1.5 rounded-lg text-sm font-medium transition-colors ${!unreadOnly ? 'bg-content text-surface' : 'bg-surface-hover text-content-secondary'}`}
>
{t('notifications.all')}
</button>
<button
onClick={() => setUnreadOnly(true)}
className={`px-3 py-1.5 rounded-lg text-sm font-medium transition-colors ${unreadOnly ? 'bg-content text-surface' : 'bg-surface-hover text-content-secondary'}`}
>
{t('notifications.unreadOnly')}
</button>
</div>
{/* Notification list */}
<div className="rounded-xl border overflow-hidden border-edge bg-surface-card">
{isLoading && displayed.length === 0 ? (
<div className="flex items-center justify-center py-16">
<Spinner className="w-6 h-6 border-2 border-slate-200 border-t-current" />
</div>
) : displayed.length === 0 ? (
<div className="flex flex-col items-center justify-center py-16 px-4 text-center gap-3">
<Bell className="w-12 h-12 text-content-faint" />
<p className="text-base font-medium text-content-muted">{t('notifications.empty')}</p>
<p className="text-sm text-content-faint">{t('notifications.emptyDescription')}</p>
</div>
) : (
displayed.map(n => (
<InAppNotificationItem key={n.id} notification={n} />
))
)}
{/* Infinite scroll trigger */}
{hasMore && (
<div ref={loaderRef} className="flex items-center justify-center py-4">
{isLoading && <Spinner className="w-5 h-5 border-2 border-slate-200 border-t-current" />}
</div>
)}
</div>
</div>
</PageShell>
)
}
|