stuff
This commit is contained in:
parent
369660bad1
commit
527ae45471
@ -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");
|
||||||
};
|
};
|
||||||
|
@ -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`);
|
||||||
}
|
}
|
||||||
|
57
app/dashboard/upload/page.tsx
Normal file
57
app/dashboard/upload/page.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
47
app/dashboard/upload/process/route.ts
Normal file
47
app/dashboard/upload/process/route.ts
Normal 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" });
|
||||||
|
}
|
@ -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>
|
||||||
|
83
components/UploadZone.tsx
Normal file
83
components/UploadZone.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
@ -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>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
@ -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>
|
||||||
|
@ -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>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
@ -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>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
}
|
};
|
||||||
|
10
package.json
10
package.json
@ -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",
|
||||||
|
@ -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
150
utils/supabase/types.ts
Normal 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
|
Loading…
x
Reference in New Issue
Block a user