diff --git a/src/interfaces/common.ts b/src/interfaces/common.ts index 9267cf70..b2e68d1f 100644 --- a/src/interfaces/common.ts +++ b/src/interfaces/common.ts @@ -60,9 +60,13 @@ export interface AxiosErrorResponse { [key: string]: unknown; }; status?: number; + statusText?: string; config?: unknown; }; - config?: unknown; + config?: { + url?: string; + [key: string]: unknown; + }; [key: string]: unknown; } diff --git a/src/services/ProfileService.ts b/src/services/ProfileService.ts index bdb27f46..26dbe9f3 100644 --- a/src/services/ProfileService.ts +++ b/src/services/ProfileService.ts @@ -10,6 +10,7 @@ import { getHeaders, errorStringForLog } from "@/libs/endorserServer"; import { handleApiError } from "./api"; import { logger } from "@/utils/logger"; import { ACCOUNT_VIEW_CONSTANTS } from "@/constants/accountView"; +import { AxiosErrorResponse } from "@/interfaces/common"; /** * Profile data interface @@ -124,36 +125,29 @@ export class ProfileService { async deleteProfile(activeDid: string): Promise { try { const headers = await getHeaders(activeDid); - logger.debug("Attempting to delete profile for DID:", activeDid); - logger.debug("Using partner API server:", this.partnerApiServer); - logger.debug("Request headers:", headers); - - const url = `${this.partnerApiServer}/api/partner/userProfile`; - logger.debug("DELETE request URL:", url); - - const response = await this.axios.delete(url, { headers }); + const response = await this.axios.delete( + `${this.partnerApiServer}/api/partner/userProfile`, + { headers }, + ); - if (response.status === 200 || response.status === 204) { - logger.debug("Profile deleted successfully"); + if (response.status === 204 || response.status === 200) { + logger.info("Profile deleted successfully"); return true; } else { - logger.error("Unexpected response status when deleting profile:", { - status: response.status, - statusText: response.statusText, - data: response.data - }); - throw new Error(`Profile not deleted - HTTP ${response.status}: ${response.statusText}`); + throw new Error( + `Profile not deleted - HTTP ${response.status}: ${response.statusText}`, + ); } } catch (error) { if (this.isApiError(error) && error.response) { - const response = error.response as any; // Type assertion for error response + const response = error.response; logger.error("API error deleting profile:", { status: response.status, statusText: response.statusText, data: response.data, - url: (error as any).config?.url + url: this.getErrorUrl(error), }); - + // Handle specific HTTP status codes if (response.status === 204) { logger.debug("Profile deleted successfully (204 No Content)"); @@ -163,7 +157,9 @@ export class ProfileService { return true; // Consider this a success if profile doesn't exist } else if (response.status === 400) { logger.error("Bad request when deleting profile:", response.data); - throw new Error(`Profile deletion failed: ${response.data?.message || 'Bad request'}`); + throw new Error( + `Profile deletion failed: ${response.data?.error?.message || "Bad request"}`, + ); } else if (response.status === 401) { logger.error("Unauthorized to delete profile"); throw new Error("You are not authorized to delete this profile"); @@ -172,7 +168,7 @@ export class ProfileService { throw new Error("You are not allowed to delete this profile"); } } - + logger.error("Error deleting profile:", errorStringForLog(error)); handleApiError(error as AxiosError, "/api/partner/userProfile"); return false; @@ -242,13 +238,22 @@ export class ProfileService { } /** - * Type guard for API errors + * Type guard for API errors with proper typing */ - private isApiError( - error: unknown, - ): error is { response?: { status?: number } } { + private isApiError(error: unknown): error is AxiosErrorResponse { return typeof error === "object" && error !== null && "response" in error; } + + /** + * Extract error URL safely from error object + */ + private getErrorUrl(error: unknown): string | undefined { + if (this.isApiError(error) && error.config) { + const config = error.config as { url?: string }; + return config.url; + } + return undefined; + } } /** diff --git a/src/views/AccountViewView.vue b/src/views/AccountViewView.vue index 1c38a8bb..5be23edf 100644 --- a/src/views/AccountViewView.vue +++ b/src/views/AccountViewView.vue @@ -174,16 +174,18 @@ :aria-busy="loadingProfile || savingProfile" > -
- - - (Debug: {{ isMapReady ? 'Map Ready' : 'Map Loading' }}) -
+
+ + + (Debug: {{ isMapReady ? "Map Ready" : "Map Loading" }}) +

The location you choose will be shared with the world until you remove @@ -918,15 +920,21 @@ export default class AccountViewView extends Vue { created() { this.notify = createNotifyHelpers(this.$notify); - + // Fix Leaflet icon issues in modern bundlers // This prevents the "Cannot read properties of undefined (reading 'Default')" error if (L.Icon.Default) { - delete (L.Icon.Default.prototype as any)._getIconUrl; + // Type-safe way to handle Leaflet icon prototype + const iconDefault = L.Icon.Default.prototype as Record; + if ("_getIconUrl" in iconDefault) { + delete iconDefault._getIconUrl; + } L.Icon.Default.mergeOptions({ - iconRetinaUrl: 'https://unpkg.com/leaflet@1.7.1/dist/images/marker-icon-2x.png', - iconUrl: 'https://unpkg.com/leaflet@1.7.1/dist/images/marker-icon.png', - shadowUrl: 'https://unpkg.com/leaflet@1.7.1/dist/images/marker-shadow.png', + iconRetinaUrl: + "https://unpkg.com/leaflet@1.7.1/dist/images/marker-icon-2x.png", + iconUrl: "https://unpkg.com/leaflet@1.7.1/dist/images/marker-icon.png", + shadowUrl: + "https://unpkg.com/leaflet@1.7.1/dist/images/marker-shadow.png", }); } } @@ -955,7 +963,7 @@ export default class AccountViewView extends Vue { this.userProfileLatitude = profile.latitude; this.userProfileLongitude = profile.longitude; this.includeUserProfileLocation = profile.includeLocation; - + // Initialize map ready state if location is included if (profile.includeLocation) { this.isMapReady = false; // Will be set to true when map is ready @@ -1543,12 +1551,18 @@ export default class AccountViewView extends Vue { try { logger.debug("Map ready event fired, map object:", map); // doing this here instead of on the l-map element avoids a recentering after a drag then zoom at startup - const zoom = this.userProfileLatitude && this.userProfileLongitude ? 12 : 2; + const zoom = + this.userProfileLatitude && this.userProfileLongitude ? 12 : 2; const lat = this.userProfileLatitude || 0; const lng = this.userProfileLongitude || 0; map.setView([lat, lng], zoom); this.isMapReady = true; - logger.debug("Map ready state set to true, coordinates:", [lat, lng], "zoom:", zoom); + logger.debug( + "Map ready state set to true, coordinates:", + [lat, lng], + "zoom:", + zoom, + ); } catch (error) { logger.error("Error in onMapReady:", error); this.isMapReady = true; // Set to true even on error to prevent infinite loading @@ -1560,7 +1574,7 @@ export default class AccountViewView extends Vue { // Check if map ref is available const mapRef = this.$refs.profileMap; logger.debug("Map ref:", mapRef); - + // Try to set map ready after component is mounted setTimeout(() => { this.isMapReady = true; @@ -1597,9 +1611,9 @@ export default class AccountViewView extends Vue { longitude: this.userProfileLongitude, includeLocation: this.includeUserProfileLocation, }; - + logger.debug("Saving profile data:", profileData); - + const success = await this.profileService.saveProfile( this.activeDid, profileData, @@ -1628,7 +1642,7 @@ export default class AccountViewView extends Vue { this.userProfileLatitude = updated.latitude; this.userProfileLongitude = updated.longitude; this.includeUserProfileLocation = updated.includeLocation; - + // Reset map ready state when toggling location if (!updated.includeLocation) { this.isMapReady = false; @@ -1679,7 +1693,7 @@ export default class AccountViewView extends Vue { } } catch (error) { logger.error("Error in deleteProfile component method:", error); - + // Show more specific error message if available if (error instanceof Error) { this.notify.error(error.message); @@ -1710,7 +1724,10 @@ export default class AccountViewView extends Vue { onLocationCheckboxChange(): void { try { - logger.debug("Location checkbox changed, new value:", this.includeUserProfileLocation); + logger.debug( + "Location checkbox changed, new value:", + this.includeUserProfileLocation, + ); if (!this.includeUserProfileLocation) { // Location checkbox was unchecked, clean up map state this.isMapReady = false; @@ -1721,7 +1738,7 @@ export default class AccountViewView extends Vue { // Location checkbox was checked, start map initialization timeout this.isMapReady = false; logger.debug("Location checked, starting map initialization timeout"); - + // Try to set map ready after a short delay to allow Vue to render setTimeout(() => { if (!this.isMapReady) { @@ -1729,7 +1746,7 @@ export default class AccountViewView extends Vue { this.isMapReady = true; } }, 1000); // 1 second delay - + this.handleMapInitFailure(); } } catch (error) {