diff --git a/src/app/(auth-pages)/forgot-password/page.tsx b/src/app/(auth-pages)/forgot-password/page.tsx index aa2c7c3..48d388d 100644 --- a/src/app/(auth-pages)/forgot-password/page.tsx +++ b/src/app/(auth-pages)/forgot-password/page.tsx @@ -1,132 +1,11 @@ 'use client'; -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, -} from '@/components/ui'; -import Link from 'next/link'; -import { forgotPassword } from '@/lib/actions'; -import { useRouter } from 'next/navigation'; -import { useAuth } from '@/components/context'; -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.', - }), -}); +import { ForgotPasswordCard } from '@/components/default/auth'; const ForgotPassword = () => { - const router = useRouter(); - const { isAuthenticated, isLoading, refreshUserData } = useAuth(); - const [statusMessage, setStatusMessage] = useState(''); - - const form = useForm>({ - resolver: zodResolver(formSchema), - defaultValues: { - email: '', - }, - }); - - // 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!'}`, - ); - } - }; - 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') ? ( - - ) : ( - - ))} - - -
-
+
); diff --git a/src/app/(auth-pages)/profile/page.tsx b/src/app/(auth-pages)/profile/page.tsx index 7d41c07..6abbbb0 100644 --- a/src/app/(auth-pages)/profile/page.tsx +++ b/src/app/(auth-pages)/profile/page.tsx @@ -1,122 +1,14 @@ 'use client'; -import { useAuth } from '@/components/context'; -import { useRouter } from 'next/navigation'; -import { useEffect } from 'react'; -import { - AvatarUpload, - ProfileForm, - ResetPasswordForm, - SignOut, -} from '@/components/default/profile'; -import { - Card, - CardHeader, - CardTitle, - CardDescription, - Separator, -} from '@/components/ui'; -import { Loader2 } from 'lucide-react'; -import { resetPassword } from '@/lib/actions'; -import { toast } from 'sonner'; -import { type Result } from '@/lib/actions'; + +import { ProfileCard } from "@/components/default/auth"; const ProfilePage = () => { - const { - profile, - isLoading, - isAuthenticated, - updateProfile, - refreshUserData, - } = useAuth(); - const router = useRouter(); - - useEffect(() => { - if (!isLoading && !isAuthenticated) { - router.push('/sign-in'); - } - }, [isLoading, isAuthenticated, router]); - - 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 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 ( -
- -
- ); - } - - // 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 ? ( -
- -
- ) : ( -
- - - - - - - -
- )} -
-
+
+
+ +
+
); }; diff --git a/src/app/(auth-pages)/sign-in/page.tsx b/src/app/(auth-pages)/sign-in/page.tsx index 524d99c..8e1caa6 100644 --- a/src/app/(auth-pages)/sign-in/page.tsx +++ b/src/app/(auth-pages)/sign-in/page.tsx @@ -1,172 +1,11 @@ 'use client'; - -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, -} from '@/components/ui'; -import Link from 'next/link'; -import { signIn } from '@/lib/actions'; -import { useRouter } from 'next/navigation'; -import { useAuth } from '@/components/context'; -import { useEffect, useState } from 'react'; -import { StatusMessage, SubmitButton } from '@/components/default'; -import { Separator } from '@/components/ui'; -import { SignInWithApple, SignInWithMicrosoft } from '@/components/default/auth'; - -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.', - }), -}); +import { SignInCard } from '@/components/default/auth'; const Login = () => { - const router = useRouter(); - const { isAuthenticated, isLoading, refreshUserData } = useAuth(); - const [statusMessage, setStatusMessage] = useState(''); - - const form = useForm>({ - resolver: zodResolver(formSchema), - defaultValues: { - email: '', - password: '', - }, - }); - - // 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!'}`, - ); - } - }; - 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 - - - - -
- - or - -
- - -
-
+
); diff --git a/src/app/(auth-pages)/sign-up/page.tsx b/src/app/(auth-pages)/sign-up/page.tsx index cead871..a54a478 100644 --- a/src/app/(auth-pages)/sign-up/page.tsx +++ b/src/app/(auth-pages)/sign-up/page.tsx @@ -1,212 +1,12 @@ 'use client'; -import { z } from 'zod'; -import { zodResolver } from '@hookform/resolvers/zod'; -import { useForm } from 'react-hook-form'; -import Link from 'next/link'; -import { signUp } from '@/lib/actions'; -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, -} from '@/components/ui'; -import { useEffect, useState } from 'react'; -import { - 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'], - }); +import { SignUpCard } from "@/components/default/auth"; const SignUp = () => { - 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', - }); - - // 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!'}`, - ); - } - }; - 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 - -
- - -
-
+
); diff --git a/src/app/page.tsx b/src/app/page.tsx index 6ad8201..1aae807 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -10,6 +10,10 @@ const HomePage = async () => { redirect('/sign-in'); } const user: User = response.data; - return ; + return ( +
+

Hello {user.email}

+
+ ) }; export default HomePage; diff --git a/src/components/default/auth/NoSession.tsx b/src/components/default/auth/NoSession.tsx deleted file mode 100644 index 3631e78..0000000 --- a/src/components/default/auth/NoSession.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import { SignUpCard } from './cards/SignUp'; - -export default function NoSession() { - return ( -
-
- -
-
- ); -} diff --git a/src/components/default/auth/cards/Profile.tsx b/src/components/default/auth/cards/Profile.tsx index 47c91f3..d5bc3b8 100644 --- a/src/components/default/auth/cards/Profile.tsx +++ b/src/components/default/auth/cards/Profile.tsx @@ -92,30 +92,28 @@ export const ProfileCard = () => { } return ( -
- - - Your Profile - - Manage your personal information and how it appears to others - - - {isLoading && !profile ? ( -
- -
- ) : ( -
- - - - - - - -
- )} -
-
+ + + Your Profile + + Manage your personal information and how it appears to others + + + {isLoading && !profile ? ( +
+ +
+ ) : ( +
+ + + + + + + +
+ )} +
); }; diff --git a/src/components/default/auth/cards/SignIn.tsx b/src/components/default/auth/cards/SignIn.tsx index 285d396..cd42293 100644 --- a/src/components/default/auth/cards/SignIn.tsx +++ b/src/components/default/auth/cards/SignIn.tsx @@ -86,7 +86,7 @@ export const SignInCard = () => { Don't have an account?{' '} - Sign up + Sign Up @@ -153,7 +153,7 @@ export const SignInCard = () => { pendingText='Signing In...' className='text-[1.0rem] cursor-pointer' > - Sign in + Sign In diff --git a/src/components/default/auth/cards/SignUp.tsx b/src/components/default/auth/cards/SignUp.tsx index f1663a8..79fb293 100644 --- a/src/components/default/auth/cards/SignUp.tsx +++ b/src/components/default/auth/cards/SignUp.tsx @@ -105,7 +105,7 @@ export const SignUpCard = () => { Already have an account?{' '} - Sign in + Sign In diff --git a/src/components/default/auth/index.ts b/src/components/default/auth/index.ts index 4fb68ac..2057985 100644 --- a/src/components/default/auth/index.ts +++ b/src/components/default/auth/index.ts @@ -1,4 +1,3 @@ -export * from './NoSession'; export * from './buttons/SignInSignUp'; export * from './buttons/SignInWithApple'; export * from './buttons/SignInWithMicrosoft'; diff --git a/src/components/default/footer/index.tsx b/src/components/default/footer/index.tsx index 7520e85..1abd519 100644 --- a/src/components/default/footer/index.tsx +++ b/src/components/default/footer/index.tsx @@ -15,24 +15,25 @@ const Footer = () => { flex items-center gap-2 transition-all duration-200' > Gitea - View Source Code on Gitea + View Source Code on Gitea
-
-

- Tech Tracker - Built for City of Gulfport IT Department -

+
+
+ Tech Tracker Logo +

+ Tech Tracker - City of Gulfport IT Department +

+

- Open Source • MIT Licensed • Self-Hosted • - - Powered by Supabase - + Open Source • MIT Licensed • Self-Hosted

diff --git a/src/components/default/header/auth/AvatarDropdown.tsx b/src/components/default/header/AvatarDropdown.tsx similarity index 73% rename from src/components/default/header/auth/AvatarDropdown.tsx rename to src/components/default/header/AvatarDropdown.tsx index f416da4..da160c4 100644 --- a/src/components/default/header/auth/AvatarDropdown.tsx +++ b/src/components/default/header/AvatarDropdown.tsx @@ -12,14 +12,15 @@ import { DropdownMenuSeparator, DropdownMenuTrigger, } from '@/components/ui'; -import { useAuth } from '@/components/context'; +import { useAuth, useTVMode } from '@/components/context'; import { useRouter } from 'next/navigation'; import { signOut } from '@/lib/actions'; import { User } from 'lucide-react'; const AvatarDropdown = () => { - const { profile, avatarUrl, isLoading, refreshUserData } = useAuth(); + const { profile, avatarUrl, refreshUserData } = useAuth(); const router = useRouter(); + const { toggleTVMode, tvMode } = useTVMode(); const handleSignOut = async () => { const result = await signOut(); @@ -41,7 +42,7 @@ const AvatarDropdown = () => { return ( - + {avatarUrl ? ( { height={64} /> ) : ( - + {profile?.full_name ? ( getInitials(profile.full_name) ) : ( - + )} )} - {profile?.full_name} + {profile?.full_name} + + + + { - const { isAuthenticated } = useAuth(); - return isAuthenticated ? ( -
- -
- ) : ( - - ); -}; -export default NavigationAuth; diff --git a/src/components/default/header/index.tsx b/src/components/default/header/index.tsx index e07332f..97ce36b 100644 --- a/src/components/default/header/index.tsx +++ b/src/components/default/header/index.tsx @@ -2,23 +2,27 @@ import Image from 'next/image'; import Link from 'next/link'; -import NavigationAuth from './auth'; -import { ThemeToggle, TVToggle, useTVMode } from '@/components/context'; +import { ThemeToggle, useTVMode } from '@/components/context'; +import { useAuth } from '@/components/context'; +import AvatarDropdown from './AvatarDropdown'; const Header = () => { const { tvMode } = useTVMode(); + const { isAuthenticated } = useAuth(); return tvMode ? ( -
-
- +
+
+
+ {isAuthenticated && } +
) : (
-
+
- - + + {isAuthenticated && }
{ />

Tech Tracker diff --git a/src/components/default/sentry/TestSentry.tsx b/src/components/default/sentry/TestSentry.tsx deleted file mode 100644 index 83eaf44..0000000 --- a/src/components/default/sentry/TestSentry.tsx +++ /dev/null @@ -1,126 +0,0 @@ -'use client'; - -import * as Sentry from '@sentry/nextjs'; -import { useState, useEffect } from 'react'; -import { - 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'; - } -} - -export const TestSentryCard = () => { - 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); - }); - }, []); - - 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. -

- - - ); -}; diff --git a/src/components/default/sentry/index.tsx b/src/components/default/sentry/index.tsx deleted file mode 100644 index d4c39cb..0000000 --- a/src/components/default/sentry/index.tsx +++ /dev/null @@ -1 +0,0 @@ -export { TestSentryCard } from './TestSentry'; diff --git a/src/server/docker/.env.example b/src/server/docker/.env.example index d3c04e1..26fa370 100644 --- a/src/server/docker/.env.example +++ b/src/server/docker/.env.example @@ -76,11 +76,11 @@ SMTP_SENDER_NAME=fake_sender ENABLE_ANONYMOUS_USERS=false -MAILER_TEMPLATES_INVITE="https://git.gbrown.org/gib/T3-Template/raw/branch/main/src/server/mail_templates/invite_user.html" -MAILER_TEMPLATES_CONFIRMATION="https://git.gbrown.org/gib/T3-Template/raw/branch/main/src/server/mail_templates/confirm_signup.html" -MAILER_TEMPLATES_RECOVERY="https://git.gbrown.org/gib/T3-Template/raw/branch/main/src/server/mail_templates/reset_password.html" -MAILER_TEMPLATES_MAGIC_LINK="https://git.gbrown.org/gib/T3-Template/raw/branch/main/src/server/mail_templates/magic_link.html" -MAILER_TEMPLATES_EMAIL_CHANGE="https://git.gbrown.org/gib/T3-Template/raw/branch/main/src/server/mail_templates/change_email_address.html" +MAILER_TEMPLATES_INVITE="https://git.gbrown.org/gib/tech-tracker-next/raw/branch/main/src/server/mail_templates/invite_user.html" +MAILER_TEMPLATES_CONFIRMATION="https://git.gbrown.org/gib/tech-tracker-next/raw/branch/main/src/server/mail_templates/confirm_signup.html" +MAILER_TEMPLATES_RECOVERY="https://git.gbrown.org/gib/tech-tracker-next/raw/branch/main/src/server/mail_templates/reset_password.html" +MAILER_TEMPLATES_MAGIC_LINK="https://git.gbrown.org/gib/tech-tracker-next/raw/branch/main/src/server/mail_templates/magic_link.html" +MAILER_TEMPLATES_EMAIL_CHANGE="https://git.gbrown.org/gib/tech-tracker-next/raw/branch/main/src/server/mail_templates/change_email_address.html" MAILER_SUBJECTS_INVITE="You've Been Invited!" MAILER_SUBJECTS_CONFIRMATION="Confirm Your Email" diff --git a/src/server/mail_templates/change_email_address.html b/src/server/mail_templates/change_email_address.html index ba090da..187061f 100644 --- a/src/server/mail_templates/change_email_address.html +++ b/src/server/mail_templates/change_email_address.html @@ -9,7 +9,7 @@
- Tech Tracker Logo + Tech Tracker Logo

Tech Tracker

@@ -17,24 +17,24 @@
- +

Confirm Change of Email

- +

Hello,

- +

We received a request to change your email address from {{ .Email }} to {{ .NewEmail }}.

- +

To confirm this change, please click the button below:

- + - +

If you didn't request this change, please contact support immediately.

- +

Tech Tracker - City of Gulfport