feat(notifications) : add profile pictures and guide previews
This commit is contained in:
@@ -1,16 +1,18 @@
|
||||
'use client';
|
||||
import { createContext, useCallback, useContext, useEffect, useRef, useState, type ReactNode } from 'react';
|
||||
import Link from 'next/link';
|
||||
import Image from '@/components/ui/resilient-image';
|
||||
import { BellIcon, BellOffIcon, SettingsIcon } from 'lucide-react';
|
||||
import { authClient } from '@/lib/auth/client';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Avatar, AvatarImage, AvatarFallback } from '@/components/ui/avatar';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
|
||||
import { Skeleton } from '@/components/ui/skeleton';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { useSitePushDisabled } from './push-client';
|
||||
|
||||
type Item = { id: string; title: string; body: string; url: string; readAt: string | null };
|
||||
type Item = { id: string; title: string; body: string; url: string; readAt: string | null; authorName: string | null; authorImage: string | null; imageUrl: string | null };
|
||||
type Feed = { items: Item[]; unread: number; nextCursor: string | null };
|
||||
const empty: Feed = { items: [], unread: 0, nextCursor: null };
|
||||
type State = Feed & { signedIn: boolean; busy: boolean; error: string; refresh: (more?: boolean) => Promise<Feed | undefined>; read: (id?: string) => Promise<void> };
|
||||
@@ -81,7 +83,11 @@ export function NotificationBell() {
|
||||
{!state.signedIn ? <Link href="/auth/login" className="text-sm text-primary underline">เข้าสู่ระบบเพื่อดูการแจ้งเตือน</Link> : <>
|
||||
{state.unread > 0 && <Button variant="link" size="sm" className="self-start" onClick={() => void state.read().catch(() => undefined)}>อ่านทั้งหมด</Button>}
|
||||
<div className="flex max-h-[60svh] flex-col gap-1 overflow-y-auto">
|
||||
{state.items.map(item => <Link key={item.id} href={item.url} prefetch={false} className={cn('rounded-lg p-3', !item.readAt ? 'bg-accent' : 'hover:bg-muted')} onClick={() => { setOpen(false); void state.read(item.id).catch(() => undefined); }}><p className="text-sm font-medium">{!item.readAt && <span className="mr-1 text-destructive" aria-label="ยังไม่ได้อ่าน">●</span>}{item.title}</p><p className="line-clamp-2 break-words text-xs text-muted-foreground">{item.body}</p></Link>)}
|
||||
{state.items.map(item => <Link key={item.id} href={item.url} prefetch={false} className={cn('flex items-start gap-3 rounded-lg p-3', !item.readAt ? 'bg-accent' : 'hover:bg-muted')} onClick={() => { setOpen(false); void state.read(item.id).catch(() => undefined); }}>
|
||||
{item.imageUrl ? <Image src={item.imageUrl} alt="" width={64} height={64} sizes="64px" className="size-16 shrink-0 rounded-lg object-contain" />
|
||||
: (item.authorName || item.authorImage) && <Avatar size="lg"><AvatarImage src={item.authorImage ?? undefined} alt="" /><AvatarFallback>{item.authorName?.slice(0, 2) || '?'}</AvatarFallback></Avatar>}
|
||||
<div className="min-w-0 flex-1"><p className="break-words text-sm font-medium">{!item.readAt && <span className="mr-1 text-destructive" aria-label="ยังไม่ได้อ่าน">●</span>}{item.title}</p><p className="line-clamp-2 break-words text-xs text-muted-foreground">{item.body}</p></div>
|
||||
</Link>)}
|
||||
{state.busy && !state.items.length && <div role="status" aria-label="กำลังโหลดการแจ้งเตือน" className="flex flex-col gap-2"><Skeleton className="h-16 w-full" /><Skeleton className="h-16 w-full" /></div>}
|
||||
{!state.busy && !state.items.length && !state.error && <p className="py-6 text-center text-sm text-muted-foreground">ยังไม่มีการแจ้งเตือน</p>}
|
||||
{state.error && <p role="alert" className="text-sm text-destructive">{state.error}</p>}
|
||||
|
||||
Reference in New Issue
Block a user