forked from trent_larson/crowd-funder-for-time-pwa
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
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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<boolean> {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -174,16 +174,18 @@
|
||||
:aria-busy="loadingProfile || savingProfile"
|
||||
></textarea>
|
||||
|
||||
<div class="flex items-center mb-4">
|
||||
<input
|
||||
v-model="includeUserProfileLocation"
|
||||
type="checkbox"
|
||||
class="mr-2"
|
||||
@change="onLocationCheckboxChange"
|
||||
/>
|
||||
<label for="includeUserProfileLocation">Include Location</label>
|
||||
<span class="text-xs text-slate-400 ml-2">(Debug: {{ isMapReady ? 'Map Ready' : 'Map Loading' }})</span>
|
||||
</div>
|
||||
<div class="flex items-center mb-4">
|
||||
<input
|
||||
v-model="includeUserProfileLocation"
|
||||
type="checkbox"
|
||||
class="mr-2"
|
||||
@change="onLocationCheckboxChange"
|
||||
/>
|
||||
<label for="includeUserProfileLocation">Include Location</label>
|
||||
<span class="text-xs text-slate-400 ml-2"
|
||||
>(Debug: {{ isMapReady ? "Map Ready" : "Map Loading" }})</span
|
||||
>
|
||||
</div>
|
||||
<div v-if="includeUserProfileLocation" class="mb-4 aspect-video">
|
||||
<p class="text-sm mb-2 text-slate-500">
|
||||
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<string, unknown>;
|
||||
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) {
|
||||
|
||||
Reference in New Issue
Block a user