diff --git a/.prettierrc b/.prettierrc
index da8128c..b81bd4d 100644
--- a/.prettierrc
+++ b/.prettierrc
@@ -1,5 +1,7 @@
{
"singleQuote": true,
"jsxSingleQuote": true,
- "trailingComma": "all"
+ "trailingComma": "all",
+ "useTabs": true,
+ "tabWidth": 2
}
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 e810e51..d74fd11 100644
--- a/src/app/(auth-pages)/auth/callback/route.ts
+++ b/src/app/(auth-pages)/auth/callback/route.ts
@@ -6,38 +6,40 @@ 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..07e552a 100644
--- a/src/app/(auth-pages)/auth/success/page.tsx
+++ b/src/app/(auth-pages)/auth/success/page.tsx
@@ -6,34 +6,36 @@ 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..c67520f 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,113 @@ 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
-
-
-
-
-
-
-
-
- );
+ return (
+
+
+
+ Reset Password
+
+
+ Don't have an account?{' '}
+
+ Sign up
+
+
+
+
+
+
+
+
+ );
};
export default ForgotPassword;
diff --git a/src/app/(auth-pages)/profile/page.tsx b/src/app/(auth-pages)/profile/page.tsx
index 9b5ae2f..7a4f047 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,106 @@ 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..dda051c 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,152 @@ 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
-
-
-
-
-
+ (
+
+
+
+ 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 8f9028c..5638eff 100644
--- a/src/app/(auth-pages)/sign-up/page.tsx
+++ b/src/app/(auth-pages)/sign-up/page.tsx
@@ -9,202 +9,221 @@ 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
-
-
-
-
-
-
-
-
- or
-
-
-
-
-
-
- );
+ return (
+
+
+ Sign Up
+
+ Already have an account?{' '}
+
+ Sign in
+
+
+
+
+
+
+
+
+ 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 354dbfe..ed4558b 100644
--- a/src/app/global-error.tsx
+++ b/src/app/global-error.tsx
@@ -12,53 +12,61 @@ 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 && (
+
+ )}
+
+
+
+
+
+
+
+
+