mirror of
https://github.com/lumina-rocks/lumina.git
synced 2026-06-04 09:41:32 +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>
|
||||
|
||||
11
package-lock.json
generated
11
package-lock.json
generated
@@ -29,6 +29,7 @@
|
||||
"bolt11": "^1.4.1",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"date-fns": "^4.1.0",
|
||||
"embla-carousel-react": "^8.0.0-rc21",
|
||||
"html5-qrcode": "^2.3.8",
|
||||
"light-bolt11-decoder": "^3.1.1",
|
||||
@@ -6033,6 +6034,16 @@
|
||||
"dev": true,
|
||||
"license": "BSD-2-Clause"
|
||||
},
|
||||
"node_modules/date-fns": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz",
|
||||
"integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/kossnocorp"
|
||||
}
|
||||
},
|
||||
"node_modules/debug": {
|
||||
"version": "4.3.4",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
"bolt11": "^1.4.1",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"date-fns": "^4.1.0",
|
||||
"embla-carousel-react": "^8.0.0-rc21",
|
||||
"html5-qrcode": "^2.3.8",
|
||||
"light-bolt11-decoder": "^3.1.1",
|
||||
|
||||
Reference in New Issue
Block a user