Ran prettier. Moved Header to layout file

This commit is contained in:
Gabriel Brown 2025-03-19 15:52:55 -05:00
parent f5e3cb6234
commit 939fd796ee
27 changed files with 482 additions and 447 deletions

View File

@ -1,7 +1,7 @@
/** /**
* Run `build` or `dev` with `SKIP_ENV_VALIDATION` to skip env validation. This is especially useful * Run `build` or `dev` with `SKIP_ENV_VALIDATION` to skip env validation. This is especially useful
* for Docker builds. * for Docker builds.
*/ */
import './src/env.js'; import './src/env.js';
import { withSentryConfig } from '@sentry/nextjs'; import { withSentryConfig } from '@sentry/nextjs';

View File

@ -1,35 +1,35 @@
import { type EmailOtpType } from '@supabase/supabase-js' import { type EmailOtpType } from '@supabase/supabase-js';
import { type NextRequest, NextResponse } from 'next/server' import { type NextRequest, NextResponse } from 'next/server';
import { createClient } from '@/utils/supabase/server' import { createClient } from '@/utils/supabase/server';
// Creating a handler to a GET request to route /auth/confirm // Creating a handler to a GET request to route /auth/confirm
export const GET = async (request: NextRequest) => { export const GET = async (request: NextRequest) => {
const { searchParams } = new URL(request.url) const { searchParams } = new URL(request.url);
const token_hash = searchParams.get('token_hash') const token_hash = searchParams.get('token_hash');
const type = searchParams.get('type') as EmailOtpType | null const type = searchParams.get('type') as EmailOtpType | null;
const next = '/account' const next = '/account';
// Create redirect link without the secret token // Create redirect link without the secret token
const redirectTo = request.nextUrl.clone() const redirectTo = request.nextUrl.clone();
redirectTo.pathname = next redirectTo.pathname = next;
redirectTo.searchParams.delete('token_hash') redirectTo.searchParams.delete('token_hash');
redirectTo.searchParams.delete('type') redirectTo.searchParams.delete('type');
if (token_hash && type) { if (token_hash && type) {
const supabase = await createClient() const supabase = await createClient();
const { error } = await supabase.auth.verifyOtp({ const { error } = await supabase.auth.verifyOtp({
type, type,
token_hash, token_hash,
}) });
if (!error) { if (!error) {
redirectTo.searchParams.delete('next') redirectTo.searchParams.delete('next');
return NextResponse.redirect(redirectTo) return NextResponse.redirect(redirectTo);
} }
} }
// return the user to an error page with some instructions // return the user to an error page with some instructions
redirectTo.pathname = '/error' redirectTo.pathname = '/error';
return NextResponse.redirect(redirectTo) return NextResponse.redirect(redirectTo);
} };

View File

@ -1,4 +1,4 @@
const ErrorPage = () => { const ErrorPage = () => {
return <p>Sorry, something went wrong</p> return <p>Sorry, something went wrong</p>;
} };
export default ErrorPage; export default ErrorPage;

View File

@ -3,6 +3,9 @@ import { GeistSans } from 'geist/font/sans';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
import { ThemeProvider } from '@/components/context/Theme'; import { ThemeProvider } from '@/components/context/Theme';
import { TVModeProvider } from '@/components/context/TVMode'; import { TVModeProvider } from '@/components/context/TVMode';
import { createClient } from '@/utils/supabase/server';
import LoginForm from '@/components/auth/LoginForm';
import Header from '@/components/defaults/Header';
import { type Metadata } from 'next'; import { type Metadata } from 'next';
export const metadata: Metadata = { export const metadata: Metadata = {
@ -28,20 +31,14 @@ export const metadata: Metadata = {
], ],
}; };
const RootLayout = ({ const RootLayout = async ({ children }: Readonly<{ children: React.ReactNode }>) => {
children,
}: Readonly<{ children: React.ReactNode }>) => {
return ( return (
<html <html
lang='en' lang='en'
className={`${GeistSans.variable}`} className={`${GeistSans.variable}`}
suppressHydrationWarning suppressHydrationWarning
> >
<body <body className={cn('min-h-screen bg-background font-sans antialiased')}>
className={cn(
'min-h-screen bg-background font-sans antialiased'
)}
>
<ThemeProvider <ThemeProvider
attribute='class' attribute='class'
defaultTheme='system' defaultTheme='system'
@ -49,7 +46,10 @@ const RootLayout = ({
disableTransitionOnChange disableTransitionOnChange
> >
<TVModeProvider> <TVModeProvider>
<main className='min-h-screen'>
<Header />
{children} {children}
</main>
</TVModeProvider> </TVModeProvider>
</ThemeProvider> </ThemeProvider>
</body> </body>

View File

@ -1,6 +1,5 @@
'use server' 'use server';
import LoginForm from '@/components/auth/LoginForm'; import LoginForm from '@/components/auth/LoginForm';
import Header from '@/components/defaults/Header';
import { createClient } from '@/utils/supabase/server'; import { createClient } from '@/utils/supabase/server';
const HomePage = async () => { const HomePage = async () => {
@ -8,21 +7,15 @@ const HomePage = async () => {
const { data: { session } } = await supabase.auth.getSession(); const { data: { session } } = await supabase.auth.getSession();
if (!session) { if (!session) {
return ( return (
<main className="min-h-screen"> <div className='flex flex-col items-center justify-center md:min-h-[70vh]'>
<Header />
<div className="flex flex-col items-center justify-center md:min-h-[80vh]">
<LoginForm /> <LoginForm />
</div> </div>
</main>
); );
} }
return ( return (
<main> <div className='flex flex-col items-center justify-center'>
<Header />
<div className="flex flex-col items-center justify-center">
<h1>Hello, {session.user.email}</h1> <h1>Hello, {session.user.email}</h1>
</div> </div>
</main>
); );
}; };
export default HomePage; export default HomePage;

View File

@ -1,4 +1,4 @@
'use client' 'use client';
import { useState, useEffect } from 'react'; import { useState, useEffect } from 'react';
import Image from 'next/image'; import Image from 'next/image';
@ -13,6 +13,7 @@ import {
import { createClient } from '@/utils/supabase/client'; import { createClient } from '@/utils/supabase/client';
import type { Session } from '@supabase/supabase-js'; import type { Session } from '@supabase/supabase-js';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { redirect } from 'next/navigation';
const AvatarDropdown = () => { const AvatarDropdown = () => {
const supabase = createClient(); const supabase = createClient();
@ -23,7 +24,9 @@ const AvatarDropdown = () => {
// Function to fetch the session // Function to fetch the session
async function fetchSession() { async function fetchSession() {
try { try {
const { data: { session } } = await supabase.auth.getSession(); const {
data: { session },
} = await supabase.auth.getSession();
setSession(session); setSession(session);
} catch (error) { } catch (error) {
console.error('Error fetching session:', error); console.error('Error fetching session:', error);
@ -33,17 +36,16 @@ const AvatarDropdown = () => {
} }
// Call the function // Call the function
fetchSession() fetchSession().catch((error) => {
.catch((error) => {
console.error('Error fetching session:', error); console.error('Error fetching session:', error);
}); });
// Set up auth state change listener // Set up auth state change listener
const { data: { subscription } } = supabase.auth.onAuthStateChange( const {
(_event, session) => { data: { subscription },
} = supabase.auth.onAuthStateChange((_event, session) => {
setSession(session); setSession(session);
} });
);
// Clean up the subscription when component unmounts // Clean up the subscription when component unmounts
return () => { return () => {
@ -54,11 +56,12 @@ const AvatarDropdown = () => {
// Handle sign out // Handle sign out
const handleSignOut = async () => { const handleSignOut = async () => {
await supabase.auth.signOut(); await supabase.auth.signOut();
redirect('/');
}; };
// Show nothing while loading // Show nothing while loading
if (loading) { if (loading) {
return <div className="animate-pulse h-8 w-8 rounded-full bg-gray-300" />; return <div className='animate-pulse h-8 w-8 rounded-full bg-gray-300' />;
} }
// If no session, return empty div // If no session, return empty div
@ -67,14 +70,16 @@ const AvatarDropdown = () => {
} }
// Get user details // Get user details
const pfp = session.user?.user_metadata?.avatar_url as string ?? const pfp =
session.user?.user_metadata?.picture as string ?? (session.user?.user_metadata?.avatar_url as string) ??
(session.user?.user_metadata?.picture as string) ??
'/images/default_user_pfp.png'; '/images/default_user_pfp.png';
const name: string = session.user?.user_metadata?.full_name as string ?? const name: string =
session.user?.user_metadata?.name as string ?? (session.user?.user_metadata?.full_name as string) ??
session.user?.email as string ?? (session.user?.user_metadata?.name as string) ??
'Profile' as string; (session.user?.email as string) ??
('Profile' as string);
// If no session, return empty div // If no session, return empty div
if (!session) return <div />; if (!session) return <div />;
@ -84,7 +89,7 @@ const AvatarDropdown = () => {
<DropdownMenuTrigger> <DropdownMenuTrigger>
<Image <Image
src={pfp} src={pfp}
alt="User profile" alt='User profile'
width={35} width={35}
height={35} height={35}
className='rounded-full border-2 border-white m-auto mr-1 md:mr-2 className='rounded-full border-2 border-white m-auto mr-1 md:mr-2
@ -103,5 +108,5 @@ const AvatarDropdown = () => {
</DropdownMenu> </DropdownMenu>
</div> </div>
); );
} };
export default AvatarDropdown; export default AvatarDropdown;

View File

@ -1,4 +1,4 @@
'use client' 'use client';
import { login, signup } from '@/server/actions/auth'; import { login, signup } from '@/server/actions/auth';
import { zodResolver } from '@hookform/resolvers/zod'; import { zodResolver } from '@hookform/resolvers/zod';
import { useForm } from 'react-hook-form'; import { useForm } from 'react-hook-form';
@ -22,15 +22,15 @@ import {
CardFooter, CardFooter,
CardHeader, CardHeader,
CardTitle, CardTitle,
} from "@/components/ui/card" } from '@/components/ui/card';
import { Separator } from '@/components/ui/separator'; import { Separator } from '@/components/ui/separator';
import MicrosoftSignIn from '@/components/auth/microsoft/SignIn'; import MicrosoftSignIn from '@/components/auth/microsoft/SignIn';
import AppleSignIn from './apple/SignIn'; import AppleSignIn from './apple/SignIn';
const formSchema = z.object({ const formSchema = z.object({
fullName: z.string().optional(), fullName: z.string().optional(),
email: z.string().email("Must be a valid email!"), email: z.string().email('Must be a valid email!'),
password: z.string().min(8, "Must be at least 8 characters!"), password: z.string().min(8, 'Must be at least 8 characters!'),
}); });
const LoginForm = () => { const LoginForm = () => {
@ -68,21 +68,17 @@ const LoginForm = () => {
> >
<CardHeader className='flex items-center justify-center'> <CardHeader className='flex items-center justify-center'>
<CardTitle>Sign In</CardTitle> <CardTitle>Sign In</CardTitle>
<CardDescription> <CardDescription>Log in or create an account.</CardDescription>
Log in or create an account.
</CardDescription>
</CardHeader> </CardHeader>
<CardContent> <CardContent>
<Form {...form}> <Form {...form}>
<form ref={formRef} className="space-y-4"> <form ref={formRef} className='space-y-4'>
<FormField <FormField
control={form.control} control={form.control}
name="fullName" name='fullName'
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel htmlFor='fullName'> <FormLabel htmlFor='fullName'>Full Name</FormLabel>
Full Name
</FormLabel>
<FormControl> <FormControl>
<Input id='fullName' type='text' {...field} /> <Input id='fullName' type='text' {...field} />
</FormControl> </FormControl>
@ -95,12 +91,10 @@ const LoginForm = () => {
/> />
<FormField <FormField
control={form.control} control={form.control}
name="email" name='email'
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel htmlFor='email'> <FormLabel htmlFor='email'>Email</FormLabel>
Email
</FormLabel>
<FormControl> <FormControl>
<Input id='email' type='email' {...field} required /> <Input id='email' type='email' {...field} required />
</FormControl> </FormControl>
@ -110,12 +104,10 @@ const LoginForm = () => {
/> />
<FormField <FormField
control={form.control} control={form.control}
name="password" name='password'
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel> <FormLabel>Password</FormLabel>
Password
</FormLabel>
<FormControl> <FormControl>
<Input id='password' type='password' {...field} required /> <Input id='password' type='password' {...field} required />
</FormControl> </FormControl>
@ -123,7 +115,7 @@ const LoginForm = () => {
</FormItem> </FormItem>
)} )}
/> />
<div className="flex gap-4 justify-center items-center"> <div className='flex gap-4 justify-center items-center'>
<Button <Button
type='button' type='button'
className='w-5/12 font-semibold bg-gradient-to-r className='w-5/12 font-semibold bg-gradient-to-r
@ -149,13 +141,11 @@ const LoginForm = () => {
<CardFooter className='flex flex-col items-center justify-center w-full'> <CardFooter className='flex flex-col items-center justify-center w-full'>
<div className='flex flex-row items-center justify-between w-5/6'> <div className='flex flex-row items-center justify-between w-5/6'>
<Separator className='w-5/12 h-0.5 rounded-3xl' /> <Separator className='w-5/12 h-0.5 rounded-3xl' />
<p className='text-center text-muted-foreground font-semibold'> <p className='text-center text-muted-foreground font-semibold'>or</p>
or
</p>
<Separator className='w-5/12 h-0.5 rounded-3xl' /> <Separator className='w-5/12 h-0.5 rounded-3xl' />
</div> </div>
<div className='m-1'> <div className='m-1'>
< MicrosoftSignIn /> <MicrosoftSignIn />
<div className='my-2'> <div className='my-2'>
<AppleSignIn /> <AppleSignIn />
</div> </div>

View File

@ -11,12 +11,11 @@ const AppleSignIn = () => {
<Image <Image
src='/images/apple_black.svg' src='/images/apple_black.svg'
alt='Apple Logo' alt='Apple Logo'
width={16} height={16} width={16}
height={16}
className='invert dark:invert-0' className='invert dark:invert-0'
/> />
<p className='font-semibold dark:text-slate-950'> <p className='font-semibold dark:text-slate-950'>Sign in with Apple</p>
Sign in with Apple
</p>
</Button> </Button>
); );
}; };

View File

@ -11,7 +11,8 @@ const MicrosoftSignIn = () => {
<Image <Image
src='/images/microsoft_logo.png' src='/images/microsoft_logo.png'
alt='Microsoft Logo' alt='Microsoft Logo'
width={20} height={20} width={20}
height={20}
/> />
<p className='font-semibold dark:text-slate-950'> <p className='font-semibold dark:text-slate-950'>
Sign in with Microsoft Sign in with Microsoft

View File

@ -1,8 +1,9 @@
'use client'; 'use client';
import React, { createContext, useContext, useState } from 'react'; import React, { createContext, useContext, useState, useEffect } from 'react';
import Image from 'next/image'; import Image from 'next/image';
import type { ReactNode } from 'react'; import type { ReactNode } from 'react';
//import { useSession } from 'next-auth/react'; import type { Session } from '@supabase/supabase-js';
import { createClient } from '@/utils/supabase/client';
interface TVModeContextProps { interface TVModeContextProps {
tvMode: boolean; tvMode: boolean;
@ -35,8 +36,45 @@ export const useTVMode = () => {
export const TVToggle = () => { export const TVToggle = () => {
const { tvMode, toggleTVMode } = useTVMode(); const { tvMode, toggleTVMode } = useTVMode();
//const { data: session } = useSession(); const supabase = createClient();
//if (!session) return <div />; const [session, setSession] = useState<Session | null>(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
// Function to fetch the session
async function fetchSession() {
try {
const {
data: { session },
} = await supabase.auth.getSession();
setSession(session);
} catch (error) {
console.error('Error fetching session:', error);
} finally {
setLoading(false);
}
}
// Call the function
fetchSession().catch((error) => {
console.error('Error fetching session:', error);
});
// Set up auth state change listener
const {
data: { subscription },
} = supabase.auth.onAuthStateChange((_event, session) => {
setSession(session);
});
// Clean up the subscription when component unmounts
return () => {
subscription.unsubscribe();
};
}, [supabase]);
if (loading || !session) return <div />;
return ( return (
<button onClick={toggleTVMode} className='mr-4 mt-1'> <button onClick={toggleTVMode} className='mr-4 mt-1'>
{tvMode ? ( {tvMode ? (

View File

@ -11,7 +11,7 @@ const Header = () => {
<div className='w-full flex flex-row items-end justify-end'> <div className='w-full flex flex-row items-end justify-end'>
<div className='flex flex-row my-auto items-center pt-2 pr-0 sm:pr-8 sm:pt-4'> <div className='flex flex-row my-auto items-center pt-2 pr-0 sm:pr-8 sm:pt-4'>
<AvatarDropdown /> <AvatarDropdown />
<div className='mb-0.5'> <div className='mb-0.5 ml-2'>
<TVToggle /> <TVToggle />
</div> </div>
<ThemeToggle /> <ThemeToggle />
@ -27,7 +27,7 @@ const Header = () => {
pt-2 pr-0 sm:pt-4 sm:pr-8' pt-2 pr-0 sm:pt-4 sm:pr-8'
> >
<AvatarDropdown /> <AvatarDropdown />
<div className='mb-0.5'> <div className='mb-0.5 ml-2'>
<TVToggle /> <TVToggle />
</div> </div>
<ThemeToggle /> <ThemeToggle />

View File

@ -1,9 +1,9 @@
"use client" 'use client';
import * as React from "react" import * as React from 'react';
import * as AvatarPrimitive from "@radix-ui/react-avatar" import * as AvatarPrimitive from '@radix-ui/react-avatar';
import { cn } from "@/lib/utils" import { cn } from '@/lib/utils';
const Avatar = React.forwardRef< const Avatar = React.forwardRef<
React.ElementRef<typeof AvatarPrimitive.Root>, React.ElementRef<typeof AvatarPrimitive.Root>,
@ -12,13 +12,13 @@ const Avatar = React.forwardRef<
<AvatarPrimitive.Root <AvatarPrimitive.Root
ref={ref} ref={ref}
className={cn( className={cn(
"relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full", 'relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full',
className className,
)} )}
{...props} {...props}
/> />
)) ));
Avatar.displayName = AvatarPrimitive.Root.displayName Avatar.displayName = AvatarPrimitive.Root.displayName;
const AvatarImage = React.forwardRef< const AvatarImage = React.forwardRef<
React.ElementRef<typeof AvatarPrimitive.Image>, React.ElementRef<typeof AvatarPrimitive.Image>,
@ -26,11 +26,11 @@ const AvatarImage = React.forwardRef<
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<AvatarPrimitive.Image <AvatarPrimitive.Image
ref={ref} ref={ref}
className={cn("aspect-square h-full w-full", className)} className={cn('aspect-square h-full w-full', className)}
{...props} {...props}
/> />
)) ));
AvatarImage.displayName = AvatarPrimitive.Image.displayName AvatarImage.displayName = AvatarPrimitive.Image.displayName;
const AvatarFallback = React.forwardRef< const AvatarFallback = React.forwardRef<
React.ElementRef<typeof AvatarPrimitive.Fallback>, React.ElementRef<typeof AvatarPrimitive.Fallback>,
@ -39,12 +39,12 @@ const AvatarFallback = React.forwardRef<
<AvatarPrimitive.Fallback <AvatarPrimitive.Fallback
ref={ref} ref={ref}
className={cn( className={cn(
"flex h-full w-full items-center justify-center rounded-full bg-muted", 'flex h-full w-full items-center justify-center rounded-full bg-muted',
className className,
)} )}
{...props} {...props}
/> />
)) ));
AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName;
export { Avatar, AvatarImage, AvatarFallback } export { Avatar, AvatarImage, AvatarFallback };

View File

@ -1,57 +1,57 @@
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 { cva, type VariantProps } from "class-variance-authority" import { cva, type VariantProps } from 'class-variance-authority';
import { cn } from "@/lib/utils" import { cn } from '@/lib/utils';
const buttonVariants = cva( const buttonVariants = cva(
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0", 'inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0',
{ {
variants: { variants: {
variant: { variant: {
default: default:
"bg-primary text-primary-foreground shadow hover:bg-primary/90", 'bg-primary text-primary-foreground shadow hover:bg-primary/90',
destructive: destructive:
"bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90", 'bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90',
outline: outline:
"border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground", 'border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground',
secondary: secondary:
"bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80", 'bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80',
ghost: "hover:bg-accent hover:text-accent-foreground", ghost: 'hover:bg-accent hover:text-accent-foreground',
link: "text-primary underline-offset-4 hover:underline", link: 'text-primary underline-offset-4 hover:underline',
}, },
size: { size: {
default: "h-9 px-4 py-2", default: 'h-9 px-4 py-2',
sm: "h-8 rounded-md px-3 text-xs", sm: 'h-8 rounded-md px-3 text-xs',
lg: "h-10 rounded-md px-8", lg: 'h-10 rounded-md px-8',
icon: "h-9 w-9", icon: 'h-9 w-9',
}, },
}, },
defaultVariants: { defaultVariants: {
variant: "default", variant: 'default',
size: "default", size: 'default',
}, },
} },
) );
export interface ButtonProps export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>, extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> { VariantProps<typeof buttonVariants> {
asChild?: boolean asChild?: boolean;
} }
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>( const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, asChild = false, ...props }, ref) => { ({ className, variant, size, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : "button" const Comp = asChild ? Slot : 'button';
return ( return (
<Comp <Comp
className={cn(buttonVariants({ variant, size, className }))} className={cn(buttonVariants({ variant, size, className }))}
ref={ref} ref={ref}
{...props} {...props}
/> />
) );
} },
) );
Button.displayName = "Button" Button.displayName = 'Button';
export { Button, buttonVariants } export { Button, buttonVariants };

View File

@ -1,6 +1,6 @@
import * as React from "react" import * as React from 'react';
import { cn } from "@/lib/utils" import { cn } from '@/lib/utils';
const Card = React.forwardRef< const Card = React.forwardRef<
HTMLDivElement, HTMLDivElement,
@ -9,13 +9,13 @@ const Card = React.forwardRef<
<div <div
ref={ref} ref={ref}
className={cn( className={cn(
"rounded-xl border bg-card text-card-foreground shadow", 'rounded-xl border bg-card text-card-foreground shadow',
className className,
)} )}
{...props} {...props}
/> />
)) ));
Card.displayName = "Card" Card.displayName = 'Card';
const CardHeader = React.forwardRef< const CardHeader = React.forwardRef<
HTMLDivElement, HTMLDivElement,
@ -23,11 +23,11 @@ const CardHeader = React.forwardRef<
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<div <div
ref={ref} ref={ref}
className={cn("flex flex-col space-y-1.5 p-6", className)} className={cn('flex flex-col space-y-1.5 p-6', className)}
{...props} {...props}
/> />
)) ));
CardHeader.displayName = "CardHeader" CardHeader.displayName = 'CardHeader';
const CardTitle = React.forwardRef< const CardTitle = React.forwardRef<
HTMLDivElement, HTMLDivElement,
@ -35,11 +35,11 @@ const CardTitle = React.forwardRef<
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<div <div
ref={ref} ref={ref}
className={cn("font-semibold leading-none tracking-tight", className)} className={cn('font-semibold leading-none tracking-tight', className)}
{...props} {...props}
/> />
)) ));
CardTitle.displayName = "CardTitle" CardTitle.displayName = 'CardTitle';
const CardDescription = React.forwardRef< const CardDescription = React.forwardRef<
HTMLDivElement, HTMLDivElement,
@ -47,19 +47,19 @@ const CardDescription = React.forwardRef<
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<div <div
ref={ref} ref={ref}
className={cn("text-sm text-muted-foreground", className)} className={cn('text-sm text-muted-foreground', className)}
{...props} {...props}
/> />
)) ));
CardDescription.displayName = "CardDescription" CardDescription.displayName = 'CardDescription';
const CardContent = React.forwardRef< const CardContent = React.forwardRef<
HTMLDivElement, HTMLDivElement,
React.HTMLAttributes<HTMLDivElement> React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<div ref={ref} className={cn("p-6 pt-0", className)} {...props} /> <div ref={ref} className={cn('p-6 pt-0', className)} {...props} />
)) ));
CardContent.displayName = "CardContent" CardContent.displayName = 'CardContent';
const CardFooter = React.forwardRef< const CardFooter = React.forwardRef<
HTMLDivElement, HTMLDivElement,
@ -67,10 +67,17 @@ const CardFooter = React.forwardRef<
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<div <div
ref={ref} ref={ref}
className={cn("flex items-center p-6 pt-0", className)} className={cn('flex items-center p-6 pt-0', className)}
{...props} {...props}
/> />
)) ));
CardFooter.displayName = "CardFooter" CardFooter.displayName = 'CardFooter';
export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent } export {
Card,
CardHeader,
CardFooter,
CardTitle,
CardDescription,
CardContent,
};

View File

@ -1,44 +1,44 @@
"use client" 'use client';
import * as React from "react" import * as React from 'react';
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu" import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu';
import { Check, ChevronRight, Circle } from "lucide-react" import { Check, ChevronRight, Circle } from 'lucide-react';
import { cn } from "@/lib/utils" import { cn } from '@/lib/utils';
const DropdownMenu = DropdownMenuPrimitive.Root const DropdownMenu = DropdownMenuPrimitive.Root;
const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger;
const DropdownMenuGroup = DropdownMenuPrimitive.Group const DropdownMenuGroup = DropdownMenuPrimitive.Group;
const DropdownMenuPortal = DropdownMenuPrimitive.Portal const DropdownMenuPortal = DropdownMenuPrimitive.Portal;
const DropdownMenuSub = DropdownMenuPrimitive.Sub const DropdownMenuSub = DropdownMenuPrimitive.Sub;
const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup;
const DropdownMenuSubTrigger = React.forwardRef< const DropdownMenuSubTrigger = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>, React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & { React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & {
inset?: boolean inset?: boolean;
} }
>(({ className, inset, children, ...props }, ref) => ( >(({ className, inset, children, ...props }, ref) => (
<DropdownMenuPrimitive.SubTrigger <DropdownMenuPrimitive.SubTrigger
ref={ref} ref={ref}
className={cn( className={cn(
"flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-[state=open]:bg-accent [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0", 'flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-[state=open]:bg-accent [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0',
inset && "pl-8", inset && 'pl-8',
className className,
)} )}
{...props} {...props}
> >
{children} {children}
<ChevronRight className="ml-auto" /> <ChevronRight className='ml-auto' />
</DropdownMenuPrimitive.SubTrigger> </DropdownMenuPrimitive.SubTrigger>
)) ));
DropdownMenuSubTrigger.displayName = DropdownMenuSubTrigger.displayName =
DropdownMenuPrimitive.SubTrigger.displayName DropdownMenuPrimitive.SubTrigger.displayName;
const DropdownMenuSubContent = React.forwardRef< const DropdownMenuSubContent = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.SubContent>, React.ElementRef<typeof DropdownMenuPrimitive.SubContent>,
@ -47,14 +47,14 @@ const DropdownMenuSubContent = React.forwardRef<
<DropdownMenuPrimitive.SubContent <DropdownMenuPrimitive.SubContent
ref={ref} ref={ref}
className={cn( className={cn(
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[--radix-dropdown-menu-content-transform-origin]", 'z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[--radix-dropdown-menu-content-transform-origin]',
className className,
)} )}
{...props} {...props}
/> />
)) ));
DropdownMenuSubContent.displayName = DropdownMenuSubContent.displayName =
DropdownMenuPrimitive.SubContent.displayName DropdownMenuPrimitive.SubContent.displayName;
const DropdownMenuContent = React.forwardRef< const DropdownMenuContent = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Content>, React.ElementRef<typeof DropdownMenuPrimitive.Content>,
@ -65,33 +65,33 @@ const DropdownMenuContent = React.forwardRef<
ref={ref} ref={ref}
sideOffset={sideOffset} sideOffset={sideOffset}
className={cn( className={cn(
"z-50 max-h-[var(--radix-dropdown-menu-content-available-height)] min-w-[8rem] overflow-y-auto overflow-x-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md", 'z-50 max-h-[var(--radix-dropdown-menu-content-available-height)] min-w-[8rem] overflow-y-auto overflow-x-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md',
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[--radix-dropdown-menu-content-transform-origin]", 'data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[--radix-dropdown-menu-content-transform-origin]',
className className,
)} )}
{...props} {...props}
/> />
</DropdownMenuPrimitive.Portal> </DropdownMenuPrimitive.Portal>
)) ));
DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName;
const DropdownMenuItem = React.forwardRef< const DropdownMenuItem = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Item>, React.ElementRef<typeof DropdownMenuPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & { React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {
inset?: boolean inset?: boolean;
} }
>(({ className, inset, ...props }, ref) => ( >(({ className, inset, ...props }, ref) => (
<DropdownMenuPrimitive.Item <DropdownMenuPrimitive.Item
ref={ref} ref={ref}
className={cn( className={cn(
"relative flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&>svg]:size-4 [&>svg]:shrink-0", 'relative flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&>svg]:size-4 [&>svg]:shrink-0',
inset && "pl-8", inset && 'pl-8',
className className,
)} )}
{...props} {...props}
/> />
)) ));
DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName;
const DropdownMenuCheckboxItem = React.forwardRef< const DropdownMenuCheckboxItem = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.CheckboxItem>, React.ElementRef<typeof DropdownMenuPrimitive.CheckboxItem>,
@ -100,22 +100,22 @@ const DropdownMenuCheckboxItem = React.forwardRef<
<DropdownMenuPrimitive.CheckboxItem <DropdownMenuPrimitive.CheckboxItem
ref={ref} ref={ref}
className={cn( className={cn(
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50", 'relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
className className,
)} )}
checked={checked} checked={checked}
{...props} {...props}
> >
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center"> <span className='absolute left-2 flex h-3.5 w-3.5 items-center justify-center'>
<DropdownMenuPrimitive.ItemIndicator> <DropdownMenuPrimitive.ItemIndicator>
<Check className="h-4 w-4" /> <Check className='h-4 w-4' />
</DropdownMenuPrimitive.ItemIndicator> </DropdownMenuPrimitive.ItemIndicator>
</span> </span>
{children} {children}
</DropdownMenuPrimitive.CheckboxItem> </DropdownMenuPrimitive.CheckboxItem>
)) ));
DropdownMenuCheckboxItem.displayName = DropdownMenuCheckboxItem.displayName =
DropdownMenuPrimitive.CheckboxItem.displayName DropdownMenuPrimitive.CheckboxItem.displayName;
const DropdownMenuRadioItem = React.forwardRef< const DropdownMenuRadioItem = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.RadioItem>, React.ElementRef<typeof DropdownMenuPrimitive.RadioItem>,
@ -124,38 +124,38 @@ const DropdownMenuRadioItem = React.forwardRef<
<DropdownMenuPrimitive.RadioItem <DropdownMenuPrimitive.RadioItem
ref={ref} ref={ref}
className={cn( className={cn(
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50", 'relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
className className,
)} )}
{...props} {...props}
> >
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center"> <span className='absolute left-2 flex h-3.5 w-3.5 items-center justify-center'>
<DropdownMenuPrimitive.ItemIndicator> <DropdownMenuPrimitive.ItemIndicator>
<Circle className="h-2 w-2 fill-current" /> <Circle className='h-2 w-2 fill-current' />
</DropdownMenuPrimitive.ItemIndicator> </DropdownMenuPrimitive.ItemIndicator>
</span> </span>
{children} {children}
</DropdownMenuPrimitive.RadioItem> </DropdownMenuPrimitive.RadioItem>
)) ));
DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName;
const DropdownMenuLabel = React.forwardRef< const DropdownMenuLabel = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Label>, React.ElementRef<typeof DropdownMenuPrimitive.Label>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & { React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & {
inset?: boolean inset?: boolean;
} }
>(({ className, inset, ...props }, ref) => ( >(({ className, inset, ...props }, ref) => (
<DropdownMenuPrimitive.Label <DropdownMenuPrimitive.Label
ref={ref} ref={ref}
className={cn( className={cn(
"px-2 py-1.5 text-sm font-semibold", 'px-2 py-1.5 text-sm font-semibold',
inset && "pl-8", inset && 'pl-8',
className className,
)} )}
{...props} {...props}
/> />
)) ));
DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName;
const DropdownMenuSeparator = React.forwardRef< const DropdownMenuSeparator = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Separator>, React.ElementRef<typeof DropdownMenuPrimitive.Separator>,
@ -163,11 +163,11 @@ const DropdownMenuSeparator = React.forwardRef<
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<DropdownMenuPrimitive.Separator <DropdownMenuPrimitive.Separator
ref={ref} ref={ref}
className={cn("-mx-1 my-1 h-px bg-muted", className)} className={cn('-mx-1 my-1 h-px bg-muted', className)}
{...props} {...props}
/> />
)) ));
DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName;
const DropdownMenuShortcut = ({ const DropdownMenuShortcut = ({
className, className,
@ -175,12 +175,12 @@ const DropdownMenuShortcut = ({
}: React.HTMLAttributes<HTMLSpanElement>) => { }: React.HTMLAttributes<HTMLSpanElement>) => {
return ( return (
<span <span
className={cn("ml-auto text-xs tracking-widest opacity-60", className)} className={cn('ml-auto text-xs tracking-widest opacity-60', className)}
{...props} {...props}
/> />
) );
} };
DropdownMenuShortcut.displayName = "DropdownMenuShortcut" DropdownMenuShortcut.displayName = 'DropdownMenuShortcut';
export { export {
DropdownMenu, DropdownMenu,
@ -198,4 +198,4 @@ export {
DropdownMenuSubContent, DropdownMenuSubContent,
DropdownMenuSubTrigger, DropdownMenuSubTrigger,
DropdownMenuRadioGroup, DropdownMenuRadioGroup,
} };

View File

@ -1,8 +1,8 @@
"use client" 'use client';
import * as React from "react" import * as React from 'react';
import * as LabelPrimitive from "@radix-ui/react-label" import * as LabelPrimitive from '@radix-ui/react-label';
import { Slot } from "@radix-ui/react-slot" import { Slot } from '@radix-ui/react-slot';
import { import {
Controller, Controller,
FormProvider, FormProvider,
@ -10,27 +10,27 @@ import {
type ControllerProps, type ControllerProps,
type FieldPath, type FieldPath,
type FieldValues, type FieldValues,
} from "react-hook-form" } from 'react-hook-form';
import { cn } from "@/lib/utils" import { cn } from '@/lib/utils';
import { Label } from "@/components/ui/label" import { Label } from '@/components/ui/label';
const Form = FormProvider const Form = FormProvider;
type FormFieldContextValue< type FormFieldContextValue<
TFieldValues extends FieldValues = FieldValues, TFieldValues extends FieldValues = FieldValues,
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues> TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
> = { > = {
name: TName name: TName;
} };
const FormFieldContext = React.createContext<FormFieldContextValue>( const FormFieldContext = React.createContext<FormFieldContextValue>(
{} as FormFieldContextValue {} as FormFieldContextValue,
) );
const FormField = < const FormField = <
TFieldValues extends FieldValues = FieldValues, TFieldValues extends FieldValues = FieldValues,
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues> TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
>({ >({
...props ...props
}: ControllerProps<TFieldValues, TName>) => { }: ControllerProps<TFieldValues, TName>) => {
@ -38,21 +38,21 @@ const FormField = <
<FormFieldContext.Provider value={{ name: props.name }}> <FormFieldContext.Provider value={{ name: props.name }}>
<Controller {...props} /> <Controller {...props} />
</FormFieldContext.Provider> </FormFieldContext.Provider>
) );
} };
const useFormField = () => { const useFormField = () => {
const fieldContext = React.useContext(FormFieldContext) const fieldContext = React.useContext(FormFieldContext);
const itemContext = React.useContext(FormItemContext) const itemContext = React.useContext(FormItemContext);
const { getFieldState, formState } = useFormContext() const { getFieldState, formState } = useFormContext();
const fieldState = getFieldState(fieldContext.name, formState) const fieldState = getFieldState(fieldContext.name, formState);
if (!fieldContext) { if (!fieldContext) {
throw new Error("useFormField should be used within <FormField>") throw new Error('useFormField should be used within <FormField>');
} }
const { id } = itemContext const { id } = itemContext;
return { return {
id, id,
@ -61,53 +61,54 @@ const useFormField = () => {
formDescriptionId: `${id}-form-item-description`, formDescriptionId: `${id}-form-item-description`,
formMessageId: `${id}-form-item-message`, formMessageId: `${id}-form-item-message`,
...fieldState, ...fieldState,
} };
} };
type FormItemContextValue = { type FormItemContextValue = {
id: string id: string;
} };
const FormItemContext = React.createContext<FormItemContextValue>( const FormItemContext = React.createContext<FormItemContextValue>(
{} as FormItemContextValue {} as FormItemContextValue,
) );
const FormItem = React.forwardRef< const FormItem = React.forwardRef<
HTMLDivElement, HTMLDivElement,
React.HTMLAttributes<HTMLDivElement> React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => { >(({ className, ...props }, ref) => {
const id = React.useId() const id = React.useId();
return ( return (
<FormItemContext.Provider value={{ id }}> <FormItemContext.Provider value={{ id }}>
<div ref={ref} className={cn("space-y-2", className)} {...props} /> <div ref={ref} className={cn('space-y-2', className)} {...props} />
</FormItemContext.Provider> </FormItemContext.Provider>
) );
}) });
FormItem.displayName = "FormItem" FormItem.displayName = 'FormItem';
const FormLabel = React.forwardRef< const FormLabel = React.forwardRef<
React.ElementRef<typeof LabelPrimitive.Root>, React.ElementRef<typeof LabelPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root>
>(({ className, ...props }, ref) => { >(({ className, ...props }, ref) => {
const { error, formItemId } = useFormField() const { error, formItemId } = useFormField();
return ( return (
<Label <Label
ref={ref} ref={ref}
className={cn(error && "text-destructive", className)} className={cn(error && 'text-destructive', className)}
htmlFor={formItemId} htmlFor={formItemId}
{...props} {...props}
/> />
) );
}) });
FormLabel.displayName = "FormLabel" FormLabel.displayName = 'FormLabel';
const FormControl = React.forwardRef< const FormControl = React.forwardRef<
React.ElementRef<typeof Slot>, React.ElementRef<typeof Slot>,
React.ComponentPropsWithoutRef<typeof Slot> React.ComponentPropsWithoutRef<typeof Slot>
>(({ ...props }, ref) => { >(({ ...props }, ref) => {
const { error, formItemId, formDescriptionId, formMessageId } = useFormField() const { error, formItemId, formDescriptionId, formMessageId } =
useFormField();
return ( return (
<Slot <Slot
@ -121,50 +122,50 @@ const FormControl = React.forwardRef<
aria-invalid={!!error} aria-invalid={!!error}
{...props} {...props}
/> />
) );
}) });
FormControl.displayName = "FormControl" FormControl.displayName = 'FormControl';
const FormDescription = React.forwardRef< const FormDescription = React.forwardRef<
HTMLParagraphElement, HTMLParagraphElement,
React.HTMLAttributes<HTMLParagraphElement> React.HTMLAttributes<HTMLParagraphElement>
>(({ className, ...props }, ref) => { >(({ className, ...props }, ref) => {
const { formDescriptionId } = useFormField() const { formDescriptionId } = useFormField();
return ( return (
<p <p
ref={ref} ref={ref}
id={formDescriptionId} id={formDescriptionId}
className={cn("text-[0.8rem] text-muted-foreground", className)} className={cn('text-[0.8rem] text-muted-foreground', className)}
{...props} {...props}
/> />
) );
}) });
FormDescription.displayName = "FormDescription" FormDescription.displayName = 'FormDescription';
const FormMessage = React.forwardRef< const FormMessage = React.forwardRef<
HTMLParagraphElement, HTMLParagraphElement,
React.HTMLAttributes<HTMLParagraphElement> React.HTMLAttributes<HTMLParagraphElement>
>(({ className, children, ...props }, ref) => { >(({ className, children, ...props }, ref) => {
const { error, formMessageId } = useFormField() const { error, formMessageId } = useFormField();
const body = error ? String(error?.message ?? "") : children const body = error ? String(error?.message ?? '') : children;
if (!body) { if (!body) {
return null return null;
} }
return ( return (
<p <p
ref={ref} ref={ref}
id={formMessageId} id={formMessageId}
className={cn("text-[0.8rem] font-medium text-destructive", className)} className={cn('text-[0.8rem] font-medium text-destructive', className)}
{...props} {...props}
> >
{body} {body}
</p> </p>
) );
}) });
FormMessage.displayName = "FormMessage" FormMessage.displayName = 'FormMessage';
export { export {
useFormField, useFormField,
@ -175,4 +176,4 @@ export {
FormDescription, FormDescription,
FormMessage, FormMessage,
FormField, FormField,
} };

View File

@ -1,22 +1,22 @@
import * as React from "react" import * as React from 'react';
import { cn } from "@/lib/utils" import { cn } from '@/lib/utils';
const Input = React.forwardRef<HTMLInputElement, React.ComponentProps<"input">>( const Input = React.forwardRef<HTMLInputElement, React.ComponentProps<'input'>>(
({ className, type, ...props }, ref) => { ({ className, type, ...props }, ref) => {
return ( return (
<input <input
type={type} type={type}
className={cn( className={cn(
"flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-base shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 md:text-sm", 'flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-base shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 md:text-sm',
className className,
)} )}
ref={ref} ref={ref}
{...props} {...props}
/> />
) );
} },
) );
Input.displayName = "Input" Input.displayName = 'Input';
export { Input } export { Input };

View File

@ -1,14 +1,14 @@
"use client" 'use client';
import * as React from "react" import * as React from 'react';
import * as LabelPrimitive from "@radix-ui/react-label" import * as LabelPrimitive from '@radix-ui/react-label';
import { cva, type VariantProps } from "class-variance-authority" import { cva, type VariantProps } from 'class-variance-authority';
import { cn } from "@/lib/utils" import { cn } from '@/lib/utils';
const labelVariants = cva( const labelVariants = cva(
"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70" 'text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70',
) );
const Label = React.forwardRef< const Label = React.forwardRef<
React.ElementRef<typeof LabelPrimitive.Root>, React.ElementRef<typeof LabelPrimitive.Root>,
@ -20,7 +20,7 @@ const Label = React.forwardRef<
className={cn(labelVariants(), className)} className={cn(labelVariants(), className)}
{...props} {...props}
/> />
)) ));
Label.displayName = LabelPrimitive.Root.displayName Label.displayName = LabelPrimitive.Root.displayName;
export { Label } export { Label };

View File

@ -1,9 +1,9 @@
"use client" 'use client';
import * as React from "react" import * as React from 'react';
import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area" import * as ScrollAreaPrimitive from '@radix-ui/react-scroll-area';
import { cn } from "@/lib/utils" import { cn } from '@/lib/utils';
const ScrollArea = React.forwardRef< const ScrollArea = React.forwardRef<
React.ElementRef<typeof ScrollAreaPrimitive.Root>, React.ElementRef<typeof ScrollAreaPrimitive.Root>,
@ -11,38 +11,38 @@ const ScrollArea = React.forwardRef<
>(({ className, children, ...props }, ref) => ( >(({ className, children, ...props }, ref) => (
<ScrollAreaPrimitive.Root <ScrollAreaPrimitive.Root
ref={ref} ref={ref}
className={cn("relative overflow-hidden", className)} className={cn('relative overflow-hidden', className)}
{...props} {...props}
> >
<ScrollAreaPrimitive.Viewport className="h-full w-full rounded-[inherit]"> <ScrollAreaPrimitive.Viewport className='h-full w-full rounded-[inherit]'>
{children} {children}
</ScrollAreaPrimitive.Viewport> </ScrollAreaPrimitive.Viewport>
<ScrollBar /> <ScrollBar />
<ScrollAreaPrimitive.Corner /> <ScrollAreaPrimitive.Corner />
</ScrollAreaPrimitive.Root> </ScrollAreaPrimitive.Root>
)) ));
ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName;
const ScrollBar = React.forwardRef< const ScrollBar = React.forwardRef<
React.ElementRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>, React.ElementRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>,
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar> React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>
>(({ className, orientation = "vertical", ...props }, ref) => ( >(({ className, orientation = 'vertical', ...props }, ref) => (
<ScrollAreaPrimitive.ScrollAreaScrollbar <ScrollAreaPrimitive.ScrollAreaScrollbar
ref={ref} ref={ref}
orientation={orientation} orientation={orientation}
className={cn( className={cn(
"flex touch-none select-none transition-colors", 'flex touch-none select-none transition-colors',
orientation === "vertical" && orientation === 'vertical' &&
"h-full w-2.5 border-l border-l-transparent p-[1px]", 'h-full w-2.5 border-l border-l-transparent p-[1px]',
orientation === "horizontal" && orientation === 'horizontal' &&
"h-2.5 flex-col border-t border-t-transparent p-[1px]", 'h-2.5 flex-col border-t border-t-transparent p-[1px]',
className className,
)} )}
{...props} {...props}
> >
<ScrollAreaPrimitive.ScrollAreaThumb className="relative flex-1 rounded-full bg-border" /> <ScrollAreaPrimitive.ScrollAreaThumb className='relative flex-1 rounded-full bg-border' />
</ScrollAreaPrimitive.ScrollAreaScrollbar> </ScrollAreaPrimitive.ScrollAreaScrollbar>
)) ));
ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName;
export { ScrollArea, ScrollBar } export { ScrollArea, ScrollBar };

View File

@ -1,31 +1,31 @@
"use client" 'use client';
import * as React from "react" import * as React from 'react';
import * as SeparatorPrimitive from "@radix-ui/react-separator" import * as SeparatorPrimitive from '@radix-ui/react-separator';
import { cn } from "@/lib/utils" import { cn } from '@/lib/utils';
const Separator = React.forwardRef< const Separator = React.forwardRef<
React.ElementRef<typeof SeparatorPrimitive.Root>, React.ElementRef<typeof SeparatorPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof SeparatorPrimitive.Root> React.ComponentPropsWithoutRef<typeof SeparatorPrimitive.Root>
>( >(
( (
{ className, orientation = "horizontal", decorative = true, ...props }, { className, orientation = 'horizontal', decorative = true, ...props },
ref ref,
) => ( ) => (
<SeparatorPrimitive.Root <SeparatorPrimitive.Root
ref={ref} ref={ref}
decorative={decorative} decorative={decorative}
orientation={orientation} orientation={orientation}
className={cn( className={cn(
"shrink-0 bg-border", 'shrink-0 bg-border',
orientation === "horizontal" ? "h-[1px] w-full" : "h-full w-[1px]", orientation === 'horizontal' ? 'h-[1px] w-full' : 'h-full w-[1px]',
className className,
)} )}
{...props} {...props}
/> />
) ),
) );
Separator.displayName = SeparatorPrimitive.Root.displayName Separator.displayName = SeparatorPrimitive.Root.displayName;
export { Separator } export { Separator };

View File

@ -1,6 +1,6 @@
import { clsx, type ClassValue } from "clsx" import { clsx, type ClassValue } from 'clsx';
import { twMerge } from "tailwind-merge" import { twMerge } from 'tailwind-merge';
export function cn(...inputs: ClassValue[]) { export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs)) return twMerge(clsx(inputs));
} }

View File

@ -1,9 +1,9 @@
import { type NextRequest } from 'next/server' import { type NextRequest } from 'next/server';
import { updateSession } from '@/utils/supabase/middleware' import { updateSession } from '@/utils/supabase/middleware';
export async function middleware(request: NextRequest) { export async function middleware(request: NextRequest) {
// update user's auth session // update user's auth session
return await updateSession(request) return await updateSession(request);
} }
export const config = { export const config = {
@ -17,4 +17,4 @@ export const config = {
*/ */
'/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)', '/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)',
], ],
} };

View File

@ -1,32 +1,33 @@
'use server' 'use server';
import 'server-only'; import 'server-only';
import { revalidatePath } from 'next/cache' import { revalidatePath } from 'next/cache';
import { redirect } from 'next/navigation' import { redirect } from 'next/navigation';
import { createClient } from '@/utils/supabase/server' import { createClient } from '@/utils/supabase/server';
export const login = async (formData: FormData) => { export const login = async (formData: FormData) => {
const supabase = await createClient() const supabase = await createClient();
// type-casting here for convenience // type-casting here for convenience
// in practice, you should validate your inputs // in practice, you should validate your inputs
const data = { const data = {
email: formData.get('email') as string, email: formData.get('email') as string,
password: formData.get('password') as string, password: formData.get('password') as string,
} };
const { error } = await supabase.auth.signInWithPassword(data) const { error } = await supabase.auth.signInWithPassword(data);
if (error) { if (error) {
redirect('/error') redirect('/error');
} }
revalidatePath('/', 'layout') revalidatePath('/', 'layout');
redirect('/');
}; };
export const signup = async (formData: FormData) => { export const signup = async (formData: FormData) => {
const supabase = await createClient() const supabase = await createClient();
// type-casting here for convenience // type-casting here for convenience
// in practice, you should validate your inputs // in practice, you should validate your inputs
@ -34,13 +35,14 @@ export const signup = async (formData: FormData) => {
fullName: formData.get('fullName') as string, fullName: formData.get('fullName') as string,
email: formData.get('email') as string, email: formData.get('email') as string,
password: formData.get('password') as string, password: formData.get('password') as string,
} };
const { error } = await supabase.auth.signUp(data) const { error } = await supabase.auth.signUp(data);
if (error) { if (error) {
redirect('/error') redirect('/error');
} }
revalidatePath('/', 'layout') revalidatePath('/', 'layout');
redirect('/');
}; };

View File

@ -1,8 +1,8 @@
import { createBrowserClient } from '@supabase/ssr' import { createBrowserClient } from '@supabase/ssr';
export const createClient = () => { export const createClient = () => {
return createBrowserClient( return createBrowserClient(
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!,
) );
}; };

View File

@ -1,31 +1,33 @@
import { createServerClient } from '@supabase/ssr' import { createServerClient } from '@supabase/ssr';
import { NextResponse, type NextRequest } from 'next/server' import { NextResponse, type NextRequest } from 'next/server';
export async function updateSession(request: NextRequest) { export async function updateSession(request: NextRequest) {
let supabaseResponse = NextResponse.next({ let supabaseResponse = NextResponse.next({
request, request,
}) });
const supabase = createServerClient( const supabase = createServerClient(
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!,
{ {
cookies: { cookies: {
getAll() { getAll() {
return request.cookies.getAll() return request.cookies.getAll();
}, },
setAll(cookiesToSet) { setAll(cookiesToSet) {
cookiesToSet.forEach(({ name, value, options }) => request.cookies.set(name, value)) cookiesToSet.forEach(({ name, value, options }) =>
request.cookies.set(name, value),
);
supabaseResponse = NextResponse.next({ supabaseResponse = NextResponse.next({
request, request,
}) });
cookiesToSet.forEach(({ name, value, options }) => cookiesToSet.forEach(({ name, value, options }) =>
supabaseResponse.cookies.set(name, value, options) supabaseResponse.cookies.set(name, value, options),
) );
}, },
}, },
} },
) );
// refreshing the auth token // refreshing the auth token
await supabase.auth.getUser() await supabase.auth.getUser();
return supabaseResponse return supabaseResponse;
} }

View File

@ -1,8 +1,8 @@
import { createServerClient } from '@supabase/ssr' import { createServerClient } from '@supabase/ssr';
import { cookies } from 'next/headers' import { cookies } from 'next/headers';
export async function createClient() { export async function createClient() {
const cookieStore = await cookies() const cookieStore = await cookies();
// Create a server's supabase client with newly configured cookie, // Create a server's supabase client with newly configured cookie,
// which could be used to maintain user's session // which could be used to maintain user's session
@ -12,13 +12,13 @@ export async function createClient() {
{ {
cookies: { cookies: {
getAll() { getAll() {
return cookieStore.getAll() return cookieStore.getAll();
}, },
setAll(cookiesToSet) { setAll(cookiesToSet) {
try { try {
cookiesToSet.forEach(({ name, value, options }) => cookiesToSet.forEach(({ name, value, options }) =>
cookieStore.set(name, value, options) cookieStore.set(name, value, options),
) );
} catch { } catch {
// The `setAll` method was called from a Server Component. // The `setAll` method was called from a Server Component.
// This can be ignored if you have middleware refreshing // This can be ignored if you have middleware refreshing
@ -26,6 +26,6 @@ export async function createClient() {
} }
}, },
}, },
} },
) );
} }

View File

@ -7,54 +7,51 @@ export default {
theme: { theme: {
extend: { extend: {
fontFamily: { fontFamily: {
sans: [ sans: ['var(--font-geist-sans)', ...fontFamily.sans],
'var(--font-geist-sans)',
...fontFamily.sans
]
}, },
borderRadius: { borderRadius: {
lg: 'var(--radius)', lg: 'var(--radius)',
md: 'calc(var(--radius) - 2px)', md: 'calc(var(--radius) - 2px)',
sm: 'calc(var(--radius) - 4px)' sm: 'calc(var(--radius) - 4px)',
}, },
colors: { colors: {
background: 'hsl(var(--background))', background: 'hsl(var(--background))',
foreground: 'hsl(var(--foreground))', foreground: 'hsl(var(--foreground))',
card: { card: {
DEFAULT: 'hsl(var(--card))', DEFAULT: 'hsl(var(--card))',
foreground: 'hsl(var(--card-foreground))' foreground: 'hsl(var(--card-foreground))',
}, },
popover: { popover: {
DEFAULT: 'hsl(var(--popover))', DEFAULT: 'hsl(var(--popover))',
foreground: 'hsl(var(--popover-foreground))' foreground: 'hsl(var(--popover-foreground))',
}, },
primary: { primary: {
DEFAULT: 'hsl(var(--primary))', DEFAULT: 'hsl(var(--primary))',
foreground: 'hsl(var(--primary-foreground))' foreground: 'hsl(var(--primary-foreground))',
}, },
primarydark: { primarydark: {
DEFAULT: 'hsl(var(--primary-dark))', DEFAULT: 'hsl(var(--primary-dark))',
foreground: 'hsl(var(--primary-foreground))' foreground: 'hsl(var(--primary-foreground))',
}, },
primarylight: { primarylight: {
DEFAULT: 'hsl(var(--primary-light))', DEFAULT: 'hsl(var(--primary-light))',
foreground: 'hsl(var(--primary-foreground))' foreground: 'hsl(var(--primary-foreground))',
}, },
secondary: { secondary: {
DEFAULT: 'hsl(var(--secondary))', DEFAULT: 'hsl(var(--secondary))',
foreground: 'hsl(var(--secondary-foreground))' foreground: 'hsl(var(--secondary-foreground))',
}, },
muted: { muted: {
DEFAULT: 'hsl(var(--muted))', DEFAULT: 'hsl(var(--muted))',
foreground: 'hsl(var(--muted-foreground))' foreground: 'hsl(var(--muted-foreground))',
}, },
accent: { accent: {
DEFAULT: 'hsl(var(--accent))', DEFAULT: 'hsl(var(--accent))',
foreground: 'hsl(var(--accent-foreground))' foreground: 'hsl(var(--accent-foreground))',
}, },
destructive: { destructive: {
DEFAULT: 'hsl(var(--destructive))', DEFAULT: 'hsl(var(--destructive))',
foreground: 'hsl(var(--destructive-foreground))' foreground: 'hsl(var(--destructive-foreground))',
}, },
border: 'hsl(var(--border))', border: 'hsl(var(--border))',
input: 'hsl(var(--input))', input: 'hsl(var(--input))',
@ -64,10 +61,10 @@ export default {
'2': 'hsl(var(--chart-2))', '2': 'hsl(var(--chart-2))',
'3': 'hsl(var(--chart-3))', '3': 'hsl(var(--chart-3))',
'4': 'hsl(var(--chart-4))', '4': 'hsl(var(--chart-4))',
'5': 'hsl(var(--chart-5))' '5': 'hsl(var(--chart-5))',
}
}
}
}, },
plugins: [require("tailwindcss-animate")], },
},
},
plugins: [require('tailwindcss-animate')],
} satisfies Config; } satisfies Config;