import Link from "next/link"; import { notFound } from "next/navigation"; import { and, eq, isNull } from "drizzle-orm"; import { auth } from "@/auth"; import { db } from "@/lib/db/client"; import { snippets, projects } from "@/lib/db/schema"; import { updateSnippetAction, deleteSnippetAction } from "@/lib/snippet-actions"; import { getSnippet, type SnippetWithProjectKey } from "@/lib/snippets"; import { Container, PageHeader } from "@/app/_components/ui/container"; import { Card, CardBody, CardHeader } from "@/app/_components/ui/card"; import { Input, Textarea, Label } from "@/app/_components/ui/input"; import { Button } from "@/app/_components/ui/button"; import { Badge } from "@/app/_components/ui/badge"; export const dynamic = "force-dynamic"; interface SiblingHit { scope: "project" | "user"; projectKey: string | null; } /** * When a snippet name exists in more than one scope (e.g. a user-scope * default plus one or more project-scope variants), we need to either * disambiguate by query string or, if no hint is given, show a picker. */ async function findAllMatches(userId: string, name: string): Promise { const rows = await db .select({ scope: snippets.scope, projectKey: projects.key, }) .from(snippets) .leftJoin(projects, eq(snippets.projectId, projects.id)) .where(and(eq(snippets.userId, userId), eq(snippets.name, name), isNull(snippets.deletedAt))); return rows as SiblingHit[]; } export default async function SnippetDetailPage({ params, searchParams, }: { params: Promise<{ name: string }>; searchParams: Promise<{ scope?: string; project?: string; edit?: string }>; }) { const session = await auth(); const userId = session!.user.id; const { name: rawName } = await params; const name = decodeURIComponent(rawName); const sp = await searchParams; const scope: "project" | "user" | undefined = sp.scope === "user" || sp.scope === "project" ? sp.scope : undefined; const project = sp.project?.trim() || undefined; const isEditing = sp.edit === "1"; const siblings = await findAllMatches(userId, name); if (siblings.length === 0) notFound(); // If multiple matches and the user hasn't disambiguated, show a picker. if (!scope && siblings.length > 1) { return ( } /> {siblings.map((s, i) => { const params = new URLSearchParams({ scope: s.scope }); if (s.scope === "project" && s.projectKey) { params.set("project", s.projectKey); } return ( 0 ? "border-t border-border" : ""}`} >
{s.scope} {s.projectKey ? ( {s.projectKey} ) : ( applies everywhere )}
); })}
); } const snippet: SnippetWithProjectKey | null = await getSnippet(userId, { name, scope, projectKey: project, }); if (!snippet) notFound(); return ( {snippet.scope} {snippet.projectKey ? ` · ${snippet.projectKey}` : ""} } actions={ <> {!isEditing ? ( ) : null} } /> {snippet.scope} {snippet.projectKey ? {snippet.projectKey} : null} · Created {new Date(snippet.createdAt).toLocaleString()} {snippet.updatedAt.getTime() !== snippet.createdAt.getTime() ? ( · Updated {new Date(snippet.updatedAt).toLocaleString()} ) : null} {isEditing ? (
{snippet.scope === "project" && snippet.projectKey ? ( ) : null}