diff --git a/src/App.vue b/src/App.vue index 2ec923904..50249b2e6 100644 --- a/src/App.vue +++ b/src/App.vue @@ -309,6 +309,28 @@ </div> </div> </div> + + <div + v-else + class="absolute inset-0 h-screen flex flex-col items-center justify-center bg-slate-900/50" + > + <div + class="flex w-11/12 max-w-sm mx-auto mb-3 overflow-hidden bg-white rounded-lg shadow-lg" + > + <div class="w-full px-6 py-6 text-slate-900 text-center"> + <p class="text-lg mb-4"> + Something has gone very wrong. We'd appreciate if you'd + contact us and let us know how you got here. Thank you! + </p> + <button + @click="close(notification.id)" + class="block w-full text-center text-md font-bold uppercase bg-slate-600 text-white px-2 py-2 rounded-md" + > + Close + </button> + </div> + </div> + </div> </div> </Notification> </div> diff --git a/src/components/PushNotificationPermission.vue b/src/components/PushNotificationPermission.vue index dd9d283d8..54af4f805 100644 --- a/src/components/PushNotificationPermission.vue +++ b/src/components/PushNotificationPermission.vue @@ -9,7 +9,7 @@ > <div v-if="isVisible" - class="fixed z-[100] top-0 inset-x-0 w-full absolute inset-0 h-screen flex flex-col items-center justify-center bg-slate-900/50" + class="fixed z-[100] top-0 inset-x-0 w-full inset-0 h-screen flex flex-col items-center justify-center bg-slate-900/50" > <div class="flex w-11/12 max-w-sm mx-auto mb-3 overflow-hidden bg-white rounded-lg shadow-lg" @@ -276,9 +276,9 @@ export default class PushNotificationPermission extends Vue { } private async askPermission(): Promise<NotificationPermission> { - logConsoleAndDb( - "Requesting permission for notifications: " + JSON.stringify(navigator), - ); + // console.log( + // "Requesting permission for notifications: " + JSON.stringify(navigator), + // ); if ( !("serviceWorker" in navigator && navigator.serviceWorker?.controller) ) { @@ -344,7 +344,7 @@ export default class PushNotificationPermission extends Vue { }, -1, ); - throw new Error("We weren't granted permission."); + throw new Error("Permission was not granted to this app."); } return permission; }, diff --git a/src/components/QuickNav.vue b/src/components/QuickNav.vue index 58b02c29f..ade216143 100644 --- a/src/components/QuickNav.vue +++ b/src/components/QuickNav.vue @@ -90,6 +90,12 @@ > <div class="flex flex-col items-center"> <fa icon="circle-user" class="fa-fw" /> + <!-- + We used to say "account", so we'll keep that in the code, + but it isn't accurate because we don't hold anything for them. + We'll say "profile" to the users. + (Or: settings, face, registry, cache, repo, vault... or separate preferences from identity.) + --> <span class="text-xs mt-1">profile</span> </div> </router-link> diff --git a/src/libs/endorserServer.ts b/src/libs/endorserServer.ts index fedf43681..ed44c399a 100644 --- a/src/libs/endorserServer.ts +++ b/src/libs/endorserServer.ts @@ -4,10 +4,10 @@ import { sha256 } from "ethereum-cryptography/sha256"; import { LRUCache } from "lru-cache"; import * as R from "ramda"; -import { DEFAULT_IMAGE_API_SERVER } from "@/constants/app"; +import { DEFAULT_IMAGE_API_SERVER, NotificationIface } from "@/constants/app"; import { Contact } from "@/db/tables/contacts"; import { accessToken, deriveAddress, nextDerivationPath } from "@/libs/crypto"; -import { NonsensitiveDexie } from "@/db/index"; +import { logConsoleAndDb, NonsensitiveDexie } from "@/db/index"; import { retrieveAccountMetadata, retrieveFullyDecryptedAccount, @@ -501,35 +501,72 @@ export function tokenExpiryTimeDescription() { /** * Get the headers for a request, potentially including Authorization */ -export async function getHeaders(did?: string) { +export async function getHeaders( + did?: string, + $notify?: (notification: NotificationIface, timeout?: number) => void, + failureMessage?: string, +) { const headers: { "Content-Type": string; Authorization?: string } = { "Content-Type": "application/json", }; if (did) { - let token; - const account = await retrieveAccountMetadata(did); - if (account?.passkeyCredIdHex) { - if ( - passkeyAccessToken && - passkeyTokenExpirationEpochSeconds > Date.now() / 1000 - ) { - // there's an active current passkey token - token = passkeyAccessToken; + try { + let token; + const account = await retrieveAccountMetadata(did); + if (account?.passkeyCredIdHex) { + if ( + passkeyAccessToken && + passkeyTokenExpirationEpochSeconds > Date.now() / 1000 + ) { + // there's an active current passkey token + token = passkeyAccessToken; + } else { + // there's no current passkey token or it's expired + token = await accessToken(did); + + passkeyAccessToken = token; + const passkeyExpirationSeconds = await getPasskeyExpirationSeconds(); + passkeyTokenExpirationEpochSeconds = + Date.now() / 1000 + passkeyExpirationSeconds; + } } else { - // there's no current passkey token or it's expired token = await accessToken(did); - - passkeyAccessToken = token; - const passkeyExpirationSeconds = await getPasskeyExpirationSeconds(); - passkeyTokenExpirationEpochSeconds = - Date.now() / 1000 + passkeyExpirationSeconds; } - } else { - token = await accessToken(did); + headers["Authorization"] = "Bearer " + token; + } catch (error) { + // This rarely happens: we've seen it when they have account info but the + // encryption secret got lost. But in most cases we want users to at + // least see their feed -- and anything else that returns results for + // anonymous users. + + // We'll continue with an anonymous request... still want to show feed and other things, but ideally let them know. + logConsoleAndDb( + "Something failed in getHeaders call (will proceed anonymously" + + ($notify ? " and notify user" : "") + + "): " + + // IntelliJ type system complains about getCircularReplacer() with: Argument of type '(obj: any, key: string, value: any) => any' is not assignable to parameter of type '(this: any, key: string, value: any) => any'. + //JSON.stringify(error, getCircularReplacer()), // JSON.stringify(error) on a Dexie error throws another error about: Converting circular structure to JSON + error, + true, + ); + if ($notify) { + // remember: only want to do this if they supplied a DID, expecting personal results + const notifyMessage = + failureMessage || + "Showing anonymous data. See the Help page for help with personal data."; + $notify( + { + group: "alert", + type: "danger", + title: "Personal Data Error", + text: notifyMessage, + }, + 3000, + ); + } } - headers["Authorization"] = "Bearer " + token; } else { - // it's often OK to request without auth; we assume necessary checks are done earlier + // it's usually OK to request without auth; we assume we're only here when allowed } return headers; } @@ -611,6 +648,7 @@ export async function getNewOffersToUser( url += "&beforeId=" + beforeOfferJwtId; } const headers = await getHeaders(activeDid); + console.log("Using headers: ", headers); const response = await axios.get(url, { headers }); return response.data; } diff --git a/src/libs/util.ts b/src/libs/util.ts index f449cbee9..653e78447 100644 --- a/src/libs/util.ts +++ b/src/libs/util.ts @@ -147,6 +147,23 @@ export interface ConfirmerData { numConfsNotVisible: number; } +// // This is meant to be a second argument to JSON.stringify to avoid circular references. +// // Usage: JSON.stringify(error, getCircularReplacer()) +// // Beware: we've seen this return "undefined" when there is actually a message, eg: DatabaseClosedError: Error DEXIE ENCRYPT ADDON: Encryption key has changed +// function getCircularReplacer() { +// const seen = new WeakSet(); +// // eslint-disable-next-line @typescript-eslint/no-explicit-any +// return (obj: any, key: string, value: any): any => { +// if (typeof value === "object" && value !== null) { +// if (seen.has(value)) { +// return "[circular ref]"; +// } +// seen.add(value); +// } +// return value; +// }; +// } + /** * @return only confirmers, excluding the issuer and hidden DIDs */ @@ -423,11 +440,13 @@ export function findAllVisibleToDids( export interface AccountKeyInfo extends Account, KeyMeta {} export const retrieveAccountCount = async (): Promise<number> => { + // one of the few times we use accountsDBPromise directly; try to avoid more usage const accountsDB = await accountsDBPromise; return await accountsDB.accounts.count(); }; export const retrieveAccountDids = async (): Promise<string[]> => { + // one of the few times we use accountsDBPromise directly; try to avoid more usage const accountsDB = await accountsDBPromise; const allAccounts = await accountsDB.accounts.toArray(); const allDids = allAccounts.map((acc) => acc.did); @@ -439,6 +458,7 @@ export const retrieveAccountDids = async (): Promise<string[]> => { export const retrieveAccountMetadata = async ( activeDid: string, ): Promise<AccountKeyInfo | undefined> => { + // one of the few times we use accountsDBPromise directly; try to avoid more usage const accountsDB = await accountsDBPromise; const account = (await accountsDB.accounts .where("did") @@ -454,6 +474,7 @@ export const retrieveAccountMetadata = async ( }; export const retrieveAllAccountsMetadata = async (): Promise<Account[]> => { + // one of the few times we use accountsDBPromise directly; try to avoid more usage const accountsDB = await accountsDBPromise; const array = await accountsDB.accounts.toArray(); return array.map((account) => { @@ -466,6 +487,7 @@ export const retrieveAllAccountsMetadata = async (): Promise<Account[]> => { export const retrieveFullyDecryptedAccount = async ( activeDid: string, ): Promise<AccountKeyInfo | undefined> => { + // one of the few times we use accountsDBPromise directly; try to avoid more usage const accountsDB = await accountsDBPromise; const account = (await accountsDB.accounts .where("did") @@ -496,6 +518,7 @@ export const generateSaveAndActivateIdentity = async (): Promise<string> => { const newId = newIdentifier(address, publicHex, privateHex, derivationPath); const identity = JSON.stringify(newId); + // one of the few times we use accountsDBPromise directly; try to avoid more usage const accountsDB = await accountsDBPromise; await accountsDB.accounts.add({ dateCreated: new Date().toISOString(), @@ -527,6 +550,7 @@ export const registerAndSavePasskey = async ( passkeyCredIdHex, publicKeyHex: Buffer.from(publicKeyBytes).toString("hex"), }; + // one of the few times we use accountsDBPromise directly; try to avoid more usage const accountsDB = await accountsDBPromise; await accountsDB.accounts.add(account); return account; diff --git a/src/router/index.ts b/src/router/index.ts index efd96bb5e..bffa5b4d2 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -18,6 +18,7 @@ const enterOrStart = async ( from: RouteLocationNormalized, next: NavigationGuardNext, ) => { + // one of the few times we use accountsDBPromise directly; try to avoid more usage const accountsDB = await accountsDBPromise; const num_accounts = await accountsDB.accounts.count(); if (num_accounts > 0) { @@ -263,6 +264,9 @@ const errorHandler = ( ) => { // Handle the error here console.error("Caught in top level error handler:", error, to, from); + alert( + "Something is very wrong. We'd love if you contacted us and let us know how you got here. Thank you!", + ); // You can also perform additional actions, such as displaying an error message or redirecting the user to a specific page }; diff --git a/src/views/AccountViewView.vue b/src/views/AccountViewView.vue index b99235970..1298f9758 100644 --- a/src/views/AccountViewView.vue +++ b/src/views/AccountViewView.vue @@ -951,8 +951,8 @@ export default class AccountViewView extends Vue { { group: "alert", type: "danger", - title: "Error Loading Account", - text: "Clear your cache and start over (after data backup).", + title: "Error Loading Profile", + text: "See the Help page about errors with your personal data.", }, -1, ); diff --git a/src/views/ClaimView.vue b/src/views/ClaimView.vue index ce7898a91..1e593e32a 100644 --- a/src/views/ClaimView.vue +++ b/src/views/ClaimView.vue @@ -484,7 +484,11 @@ import { useClipboard } from "@vueuse/core"; import GiftedDialog from "@/components/GiftedDialog.vue"; import QuickNav from "@/components/QuickNav.vue"; import { NotificationIface } from "@/constants/app"; -import { db, retrieveSettingsForActiveAccount } from "@/db/index"; +import { + db, + logConsoleAndDb, + retrieveSettingsForActiveAccount, +} from "@/db/index"; import { Contact } from "@/db/tables/contacts"; import * as serverUtil from "@/libs/endorserServer"; import { @@ -559,7 +563,24 @@ export default class ClaimView extends Vue { this.allContacts = await db.contacts.toArray(); this.isRegistered = settings.isRegistered || false; - this.allMyDids = await libsUtil.retrieveAccountDids(); + try { + this.allMyDids = await libsUtil.retrieveAccountDids(); + } catch (error) { + // continue because we want to see claims, even anonymously + logConsoleAndDb( + "Error retrieving all account DIDs on home page:" + error, + true, + ); + this.$notify( + { + group: "alert", + type: "danger", + title: "Error Loading Profile", + text: "See the Help page for problems with your personal data.", + }, + -1, + ); + } const pathParam = window.location.pathname.substring("/claim/".length); let claimId; diff --git a/src/views/ContactsView.vue b/src/views/ContactsView.vue index 408fa4d53..5bb378daf 100644 --- a/src/views/ContactsView.vue +++ b/src/views/ContactsView.vue @@ -171,8 +171,9 @@ <fa icon="circle-info" class="text-xl text-blue-500 ml-4" /> </router-link> - <span class="ml-4 text-sm overflow-hidden" - >{{ shortDid(contact.did) }}...</span + <span class="ml-4 text-sm overflow-hidden">{{ + shortDid(contact.did) + }}</span ><!-- The first 18 characters of did:peer are the same. --> </div> <div id="ContactActions" class="flex gap-1.5 mt-2"> @@ -425,7 +426,7 @@ export default class ContactsView extends Vue { group: "alert", type: "warning", title: "Blank Invite", - text: "The invite was not included. This can happen when your device cuts off the link, so you might try pasting the full link into a browser.", + text: "The invite was not included, which can happen when your iOS device cuts off the link. Try pasting the full link into a browser.", }, 7000, ); @@ -601,7 +602,7 @@ export default class ContactsView extends Vue { }; try { - const headers = await getHeaders(this.activeDid); + const headers = await getHeaders(this.activeDid, this.$notify); const givenByUrl = this.apiServer + "/api/v2/report/gives?agentDid=" + diff --git a/src/views/HelpNotificationsView.vue b/src/views/HelpNotificationsView.vue index 63bde2b18..e2c32c7d3 100644 --- a/src/views/HelpNotificationsView.vue +++ b/src/views/HelpNotificationsView.vue @@ -75,6 +75,7 @@ <button class="text-blue-500" @click="showNotificationChoice()"> Click here. </button> + <PushNotificationPermission ref="pushNotificationPermission" /> </p> </div> @@ -193,14 +194,18 @@ <h2 class="text-xl font-semibold mt-4">Reinstall</h2> <div> <p> - If all else fails, uninstall the app, ensure all the browser tabs with - it are closed, and clear out caches and storage. + If all else fails, it's best to start over. </p> <p> Of course, you'll want to back up all your data first -- all seeds as - well as the contacts & settings -- on the Account + well as the contacts & settings -- on the Profile <fa icon="circle-user" /> page. </p> + <p> + Here are instructions to uninstall the app and clear out caches and storage. + Note that you should first ensure check that the browser tabs with Time Safari are closed. + (If any are open then that will interfere with your refresh.) + </p> <ul class="ml-4 list-disc"> <li> Clear cache. @@ -304,9 +309,12 @@ import { Component, Vue } from "vue-facing-decorator"; import QuickNav from "@/components/QuickNav.vue"; import { NotificationIface } from "@/constants/app"; -import { sendTestThroughPushServer } from "@/libs/util"; +import { DIRECT_PUSH_TITLE, sendTestThroughPushServer } from "@/libs/util"; +import PushNotificationPermission from "@/components/PushNotificationPermission.vue"; +import { db } from "@/db/index"; +import { MASTER_SETTINGS_KEY } from "@/db/tables/settings"; -@Component({ components: { QuickNav } }) +@Component({ components: { PushNotificationPermission, QuickNav } }) export default class HelpNotificationsView extends Vue { $notify!: (notification: NotificationIface, timeout?: number) => void; @@ -407,14 +415,19 @@ export default class HelpNotificationsView extends Vue { } showNotificationChoice() { - this.$notify( - { - group: "modal", - type: "notification-permission", - title: "", // unused, only here to satisfy type check - text: "", // unused, only here to satisfy type check + (this.$refs.pushNotificationPermission as PushNotificationPermission).open( + DIRECT_PUSH_TITLE, + async (success: boolean, timeText: string, message?: string) => { + if (success) { + await db.settings.update(MASTER_SETTINGS_KEY, { + notifyingReminderMessage: message, + notifyingReminderTime: timeText, + }); + this.notifyingReminder = true; + this.notifyingReminderMessage = message || ""; + this.notifyingReminderTime = timeText; + } }, - -1, ); } } diff --git a/src/views/HelpView.vue b/src/views/HelpView.vue index b9bf4f0d1..dbbe34409 100644 --- a/src/views/HelpView.vue +++ b/src/views/HelpView.vue @@ -383,7 +383,7 @@ How do I access even more functionality? </h2> <p> - There is an "Advanced" section at the bottom of the Account + There is an "Advanced" section at the bottom of the Profile <fa icon="circle-user" /> page. </p> <p> @@ -422,19 +422,19 @@ </p> <h2 class="text-xl font-semibold"> - My app is misbehaving, like showing me a blank screen or failing to show a feed. + This app is misbehaving, like showing me a blank screen or failing to show my personal data. What can I do? </h2> <p> First, note that clearing the cache will clear all your identity and contact info, - so we recommend doing other things first (unless you know you have your backups ready). + so we recommend doing other things first -- and only clearing when have your backups ready. </p> <ul class="list-disc list-outside ml-4"> <li> Drag down on the screen to refresh it; do that multiple times, because - it sometimes takes multiple tries for the app to refresh to the current version. + it sometimes takes multiple tries for the app to refresh to the latest version. You can see the version information at the bottom of this page; the best - way to determine the current version is to open this page in an incognito + way to determine the latest version is to open this page in an incognito/private browser window and look at the version there. </li> <li> @@ -498,7 +498,7 @@ </a> <br /> For notifications, this service stores push token data; that can be revoked at any time - by disabling notifications on the Account <fa icon="circle-user" class="fa-fw" /> page. + by disabling notifications on the Profile <fa icon="circle-user" class="fa-fw" /> page. <br /> For all other claim data, <a href="https://endorser.ch/privacy-policy" target="_blank" class="text-blue-500"> @@ -520,9 +520,9 @@ class="text-blue-500 ml-2" > bc1q90v4ted6cpt63tjfh2lvd5xzfc67sd4g9w8xma - <fa v-show="!showDidCopy" icon="copy" class="text-slate-400 fa-fw" /> + <fa v-show="!showDidCopy" icon="copy" class="text-sm text-slate-400 fa-fw" /> + <fa v-show="showDidCopy" icon="circle-check" class="text-sm text-green-500 fa-fw"/> </button> - <span v-show="showDidCopy" class="ml-2 text-sm text-green-500">Copied</span> You can donate online via <a href="https://www.patreon.com/TimeSafari" target="_blank" class="text-blue-500">Patreon here</a>. For other donations, contact us. @@ -541,7 +541,7 @@ <p>{{ package.version }} ({{ commitHash }})</p> <h2 class="text-xl font-semibold"> - I have other questions or feedback, like getting a new account or removing my data or requesting an improvement. + I have other questions or feedback, like getting a new profile or removing my data or requesting an improvement. </h2> <p> Contact us at diff --git a/src/views/HomeView.vue b/src/views/HomeView.vue index bb9bd1cd6..8e4de4442 100644 --- a/src/views/HomeView.vue +++ b/src/views/HomeView.vue @@ -389,6 +389,7 @@ import { } from "@/constants/app"; import { db, + logConsoleAndDb, retrieveSettingsForActiveAccount, updateAccountSettings, } from "@/db/index"; @@ -486,12 +487,21 @@ export default class HomeView extends Vue { async mounted() { try { - this.allMyDids = await retrieveAccountDids(); - if (this.allMyDids.length === 0) { - this.isCreatingIdentifier = true; - const newDid = await generateSaveAndActivateIdentity(); - this.isCreatingIdentifier = false; - this.allMyDids = [newDid]; + try { + this.allMyDids = await retrieveAccountDids(); + if (this.allMyDids.length === 0) { + this.isCreatingIdentifier = true; + const newDid = await generateSaveAndActivateIdentity(); + this.isCreatingIdentifier = false; + this.allMyDids = [newDid]; + } + } catch (error) { + // continue because we want the feed to work, even anonymously + logConsoleAndDb( + "Error retrieving all account DIDs on home page:" + error, + true, + ); + // some other piece will display an error about personal info } const settings = await retrieveSettingsForActiveAccount(); @@ -546,6 +556,7 @@ export default class HomeView extends Vue { this.activeDid, this.lastAckedOfferToUserJwtId, ); + console.log("offersToUserData", offersToUserData); this.numNewOffersToUser = offersToUserData.data.length; this.newOffersToUserHitLimit = offersToUserData.hitLimit; } @@ -563,7 +574,7 @@ export default class HomeView extends Vue { // eslint-disable-next-line @typescript-eslint/no-explicit-any } catch (err: any) { - console.error("Error retrieving settings or feed.", err); + logConsoleAndDb("Error retrieving settings or feed: " + err, true); this.$notify( { group: "alert", @@ -760,13 +771,19 @@ export default class HomeView extends Vue { */ async retrieveGives(endorserApiServer: string, beforeId?: string) { const beforeQuery = beforeId == null ? "" : "&beforeId=" + beforeId; + const doNotShowErrorAgain = !!beforeId; // don't show error again if we're loading more + const headers = await getHeaders( + this.activeDid, + doNotShowErrorAgain ? undefined : this.$notify, + ); + // retrieve headers for this user, but if an error happens then report it but proceed with the fetch with no header const response = await fetch( endorserApiServer + "/api/v2/report/gives?giftNotTrade=true" + beforeQuery, { method: "GET", - headers: await getHeaders(this.activeDid), + headers: headers, }, ); diff --git a/src/views/IdentitySwitcherView.vue b/src/views/IdentitySwitcherView.vue index bfefd943f..43922034f 100644 --- a/src/views/IdentitySwitcherView.vue +++ b/src/views/IdentitySwitcherView.vue @@ -170,6 +170,7 @@ export default class IdentitySwitcherView extends Vue { title: "Delete Identity?", text: "Are you sure you want to erase this identity? (There is no undo. You may want to select it and back it up just in case.)", onYes: async () => { + // one of the few times we use accountsDBPromise directly; try to avoid more usage const accountsDB = await accountsDBPromise; await accountsDB.accounts.delete(id); this.otherIdentities = this.otherIdentities.filter( diff --git a/src/views/ProjectViewView.vue b/src/views/ProjectViewView.vue index 685e88f8f..787d7fbe8 100644 --- a/src/views/ProjectViewView.vue +++ b/src/views/ProjectViewView.vue @@ -489,7 +489,11 @@ import QuickNav from "@/components/QuickNav.vue"; import EntityIcon from "@/components/EntityIcon.vue"; import ProjectIcon from "@/components/ProjectIcon.vue"; import { NotificationIface } from "@/constants/app"; -import { db, retrieveSettingsForActiveAccount } from "@/db/index"; +import { + db, + logConsoleAndDb, + retrieveSettingsForActiveAccount, +} from "@/db/index"; import { Contact } from "@/db/tables/contacts"; import * as libsUtil from "@/libs/util"; import { @@ -557,7 +561,24 @@ export default class ProjectViewView extends Vue { this.allContacts = await db.contacts.toArray(); this.isRegistered = !!settings.isRegistered; - this.allMyDids = await retrieveAccountDids(); + try { + this.allMyDids = await retrieveAccountDids(); + } catch (error) { + // continue because we want to see claims, even anonymously + logConsoleAndDb( + "Error retrieving all account DIDs on home page:" + error, + true, + ); + this.$notify( + { + group: "alert", + type: "danger", + title: "Error Loading Profile", + text: "See the Help page to fix problems with your personal data.", + }, + -1, + ); + } const pathParam = window.location.pathname.substring("/project/".length); if (pathParam) { diff --git a/src/views/ProjectsView.vue b/src/views/ProjectsView.vue index 32fea4ea1..d1518130a 100644 --- a/src/views/ProjectsView.vue +++ b/src/views/ProjectsView.vue @@ -355,7 +355,7 @@ export default class ProjectsView extends Vue { **/ async projectDataLoader(url: string) { try { - const headers = await getHeaders(this.activeDid); + const headers = await getHeaders(this.activeDid, this.$notify); this.isLoading = true; const resp = await this.axios.get(url, { headers } as AxiosRequestConfig); if (resp.status === 200 && resp.data.data) { diff --git a/src/views/SeedBackupView.vue b/src/views/SeedBackupView.vue index 9604b014f..a991a794e 100644 --- a/src/views/SeedBackupView.vue +++ b/src/views/SeedBackupView.vue @@ -94,7 +94,7 @@ </button> </div> </div> - <div v-else>You do not have an active identifier.</div> + <div v-else>You do not have an active identity.</div> </section> </template> @@ -135,7 +135,7 @@ export default class SeedBackupView extends Vue { { group: "alert", type: "danger", - title: "Error Loading Account", + title: "Error Loading Profile", text: "Got an error loading your seed data.", }, -1,