source: src/app/students/profile/page.tsx@ 50f2fb4

Last change on this file since 50f2fb4 was 50f2fb4, checked in by Stefan-Saveski <stefansaveski19@…>, 8 months ago

Added dual language feature.

  • Property mode set to 100644
File size: 11.0 KB
Line 
1"use client"
2
3import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
4import {
5 faUser,
6 faIdCard,
7 faGraduationCap,
8 faAddressCard,
9 faSchool,
10 faCalendarAlt,
11 faFlag,
12 faVenus,
13 faMars,
14 faEnvelope,
15 faPhone,
16 faMapMarkerAlt,
17 faPassport
18} from '@fortawesome/free-solid-svg-icons';
19import { useEffect, useState } from 'react';
20import { getAccessToken } from '@/lib/auth';
21import { IconDefinition } from '@fortawesome/fontawesome-svg-core';
22import { useTranslation } from 'react-i18next';
23
24type PersonalInfo = {
25 firstName: string;
26 middleName: string;
27 lastName: string;
28 maidenName: string;
29 dateOfBirth: string;
30 gender: string;
31 nationality: string;
32 citizenship: string;
33 scholarship: string;
34 currentPlan: string;
35 registryNumber: string;
36 studyGroup: string;
37 notes?: string;
38 index: string;
39 embg: string;
40};
41
42type BirthInfo = {
43 placeOfBirth: string;
44 municipalityOfBirth: string;
45 country: string;
46};
47
48type PreviousEducation = {
49 type: string;
50 profession: string;
51 average: string | number;
52 language: string;
53 country: string;
54 previousUniversity: string;
55 previousFaculty: string;
56 previousStudyMode: string;
57};
58
59type EnrollmentInfo = {
60 enrollmentYear: string | number;
61 status: string;
62 cycle: string;
63 program: string;
64 quota: string;
65 secondaryEducationNumber: string;
66 previousEducationCredits: string | number;
67};
68
69type Contact = {
70 placeOfResidence: string;
71 municipalityOfResidence: string;
72 country: string;
73 address: string;
74 temporaryAddress: string;
75 phone: string;
76 mobilePhone: string;
77 passportNumber: string;
78 passportExpiryDate: string;
79 email: string;
80 microsoftEmail: string;
81};
82
83type StudentProfile = {
84 personalInfo: PersonalInfo;
85 birthInfo: BirthInfo;
86 previousEducation: PreviousEducation;
87 enrollmentInfo: EnrollmentInfo;
88 contact: Contact;
89};
90
91interface InfoRowProps {
92 label: string;
93 value: string | number;
94 icon?: IconDefinition;
95}
96
97const InfoRow = ({ label, value, icon }: InfoRowProps) => {
98 const { t } = useTranslation();
99 return (
100 <div className="flex justify-between items-center py-3 border-b border-gray-100 last:border-b-0">
101 <div className="flex items-center gap-2 text-gray-600 font-medium">
102 {icon && <FontAwesomeIcon icon={icon} className="w-4 h-4" />}
103 <span>{t(label)}:</span>
104 </div>
105 <div className="text-gray-900 font-semibold text-right max-w-xs break-words">
106 {value || t('n_a')}
107 </div>
108 </div>
109 );
110};
111
112interface SectionProps {
113 title: string;
114 icon: IconDefinition;
115 children: React.ReactNode;
116}
117
118const Section = ({ title, icon, children }: SectionProps) => {
119 const { t } = useTranslation();
120 return (
121 <div className="bg-white rounded-xl shadow-sm border border-gray-100 overflow-hidden">
122 <div className="bg-primary text-white px-6 py-4">
123 <div className="flex items-center gap-3">
124 <FontAwesomeIcon icon={icon} className="text-xl" />
125 <h2 className="text-xl font-bold">{t(title)}</h2>
126 </div>
127 </div>
128 <div className="p-6">
129 {children}
130 </div>
131 </div>
132 );
133};
134
135export default function ProfilePage() {
136 const [studentData, setStudentData] = useState<StudentProfile | null>(null);
137 const [isLoading, setIsLoading] = useState(true);
138 const [errorMessage, setErrorMessage] = useState<string | null>(null);
139 const { t } = useTranslation();
140
141 useEffect(() => {
142 let cancelled = false;
143
144 async function load() {
145 setIsLoading(true);
146 setErrorMessage(null);
147
148 const token = getAccessToken();
149 if (!token) {
150 setErrorMessage('Not authenticated. Please login again.');
151 setIsLoading(false);
152 return;
153 }
154
155 try {
156 const response = await fetch('https://iknow-api.onrender.com/api/user/getUser', {
157 method: 'GET',
158 headers: {
159 Authorization: `Bearer ${token}`,
160 },
161 });
162
163 if (!response.ok) {
164 const text = await response.text().catch(() => '');
165 throw new Error(text || `Failed to load profile (${response.status})`);
166 }
167
168 const data = (await response.json()) as StudentProfile;
169 if (!cancelled) setStudentData(data);
170 } catch (err) {
171 if (!cancelled) {
172 setErrorMessage(err instanceof Error ? err.message : 'Failed to load profile.');
173 }
174 } finally {
175 if (!cancelled) setIsLoading(false);
176 }
177 }
178
179 void load();
180
181 return () => {
182 cancelled = true;
183 };
184 }, []);
185
186 if (isLoading) {
187 return (
188 <div className="min-h-screen pb-8">
189 <div className="bg-white rounded-xl shadow-sm border border-gray-100 p-6">
190 {t('loading')}
191 </div>
192 </div>
193 );
194 }
195
196 if (errorMessage || !studentData) {
197 return (
198 <div className="min-h-screen pb-8">
199 <div className="rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700">
200 {errorMessage ?? t('failed_to_load_profile')}
201 </div>
202 </div>
203 );
204 }
205
206 const { personalInfo, birthInfo, previousEducation, enrollmentInfo, contact } = studentData;
207
208 return (
209 <div className="min-h-screen pb-8">
210 {/* Header */}
211 <div className="bg-primary text-white rounded-xl p-8 mb-8">
212 <div className="flex items-center gap-6">
213 <div className="relative">
214 <div className="w-20 h-20 bg-white bg-opacity-20 rounded-full flex items-center justify-center border-2 border-white border-opacity-30">
215 <FontAwesomeIcon icon={faUser} className="text-3xl text-primary" />
216 </div>
217 <div className="absolute -bottom-1 -right-1 w-6 h-6 bg-green-500 rounded-full border-2 border-white"></div>
218 </div>
219 <div>
220 <h1 className="text-3xl font-bold mb-2">
221 {personalInfo.firstName} {personalInfo.middleName} {personalInfo.lastName}
222 </h1>
223 <div className="text-lg opacity-90">
224 {t('index')}: {personalInfo.index} | {t('embg')}: {personalInfo.embg}
225 </div>
226 <div className="text-base opacity-80 mt-1">
227 {enrollmentInfo.program}
228 </div>
229 </div>
230 </div>
231 </div>
232
233 {/* Profile Sections */}
234 <div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
235
236 {/* Personal Information */}
237 <Section title="personal_info" icon={faIdCard}>
238 <InfoRow label="first_name" value={personalInfo.firstName} />
239 <InfoRow label="middle_name" value={personalInfo.middleName} />
240 <InfoRow label="last_name" value={personalInfo.lastName} />
241 <InfoRow label="maiden_name" value={personalInfo.maidenName} />
242 <InfoRow
243 label="date_of_birth"
244 value={personalInfo.dateOfBirth}
245 icon={faCalendarAlt}
246 />
247 <InfoRow
248 label="gender"
249 value={personalInfo.gender}
250 icon={personalInfo.gender === t('male') ? faMars : faVenus}
251 />
252 <InfoRow
253 label="nationality"
254 value={personalInfo.nationality}
255 icon={faFlag}
256 />
257 <InfoRow label="citizenship" value={personalInfo.citizenship} />
258 <InfoRow label="scholarship" value={personalInfo.scholarship} />
259 <InfoRow label="current_plan" value={personalInfo.currentPlan} />
260 <InfoRow label="registry_number" value={personalInfo.registryNumber} />
261 <InfoRow label="study_group" value={personalInfo.studyGroup} />
262 {personalInfo.notes && (
263 <div className="mt-4 p-4 bg-blue-50 rounded-lg">
264 <div className="text-sm font-medium text-blue-800 mb-1">{t('note')}:</div>
265 <div className="text-sm text-blue-700">{personalInfo.notes}</div>
266 </div>
267 )}
268 </Section>
269
270 {/* Birth Information */}
271 <Section title="birth_info" icon={faMapMarkerAlt}>
272 <InfoRow label="place_of_birth" value={birthInfo.placeOfBirth} />
273 <InfoRow label="municipality_of_birth" value={birthInfo.municipalityOfBirth} />
274 <InfoRow label="country" value={birthInfo.country} />
275 </Section>
276
277 {/* Previous Education */}
278 <Section title="previous_education" icon={faSchool}>
279 <InfoRow label="type" value={previousEducation.type} />
280 <InfoRow label="profession" value={previousEducation.profession} />
281 <InfoRow label="average" value={previousEducation.average} />
282 <InfoRow label="language" value={previousEducation.language} />
283 <InfoRow label="country" value={previousEducation.country} />
284 <InfoRow label="previous_university" value={previousEducation.previousUniversity} />
285 <InfoRow label="previous_faculty" value={previousEducation.previousFaculty} />
286 <InfoRow label="previous_study_mode" value={previousEducation.previousStudyMode} />
287 </Section>
288
289 {/* Enrollment Information */}
290 <Section title="enrollment_info" icon={faGraduationCap}>
291 <InfoRow label="enrollment_year" value={enrollmentInfo.enrollmentYear} />
292 <InfoRow label="status" value={enrollmentInfo.status} />
293 <InfoRow label="cycle" value={enrollmentInfo.cycle} />
294 <InfoRow label="program" value={enrollmentInfo.program} />
295 <InfoRow label="quota" value={enrollmentInfo.quota} />
296 <InfoRow label="secondary_education_number" value={enrollmentInfo.secondaryEducationNumber} />
297 <InfoRow label="previous_education_credits" value={enrollmentInfo.previousEducationCredits} />
298 </Section>
299
300 {/* Contact Information */}
301 <div className="lg:col-span-2">
302 <Section title="contact" icon={faAddressCard}>
303 <div className="grid grid-cols-1 md:grid-cols-2 gap-6">
304 <div>
305 <InfoRow
306 label="place_of_residence"
307 value={contact.placeOfResidence}
308 icon={faMapMarkerAlt}
309 />
310 <InfoRow label="municipality_of_residence" value={contact.municipalityOfResidence} />
311 <InfoRow label="country" value={contact.country} />
312 <InfoRow label="address" value={contact.address} />
313 <InfoRow label="temporary_address" value={contact.temporaryAddress} />
314 </div>
315 <div>
316 <InfoRow label="phone" value={contact.phone} icon={faPhone} />
317 <InfoRow label="mobile_phone" value={contact.mobilePhone} icon={faPhone} />
318 <InfoRow label="passport_number" value={contact.passportNumber} icon={faPassport} />
319 <InfoRow label="passport_expiry_date" value={contact.passportExpiryDate} />
320 <InfoRow
321 label="email"
322 value={contact.email}
323 icon={faEnvelope}
324 />
325 <InfoRow
326 label="microsoft_email"
327 value={contact.microsoftEmail}
328 icon={faEnvelope}
329 />
330 </div>
331 </div>
332 </Section>
333 </div>
334 </div>
335 </div>
336 );
337}
Note: See TracBrowser for help on using the repository browser.