diff --git a/.prettierrc b/.prettierrc index b81bd4d..75ebcfc 100644 --- a/.prettierrc +++ b/.prettierrc @@ -2,6 +2,5 @@ "singleQuote": true, "jsxSingleQuote": true, "trailingComma": "all", - "useTabs": true, "tabWidth": 2 } diff --git a/eslint.config.js b/eslint.config.js index 8d6c546..da3adf5 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 18b0525..937da13 100644 --- a/next.config.js +++ b/next.config.js @@ -3,65 +3,68 @@ */ import './src/env.js'; import { withSentryConfig } from '@sentry/nextjs'; +import { withPlausibleProxy } from 'next-plausible'; /** @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', - }, - }, - }, -}; +const config = withPlausibleProxy({ + customDomain: 'https://plausible.gbrown.org', +})({ + 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/package.json b/package.json index 23b0397..4c99e27 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ "clsx": "^2.1.1", "lucide-react": "^0.510.0", "next": "^15.3.3", + "next-plausible": "^3.12.4", "next-themes": "^0.4.6", "react": "^19.1.0", "react-dom": "^19.1.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2a77fd9..99803b3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -53,6 +53,9 @@ importers: next: specifier: ^15.3.3 version: 15.3.3(@babel/core@7.27.4)(@opentelemetry/api@1.9.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + next-plausible: + specifier: ^3.12.4 + version: 3.12.4(next@15.3.3(@babel/core@7.27.4)(@opentelemetry/api@1.9.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0) next-themes: specifier: ^0.4.6 version: 0.4.6(react-dom@19.1.0(react@19.1.0))(react@19.1.0) @@ -2758,6 +2761,13 @@ packages: neo-async@2.6.2: resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} + next-plausible@3.12.4: + resolution: {integrity: sha512-cD3+ixJxf8yBYvsideTxqli3fvrB7R4BXcvsNJz8Sm2X1QN039WfiXjCyNWkub4h5++rRs6fHhchUMnOuJokcg==} + peerDependencies: + next: '^11.1.0 || ^12.0.0 || ^13.0.0 || ^14.0.0 || ^15.0.0 ' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + next-themes@0.4.6: resolution: {integrity: sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA==} peerDependencies: @@ -6327,6 +6337,12 @@ snapshots: neo-async@2.6.2: {} + next-plausible@3.12.4(next@15.3.3(@babel/core@7.27.4)(@opentelemetry/api@1.9.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + dependencies: + next: 15.3.3(@babel/core@7.27.4)(@opentelemetry/api@1.9.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + next-themes@0.4.6(react-dom@19.1.0(react@19.1.0))(react@19.1.0): dependencies: react: 19.1.0 diff --git a/postcss.config.js b/postcss.config.js index 1970487..a34a3d5 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 4b043fd..09f0482 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/public/icons/gitea.svg b/public/icons/gitea.svg new file mode 100644 index 0000000..d9eb11a --- /dev/null +++ b/public/icons/gitea.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/scripts/next.config.build.js b/scripts/next.config.build.js index 39d14aa..6bcbd94 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 dd1537d..d1cec6a 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 098e696..86a556b 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 4f89b38..e810e51 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 673b7e4..e6660ab 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 dbc55f0..aa2c7c3 100644 --- a/src/app/(auth-pages)/forgot-password/page.tsx +++ b/src/app/(auth-pages)/forgot-password/page.tsx @@ -72,58 +72,63 @@ const ForgotPassword = () => { }; 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') ? ( - - ) : ( - - ))} - - -
-
+ +
+
+ + + 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)/sign-in/page.tsx b/src/app/(auth-pages)/sign-in/page.tsx index f22cef5..524d99c 100644 --- a/src/app/(auth-pages)/sign-in/page.tsx +++ b/src/app/(auth-pages)/sign-in/page.tsx @@ -24,8 +24,7 @@ import { useAuth } from '@/components/context'; import { useEffect, useState } from 'react'; import { StatusMessage, SubmitButton } from '@/components/default'; import { Separator } from '@/components/ui'; -import { SignInWithMicrosoft } from '@/components/default/auth/SignInWithMicrosoft'; -import { SignInWithApple } from '@/components/default/auth/SignInWithApple'; +import { SignInWithApple, SignInWithMicrosoft } from '@/components/default/auth'; const formSchema = z.object({ email: z.string().email({ @@ -78,93 +77,98 @@ const Login = () => { }; 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 - - - +
+
+ + + Sign In + + Don't have an account?{' '} + + Sign up + + + + +
+ + ( + + Email + + + + + + )} + /> -
- - or - -
- - - - + ( + +
+ Password + + Forgot Password? + +
+ + + + +
+ )} + /> + {statusMessage && + (statusMessage.includes('Error') || + statusMessage.includes('error') || + statusMessage.includes('failed') || + statusMessage.includes('invalid') ? ( + + ) : ( + + ))} + + Sign in + + + + +
+ + or + +
+ + +
+
+
+
); }; diff --git a/src/app/(auth-pages)/sign-up/page.tsx b/src/app/(auth-pages)/sign-up/page.tsx index 0da1249..cead871 100644 --- a/src/app/(auth-pages)/sign-up/page.tsx +++ b/src/app/(auth-pages)/sign-up/page.tsx @@ -99,112 +99,116 @@ const SignUp = () => { }; 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 - -
- - -
-
+
+
+ + + 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 deleted file mode 100644 index 797a67e..0000000 --- a/src/app/(sentry)/api/sentry/example/route.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { NextResponse } from 'next/server'; - -export const dynamic = 'force-dynamic'; -class SentryExampleAPIError extends Error { - 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...' }); -} diff --git a/src/app/api/sentry/example/route.ts b/src/app/api/sentry/example/route.ts new file mode 100644 index 0000000..d9a4a24 --- /dev/null +++ b/src/app/api/sentry/example/route.ts @@ -0,0 +1,16 @@ +import { NextResponse } from 'next/server'; + +export const dynamic = 'force-dynamic'; +class SentryExampleAPIError extends Error { + 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...' }); +} diff --git a/src/app/global-error.tsx b/src/app/global-error.tsx index 203045c..68d54e0 100644 --- a/src/app/global-error.tsx +++ b/src/app/global-error.tsx @@ -3,7 +3,7 @@ import '@/styles/globals.css'; import { cn } from '@/lib/utils'; import { AuthProvider, ThemeProvider } from '@/components/context'; -import Navigation from '@/components/default/navigation'; +import Header from '@/components/default/header'; import Footer from '@/components/default/footer'; import { Button, Toaster } from '@/components/ui'; import * as Sentry from '@sentry/nextjs'; @@ -12,53 +12,53 @@ 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 && ( + + )} +
+
+
+
+ +
+
+ + + ); }; export default GlobalError; diff --git a/src/app/layout.tsx b/src/app/layout.tsx index e3542a7..5384ec9 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -3,451 +3,421 @@ import '@/styles/globals.css'; import { Geist } from 'next/font/google'; import { cn } from '@/lib/utils'; import { - AuthProvider, - ThemeProvider, - TVModeProvider, + AuthProvider, + ThemeProvider, + TVModeProvider, } from '@/components/context'; -import Navigation from '@/components/default/navigation'; +import PlausibleProvider from 'next-plausible'; +import Header from '@/components/default/header'; import Footer from '@/components/default/footer'; import { Toaster } from '@/components/ui'; import * as Sentry from '@sentry/nextjs'; export const generateMetadata = (): Metadata => { - return { - title: { - template: '%s | Tech Tracker', - default: 'Tech Tracker', - }, - description: 'Created by Gib with Next.js & Supabase!', - applicationName: 'Tech Tracker', - keywords: - 'Tech Tracker, City of Gulfport, Information Technology, T3 Template, ' + - 'Next.js, Supabase, Tailwind, TypeScript, React, T3, Gib', - 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, + return { + title: { + template: '%s | Tech Tracker', + default: 'Tech Tracker', + }, + description: + 'App used by COG IT employees to \ + update their status throughout the day.', + applicationName: 'Tech Tracker', + keywords: + 'Tech Tracker, City of Gulfport, Information Technology, T3 Template, ' + + 'Next.js, Supabase, Tailwind, TypeScript, React, T3, Gib', + 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(), + }, + appleWebApp: { + title: 'Tech Tracker', + statusBarStyle: 'black-translucent', + startupImage: [ + '/icons/apple/splash-768x1004.png', + { + url: '/icons/apple/splash-1536x2008.png', + media: '(device-width: 768px) and (device-height: 1024px)', + }, + ], + }, + verification: { + google: 'google', + yandex: 'yandex', + yahoo: 'yahoo', + }, + category: 'technology', + /* + appLinks: { + ios: { + url: 'https://techtracker.gbrown.org/ios', + app_store_id: 'com.gbrown.techtracker', + }, + android: { + package: 'https://techtracker.gbrown.org/android', + app_name: 'app_t3_template', + }, + web: { + url: 'https://techtracker.gbrown.org', + should_fallback: true, }, }, - 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: 'Tech Tracker', - //description: 'Created by Gib with Next.js & Supabase!', - //siteId: '', - //creator: '@cs_gib', - //creatorId: '', - //images: { - //url: 'https://git.gbrown.org/gib/T3-Template/raw/main/public/icons/apple/icon.png', - //alt: 'Tech Tracker', - //}, - //app: { - //name: 'Tech Tracker', - //id: { - //iphone: '', - //ipad: '', - //googleplay: '', - //}, - //url: { - //iphone: '', - //ipad: '', - //googleplay: '', - //}, - //}, - //}, - //verification: { - //google: 'google', - //yandex: 'yandex', - //yahoo: 'yahoo', - //}, - //itunes: { - //appId: '', - //appArgument: '', - //}, - //appleWebApp: { - //title: 'Tech Tracker', - //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://techtracker.gbrown.org/ios', - //app_store_id: 'com.gbrown.techtracker', - //}, - //android: { - //package: 'https://techtracker.gbrown.org/android', - //app_name: 'app_t3_template', - //}, - //web: { - //url: 'https://techtracker.gbrown.org', - //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} -
-
-
-
- -
-
-
- - - ); + return ( + + + + + + +
+
+ {children} + +
+