This commit is contained in:
Jack Merrill 2025-04-03 17:21:52 -04:00
parent 369660bad1
commit 527ae45471
No known key found for this signature in database
15 changed files with 687 additions and 441 deletions

View File

@ -4,6 +4,8 @@ import { encodedRedirect } from "@/utils/utils";
import { createClient } from "@/utils/supabase/server"; import { createClient } from "@/utils/supabase/server";
import { headers } from "next/headers"; import { headers } from "next/headers";
import { redirect } from "next/navigation"; import { redirect } from "next/navigation";
import { Provider } from "@supabase/supabase-js";
import { revalidatePath } from "next/cache";
export const signUpAction = async (formData: FormData) => { export const signUpAction = async (formData: FormData) => {
const email = formData.get("email")?.toString(); const email = formData.get("email")?.toString();
@ -12,11 +14,7 @@ export const signUpAction = async (formData: FormData) => {
const origin = (await headers()).get("origin"); const origin = (await headers()).get("origin");
if (!email || !password) { if (!email || !password) {
return encodedRedirect( return encodedRedirect("error", "/login", "Email is required");
"error",
"/sign-up",
"Email and password are required",
);
} }
const { error } = await supabase.auth.signUp({ const { error } = await supabase.auth.signUp({
@ -34,26 +32,47 @@ export const signUpAction = async (formData: FormData) => {
return encodedRedirect( return encodedRedirect(
"success", "success",
"/sign-up", "/sign-up",
"Thanks for signing up! Please check your email for a verification link.", "Thanks for signing up! Please check your email for a verification link."
); );
} }
}; };
export const signInAction = async (formData: FormData) => { export const signInAction = async (formData: FormData) => {
const email = formData.get("email") as string; const email = formData.get("email") as string;
const password = formData.get("password") as string; const provider = formData.get("provider") as Provider;
const supabase = await createClient(); const supabase = await createClient();
if (email) {
const { error } = await supabase.auth.signInWithOtp({
email,
options: {
emailRedirectTo: `${process.env.NEXT_PUBLIC_SITE_URL}/auth/callback`,
},
});
const { error } = await supabase.auth.signInWithPassword({ if (error) {
email, return encodedRedirect("error", "/login", error.message);
password, }
}); } else if (provider) {
const { error, data } = await supabase.auth.signInWithOAuth({
provider,
options: {
redirectTo: `${process.env.NEXT_PUBLIC_SITE_URL}/auth/callback`,
},
});
if (error) { if (error) {
return encodedRedirect("error", "/sign-in", error.message); return encodedRedirect("error", "/login", error.message);
}
if (data?.url) {
return redirect(data.url);
} else {
return encodedRedirect("error", "/login", "Could not sign in");
}
} }
return redirect("/protected"); revalidatePath("/", "layout");
redirect("/dashboard");
}; };
export const forgotPasswordAction = async (formData: FormData) => { export const forgotPasswordAction = async (formData: FormData) => {
@ -75,7 +94,7 @@ export const forgotPasswordAction = async (formData: FormData) => {
return encodedRedirect( return encodedRedirect(
"error", "error",
"/forgot-password", "/forgot-password",
"Could not reset password", "Could not reset password"
); );
} }
@ -86,7 +105,7 @@ export const forgotPasswordAction = async (formData: FormData) => {
return encodedRedirect( return encodedRedirect(
"success", "success",
"/forgot-password", "/forgot-password",
"Check your email for a link to reset your password.", "Check your email for a link to reset your password."
); );
}; };
@ -100,7 +119,7 @@ export const resetPasswordAction = async (formData: FormData) => {
encodedRedirect( encodedRedirect(
"error", "error",
"/protected/reset-password", "/protected/reset-password",
"Password and confirm password are required", "Password and confirm password are required"
); );
} }
@ -108,7 +127,7 @@ export const resetPasswordAction = async (formData: FormData) => {
encodedRedirect( encodedRedirect(
"error", "error",
"/protected/reset-password", "/protected/reset-password",
"Passwords do not match", "Passwords do not match"
); );
} }
@ -120,7 +139,7 @@ export const resetPasswordAction = async (formData: FormData) => {
encodedRedirect( encodedRedirect(
"error", "error",
"/protected/reset-password", "/protected/reset-password",
"Password update failed", "Password update failed"
); );
} }
@ -130,5 +149,5 @@ export const resetPasswordAction = async (formData: FormData) => {
export const signOutAction = async () => { export const signOutAction = async () => {
const supabase = await createClient(); const supabase = await createClient();
await supabase.auth.signOut(); await supabase.auth.signOut();
return redirect("/sign-in"); return redirect("/login");
}; };

View File

@ -14,11 +14,11 @@ export async function GET(request: Request) {
const supabase = await createClient(); const supabase = await createClient();
await supabase.auth.exchangeCodeForSession(code); await supabase.auth.exchangeCodeForSession(code);
} }
console.log("code", code);
if (redirectTo) { if (redirectTo) {
return NextResponse.redirect(`${origin}${redirectTo}`); return NextResponse.redirect(`${origin}${redirectTo}`);
} }
// URL to redirect to after sign up process completes // URL to redirect to after sign up process completes
return NextResponse.redirect(`${origin}/protected`); return NextResponse.redirect(`${origin}/dashboard`);
} }

View File

@ -0,0 +1,57 @@
import UploadZone from "@/components/UploadZone";
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 { CloudUpload } from "lucide-react";
import { redirect } from "next/navigation";
export default async function Page() {
const supabase = await createClient();
const {
data: { user },
} = await supabase.auth.getUser();
if (!user) {
return redirect("/login");
}
return (
<SidebarProvider>
<AppSidebar />
<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">
Upload a Document
</BreadcrumbPage>
</BreadcrumbItem>
</BreadcrumbList>
</Breadcrumb>
</div>
</header>
<UploadZone />
</SidebarInset>
</SidebarProvider>
);
}

View File

@ -0,0 +1,47 @@
import { createClient } from "@/utils/supabase/server";
import { NextResponse } from "next/server";
import { Mistral } from "@mistralai/mistralai";
const apiKey = process.env.MISTRAL_API_KEY;
const client = new Mistral({ apiKey: apiKey });
export async function POST(request: Request) {
const supabase = await createClient();
const formData = await request.formData();
const file = formData.get("file") as File;
const fileName = formData.get("fileName") as string;
const id = formData.get("id") as string;
const uploaded_pdf = await client.files.upload({
file: {
fileName,
content: file,
},
purpose: "ocr",
});
const signedUrl = await client.files.getSignedUrl({
fileId: uploaded_pdf.id,
});
const ocrResponse = await client.ocr.process({
model: "mistral-ocr-latest",
document: {
type: "document_url",
documentUrl: signedUrl.url,
},
});
const { data, error } = await supabase
.from("documents")
.update({
ocr_data: ocrResponse,
})
.eq("id", id);
if (error) {
console.error(error);
return NextResponse.json({ error: error.message }, { status: 500 });
}
console.log("Document updated successfully:", data);
return NextResponse.json({ message: "File processed successfully" });
}

View File

@ -1,8 +1,15 @@
import { Brain, BrainCircuit, GalleryVerticalEnd } from "lucide-react"; import { Brain, BrainCircuit, GalleryVerticalEnd } from "lucide-react";
import { LoginForm } from "@/components/login-form"; import { LoginForm } from "@/components/login-form";
import { FormMessage } from "@/components/form-message";
export default async function LoginPage(props: {
searchParams: Promise<
{ success: string } | { error: string } | { message: string }
>;
}) {
const searchParams = await props.searchParams;
export default function LoginPage() {
return ( return (
<div className="bg-muted flex min-h-svh flex-col items-center justify-center gap-6 p-6 md:p-10"> <div className="bg-muted flex min-h-svh flex-col items-center justify-center gap-6 p-6 md:p-10">
<div className="flex w-full max-w-sm flex-col gap-6"> <div className="flex w-full max-w-sm flex-col gap-6">
@ -12,6 +19,7 @@ export default function LoginPage() {
</div> </div>
Neuroread Neuroread
</a> </a>
<FormMessage message={searchParams} />
<LoginForm /> <LoginForm />
</div> </div>
</div> </div>

BIN
bun.lockb

Binary file not shown.

83
components/UploadZone.tsx Normal file
View File

@ -0,0 +1,83 @@
"use client";
import { createClient } from "@/utils/supabase/client";
import { CloudUpload } from "lucide-react";
export default async function UploadZone() {
const supabase = await createClient();
const {
data: { user },
} = await supabase.auth.getUser();
const onUpload = async (file: File) => {
const uuid = crypto.randomUUID();
const { data: fileData, error: fileError } = await supabase.storage
.from("documents")
.upload(`public/${uuid}.pdf`, file);
if (fileError) {
console.error(fileError);
return;
}
console.log("File uploaded successfully:", fileData);
const { data, error } = await supabase.from("documents").insert({
id: uuid,
file_name: file.name,
owner: user!.id,
raw_file: fileData.id,
});
if (error) {
console.error(error);
return;
}
console.log("Document inserted successfully:", data);
// process file at /dashboard/upload/process
const formData = new FormData();
formData.append("file", file);
formData.append("fileName", file.name);
formData.append("id", uuid);
const response = await fetch("/dashboard/upload/process", {
method: "POST",
body: formData,
});
const result = await response.json();
console.log("File processed successfully:", result);
};
return (
<div className="flex flex-1 flex-col gap-4 px-4 py-10">
<div className="flex items-center justify-center w-full">
<label
htmlFor="dropzone-file"
className="flex flex-col items-center justify-center w-full h-64 border-2 border-muted border-dashed rounded-lg cursor-pointer bg-muted/50"
>
<div className="flex flex-col items-center justify-center pt-5 pb-5">
<CloudUpload className="w-10 h-10 mb-4 text-slate-400" />
<p className="mb-2 text-sm text-slate-400">
<span className="font-semibold">Click to upload</span> or drag and
drop
</p>
</div>
<input
id="dropzone-file"
type="file"
className="hidden"
accept="application/pdf"
onChange={(e) => {
const file = e.target.files?.[0];
if (file) {
onUpload(file);
}
}}
/>
</label>
</div>
</div>
);
}

View File

@ -1,9 +1,10 @@
"use client" "use client";
import * as React from "react" import * as React from "react";
import { import {
AudioWaveform, AudioWaveform,
Blocks, Blocks,
BrainCircuit,
Calendar, Calendar,
Command, Command,
Home, Home,
@ -13,264 +14,126 @@ import {
Settings2, Settings2,
Sparkles, Sparkles,
Trash2, Trash2,
} from "lucide-react" Upload,
} from "lucide-react";
import { NavFavorites } from "@/components/nav-favorites" import { NavDocuments } from "@/components/nav-favorites";
import { NavMain } from "@/components/nav-main" import { NavMain } from "@/components/nav-main";
import { NavSecondary } from "@/components/nav-secondary" import { NavSecondary } from "@/components/nav-secondary";
import { NavWorkspaces } from "@/components/nav-workspaces"
import { TeamSwitcher } from "@/components/team-switcher"
import { import {
Sidebar, Sidebar,
SidebarContent, SidebarContent,
SidebarHeader, SidebarHeader,
SidebarMenuButton,
SidebarRail, SidebarRail,
} from "@/components/ui/sidebar" } from "@/components/ui/sidebar";
// This is sample data.
const data = {
teams: [
{
name: "Acme Inc",
logo: Command,
plan: "Enterprise",
},
{
name: "Acme Corp.",
logo: AudioWaveform,
plan: "Startup",
},
{
name: "Evil Corp.",
logo: Command,
plan: "Free",
},
],
navMain: [
{
title: "Search",
url: "#",
icon: Search,
},
{
title: "Ask AI",
url: "#",
icon: Sparkles,
},
{
title: "Home",
url: "#",
icon: Home,
isActive: true,
},
{
title: "Inbox",
url: "#",
icon: Inbox,
badge: "10",
},
],
navSecondary: [
{
title: "Calendar",
url: "#",
icon: Calendar,
},
{
title: "Settings",
url: "#",
icon: Settings2,
},
{
title: "Templates",
url: "#",
icon: Blocks,
},
{
title: "Trash",
url: "#",
icon: Trash2,
},
{
title: "Help",
url: "#",
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: "✅",
},
],
workspaces: [
{
name: "Personal Life Management",
emoji: "🏠",
pages: [
{
name: "Daily Journal & Reflection",
url: "#",
emoji: "📔",
},
{
name: "Health & Wellness Tracker",
url: "#",
emoji: "🍏",
},
{
name: "Personal Growth & Learning Goals",
url: "#",
emoji: "🌟",
},
],
},
{
name: "Professional Development",
emoji: "💼",
pages: [
{
name: "Career Objectives & Milestones",
url: "#",
emoji: "🎯",
},
{
name: "Skill Acquisition & Training Log",
url: "#",
emoji: "🧠",
},
{
name: "Networking Contacts & Events",
url: "#",
emoji: "🤝",
},
],
},
{
name: "Creative Projects",
emoji: "🎨",
pages: [
{
name: "Writing Ideas & Story Outlines",
url: "#",
emoji: "✍️",
},
{
name: "Art & Design Portfolio",
url: "#",
emoji: "🖼️",
},
{
name: "Music Composition & Practice Log",
url: "#",
emoji: "🎵",
},
],
},
{
name: "Home Management",
emoji: "🏡",
pages: [
{
name: "Household Budget & Expense Tracking",
url: "#",
emoji: "💰",
},
{
name: "Home Maintenance Schedule & Tasks",
url: "#",
emoji: "🔧",
},
{
name: "Family Calendar & Event Planning",
url: "#",
emoji: "📅",
},
],
},
{
name: "Travel & Adventure",
emoji: "🧳",
pages: [
{
name: "Trip Planning & Itineraries",
url: "#",
emoji: "🗺️",
},
{
name: "Travel Bucket List & Inspiration",
url: "#",
emoji: "🌎",
},
{
name: "Travel Journal & Photo Gallery",
url: "#",
emoji: "📸",
},
],
},
],
}
export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) { export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
const data = {
navMain: [
{
title: "Search",
url: "/dashboard/search",
icon: Search,
},
{
title: "Home",
url: "/dashboard",
icon: Home,
isActive: true,
},
{
title: "Upload",
url: "/dashboard/upload",
icon: Upload,
},
],
navSecondary: [
{
title: "Settings",
url: "#",
icon: Settings2,
},
{
title: "Trash",
url: "#",
icon: Trash2,
},
{
title: "Help",
url: "#",
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>
<TeamSwitcher teams={data.teams} /> <SidebarMenuButton className="w-fit px-1.5">
<div className="bg-sidebar-primary text-sidebar-primary-foreground flex aspect-square size-5 items-center justify-center rounded-md">
<BrainCircuit className="size-3" />
</div>
<span className="truncate font-medium">Neuroread</span>
</SidebarMenuButton>
<NavMain items={data.navMain} /> <NavMain items={data.navMain} />
</SidebarHeader> </SidebarHeader>
<SidebarContent> <SidebarContent>
<NavFavorites favorites={data.favorites} /> <NavDocuments documents={data.favorites} />
<NavWorkspaces workspaces={data.workspaces} />
<NavSecondary items={data.navSecondary} className="mt-auto" /> <NavSecondary items={data.navSecondary} className="mt-auto" />
</SidebarContent> </SidebarContent>
<SidebarRail /> <SidebarRail />
</Sidebar> </Sidebar>
) );
} }

View File

@ -9,6 +9,8 @@ import {
} from "@/components/ui/card"; } from "@/components/ui/card";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label"; import { Label } from "@/components/ui/label";
import { createClient } from "@/utils/supabase/client";
import { signInAction } from "@/app/actions";
export function LoginForm({ export function LoginForm({
className, className,
@ -22,44 +24,51 @@ export function LoginForm({
<CardDescription>Login with your Google account</CardDescription> <CardDescription>Login with your Google account</CardDescription>
</CardHeader> </CardHeader>
<CardContent> <CardContent>
<form> <div className="grid gap-6">
<div className="grid gap-6"> <form className="flex flex-col gap-4">
<div className="flex flex-col gap-4"> <Input type="hidden" name="provider" value="google" />
<Button variant="outline" className="w-full"> <Button
<svg variant="outline"
xmlns="http://www.w3.org/2000/svg" className="w-full"
viewBox="0 0 24 24" formAction={signInAction}
className="h-full w-4 mr-2 inline-block align-middle" >
> <svg
<path xmlns="http://www.w3.org/2000/svg"
d="M12.48 10.92v3.28h7.84c-.24 1.84-.853 3.187-1.787 4.133-1.147 1.147-2.933 2.4-6.053 2.4-4.827 0-8.6-3.893-8.6-8.72s3.773-8.72 8.6-8.72c2.6 0 4.507 1.027 5.907 2.347l2.307-2.307C18.747 1.44 16.133 0 12.48 0 5.867 0 .307 5.387.307 12s5.56 12 12.173 12c3.573 0 6.267-1.173 8.373-3.36 2.16-2.16 2.84-5.213 2.84-7.667 0-.76-.053-1.467-.173-2.053H12.48z" viewBox="0 0 24 24"
fill="currentColor" className="h-full w-4 mr-2 inline-block align-middle"
/> >
</svg> <path
Continue with Google d="M12.48 10.92v3.28h7.84c-.24 1.84-.853 3.187-1.787 4.133-1.147 1.147-2.933 2.4-6.053 2.4-4.827 0-8.6-3.893-8.6-8.72s3.773-8.72 8.6-8.72c2.6 0 4.507 1.027 5.907 2.347l2.307-2.307C18.747 1.44 16.133 0 12.48 0 5.867 0 .307 5.387.307 12s5.56 12 12.173 12c3.573 0 6.267-1.173 8.373-3.36 2.16-2.16 2.84-5.213 2.84-7.667 0-.76-.053-1.467-.173-2.053H12.48z"
</Button> fill="currentColor"
</div>
<div className="after:border-border relative text-center text-sm after:absolute after:inset-0 after:top-1/2 after:z-0 after:flex after:items-center after:border-t">
<span className="bg-card text-muted-foreground relative z-10 px-2">
Or continue with
</span>
</div>
<div className="grid gap-6">
<div className="grid gap-3">
<Label htmlFor="email">Email</Label>
<Input
id="email"
type="email"
placeholder="m@example.com"
required
/> />
</div> </svg>
<Button type="submit" className="w-full"> Continue with Google
Continue </Button>
</Button> </form>
</div> <div className="after:border-border relative text-center text-sm after:absolute after:inset-0 after:top-1/2 after:z-0 after:flex after:items-center after:border-t">
<span className="bg-card text-muted-foreground relative z-10 px-2">
Or continue with
</span>
</div> </div>
</form> <form className="grid gap-6">
<div className="grid gap-3">
<Label htmlFor="email">Email</Label>
<Input
id="email"
type="email"
placeholder="m@example.com"
required
/>
</div>
<Button
type="submit"
className="w-full"
formAction={signInAction}
>
Continue
</Button>
</form>
</div>
</CardContent> </CardContent>
</Card> </Card>
</div> </div>

View File

@ -1,12 +1,13 @@
"use client" "use client";
import { import {
ArrowUpRight, ArrowUpRight,
FileText,
Link, Link,
MoreHorizontal, MoreHorizontal,
StarOff, StarOff,
Trash2, Trash2,
} from "lucide-react" } from "lucide-react";
import { import {
DropdownMenu, DropdownMenu,
@ -14,7 +15,7 @@ import {
DropdownMenuItem, DropdownMenuItem,
DropdownMenuSeparator, DropdownMenuSeparator,
DropdownMenuTrigger, DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu" } from "@/components/ui/dropdown-menu";
import { import {
SidebarGroup, SidebarGroup,
SidebarGroupLabel, SidebarGroupLabel,
@ -23,28 +24,28 @@ import {
SidebarMenuButton, SidebarMenuButton,
SidebarMenuItem, SidebarMenuItem,
useSidebar, useSidebar,
} from "@/components/ui/sidebar" } from "@/components/ui/sidebar";
export function NavFavorites({ export function NavDocuments({
favorites, documents,
}: { }: {
favorites: { documents: {
name: string name: string;
url: string url: string;
emoji: string emoji?: string;
}[] }[];
}) { }) {
const { isMobile } = useSidebar() const { isMobile } = useSidebar();
return ( return (
<SidebarGroup className="group-data-[collapsible=icon]:hidden"> <SidebarGroup className="group-data-[collapsible=icon]:hidden">
<SidebarGroupLabel>Favorites</SidebarGroupLabel> <SidebarGroupLabel>Documents</SidebarGroupLabel>
<SidebarMenu> <SidebarMenu>
{favorites.map((item) => ( {documents.map((item) => (
<SidebarMenuItem key={item.name}> <SidebarMenuItem key={item.name}>
<SidebarMenuButton asChild> <SidebarMenuButton asChild>
<a href={item.url} title={item.name}> <a href={item.url} title={item.name}>
<span>{item.emoji}</span> <span>{item.emoji ? item.emoji : <FileText />}</span>
<span>{item.name}</span> <span>{item.name}</span>
</a> </a>
</SidebarMenuButton> </SidebarMenuButton>
@ -90,5 +91,5 @@ export function NavFavorites({
</SidebarMenuItem> </SidebarMenuItem>
</SidebarMenu> </SidebarMenu>
</SidebarGroup> </SidebarGroup>
) );
} }

View File

@ -1,28 +1,30 @@
"use client" "use client";
import { type LucideIcon } from "lucide-react" import { type LucideIcon } from "lucide-react";
import { import {
SidebarMenu, SidebarMenu,
SidebarMenuButton, SidebarMenuButton,
SidebarMenuItem, SidebarMenuItem,
} from "@/components/ui/sidebar" } from "@/components/ui/sidebar";
import { usePathname, useRouter } from "next/navigation";
export function NavMain({ export function NavMain({
items, items,
}: { }: {
items: { items: {
title: string title: string;
url: string url: string;
icon: LucideIcon icon: LucideIcon;
isActive?: boolean isActive?: boolean;
}[] }[];
}) { }) {
const pathname = usePathname();
return ( return (
<SidebarMenu> <SidebarMenu>
{items.map((item) => ( {items.map((item) => (
<SidebarMenuItem key={item.title}> <SidebarMenuItem key={item.title}>
<SidebarMenuButton asChild isActive={item.isActive}> <SidebarMenuButton asChild isActive={item.url === pathname}>
<a href={item.url}> <a href={item.url}>
<item.icon /> <item.icon />
<span>{item.title}</span> <span>{item.title}</span>
@ -31,5 +33,5 @@ export function NavMain({
</SidebarMenuItem> </SidebarMenuItem>
))} ))}
</SidebarMenu> </SidebarMenu>
) );
} }

View File

@ -1,56 +1,56 @@
"use client" "use client";
import * as React from "react" import * as React from "react";
import { Slot } from "@radix-ui/react-slot" import { Slot } from "@radix-ui/react-slot";
import { VariantProps, cva } from "class-variance-authority" import { VariantProps, cva } from "class-variance-authority";
import { PanelLeftIcon } from "lucide-react" import { PanelLeftIcon } from "lucide-react";
import { useIsMobile } from "@/components/hooks/use-mobile" import { useIsMobile } from "@/hooks/use-mobile";
import { cn } from "@/components/lib/utils" import { cn } from "@/lib/utils";
import { Button } from "@/components/ui/button" import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input" import { Input } from "@/components/ui/input";
import { Separator } from "@/components/ui/separator" import { Separator } from "@/components/ui/separator";
import { import {
Sheet, Sheet,
SheetContent, SheetContent,
SheetDescription, SheetDescription,
SheetHeader, SheetHeader,
SheetTitle, SheetTitle,
} from "@/components/ui/sheet" } from "@/components/ui/sheet";
import { Skeleton } from "@/components/ui/skeleton" import { Skeleton } from "@/components/ui/skeleton";
import { import {
Tooltip, Tooltip,
TooltipContent, TooltipContent,
TooltipProvider, TooltipProvider,
TooltipTrigger, TooltipTrigger,
} from "@/components/ui/tooltip" } from "@/components/ui/tooltip";
const SIDEBAR_COOKIE_NAME = "sidebar_state" const SIDEBAR_COOKIE_NAME = "sidebar_state";
const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7 const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7;
const SIDEBAR_WIDTH = "16rem" const SIDEBAR_WIDTH = "16rem";
const SIDEBAR_WIDTH_MOBILE = "18rem" const SIDEBAR_WIDTH_MOBILE = "18rem";
const SIDEBAR_WIDTH_ICON = "3rem" const SIDEBAR_WIDTH_ICON = "3rem";
const SIDEBAR_KEYBOARD_SHORTCUT = "b" const SIDEBAR_KEYBOARD_SHORTCUT = "b";
type SidebarContextProps = { type SidebarContextProps = {
state: "expanded" | "collapsed" state: "expanded" | "collapsed";
open: boolean open: boolean;
setOpen: (open: boolean) => void setOpen: (open: boolean) => void;
openMobile: boolean openMobile: boolean;
setOpenMobile: (open: boolean) => void setOpenMobile: (open: boolean) => void;
isMobile: boolean isMobile: boolean;
toggleSidebar: () => void toggleSidebar: () => void;
} };
const SidebarContext = React.createContext<SidebarContextProps | null>(null) const SidebarContext = React.createContext<SidebarContextProps | null>(null);
function useSidebar() { function useSidebar() {
const context = React.useContext(SidebarContext) const context = React.useContext(SidebarContext);
if (!context) { if (!context) {
throw new Error("useSidebar must be used within a SidebarProvider.") throw new Error("useSidebar must be used within a SidebarProvider.");
} }
return context return context;
} }
function SidebarProvider({ function SidebarProvider({
@ -62,36 +62,36 @@ function SidebarProvider({
children, children,
...props ...props
}: React.ComponentProps<"div"> & { }: React.ComponentProps<"div"> & {
defaultOpen?: boolean defaultOpen?: boolean;
open?: boolean open?: boolean;
onOpenChange?: (open: boolean) => void onOpenChange?: (open: boolean) => void;
}) { }) {
const isMobile = useIsMobile() const isMobile = useIsMobile();
const [openMobile, setOpenMobile] = React.useState(false) const [openMobile, setOpenMobile] = React.useState(false);
// This is the internal state of the sidebar. // This is the internal state of the sidebar.
// We use openProp and setOpenProp for control from outside the component. // We use openProp and setOpenProp for control from outside the component.
const [_open, _setOpen] = React.useState(defaultOpen) const [_open, _setOpen] = React.useState(defaultOpen);
const open = openProp ?? _open const open = openProp ?? _open;
const setOpen = React.useCallback( const setOpen = React.useCallback(
(value: boolean | ((value: boolean) => boolean)) => { (value: boolean | ((value: boolean) => boolean)) => {
const openState = typeof value === "function" ? value(open) : value const openState = typeof value === "function" ? value(open) : value;
if (setOpenProp) { if (setOpenProp) {
setOpenProp(openState) setOpenProp(openState);
} else { } else {
_setOpen(openState) _setOpen(openState);
} }
// This sets the cookie to keep the sidebar state. // This sets the cookie to keep the sidebar state.
document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}` document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`;
}, },
[setOpenProp, open] [setOpenProp, open]
) );
// Helper to toggle the sidebar. // Helper to toggle the sidebar.
const toggleSidebar = React.useCallback(() => { const toggleSidebar = React.useCallback(() => {
return isMobile ? setOpenMobile((open) => !open) : setOpen((open) => !open) return isMobile ? setOpenMobile((open) => !open) : setOpen((open) => !open);
}, [isMobile, setOpen, setOpenMobile]) }, [isMobile, setOpen, setOpenMobile]);
// Adds a keyboard shortcut to toggle the sidebar. // Adds a keyboard shortcut to toggle the sidebar.
React.useEffect(() => { React.useEffect(() => {
@ -100,18 +100,18 @@ function SidebarProvider({
event.key === SIDEBAR_KEYBOARD_SHORTCUT && event.key === SIDEBAR_KEYBOARD_SHORTCUT &&
(event.metaKey || event.ctrlKey) (event.metaKey || event.ctrlKey)
) { ) {
event.preventDefault() event.preventDefault();
toggleSidebar() toggleSidebar();
} }
} };
window.addEventListener("keydown", handleKeyDown) window.addEventListener("keydown", handleKeyDown);
return () => window.removeEventListener("keydown", handleKeyDown) return () => window.removeEventListener("keydown", handleKeyDown);
}, [toggleSidebar]) }, [toggleSidebar]);
// We add a state so that we can do data-state="expanded" or "collapsed". // We add a state so that we can do data-state="expanded" or "collapsed".
// This makes it easier to style the sidebar with Tailwind classes. // This makes it easier to style the sidebar with Tailwind classes.
const state = open ? "expanded" : "collapsed" const state = open ? "expanded" : "collapsed";
const contextValue = React.useMemo<SidebarContextProps>( const contextValue = React.useMemo<SidebarContextProps>(
() => ({ () => ({
@ -124,7 +124,7 @@ function SidebarProvider({
toggleSidebar, toggleSidebar,
}), }),
[state, open, setOpen, isMobile, openMobile, setOpenMobile, toggleSidebar] [state, open, setOpen, isMobile, openMobile, setOpenMobile, toggleSidebar]
) );
return ( return (
<SidebarContext.Provider value={contextValue}> <SidebarContext.Provider value={contextValue}>
@ -148,7 +148,7 @@ function SidebarProvider({
</div> </div>
</TooltipProvider> </TooltipProvider>
</SidebarContext.Provider> </SidebarContext.Provider>
) );
} }
function Sidebar({ function Sidebar({
@ -159,11 +159,11 @@ function Sidebar({
children, children,
...props ...props
}: React.ComponentProps<"div"> & { }: React.ComponentProps<"div"> & {
side?: "left" | "right" side?: "left" | "right";
variant?: "sidebar" | "floating" | "inset" variant?: "sidebar" | "floating" | "inset";
collapsible?: "offcanvas" | "icon" | "none" collapsible?: "offcanvas" | "icon" | "none";
}) { }) {
const { isMobile, state, openMobile, setOpenMobile } = useSidebar() const { isMobile, state, openMobile, setOpenMobile } = useSidebar();
if (collapsible === "none") { if (collapsible === "none") {
return ( return (
@ -177,7 +177,7 @@ function Sidebar({
> >
{children} {children}
</div> </div>
) );
} }
if (isMobile) { if (isMobile) {
@ -202,7 +202,7 @@ function Sidebar({
<div className="flex h-full w-full flex-col">{children}</div> <div className="flex h-full w-full flex-col">{children}</div>
</SheetContent> </SheetContent>
</Sheet> </Sheet>
) );
} }
return ( return (
@ -250,7 +250,7 @@ function Sidebar({
</div> </div>
</div> </div>
</div> </div>
) );
} }
function SidebarTrigger({ function SidebarTrigger({
@ -258,7 +258,7 @@ function SidebarTrigger({
onClick, onClick,
...props ...props
}: React.ComponentProps<typeof Button>) { }: React.ComponentProps<typeof Button>) {
const { toggleSidebar } = useSidebar() const { toggleSidebar } = useSidebar();
return ( return (
<Button <Button
@ -268,19 +268,19 @@ function SidebarTrigger({
size="icon" size="icon"
className={cn("size-7", className)} className={cn("size-7", className)}
onClick={(event) => { onClick={(event) => {
onClick?.(event) onClick?.(event);
toggleSidebar() toggleSidebar();
}} }}
{...props} {...props}
> >
<PanelLeftIcon /> <PanelLeftIcon />
<span className="sr-only">Toggle Sidebar</span> <span className="sr-only">Toggle Sidebar</span>
</Button> </Button>
) );
} }
function SidebarRail({ className, ...props }: React.ComponentProps<"button">) { function SidebarRail({ className, ...props }: React.ComponentProps<"button">) {
const { toggleSidebar } = useSidebar() const { toggleSidebar } = useSidebar();
return ( return (
<button <button
@ -301,7 +301,7 @@ function SidebarRail({ className, ...props }: React.ComponentProps<"button">) {
)} )}
{...props} {...props}
/> />
) );
} }
function SidebarInset({ className, ...props }: React.ComponentProps<"main">) { function SidebarInset({ className, ...props }: React.ComponentProps<"main">) {
@ -315,7 +315,7 @@ function SidebarInset({ className, ...props }: React.ComponentProps<"main">) {
)} )}
{...props} {...props}
/> />
) );
} }
function SidebarInput({ function SidebarInput({
@ -329,7 +329,7 @@ function SidebarInput({
className={cn("bg-background h-8 w-full shadow-none", className)} className={cn("bg-background h-8 w-full shadow-none", className)}
{...props} {...props}
/> />
) );
} }
function SidebarHeader({ className, ...props }: React.ComponentProps<"div">) { function SidebarHeader({ className, ...props }: React.ComponentProps<"div">) {
@ -340,7 +340,7 @@ function SidebarHeader({ className, ...props }: React.ComponentProps<"div">) {
className={cn("flex flex-col gap-2 p-2", className)} className={cn("flex flex-col gap-2 p-2", className)}
{...props} {...props}
/> />
) );
} }
function SidebarFooter({ className, ...props }: React.ComponentProps<"div">) { function SidebarFooter({ className, ...props }: React.ComponentProps<"div">) {
@ -351,7 +351,7 @@ function SidebarFooter({ className, ...props }: React.ComponentProps<"div">) {
className={cn("flex flex-col gap-2 p-2", className)} className={cn("flex flex-col gap-2 p-2", className)}
{...props} {...props}
/> />
) );
} }
function SidebarSeparator({ function SidebarSeparator({
@ -365,7 +365,7 @@ function SidebarSeparator({
className={cn("bg-sidebar-border mx-2 w-auto", className)} className={cn("bg-sidebar-border mx-2 w-auto", className)}
{...props} {...props}
/> />
) );
} }
function SidebarContent({ className, ...props }: React.ComponentProps<"div">) { function SidebarContent({ className, ...props }: React.ComponentProps<"div">) {
@ -379,7 +379,7 @@ function SidebarContent({ className, ...props }: React.ComponentProps<"div">) {
)} )}
{...props} {...props}
/> />
) );
} }
function SidebarGroup({ className, ...props }: React.ComponentProps<"div">) { function SidebarGroup({ className, ...props }: React.ComponentProps<"div">) {
@ -390,7 +390,7 @@ function SidebarGroup({ className, ...props }: React.ComponentProps<"div">) {
className={cn("relative flex w-full min-w-0 flex-col p-2", className)} className={cn("relative flex w-full min-w-0 flex-col p-2", className)}
{...props} {...props}
/> />
) );
} }
function SidebarGroupLabel({ function SidebarGroupLabel({
@ -398,7 +398,7 @@ function SidebarGroupLabel({
asChild = false, asChild = false,
...props ...props
}: React.ComponentProps<"div"> & { asChild?: boolean }) { }: React.ComponentProps<"div"> & { asChild?: boolean }) {
const Comp = asChild ? Slot : "div" const Comp = asChild ? Slot : "div";
return ( return (
<Comp <Comp
@ -411,7 +411,7 @@ function SidebarGroupLabel({
)} )}
{...props} {...props}
/> />
) );
} }
function SidebarGroupAction({ function SidebarGroupAction({
@ -419,7 +419,7 @@ function SidebarGroupAction({
asChild = false, asChild = false,
...props ...props
}: React.ComponentProps<"button"> & { asChild?: boolean }) { }: React.ComponentProps<"button"> & { asChild?: boolean }) {
const Comp = asChild ? Slot : "button" const Comp = asChild ? Slot : "button";
return ( return (
<Comp <Comp
@ -434,7 +434,7 @@ function SidebarGroupAction({
)} )}
{...props} {...props}
/> />
) );
} }
function SidebarGroupContent({ function SidebarGroupContent({
@ -448,7 +448,7 @@ function SidebarGroupContent({
className={cn("w-full text-sm", className)} className={cn("w-full text-sm", className)}
{...props} {...props}
/> />
) );
} }
function SidebarMenu({ className, ...props }: React.ComponentProps<"ul">) { function SidebarMenu({ className, ...props }: React.ComponentProps<"ul">) {
@ -459,7 +459,7 @@ function SidebarMenu({ className, ...props }: React.ComponentProps<"ul">) {
className={cn("flex w-full min-w-0 flex-col gap-1", className)} className={cn("flex w-full min-w-0 flex-col gap-1", className)}
{...props} {...props}
/> />
) );
} }
function SidebarMenuItem({ className, ...props }: React.ComponentProps<"li">) { function SidebarMenuItem({ className, ...props }: React.ComponentProps<"li">) {
@ -470,7 +470,7 @@ function SidebarMenuItem({ className, ...props }: React.ComponentProps<"li">) {
className={cn("group/menu-item relative", className)} className={cn("group/menu-item relative", className)}
{...props} {...props}
/> />
) );
} }
const sidebarMenuButtonVariants = cva( const sidebarMenuButtonVariants = cva(
@ -493,7 +493,7 @@ const sidebarMenuButtonVariants = cva(
size: "default", size: "default",
}, },
} }
) );
function SidebarMenuButton({ function SidebarMenuButton({
asChild = false, asChild = false,
@ -504,12 +504,12 @@ function SidebarMenuButton({
className, className,
...props ...props
}: React.ComponentProps<"button"> & { }: React.ComponentProps<"button"> & {
asChild?: boolean asChild?: boolean;
isActive?: boolean isActive?: boolean;
tooltip?: string | React.ComponentProps<typeof TooltipContent> tooltip?: string | React.ComponentProps<typeof TooltipContent>;
} & VariantProps<typeof sidebarMenuButtonVariants>) { } & VariantProps<typeof sidebarMenuButtonVariants>) {
const Comp = asChild ? Slot : "button" const Comp = asChild ? Slot : "button";
const { isMobile, state } = useSidebar() const { isMobile, state } = useSidebar();
const button = ( const button = (
<Comp <Comp
@ -520,16 +520,16 @@ function SidebarMenuButton({
className={cn(sidebarMenuButtonVariants({ variant, size }), className)} className={cn(sidebarMenuButtonVariants({ variant, size }), className)}
{...props} {...props}
/> />
) );
if (!tooltip) { if (!tooltip) {
return button return button;
} }
if (typeof tooltip === "string") { if (typeof tooltip === "string") {
tooltip = { tooltip = {
children: tooltip, children: tooltip,
} };
} }
return ( return (
@ -542,7 +542,7 @@ function SidebarMenuButton({
{...tooltip} {...tooltip}
/> />
</Tooltip> </Tooltip>
) );
} }
function SidebarMenuAction({ function SidebarMenuAction({
@ -551,10 +551,10 @@ function SidebarMenuAction({
showOnHover = false, showOnHover = false,
...props ...props
}: React.ComponentProps<"button"> & { }: React.ComponentProps<"button"> & {
asChild?: boolean asChild?: boolean;
showOnHover?: boolean showOnHover?: boolean;
}) { }) {
const Comp = asChild ? Slot : "button" const Comp = asChild ? Slot : "button";
return ( return (
<Comp <Comp
@ -574,7 +574,7 @@ function SidebarMenuAction({
)} )}
{...props} {...props}
/> />
) );
} }
function SidebarMenuBadge({ function SidebarMenuBadge({
@ -596,7 +596,7 @@ function SidebarMenuBadge({
)} )}
{...props} {...props}
/> />
) );
} }
function SidebarMenuSkeleton({ function SidebarMenuSkeleton({
@ -604,12 +604,12 @@ function SidebarMenuSkeleton({
showIcon = false, showIcon = false,
...props ...props
}: React.ComponentProps<"div"> & { }: React.ComponentProps<"div"> & {
showIcon?: boolean showIcon?: boolean;
}) { }) {
// Random width between 50 to 90%. // Random width between 50 to 90%.
const width = React.useMemo(() => { const width = React.useMemo(() => {
return `${Math.floor(Math.random() * 40) + 50}%` return `${Math.floor(Math.random() * 40) + 50}%`;
}, []) }, []);
return ( return (
<div <div
@ -634,7 +634,7 @@ function SidebarMenuSkeleton({
} }
/> />
</div> </div>
) );
} }
function SidebarMenuSub({ className, ...props }: React.ComponentProps<"ul">) { function SidebarMenuSub({ className, ...props }: React.ComponentProps<"ul">) {
@ -649,7 +649,7 @@ function SidebarMenuSub({ className, ...props }: React.ComponentProps<"ul">) {
)} )}
{...props} {...props}
/> />
) );
} }
function SidebarMenuSubItem({ function SidebarMenuSubItem({
@ -663,7 +663,7 @@ function SidebarMenuSubItem({
className={cn("group/menu-sub-item relative", className)} className={cn("group/menu-sub-item relative", className)}
{...props} {...props}
/> />
) );
} }
function SidebarMenuSubButton({ function SidebarMenuSubButton({
@ -673,11 +673,11 @@ function SidebarMenuSubButton({
className, className,
...props ...props
}: React.ComponentProps<"a"> & { }: React.ComponentProps<"a"> & {
asChild?: boolean asChild?: boolean;
size?: "sm" | "md" size?: "sm" | "md";
isActive?: boolean isActive?: boolean;
}) { }) {
const Comp = asChild ? Slot : "a" const Comp = asChild ? Slot : "a";
return ( return (
<Comp <Comp
@ -695,7 +695,7 @@ function SidebarMenuSubButton({
)} )}
{...props} {...props}
/> />
) );
} }
export { export {
@ -723,4 +723,4 @@ export {
SidebarSeparator, SidebarSeparator,
SidebarTrigger, SidebarTrigger,
useSidebar, useSidebar,
} };

View File

@ -3,9 +3,12 @@
"scripts": { "scripts": {
"dev": "next dev", "dev": "next dev",
"build": "next build", "build": "next build",
"start": "next start" "start": "next start",
"types": "supabase gen types typescript --project-id pufhngbvumomxqkuxrav --schema public > ./utils/supabase/types.ts"
}, },
"dependencies": { "dependencies": {
"@ai-sdk/mistral": "^1.2.3",
"@mistralai/mistralai": "^1.5.2",
"@radix-ui/react-checkbox": "^1.1.1", "@radix-ui/react-checkbox": "^1.1.1",
"@radix-ui/react-collapsible": "^1.1.3", "@radix-ui/react-collapsible": "^1.1.3",
"@radix-ui/react-dialog": "^1.1.6", "@radix-ui/react-dialog": "^1.1.6",
@ -18,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",
"ai": "^4.2.11",
"autoprefixer": "10.4.20", "autoprefixer": "10.4.20",
"class-variance-authority": "^0.7.1", "class-variance-authority": "^0.7.1",
"clsx": "^2.1.1", "clsx": "^2.1.1",
@ -27,13 +31,15 @@
"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",
"tw-animate-css": "^1.2.5" "tw-animate-css": "^1.2.5",
"zod": "^3.24.2"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "22.10.2", "@types/node": "22.10.2",
"@types/react": "^19.0.2", "@types/react": "^19.0.2",
"@types/react-dom": "19.0.2", "@types/react-dom": "19.0.2",
"postcss": "^8.5.3", "postcss": "^8.5.3",
"supabase": "^2.20.5",
"tailwind-merge": "^2.5.2", "tailwind-merge": "^2.5.2",
"tailwindcss": "^4.1.0", "tailwindcss": "^4.1.0",
"tailwindcss-animate": "^1.0.7", "tailwindcss-animate": "^1.0.7",

View File

@ -1,10 +1,11 @@
import { createServerClient } from "@supabase/ssr"; import { createServerClient } from "@supabase/ssr";
import { cookies } from "next/headers"; import { cookies } from "next/headers";
import { Database } from "./types";
export const createClient = async () => { export const createClient = async () => {
const cookieStore = await cookies(); const cookieStore = await cookies();
return createServerClient( return createServerClient<Database>(
process.env.NEXT_PUBLIC_SUPABASE_URL!, process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!, process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
{ {
@ -24,6 +25,6 @@ export const createClient = async () => {
} }
}, },
}, },
}, }
); );
}; };

150
utils/supabase/types.ts Normal file
View File

@ -0,0 +1,150 @@
export type Json =
| string
| number
| boolean
| null
| { [key: string]: Json | undefined }
| Json[]
export type Database = {
public: {
Tables: {
documents: {
Row: {
created_at: string
file_name: string
id: string
ocr_data: Json | null
owner: string
raw_file: string
}
Insert: {
created_at?: string
file_name: string
id?: string
ocr_data?: Json | null
owner: string
raw_file: string
}
Update: {
created_at?: string
file_name?: string
id?: string
ocr_data?: Json | null
owner?: string
raw_file?: string
}
Relationships: []
}
}
Views: {
[_ in never]: never
}
Functions: {
[_ in never]: never
}
Enums: {
[_ in never]: never
}
CompositeTypes: {
[_ in never]: never
}
}
}
type PublicSchema = Database[Extract<keyof Database, "public">]
export type Tables<
PublicTableNameOrOptions extends
| keyof (PublicSchema["Tables"] & PublicSchema["Views"])
| { schema: keyof Database },
TableName extends PublicTableNameOrOptions extends { schema: keyof Database }
? keyof (Database[PublicTableNameOrOptions["schema"]]["Tables"] &
Database[PublicTableNameOrOptions["schema"]]["Views"])
: never = never,
> = PublicTableNameOrOptions extends { schema: keyof Database }
? (Database[PublicTableNameOrOptions["schema"]]["Tables"] &
Database[PublicTableNameOrOptions["schema"]]["Views"])[TableName] extends {
Row: infer R
}
? R
: never
: PublicTableNameOrOptions extends keyof (PublicSchema["Tables"] &
PublicSchema["Views"])
? (PublicSchema["Tables"] &
PublicSchema["Views"])[PublicTableNameOrOptions] extends {
Row: infer R
}
? R
: never
: never
export type TablesInsert<
PublicTableNameOrOptions extends
| keyof PublicSchema["Tables"]
| { schema: keyof Database },
TableName extends PublicTableNameOrOptions extends { schema: keyof Database }
? keyof Database[PublicTableNameOrOptions["schema"]]["Tables"]
: never = never,
> = PublicTableNameOrOptions extends { schema: keyof Database }
? Database[PublicTableNameOrOptions["schema"]]["Tables"][TableName] extends {
Insert: infer I
}
? I
: never
: PublicTableNameOrOptions extends keyof PublicSchema["Tables"]
? PublicSchema["Tables"][PublicTableNameOrOptions] extends {
Insert: infer I
}
? I
: never
: never
export type TablesUpdate<
PublicTableNameOrOptions extends
| keyof PublicSchema["Tables"]
| { schema: keyof Database },
TableName extends PublicTableNameOrOptions extends { schema: keyof Database }
? keyof Database[PublicTableNameOrOptions["schema"]]["Tables"]
: never = never,
> = PublicTableNameOrOptions extends { schema: keyof Database }
? Database[PublicTableNameOrOptions["schema"]]["Tables"][TableName] extends {
Update: infer U
}
? U
: never
: PublicTableNameOrOptions extends keyof PublicSchema["Tables"]
? PublicSchema["Tables"][PublicTableNameOrOptions] extends {
Update: infer U
}
? U
: never
: never
export type Enums<
PublicEnumNameOrOptions extends
| keyof PublicSchema["Enums"]
| { schema: keyof Database },
EnumName extends PublicEnumNameOrOptions extends { schema: keyof Database }
? keyof Database[PublicEnumNameOrOptions["schema"]]["Enums"]
: never = never,
> = PublicEnumNameOrOptions extends { schema: keyof Database }
? Database[PublicEnumNameOrOptions["schema"]]["Enums"][EnumName]
: PublicEnumNameOrOptions extends keyof PublicSchema["Enums"]
? PublicSchema["Enums"][PublicEnumNameOrOptions]
: never
export type CompositeTypes<
PublicCompositeTypeNameOrOptions extends
| keyof PublicSchema["CompositeTypes"]
| { schema: keyof Database },
CompositeTypeName extends PublicCompositeTypeNameOrOptions extends {
schema: keyof Database
}
? keyof Database[PublicCompositeTypeNameOrOptions["schema"]]["CompositeTypes"]
: never = never,
> = PublicCompositeTypeNameOrOptions extends { schema: keyof Database }
? Database[PublicCompositeTypeNameOrOptions["schema"]]["CompositeTypes"][CompositeTypeName]
: PublicCompositeTypeNameOrOptions extends keyof PublicSchema["CompositeTypes"]
? PublicSchema["CompositeTypes"][PublicCompositeTypeNameOrOptions]
: never