From 42b07ea2da455e91d4019d2991c84021609de27d Mon Sep 17 00:00:00 2001 From: Gib Date: Mon, 9 Jun 2025 09:57:27 -0500 Subject: [PATCH] Move to tabs over spaces! --- .prettierrc | 3 +- eslint.config.js | 88 +-- next.config.js | 106 +-- postcss.config.js | 6 +- prettier.config.js | 2 +- scripts/next.config.build.js | 104 +-- scripts/next.config.default.js | 92 +-- sentry.server.config.ts | 10 +- src/app/(auth-pages)/auth/callback/route.ts | 62 +- src/app/(auth-pages)/auth/success/page.tsx | 48 +- src/app/(auth-pages)/forgot-password/page.tsx | 212 +++--- src/app/(auth-pages)/profile/page.tsx | 198 ++--- src/app/(auth-pages)/sign-in/page.tsx | 282 +++---- src/app/(auth-pages)/sign-up/page.tsx | 370 ++++----- src/app/(sentry)/api/sentry/example/route.ts | 16 +- src/app/global-error.tsx | 100 +-- src/app/layout.tsx | 714 +++++++++--------- src/app/page.tsx | 158 ++-- src/components/context/Auth.tsx | 322 ++++---- src/components/context/Theme.tsx | 90 +-- src/components/default/StatusMessage.tsx | 40 +- src/components/default/SubmitButton.tsx | 50 +- src/components/default/auth/SignInSignUp.tsx | 40 +- .../default/auth/SignInWithApple.tsx | 112 +-- .../default/auth/SignInWithMicrosoft.tsx | 100 +-- src/components/default/footer/index.tsx | 30 +- src/components/default/index.tsx | 4 +- .../navigation/auth/AvatarDropdown.tsx | 142 ++-- .../default/navigation/auth/index.tsx | 26 +- src/components/default/navigation/index.tsx | 54 +- .../default/profile/AvatarUpload.tsx | 186 ++--- .../default/profile/ProfileForm.tsx | 154 ++-- .../default/profile/ResetPasswordForm.tsx | 250 +++--- src/components/default/profile/SignOut.tsx | 44 +- src/components/default/sentry/TestSentry.tsx | 218 +++--- src/components/default/tutorial/CodeBlock.tsx | 92 +-- .../default/tutorial/FetchDataSteps.tsx | 92 +-- .../default/tutorial/TutorialStep.tsx | 48 +- src/components/ui/avatar.tsx | 66 +- src/components/ui/badge.tsx | 62 +- src/components/ui/button.tsx | 94 +-- src/components/ui/card.tsx | 130 ++-- src/components/ui/checkbox.tsx | 38 +- src/components/ui/dropdown-menu.tsx | 368 ++++----- src/components/ui/form.tsx | 214 +++--- src/components/ui/input.tsx | 26 +- src/components/ui/label.tsx | 24 +- src/components/ui/separator.tsx | 32 +- src/components/ui/sonner.tsx | 30 +- src/env.js | 100 +-- src/instrumentation-client.ts | 42 +- src/instrumentation.ts | 4 +- src/lib/actions/auth.ts | 228 +++--- src/lib/actions/index.ts | 4 +- src/lib/actions/public.ts | 124 +-- src/lib/actions/storage.ts | 396 +++++----- src/lib/hooks/auth.ts | 222 +++--- src/lib/hooks/index.ts | 4 +- src/lib/hooks/public.ts | 124 +-- src/lib/hooks/storage.ts | 402 +++++----- src/lib/hooks/useFileUpload.ts | 162 ++-- src/lib/utils.ts | 2 +- src/middleware.ts | 26 +- .../docker/volumes/functions/hello/index.ts | 6 +- .../docker/volumes/functions/main/index.ts | 144 ++-- src/utils/supabase/client.ts | 8 +- src/utils/supabase/middleware.ts | 90 +-- src/utils/supabase/server.ts | 46 +- src/utils/supabase/types.ts | 328 ++++---- src/utils/supabase/utils.ts | 6 +- 70 files changed, 4109 insertions(+), 4108 deletions(-) diff --git a/.prettierrc b/.prettierrc index da8128c..fd0bc16 100644 --- a/.prettierrc +++ b/.prettierrc @@ -1,5 +1,6 @@ { "singleQuote": true, "jsxSingleQuote": true, - "trailingComma": "all" + "trailingComma": "all", + "useTabs": true } diff --git a/eslint.config.js b/eslint.config.js index 7d84329..8d6c546 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -3,51 +3,51 @@ import tseslint from 'typescript-eslint'; import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended'; const compat = new FlatCompat({ - baseDirectory: import.meta.dirname, + baseDirectory: import.meta.dirname, }); export default tseslint.config( - { - ignores: ['.next'], - }, - ...compat.extends('next/core-web-vitals'), - { - files: ['**/*.ts', '**/*.tsx'], - extends: [ - ...tseslint.configs.recommended, - ...tseslint.configs.recommendedTypeChecked, - ...tseslint.configs.stylisticTypeChecked, - eslintPluginPrettierRecommended, - ], - rules: { - '@typescript-eslint/array-type': 'off', - '@typescript-eslint/consistent-type-definitions': 'off', - '@typescript-eslint/consistent-type-imports': [ - 'warn', - { prefer: 'type-imports', fixStyle: 'inline-type-imports' }, - ], - '@typescript-eslint/no-unused-vars': [ - 'warn', - { argsIgnorePattern: '^_' }, - ], - '@typescript-eslint/require-await': 'off', - '@typescript-eslint/no-misused-promises': [ - 'error', - { checksVoidReturn: { attributes: false } }, - ], - '@typescript-eslint/no-explicit-any': 'warn', - '@typescript-eslint/no-floating-promises': 'warn', - '@typescript-eslint/no-unsafe-argument': 'warn', - }, - }, - { - linterOptions: { - reportUnusedDisableDirectives: true, - }, - languageOptions: { - parserOptions: { - projectService: true, - }, - }, - }, + { + ignores: ['.next'], + }, + ...compat.extends('next/core-web-vitals'), + { + files: ['**/*.ts', '**/*.tsx'], + extends: [ + ...tseslint.configs.recommended, + ...tseslint.configs.recommendedTypeChecked, + ...tseslint.configs.stylisticTypeChecked, + eslintPluginPrettierRecommended, + ], + rules: { + '@typescript-eslint/array-type': 'off', + '@typescript-eslint/consistent-type-definitions': 'off', + '@typescript-eslint/consistent-type-imports': [ + 'warn', + { prefer: 'type-imports', fixStyle: 'inline-type-imports' }, + ], + '@typescript-eslint/no-unused-vars': [ + 'warn', + { argsIgnorePattern: '^_' }, + ], + '@typescript-eslint/require-await': 'off', + '@typescript-eslint/no-misused-promises': [ + 'error', + { checksVoidReturn: { attributes: false } }, + ], + '@typescript-eslint/no-explicit-any': 'warn', + '@typescript-eslint/no-floating-promises': 'warn', + '@typescript-eslint/no-unsafe-argument': 'warn', + }, + }, + { + linterOptions: { + reportUnusedDisableDirectives: true, + }, + languageOptions: { + parserOptions: { + projectService: true, + }, + }, + }, ); diff --git a/next.config.js b/next.config.js index 39f27bd..18b0525 100644 --- a/next.config.js +++ b/next.config.js @@ -6,62 +6,62 @@ import { withSentryConfig } from '@sentry/nextjs'; /** @type {import("next").NextConfig} */ const config = { - output: 'standalone', - images: { - remotePatterns: [ - { - protocol: 'https', - hostname: '*.gbrown.org', - }, - ], - }, - serverExternalPackages: ['require-in-the-middle'], - experimental: { - serverActions: { - bodySizeLimit: '10mb', - }, - }, - turbopack: { - rules: { - '*.svg': { - loaders: [ - { - loader: '@svgr/webpack', - options: { - icon: true, - }, - }, - ], - as: '*.js', - }, - }, - }, + output: 'standalone', + images: { + remotePatterns: [ + { + protocol: 'https', + hostname: '*.gbrown.org', + }, + ], + }, + serverExternalPackages: ['require-in-the-middle'], + experimental: { + serverActions: { + bodySizeLimit: '10mb', + }, + }, + turbopack: { + rules: { + '*.svg': { + loaders: [ + { + loader: '@svgr/webpack', + options: { + icon: true, + }, + }, + ], + as: '*.js', + }, + }, + }, }; const sentryConfig = { - // For all available options, see: - // https://www.npmjs.com/package/@sentry/webpack-plugin#options - org: 'gib', - project: 't3-supabase-template', - sentryUrl: process.env.NEXT_PUBLIC_SENTRY_URL, - authToken: process.env.SENTRY_AUTH_TOKEN, - // Only print logs for uploading source maps in CI - silent: !process.env.CI, - // For all available options, see: - // https://docs.sentry.io/platforms/javascript/guides/nextjs/manual-setup/ - // Upload a larger set of source maps for prettier stack traces (increases build time) - widenClientFileUpload: true, - // Route browser requests to Sentry through a Next.js rewrite to circumvent ad-blockers. - // This can increase your server load as well as your hosting bill. - // Note: Check that the configured route will not match with your Next.js middleware, otherwise reporting of client- - // side errors will fail. - tunnelRoute: '/monitoring', - // Automatically tree-shake Sentry logger statements to reduce bundle size - disableLogger: true, - // Capture React Component Names - reactComponentAnnotation: { - enabled: true, - }, + // For all available options, see: + // https://www.npmjs.com/package/@sentry/webpack-plugin#options + org: 'gib', + project: 't3-supabase-template', + sentryUrl: process.env.NEXT_PUBLIC_SENTRY_URL, + authToken: process.env.SENTRY_AUTH_TOKEN, + // Only print logs for uploading source maps in CI + silent: !process.env.CI, + // For all available options, see: + // https://docs.sentry.io/platforms/javascript/guides/nextjs/manual-setup/ + // Upload a larger set of source maps for prettier stack traces (increases build time) + widenClientFileUpload: true, + // Route browser requests to Sentry through a Next.js rewrite to circumvent ad-blockers. + // This can increase your server load as well as your hosting bill. + // Note: Check that the configured route will not match with your Next.js middleware, otherwise reporting of client- + // side errors will fail. + tunnelRoute: '/monitoring', + // Automatically tree-shake Sentry logger statements to reduce bundle size + disableLogger: true, + // Capture React Component Names + reactComponentAnnotation: { + enabled: true, + }, }; export default withSentryConfig(config, sentryConfig); diff --git a/postcss.config.js b/postcss.config.js index a34a3d5..1970487 100644 --- a/postcss.config.js +++ b/postcss.config.js @@ -1,5 +1,5 @@ export default { - plugins: { - '@tailwindcss/postcss': {}, - }, + plugins: { + '@tailwindcss/postcss': {}, + }, }; diff --git a/prettier.config.js b/prettier.config.js index 09f0482..4b043fd 100644 --- a/prettier.config.js +++ b/prettier.config.js @@ -1,4 +1,4 @@ /** @type {import('prettier').Config & import('prettier-plugin-tailwindcss').PluginOptions} */ export default { - plugins: ['prettier-plugin-tailwindcss'], + plugins: ['prettier-plugin-tailwindcss'], }; diff --git a/scripts/next.config.build.js b/scripts/next.config.build.js index 6bcbd94..39d14aa 100644 --- a/scripts/next.config.build.js +++ b/scripts/next.config.build.js @@ -7,61 +7,61 @@ import { withSentryConfig } from '@sentry/nextjs'; /** @type {import("next").NextConfig} */ const config = { - output: 'standalone', - images: { - remotePatterns: [ - { - protocol: 'https', - hostname: '*.gbrown.org', - }, - ], - }, - serverExternalPackages: ['require-in-the-middle'], - experimental: { - serverActions: { - bodySizeLimit: '10mb', - }, - }, - typescript: { - ignoreBuildErrors: true, - }, - eslint: { - ignoreDuringBuilds: true, - }, - //turbopack: { - //rules: { - //'*.svg': { - //loaders: ['@svgr/webpack'], - //as: '*.js', - //}, - //}, - //}, + output: 'standalone', + images: { + remotePatterns: [ + { + protocol: 'https', + hostname: '*.gbrown.org', + }, + ], + }, + serverExternalPackages: ['require-in-the-middle'], + experimental: { + serverActions: { + bodySizeLimit: '10mb', + }, + }, + typescript: { + ignoreBuildErrors: true, + }, + eslint: { + ignoreDuringBuilds: true, + }, + //turbopack: { + //rules: { + //'*.svg': { + //loaders: ['@svgr/webpack'], + //as: '*.js', + //}, + //}, + //}, }; const sentryConfig = { - // For all available options, see: - // https://www.npmjs.com/package/@sentry/webpack-plugin#options - org: 'gib', - project: 't3-supabase-template', - sentryUrl: process.env.SENTRY_URL, - authToken: process.env.SENTRY_AUTH_TOKEN, - // Only print logs for uploading source maps in CI - silent: !process.env.CI, - // For all available options, see: - // https://docs.sentry.io/platforms/javascript/guides/nextjs/manual-setup/ - // Upload a larger set of source maps for prettier stack traces (increases build time) - widenClientFileUpload: true, - // Route browser requests to Sentry through a Next.js rewrite to circumvent ad-blockers. - // This can increase your server load as well as your hosting bill. - // Note: Check that the configured route will not match with your Next.js middleware, otherwise reporting of client- - // side errors will fail. - tunnelRoute: '/monitoring', - // Automatically tree-shake Sentry logger statements to reduce bundle size - disableLogger: true, - // Capture React Component Names - reactComponentAnnotation: { - enabled: true, - }, + // For all available options, see: + // https://www.npmjs.com/package/@sentry/webpack-plugin#options + org: 'gib', + project: 't3-supabase-template', + sentryUrl: process.env.SENTRY_URL, + authToken: process.env.SENTRY_AUTH_TOKEN, + // Only print logs for uploading source maps in CI + silent: !process.env.CI, + // For all available options, see: + // https://docs.sentry.io/platforms/javascript/guides/nextjs/manual-setup/ + // Upload a larger set of source maps for prettier stack traces (increases build time) + widenClientFileUpload: true, + // Route browser requests to Sentry through a Next.js rewrite to circumvent ad-blockers. + // This can increase your server load as well as your hosting bill. + // Note: Check that the configured route will not match with your Next.js middleware, otherwise reporting of client- + // side errors will fail. + tunnelRoute: '/monitoring', + // Automatically tree-shake Sentry logger statements to reduce bundle size + disableLogger: true, + // Capture React Component Names + reactComponentAnnotation: { + enabled: true, + }, }; export default withSentryConfig(config, sentryConfig); diff --git a/scripts/next.config.default.js b/scripts/next.config.default.js index d1cec6a..dd1537d 100644 --- a/scripts/next.config.default.js +++ b/scripts/next.config.default.js @@ -7,55 +7,55 @@ import { withSentryConfig } from '@sentry/nextjs'; /** @type {import("next").NextConfig} */ const config = { - output: 'standalone', - images: { - remotePatterns: [ - { - protocol: 'https', - hostname: '*.gbrown.org', - }, - ], - }, - serverExternalPackages: ['require-in-the-middle'], - experimental: { - serverActions: { - bodySizeLimit: '10mb', - }, - }, - //turbopack: { - //rules: { - //'*.svg': { - //loaders: ['@svgr/webpack'], - //as: '*.js', - //}, - //}, - //}, + output: 'standalone', + images: { + remotePatterns: [ + { + protocol: 'https', + hostname: '*.gbrown.org', + }, + ], + }, + serverExternalPackages: ['require-in-the-middle'], + experimental: { + serverActions: { + bodySizeLimit: '10mb', + }, + }, + //turbopack: { + //rules: { + //'*.svg': { + //loaders: ['@svgr/webpack'], + //as: '*.js', + //}, + //}, + //}, }; const sentryConfig = { - // For all available options, see: - // https://www.npmjs.com/package/@sentry/webpack-plugin#options - org: 'gib', - project: 't3-supabase-template', - sentryUrl: process.env.SENTRY_URL, - authToken: process.env.SENTRY_AUTH_TOKEN, - // Only print logs for uploading source maps in CI - silent: !process.env.CI, - // For all available options, see: - // https://docs.sentry.io/platforms/javascript/guides/nextjs/manual-setup/ - // Upload a larger set of source maps for prettier stack traces (increases build time) - widenClientFileUpload: true, - // Route browser requests to Sentry through a Next.js rewrite to circumvent ad-blockers. - // This can increase your server load as well as your hosting bill. - // Note: Check that the configured route will not match with your Next.js middleware, otherwise reporting of client- - // side errors will fail. - tunnelRoute: '/monitoring', - // Automatically tree-shake Sentry logger statements to reduce bundle size - disableLogger: true, - // Capture React Component Names - reactComponentAnnotation: { - enabled: true, - }, + // For all available options, see: + // https://www.npmjs.com/package/@sentry/webpack-plugin#options + org: 'gib', + project: 't3-supabase-template', + sentryUrl: process.env.SENTRY_URL, + authToken: process.env.SENTRY_AUTH_TOKEN, + // Only print logs for uploading source maps in CI + silent: !process.env.CI, + // For all available options, see: + // https://docs.sentry.io/platforms/javascript/guides/nextjs/manual-setup/ + // Upload a larger set of source maps for prettier stack traces (increases build time) + widenClientFileUpload: true, + // Route browser requests to Sentry through a Next.js rewrite to circumvent ad-blockers. + // This can increase your server load as well as your hosting bill. + // Note: Check that the configured route will not match with your Next.js middleware, otherwise reporting of client- + // side errors will fail. + tunnelRoute: '/monitoring', + // Automatically tree-shake Sentry logger statements to reduce bundle size + disableLogger: true, + // Capture React Component Names + reactComponentAnnotation: { + enabled: true, + }, }; export default withSentryConfig(config, sentryConfig); diff --git a/sentry.server.config.ts b/sentry.server.config.ts index 86a556b..098e696 100644 --- a/sentry.server.config.ts +++ b/sentry.server.config.ts @@ -5,11 +5,11 @@ import * as Sentry from '@sentry/nextjs'; Sentry.init({ - dsn: 'https://0468176d5291bc2b914261147bfef117@sentry.gbrown.org/6', + dsn: 'https://0468176d5291bc2b914261147bfef117@sentry.gbrown.org/6', - // Define how likely traces are sampled. Adjust this value in production, or use tracesSampler for greater control. - tracesSampleRate: 1, + // Define how likely traces are sampled. Adjust this value in production, or use tracesSampler for greater control. + tracesSampleRate: 1, - // Setting this option to true will print useful information to the console while you're setting up Sentry. - debug: false, + // Setting this option to true will print useful information to the console while you're setting up Sentry. + debug: false, }); diff --git a/src/app/(auth-pages)/auth/callback/route.ts b/src/app/(auth-pages)/auth/callback/route.ts index 8d1d901..81f50fa 100644 --- a/src/app/(auth-pages)/auth/callback/route.ts +++ b/src/app/(auth-pages)/auth/callback/route.ts @@ -6,38 +6,38 @@ import { type NextRequest } from 'next/server'; import { redirect } from 'next/navigation'; export const GET = async (request: NextRequest) => { - const { searchParams, origin } = new URL(request.url); - const code = searchParams.get('code'); - const token_hash = searchParams.get('token'); - const type = searchParams.get('type') as EmailOtpType | null; - const redirectTo = searchParams.get('redirect_to') ?? '/'; - const supabase = await createServerClient(); + const { searchParams, origin } = new URL(request.url); + const code = searchParams.get('code'); + const token_hash = searchParams.get('token'); + const type = searchParams.get('type') as EmailOtpType | null; + const redirectTo = searchParams.get('redirect_to') ?? '/'; + const supabase = await createServerClient(); - if (code) { - const { error } = await supabase.auth.exchangeCodeForSession(code); - if (error) { - console.error('OAuth error:', error); - return redirect(`/sign-in?error=${encodeURIComponent(error.message)}`); - } - return redirect(redirectTo); - } + if (code) { + const { error } = await supabase.auth.exchangeCodeForSession(code); + if (error) { + console.error('OAuth error:', error); + return redirect(`/sign-in?error=${encodeURIComponent(error.message)}`); + } + return redirect(redirectTo); + } - if (token_hash && type) { - const { error } = await supabase.auth.verifyOtp({ - type, - token_hash, - }); - if (!error) { - if (type === 'signup' || type === 'magiclink' || type === 'email') - return redirect('/'); - if (type === 'recovery' || type === 'email_change') - return redirect('/profile'); - if (type === 'invite') return redirect('/sign-up'); - } - return redirect( - `/?error=${encodeURIComponent(error?.message || 'Unknown error')}`, - ); - } + if (token_hash && type) { + const { error } = await supabase.auth.verifyOtp({ + type, + token_hash, + }); + if (!error) { + if (type === 'signup' || type === 'magiclink' || type === 'email') + return redirect('/'); + if (type === 'recovery' || type === 'email_change') + return redirect('/profile'); + if (type === 'invite') return redirect('/sign-up'); + } + return redirect( + `/?error=${encodeURIComponent(error?.message || 'Unknown error')}`, + ); + } - return redirect('/'); + return redirect('/'); }; diff --git a/src/app/(auth-pages)/auth/success/page.tsx b/src/app/(auth-pages)/auth/success/page.tsx index e6660ab..673b7e4 100644 --- a/src/app/(auth-pages)/auth/success/page.tsx +++ b/src/app/(auth-pages)/auth/success/page.tsx @@ -6,34 +6,34 @@ import { useEffect } from 'react'; import { Loader2 } from 'lucide-react'; const AuthSuccessPage = () => { - const { refreshUserData } = useAuth(); - const router = useRouter(); + const { refreshUserData } = useAuth(); + const router = useRouter(); - useEffect(() => { - const handleAuthSuccess = async () => { - // Refresh the auth context to pick up the new session - await refreshUserData(); + useEffect(() => { + const handleAuthSuccess = async () => { + // Refresh the auth context to pick up the new session + await refreshUserData(); - // Small delay to ensure state is updated - setTimeout(() => { - router.push('/'); - }, 100); - }; + // Small delay to ensure state is updated + setTimeout(() => { + router.push('/'); + }, 100); + }; - handleAuthSuccess().catch((error) => { - console.error(`Error: ${error instanceof Error ? error.message : error}`); - }); - }, [refreshUserData, router]); + handleAuthSuccess().catch((error) => { + console.error(`Error: ${error instanceof Error ? error.message : error}`); + }); + }, [refreshUserData, router]); - // Show loading while processing - return ( -
-
- -

Completing sign in...

-
-
- ); + // Show loading while processing + return ( +
+
+ +

Completing sign in...

+
+
+ ); }; export default AuthSuccessPage; diff --git a/src/app/(auth-pages)/forgot-password/page.tsx b/src/app/(auth-pages)/forgot-password/page.tsx index aae4fd2..dbc55f0 100644 --- a/src/app/(auth-pages)/forgot-password/page.tsx +++ b/src/app/(auth-pages)/forgot-password/page.tsx @@ -3,18 +3,18 @@ import { z } from 'zod'; import { zodResolver } from '@hookform/resolvers/zod'; import { useForm } from 'react-hook-form'; import { - Card, - CardContent, - CardDescription, - CardHeader, - CardTitle, - Form, - FormControl, - FormField, - FormItem, - FormLabel, - FormMessage, - Input, + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, + Input, } from '@/components/ui'; import Link from 'next/link'; import { forgotPassword } from '@/lib/actions'; @@ -24,106 +24,106 @@ import { useEffect, useState } from 'react'; import { StatusMessage, SubmitButton } from '@/components/default'; const formSchema = z.object({ - email: z.string().email({ - message: 'Please enter a valid email address.', - }), + email: z.string().email({ + message: 'Please enter a valid email address.', + }), }); const ForgotPassword = () => { - const router = useRouter(); - const { isAuthenticated, isLoading, refreshUserData } = useAuth(); - const [statusMessage, setStatusMessage] = useState(''); + const router = useRouter(); + const { isAuthenticated, isLoading, refreshUserData } = useAuth(); + const [statusMessage, setStatusMessage] = useState(''); - const form = useForm>({ - resolver: zodResolver(formSchema), - defaultValues: { - email: '', - }, - }); + const form = useForm>({ + resolver: zodResolver(formSchema), + defaultValues: { + email: '', + }, + }); - // Redirect if already authenticated - useEffect(() => { - if (isAuthenticated) { - router.push('/'); - } - }, [isAuthenticated, router]); + // Redirect if already authenticated + useEffect(() => { + if (isAuthenticated) { + router.push('/'); + } + }, [isAuthenticated, router]); - const handleForgotPassword = async (values: z.infer) => { - try { - setStatusMessage(''); - const formData = new FormData(); - formData.append('email', values.email); - const result = await forgotPassword(formData); - if (result?.success) { - await refreshUserData(); - setStatusMessage( - result?.data ?? 'Check your email for a link to reset your password.', - ); - form.reset(); - router.push(''); - } else { - setStatusMessage(`Error: ${result.error}`); - } - } catch (error) { - setStatusMessage( - `Error: ${error instanceof Error ? error.message : 'Could not sign in!'}`, - ); - } - }; + const handleForgotPassword = async (values: z.infer) => { + try { + setStatusMessage(''); + const formData = new FormData(); + formData.append('email', values.email); + const result = await forgotPassword(formData); + if (result?.success) { + await refreshUserData(); + setStatusMessage( + result?.data ?? 'Check your email for a link to reset your password.', + ); + form.reset(); + router.push(''); + } else { + setStatusMessage(`Error: ${result.error}`); + } + } catch (error) { + setStatusMessage( + `Error: ${error instanceof Error ? error.message : 'Could not sign in!'}`, + ); + } + }; - return ( - - - Reset Password - - Don't have an account?{' '} - - Sign up - - - - -
- - ( - - Email - - - - - - )} - /> - - Reset Password - - {statusMessage && - (statusMessage.includes('Error') || - statusMessage.includes('error') || - statusMessage.includes('failed') || - statusMessage.includes('invalid') ? ( - - ) : ( - - ))} - - -
-
- ); + return ( + + + Reset Password + + Don't have an account?{' '} + + Sign up + + + + +
+ + ( + + Email + + + + + + )} + /> + + Reset Password + + {statusMessage && + (statusMessage.includes('Error') || + statusMessage.includes('error') || + statusMessage.includes('failed') || + statusMessage.includes('invalid') ? ( + + ) : ( + + ))} + + +
+
+ ); }; export default ForgotPassword; diff --git a/src/app/(auth-pages)/profile/page.tsx b/src/app/(auth-pages)/profile/page.tsx index 9b5ae2f..7d41c07 100644 --- a/src/app/(auth-pages)/profile/page.tsx +++ b/src/app/(auth-pages)/profile/page.tsx @@ -3,17 +3,17 @@ import { useAuth } from '@/components/context'; import { useRouter } from 'next/navigation'; import { useEffect } from 'react'; import { - AvatarUpload, - ProfileForm, - ResetPasswordForm, - SignOut, + AvatarUpload, + ProfileForm, + ResetPasswordForm, + SignOut, } from '@/components/default/profile'; import { - Card, - CardHeader, - CardTitle, - CardDescription, - Separator, + Card, + CardHeader, + CardTitle, + CardDescription, + Separator, } from '@/components/ui'; import { Loader2 } from 'lucide-react'; import { resetPassword } from '@/lib/actions'; @@ -21,103 +21,103 @@ import { toast } from 'sonner'; import { type Result } from '@/lib/actions'; const ProfilePage = () => { - const { - profile, - isLoading, - isAuthenticated, - updateProfile, - refreshUserData, - } = useAuth(); - const router = useRouter(); + const { + profile, + isLoading, + isAuthenticated, + updateProfile, + refreshUserData, + } = useAuth(); + const router = useRouter(); - useEffect(() => { - if (!isLoading && !isAuthenticated) { - router.push('/sign-in'); - } - }, [isLoading, isAuthenticated, router]); + useEffect(() => { + if (!isLoading && !isAuthenticated) { + router.push('/sign-in'); + } + }, [isLoading, isAuthenticated, router]); - const handleAvatarUploaded = async (path: string) => { - await updateProfile({ avatar_url: path }); - await refreshUserData(); - }; + const handleAvatarUploaded = async (path: string) => { + await updateProfile({ avatar_url: path }); + await refreshUserData(); + }; - const handleProfileSubmit = async (values: { - full_name: string; - email: string; - }) => { - try { - await updateProfile({ - full_name: values.full_name, - email: values.email, - }); - } catch { - toast.error('Error updating profile!: '); - } - }; + const handleProfileSubmit = async (values: { + full_name: string; + email: string; + }) => { + try { + await updateProfile({ + full_name: values.full_name, + email: values.email, + }); + } catch { + toast.error('Error updating profile!: '); + } + }; - const handleResetPasswordSubmit = async ( - formData: FormData, - ): Promise> => { - try { - const result = await resetPassword(formData); - if (!result.success) { - toast.error(`Error resetting password: ${result.error}`); - return { success: false, error: result.error }; - } - return { success: true, data: null }; - } catch (error) { - toast.error( - `Error resetting password!: ${(error as string) ?? 'Unknown error'}`, - ); - return { success: false, error: 'Unknown error' }; - } - }; + const handleResetPasswordSubmit = async ( + formData: FormData, + ): Promise> => { + try { + const result = await resetPassword(formData); + if (!result.success) { + toast.error(`Error resetting password: ${result.error}`); + return { success: false, error: result.error }; + } + return { success: true, data: null }; + } catch (error) { + toast.error( + `Error resetting password!: ${(error as string) ?? 'Unknown error'}`, + ); + return { success: false, error: 'Unknown error' }; + } + }; - // Show loading state while checking authentication - if (isLoading) { - return ( -
- -
- ); - } + // Show loading state while checking authentication + if (isLoading) { + return ( +
+ +
+ ); + } - // If not authenticated and not loading, this will show briefly before redirect - if (!isAuthenticated) { - return ( -
-

Unauthorized - Redirecting...

-
- ); - } + // If not authenticated and not loading, this will show briefly before redirect + if (!isAuthenticated) { + return ( +
+

Unauthorized - Redirecting...

+
+ ); + } - return ( -
- - - Your Profile - - Manage your personal information and how it appears to others - - - {isLoading && !profile ? ( -
- -
- ) : ( -
- - - - - - - -
- )} -
-
- ); + return ( +
+ + + Your Profile + + Manage your personal information and how it appears to others + + + {isLoading && !profile ? ( +
+ +
+ ) : ( +
+ + + + + + + +
+ )} +
+
+ ); }; export default ProfilePage; diff --git a/src/app/(auth-pages)/sign-in/page.tsx b/src/app/(auth-pages)/sign-in/page.tsx index 4baa919..f22cef5 100644 --- a/src/app/(auth-pages)/sign-in/page.tsx +++ b/src/app/(auth-pages)/sign-in/page.tsx @@ -4,18 +4,18 @@ import { z } from 'zod'; import { zodResolver } from '@hookform/resolvers/zod'; import { useForm } from 'react-hook-form'; import { - Card, - CardContent, - CardDescription, - CardHeader, - CardTitle, - Form, - FormControl, - FormField, - FormItem, - FormLabel, - FormMessage, - Input, + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, + Input, } from '@/components/ui'; import Link from 'next/link'; import { signIn } from '@/lib/actions'; @@ -28,144 +28,144 @@ import { SignInWithMicrosoft } from '@/components/default/auth/SignInWithMicroso import { SignInWithApple } from '@/components/default/auth/SignInWithApple'; const formSchema = z.object({ - email: z.string().email({ - message: 'Please enter a valid email address.', - }), - password: z.string().min(8, { - message: 'Password must be at least 8 characters.', - }), + email: z.string().email({ + message: 'Please enter a valid email address.', + }), + password: z.string().min(8, { + message: 'Password must be at least 8 characters.', + }), }); const Login = () => { - const router = useRouter(); - const { isAuthenticated, isLoading, refreshUserData } = useAuth(); - const [statusMessage, setStatusMessage] = useState(''); + const router = useRouter(); + const { isAuthenticated, isLoading, refreshUserData } = useAuth(); + const [statusMessage, setStatusMessage] = useState(''); - const form = useForm>({ - resolver: zodResolver(formSchema), - defaultValues: { - email: '', - password: '', - }, - }); + const form = useForm>({ + resolver: zodResolver(formSchema), + defaultValues: { + email: '', + password: '', + }, + }); - // Redirect if already authenticated - useEffect(() => { - if (isAuthenticated) { - router.push('/'); - } - }, [isAuthenticated, router]); + // Redirect if already authenticated + useEffect(() => { + if (isAuthenticated) { + router.push('/'); + } + }, [isAuthenticated, router]); - const handleSignIn = async (values: z.infer) => { - try { - setStatusMessage(''); - const formData = new FormData(); - formData.append('email', values.email); - formData.append('password', values.password); - const result = await signIn(formData); - if (result?.success) { - await refreshUserData(); - form.reset(); - router.push(''); - } else { - setStatusMessage(`Error: ${result.error}`); - } - } catch (error) { - setStatusMessage( - `Error: ${error instanceof Error ? error.message : 'Could not sign in!'}`, - ); - } - }; + const handleSignIn = async (values: z.infer) => { + try { + setStatusMessage(''); + const formData = new FormData(); + formData.append('email', values.email); + formData.append('password', values.password); + const result = await signIn(formData); + if (result?.success) { + await refreshUserData(); + form.reset(); + router.push(''); + } else { + setStatusMessage(`Error: ${result.error}`); + } + } catch (error) { + setStatusMessage( + `Error: ${error instanceof Error ? error.message : 'Could not sign in!'}`, + ); + } + }; - return ( - - - Sign In - - Don't have an account?{' '} - - Sign up - - - - -
- - ( - - Email - - - - - - )} - /> + return ( + + + Sign In + + Don't have an account?{' '} + + Sign up + + + + + + + ( + + Email + + + + + + )} + /> - ( - -
- Password - - Forgot Password? - -
- - - - -
- )} - /> - {statusMessage && - (statusMessage.includes('Error') || - statusMessage.includes('error') || - statusMessage.includes('failed') || - statusMessage.includes('invalid') ? ( - - ) : ( - - ))} - - Sign in - - - + ( + +
+ Password + + Forgot Password? + +
+ + + + +
+ )} + /> + {statusMessage && + (statusMessage.includes('Error') || + statusMessage.includes('error') || + statusMessage.includes('failed') || + statusMessage.includes('invalid') ? ( + + ) : ( + + ))} + + Sign in + + + -
- - or - -
- - -
-
- ); +
+ + or + +
+ + +
+
+ ); }; export default Login; diff --git a/src/app/(auth-pages)/sign-up/page.tsx b/src/app/(auth-pages)/sign-up/page.tsx index 6b4af75..b39ed87 100644 --- a/src/app/(auth-pages)/sign-up/page.tsx +++ b/src/app/(auth-pages)/sign-up/page.tsx @@ -9,202 +9,202 @@ import { StatusMessage, SubmitButton } from '@/components/default'; import { useRouter } from 'next/navigation'; import { useAuth } from '@/components/context'; import { - Card, - CardContent, - CardDescription, - CardHeader, - CardTitle, - Form, - FormControl, - FormField, - FormItem, - FormLabel, - FormMessage, - Input, - Separator, + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, + Input, + Separator, } from '@/components/ui'; import { useEffect, useState } from 'react'; import { - SignInWithApple, - SignInWithMicrosoft + SignInWithApple, + SignInWithMicrosoft, } from '@/components/default/auth'; const formSchema = z - .object({ - name: z.string().min(2, { - message: 'Name must be at least 2 characters.', - }), - email: z.string().email({ - message: 'Please enter a valid email address.', - }), - password: z.string().min(8, { - message: 'Password must be at least 8 characters.', - }), - confirmPassword: z.string().min(8, { - message: 'Password must be at least 8 characters.', - }), - }) - .refine((data) => data.password === data.confirmPassword, { - message: 'Passwords do not match!', - path: ['confirmPassword'], - }); + .object({ + name: z.string().min(2, { + message: 'Name must be at least 2 characters.', + }), + email: z.string().email({ + message: 'Please enter a valid email address.', + }), + password: z.string().min(8, { + message: 'Password must be at least 8 characters.', + }), + confirmPassword: z.string().min(8, { + message: 'Password must be at least 8 characters.', + }), + }) + .refine((data) => data.password === data.confirmPassword, { + message: 'Passwords do not match!', + path: ['confirmPassword'], + }); const SignUp = () => { - const router = useRouter(); - const { isAuthenticated, isLoading, refreshUserData } = useAuth(); - const [statusMessage, setStatusMessage] = useState(''); + const router = useRouter(); + const { isAuthenticated, isLoading, refreshUserData } = useAuth(); + const [statusMessage, setStatusMessage] = useState(''); - const form = useForm>({ - resolver: zodResolver(formSchema), - defaultValues: { - name: '', - email: '', - password: '', - confirmPassword: '', - }, - mode: 'onChange', - }); + const form = useForm>({ + resolver: zodResolver(formSchema), + defaultValues: { + name: '', + email: '', + password: '', + confirmPassword: '', + }, + mode: 'onChange', + }); - // Redirect if already authenticated - useEffect(() => { - if (isAuthenticated) { - router.push('/'); - } - }, [isAuthenticated, router]); + // Redirect if already authenticated + useEffect(() => { + if (isAuthenticated) { + router.push('/'); + } + }, [isAuthenticated, router]); - const handleSignUp = async (values: z.infer) => { - try { - setStatusMessage(''); - const formData = new FormData(); - formData.append('name', values.name); - formData.append('email', values.email); - formData.append('password', values.password); - const result = await signUp(formData); - if (result?.success) { - await refreshUserData(); - setStatusMessage( - result.data ?? - 'Thanks for signing up! Please check your email for a verification link.', - ); - form.reset(); - router.push(''); - } else { - setStatusMessage(`Error: ${result.error}`); - } - } catch (error) { - setStatusMessage( - `Error: ${error instanceof Error ? error.message : 'Could not sign in!'}`, - ); - } - }; + const handleSignUp = async (values: z.infer) => { + try { + setStatusMessage(''); + const formData = new FormData(); + formData.append('name', values.name); + formData.append('email', values.email); + formData.append('password', values.password); + const result = await signUp(formData); + if (result?.success) { + await refreshUserData(); + setStatusMessage( + result.data ?? + 'Thanks for signing up! Please check your email for a verification link.', + ); + form.reset(); + router.push(''); + } else { + setStatusMessage(`Error: ${result.error}`); + } + } catch (error) { + setStatusMessage( + `Error: ${error instanceof Error ? error.message : 'Could not sign in!'}`, + ); + } + }; - return ( - - - Sign Up - - Already have an account?{' '} - - Sign in - - - - -
- - ( - - Name - - - - - )} - /> - ( - - Email - - - - - - )} - /> - ( - - Password - - - - - - )} - /> - ( - - Confirm Password - - - - - - )} - /> - {statusMessage && - (statusMessage.includes('Error') || - statusMessage.includes('error') || - statusMessage.includes('failed') || - statusMessage.includes('invalid') ? ( - - ) : ( - - ))} - - Sign Up - - - -
- - or - -
- - -
-
- ); + return ( + + + Sign Up + + Already have an account?{' '} + + Sign in + + + + +
+ + ( + + Name + + + + + )} + /> + ( + + Email + + + + + + )} + /> + ( + + Password + + + + + + )} + /> + ( + + Confirm Password + + + + + + )} + /> + {statusMessage && + (statusMessage.includes('Error') || + statusMessage.includes('error') || + statusMessage.includes('failed') || + statusMessage.includes('invalid') ? ( + + ) : ( + + ))} + + Sign Up + + + +
+ + or + +
+ + +
+
+ ); }; export default SignUp; diff --git a/src/app/(sentry)/api/sentry/example/route.ts b/src/app/(sentry)/api/sentry/example/route.ts index d9a4a24..797a67e 100644 --- a/src/app/(sentry)/api/sentry/example/route.ts +++ b/src/app/(sentry)/api/sentry/example/route.ts @@ -2,15 +2,15 @@ import { NextResponse } from 'next/server'; export const dynamic = 'force-dynamic'; class SentryExampleAPIError extends Error { - constructor(message: string | undefined) { - super(message); - this.name = 'SentryExampleAPIError'; - } + constructor(message: string | undefined) { + super(message); + this.name = 'SentryExampleAPIError'; + } } // A faulty API route to test Sentry's error monitoring export function GET() { - throw new SentryExampleAPIError( - 'This error is raised on the backend called by the example page.', - ); - return NextResponse.json({ data: 'Testing Sentry Error...' }); + throw new SentryExampleAPIError( + 'This error is raised on the backend called by the example page.', + ); + return NextResponse.json({ data: 'Testing Sentry Error...' }); } diff --git a/src/app/global-error.tsx b/src/app/global-error.tsx index 3c05a61..1718dca 100644 --- a/src/app/global-error.tsx +++ b/src/app/global-error.tsx @@ -12,68 +12,68 @@ import { useEffect } from 'react'; import { Geist } from 'next/font/google'; const geist = Geist({ - subsets: ['latin'], - variable: '--font-geist-sans', + subsets: ['latin'], + variable: '--font-geist-sans', }); type GlobalErrorProps = { - error: Error & { digest?: string }; - reset?: () => void; + error: Error & { digest?: string }; + reset?: () => void; }; const GlobalError = ({ error, reset = undefined }: GlobalErrorProps) => { - useEffect(() => { - Sentry.captureException(error); - }, [error]); + useEffect(() => { + Sentry.captureException(error); + }, [error]); - return ( - - - - -
-
- -
+ + + +
+
+ +
- - {reset !== undefined && ( - - )} -
-
-
-
- -
-
- - - ); + > + + {reset !== undefined && ( + + )} +
+
+
+
+ +
+
+ + + ); - return ( - - - {/* `NextError` is the default Next.js error page component. Its type + return ( + + + {/* `NextError` is the default Next.js error page component. Its type definition requires a `statusCode` prop. However, since the App Router does not expose status codes for errors, we simply pass 0 to render a generic error message. */} - - {reset !== undefined && ( - - )} - - - ); + + {reset !== undefined && ( + + )} + + + ); }; export default GlobalError; diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 79cfb93..dbf213f 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -9,370 +9,370 @@ import { Toaster } from '@/components/ui'; import * as Sentry from '@sentry/nextjs'; export const generateMetadata = (): Metadata => { - return { - title: { - template: '%s | T3 Template', - default: 'T3 Template with Supabase', - }, - description: 'Created by Gib with T3!', - applicationName: 'T3 Template', - keywords: - 'T3 Template, Next.js, Supabase, Tailwind, TypeScript, React, T3, Gib, Theo', - authors: [{ name: 'Gib', url: 'https://gbrown.org' }], - creator: 'Gib Brown', - publisher: 'Gib Brown', - formatDetection: { - email: false, - address: false, - telephone: false, - }, - robots: { - index: true, - follow: true, - nocache: false, - googleBot: { - index: true, - follow: true, - noimageindex: false, - 'max-video-preview': -1, - 'max-image-preview': 'large', - 'max-snippet': -1, - }, - }, - icons: { - icon: [ - { 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.png', type: 'image/png', sizes: '96x96' }, - { - url: '/favicon.ico', - type: 'image/x-icon', - sizes: 'any', - media: '(prefers-color-scheme: dark)', - }, - { - url: '/favicon-16x16.png', - type: 'image/png', - sizes: '16x16', - media: '(prefers-color-scheme: dark)', - }, - { - url: '/favicon-32x32.png', - type: 'image/png', - sizes: '32x32', - media: '(prefers-color-scheme: dark)', - }, - { - url: '/favicon-96x96.png', - type: 'image/png', - sizes: '96x96', - media: '(prefers-color-scheme: dark)', - }, + return { + title: { + template: '%s | T3 Template', + default: 'T3 Template with Supabase', + }, + description: 'Created by Gib with T3!', + applicationName: 'T3 Template', + keywords: + 'T3 Template, Next.js, Supabase, Tailwind, TypeScript, React, T3, Gib, Theo', + authors: [{ name: 'Gib', url: 'https://gbrown.org' }], + creator: 'Gib Brown', + publisher: 'Gib Brown', + formatDetection: { + email: false, + address: false, + telephone: false, + }, + robots: { + index: true, + follow: true, + nocache: false, + googleBot: { + index: true, + follow: true, + noimageindex: false, + 'max-video-preview': -1, + 'max-image-preview': 'large', + 'max-snippet': -1, + }, + }, + icons: { + icon: [ + { 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.png', type: 'image/png', sizes: '96x96' }, + { + url: '/favicon.ico', + type: 'image/x-icon', + sizes: 'any', + media: '(prefers-color-scheme: dark)', + }, + { + url: '/favicon-16x16.png', + type: 'image/png', + sizes: '16x16', + media: '(prefers-color-scheme: dark)', + }, + { + url: '/favicon-32x32.png', + type: 'image/png', + sizes: '32x32', + media: '(prefers-color-scheme: dark)', + }, + { + url: '/favicon-96x96.png', + type: 'image/png', + sizes: '96x96', + 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-72x72.png', type: 'image/png', sizes: '72x72' }, - { url: '/appicon/icon-96x96.png', type: 'image/png', sizes: '96x96' }, - { - url: '/appicon/icon-144x144.png', - type: 'image/png', - sizes: '144x144', - }, - { url: '/appicon/icon.png', type: 'image/png', sizes: '192x192' }, - { - url: '/appicon/icon-36x36.png', - type: 'image/png', - sizes: '36x36', - media: '(prefers-color-scheme: dark)', - }, - { - url: '/appicon/icon-48x48.png', - type: 'image/png', - sizes: '48x48', - media: '(prefers-color-scheme: dark)', - }, - { - url: '/appicon/icon-72x72.png', - type: 'image/png', - sizes: '72x72', - media: '(prefers-color-scheme: dark)', - }, - { - url: '/appicon/icon-96x96.png', - type: 'image/png', - sizes: '96x96', - media: '(prefers-color-scheme: dark)', - }, - { - url: '/appicon/icon-144x144.png', - type: 'image/png', - sizes: '144x144', - media: '(prefers-color-scheme: dark)', - }, - { - url: '/appicon/icon.png', - type: 'image/png', - sizes: '192x192', - media: '(prefers-color-scheme: dark)', - }, - ], - shortcut: [ - { url: '/appicon/icon-36x36.png', type: 'image/png', 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', - type: 'image/png', - sizes: '144x144', - }, - { url: '/appicon/icon.png', type: 'image/png', sizes: '192x192' }, - { - url: '/appicon/icon-36x36.png', - type: 'image/png', - sizes: '36x36', - media: '(prefers-color-scheme: dark)', - }, - { - url: '/appicon/icon-48x48.png', - type: 'image/png', - sizes: '48x48', - media: '(prefers-color-scheme: dark)', - }, - { - url: '/appicon/icon-72x72.png', - type: 'image/png', - sizes: '72x72', - media: '(prefers-color-scheme: dark)', - }, - { - url: '/appicon/icon-96x96.png', - type: 'image/png', - sizes: '96x96', - media: '(prefers-color-scheme: dark)', - }, - { - url: '/appicon/icon-144x144.png', - type: 'image/png', - sizes: '144x144', - media: '(prefers-color-scheme: dark)', - }, - { - url: '/appicon/icon.png', - type: 'image/png', - sizes: '192x192', - media: '(prefers-color-scheme: dark)', - }, - ], - apple: [ - { url: 'appicon/icon-57x57.png', type: 'image/png', 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', - type: 'image/png', - sizes: '114x114', - }, - { - url: 'appicon/icon-120x120.png', - type: 'image/png', - sizes: '120x120', - }, - { - url: 'appicon/icon-144x144.png', - type: 'image/png', - sizes: '144x144', - }, - { - url: 'appicon/icon-152x152.png', - type: 'image/png', - sizes: '152x152', - }, - { - url: 'appicon/icon-180x180.png', - type: 'image/png', - sizes: '180x180', - }, - { url: 'appicon/icon.png', type: 'image/png', sizes: '192x192' }, - { - url: 'appicon/icon-57x57.png', - type: 'image/png', - sizes: '57x57', - media: '(prefers-color-scheme: dark)', - }, - { - url: 'appicon/icon-60x60.png', - type: 'image/png', - sizes: '60x60', - media: '(prefers-color-scheme: dark)', - }, - { - url: 'appicon/icon-72x72.png', - type: 'image/png', - sizes: '72x72', - media: '(prefers-color-scheme: dark)', - }, - { - url: 'appicon/icon-76x76.png', - type: 'image/png', - sizes: '76x76', - media: '(prefers-color-scheme: dark)', - }, - { - url: 'appicon/icon-114x114.png', - type: 'image/png', - sizes: '114x114', - media: '(prefers-color-scheme: dark)', - }, - { - url: 'appicon/icon-120x120.png', - type: 'image/png', - sizes: '120x120', - media: '(prefers-color-scheme: dark)', - }, - { - url: 'appicon/icon-144x144.png', - type: 'image/png', - sizes: '144x144', - media: '(prefers-color-scheme: dark)', - }, - { - url: 'appicon/icon-152x152.png', - type: 'image/png', - sizes: '152x152', - media: '(prefers-color-scheme: dark)', - }, - { - url: 'appicon/icon-180x180.png', - type: 'image/png', - sizes: '180x180', - media: '(prefers-color-scheme: dark)', - }, - { - url: 'appicon/icon.png', - type: 'image/png', - sizes: '192x192', - media: '(prefers-color-scheme: dark)', - }, - ], - other: [ - { - rel: 'apple-touch-icon-precomposed', - url: '/appicon/icon-precomposed.png', - type: 'image/png', - sizes: '180x180', - }, - ], - }, - other: { - ...Sentry.getTraceData(), - }, - twitter: { - card: 'app', - title: 'T3 Template', - description: 'Created by Gib with T3!', - siteId: '', - creator: '@cs_gib', - creatorId: '', - images: { - url: 'https://git.gbrown.org/gib/T3-Template/raw/main/public/icons/apple/icon.png', - alt: 'T3 Template', - }, - app: { - name: 'T3 Template', - id: { - iphone: '', - ipad: '', - googleplay: '', - }, - url: { - iphone: '', - ipad: '', - googleplay: '', - }, - }, - }, - verification: { - google: 'google', - yandex: 'yandex', - yahoo: 'yahoo', - }, - itunes: { - appId: '', - appArgument: '', - }, - appleWebApp: { - title: 'T3 Template', - statusBarStyle: 'black-translucent', - startupImage: [ - '/icons/apple/splash-768x1004.png', - { - url: '/icons/apple/splash-1536x2008.png', - media: '(device-width: 768px) and (device-height: 1024px)', - }, - ], - }, - appLinks: { - ios: { - url: 'https://t3-template.gbrown.org/ios', - app_store_id: 't3_template', - }, - android: { - package: 'org.gbrown.android/t3-template', - app_name: 'app_t3_template', - }, - web: { - url: 'https://t3-template.gbrown.org/web', - should_fallback: true, - }, - }, - facebook: { - appId: '', - }, - pinterest: { - richPin: true, - }, - category: 'technology', - }; + { url: '/appicon/icon-36x36.png', type: 'image/png', 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', + type: 'image/png', + sizes: '144x144', + }, + { url: '/appicon/icon.png', type: 'image/png', sizes: '192x192' }, + { + url: '/appicon/icon-36x36.png', + type: 'image/png', + sizes: '36x36', + media: '(prefers-color-scheme: dark)', + }, + { + url: '/appicon/icon-48x48.png', + type: 'image/png', + sizes: '48x48', + media: '(prefers-color-scheme: dark)', + }, + { + url: '/appicon/icon-72x72.png', + type: 'image/png', + sizes: '72x72', + media: '(prefers-color-scheme: dark)', + }, + { + url: '/appicon/icon-96x96.png', + type: 'image/png', + sizes: '96x96', + media: '(prefers-color-scheme: dark)', + }, + { + url: '/appicon/icon-144x144.png', + type: 'image/png', + sizes: '144x144', + media: '(prefers-color-scheme: dark)', + }, + { + url: '/appicon/icon.png', + type: 'image/png', + sizes: '192x192', + media: '(prefers-color-scheme: dark)', + }, + ], + shortcut: [ + { url: '/appicon/icon-36x36.png', type: 'image/png', 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', + type: 'image/png', + sizes: '144x144', + }, + { url: '/appicon/icon.png', type: 'image/png', sizes: '192x192' }, + { + url: '/appicon/icon-36x36.png', + type: 'image/png', + sizes: '36x36', + media: '(prefers-color-scheme: dark)', + }, + { + url: '/appicon/icon-48x48.png', + type: 'image/png', + sizes: '48x48', + media: '(prefers-color-scheme: dark)', + }, + { + url: '/appicon/icon-72x72.png', + type: 'image/png', + sizes: '72x72', + media: '(prefers-color-scheme: dark)', + }, + { + url: '/appicon/icon-96x96.png', + type: 'image/png', + sizes: '96x96', + media: '(prefers-color-scheme: dark)', + }, + { + url: '/appicon/icon-144x144.png', + type: 'image/png', + sizes: '144x144', + media: '(prefers-color-scheme: dark)', + }, + { + url: '/appicon/icon.png', + type: 'image/png', + sizes: '192x192', + media: '(prefers-color-scheme: dark)', + }, + ], + apple: [ + { url: 'appicon/icon-57x57.png', type: 'image/png', 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', + type: 'image/png', + sizes: '114x114', + }, + { + url: 'appicon/icon-120x120.png', + type: 'image/png', + sizes: '120x120', + }, + { + url: 'appicon/icon-144x144.png', + type: 'image/png', + sizes: '144x144', + }, + { + url: 'appicon/icon-152x152.png', + type: 'image/png', + sizes: '152x152', + }, + { + url: 'appicon/icon-180x180.png', + type: 'image/png', + sizes: '180x180', + }, + { url: 'appicon/icon.png', type: 'image/png', sizes: '192x192' }, + { + url: 'appicon/icon-57x57.png', + type: 'image/png', + sizes: '57x57', + media: '(prefers-color-scheme: dark)', + }, + { + url: 'appicon/icon-60x60.png', + type: 'image/png', + sizes: '60x60', + media: '(prefers-color-scheme: dark)', + }, + { + url: 'appicon/icon-72x72.png', + type: 'image/png', + sizes: '72x72', + media: '(prefers-color-scheme: dark)', + }, + { + url: 'appicon/icon-76x76.png', + type: 'image/png', + sizes: '76x76', + media: '(prefers-color-scheme: dark)', + }, + { + url: 'appicon/icon-114x114.png', + type: 'image/png', + sizes: '114x114', + media: '(prefers-color-scheme: dark)', + }, + { + url: 'appicon/icon-120x120.png', + type: 'image/png', + sizes: '120x120', + media: '(prefers-color-scheme: dark)', + }, + { + url: 'appicon/icon-144x144.png', + type: 'image/png', + sizes: '144x144', + media: '(prefers-color-scheme: dark)', + }, + { + url: 'appicon/icon-152x152.png', + type: 'image/png', + sizes: '152x152', + media: '(prefers-color-scheme: dark)', + }, + { + url: 'appicon/icon-180x180.png', + type: 'image/png', + sizes: '180x180', + media: '(prefers-color-scheme: dark)', + }, + { + url: 'appicon/icon.png', + type: 'image/png', + sizes: '192x192', + media: '(prefers-color-scheme: dark)', + }, + ], + other: [ + { + rel: 'apple-touch-icon-precomposed', + url: '/appicon/icon-precomposed.png', + type: 'image/png', + sizes: '180x180', + }, + ], + }, + other: { + ...Sentry.getTraceData(), + }, + twitter: { + card: 'app', + title: 'T3 Template', + description: 'Created by Gib with T3!', + siteId: '', + creator: '@cs_gib', + creatorId: '', + images: { + url: 'https://git.gbrown.org/gib/T3-Template/raw/main/public/icons/apple/icon.png', + alt: 'T3 Template', + }, + app: { + name: 'T3 Template', + id: { + iphone: '', + ipad: '', + googleplay: '', + }, + url: { + iphone: '', + ipad: '', + googleplay: '', + }, + }, + }, + verification: { + google: 'google', + yandex: 'yandex', + yahoo: 'yahoo', + }, + itunes: { + appId: '', + appArgument: '', + }, + appleWebApp: { + title: 'T3 Template', + statusBarStyle: 'black-translucent', + startupImage: [ + '/icons/apple/splash-768x1004.png', + { + url: '/icons/apple/splash-1536x2008.png', + media: '(device-width: 768px) and (device-height: 1024px)', + }, + ], + }, + appLinks: { + ios: { + url: 'https://t3-template.gbrown.org/ios', + app_store_id: 't3_template', + }, + android: { + package: 'org.gbrown.android/t3-template', + app_name: 'app_t3_template', + }, + web: { + url: 'https://t3-template.gbrown.org/web', + should_fallback: true, + }, + }, + facebook: { + appId: '', + }, + pinterest: { + richPin: true, + }, + category: 'technology', + }; }; const geist = Geist({ - subsets: ['latin'], - variable: '--font-geist-sans', + subsets: ['latin'], + variable: '--font-geist-sans', }); const RootLayout = ({ children }: Readonly<{ children: React.ReactNode }>) => { - return ( - - - - -
-
- -
+ + + +
+
+ +
- {children} -
-
-
-
- -
-
- - - ); + > + {children} +
+
+
+
+ +
+
+ + + ); }; export default RootLayout; diff --git a/src/app/page.tsx b/src/app/page.tsx index 757f905..6f33d8b 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -6,91 +6,91 @@ import { getUser } from '@/lib/actions'; import type { User } from '@/utils/supabase'; import { TestSentryCard } from '@/components/default/sentry'; import { - Card, - CardContent, - CardDescription, - CardHeader, - CardTitle, - Separator, + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, + Separator, } from '@/components/ui'; import { - SignInSignUp, - SignInWithApple, - SignInWithMicrosoft, + SignInSignUp, + SignInWithApple, + SignInWithMicrosoft, } from '@/components/default/auth'; const HomePage = async () => { - const response = await getUser(); - if (!response.success || !response.data) { - return ( -
-
- - - - Welcome to the T3 Supabase Template! - - - A great place to start is by creating a new user account & - ensuring you can sign up! If you already have an account, go - ahead and sign in! - - -
- - or - -
-
- - -
-
- - - - You can also test out your connection to Sentry if you want to - start there! - - - -
-
-
- ); - } - const user: User = response.data; - return ( -
-
-
+
+ + + + Welcome to the T3 Supabase Template! + + + A great place to start is by creating a new user account & + ensuring you can sign up! If you already have an account, go + ahead and sign in! + + +
+ + or + +
+
+ + +
+
+ + + + You can also test out your connection to Sentry if you want to + start there! + + + +
+
+ + ); + } + const user: User = response.data; + return ( +
+
+
- - This is a protected component that you can only see as an - authenticated user -
-
-
-

Your user details

-
+					This is a protected component that you can only see as an
+					authenticated user
+				
+
+
+

Your user details

+
-          {JSON.stringify(user, null, 2)}
-        
-
- -
-

Next steps

- -
-
- ); + > + {JSON.stringify(user, null, 2)} + +
+ +
+

Next steps

+ +
+
+ ); }; export default HomePage; diff --git a/src/components/context/Auth.tsx b/src/components/context/Auth.tsx index ea04195..cec47a4 100644 --- a/src/components/context/Auth.tsx +++ b/src/components/context/Auth.tsx @@ -1,195 +1,195 @@ 'use client'; import React, { - type ReactNode, - createContext, - useCallback, - useContext, - useEffect, - useRef, - useState, + type ReactNode, + createContext, + useCallback, + useContext, + useEffect, + useRef, + useState, } from 'react'; import { - getProfile, - getSignedUrl, - getUser, - updateProfile as updateProfileAction, + getProfile, + getSignedUrl, + getUser, + updateProfile as updateProfileAction, } from '@/lib/hooks'; import { type User, type Profile, createClient } from '@/utils/supabase'; import { toast } from 'sonner'; type AuthContextType = { - user: User | null; - profile: Profile | null; - avatarUrl: string | null; - isLoading: boolean; - isAuthenticated: boolean; - updateProfile: (data: { - full_name?: string; - email?: string; - avatar_url?: string; - }) => Promise<{ success: boolean; data?: Profile; error?: unknown }>; - refreshUserData: () => Promise; + user: User | null; + profile: Profile | null; + avatarUrl: string | null; + isLoading: boolean; + isAuthenticated: boolean; + updateProfile: (data: { + full_name?: string; + email?: string; + avatar_url?: string; + }) => Promise<{ success: boolean; data?: Profile; error?: unknown }>; + refreshUserData: () => Promise; }; const AuthContext = createContext(undefined); export const AuthProvider = ({ children }: { children: ReactNode }) => { - const [user, setUser] = useState(null); - const [profile, setProfile] = useState(null); - const [avatarUrl, setAvatarUrl] = useState(null); - const [isLoading, setIsLoading] = useState(true); - const [isInitialized, setIsInitialized] = useState(false); - const fetchingRef = useRef(false); + const [user, setUser] = useState(null); + const [profile, setProfile] = useState(null); + const [avatarUrl, setAvatarUrl] = useState(null); + const [isLoading, setIsLoading] = useState(true); + const [isInitialized, setIsInitialized] = useState(false); + const fetchingRef = useRef(false); - const fetchUserData = useCallback( - async (showLoading = true) => { - if (fetchingRef.current) return; - fetchingRef.current = true; + const fetchUserData = useCallback( + async (showLoading = true) => { + if (fetchingRef.current) return; + fetchingRef.current = true; - try { - // Only show loading for initial load or manual refresh - if (showLoading) { - setIsLoading(true); - } + try { + // Only show loading for initial load or manual refresh + if (showLoading) { + setIsLoading(true); + } - const userResponse = await getUser(); - const profileResponse = await getProfile(); + const userResponse = await getUser(); + const profileResponse = await getProfile(); - if (!userResponse.success || !profileResponse.success) { - setUser(null); - setProfile(null); - setAvatarUrl(null); - return; - } + if (!userResponse.success || !profileResponse.success) { + setUser(null); + setProfile(null); + setAvatarUrl(null); + return; + } - setUser(userResponse.data); - setProfile(profileResponse.data); + setUser(userResponse.data); + setProfile(profileResponse.data); - // Get avatar URL if available - if (profileResponse.data.avatar_url) { - const avatarResponse = await getSignedUrl({ - bucket: 'avatars', - url: profileResponse.data.avatar_url, - }); - if (avatarResponse.success) { - setAvatarUrl(avatarResponse.data); - } else { - setAvatarUrl(null); - } - } else { - setAvatarUrl(null); - } - } catch (error) { - console.error( - 'Auth fetch error: ', - error instanceof Error - ? `${error.message}` - : 'Failed to load user data!', - ); - if (!isInitialized) { - toast.error('Failed to load user data!'); - } - } finally { - if (showLoading) { - setIsLoading(false); - } - setIsInitialized(true); - fetchingRef.current = false; - } - }, - [isInitialized], - ); + // Get avatar URL if available + if (profileResponse.data.avatar_url) { + const avatarResponse = await getSignedUrl({ + bucket: 'avatars', + url: profileResponse.data.avatar_url, + }); + if (avatarResponse.success) { + setAvatarUrl(avatarResponse.data); + } else { + setAvatarUrl(null); + } + } else { + setAvatarUrl(null); + } + } catch (error) { + console.error( + 'Auth fetch error: ', + error instanceof Error + ? `${error.message}` + : 'Failed to load user data!', + ); + if (!isInitialized) { + toast.error('Failed to load user data!'); + } + } finally { + if (showLoading) { + setIsLoading(false); + } + setIsInitialized(true); + fetchingRef.current = false; + } + }, + [isInitialized], + ); - useEffect(() => { - const supabase = createClient(); + useEffect(() => { + const supabase = createClient(); - // Initial fetch with loading - fetchUserData(true).catch((error) => { - console.error('💥 Initial fetch error:', error); - }); + // Initial fetch with loading + fetchUserData(true).catch((error) => { + console.error('💥 Initial fetch error:', error); + }); - const { - data: { subscription }, - } = supabase.auth.onAuthStateChange(async (event, session) => { - console.log('Auth state change:', event); // Debug log + const { + data: { subscription }, + } = supabase.auth.onAuthStateChange(async (event, session) => { + console.log('Auth state change:', event); // Debug log - if (event === 'SIGNED_IN') { - // Background refresh without loading state - await fetchUserData(false); - } else if (event === 'SIGNED_OUT') { - setUser(null); - setProfile(null); - setAvatarUrl(null); - setIsLoading(false); - } else if (event === 'TOKEN_REFRESHED') { - // Silent refresh - don't show loading - await fetchUserData(false); - } - }); + if (event === 'SIGNED_IN') { + // Background refresh without loading state + await fetchUserData(false); + } else if (event === 'SIGNED_OUT') { + setUser(null); + setProfile(null); + setAvatarUrl(null); + setIsLoading(false); + } else if (event === 'TOKEN_REFRESHED') { + // Silent refresh - don't show loading + await fetchUserData(false); + } + }); - return () => { - subscription.unsubscribe(); - }; - }, [fetchUserData]); + return () => { + subscription.unsubscribe(); + }; + }, [fetchUserData]); - const updateProfile = useCallback( - async (data: { - full_name?: string; - email?: string; - avatar_url?: string; - }) => { - try { - const result = await updateProfileAction(data); - if (!result.success) { - throw new Error(result.error ?? 'Failed to update profile'); - } - setProfile(result.data); + const updateProfile = useCallback( + async (data: { + full_name?: string; + email?: string; + avatar_url?: string; + }) => { + try { + const result = await updateProfileAction(data); + if (!result.success) { + throw new Error(result.error ?? 'Failed to update profile'); + } + setProfile(result.data); - // If avatar was updated, refresh the avatar URL - if (data.avatar_url && result.data.avatar_url) { - const avatarResponse = await getSignedUrl({ - bucket: 'avatars', - url: result.data.avatar_url, - transform: { width: 128, height: 128 }, - }); - if (avatarResponse.success) { - setAvatarUrl(avatarResponse.data); - } - } - toast.success('Profile updated successfully!'); - return { success: true, data: result.data }; - } catch (error) { - console.error('Error updating profile:', error); - toast.error( - error instanceof Error ? error.message : 'Failed to update profile', - ); - return { success: false, error }; - } - }, - [], - ); + // If avatar was updated, refresh the avatar URL + if (data.avatar_url && result.data.avatar_url) { + const avatarResponse = await getSignedUrl({ + bucket: 'avatars', + url: result.data.avatar_url, + transform: { width: 128, height: 128 }, + }); + if (avatarResponse.success) { + setAvatarUrl(avatarResponse.data); + } + } + toast.success('Profile updated successfully!'); + return { success: true, data: result.data }; + } catch (error) { + console.error('Error updating profile:', error); + toast.error( + error instanceof Error ? error.message : 'Failed to update profile', + ); + return { success: false, error }; + } + }, + [], + ); - const refreshUserData = useCallback(async () => { - await fetchUserData(true); // Manual refresh shows loading - }, [fetchUserData]); + const refreshUserData = useCallback(async () => { + await fetchUserData(true); // Manual refresh shows loading + }, [fetchUserData]); - const value = { - user, - profile, - avatarUrl, - isLoading, - isAuthenticated: !!user, - updateProfile, - refreshUserData, - }; + const value = { + user, + profile, + avatarUrl, + isLoading, + isAuthenticated: !!user, + updateProfile, + refreshUserData, + }; - return {children}; + return {children}; }; export const useAuth = () => { - const context = useContext(AuthContext); - if (context === undefined) { - throw new Error('useAuth must be used within an AuthProvider'); - } - return context; + const context = useContext(AuthContext); + if (context === undefined) { + throw new Error('useAuth must be used within an AuthProvider'); + } + return context; }; diff --git a/src/components/context/Theme.tsx b/src/components/context/Theme.tsx index 54445e9..59bd8ee 100644 --- a/src/components/context/Theme.tsx +++ b/src/components/context/Theme.tsx @@ -6,63 +6,63 @@ import { useTheme } from 'next-themes'; import { Button } from '@/components/ui'; export const ThemeProvider = ({ - children, - ...props + children, + ...props }: React.ComponentProps) => { - const [mounted, setMounted] = React.useState(false); + const [mounted, setMounted] = React.useState(false); - React.useEffect(() => { - setMounted(true); - }, []); + React.useEffect(() => { + setMounted(true); + }, []); - if (!mounted) return null; + if (!mounted) return null; - return {children}; + return {children}; }; export interface ThemeToggleProps - extends React.ButtonHTMLAttributes { - size?: number; + extends React.ButtonHTMLAttributes { + size?: number; } export const ThemeToggle = ({ size = 1, ...props }: ThemeToggleProps) => { - const { setTheme, resolvedTheme } = useTheme(); - const [mounted, setMounted] = React.useState(false); + const { setTheme, resolvedTheme } = useTheme(); + const [mounted, setMounted] = React.useState(false); - React.useEffect(() => { - setMounted(true); - }, []); + React.useEffect(() => { + setMounted(true); + }, []); - if (!mounted) { - return ( - - ); - } + if (!mounted) { + return ( + + ); + } - const toggleTheme = () => { - if (resolvedTheme === 'dark') setTheme('light'); - else setTheme('dark'); - }; + const toggleTheme = () => { + if (resolvedTheme === 'dark') setTheme('light'); + else setTheme('dark'); + }; - return ( - - ); + return ( + + ); }; diff --git a/src/components/default/StatusMessage.tsx b/src/components/default/StatusMessage.tsx index 9dde835..62cc3ec 100644 --- a/src/components/default/StatusMessage.tsx +++ b/src/components/default/StatusMessage.tsx @@ -1,25 +1,25 @@ export type Message = - | { success: string } - | { error: string } - | { message: string }; + | { success: string } + | { error: string } + | { message: string }; export const StatusMessage = ({ message }: { message: Message }) => { - return ( -
- {'success' in message && ( -
- {message.success} -
- )} - {'error' in message && ( -
{message.error}
- )} - {'message' in message && ( -
{message.message}
- )} -
- ); + > + {'success' in message && ( +
+ {message.success} +
+ )} + {'error' in message && ( +
{message.error}
+ )} + {'message' in message && ( +
{message.message}
+ )} + + ); }; diff --git a/src/components/default/SubmitButton.tsx b/src/components/default/SubmitButton.tsx index cf124b5..20cef64 100644 --- a/src/components/default/SubmitButton.tsx +++ b/src/components/default/SubmitButton.tsx @@ -6,34 +6,34 @@ import { useFormStatus } from 'react-dom'; import { Loader2 } from 'lucide-react'; type Props = ComponentProps & { - disabled?: boolean; - pendingText?: string; + disabled?: boolean; + pendingText?: string; }; export const SubmitButton = ({ - children, - disabled = false, - pendingText = 'Submitting...', - ...props + children, + disabled = false, + pendingText = 'Submitting...', + ...props }: Props) => { - const { pending } = useFormStatus(); + const { pending } = useFormStatus(); - return ( - - ); + return ( + + ); }; diff --git a/src/components/default/auth/SignInSignUp.tsx b/src/components/default/auth/SignInSignUp.tsx index ee965aa..0cc325c 100644 --- a/src/components/default/auth/SignInSignUp.tsx +++ b/src/components/default/auth/SignInSignUp.tsx @@ -6,28 +6,28 @@ import { type ComponentProps } from 'react'; import { type VariantProps } from 'class-variance-authority'; type SignInSignUpProps = { - className?: ComponentProps<'div'>['className']; - signInSize?: VariantProps['size']; - signUpSize?: VariantProps['size']; - signInVariant?: VariantProps['variant']; - signUpVariant?: VariantProps['variant']; + className?: ComponentProps<'div'>['className']; + signInSize?: VariantProps['size']; + signUpSize?: VariantProps['size']; + signInVariant?: VariantProps['variant']; + signUpVariant?: VariantProps['variant']; }; export const SignInSignUp = async ({ - className = 'flex gap-2', - signInSize = 'default', - signUpSize = 'sm', - signInVariant = 'outline', - signUpVariant = 'default', + className = 'flex gap-2', + signInSize = 'default', + signUpSize = 'sm', + signInVariant = 'outline', + signUpVariant = 'default', }: SignInSignUpProps) => { - return ( -
- - -
- ); + return ( +
+ + +
+ ); }; diff --git a/src/components/default/auth/SignInWithApple.tsx b/src/components/default/auth/SignInWithApple.tsx index aa9f653..48fed08 100644 --- a/src/components/default/auth/SignInWithApple.tsx +++ b/src/components/default/auth/SignInWithApple.tsx @@ -10,68 +10,68 @@ import { type ComponentProps } from 'react'; import { type VariantProps } from 'class-variance-authority'; type SignInWithAppleProps = { - className?: ComponentProps<'div'>['className']; - buttonSize?: VariantProps['size']; - buttonVariant?: VariantProps['variant']; + className?: ComponentProps<'div'>['className']; + buttonSize?: VariantProps['size']; + buttonVariant?: VariantProps['variant']; }; export const SignInWithApple = ({ - className = 'my-4', - buttonSize = 'default', - buttonVariant = 'default', + className = 'my-4', + buttonSize = 'default', + buttonVariant = 'default', }: SignInWithAppleProps) => { - const router = useRouter(); - const { isLoading, refreshUserData } = useAuth(); - const [statusMessage, setStatusMessage] = useState(''); - const [isSigningIn, setIsSigningIn] = useState(false); + const router = useRouter(); + const { isLoading, refreshUserData } = useAuth(); + const [statusMessage, setStatusMessage] = useState(''); + const [isSigningIn, setIsSigningIn] = useState(false); - const handleSignInWithApple = async (e: React.FormEvent) => { - e.preventDefault(); - try { - setStatusMessage(''); - setIsSigningIn(true); + const handleSignInWithApple = async (e: React.FormEvent) => { + e.preventDefault(); + try { + setStatusMessage(''); + setIsSigningIn(true); - const result = await signInWithApple(); + const result = await signInWithApple(); - if (result?.success && result.data) { - // Redirect to Apple OAuth page - window.location.href = result.data; - } else { - setStatusMessage(`Error: ${result.error}`); - } - } catch (error) { - setStatusMessage( - `Error: ${error instanceof Error ? error.message : 'Could not sign in!'}`, - ); - } finally { - setIsSigningIn(false); - await refreshUserData(); - router.push(''); - } - }; + if (result?.success && result.data) { + // Redirect to Apple OAuth page + window.location.href = result.data; + } else { + setStatusMessage(`Error: ${result.error}`); + } + } catch (error) { + setStatusMessage( + `Error: ${error instanceof Error ? error.message : 'Could not sign in!'}`, + ); + } finally { + setIsSigningIn(false); + await refreshUserData(); + router.push(''); + } + }; - return ( -
- -
- Apple logo -

Sign In with Apple

-
-
- {statusMessage && } - - ); + return ( +
+ +
+ Apple logo +

Sign In with Apple

+
+
+ {statusMessage && } + + ); }; diff --git a/src/components/default/auth/SignInWithMicrosoft.tsx b/src/components/default/auth/SignInWithMicrosoft.tsx index 3a80e05..462894c 100644 --- a/src/components/default/auth/SignInWithMicrosoft.tsx +++ b/src/components/default/auth/SignInWithMicrosoft.tsx @@ -9,62 +9,62 @@ import { type ComponentProps } from 'react'; import { type VariantProps } from 'class-variance-authority'; type SignInWithMicrosoftProps = { - className?: ComponentProps<'div'>['className']; - buttonSize?: VariantProps['size']; - buttonVariant?: VariantProps['variant']; + className?: ComponentProps<'div'>['className']; + buttonSize?: VariantProps['size']; + buttonVariant?: VariantProps['variant']; }; export const SignInWithMicrosoft = ({ - className = 'my-4', - buttonSize = 'default', - buttonVariant = 'default', + className = 'my-4', + buttonSize = 'default', + buttonVariant = 'default', }: SignInWithMicrosoftProps) => { - const { isLoading } = useAuth(); - const [statusMessage, setStatusMessage] = useState(''); - const [isSigningIn, setIsSigningIn] = useState(false); + const { isLoading } = useAuth(); + const [statusMessage, setStatusMessage] = useState(''); + const [isSigningIn, setIsSigningIn] = useState(false); - const handleSignInWithMicrosoft = async (e: React.FormEvent) => { - e.preventDefault(); - try { - setStatusMessage(''); - setIsSigningIn(true); + const handleSignInWithMicrosoft = async (e: React.FormEvent) => { + e.preventDefault(); + try { + setStatusMessage(''); + setIsSigningIn(true); - const result = await signInWithMicrosoft(); + const result = await signInWithMicrosoft(); - if (result?.success && result.data) { - // Redirect to Microsoft OAuth page - window.location.href = result.data; - } else { - setStatusMessage(`Error: ${result.error}`); - } - } catch (error) { - setStatusMessage( - `Error: ${error instanceof Error ? error.message : 'Could not sign in!'}`, - ); - } - }; + if (result?.success && result.data) { + // Redirect to Microsoft OAuth page + window.location.href = result.data; + } else { + setStatusMessage(`Error: ${result.error}`); + } + } catch (error) { + setStatusMessage( + `Error: ${error instanceof Error ? error.message : 'Could not sign in!'}`, + ); + } + }; - return ( -
- -
- Microsoft logo -

Sign In with Microsoft

-
-
- {statusMessage && } - - ); + return ( +
+ +
+ Microsoft logo +

Sign In with Microsoft

+
+
+ {statusMessage && } + + ); }; diff --git a/src/components/default/footer/index.tsx b/src/components/default/footer/index.tsx index 7379740..5cc359f 100644 --- a/src/components/default/footer/index.tsx +++ b/src/components/default/footer/index.tsx @@ -1,20 +1,20 @@ 'use server'; const Footer = () => { - return ( - - ); + return ( + + ); }; export default Footer; diff --git a/src/components/default/index.tsx b/src/components/default/index.tsx index 21b32b1..601bae7 100644 --- a/src/components/default/index.tsx +++ b/src/components/default/index.tsx @@ -1,5 +1,5 @@ export { - StatusMessage, - type Message, + StatusMessage, + type Message, } from '@/components/default/StatusMessage'; export { SubmitButton } from '@/components/default/SubmitButton'; diff --git a/src/components/default/navigation/auth/AvatarDropdown.tsx b/src/components/default/navigation/auth/AvatarDropdown.tsx index f416da4..a2933fc 100644 --- a/src/components/default/navigation/auth/AvatarDropdown.tsx +++ b/src/components/default/navigation/auth/AvatarDropdown.tsx @@ -2,15 +2,15 @@ import Link from 'next/link'; import { - Avatar, - AvatarFallback, - AvatarImage, - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuLabel, - DropdownMenuSeparator, - DropdownMenuTrigger, + Avatar, + AvatarFallback, + AvatarImage, + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuLabel, + DropdownMenuSeparator, + DropdownMenuTrigger, } from '@/components/ui'; import { useAuth } from '@/components/context'; import { useRouter } from 'next/navigation'; @@ -18,70 +18,70 @@ import { signOut } from '@/lib/actions'; import { User } from 'lucide-react'; const AvatarDropdown = () => { - const { profile, avatarUrl, isLoading, refreshUserData } = useAuth(); - const router = useRouter(); + const { profile, avatarUrl, isLoading, refreshUserData } = useAuth(); + const router = useRouter(); - const handleSignOut = async () => { - const result = await signOut(); - if (result?.success) { - await refreshUserData(); - router.push('/sign-in'); - } - }; + const handleSignOut = async () => { + const result = await signOut(); + if (result?.success) { + await refreshUserData(); + router.push('/sign-in'); + } + }; - const getInitials = (name: string | null | undefined): string => { - if (!name) return ''; - return name - .split(' ') - .map((n) => n[0]) - .join('') - .toUpperCase(); - }; + const getInitials = (name: string | null | undefined): string => { + if (!name) return ''; + return name + .split(' ') + .map((n) => n[0]) + .join('') + .toUpperCase(); + }; - return ( - - - - {avatarUrl ? ( - - ) : ( - - {profile?.full_name ? ( - getInitials(profile.full_name) - ) : ( - - )} - - )} - - - - {profile?.full_name} - - - - Edit profile - - - - - - - - - ); + return ( + + + + {avatarUrl ? ( + + ) : ( + + {profile?.full_name ? ( + getInitials(profile.full_name) + ) : ( + + )} + + )} + + + + {profile?.full_name} + + + + Edit profile + + + + + + + + + ); }; export default AvatarDropdown; diff --git a/src/components/default/navigation/auth/index.tsx b/src/components/default/navigation/auth/index.tsx index 5350350..f23c743 100644 --- a/src/components/default/navigation/auth/index.tsx +++ b/src/components/default/navigation/auth/index.tsx @@ -5,18 +5,18 @@ import AvatarDropdown from './AvatarDropdown'; import { SignInSignUp } from '@/components/default/auth'; const NavigationAuth = async () => { - try { - const profile = await getProfile(); - return profile.success ? ( -
- -
- ) : ( - - ); - } catch (error) { - console.error(`Error getting profile: ${error as string}`); - return ; - } + try { + const profile = await getProfile(); + return profile.success ? ( +
+ +
+ ) : ( + + ); + } catch (error) { + console.error(`Error getting profile: ${error as string}`); + return ; + } }; export default NavigationAuth; diff --git a/src/components/default/navigation/index.tsx b/src/components/default/navigation/index.tsx index bc92be7..88ea2d6 100644 --- a/src/components/default/navigation/index.tsx +++ b/src/components/default/navigation/index.tsx @@ -7,34 +7,34 @@ import { ThemeToggle } from '@/components/context'; import Image from 'next/image'; const Navigation = () => { - return ( - - ); + > +
+ + T3 Logo +

T3 Supabase Template

+ +
+ +
+
+
+ + +
+ + + ); }; export default Navigation; diff --git a/src/components/default/profile/AvatarUpload.tsx b/src/components/default/profile/AvatarUpload.tsx index 1696a6c..abd4da8 100644 --- a/src/components/default/profile/AvatarUpload.tsx +++ b/src/components/default/profile/AvatarUpload.tsx @@ -1,112 +1,112 @@ import { useFileUpload } from '@/lib/hooks/useFileUpload'; import { useAuth } from '@/components/context'; import { - Avatar, - AvatarFallback, - AvatarImage, - CardContent, + Avatar, + AvatarFallback, + AvatarImage, + CardContent, } from '@/components/ui'; import { Loader2, Pencil, Upload, User } from 'lucide-react'; type AvatarUploadProps = { - onAvatarUploaded: (path: string) => Promise; + onAvatarUploaded: (path: string) => Promise; }; export const AvatarUpload = ({ onAvatarUploaded }: AvatarUploadProps) => { - const { profile, avatarUrl } = useAuth(); - const { isUploading, fileInputRef, uploadToStorage } = useFileUpload(); + const { profile, avatarUrl } = useAuth(); + const { isUploading, fileInputRef, uploadToStorage } = useFileUpload(); - const handleAvatarClick = () => { - fileInputRef.current?.click(); - }; + const handleAvatarClick = () => { + fileInputRef.current?.click(); + }; - const handleFileChange = async (e: React.ChangeEvent) => { - const file = e.target.files?.[0]; - if (!file) return; + const handleFileChange = async (e: React.ChangeEvent) => { + const file = e.target.files?.[0]; + if (!file) return; - const result = await uploadToStorage({ - file, - bucket: 'avatars', - resize: true, - options: { - maxWidth: 500, - maxHeight: 500, - quality: 0.8, - }, - replace: { replace: true, path: profile?.avatar_url ?? file.name }, - }); - if (result.success && result.data) { - await onAvatarUploaded(result.data); - } - }; + const result = await uploadToStorage({ + file, + bucket: 'avatars', + resize: true, + options: { + maxWidth: 500, + maxHeight: 500, + quality: 0.8, + }, + replace: { replace: true, path: profile?.avatar_url ?? file.name }, + }); + if (result.success && result.data) { + await onAvatarUploaded(result.data); + } + }; - const getInitials = (name: string | null | undefined): string => { - if (!name) return ''; - return name - .split(' ') - .map((n) => n[0]) - .join('') - .toUpperCase(); - }; + const getInitials = (name: string | null | undefined): string => { + if (!name) return ''; + return name + .split(' ') + .map((n) => n[0]) + .join('') + .toUpperCase(); + }; - return ( - -
-
- - {avatarUrl ? ( - - ) : ( - - {profile?.full_name ? ( - getInitials(profile.full_name) - ) : ( - - )} - - )} - -
+
+ + {avatarUrl ? ( + + ) : ( + + {profile?.full_name ? ( + getInitials(profile.full_name) + ) : ( + + )} + + )} + +
- -
-
- + -
-
- - {isUploading && ( -
- - Uploading... -
- )} -
- - ); + size={24} + /> +
+
+ + {isUploading && ( +
+ + Uploading... +
+ )} + +
+ ); }; diff --git a/src/components/default/profile/ProfileForm.tsx b/src/components/default/profile/ProfileForm.tsx index 3610def..f144741 100644 --- a/src/components/default/profile/ProfileForm.tsx +++ b/src/components/default/profile/ProfileForm.tsx @@ -2,99 +2,99 @@ import { z } from 'zod'; import { zodResolver } from '@hookform/resolvers/zod'; import { useForm } from 'react-hook-form'; import { - CardContent, - Form, - FormControl, - FormDescription, - FormField, - FormItem, - FormLabel, - FormMessage, - Input, + CardContent, + Form, + FormControl, + FormDescription, + FormField, + FormItem, + FormLabel, + FormMessage, + Input, } from '@/components/ui'; import { useEffect } from 'react'; import { useAuth } from '@/components/context'; import { SubmitButton } from '@/components/default'; const formSchema = z.object({ - full_name: z.string().min(5, { - message: 'Full name is required & must be at least 5 characters.', - }), - email: z.string().email(), + full_name: z.string().min(5, { + message: 'Full name is required & must be at least 5 characters.', + }), + email: z.string().email(), }); type ProfileFormProps = { - onSubmit: (values: z.infer) => Promise; + onSubmit: (values: z.infer) => Promise; }; export const ProfileForm = ({ onSubmit }: ProfileFormProps) => { - const { profile, isLoading } = useAuth(); + const { profile, isLoading } = useAuth(); - const form = useForm>({ - resolver: zodResolver(formSchema), - defaultValues: { - full_name: profile?.full_name ?? '', - email: profile?.email ?? '', - }, - }); + const form = useForm>({ + resolver: zodResolver(formSchema), + defaultValues: { + full_name: profile?.full_name ?? '', + email: profile?.email ?? '', + }, + }); - // Update form values when profile changes - useEffect(() => { - if (profile) { - form.reset({ - full_name: profile.full_name ?? '', - email: profile.email ?? '', - }); - } - }, [profile, form]); + // Update form values when profile changes + useEffect(() => { + if (profile) { + form.reset({ + full_name: profile.full_name ?? '', + email: profile.email ?? '', + }); + } + }, [profile, form]); - const handleSubmit = async (values: z.infer) => { - await onSubmit(values); - }; + const handleSubmit = async (values: z.infer) => { + await onSubmit(values); + }; - return ( - -
- - ( - - Full Name - - - - Your public display name. - - - )} - /> + return ( + + + + ( + + Full Name + + + + Your public display name. + + + )} + /> - ( - - Email - - - - - Your email address associated with your account. - - - - )} - /> + ( + + Email + + + + + Your email address associated with your account. + + + + )} + /> -
- - Save Changes - -
- - -
- ); +
+ + Save Changes + +
+ + +
+ ); }; diff --git a/src/components/default/profile/ResetPasswordForm.tsx b/src/components/default/profile/ResetPasswordForm.tsx index 14ec06f..2d1e5c4 100644 --- a/src/components/default/profile/ResetPasswordForm.tsx +++ b/src/components/default/profile/ResetPasswordForm.tsx @@ -2,18 +2,18 @@ import { z } from 'zod'; import { zodResolver } from '@hookform/resolvers/zod'; import { useForm } from 'react-hook-form'; import { - CardContent, - CardDescription, - CardHeader, - CardTitle, - Form, - FormControl, - FormDescription, - FormField, - FormItem, - FormLabel, - FormMessage, - Input, + CardContent, + CardDescription, + CardHeader, + CardTitle, + Form, + FormControl, + FormDescription, + FormField, + FormItem, + FormLabel, + FormMessage, + Input, } from '@/components/ui'; import { SubmitButton } from '@/components/default'; import { useState } from 'react'; @@ -21,127 +21,127 @@ import { type Result } from '@/lib/actions'; import { StatusMessage } from '@/components/default'; const formSchema = z - .object({ - password: z.string().min(8, { - message: 'Password must be at least 8 characters.', - }), - confirmPassword: z.string(), - }) - .refine((data) => data.password === data.confirmPassword, { - message: 'Passwords do not match.', - path: ['confirmPassword'], - }); + .object({ + password: z.string().min(8, { + message: 'Password must be at least 8 characters.', + }), + confirmPassword: z.string(), + }) + .refine((data) => data.password === data.confirmPassword, { + message: 'Passwords do not match.', + path: ['confirmPassword'], + }); type ResetPasswordFormProps = { - onSubmit: (formData: FormData) => Promise>; - message?: string; + onSubmit: (formData: FormData) => Promise>; + message?: string; }; export const ResetPasswordForm = ({ - onSubmit, - message, + onSubmit, + message, }: ResetPasswordFormProps) => { - const [isLoading, setIsLoading] = useState(false); - const [statusMessage, setStatusMessage] = useState(message ?? ''); + const [isLoading, setIsLoading] = useState(false); + const [statusMessage, setStatusMessage] = useState(message ?? ''); - const form = useForm>({ - resolver: zodResolver(formSchema), - defaultValues: { - password: '', - confirmPassword: '', - }, - }); + const form = useForm>({ + resolver: zodResolver(formSchema), + defaultValues: { + password: '', + confirmPassword: '', + }, + }); - const handleUpdatePassword = async (values: z.infer) => { - setIsLoading(true); - try { - // Convert form values to FormData for your server action - const formData = new FormData(); - formData.append('password', values.password); - formData.append('confirmPassword', values.confirmPassword); + const handleUpdatePassword = async (values: z.infer) => { + setIsLoading(true); + try { + // Convert form values to FormData for your server action + const formData = new FormData(); + formData.append('password', values.password); + formData.append('confirmPassword', values.confirmPassword); - const result = await onSubmit(formData); - if (result?.success) { - setStatusMessage('Password updated successfully!'); - form.reset(); // Clear the form on success - } else { - setStatusMessage('Error: Unable to update password!'); - } - } catch (error) { - setStatusMessage( - error instanceof Error ? error.message : 'Password was not updated!', - ); - } finally { - setIsLoading(false); - } - }; + const result = await onSubmit(formData); + if (result?.success) { + setStatusMessage('Password updated successfully!'); + form.reset(); // Clear the form on success + } else { + setStatusMessage('Error: Unable to update password!'); + } + } catch (error) { + setStatusMessage( + error instanceof Error ? error.message : 'Password was not updated!', + ); + } finally { + setIsLoading(false); + } + }; - return ( -
- - Change Password - - Update your password to keep your account secure - - - -
- - ( - - New Password - - - - - Enter your new password. Must be at least 8 characters. - - - - )} - /> - ( - - Confirm Password - - - - - Please re-enter your new password to confirm. - - - - )} - /> - {statusMessage && - (statusMessage.includes('Error') || - statusMessage.includes('error') || - statusMessage.includes('failed') || - statusMessage.includes('invalid') ? ( - - ) : ( - - ))} -
- - Update Password - -
- - -
-
- ); + return ( +
+ + Change Password + + Update your password to keep your account secure + + + +
+ + ( + + New Password + + + + + Enter your new password. Must be at least 8 characters. + + + + )} + /> + ( + + Confirm Password + + + + + Please re-enter your new password to confirm. + + + + )} + /> + {statusMessage && + (statusMessage.includes('Error') || + statusMessage.includes('error') || + statusMessage.includes('failed') || + statusMessage.includes('invalid') ? ( + + ) : ( + + ))} +
+ + Update Password + +
+ + +
+
+ ); }; diff --git a/src/components/default/profile/SignOut.tsx b/src/components/default/profile/SignOut.tsx index 78327b2..effe26e 100644 --- a/src/components/default/profile/SignOut.tsx +++ b/src/components/default/profile/SignOut.tsx @@ -7,28 +7,28 @@ import { useAuth } from '@/components/context'; import { signOut } from '@/lib/actions'; export const SignOut = () => { - const { isLoading, refreshUserData } = useAuth(); - const router = useRouter(); + const { isLoading, refreshUserData } = useAuth(); + const router = useRouter(); - const handleSignOut = async () => { - const result = await signOut(); - if (result?.success) { - await refreshUserData(); - router.push('/sign-in'); - } - }; - return ( -
- - + + - Sign Out - - -
- ); + disabled={isLoading} + onClick={handleSignOut} + > + Sign Out + + + + ); }; diff --git a/src/components/default/sentry/TestSentry.tsx b/src/components/default/sentry/TestSentry.tsx index 83eaf44..92dbab4 100644 --- a/src/components/default/sentry/TestSentry.tsx +++ b/src/components/default/sentry/TestSentry.tsx @@ -3,124 +3,124 @@ import * as Sentry from '@sentry/nextjs'; import { useState, useEffect } from 'react'; import { - Button, - Card, - CardContent, - CardDescription, - CardHeader, - CardTitle, - Separator, + Button, + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, + Separator, } from '@/components/ui'; import Link from 'next/link'; import { CheckCircle, MessageCircleWarning } from 'lucide-react'; class SentryExampleFrontendError extends Error { - constructor(message: string | undefined) { - super(message); - this.name = 'SentryExampleFrontendError'; - } + constructor(message: string | undefined) { + super(message); + this.name = 'SentryExampleFrontendError'; + } } export const TestSentryCard = () => { - const [hasSentError, setHasSentError] = useState(false); - const [isConnected, setIsConnected] = useState(true); + const [hasSentError, setHasSentError] = useState(false); + const [isConnected, setIsConnected] = useState(true); - useEffect(() => { - const checkConnectivity = async () => { - console.log('Checking Sentry SDK connectivity...'); - const result = await Sentry.diagnoseSdkConnectivity(); - setIsConnected(result !== 'sentry-unreachable'); - }; - checkConnectivity().catch((error) => { - console.error('Error trying to connect to Sentry: ', error); - }); - }, []); + useEffect(() => { + const checkConnectivity = async () => { + console.log('Checking Sentry SDK connectivity...'); + const result = await Sentry.diagnoseSdkConnectivity(); + setIsConnected(result !== 'sentry-unreachable'); + }; + checkConnectivity().catch((error) => { + console.error('Error trying to connect to Sentry: ', error); + }); + }, []); - const createError = async () => { - await Sentry.startSpan( - { - name: 'Example Frontend Span', - op: 'test', - }, - async () => { - const res = await fetch('/api/sentry/example'); - if (!res.ok) { - setHasSentError(true); - throw new SentryExampleFrontendError( - 'This error is raised in our TestSentry component on the main page.', - ); - } - }, - ); - }; + const createError = async () => { + await Sentry.startSpan( + { + name: 'Example Frontend Span', + op: 'test', + }, + async () => { + const res = await fetch('/api/sentry/example'); + if (!res.ok) { + setHasSentError(true); + throw new SentryExampleFrontendError( + 'This error is raised in our TestSentry component on the main page.', + ); + } + }, + ); + }; - return ( - - -
- - - - Test Sentry -
- - Click the button below & view the sample error on{' '} - - the Sentry website - - . Navigate to the {"'"}Issues{"'"} page & you should see the sample - error! - -
- -
- - {hasSentError ? ( -
- -

Sample error was sent to Sentry!

-
- ) : !isConnected ? ( -
- -

- Wait! The Sentry SDK is not able to reach Sentry right now - - this may be due to an adblocker. For more information, see{' '} - - the troubleshooting guide. - -

-
- ) : ( -
- )} -
- -

- Warning! Sometimes Adblockers will prevent errors from being sent to - Sentry. -

- - - ); + return ( + + +
+ + + + Test Sentry +
+ + Click the button below & view the sample error on{' '} + + the Sentry website + + . Navigate to the {"'"}Issues{"'"} page & you should see the sample + error! + +
+ +
+ + {hasSentError ? ( +
+ +

Sample error was sent to Sentry!

+
+ ) : !isConnected ? ( +
+ +

+ Wait! The Sentry SDK is not able to reach Sentry right now - + this may be due to an adblocker. For more information, see{' '} + + the troubleshooting guide. + +

+
+ ) : ( +
+ )} +
+ +

+ Warning! Sometimes Adblockers will prevent errors from being sent to + Sentry. +

+ + + ); }; diff --git a/src/components/default/tutorial/CodeBlock.tsx b/src/components/default/tutorial/CodeBlock.tsx index acd1828..32b7126 100644 --- a/src/components/default/tutorial/CodeBlock.tsx +++ b/src/components/default/tutorial/CodeBlock.tsx @@ -4,58 +4,58 @@ import { useState } from 'react'; import { Button } from '@/components/ui'; const CopyIcon = () => ( - - - - + + + + ); const CheckIcon = () => ( - - - + + + ); export function CodeBlock({ code }: { code: string }) { - const [icon, setIcon] = useState(CopyIcon); + const [icon, setIcon] = useState(CopyIcon); - const copy = async () => { - await navigator?.clipboard?.writeText(code); - setIcon(CheckIcon); - setTimeout(() => setIcon(CopyIcon), 2000); - }; + const copy = async () => { + await navigator?.clipboard?.writeText(code); + setIcon(CheckIcon); + setTimeout(() => setIcon(CopyIcon), 2000); + }; - return ( -
-      
-      {code}
-    
- ); + return ( +
+			
+			{code}
+		
+ ); } diff --git a/src/components/default/tutorial/FetchDataSteps.tsx b/src/components/default/tutorial/FetchDataSteps.tsx index 1a5059c..f4dfabd 100644 --- a/src/components/default/tutorial/FetchDataSteps.tsx +++ b/src/components/default/tutorial/FetchDataSteps.tsx @@ -44,52 +44,52 @@ export default function Page() { `.trim(); export const FetchDataSteps = () => { - return ( -
    - -

    - Head over to the{' '} - - Table Editor - {' '} - for your Supabase project to create a table and insert some example - data. If you're stuck for creativity, you can copy and paste the - following into the{' '} - - SQL Editor - {' '} - and click RUN! -

    - -
    + return ( +
      + +

      + Head over to the{' '} + + Table Editor + {' '} + for your Supabase project to create a table and insert some example + data. If you're stuck for creativity, you can copy and paste the + following into the{' '} + + SQL Editor + {' '} + and click RUN! +

      + +
      - -

      - To create a Supabase client and query data from an Async Server - Component, create a new page.tsx file at{' '} - - /app/notes/page.tsx - {' '} - and add the following. -

      - -

      Alternatively, you can use a Client Component.

      - -
      + +

      + To create a Supabase client and query data from an Async Server + Component, create a new page.tsx file at{' '} + + /app/notes/page.tsx + {' '} + and add the following. +

      + +

      Alternatively, you can use a Client Component.

      + +
      - -

      You're ready to launch your product to the world! 🚀

      -
      -
    - ); + +

    You're ready to launch your product to the world! 🚀

    +
    +
+ ); }; diff --git a/src/components/default/tutorial/TutorialStep.tsx b/src/components/default/tutorial/TutorialStep.tsx index efb30fb..7a3f92d 100644 --- a/src/components/default/tutorial/TutorialStep.tsx +++ b/src/components/default/tutorial/TutorialStep.tsx @@ -1,30 +1,30 @@ import { Checkbox } from '@/components/ui'; export const TutorialStep = ({ - title, - children, + title, + children, }: { - title: string; - children: React.ReactNode; + title: string; + children: React.ReactNode; }) => { - return ( -
  • - - -
  • - ); + return ( +
  • + + +
  • + ); }; diff --git a/src/components/ui/avatar.tsx b/src/components/ui/avatar.tsx index 52e6be0..cb3b90a 100644 --- a/src/components/ui/avatar.tsx +++ b/src/components/ui/avatar.tsx @@ -6,48 +6,48 @@ import * as AvatarPrimitive from '@radix-ui/react-avatar'; import { cn } from '@/lib/utils'; function Avatar({ - className, - ...props + className, + ...props }: React.ComponentProps) { - return ( - - ); + return ( + + ); } function AvatarImage({ - className, - ...props + className, + ...props }: React.ComponentProps) { - return ( - - ); + return ( + + ); } function AvatarFallback({ - className, - ...props + className, + ...props }: React.ComponentProps) { - return ( - - ); + return ( + + ); } export { Avatar, AvatarImage, AvatarFallback }; diff --git a/src/components/ui/badge.tsx b/src/components/ui/badge.tsx index 8375444..3438196 100644 --- a/src/components/ui/badge.tsx +++ b/src/components/ui/badge.tsx @@ -5,42 +5,42 @@ import { cva, type VariantProps } from 'class-variance-authority'; import { cn } from '@/lib/utils'; const badgeVariants = cva( - 'inline-flex items-center justify-center rounded-md border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden', - { - variants: { - variant: { - default: - 'border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90', - secondary: - 'border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90', - destructive: - 'border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60', - outline: - 'text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground', - }, - }, - defaultVariants: { - variant: 'default', - }, - }, + 'inline-flex items-center justify-center rounded-md border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden', + { + variants: { + variant: { + default: + 'border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90', + secondary: + 'border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90', + destructive: + 'border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60', + outline: + 'text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground', + }, + }, + defaultVariants: { + variant: 'default', + }, + }, ); function Badge({ - className, - variant, - asChild = false, - ...props + className, + variant, + asChild = false, + ...props }: React.ComponentProps<'span'> & - VariantProps & { asChild?: boolean }) { - const Comp = asChild ? Slot : 'span'; + VariantProps & { asChild?: boolean }) { + const Comp = asChild ? Slot : 'span'; - return ( - - ); + return ( + + ); } export { Badge, badgeVariants }; diff --git a/src/components/ui/button.tsx b/src/components/ui/button.tsx index b012b53..3b08136 100644 --- a/src/components/ui/button.tsx +++ b/src/components/ui/button.tsx @@ -5,58 +5,58 @@ import { cva, type VariantProps } from 'class-variance-authority'; import { cn } from '@/lib/utils'; const buttonVariants = cva( - "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive", - { - variants: { - variant: { - default: - 'bg-primary text-primary-foreground shadow-xs hover:bg-primary/90', - destructive: - 'bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60', - outline: - '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: - 'bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80', - ghost: - 'hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50', - link: 'text-primary underline-offset-4 hover:underline', - }, - size: { - default: 'h-9 px-4 py-2 has-[>svg]:px-3', - sm: 'h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5', - lg: 'h-10 rounded-md px-6 has-[>svg]:px-4', - xl: 'h-12 rounded-md px-8 has-[>svg]:px-6', - xxl: 'h-14 rounded-md px-10 has-[>svg]:px-8', - icon: 'size-9', - smicon: 'size-6', - }, - }, - defaultVariants: { - variant: 'default', - size: 'default', - }, - }, + "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive", + { + variants: { + variant: { + default: + 'bg-primary text-primary-foreground shadow-xs hover:bg-primary/90', + destructive: + 'bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60', + outline: + '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: + 'bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80', + ghost: + 'hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50', + link: 'text-primary underline-offset-4 hover:underline', + }, + size: { + default: 'h-9 px-4 py-2 has-[>svg]:px-3', + sm: 'h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5', + lg: 'h-10 rounded-md px-6 has-[>svg]:px-4', + xl: 'h-12 rounded-md px-8 has-[>svg]:px-6', + xxl: 'h-14 rounded-md px-10 has-[>svg]:px-8', + icon: 'size-9', + smicon: 'size-6', + }, + }, + defaultVariants: { + variant: 'default', + size: 'default', + }, + }, ); function Button({ - className, - variant, - size, - asChild = false, - ...props + className, + variant, + size, + asChild = false, + ...props }: React.ComponentProps<'button'> & - VariantProps & { - asChild?: boolean; - }) { - const Comp = asChild ? Slot : 'button'; + VariantProps & { + asChild?: boolean; + }) { + const Comp = asChild ? Slot : 'button'; - return ( - - ); + return ( + + ); } export { Button, buttonVariants }; diff --git a/src/components/ui/card.tsx b/src/components/ui/card.tsx index 32a06b1..74ce9ae 100644 --- a/src/components/ui/card.tsx +++ b/src/components/ui/card.tsx @@ -3,90 +3,90 @@ import * as React from 'react'; import { cn } from '@/lib/utils'; function Card({ className, ...props }: React.ComponentProps<'div'>) { - return ( -
    - ); + return ( +
    + ); } function CardHeader({ className, ...props }: React.ComponentProps<'div'>) { - return ( -
    - ); + return ( +
    + ); } function CardTitle({ className, ...props }: React.ComponentProps<'div'>) { - return ( -
    - ); + return ( +
    + ); } function CardDescription({ className, ...props }: React.ComponentProps<'div'>) { - return ( -
    - ); + return ( +
    + ); } function CardAction({ className, ...props }: React.ComponentProps<'div'>) { - return ( -
    - ); + return ( +
    + ); } function CardContent({ className, ...props }: React.ComponentProps<'div'>) { - return ( -
    - ); + return ( +
    + ); } function CardFooter({ className, ...props }: React.ComponentProps<'div'>) { - return ( -
    - ); + return ( +
    + ); } export { - Card, - CardHeader, - CardFooter, - CardTitle, - CardAction, - CardDescription, - CardContent, + Card, + CardHeader, + CardFooter, + CardTitle, + CardAction, + CardDescription, + CardContent, }; diff --git a/src/components/ui/checkbox.tsx b/src/components/ui/checkbox.tsx index fde2498..33f9527 100644 --- a/src/components/ui/checkbox.tsx +++ b/src/components/ui/checkbox.tsx @@ -7,26 +7,26 @@ import { CheckIcon } from 'lucide-react'; import { cn } from '@/lib/utils'; function Checkbox({ - className, - ...props + className, + ...props }: React.ComponentProps) { - return ( - - - - - - ); + return ( + + + + + + ); } export { Checkbox }; diff --git a/src/components/ui/dropdown-menu.tsx b/src/components/ui/dropdown-menu.tsx index 7a8804e..7dde1e2 100644 --- a/src/components/ui/dropdown-menu.tsx +++ b/src/components/ui/dropdown-menu.tsx @@ -7,251 +7,251 @@ import { CheckIcon, ChevronRightIcon, CircleIcon } from 'lucide-react'; import { cn } from '@/lib/utils'; function DropdownMenu({ - ...props + ...props }: React.ComponentProps) { - return ; + return ; } function DropdownMenuPortal({ - ...props + ...props }: React.ComponentProps) { - return ( - - ); + return ( + + ); } function DropdownMenuTrigger({ - ...props + ...props }: React.ComponentProps) { - return ( - - ); + return ( + + ); } function DropdownMenuContent({ - className, - sideOffset = 4, - ...props + className, + sideOffset = 4, + ...props }: React.ComponentProps) { - return ( - - - - ); + return ( + + + + ); } function DropdownMenuGroup({ - ...props + ...props }: React.ComponentProps) { - return ( - - ); + return ( + + ); } function DropdownMenuItem({ - className, - inset, - variant = 'default', - ...props + className, + inset, + variant = 'default', + ...props }: React.ComponentProps & { - inset?: boolean; - variant?: 'default' | 'destructive'; + inset?: boolean; + variant?: 'default' | 'destructive'; }) { - return ( - - ); + return ( + + ); } function DropdownMenuCheckboxItem({ - className, - children, - checked, - ...props + className, + children, + checked, + ...props }: React.ComponentProps) { - return ( - - - - - - - {children} - - ); + return ( + + + + + + + {children} + + ); } function DropdownMenuRadioGroup({ - ...props + ...props }: React.ComponentProps) { - return ( - - ); + return ( + + ); } function DropdownMenuRadioItem({ - className, - children, - ...props + className, + children, + ...props }: React.ComponentProps) { - return ( - - - - - - - {children} - - ); + return ( + + + + + + + {children} + + ); } function DropdownMenuLabel({ - className, - inset, - ...props + className, + inset, + ...props }: React.ComponentProps & { - inset?: boolean; + inset?: boolean; }) { - return ( - - ); + return ( + + ); } function DropdownMenuSeparator({ - className, - ...props + className, + ...props }: React.ComponentProps) { - return ( - - ); + return ( + + ); } function DropdownMenuShortcut({ - className, - ...props + className, + ...props }: React.ComponentProps<'span'>) { - return ( - - ); + return ( + + ); } function DropdownMenuSub({ - ...props + ...props }: React.ComponentProps) { - return ; + return ; } function DropdownMenuSubTrigger({ - className, - inset, - children, - ...props + className, + inset, + children, + ...props }: React.ComponentProps & { - inset?: boolean; + inset?: boolean; }) { - return ( - - {children} - - - ); + return ( + + {children} + + + ); } function DropdownMenuSubContent({ - className, - ...props + className, + ...props }: React.ComponentProps) { - return ( - - ); + return ( + + ); } export { - DropdownMenu, - DropdownMenuPortal, - DropdownMenuTrigger, - DropdownMenuContent, - DropdownMenuGroup, - DropdownMenuLabel, - DropdownMenuItem, - DropdownMenuCheckboxItem, - DropdownMenuRadioGroup, - DropdownMenuRadioItem, - DropdownMenuSeparator, - DropdownMenuShortcut, - DropdownMenuSub, - DropdownMenuSubTrigger, - DropdownMenuSubContent, + DropdownMenu, + DropdownMenuPortal, + DropdownMenuTrigger, + DropdownMenuContent, + DropdownMenuGroup, + DropdownMenuLabel, + DropdownMenuItem, + DropdownMenuCheckboxItem, + DropdownMenuRadioGroup, + DropdownMenuRadioItem, + DropdownMenuSeparator, + DropdownMenuShortcut, + DropdownMenuSub, + DropdownMenuSubTrigger, + DropdownMenuSubContent, }; diff --git a/src/components/ui/form.tsx b/src/components/ui/form.tsx index 5dd2131..7ef26b9 100644 --- a/src/components/ui/form.tsx +++ b/src/components/ui/form.tsx @@ -4,13 +4,13 @@ import * as React from 'react'; import * as LabelPrimitive from '@radix-ui/react-label'; import { Slot } from '@radix-ui/react-slot'; import { - Controller, - FormProvider, - useFormContext, - useFormState, - type ControllerProps, - type FieldPath, - type FieldValues, + Controller, + FormProvider, + useFormContext, + useFormState, + type ControllerProps, + type FieldPath, + type FieldValues, } from 'react-hook-form'; import { cn } from '@/lib/utils'; @@ -19,150 +19,150 @@ import { Label } from '@/components/ui/label'; const Form = FormProvider; type FormFieldContextValue< - TFieldValues extends FieldValues = FieldValues, - TName extends FieldPath = FieldPath, + TFieldValues extends FieldValues = FieldValues, + TName extends FieldPath = FieldPath, > = { - name: TName; + name: TName; }; const FormFieldContext = React.createContext( - {} as FormFieldContextValue, + {} as FormFieldContextValue, ); const FormField = < - TFieldValues extends FieldValues = FieldValues, - TName extends FieldPath = FieldPath, + TFieldValues extends FieldValues = FieldValues, + TName extends FieldPath = FieldPath, >({ - ...props + ...props }: ControllerProps) => { - return ( - - - - ); + return ( + + + + ); }; const useFormField = () => { - const fieldContext = React.useContext(FormFieldContext); - const itemContext = React.useContext(FormItemContext); - const { getFieldState } = useFormContext(); - const formState = useFormState({ name: fieldContext.name }); - const fieldState = getFieldState(fieldContext.name, formState); + const fieldContext = React.useContext(FormFieldContext); + const itemContext = React.useContext(FormItemContext); + const { getFieldState } = useFormContext(); + const formState = useFormState({ name: fieldContext.name }); + const fieldState = getFieldState(fieldContext.name, formState); - if (!fieldContext) { - throw new Error('useFormField should be used within '); - } + if (!fieldContext) { + throw new Error('useFormField should be used within '); + } - const { id } = itemContext; + const { id } = itemContext; - return { - id, - name: fieldContext.name, - formItemId: `${id}-form-item`, - formDescriptionId: `${id}-form-item-description`, - formMessageId: `${id}-form-item-message`, - ...fieldState, - }; + return { + id, + name: fieldContext.name, + formItemId: `${id}-form-item`, + formDescriptionId: `${id}-form-item-description`, + formMessageId: `${id}-form-item-message`, + ...fieldState, + }; }; type FormItemContextValue = { - id: string; + id: string; }; const FormItemContext = React.createContext( - {} as FormItemContextValue, + {} as FormItemContextValue, ); function FormItem({ className, ...props }: React.ComponentProps<'div'>) { - const id = React.useId(); + const id = React.useId(); - return ( - -
    - - ); + return ( + +
    + + ); } function FormLabel({ - className, - ...props + className, + ...props }: React.ComponentProps) { - const { error, formItemId } = useFormField(); + const { error, formItemId } = useFormField(); - return ( -