forked from trent_larson/crowd-funder-for-time-pwa
Refactor AccountViewView.vue to use notify helper and PlatformServiceMixin
- Migrated all notification calls in AccountViewView.vue to use a notify helper initialized in mounted(), ensuring this.$notify is available and preventing runtime errors. - Removed NotificationMixin and $notifyHelper usage; restored and standardized notify helper pattern. - Migrated all database and platform service operations to use PlatformServiceMixin ultra-concise methods ($accountSettings, $saveSettings, $saveUserSettings, etc.). - Cleaned up unused imports and code related to previous notification and database patterns. - Ensured all linter errors and warnings are resolved in both AccountViewView.vue and notification utility files.
This commit is contained in:
223
src/services/ProfileService.ts
Normal file
223
src/services/ProfileService.ts
Normal file
@@ -0,0 +1,223 @@
|
|||||||
|
/**
|
||||||
|
* ProfileService - Handles user profile operations and API calls
|
||||||
|
* Extracted from AccountViewView.vue to improve separation of concerns
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { AxiosInstance, AxiosError } from "axios";
|
||||||
|
import { UserProfile } from "@/libs/partnerServer";
|
||||||
|
import { UserProfileResponse } from "@/interfaces/accountView";
|
||||||
|
import { getHeaders, errorStringForLog } from "@/libs/endorserServer";
|
||||||
|
import { handleApiError } from "./api";
|
||||||
|
import { logger } from "@/utils/logger";
|
||||||
|
import { ACCOUNT_VIEW_CONSTANTS } from "@/constants/accountView";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Profile data interface
|
||||||
|
*/
|
||||||
|
export interface ProfileData {
|
||||||
|
description: string;
|
||||||
|
latitude: number;
|
||||||
|
longitude: number;
|
||||||
|
includeLocation: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Profile service class
|
||||||
|
*/
|
||||||
|
export class ProfileService {
|
||||||
|
private axios: AxiosInstance;
|
||||||
|
private partnerApiServer: string;
|
||||||
|
|
||||||
|
constructor(axios: AxiosInstance, partnerApiServer: string) {
|
||||||
|
this.axios = axios;
|
||||||
|
this.partnerApiServer = partnerApiServer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load user profile from the server
|
||||||
|
* @param activeDid - The user's DID
|
||||||
|
* @returns ProfileData or null if profile doesn't exist
|
||||||
|
*/
|
||||||
|
async loadProfile(activeDid: string): Promise<ProfileData | null> {
|
||||||
|
try {
|
||||||
|
const headers = await getHeaders(activeDid);
|
||||||
|
const response = await this.axios.get<UserProfileResponse>(
|
||||||
|
`${this.partnerApiServer}/api/partner/userProfileForIssuer/${activeDid}`,
|
||||||
|
{ headers },
|
||||||
|
);
|
||||||
|
|
||||||
|
if (response.status === 200) {
|
||||||
|
const data = response.data.data;
|
||||||
|
const profileData: ProfileData = {
|
||||||
|
description: data.description || "",
|
||||||
|
latitude: data.locLat || 0,
|
||||||
|
longitude: data.locLon || 0,
|
||||||
|
includeLocation: !!(data.locLat && data.locLon),
|
||||||
|
};
|
||||||
|
return profileData;
|
||||||
|
} else {
|
||||||
|
throw new Error(ACCOUNT_VIEW_CONSTANTS.ERRORS.UNABLE_TO_LOAD_PROFILE);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
if (this.isApiError(error) && error.response?.status === 404) {
|
||||||
|
// Profile doesn't exist yet - this is normal
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.error("Error loading profile:", errorStringForLog(error));
|
||||||
|
handleApiError(error as AxiosError, "/api/partner/userProfileForIssuer");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save user profile to the server
|
||||||
|
* @param activeDid - The user's DID
|
||||||
|
* @param profileData - The profile data to save
|
||||||
|
* @returns true if successful, false otherwise
|
||||||
|
*/
|
||||||
|
async saveProfile(
|
||||||
|
activeDid: string,
|
||||||
|
profileData: ProfileData,
|
||||||
|
): Promise<boolean> {
|
||||||
|
try {
|
||||||
|
const headers = await getHeaders(activeDid);
|
||||||
|
const payload: UserProfile = {
|
||||||
|
description: profileData.description,
|
||||||
|
issuerDid: activeDid,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Add location data if location is included
|
||||||
|
if (
|
||||||
|
profileData.includeLocation &&
|
||||||
|
profileData.latitude &&
|
||||||
|
profileData.longitude
|
||||||
|
) {
|
||||||
|
payload.locLat = profileData.latitude;
|
||||||
|
payload.locLon = profileData.longitude;
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await this.axios.post(
|
||||||
|
`${this.partnerApiServer}/api/partner/userProfile`,
|
||||||
|
payload,
|
||||||
|
{ headers },
|
||||||
|
);
|
||||||
|
|
||||||
|
if (response.status === 200) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
throw new Error(ACCOUNT_VIEW_CONSTANTS.ERRORS.PROFILE_NOT_SAVED);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
logger.error("Error saving profile:", errorStringForLog(error));
|
||||||
|
handleApiError(error as AxiosError, "/api/partner/userProfile");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete user profile from the server
|
||||||
|
* @param activeDid - The user's DID
|
||||||
|
* @returns true if successful, false otherwise
|
||||||
|
*/
|
||||||
|
async deleteProfile(activeDid: string): Promise<boolean> {
|
||||||
|
try {
|
||||||
|
const headers = await getHeaders(activeDid);
|
||||||
|
const response = await this.axios.delete(
|
||||||
|
`${this.partnerApiServer}/api/partner/userProfile`,
|
||||||
|
{ headers },
|
||||||
|
);
|
||||||
|
|
||||||
|
if (response.status === 200) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
throw new Error(ACCOUNT_VIEW_CONSTANTS.ERRORS.PROFILE_NOT_DELETED);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
logger.error("Error deleting profile:", errorStringForLog(error));
|
||||||
|
handleApiError(error as AxiosError, "/api/partner/userProfile");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update profile location
|
||||||
|
* @param profileData - Current profile data
|
||||||
|
* @param latitude - New latitude
|
||||||
|
* @param longitude - New longitude
|
||||||
|
* @returns Updated profile data
|
||||||
|
*/
|
||||||
|
updateProfileLocation(
|
||||||
|
profileData: ProfileData,
|
||||||
|
latitude: number,
|
||||||
|
longitude: number,
|
||||||
|
): ProfileData {
|
||||||
|
return {
|
||||||
|
...profileData,
|
||||||
|
latitude,
|
||||||
|
longitude,
|
||||||
|
includeLocation: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toggle location inclusion in profile
|
||||||
|
* @param profileData - Current profile data
|
||||||
|
* @returns Updated profile data
|
||||||
|
*/
|
||||||
|
toggleProfileLocation(profileData: ProfileData): ProfileData {
|
||||||
|
const includeLocation = !profileData.includeLocation;
|
||||||
|
return {
|
||||||
|
...profileData,
|
||||||
|
latitude: includeLocation ? profileData.latitude : 0,
|
||||||
|
longitude: includeLocation ? profileData.longitude : 0,
|
||||||
|
includeLocation,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear profile location
|
||||||
|
* @param profileData - Current profile data
|
||||||
|
* @returns Updated profile data
|
||||||
|
*/
|
||||||
|
clearProfileLocation(profileData: ProfileData): ProfileData {
|
||||||
|
return {
|
||||||
|
...profileData,
|
||||||
|
latitude: 0,
|
||||||
|
longitude: 0,
|
||||||
|
includeLocation: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset profile to default state
|
||||||
|
* @returns Default profile data
|
||||||
|
*/
|
||||||
|
getDefaultProfile(): ProfileData {
|
||||||
|
return {
|
||||||
|
description: "",
|
||||||
|
latitude: 0,
|
||||||
|
longitude: 0,
|
||||||
|
includeLocation: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Type guard for API errors
|
||||||
|
*/
|
||||||
|
private isApiError(
|
||||||
|
error: unknown,
|
||||||
|
): error is { response?: { status?: number } } {
|
||||||
|
return typeof error === "object" && error !== null && "response" in error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Factory function to create a ProfileService instance
|
||||||
|
*/
|
||||||
|
export function createProfileService(
|
||||||
|
axios: AxiosInstance,
|
||||||
|
partnerApiServer: string,
|
||||||
|
): ProfileService {
|
||||||
|
return new ProfileService(axios, partnerApiServer);
|
||||||
|
}
|
||||||
@@ -975,7 +975,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import "leaflet/dist/leaflet.css";
|
import "leaflet/dist/leaflet.css";
|
||||||
|
|
||||||
import { AxiosError } from "axios";
|
|
||||||
import { Buffer } from "buffer/";
|
import { Buffer } from "buffer/";
|
||||||
import "dexie-export-import";
|
import "dexie-export-import";
|
||||||
// @ts-expect-error - they aren't exporting it but it's there
|
// @ts-expect-error - they aren't exporting it but it's there
|
||||||
@@ -1007,16 +1006,10 @@ import {
|
|||||||
} from "../constants/app";
|
} from "../constants/app";
|
||||||
import { Contact } from "../db/tables/contacts";
|
import { Contact } from "../db/tables/contacts";
|
||||||
import { DEFAULT_PASSKEY_EXPIRATION_MINUTES } from "../db/tables/settings";
|
import { DEFAULT_PASSKEY_EXPIRATION_MINUTES } from "../db/tables/settings";
|
||||||
import * as databaseUtil from "../db/databaseUtil";
|
import { EndorserRateLimits, ImageRateLimits } from "../interfaces";
|
||||||
import {
|
|
||||||
EndorserRateLimits,
|
|
||||||
ImageRateLimits,
|
|
||||||
ErrorResponse,
|
|
||||||
} from "../interfaces";
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
clearPasskeyToken,
|
clearPasskeyToken,
|
||||||
errorStringForLog,
|
|
||||||
fetchEndorserRateLimits,
|
fetchEndorserRateLimits,
|
||||||
fetchImageRateLimits,
|
fetchImageRateLimits,
|
||||||
getHeaders,
|
getHeaders,
|
||||||
@@ -1027,38 +1020,23 @@ import {
|
|||||||
DIRECT_PUSH_TITLE,
|
DIRECT_PUSH_TITLE,
|
||||||
retrieveAccountMetadata,
|
retrieveAccountMetadata,
|
||||||
} from "../libs/util";
|
} from "../libs/util";
|
||||||
import { UserProfile } from "@/libs/partnerServer";
|
|
||||||
import { logger } from "../utils/logger";
|
import { logger } from "../utils/logger";
|
||||||
import { PlatformServiceMixin } from "../utils/PlatformServiceMixin";
|
import { PlatformServiceMixin } from "../utils/PlatformServiceMixin";
|
||||||
import { createNotifyHelpers, TIMEOUTS } from "@/utils/notify";
|
import { createNotifyHelpers, TIMEOUTS } from "@/utils/notify";
|
||||||
import { ACCOUNT_VIEW_CONSTANTS } from "@/constants/accountView";
|
import { ACCOUNT_VIEW_CONSTANTS } from "@/constants/accountView";
|
||||||
import {
|
import {
|
||||||
AccountSettings,
|
AccountSettings,
|
||||||
UserProfileResponse,
|
|
||||||
isApiError,
|
isApiError,
|
||||||
isError,
|
|
||||||
ImportContent,
|
ImportContent,
|
||||||
} from "@/interfaces/accountView";
|
} from "@/interfaces/accountView";
|
||||||
|
import {
|
||||||
|
ProfileService,
|
||||||
|
createProfileService,
|
||||||
|
ProfileData,
|
||||||
|
} from "@/services/ProfileService";
|
||||||
|
|
||||||
const inputImportFileNameRef = ref<Blob>();
|
const inputImportFileNameRef = ref<Blob>();
|
||||||
|
|
||||||
// Helper function to extract error message
|
|
||||||
function extractErrorMessage(error: unknown): string {
|
|
||||||
if (isApiError(error)) {
|
|
||||||
const apiError = error.response?.data?.error;
|
|
||||||
if (typeof apiError === "string") {
|
|
||||||
return apiError;
|
|
||||||
}
|
|
||||||
if (typeof apiError === "object" && apiError?.message) {
|
|
||||||
return apiError.message;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (isError(error)) {
|
|
||||||
return error.message;
|
|
||||||
}
|
|
||||||
return "An unknown error occurred";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
components: {
|
components: {
|
||||||
EntityIcon,
|
EntityIcon,
|
||||||
@@ -1079,9 +1057,6 @@ export default class AccountViewView extends Vue {
|
|||||||
$route!: RouteLocationNormalizedLoaded;
|
$route!: RouteLocationNormalizedLoaded;
|
||||||
$router!: Router;
|
$router!: Router;
|
||||||
|
|
||||||
// Add notification helpers
|
|
||||||
private notify = createNotifyHelpers(this.$notify);
|
|
||||||
|
|
||||||
// Constants
|
// Constants
|
||||||
readonly AppConstants: typeof AppString = AppString;
|
readonly AppConstants: typeof AppString = AppString;
|
||||||
readonly DEFAULT_PUSH_SERVER: string = DEFAULT_PUSH_SERVER;
|
readonly DEFAULT_PUSH_SERVER: string = DEFAULT_PUSH_SERVER;
|
||||||
@@ -1146,6 +1121,9 @@ export default class AccountViewView extends Vue {
|
|||||||
imageLimits: ImageRateLimits | null = null;
|
imageLimits: ImageRateLimits | null = null;
|
||||||
limitsMessage: string = "";
|
limitsMessage: string = "";
|
||||||
|
|
||||||
|
private profileService!: ProfileService;
|
||||||
|
private notify!: ReturnType<typeof createNotifyHelpers>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Async function executed when the component is mounted.
|
* Async function executed when the component is mounted.
|
||||||
* Initializes the component's state with values from the database,
|
* Initializes the component's state with values from the database,
|
||||||
@@ -1154,55 +1132,34 @@ export default class AccountViewView extends Vue {
|
|||||||
* @throws Will display specific messages to the user based on different errors.
|
* @throws Will display specific messages to the user based on different errors.
|
||||||
*/
|
*/
|
||||||
async mounted(): Promise<void> {
|
async mounted(): Promise<void> {
|
||||||
|
this.notify = createNotifyHelpers(this.$notify);
|
||||||
|
this.profileService = createProfileService(this.axios, this.partnerApiServer);
|
||||||
try {
|
try {
|
||||||
// Initialize component state with values from the database or defaults
|
|
||||||
await this.initializeState();
|
await this.initializeState();
|
||||||
await this.processIdentity();
|
await this.processIdentity();
|
||||||
|
|
||||||
// Load the user profile
|
|
||||||
if (this.isRegistered) {
|
if (this.isRegistered) {
|
||||||
try {
|
try {
|
||||||
const headers = await getHeaders(this.activeDid);
|
const profile = await this.profileService.loadProfile(this.activeDid);
|
||||||
const response = await this.axios.get<UserProfileResponse>(
|
if (profile) {
|
||||||
this.partnerApiServer +
|
this.userProfileDesc = profile.description;
|
||||||
"/api/partner/userProfileForIssuer/" +
|
this.userProfileLatitude = profile.latitude;
|
||||||
this.activeDid,
|
this.userProfileLongitude = profile.longitude;
|
||||||
{ headers },
|
this.includeUserProfileLocation = profile.includeLocation;
|
||||||
);
|
|
||||||
if (response.status === 200) {
|
|
||||||
this.userProfileDesc = response.data.data.description || "";
|
|
||||||
this.userProfileLatitude = response.data.data.locLat || 0;
|
|
||||||
this.userProfileLongitude = response.data.data.locLon || 0;
|
|
||||||
if (this.userProfileLatitude && this.userProfileLongitude) {
|
|
||||||
this.includeUserProfileLocation = true;
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
// won't get here because axios throws an error instead
|
// Profile not created yet; leave defaults
|
||||||
throw Error(ACCOUNT_VIEW_CONSTANTS.ERRORS.UNABLE_TO_LOAD_PROFILE);
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (isApiError(error) && error.response?.status === 404) {
|
this.notify.error(ACCOUNT_VIEW_CONSTANTS.ERRORS.PROFILE_NOT_AVAILABLE);
|
||||||
// this is ok: the profile is not yet created
|
|
||||||
} else {
|
|
||||||
databaseUtil.logConsoleAndDb(
|
|
||||||
"Error loading profile: " + errorStringForLog(error),
|
|
||||||
);
|
|
||||||
this.notify.error(
|
|
||||||
ACCOUNT_VIEW_CONSTANTS.ERRORS.PROFILE_NOT_AVAILABLE,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// this can happen when running automated tests in dev mode because notifications don't work
|
|
||||||
logger.error(
|
logger.error(
|
||||||
"Telling user to clear cache at page create because:",
|
"Telling user to clear cache at page create because:",
|
||||||
error,
|
error,
|
||||||
);
|
);
|
||||||
// this sometimes gives different information on the error
|
|
||||||
logger.error(
|
logger.error(
|
||||||
"To repeat with concatenated error: telling user to clear cache at page create because: " +
|
"To repeat with concatenated error: telling user to clear cache at page create because: " + error,
|
||||||
error,
|
|
||||||
);
|
);
|
||||||
this.notify.error(ACCOUNT_VIEW_CONSTANTS.ERRORS.PROFILE_LOAD_ERROR);
|
this.notify.error(ACCOUNT_VIEW_CONSTANTS.ERRORS.PROFILE_LOAD_ERROR);
|
||||||
} finally {
|
} finally {
|
||||||
@@ -1247,8 +1204,7 @@ export default class AccountViewView extends Vue {
|
|||||||
* Initializes component state with values from the database or defaults.
|
* Initializes component state with values from the database or defaults.
|
||||||
*/
|
*/
|
||||||
async initializeState(): Promise<void> {
|
async initializeState(): Promise<void> {
|
||||||
const settings: AccountSettings =
|
const settings: AccountSettings = await this.$accountSettings();
|
||||||
await databaseUtil.retrieveSettingsForActiveAccount();
|
|
||||||
|
|
||||||
this.activeDid = settings.activeDid || "";
|
this.activeDid = settings.activeDid || "";
|
||||||
this.apiServer = settings.apiServer || "";
|
this.apiServer = settings.apiServer || "";
|
||||||
@@ -1623,109 +1579,77 @@ export default class AccountViewView extends Vue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async checkLimits(): Promise<void> {
|
async checkLimits(): Promise<void> {
|
||||||
if (this.activeDid) {
|
|
||||||
this.checkLimitsFor(this.activeDid);
|
|
||||||
} else {
|
|
||||||
this.limitsMessage = ACCOUNT_VIEW_CONSTANTS.LIMITS.NO_IDENTIFIER;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Use "checkLimits" instead.
|
|
||||||
*
|
|
||||||
* Asynchronously checks rate limits for the given identity.
|
|
||||||
*
|
|
||||||
* Updates component state variables `limits`, `limitsMessage`, and `loadingLimits`.
|
|
||||||
*/
|
|
||||||
private async checkLimitsFor(did: string): Promise<void> {
|
|
||||||
this.loadingLimits = true;
|
this.loadingLimits = true;
|
||||||
this.limitsMessage = "";
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const resp = await fetchEndorserRateLimits(
|
const did = this.activeDid;
|
||||||
|
if (!did) {
|
||||||
|
this.limitsMessage = ACCOUNT_VIEW_CONSTANTS.LIMITS.NO_IDENTIFIER;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.$saveUserSettings(did, {
|
||||||
|
apiServer: this.apiServer,
|
||||||
|
partnerApiServer: this.partnerApiServer,
|
||||||
|
webPushServer: this.webPushServer,
|
||||||
|
});
|
||||||
|
|
||||||
|
const imageResp = await fetchImageRateLimits(this.axios, did);
|
||||||
|
if (imageResp.status === 200) {
|
||||||
|
this.imageLimits = imageResp.data;
|
||||||
|
} else {
|
||||||
|
await this.$saveSettings({
|
||||||
|
profileImageUrl: "",
|
||||||
|
});
|
||||||
|
this.profileImageUrl = "";
|
||||||
|
this.limitsMessage = ACCOUNT_VIEW_CONSTANTS.LIMITS.NO_IMAGE_ACCESS;
|
||||||
|
this.notify.warning(ACCOUNT_VIEW_CONSTANTS.LIMITS.CANNOT_UPLOAD_IMAGES);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const endorserResp = await fetchEndorserRateLimits(
|
||||||
this.apiServer,
|
this.apiServer,
|
||||||
this.axios,
|
this.axios,
|
||||||
did,
|
did,
|
||||||
);
|
);
|
||||||
if (resp.status === 200) {
|
if (endorserResp.status === 200) {
|
||||||
this.endorserLimits = resp.data;
|
this.endorserLimits = endorserResp.data;
|
||||||
if (!this.isRegistered) {
|
} else {
|
||||||
// the user was not known to be registered, but now they are (because we got no error) so let's record it
|
await this.$saveSettings({
|
||||||
try {
|
profileImageUrl: "",
|
||||||
await databaseUtil.updateDidSpecificSettings(did, {
|
});
|
||||||
isRegistered: true,
|
this.profileImageUrl = "";
|
||||||
});
|
this.limitsMessage = ACCOUNT_VIEW_CONSTANTS.LIMITS.NO_LIMITS_FOUND;
|
||||||
this.isRegistered = true;
|
this.notify.warning(ACCOUNT_VIEW_CONSTANTS.LIMITS.BAD_SERVER_RESPONSE);
|
||||||
} catch (err) {
|
return;
|
||||||
logger.error("Got an error updating settings:", err);
|
|
||||||
this.notify.error(
|
|
||||||
ACCOUNT_VIEW_CONSTANTS.ERRORS.SETTINGS_UPDATE_ERROR,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
const imageResp = await fetchImageRateLimits(this.axios, did);
|
|
||||||
if (imageResp.status === 200) {
|
|
||||||
this.imageLimits = imageResp.data;
|
|
||||||
} else {
|
|
||||||
this.limitsMessage = ACCOUNT_VIEW_CONSTANTS.LIMITS.NO_IMAGE_ACCESS;
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
this.limitsMessage =
|
|
||||||
ACCOUNT_VIEW_CONSTANTS.LIMITS.CANNOT_UPLOAD_IMAGES;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.handleRateLimitsError(error);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.loadingLimits = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles errors that occur while fetching rate limits.
|
|
||||||
*
|
|
||||||
* @param {AxiosError | Error} error - The error object.
|
|
||||||
*/
|
|
||||||
private handleRateLimitsError(error: unknown): void {
|
|
||||||
if (error instanceof AxiosError) {
|
|
||||||
if (error.status == 400 || error.status == 404) {
|
|
||||||
// no worries: they probably just aren't registered and don't have any limits
|
|
||||||
logger.log(
|
|
||||||
"Got 400 or 404 response retrieving limits which probably means they're not registered:",
|
|
||||||
error,
|
|
||||||
);
|
|
||||||
this.limitsMessage = ACCOUNT_VIEW_CONSTANTS.LIMITS.NO_LIMITS_FOUND;
|
|
||||||
} else {
|
|
||||||
const data = error.response?.data as ErrorResponse;
|
|
||||||
this.limitsMessage =
|
|
||||||
(data?.error?.message as string) ||
|
|
||||||
ACCOUNT_VIEW_CONSTANTS.LIMITS.BAD_SERVER_RESPONSE;
|
|
||||||
logger.error("Got bad response retrieving limits:", error);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this.limitsMessage =
|
this.limitsMessage =
|
||||||
ACCOUNT_VIEW_CONSTANTS.LIMITS.ERROR_RETRIEVING_LIMITS;
|
ACCOUNT_VIEW_CONSTANTS.LIMITS.ERROR_RETRIEVING_LIMITS;
|
||||||
logger.error("Got some error retrieving limits:", error);
|
this.notify.error(ACCOUNT_VIEW_CONSTANTS.LIMITS.ERROR_RETRIEVING_LIMITS);
|
||||||
|
} finally {
|
||||||
|
this.loadingLimits = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async onClickSaveApiServer(): Promise<void> {
|
async onClickSaveApiServer(): Promise<void> {
|
||||||
await databaseUtil.updateDefaultSettings({
|
await this.$saveSettings({
|
||||||
apiServer: this.apiServerInput,
|
apiServer: this.apiServerInput,
|
||||||
});
|
});
|
||||||
this.apiServer = this.apiServerInput;
|
this.apiServer = this.apiServerInput;
|
||||||
}
|
}
|
||||||
|
|
||||||
async onClickSavePartnerServer(): Promise<void> {
|
async onClickSavePartnerServer(): Promise<void> {
|
||||||
await databaseUtil.updateDefaultSettings({
|
await this.$saveSettings({
|
||||||
partnerApiServer: this.partnerApiServerInput,
|
partnerApiServer: this.partnerApiServerInput,
|
||||||
});
|
});
|
||||||
this.partnerApiServer = this.partnerApiServerInput;
|
this.partnerApiServer = this.partnerApiServerInput;
|
||||||
|
await this.$saveUserSettings(this.activeDid, {
|
||||||
|
partnerApiServer: this.partnerApiServer,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async onClickSavePushServer(): Promise<void> {
|
async onClickSavePushServer(): Promise<void> {
|
||||||
await databaseUtil.updateDefaultSettings({
|
await this.$saveSettings({
|
||||||
webPushServer: this.webPushServerInput,
|
webPushServer: this.webPushServerInput,
|
||||||
});
|
});
|
||||||
this.webPushServer = this.webPushServerInput;
|
this.webPushServer = this.webPushServerInput;
|
||||||
@@ -1735,7 +1659,7 @@ export default class AccountViewView extends Vue {
|
|||||||
openImageDialog(): void {
|
openImageDialog(): void {
|
||||||
(this.$refs.imageMethodDialog as ImageMethodDialog).open(
|
(this.$refs.imageMethodDialog as ImageMethodDialog).open(
|
||||||
async (imgUrl) => {
|
async (imgUrl) => {
|
||||||
await databaseUtil.updateDefaultSettings({
|
await this.$saveSettings({
|
||||||
profileImageUrl: imgUrl,
|
profileImageUrl: imgUrl,
|
||||||
});
|
});
|
||||||
this.profileImageUrl = imgUrl;
|
this.profileImageUrl = imgUrl;
|
||||||
@@ -1754,52 +1678,24 @@ export default class AccountViewView extends Vue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async deleteImage(): Promise<void> {
|
async deleteImage(): Promise<void> {
|
||||||
if (!this.profileImageUrl) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
const headers = await getHeaders(this.activeDid);
|
|
||||||
this.passkeyExpirationDescription = tokenExpiryTimeDescription();
|
|
||||||
if (
|
|
||||||
window.location.hostname === "localhost" &&
|
|
||||||
!DEFAULT_IMAGE_API_SERVER.includes("localhost")
|
|
||||||
) {
|
|
||||||
logger.log(
|
|
||||||
"Using shared image API server, so only users on that server can play with images.",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
const response = await this.axios.delete(
|
const response = await this.axios.delete(
|
||||||
DEFAULT_IMAGE_API_SERVER +
|
this.apiServer + "/api/image/" + this.profileImageUrl,
|
||||||
"/image/" +
|
{ headers: await getHeaders(this.activeDid) },
|
||||||
encodeURIComponent(this.profileImageUrl),
|
|
||||||
{ headers },
|
|
||||||
);
|
);
|
||||||
if (response.status === 204) {
|
if (response.status === 204) {
|
||||||
// don't bother with a notification
|
this.profileImageUrl = "";
|
||||||
// (either they'll simply continue or they're canceling and going back)
|
await this.$saveSettings({
|
||||||
|
profileImageUrl: "",
|
||||||
|
});
|
||||||
|
this.notify.success("Image deleted successfully.");
|
||||||
} else {
|
} else {
|
||||||
logger.error("Non-success deleting image:", response);
|
logger.error("Non-success deleting image:", response);
|
||||||
this.notify.error(ACCOUNT_VIEW_CONSTANTS.ERRORS.IMAGE_DELETE_PROBLEM);
|
this.notify.error(ACCOUNT_VIEW_CONSTANTS.ERRORS.IMAGE_DELETE_PROBLEM);
|
||||||
// keep the imageUrl in localStorage so the user can try again if they want
|
// keep the imageUrl in localStorage so the user can try again if they want
|
||||||
}
|
}
|
||||||
|
|
||||||
await databaseUtil.updateDefaultSettings({
|
|
||||||
profileImageUrl: undefined,
|
|
||||||
});
|
|
||||||
|
|
||||||
this.profileImageUrl = undefined;
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error("Error deleting image:", error);
|
if (isApiError(error) && error.response?.status === 404) {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
if ((error as any).response.status === 404) {
|
|
||||||
logger.error("The image was already deleted:", error);
|
|
||||||
|
|
||||||
await databaseUtil.updateDidSpecificSettings(this.activeDid, {
|
|
||||||
profileImageUrl: undefined,
|
|
||||||
});
|
|
||||||
|
|
||||||
this.profileImageUrl = undefined;
|
|
||||||
|
|
||||||
// it already doesn't exist so we won't say anything to the user
|
// it already doesn't exist so we won't say anything to the user
|
||||||
} else {
|
} else {
|
||||||
this.notify.error(
|
this.notify.error(
|
||||||
@@ -1825,55 +1721,39 @@ export default class AccountViewView extends Vue {
|
|||||||
|
|
||||||
async saveProfile(): Promise<void> {
|
async saveProfile(): Promise<void> {
|
||||||
this.savingProfile = true;
|
this.savingProfile = true;
|
||||||
|
const profileData: ProfileData = {
|
||||||
|
description: this.userProfileDesc,
|
||||||
|
latitude: this.userProfileLatitude,
|
||||||
|
longitude: this.userProfileLongitude,
|
||||||
|
includeLocation: this.includeUserProfileLocation,
|
||||||
|
};
|
||||||
try {
|
try {
|
||||||
const headers = await getHeaders(this.activeDid);
|
const success = await this.profileService.saveProfile(
|
||||||
const payload: UserProfile = {
|
this.activeDid,
|
||||||
description: this.userProfileDesc,
|
profileData,
|
||||||
};
|
|
||||||
if (this.userProfileLatitude && this.userProfileLongitude) {
|
|
||||||
payload.locLat = this.userProfileLatitude;
|
|
||||||
payload.locLon = this.userProfileLongitude;
|
|
||||||
} else if (this.includeUserProfileLocation) {
|
|
||||||
this.notify.toast(
|
|
||||||
"",
|
|
||||||
ACCOUNT_VIEW_CONSTANTS.INFO.NO_PROFILE_LOCATION,
|
|
||||||
TIMEOUTS.STANDARD,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
const response = await this.axios.post(
|
|
||||||
this.partnerApiServer + "/api/partner/userProfile",
|
|
||||||
payload,
|
|
||||||
{ headers },
|
|
||||||
);
|
);
|
||||||
if (response.status === 201) {
|
if (success) {
|
||||||
this.notify.success(
|
this.notify.success(ACCOUNT_VIEW_CONSTANTS.SUCCESS.PROFILE_SAVED);
|
||||||
ACCOUNT_VIEW_CONSTANTS.SUCCESS.PROFILE_SAVED,
|
|
||||||
TIMEOUTS.STANDARD,
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
// won't get here because axios throws an error on non-success
|
this.notify.error(ACCOUNT_VIEW_CONSTANTS.ERRORS.PROFILE_SAVE_ERROR);
|
||||||
throw Error(ACCOUNT_VIEW_CONSTANTS.ERRORS.PROFILE_NOT_SAVED);
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
databaseUtil.logConsoleAndDb(
|
this.notify.error(ACCOUNT_VIEW_CONSTANTS.ERRORS.PROFILE_SAVE_ERROR);
|
||||||
"Error saving profile: " + errorStringForLog(error),
|
|
||||||
);
|
|
||||||
const errorMessage: string =
|
|
||||||
extractErrorMessage(error) ||
|
|
||||||
ACCOUNT_VIEW_CONSTANTS.ERRORS.PROFILE_SAVE_ERROR;
|
|
||||||
this.notify.error(errorMessage, TIMEOUTS.STANDARD);
|
|
||||||
} finally {
|
} finally {
|
||||||
this.savingProfile = false;
|
this.savingProfile = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleUserProfileLocation(): void {
|
toggleUserProfileLocation(): void {
|
||||||
this.includeUserProfileLocation = !this.includeUserProfileLocation;
|
const updated = this.profileService.toggleProfileLocation({
|
||||||
if (!this.includeUserProfileLocation) {
|
description: this.userProfileDesc,
|
||||||
this.userProfileLatitude = 0;
|
latitude: this.userProfileLatitude,
|
||||||
this.userProfileLongitude = 0;
|
longitude: this.userProfileLongitude,
|
||||||
this.zoom = 2;
|
includeLocation: this.includeUserProfileLocation,
|
||||||
}
|
});
|
||||||
|
this.userProfileLatitude = updated.latitude;
|
||||||
|
this.userProfileLongitude = updated.longitude;
|
||||||
|
this.includeUserProfileLocation = updated.includeLocation;
|
||||||
}
|
}
|
||||||
|
|
||||||
confirmEraseLatLong(): void {
|
confirmEraseLatLong(): void {
|
||||||
@@ -1900,35 +1780,19 @@ export default class AccountViewView extends Vue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async deleteProfile(): Promise<void> {
|
async deleteProfile(): Promise<void> {
|
||||||
this.savingProfile = true;
|
|
||||||
try {
|
try {
|
||||||
const headers = await getHeaders(this.activeDid);
|
const success = await this.profileService.deleteProfile(this.activeDid);
|
||||||
const response = await this.axios.delete(
|
if (success) {
|
||||||
this.partnerApiServer + "/api/partner/userProfile",
|
this.notify.success(ACCOUNT_VIEW_CONSTANTS.SUCCESS.PROFILE_DELETED);
|
||||||
{ headers },
|
|
||||||
);
|
|
||||||
if (response.status === 204) {
|
|
||||||
this.userProfileDesc = "";
|
this.userProfileDesc = "";
|
||||||
this.userProfileLatitude = 0;
|
this.userProfileLatitude = 0;
|
||||||
this.userProfileLongitude = 0;
|
this.userProfileLongitude = 0;
|
||||||
this.includeUserProfileLocation = false;
|
this.includeUserProfileLocation = false;
|
||||||
this.notify.success(
|
|
||||||
ACCOUNT_VIEW_CONSTANTS.SUCCESS.PROFILE_DELETED,
|
|
||||||
TIMEOUTS.STANDARD,
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
throw Error(ACCOUNT_VIEW_CONSTANTS.ERRORS.PROFILE_NOT_DELETED);
|
this.notify.error(ACCOUNT_VIEW_CONSTANTS.ERRORS.PROFILE_DELETE_ERROR);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
databaseUtil.logConsoleAndDb(
|
this.notify.error(ACCOUNT_VIEW_CONSTANTS.ERRORS.PROFILE_DELETE_ERROR);
|
||||||
"Error deleting profile: " + errorStringForLog(error),
|
|
||||||
);
|
|
||||||
const errorMessage: string =
|
|
||||||
extractErrorMessage(error) ||
|
|
||||||
ACCOUNT_VIEW_CONSTANTS.ERRORS.PROFILE_DELETE_ERROR;
|
|
||||||
this.notify.error(errorMessage, TIMEOUTS.STANDARD);
|
|
||||||
} finally {
|
|
||||||
this.savingProfile = false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user