Update theme & stuff

This commit is contained in:
2025-07-02 15:03:16 -05:00
parent d9dd83b8c1
commit 489009d35d
17 changed files with 534 additions and 189 deletions

BIN
bun.lockb

Binary file not shown.

View File

@@ -48,12 +48,12 @@
"@radix-ui/react-toggle": "^1.1.9", "@radix-ui/react-toggle": "^1.1.9",
"@radix-ui/react-toggle-group": "^1.1.10", "@radix-ui/react-toggle-group": "^1.1.10",
"@radix-ui/react-tooltip": "^1.2.7", "@radix-ui/react-tooltip": "^1.2.7",
"@sentry/nextjs": "^9.32.0", "@sentry/nextjs": "^9.34.0",
"@supabase-cache-helpers/postgrest-react-query": "^1.13.4", "@supabase-cache-helpers/postgrest-react-query": "^1.13.4",
"@supabase/ssr": "^0.6.1", "@supabase/ssr": "^0.6.1",
"@supabase/supabase-js": "^2.50.2", "@supabase/supabase-js": "^2.50.2",
"@t3-oss/env-nextjs": "^0.12.0", "@t3-oss/env-nextjs": "^0.12.0",
"@tanstack/react-query": "^5.81.2", "@tanstack/react-query": "^5.81.5",
"@tanstack/react-table": "^8.21.3", "@tanstack/react-table": "^8.21.3",
"class-variance-authority": "^0.7.1", "class-variance-authority": "^0.7.1",
"clsx": "^2.1.1", "clsx": "^2.1.1",
@@ -71,9 +71,10 @@
"react": "^19.1.0", "react": "^19.1.0",
"react-day-picker": "^9.7.0", "react-day-picker": "^9.7.0",
"react-dom": "^19.1.0", "react-dom": "^19.1.0",
"react-hook-form": "^7.58.1", "react-hook-form": "^7.59.0",
"react-icons": "^5.5.0",
"react-resizable-panels": "^3.0.3", "react-resizable-panels": "^3.0.3",
"recharts": "^3.0.1", "recharts": "^3.0.2",
"sonner": "^2.0.5", "sonner": "^2.0.5",
"tailwind-merge": "^3.3.1", "tailwind-merge": "^3.3.1",
"vaul": "^1.1.2", "vaul": "^1.1.2",
@@ -84,22 +85,22 @@
"@tailwindcss/postcss": "^4.1.11", "@tailwindcss/postcss": "^4.1.11",
"@types/cors": "^2.8.19", "@types/cors": "^2.8.19",
"@types/express": "^5.0.3", "@types/express": "^5.0.3",
"@types/node": "^20.19.1", "@types/node": "^20.19.4",
"@types/react": "^19.1.8", "@types/react": "^19.1.8",
"@types/react-dom": "^19.1.6", "@types/react-dom": "^19.1.6",
"drizzle-kit": "^0.30.6", "drizzle-kit": "^0.30.6",
"eslint": "^9.29.0", "eslint": "^9.30.1",
"eslint-config-next": "^15.3.4", "eslint-config-next": "^15.3.4",
"eslint-config-prettier": "^10.1.5", "eslint-config-prettier": "^10.1.5",
"eslint-plugin-drizzle": "^0.2.3", "eslint-plugin-drizzle": "^0.2.3",
"eslint-plugin-prettier": "^5.5.1", "eslint-plugin-prettier": "^5.5.1",
"postcss": "^8.5.6", "postcss": "^8.5.6",
"prettier": "^3.6.1", "prettier": "^3.6.2",
"prettier-plugin-tailwindcss": "^0.6.13", "prettier-plugin-tailwindcss": "^0.6.13",
"tailwindcss": "^4.1.11", "tailwindcss": "^4.1.11",
"tw-animate-css": "^1.3.4", "tw-animate-css": "^1.3.4",
"typescript": "^5.8.3", "typescript": "^5.8.3",
"typescript-eslint": "^8.35.0" "typescript-eslint": "^8.35.1"
}, },
"ct3aMetadata": { "ct3aMetadata": {
"initVersion": "7.39.3" "initVersion": "7.39.3"

View File

@@ -1,6 +1,7 @@
import '@/styles/globals.css'; import '@/styles/globals.css';
import { type Metadata } from 'next'; import { type Metadata } from 'next';
import { Geist } from 'next/font/google'; import { Inter } from 'next/font/google';
import { cn } from '@/lib/utils';
import { import {
AuthContextProvider, AuthContextProvider,
ThemeProvider, ThemeProvider,
@@ -204,16 +205,20 @@ export const generateMetadata = (): Metadata => {
}; };
}; };
const geist = Geist({ const fontSans = Inter({
subsets: ['latin'], subsets: ['latin'],
variable: '--font-geist-sans', variable: '--font-sans',
}); })
export default function RootLayout({ export default function RootLayout({
children, children,
}: Readonly<{ children: React.ReactNode }>) { }: Readonly<{ children: React.ReactNode }>) {
return ( return (
<html lang='en' className={`${geist.variable}`} suppressHydrationWarning> <html
lang='en'
className={cn('font-sans antialiased', fontSans.variable)}
suppressHydrationWarning
>
<body> <body>
<ThemeProvider <ThemeProvider
attribute='class' attribute='class'
@@ -229,10 +234,10 @@ export default function RootLayout({
trackOutboundLinks trackOutboundLinks
selfHosted selfHosted
> >
<TVModeProvider> <TVModeProvider>
{children} {children}
<Toaster /> <Toaster />
</TVModeProvider> </TVModeProvider>
</PlausibleProvider> </PlausibleProvider>
</AuthContextProvider> </AuthContextProvider>
</QueryClientProvider> </QueryClientProvider>

View File

@@ -1,36 +1,15 @@
import Link from 'next/link'; import { SignInCard } from '@/components/default/auth/cards/client/sign-in';
import { ThemeToggle } from '@/lib/hooks/context';
export default function HomePage() { export default function HomePage() {
return ( return (
<main className='flex min-h-screen flex-col items-center justify-center bg-gradient-to-b from-[#2e026d] to-[#15162c] text-white'> <main className='flex min-h-screen flex-col items-center justify-center'>
<div className='container flex flex-col items-center justify-center gap-12 px-4 py-16'> <div className='container flex flex-col items-center justify-center gap-12 px-4 py-16'>
<h1 className='text-5xl font-extrabold tracking-tight text-white sm:text-[5rem]'> <h1 className='text-5xl font-extrabold tracking-tight text-white sm:text-[5rem]'>
Create <span className='text-[hsl(280,100%,70%)]'>T3</span> App Create <span className='text-[hsl(280,100%,70%)]'>T3</span> App
</h1> </h1>
<div className='grid grid-cols-1 gap-4 sm:grid-cols-2 md:gap-8'> <ThemeToggle />
<Link <SignInCard/>
className='flex max-w-xs flex-col gap-4 rounded-xl bg-white/10 p-4 text-white hover:bg-white/20'
href='https://create.t3.gg/en/usage/first-steps'
target='_blank'
>
<h3 className='text-2xl font-bold'>First Steps </h3>
<div className='text-lg'>
Just the basics - Everything you need to know to set up your
database and authentication.
</div>
</Link>
<Link
className='flex max-w-xs flex-col gap-4 rounded-xl bg-white/10 p-4 text-white hover:bg-white/20'
href='https://create.t3.gg/en/introduction'
target='_blank'
>
<h3 className='text-2xl font-bold'>Documentation </h3>
<div className='text-lg'>
Learn more about Create T3 App, the libraries it uses, and how to
deploy it.
</div>
</Link>
</div>
</div> </div>
</main> </main>
); );

View File

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

View File

@@ -6,7 +6,7 @@ import { type VariantProps } from 'class-variance-authority';
type SignInProps = ComponentProps<'button'> & VariantProps<typeof buttonVariants>; type SignInProps = ComponentProps<'button'> & VariantProps<typeof buttonVariants>;
export const SignInButton = (props: SignInProps) => { export const SignInLinkButton = (props: SignInProps) => {
return ( return (
<Button asChild {...props}> <Button asChild {...props}>
<Link href='/sign-in'>Sign In</Link> <Link href='/sign-in'>Sign In</Link>

View File

@@ -4,27 +4,17 @@ import { StatusMessage, SubmitButton } 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 Image from 'next/image'; import { useSupabaseClient } from '@/utils/supabase';
import { FaApple } from 'react-icons/fa';
import { type buttonVariants } from '@/components/ui'; import { type buttonVariants } from '@/components/ui';
import { type ComponentProps } from 'react'; import { type ComponentProps } from 'react';
import { type VariantProps } from 'class-variance-authority'; import { type VariantProps } from 'class-variance-authority';
import { useSupabaseClient } from '@/utils/supabase';
type ButtonProps = ComponentProps<'button'> & VariantProps<typeof buttonVariants>;
type ImageProps = {
src: string;
alt: string;
className?: string;
width?: number;
height?: number;
}
type FormProps = ComponentProps<'form'>;
type TextProps = ComponentProps<'p'>;
type SignInWithAppleProps = { type SignInWithAppleProps = {
buttonProps?: ButtonProps; buttonProps?: ComponentProps<'button'> & VariantProps<typeof buttonVariants>;
imageProps?: ImageProps; formProps?: ComponentProps<'form'>;
formProps?: FormProps; textProps?: ComponentProps<'p'>;
textProps?: TextProps; iconProps?: ComponentProps<'svg'>;
}; };
export const SignInWithApple = ({ export const SignInWithApple = ({
@@ -32,19 +22,15 @@ export const SignInWithApple = ({
className: 'w-full cursor-pointer', className: 'w-full cursor-pointer',
type: 'submit', type: 'submit',
}, },
imageProps = {
src: '/icons/auth/apple.svg',
alt: 'Apple',
className: 'invert-75 dark:invert-25',
width: 24,
height: 24,
},
formProps = { formProps = {
className: 'my-4', className: 'my-4',
}, },
textProps = { textProps = {
className: 'text-[1.0rem]', className: 'text-[1.0rem]',
}, },
iconProps = {
className: 'size-5',
}
} : SignInWithAppleProps) => { } : SignInWithAppleProps) => {
const router = useRouter(); const router = useRouter();
const { loading, refreshUser } = useAuth(); const { loading, refreshUser } = useAuth();
@@ -78,13 +64,7 @@ export const SignInWithApple = ({
{...buttonProps} {...buttonProps}
> >
<div className='flex items-center gap-2'> <div className='flex items-center gap-2'>
<Image <FaApple {...iconProps} />
src={imageProps.src}
alt={imageProps.alt}
className={imageProps.className}
width={imageProps.width}
height={imageProps.height}
/>
<p className={textProps.className} {...textProps}>Sign In with Apple</p> <p className={textProps.className} {...textProps}>Sign In with Apple</p>
</div> </div>
</SubmitButton> </SubmitButton>

View File

@@ -4,27 +4,17 @@ import { StatusMessage, SubmitButton } 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 Image from 'next/image'; import { useSupabaseClient } from '@/utils/supabase';
import { FaMicrosoft } from 'react-icons/fa';
import { type buttonVariants } from '@/components/ui'; import { type buttonVariants } from '@/components/ui';
import { type ComponentProps } from 'react'; import { type ComponentProps } from 'react';
import { type VariantProps } from 'class-variance-authority'; import { type VariantProps } from 'class-variance-authority';
import { useSupabaseClient } from '@/utils/supabase';
type ButtonProps = ComponentProps<'button'> & VariantProps<typeof buttonVariants>;
type ImageProps = {
src: string;
alt: string;
className?: string;
width?: number;
height?: number;
}
type FormProps = ComponentProps<'form'>;
type TextProps = ComponentProps<'p'>;
type SignInWithMicrosoftProps = { type SignInWithMicrosoftProps = {
buttonProps?: ButtonProps; buttonProps?: ComponentProps<'button'> & VariantProps<typeof buttonVariants>;
imageProps?: ImageProps; formProps?: ComponentProps<'form'>;
formProps?: FormProps; textProps?: ComponentProps<'p'>;
textProps?: TextProps; iconProps?: ComponentProps<'svg'>;
}; };
export const SignInWithMicrosoft = ({ export const SignInWithMicrosoft = ({
@@ -32,19 +22,15 @@ export const SignInWithMicrosoft = ({
className: 'w-full cursor-pointer', className: 'w-full cursor-pointer',
type: 'submit', type: 'submit',
}, },
imageProps = {
src: '/icons/auth/microsoft.svg',
alt: 'Apple',
className: '',
width: 24,
height: 24,
},
formProps = { formProps = {
className: 'my-4', className: 'my-4',
}, },
textProps = { textProps = {
className: 'text-[1.0rem]', className: 'text-[1.0rem]',
}, },
iconProps = {
className: 'size-5',
}
} : SignInWithMicrosoftProps) => { } : SignInWithMicrosoftProps) => {
const router = useRouter(); const router = useRouter();
const { loading, refreshUser } = useAuth(); const { loading, refreshUser } = useAuth();
@@ -60,7 +46,7 @@ export const SignInWithMicrosoft = ({
setIsLoading(true); setIsLoading(true);
const result = await signInWithMicrosoft(supabase); const result = await signInWithMicrosoft(supabase);
if (result.data.url) window.location.href = result.data.url; if (result.data.url) window.location.href = result.data.url;
else setStatusMessage(`There was a problem signing in with Apple!`); else setStatusMessage(`There was a problem signing in with Microsoft!`);
} catch (error) { } catch (error) {
setStatusMessage(`Error signing in: ${error as string}`); setStatusMessage(`Error signing in: ${error as string}`);
} finally { } finally {
@@ -75,17 +61,12 @@ export const SignInWithMicrosoft = ({
<SubmitButton <SubmitButton
disabled={isLoading || loading} disabled={isLoading || loading}
pendingText='Signing in...' pendingText='Signing in...'
pendingTextClassName={textProps.className}
{...buttonProps} {...buttonProps}
> >
<div className='flex items-center gap-2'> <div className='flex items-center gap-2'>
<Image <FaMicrosoft {...iconProps} />
src={imageProps.src} <p className={textProps.className} {...textProps}>Sign In with Microsoft</p>
alt={imageProps.alt}
className={imageProps.className}
width={imageProps.width}
height={imageProps.height}
/>
<p className={textProps.className} {...textProps}>Sign In with Apple</p>
</div> </div>
</SubmitButton> </SubmitButton>
{statusMessage && <StatusMessage message={{ error: statusMessage }} />} {statusMessage && <StatusMessage message={{ error: statusMessage }} />}

View File

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

View File

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

View File

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

View File

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

View File

@@ -43,7 +43,7 @@ const signInFormSchema = z.object({
}), }),
}); });
const signUpformSchema = z const signUpFormSchema = z
.object({ .object({
name: z.string().min(2, { name: z.string().min(2, {
message: 'Name must be at least 2 characters.', message: 'Name must be at least 2 characters.',
@@ -77,8 +77,8 @@ export const SignInCard = () => {
}, },
}); });
const signUpForm = useForm<z.infer<typeof signUpformSchema>>({ const signUpForm = useForm<z.infer<typeof signUpFormSchema>>({
resolver: zodResolver(signUpformSchema), resolver: zodResolver(signUpFormSchema),
defaultValues: { defaultValues: {
name: '', name: '',
email: '', email: '',
@@ -100,11 +100,9 @@ export const SignInCard = () => {
if (!supabase) throw new Error('Supabase client not found'); if (!supabase) throw new Error('Supabase client not found');
const result = await signIn(supabase, formData); const result = await signIn(supabase, formData);
if (result.error) throw new Error(result.error.message); if (result.error) throw new Error(result.error.message);
else if (result.data) { await refreshUser();
await refreshUser(); signInForm.reset();
signInForm.reset(); router.push('');
router.push('');
}
} catch (error) { } catch (error) {
setStatusMessage(`Error signing in: ${error as string}`); setStatusMessage(`Error signing in: ${error as string}`);
} }
@@ -112,14 +110,214 @@ export const SignInCard = () => {
const handleSignUp = async (values: z.infer<typeof signUpFormSchema>) => { const handleSignUp = async (values: z.infer<typeof signUpFormSchema>) => {
try { try {
setStatusMessage('');
} catch { const formData = new FormData();
formData.append('name', values.name);
formData.append('email', values.email);
formData.append('password', values.password);
if (!supabase) throw new Error('Supabase client not found');
const result = await signUp(supabase, formData);
if (result.error) throw new Error(result.error.message);
await refreshUser();
signUpForm.reset();
router.push('');
} catch (error) {
setStatusMessage(`Error signing up: ${error as string}`);
} }
}; };
return ( return (
<Tabs defaultValue='sign-in'>
<TabsList>
<TabsTrigger value='sign-in'>Sign In</TabsTrigger>
<TabsTrigger value='sign-up'>Sign Up</TabsTrigger>
</TabsList>
<TabsContent value='sign-in'>
<Card className='min-w-xs md:min-w-sm max-w-lg'>
<CardHeader>
<CardTitle className=''>
Sign In
</CardTitle>
</CardHeader>
<CardContent>
<Form {...signInForm}>
<form
onSubmit={signInForm.handleSubmit(handleSignIn)}
className=''
>
<FormField
control={signInForm.control}
name='email'
render={({ field }) => (
<FormItem>
<FormLabel className=''>Email</FormLabel>
<FormControl>
<Input
type='email'
placeholder='you@example.com'
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={signInForm.control}
name='password'
render={({ field }) => (
<FormItem>
<div className='flex justify-between'>
<FormLabel className=''>Password</FormLabel>
<Link
href='/forgot-password'
>
Forgot Password?
</Link>
</div>
<FormControl>
<Input
type='password'
placeholder='Your password'
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
{statusMessage &&
(statusMessage.includes('Error') ||
statusMessage.includes('error') ||
statusMessage.includes('failed') ||
statusMessage.includes('invalid') ? (
<StatusMessage message={{ error: statusMessage }} />
) : (
<StatusMessage message={{ message: statusMessage }} />
))}
<SubmitButton
disabled={loading}
pendingText='Signing In...'
>
Sign In
</SubmitButton>
</form>
</Form>
<div className='flex items-center w-full gap-4'>
<Separator className='flex-1 bg-accent py-0.5' />
<span className='text-sm text-muted-foreground'>or</span>
<Separator className='flex-1 bg-accent py-0.5' />
</div>
<SignInWithMicrosoft />
<SignInWithApple />
</CardContent>
</Card>
</TabsContent>
<TabsContent value='sign-up'>
<Card className='min-w-xs md:min-w-sm max-w-lg'>
<CardHeader>
<CardTitle className=''>
Sign Up
</CardTitle>
</CardHeader>
<CardContent>
<Form {...signUpForm}>
<form
onSubmit={signUpForm.handleSubmit(handleSignUp)}
className=''
>
<FormField
control={signUpForm.control}
name='name'
render={({ field }) => (
<FormItem>
<FormLabel className=''>Name</FormLabel>
<FormControl>
<Input
type='text'
placeholder='Full Name'
{...field}
/>
</FormControl>
</FormItem>
)}
/>
<FormField
control={signUpForm.control}
name='email'
render={({ field }) => (
<FormItem>
<FormLabel className=''>Email</FormLabel>
<FormControl>
<Input
type='email'
placeholder='you@example.com'
{...field}
/>
</FormControl>
</FormItem>
)}
/>
<FormField
control={signUpForm.control}
name='password'
render={({ field }) => (
<FormItem>
<FormLabel className=''>Password</FormLabel>
<FormControl>
<Input
type='password'
placeholder='Your password'
{...field}
/>
</FormControl>
</FormItem>
)}
/>
<FormField
control={signUpForm.control}
name='confirmPassword'
render={({ field }) => (
<FormItem>
<FormLabel className=''>Confirm Passsword</FormLabel>
<FormControl>
<Input
type='password'
placeholder='Confirm your password'
{...field}
/>
</FormControl>
</FormItem>
)}
/>
{statusMessage &&
(statusMessage.includes('Error') ||
statusMessage.includes('error') ||
statusMessage.includes('failed') ||
statusMessage.includes('invalid') ? (
<StatusMessage message={{ error: statusMessage }} />
) : (
<StatusMessage message={{ success: statusMessage }} />
))}
<SubmitButton
disabled={loading}
pendingText='Signing Up...'
>
Sign Up
</SubmitButton>
</form>
</Form>
<div className='flex items-center w-full gap-4'>
<Separator className='flex-1 bg-accent py-0.5' />
<span className='text-sm text-muted-foreground'>or</span>
<Separator className='flex-1 bg-accent py-0.5' />
</div>
<SignInWithMicrosoft />
<SignInWithApple />
</CardContent>
</Card>
</TabsContent>
</Tabs>
); );
}; };

View File

@@ -9,14 +9,14 @@ type SubmitButtonProps = ComponentProps<'button'> &
VariantProps<typeof buttonVariants> & { VariantProps<typeof buttonVariants> & {
pendingText?: string; pendingText?: string;
loaderClassName?: ComponentProps<'div'>['className']; loaderClassName?: ComponentProps<'div'>['className'];
textClassName?: ComponentProps<'p'>['className']; pendingTextClassName?: ComponentProps<'p'>['className'];
}; };
export const SubmitButton = ({ export const SubmitButton = ({
children, children,
pendingText = 'Submitting...', pendingText = 'Submitting...',
loaderClassName = 'mr-2 h-4 w-4 animate-spin', loaderClassName = 'mr-2 h-4 w-4 animate-spin',
textClassName = 'text-sm font-medium', pendingTextClassName = 'text-sm font-medium',
...props ...props
}: SubmitButtonProps) => { }: SubmitButtonProps) => {
const { pending } = useFormStatus(); const { pending } = useFormStatus();
@@ -30,7 +30,7 @@ export const SubmitButton = ({
{pending || props.disabled ? ( {pending || props.disabled ? (
<> <>
<Loader2 className={loaderClassName} /> <Loader2 className={loaderClassName} />
<p className={textClassName}>{pendingText}</p> <p className={pendingTextClassName}>{pendingText}</p>
</> </>
) : ( ) : (
children children

View File

@@ -4,7 +4,7 @@
@custom-variant dark (&:is(.dark *)); @custom-variant dark (&:is(.dark *));
@theme { @theme {
--font-sans: var(--font-geist-sans), ui-sans-serif, system-ui, sans-serif, --font-sans: var(--font-sans), ui-sans-serif, system-ui, sans-serif,
"Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
} }
@@ -47,72 +47,99 @@
} }
:root { :root {
--radius: 0.65rem; --background: oklch(0.9785 0.0045 314.8050);
--background: oklch(1 0 0); --foreground: oklch(0.3710 0.0333 301.6287);
--foreground: oklch(0.141 0.005 285.823); --card: oklch(0.9940 0 0);
--card: oklch(1 0 0); --card-foreground: oklch(0.3710 0.0333 301.6287);
--card-foreground: oklch(0.141 0.005 285.823); --popover: oklch(0.9940 0 0);
--popover: oklch(1 0 0); --popover-foreground: oklch(0.3710 0.0333 301.6287);
--popover-foreground: oklch(0.141 0.005 285.823); --primary: oklch(0.4868 0.1488 286.5771);
--primary: oklch(0.645 0.246 16.439); --primary-foreground: oklch(0.9785 0.0045 314.8050);
--primary-foreground: oklch(0.969 0.015 12.422); --secondary: oklch(0.9139 0.0448 291.0467);
--secondary: oklch(0.967 0.001 286.375); --secondary-foreground: oklch(0.3008 0.0773 288.1551);
--secondary-foreground: oklch(0.21 0.006 285.885); --muted: oklch(0.8930 0.0149 312.2335);
--muted: oklch(0.967 0.001 286.375); --muted-foreground: oklch(0.5361 0.0391 305.8579);
--muted-foreground: oklch(0.552 0.016 285.938); --accent: oklch(0.8514 0.0535 342.1042);
--accent: oklch(0.967 0.001 286.375); --accent-foreground: oklch(0.3328 0.0528 311.4628);
--accent-foreground: oklch(0.21 0.006 285.885); --destructive: oklch(0.6984 0.1170 47.0382);
--destructive: oklch(0.577 0.245 27.325); --destructive-foreground: oklch(0.9785 0.0045 314.8050);
--border: oklch(0.92 0.004 286.32); --border: oklch(0.8488 0.0244 313.1102);
--input: oklch(0.92 0.004 286.32); --input: oklch(0.9352 0.0136 314.7562);
--ring: oklch(0.645 0.246 16.439); --ring: oklch(0.4868 0.1488 286.5771);
--chart-1: oklch(0.646 0.222 41.116); --chart-1: oklch(0.4868 0.1488 286.5771);
--chart-2: oklch(0.6 0.118 184.704); --chart-2: oklch(0.8514 0.0535 342.1042);
--chart-3: oklch(0.398 0.07 227.392); --chart-3: oklch(0.7388 0.0664 194.5709);
--chart-4: oklch(0.828 0.189 84.429); --chart-4: oklch(0.9197 0.1140 104.6226);
--chart-5: oklch(0.769 0.188 70.08); --chart-5: oklch(0.7409 0.0895 280.3986);
--sidebar: oklch(0.985 0 0); --sidebar: oklch(0.9569 0.0090 314.7812);
--sidebar-foreground: oklch(0.141 0.005 285.823); --sidebar-foreground: oklch(0.3710 0.0333 301.6287);
--sidebar-primary: oklch(0.645 0.246 16.439); --sidebar-primary: oklch(0.4868 0.1488 286.5771);
--sidebar-primary-foreground: oklch(0.969 0.015 12.422); --sidebar-primary-foreground: oklch(0.9785 0.0045 314.8050);
--sidebar-accent: oklch(0.967 0.001 286.375); --sidebar-accent: oklch(0.8514 0.0535 342.1042);
--sidebar-accent-foreground: oklch(0.21 0.006 285.885); --sidebar-accent-foreground: oklch(0.3328 0.0528 311.4628);
--sidebar-border: oklch(0.92 0.004 286.32); --sidebar-border: oklch(0.8759 0.0218 316.4501);
--sidebar-ring: oklch(0.645 0.246 16.439); --sidebar-ring: oklch(0.4868 0.1488 286.5771);
--font-sans: Inter, sans-serif;
--font-serif: "Lora", Georgia, serif;
--font-mono: "Fira Code", "Courier New", monospace;
--radius: 0.5rem;
--shadow-2xs: 1px 2px 5px 1px hsl(0 0% 10.1961% / 0.03);
--shadow-xs: 1px 2px 5px 1px hsl(0 0% 10.1961% / 0.03);
--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: 1px 2px 5px 1px hsl(0 0% 10.1961% / 0.06), 1px 1px 2px 0px hsl(0 0% 10.1961% / 0.06);
--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-lg: 1px 2px 5px 1px hsl(0 0% 10.1961% / 0.06), 1px 4px 6px 0px hsl(0 0% 10.1961% / 0.06);
--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-2xl: 1px 2px 5px 1px hsl(0 0% 10.1961% / 0.15);
--tracking-normal: 0em;
--spacing: 0.25rem;
} }
.dark { .dark {
--background: oklch(0.141 0.005 285.823); --background: oklch(0.2213 0.0228 309.2819);
--foreground: oklch(0.985 0 0); --foreground: oklch(0.9100 0.0260 308.1435);
--card: oklch(0.21 0.006 285.885); --card: oklch(0.2671 0.0372 295.2445);
--card-foreground: oklch(0.985 0 0); --card-foreground: oklch(0.9100 0.0260 308.1435);
--popover: oklch(0.21 0.006 285.885); --popover: oklch(0.2671 0.0372 295.2445);
--popover-foreground: oklch(0.985 0 0); --popover-foreground: oklch(0.9100 0.0260 308.1435);
--primary: oklch(0.645 0.246 16.439); --primary: oklch(0.6405 0.1338 286.4998);
--primary-foreground: oklch(0.969 0.015 12.422); --primary-foreground: oklch(0.2213 0.0228 309.2819);
--secondary: oklch(0.274 0.006 286.033); --secondary: oklch(0.4071 0.0776 288.3025);
--secondary-foreground: oklch(0.985 0 0); --secondary-foreground: oklch(0.9100 0.0260 308.1435);
--muted: oklch(0.274 0.006 286.033); --muted: oklch(0.2630 0.0340 310.8818);
--muted-foreground: oklch(0.705 0.015 286.067); --muted-foreground: oklch(0.7026 0.0304 313.2720);
--accent: oklch(0.274 0.006 286.033); --accent: oklch(0.3328 0.0528 311.4628);
--accent-foreground: oklch(0.985 0 0); --accent-foreground: oklch(0.8076 0.0881 341.1289);
--destructive: oklch(0.704 0.191 22.216); --destructive: oklch(0.7501 0.1053 47.2117);
--border: oklch(1 0 0 / 10%); --destructive-foreground: oklch(0.2213 0.0228 309.2819);
--input: oklch(1 0 0 / 15%); --border: oklch(0.3139 0.0379 309.3053);
--ring: oklch(0.645 0.246 16.439); --input: oklch(0.2913 0.0360 305.8978);
--chart-1: oklch(0.488 0.243 264.376); --ring: oklch(0.6405 0.1338 286.4998);
--chart-2: oklch(0.696 0.17 162.48); --chart-1: oklch(0.6405 0.1338 286.4998);
--chart-3: oklch(0.769 0.188 70.08); --chart-2: oklch(0.8076 0.0881 341.1289);
--chart-4: oklch(0.627 0.265 303.9); --chart-3: oklch(0.7388 0.0664 194.5709);
--chart-5: oklch(0.645 0.246 16.439); --chart-4: oklch(0.9197 0.1140 104.6226);
--sidebar: oklch(0.21 0.006 285.885); --chart-5: oklch(0.4868 0.1488 286.5771);
--sidebar-foreground: oklch(0.985 0 0); --sidebar: oklch(0.2039 0.0232 309.1750);
--sidebar-primary: oklch(0.645 0.246 16.439); --sidebar-foreground: oklch(0.9100 0.0260 308.1435);
--sidebar-primary-foreground: oklch(0.969 0.015 12.422); --sidebar-primary: oklch(0.6405 0.1338 286.4998);
--sidebar-accent: oklch(0.274 0.006 286.033); --sidebar-primary-foreground: oklch(0.2213 0.0228 309.2819);
--sidebar-accent-foreground: oklch(0.985 0 0); --sidebar-accent: oklch(0.3328 0.0528 311.4628);
--sidebar-border: oklch(1 0 0 / 10%); --sidebar-accent-foreground: oklch(0.8076 0.0881 341.1289);
--sidebar-ring: oklch(0.645 0.246 16.439); --sidebar-border: oklch(0.2913 0.0360 305.8978);
--sidebar-ring: oklch(0.6405 0.1338 286.4998);
--font-sans: Inter, sans-serif;
--font-serif: "Lora", Georgia, serif;
--font-mono: "Fira Code", "Courier New", monospace;
--radius: 0.5rem;
--shadow-2xs: 1px 2px 5px 1px hsl(0 0% 10.1961% / 0.03);
--shadow-xs: 1px 2px 5px 1px hsl(0 0% 10.1961% / 0.03);
--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: 1px 2px 5px 1px hsl(0 0% 10.1961% / 0.06), 1px 1px 2px 0px hsl(0 0% 10.1961% / 0.06);
--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-lg: 1px 2px 5px 1px hsl(0 0% 10.1961% / 0.06), 1px 4px 6px 0px hsl(0 0% 10.1961% / 0.06);
--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-2xl: 1px 2px 5px 1px hsl(0 0% 10.1961% / 0.15);
} }
@layer base { @layer base {

View File

@@ -1,5 +1,5 @@
export { useSupabaseClient } from './client'; export { useSupabaseClient } from './client';
export { updateSession } from './middleware'; export { updateSession } from './middleware';
export { useSupabaseServer } from './server'; export { SupabaseServer } from './server';
export type { Database } from './database.types'; export type { Database } from './database.types';
export type * from './types'; export type * from './types';

View File

@@ -2,10 +2,10 @@
import 'server-only'; import 'server-only';
import { createServerClient } from '@supabase/ssr'; import { createServerClient } from '@supabase/ssr';
import type { Database, SupabaseClient } from '@/utils/supabase'; import type { Database } from '@/utils/supabase';
import { cookies } from 'next/headers'; import { cookies } from 'next/headers';
export const useSupabaseServer = async (): Promise<SupabaseClient | undefined> => { export const SupabaseServer = async () => {
const cookieStore = await cookies(); const cookieStore = await cookies();
return createServerClient<Database>( return createServerClient<Database>(
process.env.NEXT_PUBLIC_SUPABASE_URL!, process.env.NEXT_PUBLIC_SUPABASE_URL!,