Page: Notifications
Page Spec: Notifications
Route
/notifications
Architecture Status
Core
Figma Reference
No Figma — designed from architecture
Visual Description
The notifications page displays transaction alerts, system messages, and payment confirmations:
- Top bar: Left side has a back arrow (ArrowLeft, NOT chevron). "Varsler" heading in bold sans-serif (NOT serif). No right-side icons.
- Notification list: Vertically stacked notification cards, each in its own white rounded card:
- Unread notifications: Bold title text with a green dot indicator (left edge)
- Read notifications: Regular title text without indicator
- Each card contains:
- Icon: Type-based icon in colored circle (green for transfers, yellow for QR payments, blue for system messages)
- Title: Bold notification heading
- Description: Muted text with details
- Timestamp: Muted text showing relative time ("2 timer siden", "I går 14:30")
- Empty state: When no notifications exist:
- Large bell icon with slash (gray, centered)
- "Ingen varsler" heading
- "Du vil motta varsler om transaksjoner og viktige meldinger her" description text
- Bottom navigation: 5 tabs — Hjem (house outline, gray), Send (paper plane outline, gray), Skann (QR outline, gray), Kort (card outline, gray), Profil (person outline, gray). No tab highlighted as active.
Background is light gray (#EEEEEE). All cards are white with rounded corners (#FFFFFF, rounded-2xl). Unread notifications appear at the top.
Page Layout
App page WITH bottom nav
├── Top Bar
│ ├── Left: Back arrow (chevron left)
│ └── Center: "Varsler" heading
├── Notification List (or Empty State)
│ └── Notification cards (sorted by date, unread first)
│ ├── Unread indicator (green dot)
│ ├── Icon (type-based color)
│ ├── Title (bold if unread)
│ ├── Description (muted text)
│ └── Timestamp (relative time)
└── Bottom Nav (no active tab)
Components
Top Bar
<div className="flex items-center gap-3 px-6 pt-6">
<Link href="/dashboard">
<button className="rounded-lg p-2 hover:bg-white/80">
<ArrowLeft className="h-5 w-5 text-[#6B7280]" />
</button>
</Link>
<h1 className="text-xl font-bold text-[#1A1A1A]">
Varsler
</h1>
</div>
Notification Card (Unread)
<div
onClick={() => markAsRead(notification.id)}
className="relative flex items-start gap-3 rounded-2xl bg-white p-4 shadow-sm cursor-pointer hover:bg-[#F9FAFB] transition-colors"
>
{/* Unread indicator dot */}
<div className="absolute left-0 top-1/2 -translate-y-1/2 h-2 w-2 rounded-full bg-[#0B6E35]" />
{/* Icon */}
<div className={`flex h-10 w-10 items-center justify-center rounded-full ${iconBgColor}`}>
{getNotificationIcon(notification.type)}
</div>
{/* Content */}
<div className="flex-1">
<p className="text-sm font-bold text-[#1A1A1A]">{notification.title}</p>
<p className="text-xs text-[#6B7280] mt-0.5">{notification.description}</p>
<p className="text-xs text-[#9CA3AF] mt-1">{formatTimestamp(notification.created_at)}</p>
</div>
</div>
Notification Card (Read)
<div
className="flex items-start gap-3 rounded-2xl bg-white p-4 shadow-sm"
>
{/* Icon */}
<div className={`flex h-10 w-10 items-center justify-center rounded-full ${iconBgColor}`}>
{getNotificationIcon(notification.type)}
</div>
{/* Content */}
<div className="flex-1">
<p className="text-sm font-semibold text-[#1A1A1A]">{notification.title}</p>
<p className="text-xs text-[#6B7280] mt-0.5">{notification.description}</p>
<p className="text-xs text-[#9CA3AF] mt-1">{formatTimestamp(notification.created_at)}</p>
</div>
</div>
Empty State
<div className="flex flex-col items-center justify-center px-6 py-16">
<div className="flex h-16 w-16 items-center justify-center rounded-full bg-[#E5E7EB]">
<BellOff className="h-8 w-8 text-[#9CA3AF]" />
</div>
<p className="mt-4 text-lg font-bold text-[#1A1A1A]">Ingen varsler</p>
<p className="mt-2 text-center text-sm text-[#6B7280] max-w-xs">
Du vil motta varsler om transaksjoner og viktige meldinger her
</p>
</div>
Notification Icon Logic
function getNotificationIcon(type: string) {
switch (type) {
case 'transaction_sent':
case 'transaction_received':
return <Send className="h-5 w-5 text-[#0B6E35]" />;
case 'qr_payment':
return <QrCode className="h-5 w-5 text-[#D4A017]" />;
case 'system_message':
case 'account_linked':
case 'security_alert':
return <AlertCircle className="h-5 w-5 text-[#3B82F6]" />;
default:
return <Bell className="h-5 w-5 text-[#6B7280]" />;
}
}
function getIconBgColor(type: string) {
switch (type) {
case 'transaction_sent':
case 'transaction_received':
return 'bg-[#0B6E35]/10';
case 'qr_payment':
return 'bg-[#D4A017]/10';
case 'system_message':
case 'account_linked':
case 'security_alert':
return 'bg-[#3B82F6]/10';
default:
return 'bg-[#E5E7EB]';
}
}
Timestamp Formatter
function formatTimestamp(timestamp: string): string {
const now = new Date();
const then = new Date(timestamp);
const diffMs = now.getTime() - then.getTime();
const diffMins = Math.floor(diffMs / 60000);
const diffHours = Math.floor(diffMs / 3600000);
const diffDays = Math.floor(diffMs / 86400000);
if (diffMins < 1) return 'Akkurat nå';
if (diffMins < 60) return `${diffMins} ${diffMins === 1 ? 'minutt' : 'minutter'} siden`;
if (diffHours < 24) return `${diffHours} ${diffHours === 1 ? 'time' : 'timer'} siden`;
if (diffDays === 1) return `I går ${then.toLocaleTimeString('nb-NO', { hour: '2-digit', minute: '2-digit' })}`;
if (diffDays < 7) return `${diffDays} dager siden`;
return then.toLocaleDateString('nb-NO', { day: 'numeric', month: 'short', hour: '2-digit', minute: '2-digit' });
}
Data Displayed
| Data | Source | API |
|---|---|---|
| Notification list | Notifications table | GET /api/notifications |
| Notification title | Notification record | From notifications table |
| Notification description | Notification record | From notifications table |
| Notification type | Notification record | transaction_sent / transaction_received / qr_payment / system_message / account_linked / security_alert |
| Notification read status | Notification record | is_read boolean |
| Notification timestamp | Notification record | created_at |
User Interactions
| Element | Action | Result |
|---|---|---|
| Back arrow | Click | Navigate back to previous page (dashboard) |
| Unread notification card | Click | Mark notification as read, update UI to show read state |
| Read notification card | Click | Optional — navigate to related resource (e.g., transaction detail) |
| Bottom nav tabs | Click | Navigate to respective page |
Norwegian Labels
| Element | Norwegian Text |
|---|---|
| Page heading | Varsler |
| Empty state heading | Ingen varsler |
| Empty state description | Du vil motta varsler om transaksjoner og viktige meldinger her |
| Timestamp: Now | Akkurat nå |
| Timestamp: Minutes | {n} minutt siden / {n} minutter siden |
| Timestamp: Hours | {n} time siden / {n} timer siden |
| Timestamp: Yesterday | I går {time} |
| Timestamp: Days | {n} dager siden |
| Notification: Transaction sent | Overføring sendt |
| Notification: Transaction received | Penger mottatt |
| Notification: QR Payment | QR-betaling fullført |
| Notification: System message | Systemmelding |
| Notification: Account linked | Bankkonto tilkoblet |
| Notification: Security alert | Sikkerhetsvarsel |
Design Tokens
| Token | Value |
|---|---|
| Page bg | #EEEEEE |
| Card bg | #FFFFFF |
| Primary | #0B6E35 |
| Primary hover | #095C2C |
| Accent/Gold | #D4A017 |
| Info blue | #3B82F6 |
| Text primary | #1A1A1A |
| Text muted | #6B7280 |
| Text light | #9CA3AF |
| Border | #E5E7EB |
| Unread indicator | #0B6E35 (green dot) |
| Brand font | font-[family-name:var(--font-fraunces)] |
| Card radius | rounded-2xl |
| Icon circle radius | rounded-full |
| Hover bg | #F9FAFB |
Bottom Navigation
Yes — No tab active (all tabs gray, outline icons).
No comments to display
No comments to display