Add document page and enhance upload functionality with user-specific document handling

This commit is contained in:
Jack Merrill 2025-04-04 01:51:12 -04:00
parent 527ae45471
commit e8060b0719
No known key found for this signature in database
GPG Key ID: FD574AFF96E99636
8 changed files with 156 additions and 65 deletions

View File

@ -0,0 +1,117 @@
import { AppSidebar } from "@/components/app-sidebar";
import { NavActions } from "@/components/nav-actions";
import {
Breadcrumb,
BreadcrumbItem,
BreadcrumbList,
BreadcrumbPage,
} from "@/components/ui/breadcrumb";
import { Separator } from "@/components/ui/separator";
import {
SidebarInset,
SidebarProvider,
SidebarTrigger,
} from "@/components/ui/sidebar";
import { createClient } from "@/utils/supabase/server";
import { redirect } from "next/navigation";
import { remark } from "remark";
import remarkHtml from "remark-html";
export default async function DocumentPage({
params,
}: {
params: { id: string };
}) {
const supabase = await createClient();
const {
data: { user },
} = await supabase.auth.getUser();
if (!user) {
return redirect("/login");
}
// Fetch the document details based on the ID from params
const { data: document, error } = await supabase
.from("documents")
.select("*")
.eq("id", params.id)
.single();
if (error || !document) {
console.error("Error fetching document:", error);
}
// If the document doesn't exist, redirect to the documents page or handle it accordingly
if (!document) {
return redirect("/dashboard");
}
const { data: documents, error: documentsError } = await supabase
.from("documents")
.select("id, file_name, created_at, owner")
.eq("owner", user.id)
.order("created_at", { ascending: false });
if (documentsError) {
console.error("Error fetching documents:", error);
return <div>Error loading documents.</div>;
}
const pages = (document.ocr_data as any).pages.map(
(page: any) => page.markdown
);
const processedContent = await remark()
.use(remarkHtml)
.process(pages.join(" "));
return (
<SidebarProvider>
<AppSidebar
documents={documents.map((d) => {
return {
name: d.file_name,
url: `/dashboard/documents/${d.id}`,
emoji: "📄",
};
})}
/>
<SidebarInset>
<header className="flex h-14 shrink-0 items-center gap-2">
<div className="flex flex-1 items-center gap-2 px-3">
<SidebarTrigger />
<Separator
orientation="vertical"
className="mr-2 data-[orientation=vertical]:h-4"
/>
<Breadcrumb>
<BreadcrumbList>
<BreadcrumbItem>
<BreadcrumbPage className="line-clamp-1">
{document.file_name || "Document Details"}
</BreadcrumbPage>
</BreadcrumbItem>
</BreadcrumbList>
</Breadcrumb>
</div>
<div className="ml-auto px-3">
<NavActions />
</div>
</header>
<div
className="prose mx-auto px-4 py-10
text-white
prose-h1:font-semibold prose-h1:text-2xl prose-h1:mb-4 prose-h1:text-white
prose-h2:font-medium prose-h2:text-xl prose-h2:mb-3 prose-h2:text-white
prose-a:text-blue-400 hover:prose-a:underline
prose-p:leading-7 prose-p:text-gray-200
prose-blockquote:italic prose-blockquote:border-l-4 prose-blockquote:pl-4 prose-blockquote:border-gray-600 prose-blockquote:text-gray-300
prose-code:bg-gray-800 prose-code:rounded prose-code:px-1 prose-code:py-0.5 prose-code:text-gray-200
prose-img:rounded-lg prose-img:shadow-sm"
dangerouslySetInnerHTML={{ __html: String(processedContent) }}
></div>
</SidebarInset>
</SidebarProvider>
);
}

View File

@ -28,9 +28,28 @@ export default async function Page() {
return redirect("/login"); return redirect("/login");
} }
const { data: documents, error } = await supabase
.from("documents")
.select("id, file_name, created_at, owner")
.eq("owner", user.id)
.order("created_at", { ascending: false });
if (error) {
console.error("Error fetching documents:", error);
return <div>Error loading documents.</div>;
}
return ( return (
<SidebarProvider> <SidebarProvider>
<AppSidebar /> <AppSidebar
documents={documents.map((d) => {
return {
name: d.file_name,
url: `/dashboard/documents/${d.id}`,
emoji: "📄",
};
})}
/>
<SidebarInset> <SidebarInset>
<header className="flex h-14 shrink-0 items-center gap-2"> <header className="flex h-14 shrink-0 items-center gap-2">
<div className="flex flex-1 items-center gap-2 px-3"> <div className="flex flex-1 items-center gap-2 px-3">
@ -50,7 +69,7 @@ export default async function Page() {
</Breadcrumb> </Breadcrumb>
</div> </div>
</header> </header>
<UploadZone /> <UploadZone user={user} />
</SidebarInset> </SidebarInset>
</SidebarProvider> </SidebarProvider>
); );

View File

@ -1,6 +1,7 @@
import { createClient } from "@/utils/supabase/server"; import { createClient } from "@/utils/supabase/server";
import { NextResponse } from "next/server"; import { NextResponse } from "next/server";
import { Mistral } from "@mistralai/mistralai"; import { Mistral } from "@mistralai/mistralai";
import { redirect } from "next/navigation";
const apiKey = process.env.MISTRAL_API_KEY; const apiKey = process.env.MISTRAL_API_KEY;
const client = new Mistral({ apiKey: apiKey }); const client = new Mistral({ apiKey: apiKey });
@ -42,6 +43,5 @@ export async function POST(request: Request) {
console.error(error); console.error(error);
return NextResponse.json({ error: error.message }, { status: 500 }); return NextResponse.json({ error: error.message }, { status: 500 });
} }
console.log("Document updated successfully:", data); return redirect(`/dashboard/documents/${id}`); // Redirect to the document page after processing
return NextResponse.json({ message: "File processed successfully" });
} }

View File

@ -1,5 +1,6 @@
@import "tailwindcss"; @import "tailwindcss";
@import "tw-animate-css"; @import "tw-animate-css";
@plugin "@tailwindcss/typography";
@custom-variant dark (&:is(.dark *)); @custom-variant dark (&:is(.dark *));

BIN
bun.lockb

Binary file not shown.

View File

@ -2,19 +2,15 @@
import { createClient } from "@/utils/supabase/client"; import { createClient } from "@/utils/supabase/client";
import { CloudUpload } from "lucide-react"; import { CloudUpload } from "lucide-react";
export default async function UploadZone() { export default function UploadZone({ user }: { user?: { id: string } }) {
const supabase = await createClient(); const supabase = createClient();
const {
data: { user },
} = await supabase.auth.getUser();
const onUpload = async (file: File) => { const onUpload = async (file: File) => {
const uuid = crypto.randomUUID(); const uuid = crypto.randomUUID();
const { data: fileData, error: fileError } = await supabase.storage const { data: fileData, error: fileError } = await supabase.storage
.from("documents") .from("documents")
.upload(`public/${uuid}.pdf`, file); .upload(`${user!.id}/${uuid}.pdf`, file);
if (fileError) { if (fileError) {
console.error(fileError); console.error(fileError);

View File

@ -27,8 +27,14 @@ import {
SidebarMenuButton, SidebarMenuButton,
SidebarRail, SidebarRail,
} from "@/components/ui/sidebar"; } from "@/components/ui/sidebar";
import { createClient } from "@/utils/supabase/client";
export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) { export function AppSidebar({
documents,
...props
}: React.ComponentProps<typeof Sidebar> & {
documents?: Array<{ name: string; url: string; emoji?: string }>;
}) {
const data = { const data = {
navMain: [ navMain: [
{ {
@ -65,59 +71,8 @@ export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
icon: MessageCircleQuestion, icon: MessageCircleQuestion,
}, },
], ],
favorites: [
{
name: "Project Management & Task Tracking",
url: "#",
emoji: "📊",
},
{
name: "Family Recipe Collection & Meal Planning",
url: "#",
emoji: "🍳",
},
{
name: "Fitness Tracker & Workout Routines",
url: "#",
emoji: "💪",
},
{
name: "Book Notes & Reading List",
url: "#",
emoji: "📚",
},
{
name: "Sustainable Gardening Tips & Plant Care",
url: "#",
emoji: "🌱",
},
{
name: "Language Learning Progress & Resources",
url: "#",
emoji: "🗣️",
},
{
name: "Home Renovation Ideas & Budget Tracker",
url: "#",
emoji: "🏠",
},
{
name: "Personal Finance & Investment Portfolio",
url: "#",
emoji: "💰",
},
{
name: "Movie & TV Show Watchlist with Reviews",
url: "#",
emoji: "🎬",
},
{
name: "Daily Habit Tracker & Goal Setting",
url: "#",
emoji: "✅",
},
],
}; };
return ( return (
<Sidebar className="border-r-0" {...props}> <Sidebar className="border-r-0" {...props}>
<SidebarHeader> <SidebarHeader>
@ -130,7 +85,7 @@ export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
<NavMain items={data.navMain} /> <NavMain items={data.navMain} />
</SidebarHeader> </SidebarHeader>
<SidebarContent> <SidebarContent>
<NavDocuments documents={data.favorites} /> <NavDocuments documents={documents} />
<NavSecondary items={data.navSecondary} className="mt-auto" /> <NavSecondary items={data.navSecondary} className="mt-auto" />
</SidebarContent> </SidebarContent>
<SidebarRail /> <SidebarRail />

View File

@ -21,6 +21,7 @@
"@supabase/ssr": "latest", "@supabase/ssr": "latest",
"@supabase/supabase-js": "latest", "@supabase/supabase-js": "latest",
"@tailwindcss/postcss": "^4.1.0", "@tailwindcss/postcss": "^4.1.0",
"@tailwindcss/typography": "^0.5.16",
"ai": "^4.2.11", "ai": "^4.2.11",
"autoprefixer": "10.4.20", "autoprefixer": "10.4.20",
"class-variance-authority": "^0.7.1", "class-variance-authority": "^0.7.1",
@ -31,6 +32,8 @@
"prettier": "^3.3.3", "prettier": "^3.3.3",
"react": "19.0.0", "react": "19.0.0",
"react-dom": "19.0.0", "react-dom": "19.0.0",
"remark": "^15.0.1",
"remark-html": "^16.0.1",
"tw-animate-css": "^1.2.5", "tw-animate-css": "^1.2.5",
"zod": "^3.24.2" "zod": "^3.24.2"
}, },