Update theme & stuff
This commit is contained in:
17
package.json
17
package.json
@@ -48,12 +48,12 @@
|
||||
"@radix-ui/react-toggle": "^1.1.9",
|
||||
"@radix-ui/react-toggle-group": "^1.1.10",
|
||||
"@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/ssr": "^0.6.1",
|
||||
"@supabase/supabase-js": "^2.50.2",
|
||||
"@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",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
@@ -71,9 +71,10 @@
|
||||
"react": "^19.1.0",
|
||||
"react-day-picker": "^9.7.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",
|
||||
"recharts": "^3.0.1",
|
||||
"recharts": "^3.0.2",
|
||||
"sonner": "^2.0.5",
|
||||
"tailwind-merge": "^3.3.1",
|
||||
"vaul": "^1.1.2",
|
||||
@@ -84,22 +85,22 @@
|
||||
"@tailwindcss/postcss": "^4.1.11",
|
||||
"@types/cors": "^2.8.19",
|
||||
"@types/express": "^5.0.3",
|
||||
"@types/node": "^20.19.1",
|
||||
"@types/node": "^20.19.4",
|
||||
"@types/react": "^19.1.8",
|
||||
"@types/react-dom": "^19.1.6",
|
||||
"drizzle-kit": "^0.30.6",
|
||||
"eslint": "^9.29.0",
|
||||
"eslint": "^9.30.1",
|
||||
"eslint-config-next": "^15.3.4",
|
||||
"eslint-config-prettier": "^10.1.5",
|
||||
"eslint-plugin-drizzle": "^0.2.3",
|
||||
"eslint-plugin-prettier": "^5.5.1",
|
||||
"postcss": "^8.5.6",
|
||||
"prettier": "^3.6.1",
|
||||
"prettier": "^3.6.2",
|
||||
"prettier-plugin-tailwindcss": "^0.6.13",
|
||||
"tailwindcss": "^4.1.11",
|
||||
"tw-animate-css": "^1.3.4",
|
||||
"typescript": "^5.8.3",
|
||||
"typescript-eslint": "^8.35.0"
|
||||
"typescript-eslint": "^8.35.1"
|
||||
},
|
||||
"ct3aMetadata": {
|
||||
"initVersion": "7.39.3"
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import '@/styles/globals.css';
|
||||
import { type Metadata } from 'next';
|
||||
import { Geist } from 'next/font/google';
|
||||
import { Inter } from 'next/font/google';
|
||||
import { cn } from '@/lib/utils';
|
||||
import {
|
||||
AuthContextProvider,
|
||||
ThemeProvider,
|
||||
@@ -204,16 +205,20 @@ export const generateMetadata = (): Metadata => {
|
||||
};
|
||||
};
|
||||
|
||||
const geist = Geist({
|
||||
const fontSans = Inter({
|
||||
subsets: ['latin'],
|
||||
variable: '--font-geist-sans',
|
||||
});
|
||||
variable: '--font-sans',
|
||||
})
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: Readonly<{ children: React.ReactNode }>) {
|
||||
return (
|
||||
<html lang='en' className={`${geist.variable}`} suppressHydrationWarning>
|
||||
<html
|
||||
lang='en'
|
||||
className={cn('font-sans antialiased', fontSans.variable)}
|
||||
suppressHydrationWarning
|
||||
>
|
||||
<body>
|
||||
<ThemeProvider
|
||||
attribute='class'
|
||||
@@ -229,10 +234,10 @@ export default function RootLayout({
|
||||
trackOutboundLinks
|
||||
selfHosted
|
||||
>
|
||||
<TVModeProvider>
|
||||
{children}
|
||||
<Toaster />
|
||||
</TVModeProvider>
|
||||
<TVModeProvider>
|
||||
{children}
|
||||
<Toaster />
|
||||
</TVModeProvider>
|
||||
</PlausibleProvider>
|
||||
</AuthContextProvider>
|
||||
</QueryClientProvider>
|
||||
|
@@ -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() {
|
||||
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'>
|
||||
<h1 className='text-5xl font-extrabold tracking-tight text-white sm:text-[5rem]'>
|
||||
Create <span className='text-[hsl(280,100%,70%)]'>T3</span> App
|
||||
</h1>
|
||||
<div className='grid grid-cols-1 gap-4 sm:grid-cols-2 md:gap-8'>
|
||||
<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/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>
|
||||
<ThemeToggle />
|
||||
<SignInCard/>
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
|
@@ -1,3 +1,3 @@
|
||||
export { SignInWithApple } from './sign-in-with-apple';
|
||||
export { SignInWithMicrosoft } from './sign-in-with-microsoft';
|
||||
export { SignInButton } from './sign-in';
|
||||
export { SignInLinkButton } from './sign-in-link';
|
||||
|
@@ -6,7 +6,7 @@ import { type VariantProps } from 'class-variance-authority';
|
||||
|
||||
type SignInProps = ComponentProps<'button'> & VariantProps<typeof buttonVariants>;
|
||||
|
||||
export const SignInButton = (props: SignInProps) => {
|
||||
export const SignInLinkButton = (props: SignInProps) => {
|
||||
return (
|
||||
<Button asChild {...props}>
|
||||
<Link href='/sign-in'>Sign In</Link>
|
@@ -4,27 +4,17 @@ import { StatusMessage, SubmitButton } from '@/components/default/forms';
|
||||
import { useAuth } from '@/lib/hooks/context';
|
||||
import { useRouter } from 'next/navigation';
|
||||
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 ComponentProps } from 'react';
|
||||
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 = {
|
||||
buttonProps?: ButtonProps;
|
||||
imageProps?: ImageProps;
|
||||
formProps?: FormProps;
|
||||
textProps?: TextProps;
|
||||
buttonProps?: ComponentProps<'button'> & VariantProps<typeof buttonVariants>;
|
||||
formProps?: ComponentProps<'form'>;
|
||||
textProps?: ComponentProps<'p'>;
|
||||
iconProps?: ComponentProps<'svg'>;
|
||||
};
|
||||
|
||||
export const SignInWithApple = ({
|
||||
@@ -32,19 +22,15 @@ export const SignInWithApple = ({
|
||||
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]',
|
||||
},
|
||||
iconProps = {
|
||||
className: 'size-5',
|
||||
}
|
||||
} : SignInWithAppleProps) => {
|
||||
const router = useRouter();
|
||||
const { loading, refreshUser } = useAuth();
|
||||
@@ -78,13 +64,7 @@ export const SignInWithApple = ({
|
||||
{...buttonProps}
|
||||
>
|
||||
<div className='flex items-center gap-2'>
|
||||
<Image
|
||||
src={imageProps.src}
|
||||
alt={imageProps.alt}
|
||||
className={imageProps.className}
|
||||
width={imageProps.width}
|
||||
height={imageProps.height}
|
||||
/>
|
||||
<FaApple {...iconProps} />
|
||||
<p className={textProps.className} {...textProps}>Sign In with Apple</p>
|
||||
</div>
|
||||
</SubmitButton>
|
||||
|
@@ -4,27 +4,17 @@ import { StatusMessage, SubmitButton } from '@/components/default/forms';
|
||||
import { useAuth } from '@/lib/hooks/context';
|
||||
import { useRouter } from 'next/navigation';
|
||||
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 ComponentProps } from 'react';
|
||||
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 = {
|
||||
buttonProps?: ButtonProps;
|
||||
imageProps?: ImageProps;
|
||||
formProps?: FormProps;
|
||||
textProps?: TextProps;
|
||||
buttonProps?: ComponentProps<'button'> & VariantProps<typeof buttonVariants>;
|
||||
formProps?: ComponentProps<'form'>;
|
||||
textProps?: ComponentProps<'p'>;
|
||||
iconProps?: ComponentProps<'svg'>;
|
||||
};
|
||||
|
||||
export const SignInWithMicrosoft = ({
|
||||
@@ -32,19 +22,15 @@ export const SignInWithMicrosoft = ({
|
||||
className: 'w-full cursor-pointer',
|
||||
type: 'submit',
|
||||
},
|
||||
imageProps = {
|
||||
src: '/icons/auth/microsoft.svg',
|
||||
alt: 'Apple',
|
||||
className: '',
|
||||
width: 24,
|
||||
height: 24,
|
||||
},
|
||||
formProps = {
|
||||
className: 'my-4',
|
||||
},
|
||||
textProps = {
|
||||
className: 'text-[1.0rem]',
|
||||
},
|
||||
iconProps = {
|
||||
className: 'size-5',
|
||||
}
|
||||
} : SignInWithMicrosoftProps) => {
|
||||
const router = useRouter();
|
||||
const { loading, refreshUser } = useAuth();
|
||||
@@ -60,7 +46,7 @@ export const SignInWithMicrosoft = ({
|
||||
setIsLoading(true);
|
||||
const result = await signInWithMicrosoft(supabase);
|
||||
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) {
|
||||
setStatusMessage(`Error signing in: ${error as string}`);
|
||||
} finally {
|
||||
@@ -75,17 +61,12 @@ export const SignInWithMicrosoft = ({
|
||||
<SubmitButton
|
||||
disabled={isLoading || loading}
|
||||
pendingText='Signing in...'
|
||||
pendingTextClassName={textProps.className}
|
||||
{...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>
|
||||
<FaMicrosoft {...iconProps} />
|
||||
<p className={textProps.className} {...textProps}>Sign In with Microsoft</p>
|
||||
</div>
|
||||
</SubmitButton>
|
||||
{statusMessage && <StatusMessage message={{ error: statusMessage }} />}
|
||||
|
3
src/components/default/auth/buttons/server/index.tsx
Normal file
3
src/components/default/auth/buttons/server/index.tsx
Normal 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';
|
15
src/components/default/auth/buttons/server/sign-in-link.tsx
Normal file
15
src/components/default/auth/buttons/server/sign-in-link.tsx
Normal 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>
|
||||
);
|
||||
};
|
@@ -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>
|
||||
);
|
||||
};
|
@@ -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>
|
||||
);
|
||||
};
|
@@ -43,7 +43,7 @@ const signInFormSchema = z.object({
|
||||
}),
|
||||
});
|
||||
|
||||
const signUpformSchema = z
|
||||
const signUpFormSchema = z
|
||||
.object({
|
||||
name: z.string().min(2, {
|
||||
message: 'Name must be at least 2 characters.',
|
||||
@@ -77,8 +77,8 @@ export const SignInCard = () => {
|
||||
},
|
||||
});
|
||||
|
||||
const signUpForm = useForm<z.infer<typeof signUpformSchema>>({
|
||||
resolver: zodResolver(signUpformSchema),
|
||||
const signUpForm = useForm<z.infer<typeof signUpFormSchema>>({
|
||||
resolver: zodResolver(signUpFormSchema),
|
||||
defaultValues: {
|
||||
name: '',
|
||||
email: '',
|
||||
@@ -100,11 +100,9 @@ export const SignInCard = () => {
|
||||
if (!supabase) throw new Error('Supabase client not found');
|
||||
const result = await signIn(supabase, formData);
|
||||
if (result.error) throw new Error(result.error.message);
|
||||
else if (result.data) {
|
||||
await refreshUser();
|
||||
signInForm.reset();
|
||||
router.push('');
|
||||
}
|
||||
await refreshUser();
|
||||
signInForm.reset();
|
||||
router.push('');
|
||||
} catch (error) {
|
||||
setStatusMessage(`Error signing in: ${error as string}`);
|
||||
}
|
||||
@@ -112,14 +110,214 @@ export const SignInCard = () => {
|
||||
|
||||
const handleSignUp = async (values: z.infer<typeof signUpFormSchema>) => {
|
||||
try {
|
||||
|
||||
} catch {
|
||||
|
||||
setStatusMessage('');
|
||||
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 (
|
||||
|
||||
<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>
|
||||
);
|
||||
|
||||
};
|
||||
|
@@ -9,14 +9,14 @@ type SubmitButtonProps = ComponentProps<'button'> &
|
||||
VariantProps<typeof buttonVariants> & {
|
||||
pendingText?: string;
|
||||
loaderClassName?: ComponentProps<'div'>['className'];
|
||||
textClassName?: ComponentProps<'p'>['className'];
|
||||
pendingTextClassName?: ComponentProps<'p'>['className'];
|
||||
};
|
||||
|
||||
export const SubmitButton = ({
|
||||
children,
|
||||
pendingText = 'Submitting...',
|
||||
loaderClassName = 'mr-2 h-4 w-4 animate-spin',
|
||||
textClassName = 'text-sm font-medium',
|
||||
pendingTextClassName = 'text-sm font-medium',
|
||||
...props
|
||||
}: SubmitButtonProps) => {
|
||||
const { pending } = useFormStatus();
|
||||
@@ -30,7 +30,7 @@ export const SubmitButton = ({
|
||||
{pending || props.disabled ? (
|
||||
<>
|
||||
<Loader2 className={loaderClassName} />
|
||||
<p className={textClassName}>{pendingText}</p>
|
||||
<p className={pendingTextClassName}>{pendingText}</p>
|
||||
</>
|
||||
) : (
|
||||
children
|
||||
|
@@ -4,7 +4,7 @@
|
||||
@custom-variant dark (&:is(.dark *));
|
||||
|
||||
@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";
|
||||
}
|
||||
|
||||
@@ -47,72 +47,99 @@
|
||||
}
|
||||
|
||||
:root {
|
||||
--radius: 0.65rem;
|
||||
--background: oklch(1 0 0);
|
||||
--foreground: oklch(0.141 0.005 285.823);
|
||||
--card: oklch(1 0 0);
|
||||
--card-foreground: oklch(0.141 0.005 285.823);
|
||||
--popover: oklch(1 0 0);
|
||||
--popover-foreground: oklch(0.141 0.005 285.823);
|
||||
--primary: oklch(0.645 0.246 16.439);
|
||||
--primary-foreground: oklch(0.969 0.015 12.422);
|
||||
--secondary: oklch(0.967 0.001 286.375);
|
||||
--secondary-foreground: oklch(0.21 0.006 285.885);
|
||||
--muted: oklch(0.967 0.001 286.375);
|
||||
--muted-foreground: oklch(0.552 0.016 285.938);
|
||||
--accent: oklch(0.967 0.001 286.375);
|
||||
--accent-foreground: oklch(0.21 0.006 285.885);
|
||||
--destructive: oklch(0.577 0.245 27.325);
|
||||
--border: oklch(0.92 0.004 286.32);
|
||||
--input: oklch(0.92 0.004 286.32);
|
||||
--ring: oklch(0.645 0.246 16.439);
|
||||
--chart-1: oklch(0.646 0.222 41.116);
|
||||
--chart-2: oklch(0.6 0.118 184.704);
|
||||
--chart-3: oklch(0.398 0.07 227.392);
|
||||
--chart-4: oklch(0.828 0.189 84.429);
|
||||
--chart-5: oklch(0.769 0.188 70.08);
|
||||
--sidebar: oklch(0.985 0 0);
|
||||
--sidebar-foreground: oklch(0.141 0.005 285.823);
|
||||
--sidebar-primary: oklch(0.645 0.246 16.439);
|
||||
--sidebar-primary-foreground: oklch(0.969 0.015 12.422);
|
||||
--sidebar-accent: oklch(0.967 0.001 286.375);
|
||||
--sidebar-accent-foreground: oklch(0.21 0.006 285.885);
|
||||
--sidebar-border: oklch(0.92 0.004 286.32);
|
||||
--sidebar-ring: oklch(0.645 0.246 16.439);
|
||||
--background: oklch(0.9785 0.0045 314.8050);
|
||||
--foreground: oklch(0.3710 0.0333 301.6287);
|
||||
--card: oklch(0.9940 0 0);
|
||||
--card-foreground: oklch(0.3710 0.0333 301.6287);
|
||||
--popover: oklch(0.9940 0 0);
|
||||
--popover-foreground: oklch(0.3710 0.0333 301.6287);
|
||||
--primary: oklch(0.4868 0.1488 286.5771);
|
||||
--primary-foreground: oklch(0.9785 0.0045 314.8050);
|
||||
--secondary: oklch(0.9139 0.0448 291.0467);
|
||||
--secondary-foreground: oklch(0.3008 0.0773 288.1551);
|
||||
--muted: oklch(0.8930 0.0149 312.2335);
|
||||
--muted-foreground: oklch(0.5361 0.0391 305.8579);
|
||||
--accent: oklch(0.8514 0.0535 342.1042);
|
||||
--accent-foreground: oklch(0.3328 0.0528 311.4628);
|
||||
--destructive: oklch(0.6984 0.1170 47.0382);
|
||||
--destructive-foreground: oklch(0.9785 0.0045 314.8050);
|
||||
--border: oklch(0.8488 0.0244 313.1102);
|
||||
--input: oklch(0.9352 0.0136 314.7562);
|
||||
--ring: oklch(0.4868 0.1488 286.5771);
|
||||
--chart-1: oklch(0.4868 0.1488 286.5771);
|
||||
--chart-2: oklch(0.8514 0.0535 342.1042);
|
||||
--chart-3: oklch(0.7388 0.0664 194.5709);
|
||||
--chart-4: oklch(0.9197 0.1140 104.6226);
|
||||
--chart-5: oklch(0.7409 0.0895 280.3986);
|
||||
--sidebar: oklch(0.9569 0.0090 314.7812);
|
||||
--sidebar-foreground: oklch(0.3710 0.0333 301.6287);
|
||||
--sidebar-primary: oklch(0.4868 0.1488 286.5771);
|
||||
--sidebar-primary-foreground: oklch(0.9785 0.0045 314.8050);
|
||||
--sidebar-accent: oklch(0.8514 0.0535 342.1042);
|
||||
--sidebar-accent-foreground: oklch(0.3328 0.0528 311.4628);
|
||||
--sidebar-border: oklch(0.8759 0.0218 316.4501);
|
||||
--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 {
|
||||
--background: oklch(0.141 0.005 285.823);
|
||||
--foreground: oklch(0.985 0 0);
|
||||
--card: oklch(0.21 0.006 285.885);
|
||||
--card-foreground: oklch(0.985 0 0);
|
||||
--popover: oklch(0.21 0.006 285.885);
|
||||
--popover-foreground: oklch(0.985 0 0);
|
||||
--primary: oklch(0.645 0.246 16.439);
|
||||
--primary-foreground: oklch(0.969 0.015 12.422);
|
||||
--secondary: oklch(0.274 0.006 286.033);
|
||||
--secondary-foreground: oklch(0.985 0 0);
|
||||
--muted: oklch(0.274 0.006 286.033);
|
||||
--muted-foreground: oklch(0.705 0.015 286.067);
|
||||
--accent: oklch(0.274 0.006 286.033);
|
||||
--accent-foreground: oklch(0.985 0 0);
|
||||
--destructive: oklch(0.704 0.191 22.216);
|
||||
--border: oklch(1 0 0 / 10%);
|
||||
--input: oklch(1 0 0 / 15%);
|
||||
--ring: oklch(0.645 0.246 16.439);
|
||||
--chart-1: oklch(0.488 0.243 264.376);
|
||||
--chart-2: oklch(0.696 0.17 162.48);
|
||||
--chart-3: oklch(0.769 0.188 70.08);
|
||||
--chart-4: oklch(0.627 0.265 303.9);
|
||||
--chart-5: oklch(0.645 0.246 16.439);
|
||||
--sidebar: oklch(0.21 0.006 285.885);
|
||||
--sidebar-foreground: oklch(0.985 0 0);
|
||||
--sidebar-primary: oklch(0.645 0.246 16.439);
|
||||
--sidebar-primary-foreground: oklch(0.969 0.015 12.422);
|
||||
--sidebar-accent: oklch(0.274 0.006 286.033);
|
||||
--sidebar-accent-foreground: oklch(0.985 0 0);
|
||||
--sidebar-border: oklch(1 0 0 / 10%);
|
||||
--sidebar-ring: oklch(0.645 0.246 16.439);
|
||||
--background: oklch(0.2213 0.0228 309.2819);
|
||||
--foreground: oklch(0.9100 0.0260 308.1435);
|
||||
--card: oklch(0.2671 0.0372 295.2445);
|
||||
--card-foreground: oklch(0.9100 0.0260 308.1435);
|
||||
--popover: oklch(0.2671 0.0372 295.2445);
|
||||
--popover-foreground: oklch(0.9100 0.0260 308.1435);
|
||||
--primary: oklch(0.6405 0.1338 286.4998);
|
||||
--primary-foreground: oklch(0.2213 0.0228 309.2819);
|
||||
--secondary: oklch(0.4071 0.0776 288.3025);
|
||||
--secondary-foreground: oklch(0.9100 0.0260 308.1435);
|
||||
--muted: oklch(0.2630 0.0340 310.8818);
|
||||
--muted-foreground: oklch(0.7026 0.0304 313.2720);
|
||||
--accent: oklch(0.3328 0.0528 311.4628);
|
||||
--accent-foreground: oklch(0.8076 0.0881 341.1289);
|
||||
--destructive: oklch(0.7501 0.1053 47.2117);
|
||||
--destructive-foreground: oklch(0.2213 0.0228 309.2819);
|
||||
--border: oklch(0.3139 0.0379 309.3053);
|
||||
--input: oklch(0.2913 0.0360 305.8978);
|
||||
--ring: oklch(0.6405 0.1338 286.4998);
|
||||
--chart-1: oklch(0.6405 0.1338 286.4998);
|
||||
--chart-2: oklch(0.8076 0.0881 341.1289);
|
||||
--chart-3: oklch(0.7388 0.0664 194.5709);
|
||||
--chart-4: oklch(0.9197 0.1140 104.6226);
|
||||
--chart-5: oklch(0.4868 0.1488 286.5771);
|
||||
--sidebar: oklch(0.2039 0.0232 309.1750);
|
||||
--sidebar-foreground: oklch(0.9100 0.0260 308.1435);
|
||||
--sidebar-primary: oklch(0.6405 0.1338 286.4998);
|
||||
--sidebar-primary-foreground: oklch(0.2213 0.0228 309.2819);
|
||||
--sidebar-accent: oklch(0.3328 0.0528 311.4628);
|
||||
--sidebar-accent-foreground: oklch(0.8076 0.0881 341.1289);
|
||||
--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 {
|
||||
|
@@ -1,5 +1,5 @@
|
||||
export { useSupabaseClient } from './client';
|
||||
export { updateSession } from './middleware';
|
||||
export { useSupabaseServer } from './server';
|
||||
export { SupabaseServer } from './server';
|
||||
export type { Database } from './database.types';
|
||||
export type * from './types';
|
||||
|
@@ -2,10 +2,10 @@
|
||||
|
||||
import 'server-only';
|
||||
import { createServerClient } from '@supabase/ssr';
|
||||
import type { Database, SupabaseClient } from '@/utils/supabase';
|
||||
import type { Database } from '@/utils/supabase';
|
||||
import { cookies } from 'next/headers';
|
||||
|
||||
export const useSupabaseServer = async (): Promise<SupabaseClient | undefined> => {
|
||||
export const SupabaseServer = async () => {
|
||||
const cookieStore = await cookies();
|
||||
return createServerClient<Database>(
|
||||
process.env.NEXT_PUBLIC_SUPABASE_URL!,
|
||||
|
Reference in New Issue
Block a user