diff --git a/getcloser/frontend/src/app/pages/Page1.tsx b/getcloser/frontend/src/app/pages/Page1.tsx index ceb1247..9ebc005 100644 --- a/getcloser/frontend/src/app/pages/Page1.tsx +++ b/getcloser/frontend/src/app/pages/Page1.tsx @@ -5,11 +5,9 @@ import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { useFormStore } from '../../store/formStore'; -import { useNavigationStore } from '../../store/navigationStore'; export default function Page1() { - const { email, setEmail, setId, setAccessToken } = useFormStore(); - const { setCurrentPage } = useNavigationStore(); + const { email, setEmail, setId, setAccessToken, setTeamId, setChallengeId, setProgressStatus } = useFormStore(); const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); @@ -54,8 +52,15 @@ export default function Page1() { if (userMeResult.sub) { setId(userMeResult.sub); } - alert('정보가 제출되었습니다!'); - setCurrentPage('page2'); + if (userMeResult.team_id) { + setTeamId(userMeResult.team_id); + } + if (userMeResult.challenge_id) { + setChallengeId(userMeResult.challenge_id); + } + if (userMeResult.progress_status) { + setProgressStatus(userMeResult.progress_status); + } } catch (error) { console.error('Error submitting form:', error); alert('정보 제출에 실패했습니다.'); diff --git a/getcloser/frontend/src/app/pages/Page2.tsx b/getcloser/frontend/src/app/pages/Page2.tsx index 1e53d8c..0c0bfb2 100644 --- a/getcloser/frontend/src/app/pages/Page2.tsx +++ b/getcloser/frontend/src/app/pages/Page2.tsx @@ -142,7 +142,7 @@ const CreateTeamView = ({ }; export default function Page2() { - const { id: myId, teamId, setTeamId, setMemberIds, reset } = useFormStore(); + const { id: myId, teamId, setTeamId, setMemberIds, progressStatus } = useFormStore(); const { setCurrentPage } = useNavigationStore(); const [view, setView] = useState('loading'); @@ -184,83 +184,33 @@ export default function Page2() { useEffect(() => { const initialize = async () => { - if (!myId) { - setCurrentPage('page1'); - return; - } - if (view !== 'loading') return; - - try { - const response = await authenticatedFetch('/api/v1/teams/me'); - - if (response.status === 404) { - setView('create'); - return; - } - - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}, message: ${(await response.json()).detail || response.statusText}`); - } - - const teamData = await response.json(); - - if (teamData && teamData.team_id) { - if (teamData.status === 'ACTIVE') { - setCurrentPage('page3'); - return; - } - - if (teamData.status === 'PENDING') { - setTeamId(teamData.team_id); - if (teamData.members && teamData.members.length > 0) { - const me = teamData.members.find((m: {id: number}) => Number(m.id) === Number(myId)); - const others = teamData.members.filter((m: {id: number}) => Number(m.id) !== Number(myId)); - const newInputs: InputState[] = Array(TEAM_SIZE).fill({ id: '', displayName: '' }); - - if (me) { - newInputs[0] = { id: String(me.id), displayName: me.name }; - } else { - newInputs[0] = { id: String(myId), displayName: '' }; // Fallback - } - - others.forEach((member: { id: number; name: string }, i: number) => { - if (i + 1 < TEAM_SIZE) { - newInputs[i + 1] = { id: String(member.id), displayName: member.name }; - } - }); - - const initialTeamMembers: TeamMember[] = teamData.members.map((member: { id: number; name: string; }) => ({ - user_id: Number(member.id), - displayName: member.name, - is_ready: false, - })); - - setInputs(newInputs); - setTeamMembers(initialTeamMembers); - setView('waiting'); - } else { - setView('create'); - } - return; - } - } + if (progressStatus === 'NONE_TEAM') { setView('create'); - } catch (error) { - console.error('Error during page initialization:', error); - if (teamId && teamId > 0) { - try { - await authenticatedFetch(`/api/v1/teams/${teamId}/cancel`, { method: 'POST' }); - } catch (cancelError) { - console.error(`Failed to leave team ${teamId} after init error:`, cancelError); + } else if (progressStatus === 'TEAM_WAITING') { + try { + const response = await authenticatedFetch('/api/v1/teams/me'); + if (!response.ok) { + throw new Error('Failed to fetch team data'); } + const teamData = await response.json(); + if (teamData && teamData.members) { + const initialTeamMembers: TeamMember[] = teamData.members.map((member: { id: number; name: string; }) => ({ + user_id: Number(member.id), + displayName: member.name, + is_ready: false, + })); + setTeamMembers(initialTeamMembers); + } + setView('waiting'); + } catch (error) { + console.error('Error fetching team data for waiting view:', error); + setView('create'); // Fallback to create view } - reset(); - setView('create'); } }; initialize(); - }, [myId, view, setCurrentPage, setTeamId, setInputs, setTeamMembers, reset]); + }, [progressStatus]); useEffect(() => { if (myId && inputs[0].id === '') { diff --git a/getcloser/frontend/src/app/pages/Page3.tsx b/getcloser/frontend/src/app/pages/Page3.tsx index 5f99218..9a86f38 100644 --- a/getcloser/frontend/src/app/pages/Page3.tsx +++ b/getcloser/frontend/src/app/pages/Page3.tsx @@ -40,10 +40,18 @@ export default function Page3() { const { setCurrentPage } = useNavigationStore(); useEffect(() => { - const CHALLENGE_DATA_KEY = 'challengeData'; - const initializeChallenge = async () => { - if (!question && id && teamId) { + // If a question is already loaded, do nothing. + if (question) { + console.log('Question already exists, skipping initialization.'); + return; + } + + // If we are on page3, we should have the necessary IDs. + // If not, something is wrong, but providers.tsx should redirect. + if (id && teamId) { + console.log('No question found in store. Fetching/assigning challenge from server...'); + // Fetch team members to get their names let members: TeamMember[] = []; try { @@ -68,30 +76,9 @@ export default function Page3() { return member ? member.name : String(userId); // Fallback to user_id as string }; - // 1. Try to restore challenge from localStorage - const savedChallengeJSON = localStorage.getItem(CHALLENGE_DATA_KEY); - if (savedChallengeJSON) { - console.log('Restoring challenge from localStorage...'); - try { - const savedChallenge = JSON.parse(savedChallengeJSON); - if (savedChallenge.category && savedChallenge.assigned_challenge_id) { - const questionInfo = questions.find((q) => q.category === String(savedChallenge.category)); - if (questionInfo) { - const memberName = findMemberName(savedChallenge.user_id); - setQuestion([memberName, questionInfo.problem].join(' ')); - setChallengeId(savedChallenge.assigned_challenge_id); - return; // Challenge successfully restored - } - } - } catch (e) { - console.error('Failed to parse challenge data from localStorage', e); - localStorage.removeItem(CHALLENGE_DATA_KEY); // Clear corrupted data - } - } - - // 2. If no valid saved data, assign a new challenge - console.log('No valid saved challenge found. Assigning a new one...'); try { + // This endpoint will assign a new challenge if one doesn't exist, + // or it should ideally return the existing one. const assignResponse = await authenticatedFetch('/api/v1/challenges/assign', { method: 'POST', headers: { 'Content-Type': 'application/json' }, @@ -100,64 +87,40 @@ export default function Page3() { if (!assignResponse.ok) { const errorData = await getJsonFromResponse(assignResponse); - throw new Error(`Failed to assign challenge: ${errorData.detail}`); + throw new Error(`Failed to assign/fetch challenge: ${errorData.detail}`); } const newChallengeData = await assignResponse.json(); - console.log('Successfully assigned new challenge:', newChallengeData); + console.log('Successfully assigned/fetched challenge:', newChallengeData); if (newChallengeData.my_assigned && newChallengeData.my_assigned.category && newChallengeData.my_assigned.assigned_challenge_id) { - const dataToSave = { - category: String(newChallengeData.my_assigned.category), - assigned_challenge_id: newChallengeData.my_assigned.assigned_challenge_id, - user_id: newChallengeData.my_assigned.user_id, - }; - - // Save to localStorage for future restoration - localStorage.setItem(CHALLENGE_DATA_KEY, JSON.stringify(dataToSave)); - console.log('Saved new challenge to localStorage.'); - - // Set state from the new challenge data - const questionInfo = questions.find((q) => q.category === dataToSave.category); + const { user_id: userId, category, assigned_challenge_id } = newChallengeData.my_assigned; + + // Set state from the challenge data + const questionInfo = questions.find((q) => q.category === String(category)); if (questionInfo) { - const memberName = findMemberName(dataToSave.user_id); + const memberName = findMemberName(userId); setQuestion([memberName, questionInfo.problem].join(' ')); - setChallengeId(dataToSave.assigned_challenge_id); + setChallengeId(assigned_challenge_id); + } else { + throw new Error(`Could not find question for category: ${category}`); } } else { throw new Error('Assigned challenge data is incomplete or malformed.'); } } catch (error) { - console.error('Error assigning challenge:', error); - // General error handling: leave team, reset state, clear local storage - if (teamId && teamId > 0) { - try { - console.log(`Attempting to leave team ${teamId} due to error...`); - await authenticatedFetch(`/api/v1/teams/${teamId}/cancel`, { method: 'POST' }); - console.log(`Successfully left team ${teamId}.`); - } catch (cancelError) { - console.error(`Failed to leave team ${teamId}:`, cancelError); - } - } - localStorage.removeItem(CHALLENGE_DATA_KEY); + console.error('Error in challenge initialization:', error); + // Attempt to recover by going back to team formation reset(); - localStorage.removeItem('lastPage'); - setCurrentPage('page1'); + setCurrentPage('page2'); } - } else if (question) { - console.log('Question already exists, skipping initialization.'); - } else { - console.log('Missing id or teamId, cannot initialize challenge. Resetting.'); - localStorage.removeItem(CHALLENGE_DATA_KEY); - reset(); - localStorage.removeItem('lastPage'); - setCurrentPage('page1'); } }; initializeChallenge(); - }, [id, teamId, question, memberIds, setQuestion, setChallengeId, reset, setCurrentPage]); - + // Added challengeId to dependency array to react to changes if needed, + // though the main trigger is the absence of `question`. + }, [id, teamId, question, memberIds, setQuestion, setChallengeId, reset, setCurrentPage, challengeId]); const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); @@ -168,8 +131,6 @@ export default function Page3() { submitted_answer: answer, }; - console.log(JSON.stringify(requestBody)); - try { const response = await authenticatedFetch('/api/v1/challenges/submit', { method: 'POST', diff --git a/getcloser/frontend/src/app/pages/Page4.tsx b/getcloser/frontend/src/app/pages/Page4.tsx index 3ce2794..1c4e740 100644 --- a/getcloser/frontend/src/app/pages/Page4.tsx +++ b/getcloser/frontend/src/app/pages/Page4.tsx @@ -34,7 +34,7 @@ interface ChallengeResult { } export default function Page4() { - const { id, isCorrect } = useFormStore(); // Added isCorrect + const { id, progressStatus } = useFormStore(); // Use progressStatus const { setCurrentPage } = useNavigationStore(); const [result, setResult] = useState(''); @@ -44,15 +44,17 @@ export default function Page4() { const [selectedMemberChallenge, setSelectedMemberChallenge] = useState(null); const [isModalOpen, setIsModalOpen] = useState(false); - // This useEffect will set the initial success/failure based on isCorrect from the store + // This useEffect will set the initial success/failure based on progressStatus from the store useEffect(() => { - if (isCorrect === null) { - console.warn('isCorrect is not set, defaulting to 실패'); + if (progressStatus === 'CHALLENGE_SUCCESS' || progressStatus === 'REDEEMED') { + setResult('성공'); + } else if (progressStatus === 'CHALLENGE_FAILED') { setResult('실패'); } else { - setResult(isCorrect ? '성공' : '실패'); + console.warn('progressStatus is not set to a known success/failure state, defaulting to 실패'); + setResult('실패'); } - }, [isCorrect]); // Depend on isCorrect from the store + }, [progressStatus]); // Depend on progressStatus from the store // New useEffect to fetch team data when result is '성공' useEffect(() => { @@ -166,21 +168,24 @@ export default function Page4() {

우리 팀원들

- {teamData.members.map((member) => ( -
handleMemberClick(member.user_id)}> -

{member.name}

- {member.github_url && ( -

+ {teamData.members + .filter(member => member.id !== id) // Filter out current user's info + .map((member) => ( +

+

{member.name}

+

Email: {member.email}

+ {member.github_url && ( +

GitHub: {member.github_url} -

- )} - {member.linkedin_url && ( -

+

+ )} + {member.linkedin_url && ( +

LinkedIn: {member.linkedin_url} -

- )} -
- ))} +

+ )} +
+ ))}
)} diff --git a/getcloser/frontend/src/app/providers.tsx b/getcloser/frontend/src/app/providers.tsx index 33252fd..003fa11 100644 --- a/getcloser/frontend/src/app/providers.tsx +++ b/getcloser/frontend/src/app/providers.tsx @@ -3,59 +3,114 @@ import React, { useEffect } from 'react'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { useNavigationStore } from '../store/navigationStore'; +import { authenticatedFetch } from '../lib/api'; import { useFormStore } from '../store/formStore'; const queryClient = new QueryClient(); function StoreInitializer() { useEffect(() => { - // Function to run on initial load - const initialize = () => { - if (typeof window !== 'undefined') { - const savedPage = localStorage.getItem('lastPage'); - if (savedPage) { - useNavigationStore.getState().setCurrentPage(savedPage); + const { setProgressStatus, setTeamId, setChallengeId, setId } = useFormStore.getState(); + + const fetchUserStatus = async () => { + try { + const userMeResponse = await authenticatedFetch('/api/v1/users/me'); + if (!userMeResponse.ok) { + throw new Error(`HTTP error! status: ${userMeResponse.status}`); } + const userMeResult = await userMeResponse.json(); + console.log('User Me API Response from provider:', userMeResult); - const accessToken = useFormStore.getState().accessToken; - const currentPage = useNavigationStore.getState().currentPage; + if (userMeResult.sub) setId(userMeResult.sub); + if (userMeResult.team_id) setTeamId(userMeResult.team_id); + if (userMeResult.challenge_id) setChallengeId(userMeResult.challenge_id); + if (userMeResult.progress_status) setProgressStatus(userMeResult.progress_status); - // If no accessToken and not already on 'page1', redirect to 'page1' - if (!accessToken && currentPage !== 'page1') { - useNavigationStore.getState().setCurrentPage('page1'); - localStorage.removeItem('lastPage'); // Clear lastPage on auth redirect - } + } catch (error) { + console.error('Error fetching user status:', error); + // Could handle token invalidation here } }; - initialize(); - - // Subscribe to store changes and save to localStorage - const unsubscribe = useNavigationStore.subscribe( - (state) => { - if (typeof window !== 'undefined') { - localStorage.setItem('lastPage', state.currentPage); + // Function to run on initial load + const initialize = () => { + if (typeof window !== 'undefined') { + const { accessToken, progressStatus } = useFormStore.getState(); + const { currentPage, setCurrentPage } = useNavigationStore.getState(); + if (accessToken) { + if (progressStatus) { + switch (progressStatus) { + case 'NONE_TEAM': + case 'TEAM_WAITING': + if (currentPage !== 'page2') setCurrentPage('page2'); + break; + case 'CHALLENGE_ASSIGNED': + if (currentPage !== 'page3') setCurrentPage('page3'); + break; + case 'CHALLENGE_SUCCESS': + case 'CHALLENGE_FAILED': + case 'REDEEMED': + if (currentPage !== 'page4') setCurrentPage('page4'); + break; + default: + if (currentPage === '' || currentPage === 'page1') { + // Potentially stale or unrecognized status, refresh + fetchUserStatus(); + } + break; + } + } else { + // No progressStatus, fetch it + fetchUserStatus(); + } + } else { + // If no accessToken and not already on 'page1', redirect to 'page1' + if (currentPage !== 'page1') { + setCurrentPage('page1'); + } } } - ); + }; - // Also subscribe to formStore for accessToken changes + initialize(); + + // Subscribe to formStore for changes const unsubscribeFormStore = useFormStore.subscribe( (state, prevState) => { if (typeof window !== 'undefined') { - const accessToken = state.accessToken; - const currentPage = useNavigationStore.getState().currentPage; - // If the access token is cleared and we are not on page1, navigate to page1 - if (!accessToken && prevState.accessToken && currentPage !== 'page1') { - useNavigationStore.getState().setCurrentPage('page1'); - localStorage.removeItem('lastPage'); // Clear lastPage on auth redirect + const { accessToken, progressStatus } = state; + const { setCurrentPage, currentPage } = useNavigationStore.getState(); + + // Handle login/logout + if (!accessToken && prevState.accessToken) { + // User just logged out + if (currentPage !== 'page1') { + setCurrentPage('page1'); + } + } + + // Handle progressStatus changes + if (progressStatus && progressStatus !== prevState.progressStatus) { + switch (progressStatus) { + case 'NONE_TEAM': + case 'TEAM_WAITING': + setCurrentPage('page2'); + break; + case 'CHALLENGE_ASSIGNED': + setCurrentPage('page3'); + break; + case 'CHALLENGE_SUCCESS': + case 'CHALLENGE_FAILED': + case 'REDEEMED': + setCurrentPage('page4'); + break; + } } } } ); return () => { - unsubscribe(); unsubscribeFormStore(); }; }, []); diff --git a/getcloser/frontend/src/store/formStore.ts b/getcloser/frontend/src/store/formStore.ts index 006d1f0..ad8a461 100644 --- a/getcloser/frontend/src/store/formStore.ts +++ b/getcloser/frontend/src/store/formStore.ts @@ -11,6 +11,7 @@ interface FormState { teamId: number; memberIds: number[]; isCorrect: boolean | null; + progressStatus: string; setEmail: (email: string) => void; setId: (id: number) => void; setAccessToken: (accessToken: string) => void; @@ -20,6 +21,7 @@ interface FormState { setTeamId: (teamId: number) => void; setMemberIds: (memberIds: number[]) => void; setIsCorrect: (isCorrect: boolean) => void; + setProgressStatus: (progressStatus: string) => void; reset: () => void; } @@ -33,6 +35,7 @@ const initialState = { teamId: 0, memberIds: [], isCorrect: null, + progressStatus: '', }; export const useFormStore = create()( @@ -48,6 +51,7 @@ export const useFormStore = create()( setTeamId: (teamId) => set({ teamId }), setMemberIds: (memberIds) => set({ memberIds }), setIsCorrect: (isCorrect) => set({ isCorrect }), + setProgressStatus: (progressStatus) => set({ progressStatus }), reset: () => set(initialState), }), {