Ran prettier. Moved Header to layout file
This commit is contained in:
parent
f5e3cb6234
commit
939fd796ee
@ -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';
|
||||||
|
|
||||||
|
@ -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);
|
||||||
}
|
};
|
||||||
|
@ -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;
|
||||||
|
@ -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>
|
||||||
{children}
|
<main className='min-h-screen'>
|
||||||
|
<Header />
|
||||||
|
{children}
|
||||||
|
</main>
|
||||||
</TVModeProvider>
|
</TVModeProvider>
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
</body>
|
</body>
|
||||||
|
@ -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 />
|
<LoginForm />
|
||||||
<div className="flex flex-col items-center justify-center md:min-h-[80vh]">
|
</div>
|
||||||
<LoginForm />
|
|
||||||
</div>
|
|
||||||
</main>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<main>
|
<div className='flex flex-col items-center justify-center'>
|
||||||
<Header />
|
<h1>Hello, {session.user.email}</h1>
|
||||||
<div className="flex flex-col items-center justify-center">
|
</div>
|
||||||
<h1>Hello, {session.user.email}</h1>
|
|
||||||
</div>
|
|
||||||
</main>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
export default HomePage;
|
export default HomePage;
|
||||||
|
@ -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 },
|
||||||
setSession(session);
|
} = supabase.auth.onAuthStateChange((_event, 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) ??
|
||||||
'/images/default_user_pfp.png';
|
(session.user?.user_metadata?.picture as string) ??
|
||||||
|
'/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;
|
||||||
|
@ -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>
|
||||||
|
@ -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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -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
|
||||||
|
@ -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 ? (
|
||||||
|
@ -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 />
|
||||||
|
@ -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 };
|
||||||
|
@ -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 };
|
||||||
|
@ -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,
|
||||||
|
};
|
||||||
|
@ -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,
|
||||||
}
|
};
|
||||||
|
@ -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,
|
||||||
}
|
};
|
||||||
|
@ -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 };
|
||||||
|
@ -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 };
|
||||||
|
@ -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 };
|
||||||
|
@ -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 };
|
||||||
|
@ -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));
|
||||||
}
|
}
|
||||||
|
@ -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)$).*)',
|
||||||
],
|
],
|
||||||
}
|
};
|
||||||
|
@ -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('/');
|
||||||
};
|
};
|
||||||
|
@ -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!,
|
||||||
)
|
);
|
||||||
};
|
};
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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() {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
@ -2,72 +2,69 @@ import { type Config } from 'tailwindcss';
|
|||||||
import { fontFamily } from 'tailwindcss/defaultTheme';
|
import { fontFamily } from 'tailwindcss/defaultTheme';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
darkMode: ['class'],
|
darkMode: ['class'],
|
||||||
content: ['./src/**/*.tsx'],
|
content: ['./src/**/*.tsx'],
|
||||||
theme: {
|
theme: {
|
||||||
extend: {
|
extend: {
|
||||||
fontFamily: {
|
fontFamily: {
|
||||||
sans: [
|
sans: ['var(--font-geist-sans)', ...fontFamily.sans],
|
||||||
'var(--font-geist-sans)',
|
},
|
||||||
...fontFamily.sans
|
borderRadius: {
|
||||||
]
|
lg: 'var(--radius)',
|
||||||
},
|
md: 'calc(var(--radius) - 2px)',
|
||||||
borderRadius: {
|
sm: 'calc(var(--radius) - 4px)',
|
||||||
lg: 'var(--radius)',
|
},
|
||||||
md: 'calc(var(--radius) - 2px)',
|
colors: {
|
||||||
sm: 'calc(var(--radius) - 4px)'
|
background: 'hsl(var(--background))',
|
||||||
},
|
foreground: 'hsl(var(--foreground))',
|
||||||
colors: {
|
card: {
|
||||||
background: 'hsl(var(--background))',
|
DEFAULT: 'hsl(var(--card))',
|
||||||
foreground: 'hsl(var(--foreground))',
|
foreground: 'hsl(var(--card-foreground))',
|
||||||
card: {
|
},
|
||||||
DEFAULT: 'hsl(var(--card))',
|
popover: {
|
||||||
foreground: 'hsl(var(--card-foreground))'
|
DEFAULT: 'hsl(var(--popover))',
|
||||||
},
|
foreground: 'hsl(var(--popover-foreground))',
|
||||||
popover: {
|
},
|
||||||
DEFAULT: 'hsl(var(--popover))',
|
primary: {
|
||||||
foreground: 'hsl(var(--popover-foreground))'
|
DEFAULT: 'hsl(var(--primary))',
|
||||||
},
|
foreground: 'hsl(var(--primary-foreground))',
|
||||||
primary: {
|
},
|
||||||
DEFAULT: 'hsl(var(--primary))',
|
primarydark: {
|
||||||
foreground: 'hsl(var(--primary-foreground))'
|
DEFAULT: 'hsl(var(--primary-dark))',
|
||||||
},
|
foreground: 'hsl(var(--primary-foreground))',
|
||||||
primarydark: {
|
},
|
||||||
DEFAULT: 'hsl(var(--primary-dark))',
|
primarylight: {
|
||||||
foreground: 'hsl(var(--primary-foreground))'
|
DEFAULT: 'hsl(var(--primary-light))',
|
||||||
},
|
foreground: 'hsl(var(--primary-foreground))',
|
||||||
primarylight: {
|
},
|
||||||
DEFAULT: 'hsl(var(--primary-light))',
|
secondary: {
|
||||||
foreground: 'hsl(var(--primary-foreground))'
|
DEFAULT: 'hsl(var(--secondary))',
|
||||||
},
|
foreground: 'hsl(var(--secondary-foreground))',
|
||||||
secondary: {
|
},
|
||||||
DEFAULT: 'hsl(var(--secondary))',
|
muted: {
|
||||||
foreground: 'hsl(var(--secondary-foreground))'
|
DEFAULT: 'hsl(var(--muted))',
|
||||||
},
|
foreground: 'hsl(var(--muted-foreground))',
|
||||||
muted: {
|
},
|
||||||
DEFAULT: 'hsl(var(--muted))',
|
accent: {
|
||||||
foreground: 'hsl(var(--muted-foreground))'
|
DEFAULT: 'hsl(var(--accent))',
|
||||||
},
|
foreground: 'hsl(var(--accent-foreground))',
|
||||||
accent: {
|
},
|
||||||
DEFAULT: 'hsl(var(--accent))',
|
destructive: {
|
||||||
foreground: 'hsl(var(--accent-foreground))'
|
DEFAULT: 'hsl(var(--destructive))',
|
||||||
},
|
foreground: 'hsl(var(--destructive-foreground))',
|
||||||
destructive: {
|
},
|
||||||
DEFAULT: 'hsl(var(--destructive))',
|
border: 'hsl(var(--border))',
|
||||||
foreground: 'hsl(var(--destructive-foreground))'
|
input: 'hsl(var(--input))',
|
||||||
},
|
ring: 'hsl(var(--ring))',
|
||||||
border: 'hsl(var(--border))',
|
chart: {
|
||||||
input: 'hsl(var(--input))',
|
'1': 'hsl(var(--chart-1))',
|
||||||
ring: 'hsl(var(--ring))',
|
'2': 'hsl(var(--chart-2))',
|
||||||
chart: {
|
'3': 'hsl(var(--chart-3))',
|
||||||
'1': 'hsl(var(--chart-1))',
|
'4': 'hsl(var(--chart-4))',
|
||||||
'2': 'hsl(var(--chart-2))',
|
'5': 'hsl(var(--chart-5))',
|
||||||
'3': 'hsl(var(--chart-3))',
|
},
|
||||||
'4': 'hsl(var(--chart-4))',
|
},
|
||||||
'5': 'hsl(var(--chart-5))'
|
},
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
plugins: [require("tailwindcss-animate")],
|
plugins: [require('tailwindcss-animate')],
|
||||||
} satisfies Config;
|
} satisfies Config;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user