From 04f03604c2061fe4bf9087d21d03543531650697 Mon Sep 17 00:00:00 2001 From: arsam Date: Fri, 18 Sep 2026 23:17:19 +0330 Subject: [PATCH] Import source: vault/app/snippets/[id]/page Import the existing local project snapshot. This commit organizes existing files; it does not represent newly developed functionality. --- vault/app/snippets/[id]/page.tsx | 192 +++++++++++++++++++++++++++++++ 1 file changed, 192 insertions(+) create mode 100644 vault/app/snippets/[id]/page.tsx diff --git a/vault/app/snippets/[id]/page.tsx b/vault/app/snippets/[id]/page.tsx new file mode 100644 index 0000000..a8d978d --- /dev/null +++ b/vault/app/snippets/[id]/page.tsx @@ -0,0 +1,192 @@ +"use client"; + +import { useEffect, useState, useCallback } from "react"; +import { useParams, useRouter } from "next/navigation"; +import { motion } from "framer-motion"; +import { Lock, Link as LinkIcon, Clock, Edit, Trash2, Share2, ArrowLeft, Copy, Check, FileText } from "lucide-react"; +import Link from "next/link"; +import { Button } from "@/components/ui/button"; +import { Badge } from "@/components/ui/badge"; +import { Separator } from "@/components/ui/separator"; +import { CodeViewer } from "@/components/code-viewer"; +import { api } from "@/lib/api-client"; +import { Snippet } from "@/lib/types"; +import { formatExpiry } from "@/lib/expiry-format"; +import { toast } from "sonner"; +import { cn } from "@/lib/utils"; + +export default function ViewSnippetPage() { + const params = useParams(); + const router = useRouter(); + const id = params.id as string; + const [snippet, setSnippet] = useState(null); + const [loading, setLoading] = useState(true); + const [linkCopied, setLinkCopied] = useState(false); + + useEffect(() => { + api.getSnippet(id) + .then(({ snippet }) => setSnippet(snippet)) + .catch((error) => { + setSnippet(null); + if (error instanceof Error && error.message === "Authentication required") router.push("/login"); + }) + .finally(() => setLoading(false)); + }, [id, router]); + + const handleDelete = useCallback(() => { + api.deleteSnippet(id) + .then(() => { + toast.success("Snippet deleted"); + router.push("/snippets"); + }) + .catch((error) => toast.error(error instanceof Error ? error.message : "Failed to delete snippet")); + }, [id, router]); + + const shareUrl = typeof window !== "undefined" ? `${window.location.origin}/s/${id}` : `/s/${id}`; + const rawUrl = typeof window !== "undefined" ? `${window.location.origin}/raw/${id}` : `/raw/${id}`; + + const handleCopyLink = useCallback(async () => { + await navigator.clipboard.writeText(shareUrl); + setLinkCopied(true); + toast.success("Share link copied!"); + setTimeout(() => setLinkCopied(false), 2000); + }, [shareUrl]); + + if (loading) { + return ( +
+
+
+
+
+
+
+ ); + } + + if (!snippet) { + return ( +
+

Snippet not found

+

This snippet may have expired or been deleted.

+ + + +
+ ); + } + + const expiryLabel = formatExpiry(snippet.expiresAt); + + return ( +
+ + + + + +
+
+
+

{snippet.title}

+ + {snippet.privacy === "password" ? ( + <> Password + ) : ( + <> Secret + )} + +
+
+ {snippet.language} + + + {new Date(snippet.createdAt).toLocaleDateString()} + + {expiryLabel && ( + + + {expiryLabel} + + )} +
+
+ +
+ + + + +
+
+ + {/* Share URL bar */} +
+ + {shareUrl} +
+ + + + +
+
+ + {snippet.tags.length > 0 && ( +
+ {snippet.tags.map((tag) => ( + + {tag} + + ))} +
+ )} + + + + +
+
+ ); +}