source: src/app/students/profile/page.tsx@ 997d094

Last change on this file since 997d094 was 90f7842, checked in by Stefan-Saveski <stefansaveski19@…>, 9 months ago

fix: Update API endpoints to use production URL for user data retrieval and authentication

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