Files
2026-06-23 14:21:59 +05:30

116 lines
4.2 KiB
TypeScript

"use client";
import { useEffect, useState } from "react";
import Link from "next/link";
import { Trash2, ExternalLink } from "lucide-react";
import { bookmarks, SocialError, type Bookmark } from "@/lib/social";
import { useAuth } from "@/components/auth/auth-provider";
import { appendHighlight, hasHighlight } from "@/lib/text-highlight";
import { displaySourceLabel } from "@/lib/source-reference";
export default function MyBookmarksPage() {
const { user } = useAuth();
const [list, setList] = useState<Bookmark[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
bookmarks
.list()
.then((r) => setList(r ?? []))
.catch((err) =>
setError(
err instanceof SocialError ? err.message : "Could not load bookmarks.",
),
)
.finally(() => setLoading(false));
}, []);
async function remove(id: string) {
const prev = list;
setList((rows) => rows.filter((b) => b.id !== id));
try {
await bookmarks.delete(id);
} catch (err) {
setList(prev);
setError(err instanceof SocialError ? err.message : "Could not delete.");
}
}
// Effective cap from the server (plan default merged with any admin
// override). -1 would mean unlimited (bookmarks aren't, today).
const limit = user?.limits?.bookmarks ?? 1;
if (loading) return <p className="text-sm text-muted-foreground">Loading…</p>;
return (
<div className="space-y-5">
<div className="rounded-lg bg-card/60 ring-1 ring-foreground/8 px-4 py-3 text-sm flex items-center justify-between">
<span className="text-muted-foreground">
Bookmarks used:{" "}
<span className="text-foreground tabular-nums">
{list.length} / {limit}
</span>
</span>
{user?.plan !== "premium" && list.length >= limit ? (
<span className="text-xs text-amber-600 dark:text-amber-400">
Upgrade to premium for 5 bookmarks
</span>
) : null}
</div>
{error ? <p className="text-sm text-destructive">{error}</p> : null}
{list.length === 0 ? (
<p className="text-sm text-muted-foreground">
No bookmarks yet. Select text anywhere and use “Add bookmark” in the
more menu.
</p>
) : (
<ul className="space-y-3">
{list.map((b) => {
// If the stored URL doesn't already carry a `?hl=` (old data, or
// a bookmark saved without a selection), synthesise one from the
// excerpt so clicking still scrolls to + highlights the passage.
const href =
hasHighlight(b.source_url) || !b.excerpt
? b.source_url
: appendHighlight(b.source_url, b.excerpt);
return (
<li
key={b.id}
className="rounded-xl bg-card ring-1 ring-foreground/10 p-4 flex items-start gap-3"
>
<div className="flex-1 min-w-0">
<Link
href={href}
className="font-[family-name:var(--font-title)] text-base text-foreground hover:underline inline-flex items-center gap-1"
>
<ExternalLink className="size-3.5" />
{displaySourceLabel(b.source_url, b.source_title)}
</Link>
{b.excerpt ? (
<p className="mt-1 text-sm text-muted-foreground italic line-clamp-2">
“{b.excerpt}”
</p>
) : null}
<p className="text-xs text-muted-foreground mt-2">
Saved {new Date(b.created_at).toLocaleDateString()}
</p>
</div>
<button
onClick={() => remove(b.id)}
aria-label="Delete bookmark"
className="size-9 inline-flex items-center justify-center rounded-md text-muted-foreground hover:text-destructive hover:bg-destructive/10 transition-colors"
>
<Trash2 className="size-4" />
</button>
</li>
);
})}
</ul>
)}
</div>
);
}