I fixed some issues with how we were setting up styles. should be consistent now

This commit is contained in:
2025-07-03 15:37:08 -05:00
parent 489009d35d
commit 6ef77c481d
15 changed files with 727 additions and 509 deletions

View File

@@ -1,3 +1,3 @@
export { SignInWithApple } from './sign-in-with-apple'; export { SignInWithApple, type SignInWithAppleProps } from './sign-in-with-apple';
export { SignInWithMicrosoft } from './sign-in-with-microsoft'; export { SignInWithMicrosoft, type SignInWithMicrosoftProps } from './sign-in-with-microsoft';
export { SignInLinkButton } from './sign-in-link'; export { SignInLinkButton } from './sign-in-link';

View File

@@ -1,12 +1,11 @@
'use client'; 'use client';
import Link from 'next/link'; import Link from 'next/link';
import { Button, type buttonVariants } from '@/components/ui'; import { Button } from '@/components/ui';
import { type ComponentProps } from 'react'; import { type ComponentProps } from 'react';
import { type VariantProps } from 'class-variance-authority';
type SignInProps = ComponentProps<'button'> & VariantProps<typeof buttonVariants>; type SignInLinkButtonProps = Omit<ComponentProps<typeof Button>, 'asChild'>;
export const SignInLinkButton = (props: SignInProps) => { export const SignInLinkButton = (props: SignInLinkButtonProps) => {
return ( return (
<Button asChild {...props}> <Button asChild {...props}>
<Link href='/sign-in'>Sign In</Link> <Link href='/sign-in'>Sign In</Link>

View File

@@ -1,36 +1,36 @@
'use client'; 'use client';
import { signInWithApple } from '@/lib/queries'; import { signInWithApple } from '@/lib/queries';
import { StatusMessage, SubmitButton } from '@/components/default/forms'; import {
StatusMessage,
SubmitButton,
type SubmitButtonProps,
} from '@/components/default/forms';
import { useAuth } from '@/lib/hooks/context'; import { useAuth } from '@/lib/hooks/context';
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
import { useState } from 'react'; import { useState } from 'react';
import { useSupabaseClient } from '@/utils/supabase'; import { useSupabaseClient } from '@/utils/supabase';
import { FaApple } from 'react-icons/fa'; import { FaApple } from 'react-icons/fa';
import { type buttonVariants } from '@/components/ui';
import { type ComponentProps } from 'react'; import { type ComponentProps } from 'react';
import { type VariantProps } from 'class-variance-authority'; import { cn } from '@/lib/utils';
type SignInWithAppleProps = { export type SignInWithAppleProps = {
buttonProps?: ComponentProps<'button'> & VariantProps<typeof buttonVariants>; submitButtonProps?: SubmitButtonProps;
formProps?: ComponentProps<'form'>; formClassName?: ComponentProps<'form'>['className'];
textProps?: ComponentProps<'p'>; formProps?: Omit<ComponentProps<'form'>, 'className'>;
iconProps?: ComponentProps<'svg'>; textClassName?: ComponentProps<'p'>['className'];
textProps?: Omit<ComponentProps<'p'>, 'className'>;
iconClassName?: ComponentProps<'svg'>['className'];
iconProps?: Omit<ComponentProps<'svg'>, 'className'>;
}; };
export const SignInWithApple = ({ export const SignInWithApple = ({
buttonProps = { submitButtonProps,
className: 'w-full cursor-pointer', formClassName,
type: 'submit', formProps,
}, textClassName,
formProps = { textProps,
className: 'my-4', iconClassName,
}, iconProps,
textProps = {
className: 'text-[1.0rem]',
},
iconProps = {
className: 'size-5',
}
} : SignInWithAppleProps) => { } : SignInWithAppleProps) => {
const router = useRouter(); const router = useRouter();
const { loading, refreshUser } = useAuth(); const { loading, refreshUser } = useAuth();
@@ -57,15 +57,22 @@ export const SignInWithApple = ({
}; };
return ( return (
<form onSubmit={handleSignInWithApple} {...formProps}> <form
onSubmit={handleSignInWithApple}
className={cn('my-4', formClassName)}
{...formProps}
>
<SubmitButton <SubmitButton
disabled={isLoading || loading} disabled={isLoading || loading}
pendingText='Signing in...' pendingText='Signing in...'
{...buttonProps} className={cn('w-full', submitButtonProps?.className)}
{...submitButtonProps}
> >
<div className='flex items-center gap-2'> <div className='flex items-center gap-2'>
<FaApple {...iconProps} /> <FaApple className={cn('size-5', iconClassName)} {...iconProps} />
<p className={textProps.className} {...textProps}>Sign In with Apple</p> <p className={cn('text-[1.0rem]', textClassName)} {...textProps}>
Sign In with Apple
</p>
</div> </div>
</SubmitButton> </SubmitButton>
{statusMessage && <StatusMessage message={{ error: statusMessage }} />} {statusMessage && <StatusMessage message={{ error: statusMessage }} />}

View File

@@ -1,36 +1,36 @@
'use client'; 'use client';
import { signInWithMicrosoft } from '@/lib/queries'; import { signInWithMicrosoft } from '@/lib/queries';
import { StatusMessage, SubmitButton } from '@/components/default/forms'; import {
StatusMessage,
SubmitButton,
type SubmitButtonProps,
} from '@/components/default/forms';
import { useAuth } from '@/lib/hooks/context'; import { useAuth } from '@/lib/hooks/context';
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
import { useState } from 'react'; import { useState } from 'react';
import { useSupabaseClient } from '@/utils/supabase'; import { useSupabaseClient } from '@/utils/supabase';
import { FaMicrosoft } from 'react-icons/fa'; import { FaMicrosoft } from 'react-icons/fa';
import { type buttonVariants } from '@/components/ui';
import { type ComponentProps } from 'react'; import { type ComponentProps } from 'react';
import { type VariantProps } from 'class-variance-authority'; import { cn } from '@/lib/utils';
type SignInWithMicrosoftProps = { export type SignInWithMicrosoftProps = {
buttonProps?: ComponentProps<'button'> & VariantProps<typeof buttonVariants>; submitButtonProps?: SubmitButtonProps;
formProps?: ComponentProps<'form'>; formClassName?: ComponentProps<'form'>['className'];
textProps?: ComponentProps<'p'>; formProps?: Omit<ComponentProps<'form'>, 'className'>;
iconProps?: ComponentProps<'svg'>; textClassName?: ComponentProps<'p'>['className'];
textProps?: Omit<ComponentProps<'p'>, 'className'>;
iconClassName?: ComponentProps<'svg'>['className'];
iconProps?: Omit<ComponentProps<'svg'>, 'className'>;
}; };
export const SignInWithMicrosoft = ({ export const SignInWithMicrosoft = ({
buttonProps = { submitButtonProps,
className: 'w-full cursor-pointer', formClassName,
type: 'submit', formProps,
}, textClassName,
formProps = { textProps,
className: 'my-4', iconClassName,
}, iconProps,
textProps = {
className: 'text-[1.0rem]',
},
iconProps = {
className: 'size-5',
}
} : SignInWithMicrosoftProps) => { } : SignInWithMicrosoftProps) => {
const router = useRouter(); const router = useRouter();
const { loading, refreshUser } = useAuth(); const { loading, refreshUser } = useAuth();
@@ -57,16 +57,22 @@ export const SignInWithMicrosoft = ({
}; };
return ( return (
<form onSubmit={handleSignInWithMicrosoft} {...formProps}> <form
onSubmit={handleSignInWithMicrosoft}
className={cn('my-4', formClassName)}
{...formProps}
>
<SubmitButton <SubmitButton
disabled={isLoading || loading} disabled={isLoading || loading}
pendingText='Signing in...' pendingText='Signing in...'
pendingTextClassName={textProps.className} className={cn('w-full', submitButtonProps?.className)}
{...buttonProps} {...submitButtonProps}
> >
<div className='flex items-center gap-2'> <div className='flex items-center gap-2'>
<FaMicrosoft {...iconProps} /> <FaMicrosoft className={cn('size-5', iconClassName)} {...iconProps} />
<p className={textProps.className} {...textProps}>Sign In with Microsoft</p> <p className={cn('text-[1.0rem]', textClassName)} {...textProps}>
Sign In with Microsoft
</p>
</div> </div>
</SubmitButton> </SubmitButton>
{statusMessage && <StatusMessage message={{ error: statusMessage }} />} {statusMessage && <StatusMessage message={{ error: statusMessage }} />}

View File

@@ -1,12 +1,11 @@
'use server'; 'use server';
import Link from 'next/link'; import Link from 'next/link';
import { Button, type buttonVariants } from '@/components/ui'; import { Button } from '@/components/ui';
import { type ComponentProps } from 'react'; import { type ComponentProps } from 'react';
import { type VariantProps } from 'class-variance-authority';
type SignInProps = ComponentProps<'button'> & VariantProps<typeof buttonVariants>; type SignInLinkButtonProps = Omit<ComponentProps<typeof Button>, 'asChild'>;
export const SignInLinkButton = async (props: SignInProps) => { export const SignInLinkButton = async (props: SignInLinkButtonProps) => {
return ( return (
<Button asChild {...props}> <Button asChild {...props}>
<Link href='/sign-in'>Sign In</Link> <Link href='/sign-in'>Sign In</Link>

View File

@@ -1,47 +1,32 @@
'use server'; 'use server';
import { signInWithApple } from '@/lib/queries'; import { signInWithApple } from '@/lib/queries';
import { SubmitButton } from '@/components/default/forms'; import {
import Image from 'next/image'; SubmitButton,
import { type buttonVariants } from '@/components/ui'; type SubmitButtonProps,
import { type ComponentProps } from 'react'; } from '@/components/default/forms';
import { type VariantProps } from 'class-variance-authority';
import { SupabaseServer } from '@/utils/supabase'; import { SupabaseServer } from '@/utils/supabase';
import { FaApple } from 'react-icons/fa';
import { type ComponentProps } from 'react';
import { cn } from '@/lib/utils';
type ButtonProps = ComponentProps<'button'> & VariantProps<typeof buttonVariants>; export type SignInWithAppleProps = {
type ImageProps = { submitButtonProps?: SubmitButtonProps;
src: string; formClassName?: ComponentProps<'form'>['className'];
alt: string; formProps?: Omit<ComponentProps<'form'>, 'className'>;
className?: string; textClassName?: ComponentProps<'p'>['className'];
width?: number; textProps?: Omit<ComponentProps<'p'>, 'className'>;
height?: number; iconClassName?: ComponentProps<'svg'>['className'];
} iconProps?: Omit<ComponentProps<'svg'>, 'className'>;
type FormProps = ComponentProps<'form'>;
type TextProps = ComponentProps<'p'>;
type SignInWithAppleProps = {
buttonProps?: ButtonProps;
imageProps?: ImageProps;
formProps?: FormProps;
textProps?: TextProps;
}; };
export const SignInWithApple = async ({ export const SignInWithApple = async ({
buttonProps = { submitButtonProps,
className: 'w-full cursor-pointer', formClassName,
type: 'submit', formProps,
}, textClassName,
imageProps = { textProps,
src: '/icons/auth/apple.svg', iconClassName,
alt: 'Apple', iconProps,
className: 'invert-75 dark:invert-25',
width: 24,
height: 24,
},
formProps = {
className: 'my-4',
},
textProps = {
className: 'text-[1.0rem]',
},
} : SignInWithAppleProps) => { } : SignInWithAppleProps) => {
const supabase = await SupabaseServer(); const supabase = await SupabaseServer();
@@ -49,28 +34,30 @@ export const SignInWithApple = async ({
try { try {
if (!supabase) throw new Error('Supabase client not found'); if (!supabase) throw new Error('Supabase client not found');
const result = await signInWithApple(supabase); const result = await signInWithApple(supabase);
if (result.error) throw new Error(`Error Signing in with Apple: ${result.error.message}`); if (result.error)
if (result.data.url) window.location.href = result.data.url; throw new Error(`Error signing in with Microsoft: ${result.error.message}`);
else if (result.data.url) window.location.href = result.data.url;
} catch (error) { } catch (error) {
console.error(error); console.error(error);
} }
}; };
return ( return (
<form action={handleSignInWithApple} {...formProps}> <form
action={handleSignInWithApple}
className={cn('my-4', formClassName)}
{...formProps}
>
<SubmitButton <SubmitButton
pendingText='Signing in...' pendingText='Signing in...'
{...buttonProps} className={cn('w-full', submitButtonProps?.className)}
{...submitButtonProps}
> >
<div className='flex items-center gap-2'> <div className='flex items-center gap-2'>
<Image <FaApple className={cn('size-5', iconClassName)} {...iconProps} />
src={imageProps.src} <p className={cn('text-[1.0rem]', textClassName)} {...textProps}>
alt={imageProps.alt} Sign In with Apple
className={imageProps.className} </p>
width={imageProps.width}
height={imageProps.height}
/>
<p className={textProps.className} {...textProps}>Sign In with Apple</p>
</div> </div>
</SubmitButton> </SubmitButton>
</form> </form>

View File

@@ -1,47 +1,32 @@
'use server'; 'use server';
import { signInWithMicrosoft } from '@/lib/queries'; import { signInWithMicrosoft } from '@/lib/queries';
import { SubmitButton } from '@/components/default/forms'; import {
import Image from 'next/image'; SubmitButton,
import { type buttonVariants } from '@/components/ui'; type SubmitButtonProps,
import { type ComponentProps } from 'react'; } from '@/components/default/forms';
import { type VariantProps } from 'class-variance-authority';
import { SupabaseServer } from '@/utils/supabase'; import { SupabaseServer } from '@/utils/supabase';
import { FaMicrosoft } from 'react-icons/fa';
import { type ComponentProps } from 'react';
import { cn } from '@/lib/utils';
type ButtonProps = ComponentProps<'button'> & VariantProps<typeof buttonVariants>; export type SignInWithMicrosoftProps = {
type ImageProps = { submitButtonProps?: SubmitButtonProps;
src: string; formClassName?: ComponentProps<'form'>['className'];
alt: string; formProps?: Omit<ComponentProps<'form'>, 'className'>;
className?: string; textClassName?: ComponentProps<'p'>['className'];
width?: number; textProps?: Omit<ComponentProps<'p'>, 'className'>;
height?: number; iconClassName?: ComponentProps<'svg'>['className'];
} iconProps?: Omit<ComponentProps<'svg'>, 'className'>;
type FormProps = ComponentProps<'form'>;
type TextProps = ComponentProps<'p'>;
type SignInWithMicrosoftProps = {
buttonProps?: ButtonProps;
imageProps?: ImageProps;
formProps?: FormProps;
textProps?: TextProps;
}; };
export const SignInWithMicrosoft = async ({ export const SignInWithMicrosoft = async ({
buttonProps = { submitButtonProps,
className: 'w-full cursor-pointer', formClassName,
type: 'submit', formProps,
}, textClassName,
imageProps = { textProps,
src: '/icons/auth/microsoft.svg', iconClassName,
alt: 'Microsoft', iconProps,
className: 'invert-75 dark:invert-25',
width: 24,
height: 24,
},
formProps = {
className: 'my-4',
},
textProps = {
className: 'text-[1.0rem]',
},
} : SignInWithMicrosoftProps) => { } : SignInWithMicrosoftProps) => {
const supabase = await SupabaseServer(); const supabase = await SupabaseServer();
@@ -49,28 +34,30 @@ export const SignInWithMicrosoft = async ({
try { try {
if (!supabase) throw new Error('Supabase client not found'); if (!supabase) throw new Error('Supabase client not found');
const result = await signInWithMicrosoft(supabase); const result = await signInWithMicrosoft(supabase);
if (result.error) throw new Error(`Error Signing in with Microsoft: ${result.error.message}`); if (result.error)
if (result.data.url) window.location.href = result.data.url; throw new Error(`Error signing in with Microsoft: ${result.error.message}`);
else if (result.data.url) window.location.href = result.data.url;
} catch (error) { } catch (error) {
console.error(error); console.error(error);
} }
}; };
return ( return (
<form action={handleSignInWithMicrosoft} {...formProps}> <form
action={handleSignInWithMicrosoft}
className={cn('my-4', formClassName)}
{...formProps}
>
<SubmitButton <SubmitButton
pendingText='Signing in...' pendingText='Signing in...'
{...buttonProps} className={cn('w-full', submitButtonProps?.className)}
{...submitButtonProps}
> >
<div className='flex items-center gap-2'> <div className='flex items-center gap-2'>
<Image <FaMicrosoft className={cn('size-5', iconClassName)} {...iconProps} />
src={imageProps.src} <p className={cn('text-[1.0rem]', textClassName)} {...textProps}>
alt={imageProps.alt} Sign In with Microsoft
className={imageProps.className} </p>
width={imageProps.width}
height={imageProps.height}
/>
<p className={textProps.className} {...textProps}>Sign In with Microsoft</p>
</div> </div>
</SubmitButton> </SubmitButton>
</form> </form>

View File

@@ -13,9 +13,6 @@ import { StatusMessage, SubmitButton } from '@/components/default/forms';
import { import {
Card, Card,
CardContent, CardContent,
CardDescription,
CardHeader,
CardTitle,
Form, Form,
FormControl, FormControl,
FormField, FormField,
@@ -31,8 +28,12 @@ import {
} from '@/components/ui'; } from '@/components/ui';
import { import {
SignInWithApple, SignInWithApple,
SignInWithMicrosoft type SignInWithAppleProps,
SignInWithMicrosoft,
type SignInWithMicrosoftProps,
} from '@/components/default/auth/buttons/client'; } from '@/components/default/auth/buttons/client';
import { type ComponentProps } from 'react';
import { cn } from '@/lib/utils';
const signInFormSchema = z.object({ const signInFormSchema = z.object({
email: z.string().email({ email: z.string().email({
@@ -63,7 +64,47 @@ const signUpFormSchema = z
path: ['confirmPassword'], path: ['confirmPassword'],
}); });
export const SignInCard = () => { type SignInCardProps = {
containerClassName?: ComponentProps<typeof Card>['className'];
containerProps?: Omit<ComponentProps<typeof Card>, 'className'>;
tabsClassName?: ComponentProps<typeof Tabs>['className'];
tabsProps?: Omit<ComponentProps<typeof Tabs>, 'className'>;
tabsListClassName?: ComponentProps<typeof TabsList>['className'];
tabsListProps?: Omit<ComponentProps<typeof TabsList>, 'className'>;
tabsTriggerClassName?: ComponentProps<typeof TabsTrigger>['className'];
tabsTriggerProps?: Omit<ComponentProps<typeof TabsTrigger>, 'className' | 'value'>;
cardClassName?: ComponentProps<typeof Card>['className'];
cardProps?: Omit<ComponentProps<typeof Card>, 'className'>;
formClassName?: ComponentProps<'form'>['className'];
formProps?: Omit<ComponentProps<'form'>, 'className' | 'onSubmit'>;
formLabelClassName?: ComponentProps<typeof FormLabel>['className'];
formLabelProps?: Omit<ComponentProps<typeof FormLabel>, 'className'>;
submitButtonProps?: Omit<ComponentProps<typeof SubmitButton>,
'pendingText' | 'disabled'>;
signInWithAppleProps?: SignInWithAppleProps;
signInWithMicrosoftProps?: SignInWithMicrosoftProps;
};
export const SignInCard = ({
containerClassName,
containerProps,
tabsClassName,
tabsProps = { defaultValue: 'sign-in' },
tabsListClassName,
tabsListProps,
tabsTriggerClassName,
tabsTriggerProps,
cardClassName,
cardProps,
formClassName,
formProps,
formLabelClassName,
formLabelProps,
submitButtonProps,
signInWithAppleProps,
signInWithMicrosoftProps,
}: SignInCardProps) => {
const router = useRouter(); const router = useRouter();
const { isAuthenticated, loading, refreshUser } = useAuth(); const { isAuthenticated, loading, refreshUser } = useAuth();
const [statusMessage, setStatusMessage] = useState(''); const [statusMessage, setStatusMessage] = useState('');
@@ -71,10 +112,7 @@ export const SignInCard = () => {
const signInForm = useForm<z.infer<typeof signInFormSchema>>({ const signInForm = useForm<z.infer<typeof signInFormSchema>>({
resolver: zodResolver(signInFormSchema), resolver: zodResolver(signInFormSchema),
defaultValues: { defaultValues: { email: '', password: '' },
email: '',
password: '',
},
}); });
const signUpForm = useForm<z.infer<typeof signUpFormSchema>>({ const signUpForm = useForm<z.infer<typeof signUpFormSchema>>({
@@ -127,30 +165,64 @@ export const SignInCard = () => {
}; };
return ( return (
<Tabs defaultValue='sign-in'> <Card
<TabsList> className={cn('p-4 bg-card/25 min-h-[720px]', containerClassName)}
<TabsTrigger value='sign-in'>Sign In</TabsTrigger> {...containerProps}
<TabsTrigger value='sign-up'>Sign Up</TabsTrigger> >
<Tabs
className={cn('items-center', tabsClassName)}
{...tabsProps}
>
<TabsList
className={cn('py-6', tabsListClassName)}
{...tabsListProps}
>
<TabsTrigger
value='sign-in'
className={cn(
'p-6 text-2xl font-bold cursor-pointer',
tabsTriggerClassName
)}
{...tabsTriggerProps}
>
Sign In
</TabsTrigger>
<TabsTrigger
value='sign-up'
className={cn(
'p-6 text-2xl font-bold cursor-pointer',
tabsTriggerClassName,
)}
{...tabsTriggerProps}
>
Sign Up
</TabsTrigger>
</TabsList> </TabsList>
<TabsContent value='sign-in'> <TabsContent value='sign-in'>
<Card className='min-w-xs md:min-w-sm max-w-lg'> <Card
<CardHeader> className={cn(
<CardTitle className=''> 'min-w-xs sm:min-w-sm bg-card/50',
Sign In cardClassName,
</CardTitle> )}
</CardHeader> {...cardProps}
>
<CardContent> <CardContent>
<Form {...signInForm}> <Form {...signInForm}>
<form <form
onSubmit={signInForm.handleSubmit(handleSignIn)} onSubmit={signInForm.handleSubmit(handleSignIn)}
className='' className={cn('flex flex-col space-y-6', formClassName)}
{...formProps}
> >
<FormField <FormField
control={signInForm.control} control={signInForm.control}
name='email' name='email'
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel className=''>Email</FormLabel> <FormLabel
className={cn('text-xl', formLabelClassName)}
{...formLabelProps}>
Email
</FormLabel>
<FormControl> <FormControl>
<Input <Input
type='email' type='email'
@@ -168,7 +240,12 @@ export const SignInCard = () => {
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<div className='flex justify-between'> <div className='flex justify-between'>
<FormLabel className=''>Password</FormLabel> <FormLabel
className={cn('text-xl', formLabelClassName)}
{...formLabelProps}
>
Password
</FormLabel>
<Link <Link
href='/forgot-password' href='/forgot-password'
> >
@@ -198,40 +275,82 @@ export const SignInCard = () => {
<SubmitButton <SubmitButton
disabled={loading} disabled={loading}
pendingText='Signing In...' pendingText='Signing In...'
className={cn(
'text-lg font-semibold w-2/3 mx-auto',
submitButtonProps?.className
)}
{...submitButtonProps}
> >
Sign In Sign In
</SubmitButton> </SubmitButton>
</form> </form>
</Form> </Form>
<div className='flex items-center w-full gap-4'> <div className='flex items-center w-full gap-4 mt-4'>
<Separator className='flex-1 bg-accent py-0.5' /> <Separator className='flex-1 bg-muted-foreground/50 py-0.5' />
<span className='text-sm text-muted-foreground'>or</span> <span className='text-muted-foreground'>or</span>
<Separator className='flex-1 bg-accent py-0.5' /> <Separator className='flex-1 bg-muted-foreground/50 py-0.5' />
</div> </div>
<SignInWithMicrosoft /> <SignInWithMicrosoft
<SignInWithApple /> submitButtonProps = {{
className: cn(
'flex w-5/6 m-auto',
signInWithMicrosoftProps?.submitButtonProps?.className),
}}
textClassName={cn(
'text-lg',
signInWithMicrosoftProps?.textClassName,
)}
iconClassName={cn(
'size-6',
signInWithMicrosoftProps?.iconClassName,
)}
{...signInWithMicrosoftProps}
/>
<SignInWithApple
submitButtonProps = {{
className: cn(
'flex w-5/6 m-auto',
signInWithAppleProps?.submitButtonProps?.className),
}}
textClassName={cn(
'text-lg',
signInWithAppleProps?.textClassName,
)}
iconClassName={cn(
'size-6',
signInWithAppleProps?.iconClassName,
)}
{...signInWithAppleProps}
/>
</CardContent> </CardContent>
</Card> </Card>
</TabsContent> </TabsContent>
<TabsContent value='sign-up'> <TabsContent value='sign-up'>
<Card className='min-w-xs md:min-w-sm max-w-lg'> <Card
<CardHeader> className={cn(
<CardTitle className=''> 'min-w-xs sm:min-w-sm bg-card/50',
Sign Up cardClassName,
</CardTitle> )}
</CardHeader> {...cardProps}
>
<CardContent> <CardContent>
<Form {...signUpForm}> <Form {...signUpForm}>
<form <form
className={cn('flex flex-col space-y-6', formClassName)}
onSubmit={signUpForm.handleSubmit(handleSignUp)} onSubmit={signUpForm.handleSubmit(handleSignUp)}
className='' {...formProps}
> >
<FormField <FormField
control={signUpForm.control} control={signUpForm.control}
name='name' name='name'
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel className=''>Name</FormLabel> <FormLabel
className={cn('text-xl', formLabelClassName)}
{...formLabelProps}
>
Name
</FormLabel>
<FormControl> <FormControl>
<Input <Input
type='text' type='text'
@@ -247,7 +366,12 @@ export const SignInCard = () => {
name='email' name='email'
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel className=''>Email</FormLabel> <FormLabel
className={cn('text-xl', formLabelClassName)}
{...formLabelProps}
>
Email
</FormLabel>
<FormControl> <FormControl>
<Input <Input
type='email' type='email'
@@ -263,7 +387,12 @@ export const SignInCard = () => {
name='password' name='password'
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel className=''>Password</FormLabel> <FormLabel
className={cn('text-xl', formLabelClassName)}
{...formLabelProps}
>
Password
</FormLabel>
<FormControl> <FormControl>
<Input <Input
type='password' type='password'
@@ -279,7 +408,12 @@ export const SignInCard = () => {
name='confirmPassword' name='confirmPassword'
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel className=''>Confirm Passsword</FormLabel> <FormLabel
className={cn('text-xl', formLabelClassName)}
{...formLabelProps}
>
Confirm Passsword
</FormLabel>
<FormControl> <FormControl>
<Input <Input
type='password' type='password'
@@ -302,23 +436,58 @@ export const SignInCard = () => {
<SubmitButton <SubmitButton
disabled={loading} disabled={loading}
pendingText='Signing Up...' pendingText='Signing Up...'
className={cn(
'text-lg font-semibold w-2/3 mx-auto',
submitButtonProps?.className
)}
{...submitButtonProps}
> >
Sign Up Sign Up
</SubmitButton> </SubmitButton>
</form> </form>
</Form> </Form>
<div className='flex items-center w-full gap-4'> <div className='flex items-center w-full gap-4 mt-4'>
<Separator className='flex-1 bg-accent py-0.5' /> <Separator className='flex-1 bg-accent py-0.5' />
<span className='text-sm text-muted-foreground'>or</span> <span className='text-sm text-muted-foreground'>or</span>
<Separator className='flex-1 bg-accent py-0.5' /> <Separator className='flex-1 bg-accent py-0.5' />
</div> </div>
<SignInWithMicrosoft /> <SignInWithMicrosoft
<SignInWithApple /> submitButtonProps = {{
className: cn(
'flex w-5/6 m-auto',
signInWithMicrosoftProps?.submitButtonProps?.className),
}}
textClassName={cn(
'text-lg',
signInWithMicrosoftProps?.textClassName,
)}
iconClassName={cn(
'size-6',
signInWithMicrosoftProps?.iconClassName,
)}
{...signInWithMicrosoftProps}
/>
<SignInWithApple
submitButtonProps = {{
className: cn(
'flex w-5/6 m-auto',
signInWithAppleProps?.submitButtonProps?.className),
}}
textClassName={cn(
'text-lg',
signInWithAppleProps?.textClassName,
)}
iconClassName={cn(
'size-6',
signInWithAppleProps?.iconClassName,
)}
{...signInWithAppleProps}
/>
</CardContent> </CardContent>
</Card> </Card>
</TabsContent> </TabsContent>
</Tabs> </Tabs>
</Card>
); );
}; };

View File

@@ -1,2 +1,2 @@
export { StatusMessage } from './status-message'; export { StatusMessage } from './status-message';
export { SubmitButton } from './submit-button'; export { SubmitButton, type SubmitButtonProps } from './submit-button';

View File

@@ -1,25 +1,52 @@
import { type ComponentProps } from 'react';
import { cn } from '@/lib/utils';
type Message = type Message =
| { success: string} | { success: string}
| { error: string} | { error: string}
| { message: string} | { message: string}
export const StatusMessage = ({message}: {message: Message}) => { type StatusMessageProps = {
message: Message;
containerClassName?: ComponentProps<'div'>['className'];
containerProps?: Omit<ComponentProps<'div'>, 'className'>;
textClassName?: ComponentProps<'div'>['className'];
textProps?: Omit<ComponentProps<'div'>, 'className'>;
};
export const StatusMessage = ({
message,
containerClassName,
containerProps,
textClassName,
textProps,
}: StatusMessageProps) => {
return ( return (
<div className='flex flex-col gap-2 w-full <div
text-sm bg-accent rounded-md p-2 px-4' className={cn(
'flex flex-col gap-2 w-full\
text-sm bg-accent rounded-md p-2 px-4',
containerClassName,
)}
{...containerProps}
> >
{'success' in message && ( {'success' in message && (
<div className='dark:text-green-500 text-green-700'> <div className={cn(
'dark:text-green-500 text-green-700',
textClassName
)}
{...textProps}
>
{message.success} {message.success}
</div> </div>
)} )}
{'error' in message && ( {'error' in message && (
<div className='text-destructive'> <div className={cn('text-destructive', textClassName)}>
{message.error} {message.error}
</div> </div>
)} )}
{'message' in message && ( {'message' in message && (
<div> <div className={textClassName}>
{message.message} {message.message}
</div> </div>
)} )}

View File

@@ -1,36 +1,52 @@
'use client'; 'use client';
import { Button, type buttonVariants } from '@/components/ui'; import { Button } from '@/components/ui';
import { type ComponentProps } from 'react'; import { type ComponentProps } from 'react';
import { type VariantProps } from 'class-variance-authority';
import { useFormStatus } from 'react-dom'; import { useFormStatus } from 'react-dom';
import { Loader2 } from 'lucide-react'; import { Loader2 } from 'lucide-react';
import { cn } from '@/lib/utils';
type SubmitButtonProps = ComponentProps<'button'> & export type SubmitButtonProps = Omit<
VariantProps<typeof buttonVariants> & { ComponentProps<typeof Button>,
'type' | 'aria-disabled' | 'className',
> & {
className?: ComponentProps<typeof Button>['className'];
pendingText?: string; pendingText?: string;
loaderClassName?: ComponentProps<'div'>['className'];
pendingTextClassName?: ComponentProps<'p'>['className']; pendingTextClassName?: ComponentProps<'p'>['className'];
pendingTextProps?: Omit<ComponentProps<'p'>, 'className'>;
loaderClassName?: ComponentProps<typeof Loader2>['className'];
loaderProps?: Omit<ComponentProps<typeof Loader2>, 'className'>;
}; };
export const SubmitButton = ({ export const SubmitButton = ({
children, children,
className,
pendingText = 'Submitting...', pendingText = 'Submitting...',
loaderClassName = 'mr-2 h-4 w-4 animate-spin', pendingTextClassName,
pendingTextClassName = 'text-sm font-medium', pendingTextProps,
loaderClassName,
loaderProps,
...props ...props
}: SubmitButtonProps) => { }: SubmitButtonProps) => {
const { pending } = useFormStatus(); const { pending } = useFormStatus();
return ( return (
<Button <Button
className='cursor-pointer' className={cn('cursor-pointer', className)}
type='submit' type='submit'
aria-disabled={pending} aria-disabled={pending}
{...props} {...props}
> >
{pending || props.disabled ? ( {pending || props.disabled ? (
<> <>
<Loader2 className={loaderClassName} /> <Loader2
<p className={pendingTextClassName}>{pendingText}</p> className={cn('mr-2 h-4 w-4 animate-spin', loaderClassName)}
{...loaderProps}
/>
<p
className={cn('text-sm font-medium', pendingTextClassName)}
{...pendingTextProps}
>
{pendingText}
</p>
</> </>
) : ( ) : (
children children

View File

@@ -1,40 +1,51 @@
'use client'; 'use client';
import * as React from 'react'; import {
useEffect,
useState,
type ComponentProps,
} from 'react';
import { ThemeProvider as NextThemesProvider } from 'next-themes'; import { ThemeProvider as NextThemesProvider } from 'next-themes';
import { Moon, Sun } from 'lucide-react'; import { Moon, Sun } from 'lucide-react';
import { useTheme } from 'next-themes'; import { useTheme } from 'next-themes';
import { Button } from '@/components/ui'; import { Button } from '@/components/ui';
import { cn } from '@/lib/utils';
const ThemeProvider = ({ const ThemeProvider = ({
children, children,
...props ...props
}: React.ComponentProps<typeof NextThemesProvider>) => { }: ComponentProps<typeof NextThemesProvider>) => {
const [mounted, setMounted] = React.useState(false);
React.useEffect(() => { const [mounted, setMounted] = useState(false);
setMounted(true);
}, []); useEffect(() => { setMounted(true) }, []);
if (!mounted) return null; if (!mounted) return null;
return <NextThemesProvider {...props}>{children}</NextThemesProvider>; return <NextThemesProvider {...props}>{children}</NextThemesProvider>;
}; };
type ThemeToggleProps = React.ButtonHTMLAttributes<HTMLButtonElement> & { type ThemeToggleProps = {
size?: number; size?: number;
buttonClassName?: ComponentProps<typeof Button>['className'];
buttonProps?: Omit<ComponentProps<typeof Button>, 'className' | 'onClick'>;
}; };
const ThemeToggle = ({ size = 1, ...props }: ThemeToggleProps) => { const ThemeToggle = ({
const { setTheme, resolvedTheme } = useTheme(); size = 1,
const [mounted, setMounted] = React.useState(false); buttonClassName,
buttonProps = {
variant: 'outline',
size: 'icon',
},
}: ThemeToggleProps) => {
React.useEffect(() => { const { setTheme, resolvedTheme } = useTheme();
setMounted(true); const [mounted, setMounted] = useState(false);
}, []);
useEffect(() => { setMounted(true) }, []);
if (!mounted) { if (!mounted) {
return ( return (
<Button variant='outline' size='icon' {...props}> <Button className={buttonClassName} {...buttonProps}>
<span style={{ height: `${size}rem`, width: `${size}rem` }} /> <span style={{ height: `${size}rem`, width: `${size}rem` }} />
</Button> </Button>
); );
@@ -47,11 +58,9 @@ const ThemeToggle = ({ size = 1, ...props }: ThemeToggleProps) => {
return ( return (
<Button <Button
variant='outline' className={cn('cursor-pointer', buttonClassName)}
size='icon'
className='cursor-pointer'
onClick={toggleTheme} onClick={toggleTheme}
{...props} {...buttonProps}
> >
<Sun <Sun
style={{ height: `${size}rem`, width: `${size}rem` }} style={{ height: `${size}rem`, width: `${size}rem` }}
@@ -61,7 +70,6 @@ const ThemeToggle = ({ size = 1, ...props }: ThemeToggleProps) => {
style={{ height: `${size}rem`, width: `${size}rem` }} style={{ height: `${size}rem`, width: `${size}rem` }}
className='absolute rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100' className='absolute rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100'
/> />
<span className='sr-only'>Toggle theme</span>
</Button> </Button>
); );
}; };

View File

@@ -2,9 +2,9 @@
import Image from 'next/image'; import Image from 'next/image';
import React, { createContext, useContext, useState } from 'react'; import React, { createContext, useContext, useState } from 'react';
import type { ReactNode } from 'react'; import type { ReactNode } from 'react';
import { Button, type buttonVariants } from '@/components/ui'; import { Button } from '@/components/ui';
import { type ComponentProps } from 'react'; import { type ComponentProps } from 'react';
import { type VariantProps } from 'class-variance-authority'; import { cn } from '@/lib/utils';
type TVModeContextProps = { type TVModeContextProps = {
tvMode: boolean; tvMode: boolean;
@@ -12,11 +12,9 @@ type TVModeContextProps = {
}; };
type TVToggleProps = { type TVToggleProps = {
className?: ComponentProps<'button'>['className']; buttonClassName?: ComponentProps<typeof Button>['className'];
buttonSize?: VariantProps<typeof buttonVariants>['size']; buttonProps?: Omit<ComponentProps<typeof Button>, 'className'>;
buttonVariant?: VariantProps<typeof buttonVariants>['variant']; size: number;
imageWidth?: number;
imageHeight?: number;
}; };
const TVModeContext = createContext<TVModeContextProps | undefined>(undefined); const TVModeContext = createContext<TVModeContextProps | undefined>(undefined);
@@ -42,33 +40,33 @@ const useTVMode = () => {
}; };
const TVToggle = ({ const TVToggle = ({
className = 'my-auto cursor-pointer', buttonClassName,
buttonSize = 'default', buttonProps = {
buttonVariant = 'link', variant: 'outline',
imageWidth = 25, size: 'default',
imageHeight = 25, },
size = 25,
}: TVToggleProps) => { }: TVToggleProps) => {
const { tvMode, toggleTVMode } = useTVMode(); const { tvMode, toggleTVMode } = useTVMode();
return ( return (
<Button <Button
onClick={toggleTVMode} onClick={toggleTVMode}
className={className} className={cn('my-auto cursor-pointer', buttonClassName)}
size={buttonSize} {...buttonProps}
variant={buttonVariant}
> >
{tvMode ? ( {tvMode ? (
<Image <Image
src='/icons/tv/exit.svg' src='/icons/tv/exit.svg'
alt='Exit TV Mode' alt='Exit TV Mode'
width={imageWidth} width={size}
height={imageHeight} height={size}
/> />
) : ( ) : (
<Image <Image
src='/icons/tv/enter.svg' src='/icons/tv/enter.svg'
alt='Exit TV Mode' alt='Enter TV Mode'
width={imageWidth} width={size}
height={imageHeight} height={size}
/> />
)} )}
</Button> </Button>

View File

@@ -16,5 +16,5 @@ export const ccn = ({
on: string; on: string;
off: string; off: string;
}) => { }) => {
return className + ' ' + (context ? on : off); return twMerge(className, context ? on : off);
}; };

View File

@@ -9,10 +9,6 @@
} }
@theme inline { @theme inline {
--radius-sm: calc(var(--radius) - 4px);
--radius-md: calc(var(--radius) - 2px);
--radius-lg: var(--radius);
--radius-xl: calc(var(--radius) + 4px);
--color-background: var(--background); --color-background: var(--background);
--color-foreground: var(--foreground); --color-foreground: var(--foreground);
--color-card: var(--card); --color-card: var(--card);
@@ -28,6 +24,7 @@
--color-accent: var(--accent); --color-accent: var(--accent);
--color-accent-foreground: var(--accent-foreground); --color-accent-foreground: var(--accent-foreground);
--color-destructive: var(--destructive); --color-destructive: var(--destructive);
--color-destructive-foreground: var(--destructive-foreground);
--color-border: var(--border); --color-border: var(--border);
--color-input: var(--input); --color-input: var(--input);
--color-ring: var(--ring); --color-ring: var(--ring);
@@ -44,102 +41,120 @@
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground); --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
--color-sidebar-border: var(--sidebar-border); --color-sidebar-border: var(--sidebar-border);
--color-sidebar-ring: var(--sidebar-ring); --color-sidebar-ring: var(--sidebar-ring);
--font-sans: var(--font-sans);
--font-mono: var(--font-mono);
--font-serif: var(--font-serif);
--radius-sm: calc(var(--radius) - 4px);
--radius-md: calc(var(--radius) - 2px);
--radius-lg: var(--radius);
--radius-xl: calc(var(--radius) + 4px);
--shadow-2xs: var(--shadow-2xs);
--shadow-xs: var(--shadow-xs);
--shadow-sm: var(--shadow-sm);
--shadow: var(--shadow);
--shadow-md: var(--shadow-md);
--shadow-lg: var(--shadow-lg);
--shadow-xl: var(--shadow-xl);
--shadow-2xl: var(--shadow-2xl);
} }
:root { :root {
--background: oklch(0.9785 0.0045 314.8050); --background: oklch(0.9227 0.0011 17.1793);
--foreground: oklch(0.3710 0.0333 301.6287); --foreground: oklch(0.2840 0.0220 262.4967);
--card: oklch(0.9940 0 0); --card: oklch(0.9699 0.0013 106.4238);
--card-foreground: oklch(0.3710 0.0333 301.6287); --card-foreground: oklch(0.2840 0.0220 262.4967);
--popover: oklch(0.9940 0 0); --popover: oklch(0.9699 0.0013 106.4238);
--popover-foreground: oklch(0.3710 0.0333 301.6287); --popover-foreground: oklch(0.2840 0.0220 262.4967);
--primary: oklch(0.4868 0.1488 286.5771); --primary: oklch(0.6378 0.1247 281.2150);
--primary-foreground: oklch(0.9785 0.0045 314.8050); --primary-foreground: oklch(1.0000 0 0);
--secondary: oklch(0.9139 0.0448 291.0467); --secondary: oklch(0.8682 0.0026 48.7143);
--secondary-foreground: oklch(0.3008 0.0773 288.1551); --secondary-foreground: oklch(0.4507 0.0152 255.5845);
--muted: oklch(0.8930 0.0149 312.2335); --muted: oklch(0.9227 0.0011 17.1793);
--muted-foreground: oklch(0.5361 0.0391 305.8579); --muted-foreground: oklch(0.5551 0.0147 266.6154);
--accent: oklch(0.8514 0.0535 342.1042); --accent: oklch(0.9409 0.0164 322.6966);
--accent-foreground: oklch(0.3328 0.0528 311.4628); --accent-foreground: oklch(0.3774 0.0189 260.6754);
--destructive: oklch(0.6984 0.1170 47.0382); --destructive: oklch(0.6322 0.1310 21.4751);
--destructive-foreground: oklch(0.9785 0.0045 314.8050); --destructive-foreground: oklch(1.0000 0 0);
--border: oklch(0.8488 0.0244 313.1102); --border: oklch(0.8682 0.0026 48.7143);
--input: oklch(0.9352 0.0136 314.7562); --input: oklch(0.8682 0.0026 48.7143);
--ring: oklch(0.4868 0.1488 286.5771); --ring: oklch(0.6378 0.1247 281.2150);
--chart-1: oklch(0.4868 0.1488 286.5771); --chart-1: oklch(0.6378 0.1247 281.2150);
--chart-2: oklch(0.8514 0.0535 342.1042); --chart-2: oklch(0.5608 0.1433 283.1275);
--chart-3: oklch(0.7388 0.0664 194.5709); --chart-3: oklch(0.5008 0.1358 283.9499);
--chart-4: oklch(0.9197 0.1140 104.6226); --chart-4: oklch(0.4372 0.1108 283.4322);
--chart-5: oklch(0.7409 0.0895 280.3986); --chart-5: oklch(0.3928 0.0817 282.8932);
--sidebar: oklch(0.9569 0.0090 314.7812); --sidebar: oklch(0.8682 0.0026 48.7143);
--sidebar-foreground: oklch(0.3710 0.0333 301.6287); --sidebar-foreground: oklch(0.2840 0.0220 262.4967);
--sidebar-primary: oklch(0.4868 0.1488 286.5771); --sidebar-primary: oklch(0.6378 0.1247 281.2150);
--sidebar-primary-foreground: oklch(0.9785 0.0045 314.8050); --sidebar-primary-foreground: oklch(1.0000 0 0);
--sidebar-accent: oklch(0.8514 0.0535 342.1042); --sidebar-accent: oklch(0.9409 0.0164 322.6966);
--sidebar-accent-foreground: oklch(0.3328 0.0528 311.4628); --sidebar-accent-foreground: oklch(0.3774 0.0189 260.6754);
--sidebar-border: oklch(0.8759 0.0218 316.4501); --sidebar-border: oklch(0.8682 0.0026 48.7143);
--sidebar-ring: oklch(0.4868 0.1488 286.5771); --sidebar-ring: oklch(0.6378 0.1247 281.2150);
--font-sans: Inter, sans-serif; --font-sans: Inter, sans-serif;
--font-serif: "Lora", Georgia, serif; --font-serif: ui-serif, Georgia, Cambria, "Times New Roman", Times, serif;
--font-mono: "Fira Code", "Courier New", monospace; --font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
--radius: 0.5rem; --radius: 1.0rem;
--shadow-2xs: 1px 2px 5px 1px hsl(0 0% 10.1961% / 0.03); --shadow-2xs: 2px 2px 10px 4px hsl(240 1.9608% 60% / 0.09);
--shadow-xs: 1px 2px 5px 1px hsl(0 0% 10.1961% / 0.03); --shadow-xs: 2px 2px 10px 4px hsl(240 1.9608% 60% / 0.09);
--shadow-sm: 1px 2px 5px 1px hsl(0 0% 10.1961% / 0.06), 1px 1px 2px 0px hsl(0 0% 10.1961% / 0.06); --shadow-sm: 2px 2px 10px 4px hsl(240 1.9608% 60% / 0.18), 2px 1px 2px 3px hsl(240 1.9608% 60% / 0.18);
--shadow: 1px 2px 5px 1px hsl(0 0% 10.1961% / 0.06), 1px 1px 2px 0px hsl(0 0% 10.1961% / 0.06); --shadow: 2px 2px 10px 4px hsl(240 1.9608% 60% / 0.18), 2px 1px 2px 3px hsl(240 1.9608% 60% / 0.18);
--shadow-md: 1px 2px 5px 1px hsl(0 0% 10.1961% / 0.06), 1px 2px 4px 0px hsl(0 0% 10.1961% / 0.06); --shadow-md: 2px 2px 10px 4px hsl(240 1.9608% 60% / 0.18), 2px 2px 4px 3px hsl(240 1.9608% 60% / 0.18);
--shadow-lg: 1px 2px 5px 1px hsl(0 0% 10.1961% / 0.06), 1px 4px 6px 0px hsl(0 0% 10.1961% / 0.06); --shadow-lg: 2px 2px 10px 4px hsl(240 1.9608% 60% / 0.18), 2px 4px 6px 3px hsl(240 1.9608% 60% / 0.18);
--shadow-xl: 1px 2px 5px 1px hsl(0 0% 10.1961% / 0.06), 1px 8px 10px 0px hsl(0 0% 10.1961% / 0.06); --shadow-xl: 2px 2px 10px 4px hsl(240 1.9608% 60% / 0.18), 2px 8px 10px 3px hsl(240 1.9608% 60% / 0.18);
--shadow-2xl: 1px 2px 5px 1px hsl(0 0% 10.1961% / 0.15); --shadow-2xl: 2px 2px 10px 4px hsl(240 1.9608% 60% / 0.45);
--tracking-normal: 0em; --tracking-normal: 0em;
--spacing: 0.25rem; --spacing: 0.25rem;
} }
.dark { .dark {
--background: oklch(0.2213 0.0228 309.2819); --background: oklch(0.2236 0.0049 67.5717);
--foreground: oklch(0.9100 0.0260 308.1435); --foreground: oklch(0.9301 0.0075 260.7315);
--card: oklch(0.2671 0.0372 295.2445); --card: oklch(0.2793 0.0057 56.1503);
--card-foreground: oklch(0.9100 0.0260 308.1435); --card-foreground: oklch(0.9301 0.0075 260.7315);
--popover: oklch(0.2671 0.0372 295.2445); --popover: oklch(0.2793 0.0057 56.1503);
--popover-foreground: oklch(0.9100 0.0260 308.1435); --popover-foreground: oklch(0.9301 0.0075 260.7315);
--primary: oklch(0.6405 0.1338 286.4998); --primary: oklch(0.7223 0.0946 279.6746);
--primary-foreground: oklch(0.2213 0.0228 309.2819); --primary-foreground: oklch(0.2236 0.0049 67.5717);
--secondary: oklch(0.4071 0.0776 288.3025); --secondary: oklch(0.3352 0.0055 56.2080);
--secondary-foreground: oklch(0.9100 0.0260 308.1435); --secondary-foreground: oklch(0.8726 0.0059 264.5296);
--muted: oklch(0.2630 0.0340 310.8818); --muted: oklch(0.2793 0.0057 56.1503);
--muted-foreground: oklch(0.7026 0.0304 313.2720); --muted-foreground: oklch(0.7176 0.0111 261.7826);
--accent: oklch(0.3328 0.0528 311.4628); --accent: oklch(0.3889 0.0053 56.2463);
--accent-foreground: oklch(0.8076 0.0881 341.1289); --accent-foreground: oklch(0.8726 0.0059 264.5296);
--destructive: oklch(0.7501 0.1053 47.2117); --destructive: oklch(0.6322 0.1310 21.4751);
--destructive-foreground: oklch(0.2213 0.0228 309.2819); --destructive-foreground: oklch(0.2236 0.0049 67.5717);
--border: oklch(0.3139 0.0379 309.3053); --border: oklch(0.3352 0.0055 56.2080);
--input: oklch(0.2913 0.0360 305.8978); --input: oklch(0.3352 0.0055 56.2080);
--ring: oklch(0.6405 0.1338 286.4998); --ring: oklch(0.7223 0.0946 279.6746);
--chart-1: oklch(0.6405 0.1338 286.4998); --chart-1: oklch(0.7223 0.0946 279.6746);
--chart-2: oklch(0.8076 0.0881 341.1289); --chart-2: oklch(0.6378 0.1247 281.2150);
--chart-3: oklch(0.7388 0.0664 194.5709); --chart-3: oklch(0.5608 0.1433 283.1275);
--chart-4: oklch(0.9197 0.1140 104.6226); --chart-4: oklch(0.5008 0.1358 283.9499);
--chart-5: oklch(0.4868 0.1488 286.5771); --chart-5: oklch(0.4372 0.1108 283.4322);
--sidebar: oklch(0.2039 0.0232 309.1750); --sidebar: oklch(0.3352 0.0055 56.2080);
--sidebar-foreground: oklch(0.9100 0.0260 308.1435); --sidebar-foreground: oklch(0.9301 0.0075 260.7315);
--sidebar-primary: oklch(0.6405 0.1338 286.4998); --sidebar-primary: oklch(0.7223 0.0946 279.6746);
--sidebar-primary-foreground: oklch(0.2213 0.0228 309.2819); --sidebar-primary-foreground: oklch(0.2236 0.0049 67.5717);
--sidebar-accent: oklch(0.3328 0.0528 311.4628); --sidebar-accent: oklch(0.3889 0.0053 56.2463);
--sidebar-accent-foreground: oklch(0.8076 0.0881 341.1289); --sidebar-accent-foreground: oklch(0.8726 0.0059 264.5296);
--sidebar-border: oklch(0.2913 0.0360 305.8978); --sidebar-border: oklch(0.3352 0.0055 56.2080);
--sidebar-ring: oklch(0.6405 0.1338 286.4998); --sidebar-ring: oklch(0.7223 0.0946 279.6746);
--font-sans: Inter, sans-serif; --font-sans: Inter, sans-serif;
--font-serif: "Lora", Georgia, serif; --font-serif: ui-serif, Georgia, Cambria, "Times New Roman", Times, serif;
--font-mono: "Fira Code", "Courier New", monospace; --font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
--radius: 0.5rem; --radius: 1.0rem;
--shadow-2xs: 1px 2px 5px 1px hsl(0 0% 10.1961% / 0.03); --shadow-2xs: 2px 2px 10px 4px hsl(0 0% 10.1961% / 0.09);
--shadow-xs: 1px 2px 5px 1px hsl(0 0% 10.1961% / 0.03); --shadow-xs: 2px 2px 10px 4px hsl(0 0% 10.1961% / 0.09);
--shadow-sm: 1px 2px 5px 1px hsl(0 0% 10.1961% / 0.06), 1px 1px 2px 0px hsl(0 0% 10.1961% / 0.06); --shadow-sm: 2px 2px 10px 4px hsl(0 0% 10.1961% / 0.18), 2px 1px 2px 3px hsl(0 0% 10.1961% / 0.18);
--shadow: 1px 2px 5px 1px hsl(0 0% 10.1961% / 0.06), 1px 1px 2px 0px hsl(0 0% 10.1961% / 0.06); --shadow: 2px 2px 10px 4px hsl(0 0% 10.1961% / 0.18), 2px 1px 2px 3px hsl(0 0% 10.1961% / 0.18);
--shadow-md: 1px 2px 5px 1px hsl(0 0% 10.1961% / 0.06), 1px 2px 4px 0px hsl(0 0% 10.1961% / 0.06); --shadow-md: 2px 2px 10px 4px hsl(0 0% 10.1961% / 0.18), 2px 2px 4px 3px hsl(0 0% 10.1961% / 0.18);
--shadow-lg: 1px 2px 5px 1px hsl(0 0% 10.1961% / 0.06), 1px 4px 6px 0px hsl(0 0% 10.1961% / 0.06); --shadow-lg: 2px 2px 10px 4px hsl(0 0% 10.1961% / 0.18), 2px 4px 6px 3px hsl(0 0% 10.1961% / 0.18);
--shadow-xl: 1px 2px 5px 1px hsl(0 0% 10.1961% / 0.06), 1px 8px 10px 0px hsl(0 0% 10.1961% / 0.06); --shadow-xl: 2px 2px 10px 4px hsl(0 0% 10.1961% / 0.18), 2px 8px 10px 3px hsl(0 0% 10.1961% / 0.18);
--shadow-2xl: 1px 2px 5px 1px hsl(0 0% 10.1961% / 0.15); --shadow-2xl: 2px 2px 10px 4px hsl(0 0% 10.1961% / 0.45);
} }
@layer base { @layer base {