Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 10 additions & 5 deletions getcloser/frontend/src/app/pages/Page1.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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('์ •๋ณด ์ œ์ถœ์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค.');
Expand Down
92 changes: 21 additions & 71 deletions getcloser/frontend/src/app/pages/Page2.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<View>('loading');
Expand Down Expand Up @@ -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 === '') {
Expand Down
97 changes: 29 additions & 68 deletions getcloser/frontend/src/app/pages/Page3.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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' },
Expand All @@ -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();
Expand All @@ -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',
Expand Down
43 changes: 24 additions & 19 deletions getcloser/frontend/src/app/pages/Page4.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<string>('');
Expand All @@ -44,15 +44,17 @@ export default function Page4() {
const [selectedMemberChallenge, setSelectedMemberChallenge] = useState<ChallengeResult | null>(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(() => {
Expand Down Expand Up @@ -166,21 +168,24 @@ export default function Page4() {
<div className="mt-8 text-left">
<h3 className="text-2xl font-bold mb-4">์šฐ๋ฆฌ ํŒ€์›๋“ค</h3>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{teamData.members.map((member) => (
<div key={member.user_id} className="bg-muted p-4 rounded-lg shadow-md cursor-pointer hover:bg-muted/80" onClick={() => handleMemberClick(member.user_id)}>
<p className="text-lg font-semibold">{member.name}</p>
{member.github_url && (
<p className="text-sm text-gray-600">
{teamData.members
.filter(member => member.id !== id) // Filter out current user's info
.map((member) => (
<div key={member.id} className="bg-muted p-4 rounded-lg shadow-md">
<p className="text-lg font-semibold">{member.name}</p>
<p className="text-sm text-gray-600">Email: {member.email}</p>
{member.github_url && (
<p className="text-sm text-gray-600">
GitHub: <a href={member.github_url} target="_blank" rel="noopener noreferrer" className="text-blue-500 hover:underline">{member.github_url}</a>
</p>
)}
{member.linkedin_url && (
<p className="text-sm text-gray-600">
</p>
)}
{member.linkedin_url && (
<p className="text-sm text-gray-600">
LinkedIn: <a href={member.linkedin_url} target="_blank" rel="noopener noreferrer" className="text-blue-500 hover:underline">{member.linkedin_url}</a>
</p>
)}
</div>
))}
</p>
)}
</div>
))}
</div>
</div>
)}
Expand Down
Loading
Loading