feat: add date validation and safe ISO string conversion for improved date handling

This commit is contained in:
2025-11-17 21:41:39 +01:00
parent 420bab49d1
commit 9cba26a82f
6 changed files with 83 additions and 46 deletions

View File

@@ -7,6 +7,7 @@ import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
import { Calendar } from 'lucide-react';
import { useAuthor } from '@/hooks/useAuthor';
import { genUserName } from '@/lib/genUserName';
import { isValidDate, toISOStringSafe } from '@/lib/date';
interface ArticlePreviewProps {
post: NostrEvent;
@@ -48,6 +49,8 @@ export function ArticlePreview({ post, variant = 'default', showAuthor = true }:
? { month: 'short', day: 'numeric', year: 'numeric' }
: { year: 'numeric', month: 'long', day: 'numeric' };
const valid = isValidDate(date);
return (
<Link to={`/${naddr}`}>
<Card className="overflow-hidden hover:shadow-lg transition-shadow h-full flex flex-col">
@@ -71,12 +74,14 @@ export function ArticlePreview({ post, variant = 'default', showAuthor = true }:
)}
</CardHeader>
<CardContent className="pt-0">
<div className={`flex items-center gap-2 text-xs text-muted-foreground ${showAuthor || hashtags.length > 0 ? 'mb-3' : ''}`}>
<Calendar className="h-3 w-3" />
<time dateTime={date.toISOString()}>
{date.toLocaleDateString('en-US', dateFormat as Intl.DateTimeFormatOptions)}
</time>
</div>
{valid && (
<div className={`flex items-center gap-2 text-xs text-muted-foreground ${showAuthor || hashtags.length > 0 ? 'mb-3' : ''}`}>
<Calendar className="h-3 w-3" />
<time dateTime={toISOStringSafe(date)}>
{date.toLocaleDateString('en-US', dateFormat as Intl.DateTimeFormatOptions)}
</time>
</div>
)}
{showAuthor && (
<div className={`flex items-center gap-2 ${hashtags.length > 0 ? 'mb-3' : ''}`}>
<Avatar className="h-6 w-6">

View File

@@ -17,6 +17,7 @@ import { Separator } from '@/components/ui/separator';
import { Calendar, Heart, Edit, ArrowLeft, Share2, Check, Code } from 'lucide-react';
import { genUserName } from '@/lib/genUserName';
import { calculateReadingTime } from '@/lib/calculateReadingTime';
import { isValidDate, toISOStringSafe } from '@/lib/date';
import { useToast } from '@/hooks/useToast';
import { useState } from 'react';
import {
@@ -108,6 +109,8 @@ export function ArticleView({ post }: ArticleViewProps) {
}
};
const validDate = isValidDate(date);
return (
<div className="min-h-screen">
<ArticleProgressBar />
@@ -144,16 +147,18 @@ export function ArticleView({ post }: ArticleViewProps) {
</Avatar>
<div>
<div className="font-semibold">{displayName}</div>
<div className="flex items-center gap-2 text-sm text-muted-foreground">
<Calendar className="h-3 w-3" />
<time dateTime={date.toISOString()}>
{date.toLocaleDateString('en-US', {
year: 'numeric',
month: 'long',
day: 'numeric',
})}
</time>
</div>
{validDate && (
<div className="flex items-center gap-2 text-sm text-muted-foreground">
<Calendar className="h-3 w-3" />
<time dateTime={toISOStringSafe(date)}>
{date.toLocaleDateString('en-US', {
year: 'numeric',
month: 'long',
day: 'numeric',
})}
</time>
</div>
)}
</div>
</Link>

13
src/lib/date.ts Normal file
View File

@@ -0,0 +1,13 @@
export function isValidDate(date: Date | null | undefined): boolean {
if (!date) return false;
return !isNaN(date.getTime());
}
export function toISOStringSafe(date: Date | null | undefined): string {
if (!isValidDate(date as Date)) return "";
try {
return (date as Date).toISOString();
} catch {
return "";
}
}

View File

@@ -7,6 +7,7 @@ import { ArticleView } from '@/components/ArticleView';
import { Skeleton } from '@/components/ui/skeleton';
import NotFound from '@/pages/NotFound';
import { genUserName } from '@/lib/genUserName';
import { toISOStringSafe } from '@/lib/date';
export default function ArticlePage() {
const { nip19: naddr } = useParams<{ nip19: string }>();
@@ -71,11 +72,16 @@ export default function ArticlePage() {
ogImage: image || `${siteUrl}/icon-512.png`,
ogSiteName: 'zelo.news',
// Article-specific OG tags
...(post && isValidNaddr && {
articlePublishedTime: date.toISOString(),
articleAuthor: [authorName],
...(hashtags.length > 0 && { articleTag: hashtags }),
}),
...(post && isValidNaddr && (() => {
const iso = toISOStringSafe(date);
return iso
? {
articlePublishedTime: iso,
articleAuthor: [authorName],
...(hashtags.length > 0 && { articleTag: hashtags }),
}
: {};
})()),
// Twitter Card tags
twitterCard: 'summary_large_image',
twitterTitle: title,

View File

@@ -13,6 +13,7 @@ import { ArrowLeft, Calendar, Hash } from 'lucide-react';
import { genUserName } from '@/lib/genUserName';
import type { NostrEvent } from '@nostrify/nostrify';
import NotFound from './NotFound';
import { isValidDate, toISOStringSafe } from '@/lib/date';
interface EventPageProps {
eventId: string;
@@ -118,6 +119,7 @@ export function EventPage({ eventId, relayHints, authorPubkey, kind }: EventPage
}
const date = new Date(event.created_at * 1000);
const validDate = isValidDate(date);
return (
<div className="min-h-screen">
@@ -141,19 +143,21 @@ export function EventPage({ eventId, relayHints, authorPubkey, kind }: EventPage
<Hash className="h-3 w-3" />
Kind {event.kind}
</Badge>
<time
dateTime={date.toISOString()}
className="text-sm text-muted-foreground flex items-center gap-1"
>
<Calendar className="h-3 w-3" />
{date.toLocaleDateString('en-US', {
year: 'numeric',
month: 'long',
day: 'numeric',
hour: '2-digit',
minute: '2-digit',
})}
</time>
{validDate && (
<time
dateTime={toISOStringSafe(date)}
className="text-sm text-muted-foreground flex items-center gap-1"
>
<Calendar className="h-3 w-3" />
{date.toLocaleDateString('en-US', {
year: 'numeric',
month: 'long',
day: 'numeric',
hour: '2-digit',
minute: '2-digit',
})}
</time>
)}
</div>
<Link

View File

@@ -18,6 +18,7 @@ import { Heart, MessageCircle, ArrowLeft } from 'lucide-react';
import { genUserName } from '@/lib/genUserName';
import type { NostrEvent } from '@nostrify/nostrify';
import NotFound from './NotFound';
import { isValidDate, toISOStringSafe } from '@/lib/date';
interface NotePageProps {
eventId: string;
@@ -117,6 +118,7 @@ export function NotePage({ eventId }: NotePageProps) {
}
const date = new Date(note.created_at * 1000);
const validDate = isValidDate(date);
return (
<div className="min-h-screen">
@@ -145,18 +147,20 @@ export function NotePage({ eventId }: NotePageProps) {
</Avatar>
<div>
<div className="font-semibold">{displayName}</div>
<time
dateTime={date.toISOString()}
className="text-sm text-muted-foreground"
>
{date.toLocaleDateString('en-US', {
year: 'numeric',
month: 'long',
day: 'numeric',
hour: '2-digit',
minute: '2-digit',
})}
</time>
{validDate && (
<time
dateTime={toISOStringSafe(date)}
className="text-sm text-muted-foreground"
>
{date.toLocaleDateString('en-US', {
year: 'numeric',
month: 'long',
day: 'numeric',
hour: '2-digit',
minute: '2-digit',
})}
</time>
)}
</div>
</Link>
</CardHeader>