MichaelEdou
Initial commit — ICC Interac Manager full-stack app
149698e
import { useTranslation } from 'react-i18next';
import { CheckCircle, AlertTriangle, Store, Monitor, X } from 'lucide-react';
import { cn } from '@/lib/utils';
interface Notification {
id: string;
type: 'success' | 'warning' | 'info' | 'system';
titleKey: string;
descriptionKey: string;
timeKey: string;
isRead: boolean;
actions?: { labelKey: string; variant: 'primary' | 'secondary' | 'warning' }[];
}
const todayNotifications: Notification[] = [
{
id: '1',
type: 'success',
titleKey: 'notifications.scanComplete',
descriptionKey: 'notifications.scanCompleteDesc',
timeKey: 'notifications.time.2mAgo',
isRead: false,
actions: [{ labelKey: 'notifications.viewResults', variant: 'primary' }],
},
{
id: '2',
type: 'warning',
titleKey: 'notifications.parsingError',
descriptionKey: 'notifications.parsingErrorDesc',
timeKey: 'notifications.time.15mAgo',
isRead: false,
actions: [
{ labelKey: 'notifications.ignore', variant: 'secondary' },
{ labelKey: 'notifications.fix', variant: 'warning' },
],
},
{
id: '3',
type: 'info',
titleKey: 'notifications.newBranch',
descriptionKey: 'notifications.newBranchDesc',
timeKey: 'notifications.time.1hAgo',
isRead: true,
},
];
const yesterdayNotifications: Notification[] = [
{
id: '4',
type: 'system',
titleKey: 'notifications.systemUpdate',
descriptionKey: 'notifications.systemUpdateDesc',
timeKey: '10:00 AM',
isRead: true,
},
];
const iconMap = {
success: { icon: CheckCircle, bg: 'bg-emerald-100', text: 'text-emerald-600' },
warning: { icon: AlertTriangle, bg: 'bg-amber-100', text: 'text-amber-600' },
info: { icon: Store, bg: 'bg-blue-100', text: 'text-blue-600' },
system: { icon: Monitor, bg: 'bg-slate-100', text: 'text-slate-600' },
};
const actionStyles = {
primary: 'text-emerald-700 bg-emerald-50 hover:bg-emerald-100 border-emerald-100',
secondary: 'text-slate-700 bg-white hover:bg-slate-50 border-slate-200 shadow-sm',
warning: 'text-amber-700 bg-amber-50 hover:bg-amber-100 border-amber-100',
};
interface NotificationPanelProps {
onClose: () => void;
}
export default function NotificationPanel({ onClose }: NotificationPanelProps) {
const { t } = useTranslation();
const renderNotification = (notif: Notification) => {
const { icon: Icon, bg, text } = iconMap[notif.type];
return (
<div
key={notif.id}
className={cn(
'group relative flex gap-4 px-5 py-4 hover:bg-white transition-colors cursor-pointer',
notif.isRead ? 'bg-white/60' : 'bg-white'
)}
>
{/* Left accent bar */}
{!notif.isRead ? (
<div className="absolute left-0 top-0 bottom-0 w-1 bg-primary" />
) : (
<div className="absolute left-0 top-0 bottom-0 w-1 bg-transparent group-hover:bg-slate-200 transition-colors" />
)}
{/* Icon */}
<div className={cn('mt-1 flex h-9 w-9 flex-none items-center justify-center rounded-full ring-4 ring-white', bg)}>
<Icon className={cn('h-[18px] w-[18px]', text)} />
</div>
{/* Content */}
<div className="flex-auto">
<div className="flex items-baseline justify-between gap-x-4">
<p className="text-sm font-semibold leading-6 text-slate-900">{t(notif.titleKey)}</p>
<p className="flex-none text-xs text-slate-500">
{notif.timeKey.startsWith('notifications.') ? t(notif.timeKey) : notif.timeKey}
</p>
</div>
<p
className="text-sm leading-5 text-slate-600"
dangerouslySetInnerHTML={{ __html: t(notif.descriptionKey) }}
/>
{notif.actions && (
<div className="mt-3 flex gap-2">
{notif.actions.map((action) => (
<button
key={action.labelKey}
className={cn(
'text-xs font-medium px-3 py-1.5 rounded-md border transition-colors',
actionStyles[action.variant]
)}
>
{t(action.labelKey)}
</button>
))}
</div>
)}
</div>
{/* Unread dot */}
{!notif.isRead && (
<div className="flex-none self-start">
<div className="h-2 w-2 rounded-full bg-primary mt-2" />
</div>
)}
</div>
);
};
return (
<div className="absolute top-4 right-8 z-50 w-full max-w-md animate-in slide-in-from-top-4 fade-in duration-200">
<div className="flex flex-col rounded-2xl border border-border bg-white shadow-2xl overflow-hidden ring-1 ring-black/5">
{/* Header */}
<div className="flex items-center justify-between border-b border-border px-5 py-4 bg-white/50 backdrop-blur-sm">
<div className="flex items-center gap-2">
<div className="bg-indigo-50 text-indigo-600 rounded-md p-1">
<svg className="h-5 w-5" viewBox="0 0 24 24" fill="currentColor">
<path d="M12 22c1.1 0 2-.9 2-2h-4c0 1.1.9 2 2 2zm6-6v-5c0-3.07-1.63-5.64-4.5-6.32V4c0-.83-.67-1.5-1.5-1.5s-1.5.67-1.5 1.5v.68C7.64 5.36 6 7.92 6 11v5l-2 2v1h16v-1l-2-2zm-2 1H8v-6c0-2.48 1.51-4.5 4-4.5s4 2.02 4 4.5v6z" />
</svg>
</div>
<div>
<h3 className="text-sm font-bold text-slate-900">{t('notifications.title')}</h3>
<p className="text-[11px] text-slate-500 font-medium">{t('notifications.newCount', { count: 3 })}</p>
</div>
</div>
<div className="flex gap-2">
<button className="text-[11px] font-semibold text-primary hover:text-primary/80 hover:bg-primary/5 px-2.5 py-1.5 rounded-md transition-colors">
{t('notifications.markAllRead')}
</button>
<button
onClick={onClose}
className="text-slate-400 hover:text-slate-600 p-1 hover:bg-slate-100 rounded-md transition-colors"
>
<X className="h-[18px] w-[18px]" />
</button>
</div>
</div>
{/* Scrollable body */}
<div className="max-h-[600px] overflow-y-auto bg-slate-50/50">
{/* Today */}
<div className="px-5 py-2.5 sticky top-0 bg-slate-50/95 backdrop-blur-sm border-b border-border z-10">
<span className="text-xs font-semibold text-slate-500 uppercase tracking-wider">
{t('notifications.today')}
</span>
</div>
<div className="divide-y divide-border">
{todayNotifications.map(renderNotification)}
</div>
{/* Yesterday */}
<div className="px-5 py-2.5 sticky top-0 bg-slate-50/95 backdrop-blur-sm border-b border-border border-t z-10">
<span className="text-xs font-semibold text-slate-500 uppercase tracking-wider">
{t('notifications.yesterday')}
</span>
</div>
<div className="divide-y divide-border">
{yesterdayNotifications.map(renderNotification)}
</div>
{/* View all */}
<div className="p-3 text-center border-t border-border bg-slate-50">
<button className="text-xs font-semibold text-primary hover:text-primary/80 transition-colors">
{t('notifications.viewAll')}
</button>
</div>
</div>
</div>
</div>
);
}