source: petify-frontend/src/api/profile.ts@ f6ed6e4

Last change on this file since f6ed6e4 was f6ed6e4, checked in by veronika-ils <ilioskaveronika@…>, 17 hours ago

feat: The app is consistent with the changes made in phase 1 and phase 2

  • Property mode set to 100644
File size: 19.3 KB
Line 
1function getBaseUrl(): string {
2 const base = (import.meta.env.VITE_API_BASE_URL as string | undefined) ?? ''
3 return base.replace(/\/$/, '')
4}
5
6function joinUrl(base: string, path: string): string {
7 if (!base) return path
8 return `${base}${path.startsWith('/') ? '' : '/'}${path}`
9}
10
11export interface UserProfile {
12 userId: number
13 username: string
14 email: string
15 firstName: string
16 lastName: string
17 verified?: boolean
18}
19
20export interface Pet {
21 animalId: number
22 name: string
23 sex: string
24 dateOfBirth?: string
25 photoUrl?: string
26 type: string
27 species: string
28 breed?: string
29 locatedName?: string
30}
31
32export interface VetClinic {
33 clinicId: number
34 name: string
35 city: string
36 address: string
37 workDays?: string
38 startTime?: string
39 endTime?: string
40 scheduleComplete?: boolean
41}
42
43export interface OwnerAppointment {
44 appointmentId: number
45 clinicId: number
46 clinicName?: string
47 clinicCity?: string
48 clinicAddress?: string
49 animalId: number
50 petName?: string
51 petSpecies?: string
52 petPhotoUrl?: string
53 status: string
54 dateTime: string
55 notes?: string
56}
57
58export interface AppointmentSlot {
59 dateTime: string
60 label: string
61}
62
63export interface ClinicAppointment {
64 appointmentId: number
65 clinicId: number
66 animalId: number
67 petName?: string
68 petSpecies?: string
69 ownerId: number
70 ownerName?: string
71 status: string
72 dateTime: string
73 label: string
74 notes?: string
75}
76
77export interface AppNotification {
78 notificationId: number
79 type: string
80 message: string
81 isRead: boolean
82 createdAt: string
83}
84
85export interface ClinicUnavailableSlot {
86 slotId: number
87 clinicId: number
88 dateTime: string
89 label: string
90 reason?: string
91}
92
93export interface HealthRecord {
94 healthRecordId: number
95 animalId: number
96 animalName?: string
97 appointmentId: number
98 clinicId?: number
99 clinicName?: string
100 type: string
101 description?: string
102 date: string
103 appointmentDateTime?: string
104}
105
106async function readJsonOrError<T>(response: Response, fallback: string): Promise<T> {
107 const text = await response.text()
108 let parsed: any = null
109
110 if (text) {
111 try {
112 parsed = JSON.parse(text)
113 } catch {
114 parsed = null
115 }
116 }
117
118 if (!response.ok) {
119 throw new Error(parsed?.error || `${fallback}: ${response.status} ${response.statusText}. ${text.slice(0, 200)}`)
120 }
121
122 return parsed as T
123}
124
125export async function getUserProfile(userId: number): Promise<UserProfile> {
126 const url = joinUrl(getBaseUrl(), `/api/users/${userId}`)
127 const response = await fetch(url, {
128 method: 'GET',
129 headers: {
130 'Content-Type': 'application/json',
131 },
132 })
133
134 if (!response.ok) {
135 throw new Error(`Failed to fetch user profile: ${response.statusText}`)
136 }
137
138 return await response.json()
139}
140
141export async function getUserListings(userId: number): Promise<any[]> {
142 const url = joinUrl(getBaseUrl(), `/api/listings/my-listings`)
143 const response = await fetch(url, {
144 method: 'GET',
145 headers: {
146 'Content-Type': 'application/json',
147 'X-User-Id': String(userId),
148 },
149 })
150
151 if (!response.ok) {
152 const text = await response.text()
153 let apiError = ''
154 try {
155 apiError = JSON.parse(text).error || ''
156 } catch {
157 apiError = ''
158 }
159 throw new Error(apiError || `Failed to fetch listings: ${response.status} ${response.statusText}`)
160 }
161
162 return await response.json()
163}
164
165export async function getUserPets(userId: number): Promise<Pet[]> {
166 const url = joinUrl(getBaseUrl(), `/api/users/${userId}/pets`)
167 const response = await fetch(url, {
168 method: 'GET',
169 headers: {
170 'Content-Type': 'application/json',
171 'X-User-Id': String(userId),
172 },
173 })
174
175 if (!response.ok) {
176 throw new Error(`Failed to fetch pets: ${response.statusText}`)
177 }
178
179 return await response.json()
180}
181
182export async function createPet(
183 userId: number,
184 data: {
185 name: string
186 sex: string
187 dateOfBirth?: string
188 photo?: File
189 type: string
190 species: string
191 breed?: string
192 locatedName?: string
193 }
194): Promise<Pet> {
195 const url = joinUrl(getBaseUrl(), `/api/users/${userId}/pets`)
196 const formData = new FormData()
197
198 formData.append('name', data.name)
199 formData.append('sex', data.sex)
200 formData.append('type', data.type)
201 formData.append('species', data.species)
202 if (data.dateOfBirth) formData.append('dateOfBirth', data.dateOfBirth)
203 if (data.breed) formData.append('breed', data.breed)
204 if (data.locatedName) formData.append('locatedName', data.locatedName)
205 if (data.photo) formData.append('photo', data.photo)
206
207 console.log('🔗 API URL:', url)
208 console.log('📦 Request payload:', data)
209 console.log('📋 Headers:', {
210 'X-User-Id': String(userId),
211 })
212
213 const response = await fetch(url, {
214 method: 'POST',
215 headers: {
216 'X-User-Id': String(userId),
217 },
218 body: formData,
219 })
220
221 console.log('📬 Response status:', response.status)
222 console.log('📬 Response headers:', response.headers)
223
224 if (!response.ok) {
225 const error = await response.json()
226 console.error('❌ Error response:', error)
227 throw new Error(error.error || `Failed to create pet: ${response.statusText}`)
228 }
229
230 const result = await response.json()
231 console.log('✅ Pet created successfully:', result)
232 return result
233}
234
235export async function createListing(
236 userId: number,
237 data: {
238 animalId: number
239 description: string
240 price: number
241 }
242): Promise<any> {
243 const url = joinUrl(getBaseUrl(), `/api/listings`)
244 const response = await fetch(url, {
245 method: 'POST',
246 headers: {
247 'Content-Type': 'application/json',
248 'X-User-Id': String(userId),
249 },
250 body: JSON.stringify(data),
251 })
252
253 if (!response.ok) {
254 const error = await response.json()
255 throw new Error(error.error || `Failed to create listing: ${response.statusText}`)
256 }
257
258 return await response.json()
259}
260
261export async function deleteListing(userId: number, listingId: number): Promise<void> {
262 const url = joinUrl(getBaseUrl(), `/api/listings/${listingId}`)
263 const response = await fetch(url, {
264 method: 'DELETE',
265 headers: {
266 'Content-Type': 'application/json',
267 'X-User-Id': String(userId),
268 },
269 })
270
271 if (!response.ok) {
272 const error = await response.json()
273 throw new Error(error.error || `Failed to delete listing: ${response.statusText}`)
274 }
275}
276
277export async function updateListingStatus(
278 userId: number,
279 listingId: number,
280 status: string
281): Promise<any> {
282 const url = joinUrl(getBaseUrl(), `/api/listings/${listingId}/status`)
283 const response = await fetch(url, {
284 method: 'PATCH',
285 headers: {
286 'Content-Type': 'application/json',
287 'X-User-Id': String(userId),
288 },
289 body: JSON.stringify({ status }),
290 })
291
292 if (!response.ok) {
293 const error = await response.json()
294 throw new Error(error.error || `Failed to update listing: ${response.statusText}`)
295 }
296
297 return await response.json()
298}
299
300export async function getPet(petId: number): Promise<Pet> {
301 const url = joinUrl(getBaseUrl(), `/api/pets/${petId}`)
302 const response = await fetch(url, {
303 method: 'GET',
304 headers: {
305 'Content-Type': 'application/json',
306 },
307 })
308
309 if (!response.ok) {
310 throw new Error(`Failed to fetch pet: ${response.statusText}`)
311 }
312
313 return await response.json()
314}
315
316export async function loadUserVerificationStatus(userId: number): Promise<boolean> {
317 try {
318 const url = joinUrl(getBaseUrl(), `/api/users/${userId}/verified`)
319 const response = await fetch(url, {
320 method: 'GET',
321 headers: {
322 'Content-Type': 'application/json',
323 },
324 })
325
326 if (!response.ok) {
327 return false
328 }
329
330 const data = await response.json()
331 return data.verified || false
332 } catch (error) {
333 console.error('Failed to load user verification status:', error)
334 return false
335 }
336}
337
338export async function createAppointment(
339 userId: number,
340 data: {
341 clinicId: number
342 animalId: number
343 dateTime: string
344 notes?: string
345 }
346): Promise<any> {
347 const url = joinUrl(getBaseUrl(), `/api/appointments`)
348 const response = await fetch(url, {
349 method: 'POST',
350 headers: {
351 'Content-Type': 'application/json',
352 'X-User-Id': String(userId),
353 },
354 body: JSON.stringify(data),
355 })
356
357 if (!response.ok) {
358 const error = await response.json()
359 throw new Error(error.error || `Failed to create appointment: ${response.statusText}`)
360 }
361
362 return await response.json()
363}
364
365export async function getPetHealthRecords(petId: number): Promise<HealthRecord[]> {
366 const url = joinUrl(getBaseUrl(), `/api/pets/${petId}/health-records`)
367 const response = await fetch(url, {
368 method: 'GET',
369 headers: {
370 'Content-Type': 'application/json',
371 },
372 })
373
374 return await readJsonOrError<HealthRecord[]>(response, 'Failed to fetch health records')
375}
376
377export async function createHealthRecord(
378 userId: number,
379 data: {
380 appointmentId: number
381 type: string
382 description?: string
383 }
384): Promise<HealthRecord> {
385 const url = joinUrl(getBaseUrl(), `/api/health-records`)
386 const response = await fetch(url, {
387 method: 'POST',
388 headers: {
389 'Content-Type': 'application/json',
390 'X-User-Id': String(userId),
391 },
392 body: JSON.stringify(data),
393 })
394
395 return await readJsonOrError<HealthRecord>(response, 'Failed to create health record')
396}
397
398export async function cancelOwnerAppointment(userId: number, appointmentId: number): Promise<OwnerAppointment> {
399 const url = joinUrl(getBaseUrl(), `/api/appointments/my/${appointmentId}/cancel`)
400 const response = await fetch(url, {
401 method: 'PATCH',
402 headers: {
403 'Content-Type': 'application/json',
404 'X-User-Id': String(userId),
405 },
406 })
407
408 if (!response.ok) {
409 const text = await response.text()
410 let apiError = ''
411 try {
412 const error = JSON.parse(text)
413 apiError = error.error || ''
414 } catch {
415 apiError = ''
416 }
417
418 if (apiError) {
419 throw new Error(apiError)
420 }
421
422 throw new Error(`Failed to cancel appointment: ${response.status} ${response.statusText}. ${text.slice(0, 200)}`)
423 }
424
425 return await response.json()
426}
427
428export async function getClinics(): Promise<VetClinic[]> {
429 const url = joinUrl(getBaseUrl(), `/api/clinics`)
430 const response = await fetch(url, {
431 method: 'GET',
432 headers: {
433 'Content-Type': 'application/json',
434 },
435 })
436
437 const contentType = response.headers.get('content-type') || ''
438
439 if (!response.ok) {
440 const text = await response.text()
441 throw new Error(`Failed to fetch clinics: ${response.status} ${response.statusText}. ${text.slice(0, 200)}`)
442 }
443
444 if (!contentType.includes('application/json')) {
445 const text = await response.text()
446 throw new Error(`Clinics API returned non-JSON. Check VITE_API_BASE_URL/backend. ${text.slice(0, 200)}`)
447 }
448
449 return await response.json()
450}
451
452export async function getMyClinic(userId: number): Promise<VetClinic> {
453 const url = joinUrl(getBaseUrl(), `/api/clinics/my`)
454 const response = await fetch(url, {
455 method: 'GET',
456 headers: {
457 'Content-Type': 'application/json',
458 'X-User-Id': String(userId),
459 },
460 })
461
462 if (!response.ok) {
463 const error = await response.json()
464 throw new Error(error.error || `Failed to fetch clinic profile: ${response.statusText}`)
465 }
466
467 return await response.json()
468}
469
470export async function updateMyClinicSchedule(
471 userId: number,
472 data: {
473 workDays: string
474 startTime: string
475 endTime: string
476 }
477): Promise<VetClinic> {
478 const url = joinUrl(getBaseUrl(), `/api/clinics/my/schedule`)
479 const response = await fetch(url, {
480 method: 'PUT',
481 headers: {
482 'Content-Type': 'application/json',
483 'X-User-Id': String(userId),
484 },
485 body: JSON.stringify(data),
486 })
487
488 if (!response.ok) {
489 const error = await response.json()
490 throw new Error(error.error || `Failed to update clinic schedule: ${response.statusText}`)
491 }
492
493 return await response.json()
494}
495
496export async function getClinicAvailableSlots(clinicId: number, date: string): Promise<AppointmentSlot[]> {
497 const url = joinUrl(getBaseUrl(), `/api/appointments/clinics/${clinicId}/available-slots?date=${encodeURIComponent(date)}`)
498 const response = await fetch(url, {
499 method: 'GET',
500 headers: {
501 'Content-Type': 'application/json',
502 },
503 })
504
505 if (!response.ok) {
506 const text = await response.text()
507 let apiError = ''
508 try {
509 const error = JSON.parse(text)
510 apiError = error.error || ''
511 } catch {
512 apiError = ''
513 }
514
515 if (apiError) {
516 throw new Error(apiError)
517 }
518
519 throw new Error(`Failed to fetch available slots: ${response.status} ${response.statusText}. ${text.slice(0, 200)}`)
520 }
521
522 return await response.json()
523}
524
525export async function getClinicAppointments(clinicId: number, date: string): Promise<ClinicAppointment[]> {
526 const url = joinUrl(getBaseUrl(), `/api/appointments/clinics/${clinicId}?date=${encodeURIComponent(date)}`)
527 const response = await fetch(url, {
528 method: 'GET',
529 headers: {
530 'Content-Type': 'application/json',
531 },
532 })
533
534 if (!response.ok) {
535 const text = await response.text()
536 throw new Error(`Failed to fetch clinic appointments: ${response.status} ${response.statusText}. ${text.slice(0, 200)}`)
537 }
538
539 return await response.json()
540}
541
542export async function getMyClinicAppointments(userId: number, date: string): Promise<ClinicAppointment[]> {
543 const url = joinUrl(getBaseUrl(), `/api/appointments/my-clinic?date=${encodeURIComponent(date)}`)
544 const response = await fetch(url, {
545 method: 'GET',
546 headers: {
547 'Content-Type': 'application/json',
548 'X-User-Id': String(userId),
549 },
550 })
551
552 if (!response.ok) {
553 const text = await response.text()
554 throw new Error(`Failed to fetch clinic appointments: ${response.status} ${response.statusText}. ${text.slice(0, 200)}`)
555 }
556
557 return await response.json()
558}
559
560export async function markMyClinicAppointmentNoShow(userId: number, appointmentId: number): Promise<ClinicAppointment> {
561 const url = joinUrl(getBaseUrl(), `/api/appointments/my-clinic/${appointmentId}/no-show`)
562 const response = await fetch(url, {
563 method: 'PATCH',
564 headers: {
565 'Content-Type': 'application/json',
566 'X-User-Id': String(userId),
567 },
568 })
569
570 if (!response.ok) {
571 const text = await response.text()
572 let apiError = ''
573 try {
574 const error = JSON.parse(text)
575 apiError = error.error || ''
576 } catch {
577 apiError = ''
578 }
579
580 if (apiError) {
581 throw new Error(apiError)
582 }
583
584 throw new Error(`Failed to mark appointment as no-show: ${response.status} ${response.statusText}. ${text.slice(0, 200)}`)
585 }
586
587 return await response.json()
588}
589
590export async function getMyClinicAvailableSlots(userId: number, date: string): Promise<AppointmentSlot[]> {
591 const url = joinUrl(getBaseUrl(), `/api/appointments/my-clinic/available-slots?date=${encodeURIComponent(date)}`)
592 const response = await fetch(url, {
593 method: 'GET',
594 headers: {
595 'Content-Type': 'application/json',
596 'X-User-Id': String(userId),
597 },
598 })
599
600 if (!response.ok) {
601 const error = await response.json()
602 throw new Error(error.error || `Failed to fetch available slots: ${response.statusText}`)
603 }
604
605 return await response.json()
606}
607
608export async function getClinicUnavailableSlots(clinicId: number, date: string): Promise<ClinicUnavailableSlot[]> {
609 const url = joinUrl(getBaseUrl(), `/api/appointments/clinics/${clinicId}/unavailable-slots?date=${encodeURIComponent(date)}`)
610 const response = await fetch(url, {
611 method: 'GET',
612 headers: {
613 'Content-Type': 'application/json',
614 },
615 })
616
617 if (!response.ok) {
618 const text = await response.text()
619 throw new Error(`Failed to fetch unavailable slots: ${response.status} ${response.statusText}. ${text.slice(0, 200)}`)
620 }
621
622 return await response.json()
623}
624
625export async function getMyClinicUnavailableSlots(userId: number, date: string): Promise<ClinicUnavailableSlot[]> {
626 const url = joinUrl(getBaseUrl(), `/api/appointments/my-clinic/unavailable-slots?date=${encodeURIComponent(date)}`)
627 const response = await fetch(url, {
628 method: 'GET',
629 headers: {
630 'Content-Type': 'application/json',
631 'X-User-Id': String(userId),
632 },
633 })
634
635 if (!response.ok) {
636 const text = await response.text()
637 throw new Error(`Failed to fetch unavailable slots: ${response.status} ${response.statusText}. ${text.slice(0, 200)}`)
638 }
639
640 return await response.json()
641}
642
643export async function createClinicUnavailableSlot(
644 clinicId: number,
645 data: { dateTime: string; reason?: string }
646): Promise<ClinicUnavailableSlot> {
647 const url = joinUrl(getBaseUrl(), `/api/appointments/clinics/${clinicId}/unavailable-slots`)
648 const response = await fetch(url, {
649 method: 'POST',
650 headers: {
651 'Content-Type': 'application/json',
652 },
653 body: JSON.stringify(data),
654 })
655
656 if (!response.ok) {
657 const error = await response.json()
658 throw new Error(error.error || `Failed to block slot: ${response.statusText}`)
659 }
660
661 return await response.json()
662}
663
664export async function createMyClinicUnavailableSlot(
665 userId: number,
666 data: { dateTime: string; reason?: string }
667): Promise<ClinicUnavailableSlot> {
668 const url = joinUrl(getBaseUrl(), `/api/appointments/my-clinic/unavailable-slots`)
669 const response = await fetch(url, {
670 method: 'POST',
671 headers: {
672 'Content-Type': 'application/json',
673 'X-User-Id': String(userId),
674 },
675 body: JSON.stringify(data),
676 })
677
678 if (!response.ok) {
679 const error = await response.json()
680 throw new Error(error.error || `Failed to block slot: ${response.statusText}`)
681 }
682
683 return await response.json()
684}
685
686export async function deleteClinicUnavailableSlot(clinicId: number, slotId: number): Promise<void> {
687 const url = joinUrl(getBaseUrl(), `/api/appointments/clinics/${clinicId}/unavailable-slots/${slotId}`)
688 const response = await fetch(url, {
689 method: 'DELETE',
690 headers: {
691 'Content-Type': 'application/json',
692 },
693 })
694
695 if (!response.ok) {
696 const error = await response.json()
697 throw new Error(error.error || `Failed to unblock slot: ${response.statusText}`)
698 }
699}
700
701export async function deleteMyClinicUnavailableSlot(userId: number, slotId: number): Promise<void> {
702 const url = joinUrl(getBaseUrl(), `/api/appointments/my-clinic/unavailable-slots/${slotId}`)
703 const response = await fetch(url, {
704 method: 'DELETE',
705 headers: {
706 'Content-Type': 'application/json',
707 'X-User-Id': String(userId),
708 },
709 })
710
711 if (!response.ok) {
712 const error = await response.json()
713 throw new Error(error.error || `Failed to unblock slot: ${response.statusText}`)
714 }
715}
716
717export async function getOwnerAppointments(userId: number): Promise<OwnerAppointment[]> {
718 const url = joinUrl(getBaseUrl(), `/api/appointments/my`)
719 const response = await fetch(url, {
720 method: 'GET',
721 headers: {
722 'Content-Type': 'application/json',
723 'X-User-Id': String(userId),
724 },
725 })
726
727 if (!response.ok) {
728 const text = await response.text()
729 throw new Error(`Failed to fetch appointments: ${response.status} ${response.statusText}. ${text.slice(0, 200)}`)
730 }
731
732 return await response.json()
733}
734
735export async function getMyNotifications(userId: number): Promise<AppNotification[]> {
736 const url = joinUrl(getBaseUrl(), `/api/notifications/my`)
737 const response = await fetch(url, {
738 method: 'GET',
739 headers: {
740 'Content-Type': 'application/json',
741 'X-User-Id': String(userId),
742 },
743 })
744
745 if (!response.ok) {
746 const text = await response.text()
747 throw new Error(`Failed to fetch notifications: ${response.status} ${response.statusText}. ${text.slice(0, 200)}`)
748 }
749
750 return await response.json()
751}
Note: See TracBrowser for help on using the repository browser.