Browse Source

fix(types): resolve TypeScript any type violations

- Replace any types in ProfileService with AxiosErrorResponse interface
- Add type-safe error URL extraction method
- Fix Leaflet icon type assertion using Record<string, unknown>
- Enhance AxiosErrorResponse interface with missing properties
- Maintain existing functionality while improving type safety

Closes typing violations in ProfileService.ts and AccountViewView.vue
build-web-serve-test
Matthew Raymer 3 days ago
parent
commit
86e9aa75c1
  1. 6
      src/interfaces/common.ts
  2. 55
      src/services/ProfileService.ts
  3. 69
      src/views/AccountViewView.vue

6
src/interfaces/common.ts

@ -60,9 +60,13 @@ export interface AxiosErrorResponse {
[key: string]: unknown; [key: string]: unknown;
}; };
status?: number; status?: number;
statusText?: string;
config?: unknown; config?: unknown;
}; };
config?: unknown; config?: {
url?: string;
[key: string]: unknown;
};
[key: string]: unknown; [key: string]: unknown;
} }

55
src/services/ProfileService.ts

@ -10,6 +10,7 @@ import { getHeaders, errorStringForLog } from "@/libs/endorserServer";
import { handleApiError } from "./api"; import { handleApiError } from "./api";
import { logger } from "@/utils/logger"; import { logger } from "@/utils/logger";
import { ACCOUNT_VIEW_CONSTANTS } from "@/constants/accountView"; import { ACCOUNT_VIEW_CONSTANTS } from "@/constants/accountView";
import { AxiosErrorResponse } from "@/interfaces/common";
/** /**
* Profile data interface * Profile data interface
@ -124,36 +125,29 @@ export class ProfileService {
async deleteProfile(activeDid: string): Promise<boolean> { async deleteProfile(activeDid: string): Promise<boolean> {
try { try {
const headers = await getHeaders(activeDid); const headers = await getHeaders(activeDid);
logger.debug("Attempting to delete profile for DID:", activeDid); const response = await this.axios.delete(
logger.debug("Using partner API server:", this.partnerApiServer); `${this.partnerApiServer}/api/partner/userProfile`,
logger.debug("Request headers:", headers); { headers },
);
const url = `${this.partnerApiServer}/api/partner/userProfile`;
logger.debug("DELETE request URL:", url);
const response = await this.axios.delete(url, { headers });
if (response.status === 200 || response.status === 204) { if (response.status === 204 || response.status === 200) {
logger.debug("Profile deleted successfully"); logger.info("Profile deleted successfully");
return true; return true;
} else { } else {
logger.error("Unexpected response status when deleting profile:", { throw new Error(
status: response.status, `Profile not deleted - HTTP ${response.status}: ${response.statusText}`,
statusText: response.statusText, );
data: response.data
});
throw new Error(`Profile not deleted - HTTP ${response.status}: ${response.statusText}`);
} }
} catch (error) { } catch (error) {
if (this.isApiError(error) && error.response) { 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:", { logger.error("API error deleting profile:", {
status: response.status, status: response.status,
statusText: response.statusText, statusText: response.statusText,
data: response.data, data: response.data,
url: (error as any).config?.url url: this.getErrorUrl(error),
}); });
// Handle specific HTTP status codes // Handle specific HTTP status codes
if (response.status === 204) { if (response.status === 204) {
logger.debug("Profile deleted successfully (204 No Content)"); 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 return true; // Consider this a success if profile doesn't exist
} else if (response.status === 400) { } else if (response.status === 400) {
logger.error("Bad request when deleting profile:", response.data); 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) { } else if (response.status === 401) {
logger.error("Unauthorized to delete profile"); logger.error("Unauthorized to delete profile");
throw new Error("You are not authorized to delete this 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"); throw new Error("You are not allowed to delete this profile");
} }
} }
logger.error("Error deleting profile:", errorStringForLog(error)); logger.error("Error deleting profile:", errorStringForLog(error));
handleApiError(error as AxiosError, "/api/partner/userProfile"); handleApiError(error as AxiosError, "/api/partner/userProfile");
return false; return false;
@ -242,13 +238,22 @@ export class ProfileService {
} }
/** /**
* Type guard for API errors * Type guard for API errors with proper typing
*/ */
private isApiError( private isApiError(error: unknown): error is AxiosErrorResponse {
error: unknown,
): error is { response?: { status?: number } } {
return typeof error === "object" && error !== null && "response" in error; 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;
}
} }
/** /**

69
src/views/AccountViewView.vue

@ -174,16 +174,18 @@
:aria-busy="loadingProfile || savingProfile" :aria-busy="loadingProfile || savingProfile"
></textarea> ></textarea>
<div class="flex items-center mb-4"> <div class="flex items-center mb-4">
<input <input
v-model="includeUserProfileLocation" v-model="includeUserProfileLocation"
type="checkbox" type="checkbox"
class="mr-2" class="mr-2"
@change="onLocationCheckboxChange" @change="onLocationCheckboxChange"
/> />
<label for="includeUserProfileLocation">Include Location</label> <label for="includeUserProfileLocation">Include Location</label>
<span class="text-xs text-slate-400 ml-2">(Debug: {{ isMapReady ? 'Map Ready' : 'Map Loading' }})</span> <span class="text-xs text-slate-400 ml-2"
</div> >(Debug: {{ isMapReady ? "Map Ready" : "Map Loading" }})</span
>
</div>
<div v-if="includeUserProfileLocation" class="mb-4 aspect-video"> <div v-if="includeUserProfileLocation" class="mb-4 aspect-video">
<p class="text-sm mb-2 text-slate-500"> <p class="text-sm mb-2 text-slate-500">
The location you choose will be shared with the world until you remove The location you choose will be shared with the world until you remove
@ -918,15 +920,21 @@ export default class AccountViewView extends Vue {
created() { created() {
this.notify = createNotifyHelpers(this.$notify); this.notify = createNotifyHelpers(this.$notify);
// Fix Leaflet icon issues in modern bundlers // Fix Leaflet icon issues in modern bundlers
// This prevents the "Cannot read properties of undefined (reading 'Default')" error // This prevents the "Cannot read properties of undefined (reading 'Default')" error
if (L.Icon.Default) { 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<string, unknown>;
if ("_getIconUrl" in iconDefault) {
delete iconDefault._getIconUrl;
}
L.Icon.Default.mergeOptions({ L.Icon.Default.mergeOptions({
iconRetinaUrl: 'https://unpkg.com/leaflet@1.7.1/dist/images/marker-icon-2x.png', iconRetinaUrl:
iconUrl: 'https://unpkg.com/leaflet@1.7.1/dist/images/marker-icon.png', "https://unpkg.com/leaflet@1.7.1/dist/images/marker-icon-2x.png",
shadowUrl: 'https://unpkg.com/leaflet@1.7.1/dist/images/marker-shadow.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.userProfileLatitude = profile.latitude;
this.userProfileLongitude = profile.longitude; this.userProfileLongitude = profile.longitude;
this.includeUserProfileLocation = profile.includeLocation; this.includeUserProfileLocation = profile.includeLocation;
// Initialize map ready state if location is included // Initialize map ready state if location is included
if (profile.includeLocation) { if (profile.includeLocation) {
this.isMapReady = false; // Will be set to true when map is ready this.isMapReady = false; // Will be set to true when map is ready
@ -1543,12 +1551,18 @@ export default class AccountViewView extends Vue {
try { try {
logger.debug("Map ready event fired, map object:", map); 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 // 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 lat = this.userProfileLatitude || 0;
const lng = this.userProfileLongitude || 0; const lng = this.userProfileLongitude || 0;
map.setView([lat, lng], zoom); map.setView([lat, lng], zoom);
this.isMapReady = true; 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) { } catch (error) {
logger.error("Error in onMapReady:", error); logger.error("Error in onMapReady:", error);
this.isMapReady = true; // Set to true even on error to prevent infinite loading 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 // Check if map ref is available
const mapRef = this.$refs.profileMap; const mapRef = this.$refs.profileMap;
logger.debug("Map ref:", mapRef); logger.debug("Map ref:", mapRef);
// Try to set map ready after component is mounted // Try to set map ready after component is mounted
setTimeout(() => { setTimeout(() => {
this.isMapReady = true; this.isMapReady = true;
@ -1597,9 +1611,9 @@ export default class AccountViewView extends Vue {
longitude: this.userProfileLongitude, longitude: this.userProfileLongitude,
includeLocation: this.includeUserProfileLocation, includeLocation: this.includeUserProfileLocation,
}; };
logger.debug("Saving profile data:", profileData); logger.debug("Saving profile data:", profileData);
const success = await this.profileService.saveProfile( const success = await this.profileService.saveProfile(
this.activeDid, this.activeDid,
profileData, profileData,
@ -1628,7 +1642,7 @@ export default class AccountViewView extends Vue {
this.userProfileLatitude = updated.latitude; this.userProfileLatitude = updated.latitude;
this.userProfileLongitude = updated.longitude; this.userProfileLongitude = updated.longitude;
this.includeUserProfileLocation = updated.includeLocation; this.includeUserProfileLocation = updated.includeLocation;
// Reset map ready state when toggling location // Reset map ready state when toggling location
if (!updated.includeLocation) { if (!updated.includeLocation) {
this.isMapReady = false; this.isMapReady = false;
@ -1679,7 +1693,7 @@ export default class AccountViewView extends Vue {
} }
} catch (error) { } catch (error) {
logger.error("Error in deleteProfile component method:", error); logger.error("Error in deleteProfile component method:", error);
// Show more specific error message if available // Show more specific error message if available
if (error instanceof Error) { if (error instanceof Error) {
this.notify.error(error.message); this.notify.error(error.message);
@ -1710,7 +1724,10 @@ export default class AccountViewView extends Vue {
onLocationCheckboxChange(): void { onLocationCheckboxChange(): void {
try { try {
logger.debug("Location checkbox changed, new value:", this.includeUserProfileLocation); logger.debug(
"Location checkbox changed, new value:",
this.includeUserProfileLocation,
);
if (!this.includeUserProfileLocation) { if (!this.includeUserProfileLocation) {
// Location checkbox was unchecked, clean up map state // Location checkbox was unchecked, clean up map state
this.isMapReady = false; this.isMapReady = false;
@ -1721,7 +1738,7 @@ export default class AccountViewView extends Vue {
// Location checkbox was checked, start map initialization timeout // Location checkbox was checked, start map initialization timeout
this.isMapReady = false; this.isMapReady = false;
logger.debug("Location checked, starting map initialization timeout"); logger.debug("Location checked, starting map initialization timeout");
// Try to set map ready after a short delay to allow Vue to render // Try to set map ready after a short delay to allow Vue to render
setTimeout(() => { setTimeout(() => {
if (!this.isMapReady) { if (!this.isMapReady) {
@ -1729,7 +1746,7 @@ export default class AccountViewView extends Vue {
this.isMapReady = true; this.isMapReady = true;
} }
}, 1000); // 1 second delay }, 1000); // 1 second delay
this.handleMapInitFailure(); this.handleMapInitFailure();
} }
} catch (error) { } catch (error) {

Loading…
Cancel
Save