diff --git a/src/app/(frontend)/profile/page.tsx b/src/app/(frontend)/profile/page.tsx index 6b312b2..2d0e768 100644 --- a/src/app/(frontend)/profile/page.tsx +++ b/src/app/(frontend)/profile/page.tsx @@ -1,4 +1,4 @@ -"use client"; +'use client'; import { useForm } from 'react-hook-form'; import { SignupInput, signupSchema } from '@/lib/zod/schema/signupInput'; @@ -6,286 +6,260 @@ import { zodResolver } from '@hookform/resolvers/zod'; import FormInput from '@/components/ui/FormInput'; import FormSelect from '@/components/ui/FormSelect'; import { Button } from '@/components/ui/Button'; -import { useEffect, useState, useRef } from 'react'; import Image from 'next/image'; -import { useSession, signOut } from 'next-auth/react'; +import {useEffect, useRef, useState} from 'react'; import { useRouter } from 'next/navigation'; +import { signOut } from 'next-auth/react'; +import { useAuthenticatedMember } from '@/features/members/data/tanstack/useAuthenticatedMember'; +import type { Member } from '@/payload-types'; export default function Profile() { + const router = useRouter(); + const member = useAuthenticatedMember(); + const formInitialized = useRef(false); + const [showSavedMessage, setShowSavedMessage] = useState(false); + const savedMessageTimeout = useRef(null); - const [loading, setLoading] = useState(true); - const memberIdRef = useRef(null); // Store member ID + const mapMemberToForm = (member: Member): SignupInput => ({ + firstName: member.firstName, + lastName: member.lastName, + email: member.email, + gender: member.gender, + yearOfStudy: member.yearOfStudy ?? '1st Year', + ethnicity: member.ethnicity ?? '', + upi: member.upi ?? undefined, + membershipCardNumber: member.membershipCardNumber ?? undefined, + studentID: member.studentID ?? undefined, + notes: member.notes ?? undefined, + timestamp: new Date(member.timestamp), + }); - const { - register, - handleSubmit, - formState: { errors, isSubmitting, isDirty }, - reset, - watch, - } = useForm({ - resolver: zodResolver(signupSchema), - defaultValues: undefined, - }); + const formattedMember = member ? mapMemberToForm(member) : undefined; - const { data: session, status } = useSession(); - const router = useRouter(); + const { + register, + handleSubmit, + formState: { errors, isSubmitting, isDirty, isSubmitted }, + reset, + watch, + } = useForm({ + resolver: zodResolver(signupSchema), + defaultValues: undefined, + }); - // visible debug banner (remove in production) - useEffect(() => { - // eslint-disable-next-line no-console - console.log('useSession status:', status, 'session:', session); - }, [status, session]); + // Initialize form once when member data is loaded + useEffect(() => { + if (formattedMember && !formInitialized.current) { + reset(formattedMember); + formInitialized.current = true; + } + }, [formattedMember, reset]); + + // Redirect unauthenticated users + useEffect(() => { + if (member === null) { + router.replace('/'); + } + }, [member, router]); - // Redirect to home if not authenticated - useEffect(() => { - // wait until auth resolved - if (status === 'loading') return; - if (status === 'unauthenticated') { - // eslint-disable-next-line no-console - console.log('Not authenticated — redirecting to /'); - // router.replace('/'); - } - }, [status, router]); + const onSubmit = async (data: SignupInput) => { + if (!member?.id) { + alert('Member ID not found.'); + return; + } + try { + const res = await fetch(`/api/members/${member.id}`, { + method: 'PATCH', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(data), + }); + if (!res.ok) throw new Error('Failed to update profile'); + reset(data); - useEffect(() => { - async function fetchMember() { - setLoading(true); - // eslint-disable-next-line no-console - console.log('fetchMember start, session email:', session?.user?.email, 'status:', status); - if (status !== 'authenticated' || !session?.user?.email) { - if (session?.user?.email) { - // eslint-disable-next-line no-console - console.log('Prefilling email from session:', session.user.email); - reset({ email: session.user.email } as any); - } - setLoading(false); - return; - } - const email = session.user.email; - // eslint-disable-next-line no-console - console.log('Fetching member by email:', email); - try { - const res = await fetch(`/api/members?where[email][equals]=${encodeURIComponent(email)}`); - const data = await res.json(); - // eslint-disable-next-line no-console - console.log('Member fetch result:', data); - if (data.docs && data.docs.length > 0) { - const member = { - ...data.docs[0], - timestamp: new Date(data.docs[0].timestamp), - }; - memberIdRef.current = data.docs[0].id; - reset(member); - } else { - reset({ email: session?.user?.email } as any); - } - } catch (err) { - // eslint-disable-next-line no-console - console.error('fetchMember error:', err); - } finally { - setLoading(false); - } - } - fetchMember(); - }, [reset, session?.user?.email, status]); + // show success message + setShowSavedMessage(true); - const onSubmit = async (data: SignupInput) => { - if (!memberIdRef.current) { - alert('Member ID not found.'); - return; - } - try { - const res = await fetch(`/api/members/${memberIdRef.current}`, { - method: 'PATCH', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify(data), - }); - if (!res.ok) { - throw new Error('Failed to update profile'); - } - alert('Profile updated!'); - reset(data); - } catch (error: any) { - alert(error.message || 'An error occurred'); - } - }; + // clear previous timeout if it exists + if (savedMessageTimeout.current) { + clearTimeout(savedMessageTimeout.current); + } - if (loading) { - return
Loading...
; + savedMessageTimeout.current = setTimeout(() => { + setShowSavedMessage(false); + }, 3000); + } catch (error: any) { + alert(error.message || 'An error occurred'); } + }; + + if (!formattedMember) { + return
Loading...
; + } + + return ( +
+
+ ESA Signup + ESA Signup + ESA Signup +
+ +

Profile

+ +
+

View and edit your ESA profile!

+
- return ( -
- {/* Debug panel - remove when fixed */} - {/*
-
auth status: {status}
-
email: {session?.user?.email ?? 'none'}
-
*/} +
-
- +
+

+ {watch('firstName') || 'firstname'} +
+ {watch('lastName') || 'lastname'} +

- - {"ESA -
+
+
+ + +
- {/* Title/body text */} -

Profile

+
+ + + +
-
-

View and edit your ESA profile!

-
+ -
+
+ + +
- {/* Profile Edit Form */} -
-

- {watch('firstName') || 'firstname'} -
- {watch('lastName') || 'lastname'} -

+ - -
- - -
-
- - - -
- -
- - -
- -
-
- {/* signout */} - -
- - -
- -
-
- ); +
+
+ +
+ + +

{showSavedMessage && 'Profile Saved!'}

+
+ +
+
+ ); } diff --git a/src/collections/Members.ts b/src/collections/Members.ts index a8016dc..47492b1 100644 --- a/src/collections/Members.ts +++ b/src/collections/Members.ts @@ -3,6 +3,8 @@ import { addDataAndFileToRequest } from 'payload'; import { Member } from '@/payload-types'; import Papa from 'papaparse'; import { isTier3 } from '@/access/isTier3'; +import { getServerSession } from 'next-auth'; +import authOptions from '@/lib/auth/authOptions'; export const Members: CollectionConfig = { slug: 'members', @@ -12,7 +14,21 @@ export const Members: CollectionConfig = { access: { read: () => true, create: isTier3, - update: isTier3, + update: async ({ req }) => { + const { user } = req; + + if (user?.roles?.includes('tier3')) return true; + + const session = await getServerSession(authOptions) + const authEmail = session?.user?.email; + + // If the authenticated user's email matches the email of the member being updated, allow update + if (authEmail == req.data?.email) { + return true; + } + + return false; + }, delete: isTier3, }, endpoints: [