Skip to content

Commit 27a82c6

Browse files
authored
Merge pull request #203 from Pseudo-Lab/fix-frontend-my-info
feat(getcloser): implement API calls
2 parents 9f3a97d + 5ecbe75 commit 27a82c6

7 files changed

Lines changed: 188 additions & 42 deletions

File tree

getcloser/frontend/src/app/layout.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
'use client';
22

3-
import type { Metadata } from "next";
3+
44
import { Geist, Geist_Mono, Dongle } from "next/font/google";
55
import "./globals.css";
66
import { Providers } from './providers';

getcloser/frontend/src/app/page1/page.tsx

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,15 @@
11
'use client';
22

3-
import Link from 'next/link';
43
import Image from 'next/image';
54
import { Button } from '@/components/ui/button';
65
import { Input } from '@/components/ui/input';
76
import { Label } from '@/components/ui/label';
87
import { useFormStore } from '../../store/formStore';
9-
import { Rows } from 'lucide-react';
108

119
import { useRouter } from 'next/navigation';
1210

1311
export default function Page1() {
14-
const { email, setEmail } = useFormStore();
12+
const { email, setEmail, setId } = useFormStore();
1513
const router = useRouter();
1614

1715
const handleSubmit = async (e: React.FormEvent) => {
@@ -33,6 +31,9 @@ export default function Page1() {
3331

3432
const result = await response.json();
3533
console.log('Auth API Response:', result);
34+
if (result.id) {
35+
setId(result.id); // Store the ID in the form store
36+
}
3637
alert('정보가 제출되었습니다!');
3738
router.push('/page2'); // Navigate to page2 after successful submission
3839
} catch (error) {
@@ -49,7 +50,7 @@ export default function Page1() {
4950
<div className="items-center text-center" style={{marginBottom: 36}}>
5051
<p className="text-md mt-1">Pseudo Lab</p>
5152
<p className="text-md">2nd Grand Gathering</p>
52-
<p className="text-md">2025. 11. 15</p>
53+
<p className="text-md">2025. 12. 20</p>
5354
</div>
5455
<form className="space-y-4" onSubmit={handleSubmit}>
5556
<div className='items-center text-center'>

getcloser/frontend/src/app/page2/page.tsx

Lines changed: 93 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
'use client';
22

3-
import React, { useState, useEffect, useRef } from 'react';
3+
import React, { useState, useEffect, useRef, useCallback } from 'react';
44
import Link from 'next/link';
55
import { useRouter } from 'next/navigation';
66
import { Button } from '@/components/ui/button';
@@ -11,25 +11,64 @@ import Modal from '@/components/Modal';
1111
import Cookies from 'js-cookie';
1212

1313
export default function Page2() {
14-
const { email } = useFormStore();
14+
const { id, setTeamId, setMemberIds } = useFormStore();
1515
const router = useRouter();
16-
const [inputs, setInputs] = useState<string[]>(() => {
17-
const initialInputs = Array(5).fill('');
18-
if (email) {
19-
initialInputs[0] = email;
20-
}
16+
const [inputs, setInputs] = useState<Array<{ id: string; displayName: string }>>(() => {
17+
const initialInputs = Array(5).fill({ id: '', displayName: '' });
2118
return initialInputs;
2219
});
2320
const [showModal, setShowModal] = useState(false);
2421
const timeoutRef = useRef<NodeJS.Timeout | null>(null); // Add timeoutRef
2522

23+
const fetchUserById = useCallback(async (index: number, userId: string) => {
24+
if (!userId) return; // Don't fetch if ID is empty
25+
26+
// Prevent adding self as a team member
27+
if (index !== 0 && id && Number(userId) === Number(id)) { // Check only for other team members, not self
28+
alert(`자기 자신(${userId})을 팀원으로 추가할 수 없습니다.`);
29+
const newInputs = [...inputs];
30+
newInputs[index] = { id: '', displayName: '' }; // Clear the input field
31+
setInputs(newInputs);
32+
return;
33+
}
34+
35+
try {
36+
const response = await fetch(`/api/v1/users/${userId}`);
37+
if (!response.ok) {
38+
throw new Error(`HTTP error! status: ${response.status}`);
39+
}
40+
const userData = await response.json();
41+
// Assuming the API returns an object with a 'data' field which is the display name
42+
const newInputs = [...inputs];
43+
newInputs[index] = { id: userId, displayName: userData.data || userId }; // Store both id and display name
44+
setInputs(newInputs);
45+
} catch (error: unknown) {
46+
let errorMessage = '알 수 없는 오류가 발생했습니다.';
47+
if (error instanceof Error) {
48+
errorMessage = error.message;
49+
}
50+
console.error(`Error fetching user ${userId}:`, errorMessage);
51+
const newInputs = [...inputs];
52+
newInputs[index] = { id: userId, displayName: userId }; // Fallback to just ID if fetch fails
53+
setInputs(newInputs);
54+
// Optionally, display an error message to the user
55+
}
56+
}, [id, inputs]);
57+
2658
useEffect(() => {
2759
const hasSeenModal = Cookies.get('doNotShowModalPage2');
2860
if (!hasSeenModal) {
2961
setShowModal(true);
3062
}
3163
}, []);
3264

65+
// Effect to pre-fill the first input if ID is available
66+
useEffect(() => {
67+
if (id && inputs[0].id === '') { // Only fetch if ID exists and input is empty
68+
fetchUserById(0, id);
69+
}
70+
}, [id, fetchUserById, inputs]); // Rerun when id changes
71+
3372
const handleConfirm = () => {
3473
setShowModal(false);
3574
};
@@ -41,7 +80,7 @@ export default function Page2() {
4180

4281
const handleInputChange = (index: number, value: string) => {
4382
const newInputs = [...inputs];
44-
newInputs[index] = value;
83+
newInputs[index] = { id: value, displayName: value }; // Temporarily set display name to ID
4584
setInputs(newInputs);
4685

4786
// Clear any existing timeout
@@ -55,28 +94,53 @@ export default function Page2() {
5594
}, 3000);
5695
};
5796

58-
const fetchUserById = async (index: number, id: string) => {
59-
if (!id) return; // Don't fetch if ID is empty
97+
const handleSolveProblem = async () => {
98+
// Step 1: Check if all inputs are filled
99+
const allInputsFilled = inputs.every(input => input.id !== '');
100+
if (!allInputsFilled) {
101+
alert('모든 팀원 ID를 채워주세요.');
102+
return;
103+
}
104+
105+
// Step 2: Construct the JSON body
106+
const myId = id; // id from useFormStore()
107+
const memberIds = inputs.slice(1).map(input => input.id); // Exclude the first input (my_id)
108+
109+
const requestBody = {
110+
my_id: myId,
111+
member_ids: memberIds,
112+
};
113+
114+
console.log('Request Body:', requestBody);
115+
116+
// Step 3: Make the POST request
60117
try {
61-
const response = await fetch(`${process.env.APP_HOST}/v1/users/${id}`);
118+
const response = await fetch('/api/v1/teams/create', {
119+
method: 'POST',
120+
headers: {
121+
'Content-Type': 'application/json',
122+
},
123+
body: JSON.stringify(requestBody),
124+
});
125+
62126
if (!response.ok) {
63-
throw new Error(`HTTP error! status: ${response.status}`);
127+
const errorData = await response.json();
128+
throw new Error(`HTTP error! status: ${response.status}, message: ${errorData.detail || response.statusText}`);
64129
}
65-
const userData = await response.json();
66-
// Assuming the API returns an object with a 'detail' field as per user's confirmation
67-
const newInputs = [...inputs];
68-
newInputs[index] = userData.detail || id; // Use fetched detail, or original ID if not found
69-
setInputs(newInputs);
70-
} catch (error) {
71-
console.error(`Error fetching user ${id}:`, error);
72-
// Optionally, display an error message to the user
73-
}
74-
};
75130

76-
const handleSolveProblem = () => {
77-
console.log('Inputs:', inputs);
78-
// You can add logic here to process the inputs before navigating
79-
router.push('/page3');
131+
const responseData = await response.json();
132+
console.log('Team creation successful:', responseData);
133+
setTeamId(responseData.team_id); // Save team_id to store
134+
setMemberIds(responseData.members_ids); // Save members_ids to store
135+
router.push('/page3'); // Navigate to page3 on success
136+
} catch (error: unknown) {
137+
console.error('Error creating team:', error);
138+
let errorMessage = '알 수 없는 오류가 발생했습니다.';
139+
if (error instanceof Error) {
140+
errorMessage = error.message;
141+
}
142+
alert(`팀 생성에 실패했습니다: ${errorMessage}`);
143+
}
80144
};
81145

82146
return (
@@ -95,7 +159,6 @@ export default function Page2() {
95159
onDoNotShowAgain={handleDoNotShowAgain}
96160
isOpen={showModal}
97161
/>
98-
{email && <p className="mt-4">이메일: <strong>{email}</strong></p>}
99162

100163
<div className="mt-8 space-y-4">
101164
{[...Array(5)].map((_, index) => (
@@ -106,13 +169,13 @@ export default function Page2() {
106169
id={`team-id-${index}`}
107170
type="id"
108171
placeholder={`팀원 ${index + 1}의 번호`}
109-
value={inputs[index]}
172+
value={inputs[index].displayName}
110173
onChange={(e) => handleInputChange(index, e.target.value)}
111-
onBlur={(e) => {
174+
onBlur={() => {
112175
if (timeoutRef.current) {
113176
clearTimeout(timeoutRef.current); // Clear any pending debounce
114177
}
115-
fetchUserById(index, e.target.value); // Fetch immediately on blur
178+
fetchUserById(index, inputs[index].id); // Fetch immediately on blur using the stored ID
116179
}}
117180
disabled={index === 0}
118181
/>

getcloser/frontend/src/app/page3/page.tsx

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,58 @@ import { Button } from '@/components/ui/button';
66
import { Label } from '@/components/ui/label';
77
import { Textarea } from '@/components/ui/textarea';
88
import { useFormStore } from '../../store/formStore';
9-
import React from 'react';
9+
import React, { useEffect } from 'react'; // Import useEffect
1010

1111
export default function Page3() {
12-
const { question, answer, setAnswer } = useFormStore();
12+
const { question, answer, setAnswer, id, teamId, memberIds } = useFormStore(); // Destructure new state
1313
const router = useRouter();
1414

15+
useEffect(() => {
16+
const assignChallenges = async () => {
17+
if (!teamId || !id || memberIds.length === 0) {
18+
console.warn('Missing teamId, my_id, or memberIds for challenge assignment.');
19+
return;
20+
}
21+
22+
const requestBody = {
23+
team_id: teamId,
24+
my_id: id,
25+
members_ids: memberIds,
26+
};
27+
28+
console.log('Assign Challenges Request Body:', requestBody);
29+
30+
try {
31+
const response = await fetch('/api/v1/challenge/assign-challenges', {
32+
method: 'POST',
33+
headers: {
34+
'Content-Type': 'application/json',
35+
},
36+
body: JSON.stringify(requestBody),
37+
});
38+
39+
if (!response.ok) {
40+
const errorData = await response.json();
41+
throw new Error(`HTTP error! status: ${response.status}, message: ${errorData.detail || response.statusText}`);
42+
}
43+
44+
const responseData = await response.json();
45+
console.log('Challenge assignment successful:', responseData);
46+
// Here you might want to update the question in the store based on responseData
47+
// setQuestion(responseData.challenge_question);
48+
} catch (error: unknown) {
49+
console.error('Error assigning challenges:', error);
50+
let errorMessage = '알 수 없는 오류가 발생했습니다.';
51+
if (error instanceof Error) {
52+
errorMessage = error.message;
53+
}
54+
alert(`챌린지 할당에 실패했습니다: ${errorMessage}`);
55+
}
56+
};
57+
58+
assignChallenges();
59+
}, [id, teamId, memberIds]); // Dependencies for useEffect
60+
1561
const handleSubmit = (e: React.FormEvent) => {
1662
e.preventDefault();
1763
console.log('Question:', question, 'Answer:', answer);
Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,43 @@
11
'use client';
22

3-
import React from 'react';
3+
import React, { useState, useEffect } from 'react';
44
import { useFormStore } from '../store/formStore';
55

66
export default function Header() {
7-
const { email } = useFormStore();
7+
const { id } = useFormStore();
8+
const [userName, setUserName] = useState<string | null>(null);
9+
10+
useEffect(() => {
11+
if (id) {
12+
const fetchUserName = async () => {
13+
try {
14+
const response = await fetch(`/api/v1/users/${id}`);
15+
if (!response.ok) {
16+
throw new Error(`HTTP error! status: ${response.status}`);
17+
}
18+
const userData = await response.json();
19+
setUserName(userData.data || null);
20+
} catch (error) {
21+
console.error(`Error fetching user ${id}:`, error);
22+
setUserName(null); // Clear user name on error
23+
}
24+
};
25+
fetchUserName();
26+
} else {
27+
setUserName(null); // Clear user name if id is empty
28+
}
29+
}, [id]);
830

931
return (
1032
<header className="py-4 bg-background text-foreground border-b border-border">
1133
<h1 className="text-3xl font-bold">친해지길바라</h1>
1234
<p className="text-md mt-1 text-center" style={{ margin: 0, padding: 0, lineHeight: '1em' }}>
1335
Pseudo Lab<br />
1436
2nd Grand Gathering<br />
15-
2025. 11. 15
37+
2025. 12. 20
1638
</p>
17-
{email && <p className="text-sm mt-2"><strong>{email}</strong></p>}
39+
{id && userName && <p className="text-sm mt-2">ID: <strong>{id}</strong>, 이름: <strong>{userName}</strong></p>}
40+
{id && !userName && <p className="text-sm mt-2">ID: <strong>{id}</strong></p>}
1841
</header>
1942
);
2043
}

getcloser/frontend/src/store/formStore.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,30 @@ import { create } from 'zustand';
22

33
interface FormState {
44
email: string;
5+
id: string;
56
question: string;
67
answer: string;
8+
teamId: string;
9+
memberIds: string[];
710
setEmail: (email: string) => void;
11+
setId: (id: string) => void;
812
setQuestion: (question: string) => void;
913
setAnswer: (answer: string) => void;
14+
setTeamId: (teamId: string) => void;
15+
setMemberIds: (memberIds: string[]) => void;
1016
}
1117

1218
export const useFormStore = create<FormState>((set) => ({
1319
email: '',
20+
id: '',
1421
question: '',
1522
answer: '',
23+
teamId: '',
24+
memberIds: [],
1625
setEmail: (email) => set({ email }),
26+
setId: (id) => set({ id }),
1727
setQuestion: (question) => set({ question }),
1828
setAnswer: (answer) => set({ answer }),
29+
setTeamId: (teamId) => set({ teamId }),
30+
setMemberIds: (memberIds) => set({ memberIds }),
1931
}));

getcloser/frontend/tailwind.config.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type { Config } from 'tailwindcss';
22
import defaultTheme from 'tailwindcss/defaultTheme';
3+
import tailwindAnimate from 'tailwindcss-animate';
34

45
const config: Config = {
56
darkMode: false,
@@ -79,7 +80,7 @@ const config: Config = {
7980
},
8081
},
8182
},
82-
plugins: [require('tailwindcss-animate')],
83+
plugins: [tailwindAnimate],
8384
};
8485

8586
export default config;

0 commit comments

Comments
 (0)