diff --git a/src/components/nostr/calendar/CalendarStatusBadge.tsx b/src/components/nostr/calendar/CalendarStatusBadge.tsx new file mode 100644 index 0000000..a42ffc9 --- /dev/null +++ b/src/components/nostr/calendar/CalendarStatusBadge.tsx @@ -0,0 +1,71 @@ +import type { LucideIcon } from "lucide-react"; +import { Clock, CheckCircle, CalendarDays, CalendarClock } from "lucide-react"; +import { cn } from "@/lib/utils"; +import type { CalendarEventStatus } from "@/lib/calendar-event"; + +interface StatusConfig { + label: string; + className: string; + icon: LucideIcon; +} + +const STATUS_CONFIG: Record< + CalendarEventStatus, + { label: string; className: string } +> = { + upcoming: { label: "upcoming", className: "text-blue-500" }, + ongoing: { label: "now", className: "text-green-500" }, + past: { label: "past", className: "text-muted-foreground" }, +}; + +interface CalendarStatusBadgeProps { + status: CalendarEventStatus; + /** Icon variant - use CalendarDays for date events, CalendarClock for time events */ + variant?: "date" | "time"; + /** Size variant - sm for feed, md for detail views */ + size?: "sm" | "md"; +} + +/** + * Status badge for calendar events (NIP-52) + * Displays the current status (upcoming/now/past) with an appropriate icon + */ +export function CalendarStatusBadge({ + status, + variant = "date", + size = "sm", +}: CalendarStatusBadgeProps) { + const baseConfig = STATUS_CONFIG[status]; + const OngoingIcon = variant === "time" ? CalendarClock : CalendarDays; + + const config: StatusConfig = { + ...baseConfig, + icon: + status === "ongoing" + ? OngoingIcon + : status === "upcoming" + ? Clock + : CheckCircle, + }; + + const Icon = config.icon; + + const sizeClasses = { + sm: { text: "text-xs", icon: "w-3 h-3", gap: "gap-1" }, + md: { text: "text-sm", icon: "w-4 h-4", gap: "gap-1" }, + }[size]; + + return ( +
+ + {config.label} +
+ ); +} diff --git a/src/components/nostr/kinds/CalendarDateEventDetailRenderer.tsx b/src/components/nostr/kinds/CalendarDateEventDetailRenderer.tsx index ace3e00..48d464f 100644 --- a/src/components/nostr/kinds/CalendarDateEventDetailRenderer.tsx +++ b/src/components/nostr/kinds/CalendarDateEventDetailRenderer.tsx @@ -3,58 +3,12 @@ import { parseDateCalendarEvent, getDateEventStatus, formatDateRange, - type CalendarEventStatus, } from "@/lib/calendar-event"; import { UserName } from "../UserName"; import { MarkdownContent } from "../MarkdownContent"; +import { CalendarStatusBadge } from "../calendar/CalendarStatusBadge"; import { Label } from "@/components/ui/label"; -import { - CalendarDays, - MapPin, - Users, - Clock, - CheckCircle, - Hash, - ExternalLink, -} from "lucide-react"; -import { cn } from "@/lib/utils"; - -/** - * Status badge for calendar events - */ -function CalendarStatusBadge({ status }: { status: CalendarEventStatus }) { - const config = { - upcoming: { - label: "upcoming", - className: "text-blue-500", - icon: Clock, - }, - ongoing: { - label: "now", - className: "text-green-500", - icon: CalendarDays, - }, - past: { - label: "past", - className: "text-muted-foreground", - icon: CheckCircle, - }, - }[status]; - - const Icon = config.icon; - - return ( -
- - {config.label} -
- ); -} +import { MapPin, Users, Hash, ExternalLink } from "lucide-react"; /** * Detail renderer for Kind 31922 - Date-Based Calendar Event @@ -75,7 +29,7 @@ export function CalendarDateEventDetailRenderer({
{/* Title */}

- {parsed.title || "Untitled Event"} + {parsed.title || parsed.identifier}

{/* Date and Status: date left, badge right */} @@ -83,7 +37,7 @@ export function CalendarDateEventDetailRenderer({ {dateRange && ( {dateRange} )} - + {/* Organizer */} diff --git a/src/components/nostr/kinds/CalendarDateEventRenderer.tsx b/src/components/nostr/kinds/CalendarDateEventRenderer.tsx index a4a3865..cd0fb4c 100644 --- a/src/components/nostr/kinds/CalendarDateEventRenderer.tsx +++ b/src/components/nostr/kinds/CalendarDateEventRenderer.tsx @@ -2,53 +2,15 @@ import { parseDateCalendarEvent, getDateEventStatus, formatDateRange, - type CalendarEventStatus, } from "@/lib/calendar-event"; import { BaseEventContainer, ClickableEventTitle, type BaseEventProps, } from "./BaseEventRenderer"; +import { CalendarStatusBadge } from "../calendar/CalendarStatusBadge"; import { Label } from "@/components/ui/label"; -import { CalendarDays, MapPin, Users, Clock, CheckCircle } from "lucide-react"; -import { cn } from "@/lib/utils"; - -/** - * Status badge for calendar events - */ -function CalendarStatusBadge({ status }: { status: CalendarEventStatus }) { - const config = { - upcoming: { - label: "upcoming", - className: "text-blue-500", - icon: Clock, - }, - ongoing: { - label: "now", - className: "text-green-500", - icon: CalendarDays, - }, - past: { - label: "past", - className: "text-muted-foreground", - icon: CheckCircle, - }, - }[status]; - - const Icon = config.icon; - - return ( -
- - {config.label} -
- ); -} +import { MapPin, Users } from "lucide-react"; /** * Renderer for Kind 31922 - Date-Based Calendar Event @@ -67,7 +29,7 @@ export function CalendarDateEventRenderer({ event }: BaseEventProps) { event={event} className="text-lg font-semibold text-foreground" > - {parsed.title || "Untitled Event"} + {parsed.title || parsed.identifier} {/* Date and status: time left, badge right */} @@ -75,7 +37,7 @@ export function CalendarDateEventRenderer({ event }: BaseEventProps) { {dateRange && ( {dateRange} )} - + {/* Description preview */} diff --git a/src/components/nostr/kinds/CalendarTimeEventDetailRenderer.tsx b/src/components/nostr/kinds/CalendarTimeEventDetailRenderer.tsx index bc12390..e3c088e 100644 --- a/src/components/nostr/kinds/CalendarTimeEventDetailRenderer.tsx +++ b/src/components/nostr/kinds/CalendarTimeEventDetailRenderer.tsx @@ -3,59 +3,12 @@ import { parseTimeCalendarEvent, getTimeEventStatus, formatTimeRange, - type CalendarEventStatus, } from "@/lib/calendar-event"; import { UserName } from "../UserName"; import { MarkdownContent } from "../MarkdownContent"; +import { CalendarStatusBadge } from "../calendar/CalendarStatusBadge"; import { Label } from "@/components/ui/label"; -import { - CalendarClock, - MapPin, - Users, - Clock, - CheckCircle, - Hash, - ExternalLink, - Globe, -} from "lucide-react"; -import { cn } from "@/lib/utils"; - -/** - * Status badge for calendar events - */ -function CalendarStatusBadge({ status }: { status: CalendarEventStatus }) { - const config = { - upcoming: { - label: "upcoming", - className: "text-blue-500", - icon: Clock, - }, - ongoing: { - label: "now", - className: "text-green-500", - icon: CalendarClock, - }, - past: { - label: "past", - className: "text-muted-foreground", - icon: CheckCircle, - }, - }[status]; - - const Icon = config.icon; - - return ( -
- - {config.label} -
- ); -} +import { MapPin, Users, Hash, ExternalLink, Globe } from "lucide-react"; /** * Detail renderer for Kind 31923 - Time-Based Calendar Event @@ -81,7 +34,7 @@ export function CalendarTimeEventDetailRenderer({
{/* Title */}

- {parsed.title || "Untitled Event"} + {parsed.title || parsed.identifier}

{/* Time and Status: time left, badge right */} @@ -89,7 +42,7 @@ export function CalendarTimeEventDetailRenderer({ {timeRange && ( {timeRange} )} - + {/* Timezone indicator */} diff --git a/src/components/nostr/kinds/CalendarTimeEventRenderer.tsx b/src/components/nostr/kinds/CalendarTimeEventRenderer.tsx index 524ce52..968420b 100644 --- a/src/components/nostr/kinds/CalendarTimeEventRenderer.tsx +++ b/src/components/nostr/kinds/CalendarTimeEventRenderer.tsx @@ -2,53 +2,15 @@ import { parseTimeCalendarEvent, getTimeEventStatus, formatTimeRange, - type CalendarEventStatus, } from "@/lib/calendar-event"; import { BaseEventContainer, ClickableEventTitle, type BaseEventProps, } from "./BaseEventRenderer"; +import { CalendarStatusBadge } from "../calendar/CalendarStatusBadge"; import { Label } from "@/components/ui/label"; -import { CalendarClock, MapPin, Users, Clock, CheckCircle } from "lucide-react"; -import { cn } from "@/lib/utils"; - -/** - * Status badge for calendar events - */ -function CalendarStatusBadge({ status }: { status: CalendarEventStatus }) { - const config = { - upcoming: { - label: "upcoming", - className: "text-blue-500", - icon: Clock, - }, - ongoing: { - label: "now", - className: "text-green-500", - icon: CalendarClock, - }, - past: { - label: "past", - className: "text-muted-foreground", - icon: CheckCircle, - }, - }[status]; - - const Icon = config.icon; - - return ( -
- - {config.label} -
- ); -} +import { MapPin, Users } from "lucide-react"; /** * Renderer for Kind 31923 - Time-Based Calendar Event @@ -72,7 +34,7 @@ export function CalendarTimeEventRenderer({ event }: BaseEventProps) { event={event} className="text-lg font-semibold text-foreground" > - {parsed.title || "Untitled Event"} + {parsed.title || parsed.identifier} {/* Time and status: time left, badge right */} @@ -80,7 +42,7 @@ export function CalendarTimeEventRenderer({ event }: BaseEventProps) { {timeRange && ( {timeRange} )} - + {/* Description preview */} diff --git a/src/lib/calendar-event.ts b/src/lib/calendar-event.ts index 974d64c..5ee6876 100644 --- a/src/lib/calendar-event.ts +++ b/src/lib/calendar-event.ts @@ -1,5 +1,5 @@ import type { NostrEvent } from "@/types/nostr"; -import { getTagValue } from "applesauce-core/helpers"; +import { getTagValue, getOrComputeCachedValue } from "applesauce-core/helpers"; /** * Participant in a calendar event (NIP-52) @@ -14,7 +14,6 @@ export interface CalendarParticipant { * Parsed Date-Based Calendar Event (kind 31922) */ export interface ParsedDateCalendarEvent { - event: NostrEvent; identifier: string; title: string; start: string; // YYYY-MM-DD @@ -31,7 +30,6 @@ export interface ParsedDateCalendarEvent { * Parsed Time-Based Calendar Event (kind 31923) */ export interface ParsedTimeCalendarEvent { - event: NostrEvent; identifier: string; title: string; start: number; // Unix timestamp @@ -51,6 +49,10 @@ export interface ParsedTimeCalendarEvent { */ export type CalendarEventStatus = "upcoming" | "ongoing" | "past"; +// Caching symbols for parsed calendar events +const ParsedDateCalendarEventSymbol = Symbol("ParsedDateCalendarEvent"); +const ParsedTimeCalendarEventSymbol = Symbol("ParsedTimeCalendarEvent"); + /** * Get all values for a given tag name */ @@ -74,12 +76,12 @@ function parseParticipants(event: NostrEvent): CalendarParticipant[] { /** * Parse a kind 31922 Date-Based Calendar Event + * Results are cached on the event object for performance */ export function parseDateCalendarEvent( event: NostrEvent, ): ParsedDateCalendarEvent { - return { - event, + return getOrComputeCachedValue(event, ParsedDateCalendarEventSymbol, () => ({ identifier: getTagValue(event, "d") || "", title: getTagValue(event, "title") || "", start: getTagValue(event, "start") || "", @@ -90,33 +92,35 @@ export function parseDateCalendarEvent( participants: parseParticipants(event), hashtags: getTagValues(event, "t"), references: getTagValues(event, "r"), - }; + })); } /** * Parse a kind 31923 Time-Based Calendar Event + * Results are cached on the event object for performance */ export function parseTimeCalendarEvent( event: NostrEvent, ): ParsedTimeCalendarEvent { - const startStr = getTagValue(event, "start"); - const endStr = getTagValue(event, "end"); + return getOrComputeCachedValue(event, ParsedTimeCalendarEventSymbol, () => { + const startStr = getTagValue(event, "start"); + const endStr = getTagValue(event, "end"); - return { - event, - identifier: getTagValue(event, "d") || "", - title: getTagValue(event, "title") || "", - start: startStr ? parseInt(startStr, 10) : 0, - end: endStr ? parseInt(endStr, 10) : undefined, - startTzid: getTagValue(event, "start_tzid") || undefined, - endTzid: getTagValue(event, "end_tzid") || undefined, - description: event.content || "", - locations: getTagValues(event, "location"), - geohash: getTagValue(event, "g") || undefined, - participants: parseParticipants(event), - hashtags: getTagValues(event, "t"), - references: getTagValues(event, "r"), - }; + return { + identifier: getTagValue(event, "d") || "", + title: getTagValue(event, "title") || "", + start: startStr ? parseInt(startStr, 10) : 0, + end: endStr ? parseInt(endStr, 10) : undefined, + startTzid: getTagValue(event, "start_tzid") || undefined, + endTzid: getTagValue(event, "end_tzid") || undefined, + description: event.content || "", + locations: getTagValues(event, "location"), + geohash: getTagValue(event, "g") || undefined, + participants: parseParticipants(event), + hashtags: getTagValues(event, "t"), + references: getTagValues(event, "r"), + }; + }); } /**