mirror of
https://github.com/lumina-rocks/lumina.git
synced 2026-06-04 17:51:16 +02:00
feat: Enhance notification display with date grouping (#123)
* feat: Enhance notification display with date grouping and time formatting * fix: Update date parsing in Notifications component and improve notification key assignment --------- Co-authored-by: highperfocused <highperfocused@pm.me>
This commit is contained in:
@@ -1,6 +1,5 @@
|
||||
import React from 'react';
|
||||
import { useNostrEvents, useProfile } from "nostr-react";
|
||||
import { Card, CardHeader, CardTitle, CardContent, CardFooter, CardDescription } from '@/components/ui/card';
|
||||
import {
|
||||
NostrEvent,
|
||||
Event,
|
||||
@@ -8,6 +7,7 @@ import {
|
||||
} from "nostr-tools";
|
||||
import { Avatar, AvatarImage } from './ui/avatar';
|
||||
import Link from 'next/link';
|
||||
import { format } from 'date-fns';
|
||||
|
||||
interface NotificationProps {
|
||||
event: NostrEvent;
|
||||
@@ -52,60 +52,69 @@ const Notification: React.FC<NotificationProps> = ({ event }) => {
|
||||
|
||||
let name = userData?.name ?? nip19.npubEncode(event.pubkey).slice(0, 8) + ':' + nip19.npubEncode(event.pubkey).slice(-3);
|
||||
let createdAt = new Date(event.created_at * 1000);
|
||||
|
||||
const formatTime = (date: Date) => {
|
||||
return format(date, 'h:mm a');
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className='pt-6 px-6'>
|
||||
{/* ZAP */}
|
||||
{event.kind === 9735 && (
|
||||
<div className='grid grid-cols-6 justify-center items-center'>
|
||||
<p className='col-span-1'>{sats} sats ⚡️</p>
|
||||
<div className='col-span-1'>
|
||||
<Avatar>
|
||||
<AvatarImage src={userData?.picture} alt={name} />
|
||||
</Avatar>
|
||||
const getNotificationContent = () => {
|
||||
switch (event.kind) {
|
||||
case 9735: // ZAP
|
||||
return (
|
||||
<div className='flex items-center space-x-3 p-3 hover:bg-muted/50 rounded-md transition-colors'>
|
||||
<div className='flex-shrink-0 w-10 text-center font-medium text-amber-500'>
|
||||
{sats} ⚡️
|
||||
</div>
|
||||
<div className='col-span-4'>
|
||||
<p>{name} zapped you</p>
|
||||
<p>{createdAt.toLocaleDateString() + ' ' + createdAt.toLocaleTimeString()}</p>
|
||||
<Avatar className='flex-shrink-0'>
|
||||
<AvatarImage src={userData?.picture} alt={name} />
|
||||
</Avatar>
|
||||
<div className='flex-1 min-w-0'>
|
||||
<p className='text-sm font-medium'>{name} <span className='text-muted-foreground font-normal'>zapped you</span></p>
|
||||
<p className='text-xs text-muted-foreground'>{formatTime(createdAt)}</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{/* FOLLOW */}
|
||||
{event.kind === 3 && (
|
||||
<div className='grid grid-cols-6 justify-center items-center'>
|
||||
<p className='col-span-1'>{event.content}</p>
|
||||
<div className='col-span-1'>
|
||||
<Avatar>
|
||||
<AvatarImage src={userData?.picture} alt={name} />
|
||||
</Avatar>
|
||||
);
|
||||
case 3: // FOLLOW
|
||||
return (
|
||||
<div className='flex items-center space-x-3 p-3 hover:bg-muted/50 rounded-md transition-colors'>
|
||||
<div className='flex-shrink-0 w-10 text-center font-medium text-blue-500'>
|
||||
👋
|
||||
</div>
|
||||
<div className='col-span-4'>
|
||||
<p>{name} started following you</p>
|
||||
<p>{createdAt.toLocaleDateString() + ' ' + createdAt.toLocaleTimeString()}</p>
|
||||
<Avatar className='flex-shrink-0'>
|
||||
<AvatarImage src={userData?.picture} alt={name} />
|
||||
</Avatar>
|
||||
<div className='flex-1 min-w-0'>
|
||||
<p className='text-sm font-medium'>{name} <span className='text-muted-foreground font-normal'>started following you</span></p>
|
||||
<p className='text-xs text-muted-foreground'>{formatTime(createdAt)}</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{/* REACTION */}
|
||||
{event.kind === 7 && (
|
||||
<Link href={"/note/" + reactedToId}>
|
||||
<div className='grid grid-cols-6 justify-center items-center'>
|
||||
<p className='col-span-1'>{event.content}</p>
|
||||
<div className='col-span-1'>
|
||||
<Avatar>
|
||||
<AvatarImage src={userData?.picture} alt={name} />
|
||||
</Avatar>
|
||||
);
|
||||
case 7: // REACTION
|
||||
return (
|
||||
<Link href={"/note/" + reactedToId} className='block'>
|
||||
<div className='flex items-center space-x-3 p-3 hover:bg-muted/50 rounded-md transition-colors'>
|
||||
<div className='flex-shrink-0 w-10 text-center text-lg'>
|
||||
{event.content}
|
||||
</div>
|
||||
<div className='col-span-4'>
|
||||
<p>{name} reacted to you</p>
|
||||
<p>{createdAt.toLocaleDateString() + ' ' + createdAt.toLocaleTimeString()}</p>
|
||||
<Avatar className='flex-shrink-0'>
|
||||
<AvatarImage src={userData?.picture} alt={name} />
|
||||
</Avatar>
|
||||
<div className='flex-1 min-w-0'>
|
||||
<p className='text-sm font-medium'>{name} <span className='text-muted-foreground font-normal'>reacted to your post</span></p>
|
||||
<p className='text-xs text-muted-foreground'>{formatTime(createdAt)}</p>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
<hr className='mt-6' />
|
||||
</>
|
||||
);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className='notification-item'>
|
||||
{getNotificationContent()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
nip19,
|
||||
} from "nostr-tools";
|
||||
import Notification from './Notification';
|
||||
import { format, isSameDay, parseISO } from 'date-fns';
|
||||
|
||||
interface NotificationsProps {
|
||||
pubkey: string;
|
||||
@@ -52,10 +53,46 @@ const Notifications: React.FC<NotificationsProps> = ({ pubkey }) => {
|
||||
// }
|
||||
// });
|
||||
|
||||
// Create a combined and properly sorted array of all notifications
|
||||
// const allNotifications = [...(zaps || []), ...(reactions || [])].sort(
|
||||
// (a, b) => (b.created_at || 0) - (a.created_at || 0)
|
||||
// );
|
||||
// Sort all notifications by date (newest first)
|
||||
const sortedEvents = [...events].sort(
|
||||
(a, b) => (b.created_at || 0) - (a.created_at || 0)
|
||||
);
|
||||
|
||||
// Group notifications by date
|
||||
const groupedNotifications = () => {
|
||||
const groups: { [key: string]: typeof events } = {};
|
||||
|
||||
sortedEvents.forEach(event => {
|
||||
const date = new Date(event.created_at * 1000);
|
||||
const dateKey = format(date, 'yyyy-MM-dd');
|
||||
|
||||
if (!groups[dateKey]) {
|
||||
groups[dateKey] = [];
|
||||
}
|
||||
|
||||
groups[dateKey].push(event);
|
||||
});
|
||||
|
||||
return groups;
|
||||
};
|
||||
|
||||
// Get formatted date heading based on date
|
||||
const getDateHeading = (dateStr: string) => {
|
||||
const date = parseISO(dateStr);
|
||||
const today = new Date();
|
||||
const yesterday = new Date();
|
||||
yesterday.setDate(yesterday.getDate() - 1);
|
||||
|
||||
if (isSameDay(date, today)) {
|
||||
return "Today";
|
||||
} else if (isSameDay(date, yesterday)) {
|
||||
return "Yesterday";
|
||||
} else {
|
||||
return format(date, 'EEEE, MMMM d, yyyy');
|
||||
}
|
||||
};
|
||||
|
||||
const notificationGroups = groupedNotifications();
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -67,8 +104,19 @@ const Notifications: React.FC<NotificationsProps> = ({ pubkey }) => {
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{events.length > 0 ? (
|
||||
events.map((notification, index) => (
|
||||
<Notification key={index} event={notification} />
|
||||
Object.keys(notificationGroups).map(dateKey => (
|
||||
<div key={dateKey} className="mb-6">
|
||||
<div className="sticky top-0 bg-background/95 backdrop-blur-sm py-2 mb-2 border-b">
|
||||
<h3 className="text-sm font-medium text-muted-foreground">
|
||||
{getDateHeading(dateKey)}
|
||||
</h3>
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
{notificationGroups[dateKey].map((notification) => (
|
||||
<Notification key={notification.id} event={notification} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
) : (
|
||||
<div className="text-center py-4 text-muted-foreground">No notifications yet</div>
|
||||
|
||||
Reference in New Issue
Block a user