From 527ae45471c22d054b44b626737991c3e5b5cc0a Mon Sep 17 00:00:00 2001 From: Jack Merrill Date: Thu, 3 Apr 2025 17:21:52 -0400 Subject: [PATCH] stuff --- app/actions.ts | 59 +++-- app/auth/callback/route.ts | 4 +- app/dashboard/upload/page.tsx | 57 +++++ app/dashboard/upload/process/route.ts | 47 ++++ app/login/page.tsx | 10 +- bun.lockb | Bin 110609 -> 127328 bytes components/UploadZone.tsx | 83 ++++++ components/app-sidebar.tsx | 351 ++++++++------------------ components/login-form.tsx | 81 +++--- components/nav-favorites.tsx | 33 +-- components/nav-main.tsx | 22 +- components/ui/sidebar.tsx | 216 ++++++++-------- package.json | 10 +- utils/supabase/server.ts | 5 +- utils/supabase/types.ts | 150 +++++++++++ 15 files changed, 687 insertions(+), 441 deletions(-) create mode 100644 app/dashboard/upload/page.tsx create mode 100644 app/dashboard/upload/process/route.ts create mode 100644 components/UploadZone.tsx create mode 100644 utils/supabase/types.ts diff --git a/app/actions.ts b/app/actions.ts index dbf8a26..d02e6ce 100644 --- a/app/actions.ts +++ b/app/actions.ts @@ -4,6 +4,8 @@ import { encodedRedirect } from "@/utils/utils"; import { createClient } from "@/utils/supabase/server"; import { headers } from "next/headers"; import { redirect } from "next/navigation"; +import { Provider } from "@supabase/supabase-js"; +import { revalidatePath } from "next/cache"; export const signUpAction = async (formData: FormData) => { const email = formData.get("email")?.toString(); @@ -12,11 +14,7 @@ export const signUpAction = async (formData: FormData) => { const origin = (await headers()).get("origin"); if (!email || !password) { - return encodedRedirect( - "error", - "/sign-up", - "Email and password are required", - ); + return encodedRedirect("error", "/login", "Email is required"); } const { error } = await supabase.auth.signUp({ @@ -34,26 +32,47 @@ export const signUpAction = async (formData: FormData) => { return encodedRedirect( "success", "/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) => { 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(); + 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({ - email, - password, - }); + if (error) { + return encodedRedirect("error", "/login", error.message); + } + } else if (provider) { + const { error, data } = await supabase.auth.signInWithOAuth({ + provider, + options: { + redirectTo: `${process.env.NEXT_PUBLIC_SITE_URL}/auth/callback`, + }, + }); - if (error) { - return encodedRedirect("error", "/sign-in", error.message); + if (error) { + 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) => { @@ -75,7 +94,7 @@ export const forgotPasswordAction = async (formData: FormData) => { return encodedRedirect( "error", "/forgot-password", - "Could not reset password", + "Could not reset password" ); } @@ -86,7 +105,7 @@ export const forgotPasswordAction = async (formData: FormData) => { return encodedRedirect( "success", "/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( "error", "/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( "error", "/protected/reset-password", - "Passwords do not match", + "Passwords do not match" ); } @@ -120,7 +139,7 @@ export const resetPasswordAction = async (formData: FormData) => { encodedRedirect( "error", "/protected/reset-password", - "Password update failed", + "Password update failed" ); } @@ -130,5 +149,5 @@ export const resetPasswordAction = async (formData: FormData) => { export const signOutAction = async () => { const supabase = await createClient(); await supabase.auth.signOut(); - return redirect("/sign-in"); + return redirect("/login"); }; diff --git a/app/auth/callback/route.ts b/app/auth/callback/route.ts index dd415a4..384d5b8 100644 --- a/app/auth/callback/route.ts +++ b/app/auth/callback/route.ts @@ -14,11 +14,11 @@ export async function GET(request: Request) { const supabase = await createClient(); await supabase.auth.exchangeCodeForSession(code); } - + console.log("code", code); if (redirectTo) { return NextResponse.redirect(`${origin}${redirectTo}`); } // URL to redirect to after sign up process completes - return NextResponse.redirect(`${origin}/protected`); + return NextResponse.redirect(`${origin}/dashboard`); } diff --git a/app/dashboard/upload/page.tsx b/app/dashboard/upload/page.tsx new file mode 100644 index 0000000..2d02584 --- /dev/null +++ b/app/dashboard/upload/page.tsx @@ -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 ( + + + +
+
+ + + + + + + Upload a Document + + + + +
+
+ +
+
+ ); +} diff --git a/app/dashboard/upload/process/route.ts b/app/dashboard/upload/process/route.ts new file mode 100644 index 0000000..425f284 --- /dev/null +++ b/app/dashboard/upload/process/route.ts @@ -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" }); +} diff --git a/app/login/page.tsx b/app/login/page.tsx index 94b501b..032da59 100644 --- a/app/login/page.tsx +++ b/app/login/page.tsx @@ -1,8 +1,15 @@ import { Brain, BrainCircuit, GalleryVerticalEnd } from "lucide-react"; 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 (
@@ -12,6 +19,7 @@ export default function LoginPage() {
Neuroread +
diff --git a/bun.lockb b/bun.lockb index 36eaec4bb13ab0d4a322636813e7cecac9ea0f55..dffef7bda07e35672bf3f93eab36f2b781113cbb 100755 GIT binary patch delta 30725 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 f$3|h@z3c;V&}sJ { + 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 ( +
+
+ +
+
+ ); +} diff --git a/components/app-sidebar.tsx b/components/app-sidebar.tsx index 741a40b..16c1ecf 100644 --- a/components/app-sidebar.tsx +++ b/components/app-sidebar.tsx @@ -1,9 +1,10 @@ -"use client" +"use client"; -import * as React from "react" +import * as React from "react"; import { AudioWaveform, Blocks, + BrainCircuit, Calendar, Command, Home, @@ -13,264 +14,126 @@ import { Settings2, Sparkles, Trash2, -} from "lucide-react" + Upload, +} from "lucide-react"; -import { NavFavorites } from "@/components/nav-favorites" -import { NavMain } from "@/components/nav-main" -import { NavSecondary } from "@/components/nav-secondary" -import { NavWorkspaces } from "@/components/nav-workspaces" -import { TeamSwitcher } from "@/components/team-switcher" +import { NavDocuments } from "@/components/nav-favorites"; +import { NavMain } from "@/components/nav-main"; +import { NavSecondary } from "@/components/nav-secondary"; import { Sidebar, SidebarContent, SidebarHeader, + SidebarMenuButton, SidebarRail, -} 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: "πŸ“Έ", - }, - ], - }, - ], -} +} from "@/components/ui/sidebar"; export function AppSidebar({ ...props }: React.ComponentProps) { + 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 ( - + +
+ +
+ Neuroread +
- - +
- ) + ); } diff --git a/components/login-form.tsx b/components/login-form.tsx index 44add0e..31b8b02 100644 --- a/components/login-form.tsx +++ b/components/login-form.tsx @@ -9,6 +9,8 @@ import { } from "@/components/ui/card"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; +import { createClient } from "@/utils/supabase/client"; +import { signInAction } from "@/app/actions"; export function LoginForm({ className, @@ -22,44 +24,51 @@ export function LoginForm({ Login with your Google account -
-
-
- -
-
- - Or continue with - -
-
-
- - + + +
- -
+ + Continue with Google + + +
+ + Or continue with +
- +
+
+ + +
+ +
+
diff --git a/components/nav-favorites.tsx b/components/nav-favorites.tsx index 34e2aea..cb3382e 100644 --- a/components/nav-favorites.tsx +++ b/components/nav-favorites.tsx @@ -1,12 +1,13 @@ -"use client" +"use client"; import { ArrowUpRight, + FileText, Link, MoreHorizontal, StarOff, Trash2, -} from "lucide-react" +} from "lucide-react"; import { DropdownMenu, @@ -14,7 +15,7 @@ import { DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger, -} from "@/components/ui/dropdown-menu" +} from "@/components/ui/dropdown-menu"; import { SidebarGroup, SidebarGroupLabel, @@ -23,28 +24,28 @@ import { SidebarMenuButton, SidebarMenuItem, useSidebar, -} from "@/components/ui/sidebar" +} from "@/components/ui/sidebar"; -export function NavFavorites({ - favorites, +export function NavDocuments({ + documents, }: { - favorites: { - name: string - url: string - emoji: string - }[] + documents: { + name: string; + url: string; + emoji?: string; + }[]; }) { - const { isMobile } = useSidebar() + const { isMobile } = useSidebar(); return ( - Favorites + Documents - {favorites.map((item) => ( + {documents.map((item) => ( - {item.emoji} + {item.emoji ? item.emoji : } {item.name} @@ -90,5 +91,5 @@ export function NavFavorites({ - ) + ); } diff --git a/components/nav-main.tsx b/components/nav-main.tsx index 31edc97..0460b1e 100644 --- a/components/nav-main.tsx +++ b/components/nav-main.tsx @@ -1,28 +1,30 @@ -"use client" +"use client"; -import { type LucideIcon } from "lucide-react" +import { type LucideIcon } from "lucide-react"; import { SidebarMenu, SidebarMenuButton, SidebarMenuItem, -} from "@/components/ui/sidebar" +} from "@/components/ui/sidebar"; +import { usePathname, useRouter } from "next/navigation"; export function NavMain({ items, }: { items: { - title: string - url: string - icon: LucideIcon - isActive?: boolean - }[] + title: string; + url: string; + icon: LucideIcon; + isActive?: boolean; + }[]; }) { + const pathname = usePathname(); return ( {items.map((item) => ( - + {item.title} @@ -31,5 +33,5 @@ export function NavMain({ ))} - ) + ); } diff --git a/components/ui/sidebar.tsx b/components/ui/sidebar.tsx index 3b3ee77..485ac82 100644 --- a/components/ui/sidebar.tsx +++ b/components/ui/sidebar.tsx @@ -1,56 +1,56 @@ -"use client" +"use client"; -import * as React from "react" -import { Slot } from "@radix-ui/react-slot" -import { VariantProps, cva } from "class-variance-authority" -import { PanelLeftIcon } from "lucide-react" +import * as React from "react"; +import { Slot } from "@radix-ui/react-slot"; +import { VariantProps, cva } from "class-variance-authority"; +import { PanelLeftIcon } from "lucide-react"; -import { useIsMobile } from "@/components/hooks/use-mobile" -import { cn } from "@/components/lib/utils" -import { Button } from "@/components/ui/button" -import { Input } from "@/components/ui/input" -import { Separator } from "@/components/ui/separator" +import { useIsMobile } from "@/hooks/use-mobile"; +import { cn } from "@/lib/utils"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Separator } from "@/components/ui/separator"; import { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle, -} from "@/components/ui/sheet" -import { Skeleton } from "@/components/ui/skeleton" +} from "@/components/ui/sheet"; +import { Skeleton } from "@/components/ui/skeleton"; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, -} from "@/components/ui/tooltip" +} from "@/components/ui/tooltip"; -const SIDEBAR_COOKIE_NAME = "sidebar_state" -const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7 -const SIDEBAR_WIDTH = "16rem" -const SIDEBAR_WIDTH_MOBILE = "18rem" -const SIDEBAR_WIDTH_ICON = "3rem" -const SIDEBAR_KEYBOARD_SHORTCUT = "b" +const SIDEBAR_COOKIE_NAME = "sidebar_state"; +const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7; +const SIDEBAR_WIDTH = "16rem"; +const SIDEBAR_WIDTH_MOBILE = "18rem"; +const SIDEBAR_WIDTH_ICON = "3rem"; +const SIDEBAR_KEYBOARD_SHORTCUT = "b"; type SidebarContextProps = { - state: "expanded" | "collapsed" - open: boolean - setOpen: (open: boolean) => void - openMobile: boolean - setOpenMobile: (open: boolean) => void - isMobile: boolean - toggleSidebar: () => void -} + state: "expanded" | "collapsed"; + open: boolean; + setOpen: (open: boolean) => void; + openMobile: boolean; + setOpenMobile: (open: boolean) => void; + isMobile: boolean; + toggleSidebar: () => void; +}; -const SidebarContext = React.createContext(null) +const SidebarContext = React.createContext(null); function useSidebar() { - const context = React.useContext(SidebarContext) + const context = React.useContext(SidebarContext); 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({ @@ -62,36 +62,36 @@ function SidebarProvider({ children, ...props }: React.ComponentProps<"div"> & { - defaultOpen?: boolean - open?: boolean - onOpenChange?: (open: boolean) => void + defaultOpen?: boolean; + open?: boolean; + onOpenChange?: (open: boolean) => void; }) { - const isMobile = useIsMobile() - const [openMobile, setOpenMobile] = React.useState(false) + const isMobile = useIsMobile(); + const [openMobile, setOpenMobile] = React.useState(false); // This is the internal state of the sidebar. // We use openProp and setOpenProp for control from outside the component. - const [_open, _setOpen] = React.useState(defaultOpen) - const open = openProp ?? _open + const [_open, _setOpen] = React.useState(defaultOpen); + const open = openProp ?? _open; const setOpen = React.useCallback( (value: boolean | ((value: boolean) => boolean)) => { - const openState = typeof value === "function" ? value(open) : value + const openState = typeof value === "function" ? value(open) : value; if (setOpenProp) { - setOpenProp(openState) + setOpenProp(openState); } else { - _setOpen(openState) + _setOpen(openState); } // 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] - ) + ); // Helper to toggle the sidebar. const toggleSidebar = React.useCallback(() => { - return isMobile ? setOpenMobile((open) => !open) : setOpen((open) => !open) - }, [isMobile, setOpen, setOpenMobile]) + return isMobile ? setOpenMobile((open) => !open) : setOpen((open) => !open); + }, [isMobile, setOpen, setOpenMobile]); // Adds a keyboard shortcut to toggle the sidebar. React.useEffect(() => { @@ -100,18 +100,18 @@ function SidebarProvider({ event.key === SIDEBAR_KEYBOARD_SHORTCUT && (event.metaKey || event.ctrlKey) ) { - event.preventDefault() - toggleSidebar() + event.preventDefault(); + toggleSidebar(); } - } + }; - window.addEventListener("keydown", handleKeyDown) - return () => window.removeEventListener("keydown", handleKeyDown) - }, [toggleSidebar]) + window.addEventListener("keydown", handleKeyDown); + return () => window.removeEventListener("keydown", handleKeyDown); + }, [toggleSidebar]); // 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. - const state = open ? "expanded" : "collapsed" + const state = open ? "expanded" : "collapsed"; const contextValue = React.useMemo( () => ({ @@ -124,7 +124,7 @@ function SidebarProvider({ toggleSidebar, }), [state, open, setOpen, isMobile, openMobile, setOpenMobile, toggleSidebar] - ) + ); return ( @@ -148,7 +148,7 @@ function SidebarProvider({ - ) + ); } function Sidebar({ @@ -159,11 +159,11 @@ function Sidebar({ children, ...props }: React.ComponentProps<"div"> & { - side?: "left" | "right" - variant?: "sidebar" | "floating" | "inset" - collapsible?: "offcanvas" | "icon" | "none" + side?: "left" | "right"; + variant?: "sidebar" | "floating" | "inset"; + collapsible?: "offcanvas" | "icon" | "none"; }) { - const { isMobile, state, openMobile, setOpenMobile } = useSidebar() + const { isMobile, state, openMobile, setOpenMobile } = useSidebar(); if (collapsible === "none") { return ( @@ -177,7 +177,7 @@ function Sidebar({ > {children} - ) + ); } if (isMobile) { @@ -202,7 +202,7 @@ function Sidebar({
{children}
- ) + ); } return ( @@ -250,7 +250,7 @@ function Sidebar({ - ) + ); } function SidebarTrigger({ @@ -258,7 +258,7 @@ function SidebarTrigger({ onClick, ...props }: React.ComponentProps) { - const { toggleSidebar } = useSidebar() + const { toggleSidebar } = useSidebar(); return ( - ) + ); } function SidebarRail({ className, ...props }: React.ComponentProps<"button">) { - const { toggleSidebar } = useSidebar() + const { toggleSidebar } = useSidebar(); return (