UI Stabilization: Global color theme updates (#75B2D6), AI Assistant styling refactor, and navigation fixes
This commit is contained in:
@@ -46,6 +46,15 @@ interface ReminderNote {
|
||||
isReminderDone: boolean
|
||||
}
|
||||
|
||||
// ── Memento brand tokens ──────────────────────────────────────────────────────
|
||||
const C = {
|
||||
blue: '#E9ECEF',
|
||||
gold: '#D4A373',
|
||||
green: '#A3B18A',
|
||||
dark: '#1C1C1C',
|
||||
beige: '#F2F0E9',
|
||||
}
|
||||
|
||||
export function NotificationPanel() {
|
||||
const { refreshNotes } = useRefresh()
|
||||
const { t } = useLanguage()
|
||||
@@ -100,7 +109,6 @@ export function NotificationPanel() {
|
||||
refreshNotes(null)
|
||||
setOpen(false)
|
||||
} catch (error: any) {
|
||||
console.error('[NOTIFICATION] Error:', error)
|
||||
toast.error(error.message || t('general.error'))
|
||||
}
|
||||
}
|
||||
@@ -112,7 +120,6 @@ export function NotificationPanel() {
|
||||
toast.info(t('notification.declined'))
|
||||
if (requests.length <= 1) setOpen(false)
|
||||
} catch (error: any) {
|
||||
console.error('[NOTIFICATION] Error:', error)
|
||||
toast.error(error.message || t('general.error'))
|
||||
}
|
||||
}
|
||||
@@ -139,6 +146,23 @@ export function NotificationPanel() {
|
||||
|
||||
const hasContent = requests.length > 0 || activeReminders.length > 0 || appNotifications.length > 0
|
||||
|
||||
// ── icon bg/color per notification type ──────────────────────────────────
|
||||
const notifIconStyle = (type: string) => {
|
||||
if (type === 'agent_success') return { bg: `${C.green}20`, color: C.green }
|
||||
if (type === 'agent_slides_ready') return { bg: `${C.blue}20`, color: C.blue }
|
||||
if (type === 'agent_canvas_ready') return { bg: `${C.blue}20`, color: C.blue }
|
||||
if (type === 'agent_failure') return { bg: '#EF444420', color: '#EF4444' }
|
||||
return { bg: `${C.gold}20`, color: C.gold }
|
||||
}
|
||||
|
||||
const notifLabelColor = (type: string) => {
|
||||
if (type === 'agent_success') return C.green
|
||||
if (type === 'agent_slides_ready') return C.blue
|
||||
if (type === 'agent_canvas_ready') return C.blue
|
||||
if (type === 'agent_failure') return '#EF4444'
|
||||
return C.gold
|
||||
}
|
||||
|
||||
return (
|
||||
<Popover open={open} onOpenChange={setOpen}>
|
||||
<PopoverTrigger asChild>
|
||||
@@ -147,163 +171,172 @@ export function NotificationPanel() {
|
||||
>
|
||||
<Bell className="h-4 w-4 transition-transform duration-200 hover:scale-110" />
|
||||
{pendingCount > 0 && (
|
||||
<span className="absolute -top-1 -right-1 h-4 w-4 flex items-center justify-center rounded-full bg-rose-500 text-white text-[9px] font-bold border border-white shadow-sm">
|
||||
<span
|
||||
className="absolute -top-1 -right-1 h-4 w-4 flex items-center justify-center rounded-full text-white text-[9px] font-bold border border-white shadow-sm"
|
||||
style={{ background: C.gold }}
|
||||
>
|
||||
{pendingCount > 9 ? '9+' : pendingCount}
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent align="end" className="w-80 p-0">
|
||||
<div className="px-4 py-3 border-b bg-gradient-to-r from-primary/5 to-primary/10 dark:from-primary/10 dark:to-primary/15">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
<Bell className="h-4 w-4 text-primary dark:text-primary-foreground" />
|
||||
<span className="font-semibold text-sm">{t('notification.notifications')}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
{appNotifications.length > 0 && (
|
||||
<button
|
||||
onClick={handleMarkAllRead}
|
||||
className="text-[10px] text-muted-foreground hover:text-foreground transition-colors"
|
||||
title={t('notification.markAllRead') || 'Mark all read'}
|
||||
>
|
||||
<Check className="h-3.5 w-3.5" />
|
||||
</button>
|
||||
)}
|
||||
{pendingCount > 0 && (
|
||||
<Badge className="bg-primary hover:bg-primary/90 text-primary-foreground shadow-md">
|
||||
{pendingCount}
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<PopoverContent align="end" className="w-80 p-0 rounded-2xl overflow-hidden shadow-xl border border-black/10">
|
||||
{/* Header */}
|
||||
<div className="px-4 py-3 border-b flex items-center justify-between" style={{ background: `${C.beige}` }}>
|
||||
<div className="flex items-center gap-2">
|
||||
<Bell className="h-4 w-4" style={{ color: C.dark }} />
|
||||
<span className="font-bold text-sm tracking-tight" style={{ color: C.dark }}>
|
||||
{t('notification.notifications')}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
{appNotifications.length > 0 && (
|
||||
<button
|
||||
onClick={handleMarkAllRead}
|
||||
className="text-[10px] text-foreground/40 hover:text-foreground transition-colors"
|
||||
title={t('notification.markAllRead') || 'Mark all read'}
|
||||
>
|
||||
<Check className="h-3.5 w-3.5" />
|
||||
</button>
|
||||
)}
|
||||
{pendingCount > 0 && (
|
||||
<span
|
||||
className="h-5 px-1.5 flex items-center justify-center rounded-full text-white text-[9px] font-bold"
|
||||
style={{ background: C.gold }}
|
||||
>
|
||||
{pendingCount}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{isLoading ? (
|
||||
<div className="p-6 text-center text-sm text-muted-foreground">
|
||||
<div className="animate-spin h-6 w-6 border-2 border-primary border-t-transparent rounded-full mx-auto mb-2" />
|
||||
<div className="animate-spin h-6 w-6 border-2 border-t-transparent rounded-full mx-auto mb-2" style={{ borderColor: C.blue, borderTopColor: 'transparent' }} />
|
||||
</div>
|
||||
) : !hasContent ? (
|
||||
<div className="p-6 text-center text-sm text-muted-foreground">
|
||||
<Bell className="h-10 w-10 mx-auto mb-3 opacity-30" />
|
||||
<p className="font-medium">{t('notification.noNotifications') || 'No new notifications'}</p>
|
||||
<div className="p-8 text-center">
|
||||
<Bell className="h-9 w-9 mx-auto mb-3 opacity-20" />
|
||||
<p className="text-[12px] font-medium text-foreground/40">{t('notification.noNotifications') || 'Aucune notification'}</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="max-h-96 overflow-y-auto">
|
||||
{/* App notifications (agents, system) */}
|
||||
<div className="max-h-96 overflow-y-auto divide-y divide-black/5">
|
||||
|
||||
{/* ── App notifications (agents, system) ── */}
|
||||
{appNotifications.map((notif) => {
|
||||
const isSlides = notif.type === 'agent_slides_ready'
|
||||
const isCanvas = notif.type === 'agent_canvas_ready'
|
||||
const canvasId = notif.relatedId
|
||||
const iconStyle = notifIconStyle(notif.type)
|
||||
|
||||
return (
|
||||
<div
|
||||
key={notif.id}
|
||||
className="p-3 border-b last:border-0 hover:bg-accent/50 transition-colors duration-150"
|
||||
>
|
||||
<div
|
||||
className="flex items-start gap-3 cursor-pointer"
|
||||
onClick={() => {
|
||||
if (notif.actionUrl) {
|
||||
handleMarkNotifRead(notif.id)
|
||||
setOpen(false)
|
||||
router.push(notif.actionUrl)
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div className={cn(
|
||||
"mt-0.5 flex-none rounded-full p-1",
|
||||
notif.type === 'agent_success' && 'bg-green-100 dark:bg-green-900/30 text-green-600',
|
||||
notif.type === 'agent_slides_ready' && 'bg-purple-100 dark:bg-purple-900/30 text-purple-600',
|
||||
notif.type === 'agent_canvas_ready' && 'bg-blue-100 dark:bg-blue-900/30 text-blue-600',
|
||||
notif.type === 'agent_failure' && 'bg-red-100 dark:bg-red-900/30 text-red-600',
|
||||
notif.type === 'system' && 'bg-blue-100 dark:bg-blue-900/30 text-blue-600',
|
||||
)}>
|
||||
{isSlides ? (
|
||||
<Presentation className="w-3.5 h-3.5" />
|
||||
) : isCanvas ? (
|
||||
<Pencil className="w-3.5 h-3.5" />
|
||||
) : notif.type.startsWith('agent') ? (
|
||||
<Bot className="w-3.5 h-3.5" />
|
||||
) : (
|
||||
<AlertCircle className="w-3.5 h-3.5" />
|
||||
)}
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-center gap-1.5 mb-0.5">
|
||||
<span className={cn(
|
||||
"text-[10px] font-semibold uppercase tracking-wider",
|
||||
notif.type === 'agent_success' && 'text-green-600 dark:text-green-400',
|
||||
notif.type === 'agent_slides_ready' && 'text-purple-600 dark:text-purple-400',
|
||||
notif.type === 'agent_canvas_ready' && 'text-blue-600 dark:text-blue-400',
|
||||
notif.type === 'agent_failure' && 'text-red-600 dark:text-red-400',
|
||||
notif.type === 'system' && 'text-blue-600 dark:text-blue-400',
|
||||
)}>
|
||||
{notif.type === 'agent_slides_ready' && (t('notification.slidesReady') || 'Slides Ready')}
|
||||
{notif.type === 'agent_canvas_ready' && (t('notification.canvasReady') || 'Diagram Ready')}
|
||||
{notif.type === 'agent_success' && (t('notification.agentSuccess') || 'Agent completed')}
|
||||
{notif.type === 'agent_failure' && (t('notification.agentFailed') || 'Agent failed')}
|
||||
{notif.type === 'system' && 'System'}
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-sm font-medium truncate">{notif.title}</p>
|
||||
{notif.message && (
|
||||
<p className="text-xs text-muted-foreground mt-0.5 line-clamp-2">{notif.message}</p>
|
||||
)}
|
||||
<div className="flex items-center gap-1 mt-1 text-xs text-muted-foreground">
|
||||
<Clock className="w-3 h-3" />
|
||||
{formatDistanceToNow(new Date(notif.createdAt), { addSuffix: true })}
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
onClick={(e) => { e.stopPropagation(); handleMarkNotifRead(notif.id) }}
|
||||
className="mt-0.5 text-muted-foreground/40 hover:text-foreground transition-colors"
|
||||
title={t('notification.dismiss') || 'Dismiss'}
|
||||
>
|
||||
<X className="w-3.5 h-3.5" />
|
||||
</button>
|
||||
</div>
|
||||
{isSlides && canvasId && (
|
||||
<div className="mt-2 ml-8">
|
||||
<button
|
||||
onClick={async () => {
|
||||
<div key={notif.id} className="p-3 hover:bg-black/[0.02] transition-colors">
|
||||
<div
|
||||
className="flex items-start gap-3 cursor-pointer"
|
||||
onClick={() => {
|
||||
if (notif.actionUrl) {
|
||||
handleMarkNotifRead(notif.id)
|
||||
window.open(`/api/canvas/download?id=${canvasId}`, '_blank')
|
||||
}}
|
||||
className="flex items-center gap-1.5 px-3 py-1.5 text-xs font-semibold rounded-md bg-purple-500 text-white hover:bg-purple-600 shadow-sm transition-all active:scale-95"
|
||||
setOpen(false)
|
||||
router.push(notif.actionUrl)
|
||||
}
|
||||
}}
|
||||
>
|
||||
{/* Icon badge */}
|
||||
<div
|
||||
className="mt-0.5 flex-none rounded-lg p-1.5"
|
||||
style={{ background: iconStyle.bg, color: iconStyle.color }}
|
||||
>
|
||||
<Download className="w-3 h-3" />
|
||||
{t('notification.downloadPptx') || 'Download .pptx'}
|
||||
{isSlides ? <Presentation className="w-3.5 h-3.5" />
|
||||
: isCanvas ? <Pencil className="w-3.5 h-3.5" />
|
||||
: notif.type.startsWith('agent') ? <Bot className="w-3.5 h-3.5" />
|
||||
: <AlertCircle className="w-3.5 h-3.5" />}
|
||||
</div>
|
||||
|
||||
<div className="flex-1 min-w-0">
|
||||
<span
|
||||
className="text-[9px] font-bold uppercase tracking-[0.2em]"
|
||||
style={{ color: notifLabelColor(notif.type) }}
|
||||
>
|
||||
{notif.type === 'agent_slides_ready' && (t('notification.slidesReady') || 'Présentation prête')}
|
||||
{notif.type === 'agent_canvas_ready' && (t('notification.canvasReady') || 'Diagramme prêt')}
|
||||
{notif.type === 'agent_success' && (t('notification.agentSuccess') || 'Agent terminé')}
|
||||
{notif.type === 'agent_failure' && (t('notification.agentFailed') || 'Agent échoué')}
|
||||
{notif.type === 'system' && 'Système'}
|
||||
</span>
|
||||
<p className="text-[13px] font-semibold truncate mt-0.5">{notif.title}</p>
|
||||
{notif.message && (
|
||||
<p className="text-[11px] text-foreground/50 mt-0.5 line-clamp-2">{notif.message}</p>
|
||||
)}
|
||||
<div className="flex items-center gap-1 mt-1 text-[10px] text-foreground/30">
|
||||
<Clock className="w-3 h-3" />
|
||||
{formatDistanceToNow(new Date(notif.createdAt), { addSuffix: true })}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
onClick={(e) => { e.stopPropagation(); handleMarkNotifRead(notif.id) }}
|
||||
className="mt-0.5 text-foreground/20 hover:text-foreground transition-colors"
|
||||
>
|
||||
<X className="w-3.5 h-3.5" />
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Download PPTX button */}
|
||||
{isSlides && canvasId && (
|
||||
<div className="mt-2 ml-8">
|
||||
<button
|
||||
onClick={async () => {
|
||||
handleMarkNotifRead(notif.id)
|
||||
try {
|
||||
const res = await fetch(`/api/canvas?id=${canvasId}`)
|
||||
const data = await res.json()
|
||||
if (!data.canvas?.data) throw new Error()
|
||||
const parsed = JSON.parse(data.canvas.data)
|
||||
if (!parsed.base64) throw new Error()
|
||||
const bytes = Uint8Array.from(atob(parsed.base64), c => c.charCodeAt(0))
|
||||
const blob = new Blob([bytes], { type: 'application/vnd.openxmlformats-officedocument.presentationml.presentation' })
|
||||
const url = URL.createObjectURL(blob)
|
||||
const a = document.createElement('a')
|
||||
a.href = url
|
||||
a.download = parsed.filename || `${data.canvas.name || 'presentation'}.pptx`
|
||||
document.body.appendChild(a); a.click()
|
||||
document.body.removeChild(a); URL.revokeObjectURL(url)
|
||||
} catch { toast.error('Échec du téléchargement') }
|
||||
}}
|
||||
className="flex items-center gap-1.5 px-3 py-1.5 text-[10px] font-bold rounded-lg text-white uppercase tracking-wide transition-all hover:opacity-90 active:scale-95 shadow-sm"
|
||||
style={{ background: C.blue }}
|
||||
>
|
||||
<Download className="w-3 h-3" />
|
||||
{t('notification.downloadPptx') || 'Télécharger .pptx'}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
|
||||
{/* Overdue reminders */}
|
||||
{/* ── Overdue reminders ── */}
|
||||
{overdueReminders.map((note) => (
|
||||
<div
|
||||
key={note.id}
|
||||
className="p-3 border-b last:border-0 hover:bg-accent/50 transition-colors duration-150"
|
||||
>
|
||||
<div key={note.id} className="p-3 hover:bg-black/[0.02] transition-colors">
|
||||
<div className="flex items-start gap-3">
|
||||
<button
|
||||
onClick={() => handleToggleReminder(note.id, true)}
|
||||
className="mt-0.5 flex-none text-amber-500 hover:text-green-500 transition-colors"
|
||||
className="mt-0.5 flex-none transition-colors hover:opacity-70"
|
||||
style={{ color: C.gold }}
|
||||
title={t('reminders.markDone')}
|
||||
>
|
||||
<Circle className="w-4 h-4" />
|
||||
</button>
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-center gap-1.5 mb-0.5">
|
||||
<AlertCircle className="w-3 h-3 text-amber-500" />
|
||||
<span className="text-[10px] font-semibold uppercase tracking-wider text-amber-600 dark:text-amber-400">
|
||||
<AlertCircle className="w-3 h-3" style={{ color: C.gold }} />
|
||||
<span className="text-[9px] font-bold uppercase tracking-[0.2em]" style={{ color: C.gold }}>
|
||||
{t('reminders.overdue')}
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-sm font-medium truncate">{note.title || t('notification.untitled')}</p>
|
||||
<div className="flex items-center gap-1 mt-1 text-xs text-muted-foreground">
|
||||
<p className="text-[13px] font-semibold truncate">{note.title || t('notification.untitled')}</p>
|
||||
<div className="flex items-center gap-1 mt-1 text-[10px] text-foreground/30">
|
||||
<Clock className="w-3 h-3" />
|
||||
{note.reminder && formatDistanceToNow(new Date(note.reminder), { addSuffix: true })}
|
||||
</div>
|
||||
@@ -312,17 +345,14 @@ export function NotificationPanel() {
|
||||
</div>
|
||||
))}
|
||||
|
||||
{/* Upcoming reminders */}
|
||||
{/* ── Upcoming reminders ── */}
|
||||
{upcomingReminders.slice(0, 5).map((note) => (
|
||||
<div
|
||||
key={note.id}
|
||||
className="p-3 border-b last:border-0 hover:bg-accent/50 transition-colors duration-150"
|
||||
>
|
||||
<div key={note.id} className="p-3 hover:bg-black/[0.02] transition-colors">
|
||||
<div className="flex items-start gap-3">
|
||||
<Clock className="w-4 h-4 mt-0.5 flex-none text-primary" />
|
||||
<Clock className="w-4 h-4 mt-0.5 flex-none" style={{ color: C.blue }} />
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="text-sm font-medium truncate">{note.title || t('notification.untitled')}</p>
|
||||
<div className="text-xs text-muted-foreground mt-0.5">
|
||||
<p className="text-[13px] font-semibold truncate">{note.title || t('notification.untitled')}</p>
|
||||
<div className="text-[11px] text-foreground/40 mt-0.5">
|
||||
{note.reminder && new Date(note.reminder).toLocaleDateString(undefined, {
|
||||
month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit'
|
||||
})}
|
||||
@@ -332,56 +362,48 @@ export function NotificationPanel() {
|
||||
</div>
|
||||
))}
|
||||
|
||||
{/* Share requests */}
|
||||
{/* ── Share requests ── */}
|
||||
{requests.map((request) => (
|
||||
<div
|
||||
key={request.id}
|
||||
className="p-3 border-b last:border-0 hover:bg-accent/50 transition-colors duration-150"
|
||||
>
|
||||
<div className="flex items-start gap-3 mb-2">
|
||||
<div className="h-7 w-7 rounded-full bg-gradient-to-br from-blue-500 to-indigo-600 flex items-center justify-center text-white font-semibold text-[10px] shadow-md shrink-0">
|
||||
<div key={request.id} className="p-4 hover:bg-black/[0.02] transition-colors space-y-3">
|
||||
<div className="flex items-start gap-3">
|
||||
{/* Avatar */}
|
||||
<div
|
||||
className="h-8 w-8 rounded-full flex items-center justify-center text-white font-bold text-[11px] shrink-0 shadow-sm"
|
||||
style={{ background: `linear-gradient(135deg, ${C.blue}, ${C.green})` }}
|
||||
>
|
||||
{(request.sharer.name || request.sharer.email)[0].toUpperCase()}
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="text-sm font-semibold truncate">
|
||||
<div className="flex items-center gap-1.5 mb-0.5">
|
||||
<Share2 className="w-3 h-3" style={{ color: C.blue }} />
|
||||
<span className="text-[9px] font-bold uppercase tracking-[0.2em]" style={{ color: C.blue }}>
|
||||
Partage
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-[13px] font-semibold truncate">
|
||||
{request.sharer.name || request.sharer.email}
|
||||
</p>
|
||||
<p className="text-xs text-muted-foreground truncate mt-0.5">
|
||||
<p className="text-[11px] text-foreground/50 truncate">
|
||||
{t('notification.shared', { title: request.note.title || t('notification.untitled') })}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-2 mt-2">
|
||||
<div className="flex gap-2 ml-11">
|
||||
<button
|
||||
onClick={() => handleDecline(request.id)}
|
||||
className={cn(
|
||||
"flex-1 h-7 px-3 text-[11px] font-semibold rounded-md",
|
||||
"border border-border bg-background",
|
||||
"text-muted-foreground",
|
||||
"hover:bg-muted hover:text-foreground",
|
||||
"transition-all duration-200",
|
||||
"flex items-center justify-center gap-1",
|
||||
"active:scale-95"
|
||||
)}
|
||||
className="flex-1 h-7 px-3 text-[11px] font-semibold rounded-lg border border-black/15 text-foreground/60 hover:bg-black/5 transition-all active:scale-95 flex items-center justify-center gap-1"
|
||||
>
|
||||
<X className="h-3 w-3" />
|
||||
{t('notification.decline') || t('general.cancel')}
|
||||
{t('notification.decline') || 'Refuser'}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleAccept(request.id)}
|
||||
className={cn(
|
||||
"flex-1 h-7 px-3 text-[11px] font-semibold rounded-md",
|
||||
"bg-primary text-primary-foreground",
|
||||
"hover:bg-primary/90",
|
||||
"shadow-sm",
|
||||
"transition-all duration-200",
|
||||
"flex items-center justify-center gap-1",
|
||||
"active:scale-95"
|
||||
)}
|
||||
className="flex-1 h-7 px-3 text-[11px] font-bold rounded-lg text-white transition-all active:scale-95 flex items-center justify-center gap-1 shadow-sm hover:opacity-90"
|
||||
style={{ background: C.blue }}
|
||||
>
|
||||
<Check className="h-3 w-3" />
|
||||
{t('notification.accept') || t('general.confirm')}
|
||||
{t('notification.accept') || 'Accepter'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -389,14 +411,15 @@ export function NotificationPanel() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Footer link to reminders page */}
|
||||
{/* Footer */}
|
||||
{activeReminders.length > 0 && (
|
||||
<div className="px-4 py-2 border-t bg-muted/30">
|
||||
<div className="px-4 py-2.5 border-t bg-black/[0.02]">
|
||||
<a
|
||||
href="/reminders"
|
||||
className="text-[11px] font-medium text-primary hover:underline"
|
||||
className="text-[11px] font-semibold hover:opacity-70 transition-opacity"
|
||||
style={{ color: C.blue }}
|
||||
>
|
||||
{t('reminders.viewAll') || t('reminders.title') || 'Voir tous les rappels'}
|
||||
{t('reminders.viewAll') || 'Voir tous les rappels →'}
|
||||
</a>
|
||||
</div>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user