"use client"; import { forwardRef } from "react"; import { Badge } from "@/components/ui/badge"; import { Card } from "@/components/ui/card"; import { BookOpen, Calendar, Chrome, Clock, Code, FileText, Globe, Hash, Heart, Highlighter, Link, Rss, Smartphone, Upload, Zap, } from "lucide-react"; import { z } from "zod"; import { zBookmarkSourceSchema } from "@karakeep/shared/types/bookmarks"; import { zWrappedStatsResponseSchema } from "@karakeep/shared/types/users"; type WrappedStats = z.infer; type BookmarkSource = z.infer; interface WrappedContentProps { stats: WrappedStats; userName?: string; } const dayNames = [ "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", ]; const monthNames = [ "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", ]; function formatSourceName(source: BookmarkSource | null): string { if (!source) return "Unknown"; const sourceMap: Record = { api: "API", web: "Web", extension: "Browser Extension", cli: "CLI", mobile: "Mobile App", singlefile: "SingleFile", rss: "RSS Feed", import: "Import", }; return sourceMap[source]; } function getSourceIcon(source: BookmarkSource | null, className = "h-5 w-5") { const iconProps = { className }; switch (source) { case "api": return ; case "web": return ; case "extension": return ; case "cli": return ; case "mobile": return ; case "singlefile": return ; case "rss": return ; case "import": return ; default: return ; } } export const WrappedContent = forwardRef( ({ stats, userName }, ref) => { const maxMonthlyCount = Math.max( ...stats.monthlyActivity.map((m) => m.count), ); return (
{/* Header */}

Your {stats.year} Wrapped

A Year in Karakeep

{userName && (

{userName}

)}

You saved

{stats.totalBookmarks}

{stats.totalBookmarks === 1 ? "item" : "items"} this year

{/* First Bookmark */} {stats.firstBookmark && (

First Bookmark of {stats.year}

{new Date( stats.firstBookmark.createdAt, ).toLocaleDateString("en-US", { month: "long", day: "numeric", })}

{stats.firstBookmark.title && (

“{stats.firstBookmark.title}”

)}
)} {/* Activity + Peak */}

Activity Highlights

{stats.mostActiveDay && (

Most Active Day

{new Date(stats.mostActiveDay.date).toLocaleDateString( "en-US", { month: "short", day: "numeric", }, )}

{stats.mostActiveDay.count}{" "} {stats.mostActiveDay.count === 1 ? "save" : "saves"}

)}

Peak Hour

{stats.peakHour === 0 ? "12 AM" : stats.peakHour < 12 ? `${stats.peakHour} AM` : stats.peakHour === 12 ? "12 PM" : `${stats.peakHour - 12} PM`}

Peak Day

{dayNames[stats.peakDayOfWeek]}

{/* Top Lists */} {(stats.topDomains.length > 0 || stats.topTags.length > 0) && (

Top Lists

{stats.topDomains.length > 0 && (

Sites

{stats.topDomains.map((domain, index) => (
{index + 1}
{domain.domain}
{domain.count}
))}
)} {stats.topTags.length > 0 && (

Tags

{stats.topTags.map((tag, index) => (
{index + 1}
{tag.name}
{tag.count}
))}
)}
)} {/* Bookmarks by Source */} {stats.bookmarksBySource.length > 0 && (

How You Save

{stats.bookmarksBySource.map((source) => (
{getSourceIcon(source.source, "h-4 w-4")} {formatSourceName(source.source)}
{source.count}
))}
)} {/* Monthly Activity */}

Your Year in Saves

{stats.monthlyActivity.map((month) => (
{monthNames[month.month - 1]}
0 ? (month.count / maxMonthlyCount) * 100 : 0}%`, }} />
{month.count}
))}
{/* Summary Stats */}

{stats.totalFavorites}

Favorites

{stats.totalTags}

Tags Created

{stats.totalHighlights}

Highlights

{stats.bookmarksByType.link}

Links

{stats.bookmarksByType.text}

Notes

{stats.bookmarksByType.asset}

Assets

{/* Footer */}
Made with Karakeep
); }, ); WrappedContent.displayName = "WrappedContent";