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

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

Petify fullstack project

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