"use client" import { useEffect, useMemo, useState } from "react" import Link from "next/link" import { useRouter } from "next/navigation" import { ArrowLeft, Bell, BellOff, CheckCheck, ChevronRight, Flag, Trophy, UserPlus, Users, } from "lucide-react" import { Wordmark } from "@/components/wordmark" import { cn } from "@/lib/utils" import { BottomTabBar } from "@/components/dashboard" // V0-designet (zip 20, varsler-runden 2026-07-25) -- datalag skrevet om fra // mock til ekte fetch mot GET /notifications + POST .../read + .../read-all // (app/routers/notifications.py). Scenario-veksleren fra V0-eksporten fjernet. type NotificationKind = "friend" | "tournament" | "round" | "result" type ApiNotification = { id: string type: NotificationKind message: string link_path: string created_at: string read_at: string | null } const KIND_ICON: Record = { friend: UserPlus, tournament: Trophy, round: Flag, result: Users, } const KIND_LABEL: Record = { friend: "Venneforespørsel", tournament: "Turnering", round: "Runde", result: "Resultat", } function formatRelativeTime(iso: string): string { const minutesAgo = Math.max(0, (Date.now() - new Date(iso).getTime()) / 60000) if (minutesAgo < 1) return "akkurat nå" if (minutesAgo < 60) { const m = Math.round(minutesAgo) return `for ${m} ${m === 1 ? "minutt" : "minutter"} siden` } const hours = Math.floor(minutesAgo / 60) if (hours < 24) { return `for ${hours} ${hours === 1 ? "time" : "timer"} siden` } const days = Math.floor(hours / 24) if (days < 7) { return `for ${days} ${days === 1 ? "dag" : "dager"} siden` } const weeks = Math.floor(days / 7) return `for ${weeks} ${weeks === 1 ? "uke" : "uker"} siden` } export function Notifications() { const router = useRouter() const [items, setItems] = useState(null) useEffect(() => { fetch("/notifications", { credentials: "include" }) .then((res) => { if (res.status === 401) { router.replace("/") return null } return res.ok ? res.json() : [] }) .then((data: ApiNotification[] | null) => { if (data) setItems(data) }) .catch(() => setItems([])) }, [router]) const sorted = useMemo( () => [...(items ?? [])].sort((a, b) => (a.created_at < b.created_at ? 1 : -1)), [items], ) const unreadCount = sorted.filter((n) => n.read_at === null).length async function markAllRead() { setItems((prev) => (prev ? prev.map((n) => ({ ...n, read_at: n.read_at ?? new Date().toISOString() })) : prev)) await fetch("/notifications/read-all", { method: "POST", credentials: "include" }) } async function markRead(id: string) { setItems((prev) => (prev ? prev.map((n) => (n.id === id ? { ...n, read_at: n.read_at ?? new Date().toISOString() } : n)) : prev)) await fetch(`/notifications/${id}/read`, { method: "POST", credentials: "include" }) } return (

Varsler

{items === null ? "Henter varsler …" : unreadCount > 0 ? unreadCount === 1 ? "Du har 1 ulest varsel." : `Du har ${unreadCount} uleste varsler.` : "Alt er lest. Her dukker nye varsler opp."}

{sorted.length > 0 && (
)}
{items !== null && sorted.length === 0 ? ( ) : (
    {sorted.map((n) => (
  • markRead(n.id)} />
  • ))}
)}
) } function NotificationRow({ notification, onActivate, }: { notification: ApiNotification onActivate: () => void }) { const Icon = KIND_ICON[notification.type] const unread = notification.read_at === null const readState = unread ? "Ulest" : "Lest" return ( {/* Ulest-indikator: en prikk i en reservert plass, slik at leste rader forblir på linje. */}