Switching to tabs over spaces!
This commit is contained in:
@ -1,5 +1,7 @@
|
|||||||
{
|
{
|
||||||
"singleQuote": true,
|
"singleQuote": true,
|
||||||
"jsxSingleQuote": true,
|
"jsxSingleQuote": true,
|
||||||
"trailingComma": "all"
|
"trailingComma": "all",
|
||||||
|
"useTabs": true,
|
||||||
|
"tabWidth": 2
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,9 @@ export const GET = async (request: NextRequest) => {
|
|||||||
const { error } = await supabase.auth.exchangeCodeForSession(code);
|
const { error } = await supabase.auth.exchangeCodeForSession(code);
|
||||||
if (error) {
|
if (error) {
|
||||||
console.error('OAuth error:', error);
|
console.error('OAuth error:', error);
|
||||||
return redirect(`/sign-in?error=${encodeURIComponent(error.message)}`);
|
return redirect(
|
||||||
|
`/sign-in?error=${encodeURIComponent(error.message)}`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return redirect(redirectTo);
|
return redirect(redirectTo);
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,9 @@ const AuthSuccessPage = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
handleAuthSuccess().catch((error) => {
|
handleAuthSuccess().catch((error) => {
|
||||||
console.error(`Error: ${error instanceof Error ? error.message : error}`);
|
console.error(
|
||||||
|
`Error: ${error instanceof Error ? error.message : error}`,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
}, [refreshUserData, router]);
|
}, [refreshUserData, router]);
|
||||||
|
|
||||||
|
@ -57,7 +57,8 @@ const ForgotPassword = () => {
|
|||||||
if (result?.success) {
|
if (result?.success) {
|
||||||
await refreshUserData();
|
await refreshUserData();
|
||||||
setStatusMessage(
|
setStatusMessage(
|
||||||
result?.data ?? 'Check your email for a link to reset your password.',
|
result?.data ??
|
||||||
|
'Check your email for a link to reset your password.',
|
||||||
);
|
);
|
||||||
form.reset();
|
form.reset();
|
||||||
router.push('');
|
router.push('');
|
||||||
@ -74,7 +75,9 @@ const ForgotPassword = () => {
|
|||||||
return (
|
return (
|
||||||
<Card className='min-w-xs md:min-w-sm'>
|
<Card className='min-w-xs md:min-w-sm'>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle className='text-2xl font-medium'>Reset Password</CardTitle>
|
<CardTitle className='text-2xl font-medium'>
|
||||||
|
Reset Password
|
||||||
|
</CardTitle>
|
||||||
<CardDescription className='text-sm text-foreground'>
|
<CardDescription className='text-sm text-foreground'>
|
||||||
Don't have an account?{' '}
|
Don't have an account?{' '}
|
||||||
<Link className='font-medium underline' href='/sign-up'>
|
<Link className='font-medium underline' href='/sign-up'>
|
||||||
@ -116,9 +119,13 @@ const ForgotPassword = () => {
|
|||||||
statusMessage.includes('error') ||
|
statusMessage.includes('error') ||
|
||||||
statusMessage.includes('failed') ||
|
statusMessage.includes('failed') ||
|
||||||
statusMessage.includes('invalid') ? (
|
statusMessage.includes('invalid') ? (
|
||||||
<StatusMessage message={{ error: statusMessage }} />
|
<StatusMessage
|
||||||
|
message={{ error: statusMessage }}
|
||||||
|
/>
|
||||||
) : (
|
) : (
|
||||||
<StatusMessage message={{ success: statusMessage }} />
|
<StatusMessage
|
||||||
|
message={{ success: statusMessage }}
|
||||||
|
/>
|
||||||
))}
|
))}
|
||||||
</form>
|
</form>
|
||||||
</Form>
|
</Form>
|
||||||
|
@ -97,7 +97,8 @@ const ProfilePage = () => {
|
|||||||
<CardHeader className='pb-2'>
|
<CardHeader className='pb-2'>
|
||||||
<CardTitle className='text-2xl'>Your Profile</CardTitle>
|
<CardTitle className='text-2xl'>Your Profile</CardTitle>
|
||||||
<CardDescription>
|
<CardDescription>
|
||||||
Manage your personal information and how it appears to others
|
Manage your personal information and how it appears to
|
||||||
|
others
|
||||||
</CardDescription>
|
</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
{isLoading && !profile ? (
|
{isLoading && !profile ? (
|
||||||
@ -110,7 +111,9 @@ const ProfilePage = () => {
|
|||||||
<Separator />
|
<Separator />
|
||||||
<ProfileForm onSubmit={handleProfileSubmit} />
|
<ProfileForm onSubmit={handleProfileSubmit} />
|
||||||
<Separator />
|
<Separator />
|
||||||
<ResetPasswordForm onSubmit={handleResetPasswordSubmit} />
|
<ResetPasswordForm
|
||||||
|
onSubmit={handleResetPasswordSubmit}
|
||||||
|
/>
|
||||||
<Separator />
|
<Separator />
|
||||||
<SignOut />
|
<SignOut />
|
||||||
</div>
|
</div>
|
||||||
|
@ -99,7 +99,9 @@ const Login = () => {
|
|||||||
name='email'
|
name='email'
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel className='text-lg'>Email</FormLabel>
|
<FormLabel className='text-lg'>
|
||||||
|
Email
|
||||||
|
</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input
|
<Input
|
||||||
type='email'
|
type='email'
|
||||||
@ -118,7 +120,9 @@ const Login = () => {
|
|||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<div className='flex justify-between'>
|
<div className='flex justify-between'>
|
||||||
<FormLabel className='text-lg'>Password</FormLabel>
|
<FormLabel className='text-lg'>
|
||||||
|
Password
|
||||||
|
</FormLabel>
|
||||||
<Link
|
<Link
|
||||||
className='text-xs text-foreground underline text-right'
|
className='text-xs text-foreground underline text-right'
|
||||||
href='/forgot-password'
|
href='/forgot-password'
|
||||||
@ -142,9 +146,13 @@ const Login = () => {
|
|||||||
statusMessage.includes('error') ||
|
statusMessage.includes('error') ||
|
||||||
statusMessage.includes('failed') ||
|
statusMessage.includes('failed') ||
|
||||||
statusMessage.includes('invalid') ? (
|
statusMessage.includes('invalid') ? (
|
||||||
<StatusMessage message={{ error: statusMessage }} />
|
<StatusMessage
|
||||||
|
message={{ error: statusMessage }}
|
||||||
|
/>
|
||||||
) : (
|
) : (
|
||||||
<StatusMessage message={{ message: statusMessage }} />
|
<StatusMessage
|
||||||
|
message={{ message: statusMessage }}
|
||||||
|
/>
|
||||||
))}
|
))}
|
||||||
<SubmitButton
|
<SubmitButton
|
||||||
disabled={isLoading}
|
disabled={isLoading}
|
||||||
|
@ -104,7 +104,10 @@ const SignUp = () => {
|
|||||||
<CardTitle className='text-3xl font-medium'>Sign Up</CardTitle>
|
<CardTitle className='text-3xl font-medium'>Sign Up</CardTitle>
|
||||||
<CardDescription className='text-foreground'>
|
<CardDescription className='text-foreground'>
|
||||||
Already have an account?{' '}
|
Already have an account?{' '}
|
||||||
<Link className='text-primary font-medium underline' href='/sign-in'>
|
<Link
|
||||||
|
className='text-primary font-medium underline'
|
||||||
|
href='/sign-in'
|
||||||
|
>
|
||||||
Sign in
|
Sign in
|
||||||
</Link>
|
</Link>
|
||||||
</CardDescription>
|
</CardDescription>
|
||||||
@ -120,9 +123,15 @@ const SignUp = () => {
|
|||||||
name='name'
|
name='name'
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel className='text-lg'>Name</FormLabel>
|
<FormLabel className='text-lg'>
|
||||||
|
Name
|
||||||
|
</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input type='text' placeholder='Full Name' {...field} />
|
<Input
|
||||||
|
type='text'
|
||||||
|
placeholder='Full Name'
|
||||||
|
{...field}
|
||||||
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
@ -132,7 +141,9 @@ const SignUp = () => {
|
|||||||
name='email'
|
name='email'
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel className='text-lg'>Email</FormLabel>
|
<FormLabel className='text-lg'>
|
||||||
|
Email
|
||||||
|
</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input
|
<Input
|
||||||
type='email'
|
type='email'
|
||||||
@ -149,7 +160,9 @@ const SignUp = () => {
|
|||||||
name='password'
|
name='password'
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel className='text-lg'>Password</FormLabel>
|
<FormLabel className='text-lg'>
|
||||||
|
Password
|
||||||
|
</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input
|
<Input
|
||||||
type='password'
|
type='password'
|
||||||
@ -166,7 +179,9 @@ const SignUp = () => {
|
|||||||
name='confirmPassword'
|
name='confirmPassword'
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel className='text-lg'>Confirm Password</FormLabel>
|
<FormLabel className='text-lg'>
|
||||||
|
Confirm Password
|
||||||
|
</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input
|
<Input
|
||||||
type='password'
|
type='password'
|
||||||
@ -183,9 +198,13 @@ const SignUp = () => {
|
|||||||
statusMessage.includes('error') ||
|
statusMessage.includes('error') ||
|
||||||
statusMessage.includes('failed') ||
|
statusMessage.includes('failed') ||
|
||||||
statusMessage.includes('invalid') ? (
|
statusMessage.includes('invalid') ? (
|
||||||
<StatusMessage message={{ error: statusMessage }} />
|
<StatusMessage
|
||||||
|
message={{ error: statusMessage }}
|
||||||
|
/>
|
||||||
) : (
|
) : (
|
||||||
<StatusMessage message={{ success: statusMessage }} />
|
<StatusMessage
|
||||||
|
message={{ success: statusMessage }}
|
||||||
|
/>
|
||||||
))}
|
))}
|
||||||
<SubmitButton
|
<SubmitButton
|
||||||
className='text-[1.0rem] cursor-pointer'
|
className='text-[1.0rem] cursor-pointer'
|
||||||
|
@ -27,9 +27,15 @@ const GlobalError = ({ error, reset = undefined }: GlobalErrorProps) => {
|
|||||||
}, [error]);
|
}, [error]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<html lang='en' className={`${geist.variable}`} suppressHydrationWarning>
|
<html
|
||||||
|
lang='en'
|
||||||
|
className={`${geist.variable}`}
|
||||||
|
suppressHydrationWarning
|
||||||
|
>
|
||||||
<body
|
<body
|
||||||
className={cn('bg-background text-foreground font-sans antialiased')}
|
className={cn(
|
||||||
|
'bg-background text-foreground font-sans antialiased',
|
||||||
|
)}
|
||||||
>
|
>
|
||||||
<ThemeProvider
|
<ThemeProvider
|
||||||
attribute='class'
|
attribute='class'
|
||||||
@ -47,7 +53,9 @@ const GlobalError = ({ error, reset = undefined }: GlobalErrorProps) => {
|
|||||||
>
|
>
|
||||||
<NextError statusCode={0} />
|
<NextError statusCode={0} />
|
||||||
{reset !== undefined && (
|
{reset !== undefined && (
|
||||||
<Button onClick={() => reset()}>Try again</Button>
|
<Button onClick={() => reset()}>
|
||||||
|
Try again
|
||||||
|
</Button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -46,8 +46,16 @@ export const generateMetadata = (): Metadata => {
|
|||||||
icons: {
|
icons: {
|
||||||
icon: [
|
icon: [
|
||||||
{ url: '/favicon.ico', type: 'image/x-icon', sizes: 'any' },
|
{ url: '/favicon.ico', type: 'image/x-icon', sizes: 'any' },
|
||||||
{ url: '/favicon-16x16.png', type: 'image/png', sizes: '16x16' },
|
{
|
||||||
{ url: '/favicon-32x32.png', type: 'image/png', sizes: '32x32' },
|
url: '/favicon-16x16.png',
|
||||||
|
type: 'image/png',
|
||||||
|
sizes: '16x16',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
url: '/favicon-32x32.png',
|
||||||
|
type: 'image/png',
|
||||||
|
sizes: '32x32',
|
||||||
|
},
|
||||||
{ url: '/favicon.png', type: 'image/png', sizes: '96x96' },
|
{ url: '/favicon.png', type: 'image/png', sizes: '96x96' },
|
||||||
{
|
{
|
||||||
url: '/favicon.ico',
|
url: '/favicon.ico',
|
||||||
@ -74,16 +82,36 @@ export const generateMetadata = (): Metadata => {
|
|||||||
media: '(prefers-color-scheme: dark)',
|
media: '(prefers-color-scheme: dark)',
|
||||||
},
|
},
|
||||||
|
|
||||||
{ url: '/appicon/icon-36x36.png', type: 'image/png', sizes: '36x36' },
|
{
|
||||||
{ url: '/appicon/icon-48x48.png', type: 'image/png', sizes: '48x48' },
|
url: '/appicon/icon-36x36.png',
|
||||||
{ url: '/appicon/icon-72x72.png', type: 'image/png', sizes: '72x72' },
|
type: 'image/png',
|
||||||
{ url: '/appicon/icon-96x96.png', type: 'image/png', sizes: '96x96' },
|
sizes: '36x36',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
url: '/appicon/icon-48x48.png',
|
||||||
|
type: 'image/png',
|
||||||
|
sizes: '48x48',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
url: '/appicon/icon-72x72.png',
|
||||||
|
type: 'image/png',
|
||||||
|
sizes: '72x72',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
url: '/appicon/icon-96x96.png',
|
||||||
|
type: 'image/png',
|
||||||
|
sizes: '96x96',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
url: '/appicon/icon-144x144.png',
|
url: '/appicon/icon-144x144.png',
|
||||||
type: 'image/png',
|
type: 'image/png',
|
||||||
sizes: '144x144',
|
sizes: '144x144',
|
||||||
},
|
},
|
||||||
{ url: '/appicon/icon.png', type: 'image/png', sizes: '192x192' },
|
{
|
||||||
|
url: '/appicon/icon.png',
|
||||||
|
type: 'image/png',
|
||||||
|
sizes: '192x192',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
url: '/appicon/icon-36x36.png',
|
url: '/appicon/icon-36x36.png',
|
||||||
type: 'image/png',
|
type: 'image/png',
|
||||||
@ -122,16 +150,36 @@ export const generateMetadata = (): Metadata => {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
shortcut: [
|
shortcut: [
|
||||||
{ url: '/appicon/icon-36x36.png', type: 'image/png', sizes: '36x36' },
|
{
|
||||||
{ url: '/appicon/icon-48x48.png', type: 'image/png', sizes: '48x48' },
|
url: '/appicon/icon-36x36.png',
|
||||||
{ url: '/appicon/icon-72x72.png', type: 'image/png', sizes: '72x72' },
|
type: 'image/png',
|
||||||
{ url: '/appicon/icon-96x96.png', type: 'image/png', sizes: '96x96' },
|
sizes: '36x36',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
url: '/appicon/icon-48x48.png',
|
||||||
|
type: 'image/png',
|
||||||
|
sizes: '48x48',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
url: '/appicon/icon-72x72.png',
|
||||||
|
type: 'image/png',
|
||||||
|
sizes: '72x72',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
url: '/appicon/icon-96x96.png',
|
||||||
|
type: 'image/png',
|
||||||
|
sizes: '96x96',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
url: '/appicon/icon-144x144.png',
|
url: '/appicon/icon-144x144.png',
|
||||||
type: 'image/png',
|
type: 'image/png',
|
||||||
sizes: '144x144',
|
sizes: '144x144',
|
||||||
},
|
},
|
||||||
{ url: '/appicon/icon.png', type: 'image/png', sizes: '192x192' },
|
{
|
||||||
|
url: '/appicon/icon.png',
|
||||||
|
type: 'image/png',
|
||||||
|
sizes: '192x192',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
url: '/appicon/icon-36x36.png',
|
url: '/appicon/icon-36x36.png',
|
||||||
type: 'image/png',
|
type: 'image/png',
|
||||||
@ -170,10 +218,26 @@ export const generateMetadata = (): Metadata => {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
apple: [
|
apple: [
|
||||||
{ url: 'appicon/icon-57x57.png', type: 'image/png', sizes: '57x57' },
|
{
|
||||||
{ url: 'appicon/icon-60x60.png', type: 'image/png', sizes: '60x60' },
|
url: 'appicon/icon-57x57.png',
|
||||||
{ url: 'appicon/icon-72x72.png', type: 'image/png', sizes: '72x72' },
|
type: 'image/png',
|
||||||
{ url: 'appicon/icon-76x76.png', type: 'image/png', sizes: '76x76' },
|
sizes: '57x57',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
url: 'appicon/icon-60x60.png',
|
||||||
|
type: 'image/png',
|
||||||
|
sizes: '60x60',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
url: 'appicon/icon-72x72.png',
|
||||||
|
type: 'image/png',
|
||||||
|
sizes: '72x72',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
url: 'appicon/icon-76x76.png',
|
||||||
|
type: 'image/png',
|
||||||
|
sizes: '76x76',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
url: 'appicon/icon-114x114.png',
|
url: 'appicon/icon-114x114.png',
|
||||||
type: 'image/png',
|
type: 'image/png',
|
||||||
@ -199,7 +263,11 @@ export const generateMetadata = (): Metadata => {
|
|||||||
type: 'image/png',
|
type: 'image/png',
|
||||||
sizes: '180x180',
|
sizes: '180x180',
|
||||||
},
|
},
|
||||||
{ url: 'appicon/icon.png', type: 'image/png', sizes: '192x192' },
|
{
|
||||||
|
url: 'appicon/icon.png',
|
||||||
|
type: 'image/png',
|
||||||
|
sizes: '192x192',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
url: 'appicon/icon-57x57.png',
|
url: 'appicon/icon-57x57.png',
|
||||||
type: 'image/png',
|
type: 'image/png',
|
||||||
@ -349,9 +417,15 @@ const geist = Geist({
|
|||||||
|
|
||||||
const RootLayout = ({ children }: Readonly<{ children: React.ReactNode }>) => {
|
const RootLayout = ({ children }: Readonly<{ children: React.ReactNode }>) => {
|
||||||
return (
|
return (
|
||||||
<html lang='en' className={`${geist.variable}`} suppressHydrationWarning>
|
<html
|
||||||
|
lang='en'
|
||||||
|
className={`${geist.variable}`}
|
||||||
|
suppressHydrationWarning
|
||||||
|
>
|
||||||
<body
|
<body
|
||||||
className={cn('bg-background text-foreground font-sans antialiased')}
|
className={cn(
|
||||||
|
'bg-background text-foreground font-sans antialiased',
|
||||||
|
)}
|
||||||
>
|
>
|
||||||
<ThemeProvider
|
<ThemeProvider
|
||||||
attribute='class'
|
attribute='class'
|
||||||
|
@ -31,9 +31,9 @@ const HomePage = async () => {
|
|||||||
Welcome to the T3 Supabase Template!
|
Welcome to the T3 Supabase Template!
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
<CardDescription className='text-[1.0rem] mb-2'>
|
<CardDescription className='text-[1.0rem] mb-2'>
|
||||||
A great place to start is by creating a new user account &
|
A great place to start is by creating a new user
|
||||||
ensuring you can sign up! If you already have an account, go
|
account & ensuring you can sign up! If you
|
||||||
ahead and sign in!
|
already have an account, go ahead and sign in!
|
||||||
</CardDescription>
|
</CardDescription>
|
||||||
<SignInSignUp
|
<SignInSignUp
|
||||||
className='flex gap-4 w-full justify-center'
|
className='flex gap-4 w-full justify-center'
|
||||||
@ -42,7 +42,9 @@ const HomePage = async () => {
|
|||||||
/>
|
/>
|
||||||
<div className='flex items-center w-full gap-4'>
|
<div className='flex items-center w-full gap-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>
|
||||||
<div className='flex gap-4'>
|
<div className='flex gap-4'>
|
||||||
@ -53,8 +55,8 @@ const HomePage = async () => {
|
|||||||
<Separator className='bg-accent' />
|
<Separator className='bg-accent' />
|
||||||
<CardContent className='flex flex-col px-5 py-2 items-center justify-center'>
|
<CardContent className='flex flex-col px-5 py-2 items-center justify-center'>
|
||||||
<CardTitle className='text-lg mb-6 w-2/3 text-center'>
|
<CardTitle className='text-lg mb-6 w-2/3 text-center'>
|
||||||
You can also test out your connection to Sentry if you want to
|
You can also test out your connection to Sentry
|
||||||
start there!
|
if you want to start there!
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
<TestSentryCard />
|
<TestSentryCard />
|
||||||
</CardContent>
|
</CardContent>
|
||||||
|
@ -161,7 +161,9 @@ export const AuthProvider = ({ children }: { children: ReactNode }) => {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error updating profile:', error);
|
console.error('Error updating profile:', error);
|
||||||
toast.error(
|
toast.error(
|
||||||
error instanceof Error ? error.message : 'Failed to update profile',
|
error instanceof Error
|
||||||
|
? error.message
|
||||||
|
: 'Failed to update profile',
|
||||||
);
|
);
|
||||||
return { success: false, error };
|
return { success: false, error };
|
||||||
}
|
}
|
||||||
@ -183,7 +185,9 @@ export const AuthProvider = ({ children }: { children: ReactNode }) => {
|
|||||||
refreshUserData,
|
refreshUserData,
|
||||||
};
|
};
|
||||||
|
|
||||||
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
|
return (
|
||||||
|
<AuthContext.Provider value={value}>{children}</AuthContext.Provider>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useAuth = () => {
|
export const useAuth = () => {
|
||||||
|
@ -71,7 +71,9 @@ export const SignInWithApple = ({
|
|||||||
<p className='text-[1.0rem]'>Sign In with Apple</p>
|
<p className='text-[1.0rem]'>Sign In with Apple</p>
|
||||||
</div>
|
</div>
|
||||||
</SubmitButton>
|
</SubmitButton>
|
||||||
{statusMessage && <StatusMessage message={{ error: statusMessage }} />}
|
{statusMessage && (
|
||||||
|
<StatusMessage message={{ error: statusMessage }} />
|
||||||
|
)}
|
||||||
</form>
|
</form>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -64,7 +64,9 @@ export const SignInWithMicrosoft = ({
|
|||||||
<p className='text-[1.0rem]'>Sign In with Microsoft</p>
|
<p className='text-[1.0rem]'>Sign In with Microsoft</p>
|
||||||
</div>
|
</div>
|
||||||
</SubmitButton>
|
</SubmitButton>
|
||||||
{statusMessage && <StatusMessage message={{ error: statusMessage }} />}
|
{statusMessage && (
|
||||||
|
<StatusMessage message={{ error: statusMessage }} />
|
||||||
|
)}
|
||||||
</form>
|
</form>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -18,8 +18,15 @@ const Navigation = () => {
|
|||||||
>
|
>
|
||||||
<div className='flex gap-5 items-center font-semibold'>
|
<div className='flex gap-5 items-center font-semibold'>
|
||||||
<Link className='flex flex-row my-auto gap-2' href='/'>
|
<Link className='flex flex-row my-auto gap-2' href='/'>
|
||||||
<Image src='/favicon.png' alt='T3 Logo' width={50} height={50} />
|
<Image
|
||||||
<h1 className='my-auto text-2xl'>T3 Supabase Template</h1>
|
src='/favicon.png'
|
||||||
|
alt='T3 Logo'
|
||||||
|
width={50}
|
||||||
|
height={50}
|
||||||
|
/>
|
||||||
|
<h1 className='my-auto text-2xl'>
|
||||||
|
T3 Supabase Template
|
||||||
|
</h1>
|
||||||
</Link>
|
</Link>
|
||||||
<div className='flex items-center gap-2'>
|
<div className='flex items-center gap-2'>
|
||||||
<Button asChild>
|
<Button asChild>
|
||||||
|
@ -55,7 +55,10 @@ export const ProfileForm = ({ onSubmit }: ProfileFormProps) => {
|
|||||||
return (
|
return (
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<Form {...form}>
|
<Form {...form}>
|
||||||
<form onSubmit={form.handleSubmit(handleSubmit)} className='space-y-6'>
|
<form
|
||||||
|
onSubmit={form.handleSubmit(handleSubmit)}
|
||||||
|
className='space-y-6'
|
||||||
|
>
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name='full_name'
|
name='full_name'
|
||||||
@ -65,7 +68,9 @@ export const ProfileForm = ({ onSubmit }: ProfileFormProps) => {
|
|||||||
<FormControl>
|
<FormControl>
|
||||||
<Input {...field} />
|
<Input {...field} />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormDescription>Your public display name.</FormDescription>
|
<FormDescription>
|
||||||
|
Your public display name.
|
||||||
|
</FormDescription>
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
@ -81,7 +86,8 @@ export const ProfileForm = ({ onSubmit }: ProfileFormProps) => {
|
|||||||
<Input {...field} />
|
<Input {...field} />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormDescription>
|
<FormDescription>
|
||||||
Your email address associated with your account.
|
Your email address associated with your
|
||||||
|
account.
|
||||||
</FormDescription>
|
</FormDescription>
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
@ -89,7 +95,10 @@ export const ProfileForm = ({ onSubmit }: ProfileFormProps) => {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<div className='flex justify-center'>
|
<div className='flex justify-center'>
|
||||||
<SubmitButton disabled={isLoading} pendingText='Saving...'>
|
<SubmitButton
|
||||||
|
disabled={isLoading}
|
||||||
|
pendingText='Saving...'
|
||||||
|
>
|
||||||
Save Changes
|
Save Changes
|
||||||
</SubmitButton>
|
</SubmitButton>
|
||||||
</div>
|
</div>
|
||||||
|
@ -69,7 +69,9 @@ export const ResetPasswordForm = ({
|
|||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
setStatusMessage(
|
setStatusMessage(
|
||||||
error instanceof Error ? error.message : 'Password was not updated!',
|
error instanceof Error
|
||||||
|
? error.message
|
||||||
|
: 'Password was not updated!',
|
||||||
);
|
);
|
||||||
} finally {
|
} finally {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
@ -100,7 +102,8 @@ export const ResetPasswordForm = ({
|
|||||||
<Input type='password' {...field} />
|
<Input type='password' {...field} />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormDescription>
|
<FormDescription>
|
||||||
Enter your new password. Must be at least 8 characters.
|
Enter your new password. Must be at
|
||||||
|
least 8 characters.
|
||||||
</FormDescription>
|
</FormDescription>
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
@ -116,7 +119,8 @@ export const ResetPasswordForm = ({
|
|||||||
<Input type='password' {...field} />
|
<Input type='password' {...field} />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormDescription>
|
<FormDescription>
|
||||||
Please re-enter your new password to confirm.
|
Please re-enter your new password to
|
||||||
|
confirm.
|
||||||
</FormDescription>
|
</FormDescription>
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
@ -127,9 +131,13 @@ export const ResetPasswordForm = ({
|
|||||||
statusMessage.includes('error') ||
|
statusMessage.includes('error') ||
|
||||||
statusMessage.includes('failed') ||
|
statusMessage.includes('failed') ||
|
||||||
statusMessage.includes('invalid') ? (
|
statusMessage.includes('invalid') ? (
|
||||||
<StatusMessage message={{ error: statusMessage }} />
|
<StatusMessage
|
||||||
|
message={{ error: statusMessage }}
|
||||||
|
/>
|
||||||
) : (
|
) : (
|
||||||
<StatusMessage message={{ message: statusMessage }} />
|
<StatusMessage
|
||||||
|
message={{ message: statusMessage }}
|
||||||
|
/>
|
||||||
))}
|
))}
|
||||||
<div className='flex justify-center'>
|
<div className='flex justify-center'>
|
||||||
<SubmitButton
|
<SubmitButton
|
||||||
|
@ -69,7 +69,9 @@ export const TestSentryCard = () => {
|
|||||||
fill='currentcolor'
|
fill='currentcolor'
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
<CardTitle className='text-3xl my-auto'>Test Sentry</CardTitle>
|
<CardTitle className='text-3xl my-auto'>
|
||||||
|
Test Sentry
|
||||||
|
</CardTitle>
|
||||||
</div>
|
</div>
|
||||||
<CardDescription className='text-[1.0rem]'>
|
<CardDescription className='text-[1.0rem]'>
|
||||||
Click the button below & view the sample error on{' '}
|
Click the button below & view the sample error on{' '}
|
||||||
@ -79,8 +81,8 @@ export const TestSentryCard = () => {
|
|||||||
>
|
>
|
||||||
the Sentry website
|
the Sentry website
|
||||||
</Link>
|
</Link>
|
||||||
. Navigate to the {"'"}Issues{"'"} page & you should see the sample
|
. Navigate to the {"'"}Issues{"'"} page & you should see the
|
||||||
error!
|
sample error!
|
||||||
</CardDescription>
|
</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
@ -95,14 +97,20 @@ export const TestSentryCard = () => {
|
|||||||
{hasSentError ? (
|
{hasSentError ? (
|
||||||
<div className='rounded-md bg-green-500/80 dark:bg-green-500/50 py-2 px-4 flex flex-row gap-2 my-auto'>
|
<div className='rounded-md bg-green-500/80 dark:bg-green-500/50 py-2 px-4 flex flex-row gap-2 my-auto'>
|
||||||
<CheckCircle size={30} className='my-auto' />
|
<CheckCircle size={30} className='my-auto' />
|
||||||
<p className='text-lg'>Sample error was sent to Sentry!</p>
|
<p className='text-lg'>
|
||||||
|
Sample error was sent to Sentry!
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
) : !isConnected ? (
|
) : !isConnected ? (
|
||||||
<div className='rounded-md bg-red-600/50 dark:bg-red-500/50 py-2 px-4 flex flex-row gap-2 my-auto'>
|
<div className='rounded-md bg-red-600/50 dark:bg-red-500/50 py-2 px-4 flex flex-row gap-2 my-auto'>
|
||||||
<MessageCircleWarning size={40} className='my-auto' />
|
<MessageCircleWarning
|
||||||
|
size={40}
|
||||||
|
className='my-auto'
|
||||||
|
/>
|
||||||
<p>
|
<p>
|
||||||
Wait! The Sentry SDK is not able to reach Sentry right now -
|
Wait! The Sentry SDK is not able to reach Sentry
|
||||||
this may be due to an adblocker. For more information, see{' '}
|
right now - this may be due to an adblocker. For
|
||||||
|
more information, see{' '}
|
||||||
<Link
|
<Link
|
||||||
href='https://docs.sentry.io/platforms/javascript/guides/nextjs/troubleshooting/#the-sdk-is-not-sending-any-data'
|
href='https://docs.sentry.io/platforms/javascript/guides/nextjs/troubleshooting/#the-sdk-is-not-sending-any-data'
|
||||||
className='text-accent-foreground underline hover:text-primary'
|
className='text-accent-foreground underline hover:text-primary'
|
||||||
@ -117,8 +125,8 @@ export const TestSentryCard = () => {
|
|||||||
</div>
|
</div>
|
||||||
<Separator className='my-4 bg-accent' />
|
<Separator className='my-4 bg-accent' />
|
||||||
<p className='description'>
|
<p className='description'>
|
||||||
Warning! Sometimes Adblockers will prevent errors from being sent to
|
Warning! Sometimes Adblockers will prevent errors from being
|
||||||
Sentry.
|
sent to Sentry.
|
||||||
</p>
|
</p>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
@ -57,9 +57,9 @@ export const FetchDataSteps = () => {
|
|||||||
>
|
>
|
||||||
Table Editor
|
Table Editor
|
||||||
</a>{' '}
|
</a>{' '}
|
||||||
for your Supabase project to create a table and insert some example
|
for your Supabase project to create a table and insert some
|
||||||
data. If you're stuck for creativity, you can copy and paste the
|
example data. If you're stuck for creativity, you can
|
||||||
following into the{' '}
|
copy and paste the following into the{' '}
|
||||||
<a
|
<a
|
||||||
href='https://supabase.com/dashboard/project/_/sql/new'
|
href='https://supabase.com/dashboard/project/_/sql/new'
|
||||||
className='font-bold hover:underline text-foreground/80'
|
className='font-bold hover:underline text-foreground/80'
|
||||||
@ -75,8 +75,8 @@ export const FetchDataSteps = () => {
|
|||||||
|
|
||||||
<TutorialStep title='Query Supabase data from Next.js'>
|
<TutorialStep title='Query Supabase data from Next.js'>
|
||||||
<p>
|
<p>
|
||||||
To create a Supabase client and query data from an Async Server
|
To create a Supabase client and query data from an Async
|
||||||
Component, create a new page.tsx file at{' '}
|
Server Component, create a new page.tsx file at{' '}
|
||||||
<span className='relative rounded bg-muted px-[0.3rem] py-[0.2rem] font-mono text-xs font-medium text-secondary-foreground border'>
|
<span className='relative rounded bg-muted px-[0.3rem] py-[0.2rem] font-mono text-xs font-medium text-secondary-foreground border'>
|
||||||
/app/notes/page.tsx
|
/app/notes/page.tsx
|
||||||
</span>{' '}
|
</span>{' '}
|
||||||
|
@ -17,8 +17,7 @@ const buttonVariants = cva(
|
|||||||
'border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50',
|
'border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50',
|
||||||
secondary:
|
secondary:
|
||||||
'bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80',
|
'bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80',
|
||||||
ghost:
|
ghost: 'hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50',
|
||||||
'hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50',
|
|
||||||
link: 'text-primary underline-offset-4 hover:underline',
|
link: 'text-primary underline-offset-4 hover:underline',
|
||||||
},
|
},
|
||||||
size: {
|
size: {
|
||||||
|
@ -16,7 +16,10 @@ function DropdownMenuPortal({
|
|||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.Portal>) {
|
}: React.ComponentProps<typeof DropdownMenuPrimitive.Portal>) {
|
||||||
return (
|
return (
|
||||||
<DropdownMenuPrimitive.Portal data-slot='dropdown-menu-portal' {...props} />
|
<DropdownMenuPrimitive.Portal
|
||||||
|
data-slot='dropdown-menu-portal'
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -55,7 +58,10 @@ function DropdownMenuGroup({
|
|||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.Group>) {
|
}: React.ComponentProps<typeof DropdownMenuPrimitive.Group>) {
|
||||||
return (
|
return (
|
||||||
<DropdownMenuPrimitive.Group data-slot='dropdown-menu-group' {...props} />
|
<DropdownMenuPrimitive.Group
|
||||||
|
data-slot='dropdown-menu-group'
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -195,7 +201,9 @@ function DropdownMenuShortcut({
|
|||||||
function DropdownMenuSub({
|
function DropdownMenuSub({
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.Sub>) {
|
}: React.ComponentProps<typeof DropdownMenuPrimitive.Sub>) {
|
||||||
return <DropdownMenuPrimitive.Sub data-slot='dropdown-menu-sub' {...props} />;
|
return (
|
||||||
|
<DropdownMenuPrimitive.Sub data-slot='dropdown-menu-sub' {...props} />
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function DropdownMenuSubTrigger({
|
function DropdownMenuSubTrigger({
|
||||||
|
@ -40,7 +40,8 @@ export const env = createEnv({
|
|||||||
CI: process.env.CI,
|
CI: process.env.CI,
|
||||||
|
|
||||||
NEXT_PUBLIC_SUPABASE_URL: process.env.NEXT_PUBLIC_SUPABASE_URL,
|
NEXT_PUBLIC_SUPABASE_URL: process.env.NEXT_PUBLIC_SUPABASE_URL,
|
||||||
NEXT_PUBLIC_SUPABASE_ANON_KEY: process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY,
|
NEXT_PUBLIC_SUPABASE_ANON_KEY:
|
||||||
|
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY,
|
||||||
NEXT_PUBLIC_SITE_URL: process.env.NEXT_PUBLIC_SITE_URL,
|
NEXT_PUBLIC_SITE_URL: process.env.NEXT_PUBLIC_SITE_URL,
|
||||||
NEXT_PUBLIC_SENTRY_DSN: process.env.NEXT_PUBLIC_SENTRY_DSN,
|
NEXT_PUBLIC_SENTRY_DSN: process.env.NEXT_PUBLIC_SENTRY_DSN,
|
||||||
NEXT_PUBLIC_SENTRY_URL: process.env.NEXT_PUBLIC_SENTRY_URL,
|
NEXT_PUBLIC_SENTRY_URL: process.env.NEXT_PUBLIC_SENTRY_URL,
|
||||||
|
@ -124,7 +124,9 @@ export const uploadFile = async ({
|
|||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
error:
|
error:
|
||||||
error instanceof Error ? error.message : 'Unknown error uploading file',
|
error instanceof Error
|
||||||
|
? error.message
|
||||||
|
: 'Unknown error uploading file',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -147,7 +149,9 @@ export const replaceFile = async ({
|
|||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
error:
|
error:
|
||||||
error instanceof Error ? error.message : 'Unknown error replacing file',
|
error instanceof Error
|
||||||
|
? error.message
|
||||||
|
: 'Unknown error replacing file',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -171,7 +175,9 @@ export const deleteFile = async ({
|
|||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
error:
|
error:
|
||||||
error instanceof Error ? error.message : 'Unknown error deleting file',
|
error instanceof Error
|
||||||
|
? error.message
|
||||||
|
: 'Unknown error deleting file',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -205,7 +211,9 @@ export const listFiles = async ({
|
|||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
error:
|
error:
|
||||||
error instanceof Error ? error.message : 'Unknown error listing files',
|
error instanceof Error
|
||||||
|
? error.message
|
||||||
|
: 'Unknown error listing files',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -124,7 +124,9 @@ export const uploadFile = async ({
|
|||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
error:
|
error:
|
||||||
error instanceof Error ? error.message : 'Unknown error uploading file',
|
error instanceof Error
|
||||||
|
? error.message
|
||||||
|
: 'Unknown error uploading file',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -150,7 +152,9 @@ export const replaceFile = async ({
|
|||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
error:
|
error:
|
||||||
error instanceof Error ? error.message : 'Unknown error replacing file',
|
error instanceof Error
|
||||||
|
? error.message
|
||||||
|
: 'Unknown error replacing file',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -174,7 +178,9 @@ export const deleteFile = async ({
|
|||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
error:
|
error:
|
||||||
error instanceof Error ? error.message : 'Unknown error deleting file',
|
error instanceof Error
|
||||||
|
? error.message
|
||||||
|
: 'Unknown error deleting file',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -208,7 +214,9 @@ export const listFiles = async ({
|
|||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
error:
|
error:
|
||||||
error instanceof Error ? error.message : 'Unknown error listing files',
|
error instanceof Error
|
||||||
|
? error.message
|
||||||
|
: 'Unknown error listing files',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -72,7 +72,9 @@ export const useFileUpload = () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (!uploadResult.success) {
|
if (!uploadResult.success) {
|
||||||
throw new Error(uploadResult.error || `Failed to upload to ${bucket}`);
|
throw new Error(
|
||||||
|
uploadResult.error || `Failed to upload to ${bucket}`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return { success: true, data: uploadResult.data };
|
return { success: true, data: uploadResult.data };
|
||||||
|
@ -41,7 +41,10 @@ export const updateSession = async (
|
|||||||
const user = await supabase.auth.getUser();
|
const user = await supabase.auth.getUser();
|
||||||
|
|
||||||
// protected routes
|
// protected routes
|
||||||
if (request.nextUrl.pathname.startsWith('/reset-password') && user.error) {
|
if (
|
||||||
|
request.nextUrl.pathname.startsWith('/reset-password') &&
|
||||||
|
user.error
|
||||||
|
) {
|
||||||
return NextResponse.redirect(new URL('/sign-in', request.url));
|
return NextResponse.redirect(new URL('/sign-in', request.url));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user