forked from jsnbuchanan/crowd-funder-for-time-pwa
Merge branch 'master' into gifting-ui-2025-05
This commit is contained in:
@@ -263,6 +263,7 @@ Raymer * @version 1.0.0 */
|
||||
import { UAParser } from "ua-parser-js";
|
||||
import { Component, Vue } from "vue-facing-decorator";
|
||||
import { Router } from "vue-router";
|
||||
import { Capacitor } from "@capacitor/core";
|
||||
|
||||
//import App from "../App.vue";
|
||||
import EntityIcon from "../components/EntityIcon.vue";
|
||||
@@ -281,6 +282,7 @@ import {
|
||||
AppString,
|
||||
NotificationIface,
|
||||
PASSKEYS_ENABLED,
|
||||
USE_DEXIE_DB,
|
||||
} from "../constants/app";
|
||||
import {
|
||||
db,
|
||||
@@ -294,6 +296,7 @@ import {
|
||||
checkIsAnyFeedFilterOn,
|
||||
MASTER_SETTINGS_KEY,
|
||||
} from "../db/tables/settings";
|
||||
import * as databaseUtil from "../db/databaseUtil";
|
||||
import {
|
||||
contactForDid,
|
||||
containsNonHiddenDid,
|
||||
@@ -310,10 +313,52 @@ import {
|
||||
GiverReceiverInputInfo,
|
||||
OnboardPage,
|
||||
} from "../libs/util";
|
||||
import { GiveSummaryRecord } from "../interfaces";
|
||||
import { GiveSummaryRecord } from "../interfaces/records";
|
||||
import * as serverUtil from "../libs/endorserServer";
|
||||
import { logger } from "../utils/logger";
|
||||
import { GiveRecordWithContactInfo } from "../types";
|
||||
import { GiveRecordWithContactInfo } from "../interfaces/give";
|
||||
import { PlatformServiceFactory } from "@/services/PlatformServiceFactory";
|
||||
|
||||
interface Claim {
|
||||
claim?: Claim; // For nested claims in Verifiable Credentials
|
||||
agent?: {
|
||||
identifier?: string;
|
||||
did?: string;
|
||||
};
|
||||
recipient?: {
|
||||
identifier?: string;
|
||||
did?: string;
|
||||
};
|
||||
provider?:
|
||||
| {
|
||||
identifier?: string;
|
||||
}
|
||||
| Array<{ identifier?: string }>;
|
||||
object?: {
|
||||
amountOfThisGood?: number;
|
||||
unitCode?: string;
|
||||
};
|
||||
description?: string;
|
||||
image?: string;
|
||||
}
|
||||
|
||||
interface FulfillsPlan {
|
||||
locLat?: number;
|
||||
locLon?: number;
|
||||
name?: string;
|
||||
}
|
||||
|
||||
interface Provider {
|
||||
identifier?: string;
|
||||
}
|
||||
|
||||
interface ProvidedByPlan {
|
||||
name?: string;
|
||||
}
|
||||
|
||||
interface FeedError {
|
||||
userMessage?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* HomeView Component
|
||||
@@ -436,18 +481,92 @@ export default class HomeView extends Vue {
|
||||
*/
|
||||
private async initializeIdentity() {
|
||||
try {
|
||||
this.allMyDids = await retrieveAccountDids();
|
||||
if (this.allMyDids.length === 0) {
|
||||
this.isCreatingIdentifier = true;
|
||||
const newDid = await generateSaveAndActivateIdentity();
|
||||
this.isCreatingIdentifier = false;
|
||||
this.allMyDids = [newDid];
|
||||
// Retrieve DIDs with better error handling
|
||||
try {
|
||||
this.allMyDids = await retrieveAccountDids();
|
||||
logConsoleAndDb(`[HomeView] Retrieved ${this.allMyDids.length} DIDs`);
|
||||
} catch (error) {
|
||||
logConsoleAndDb(`[HomeView] Failed to retrieve DIDs: ${error}`, true);
|
||||
throw new Error(
|
||||
"Failed to load existing identities. Please try restarting the app.",
|
||||
);
|
||||
}
|
||||
|
||||
const settings = await retrieveSettingsForActiveAccount();
|
||||
// Create new DID if needed
|
||||
if (this.allMyDids.length === 0) {
|
||||
try {
|
||||
this.isCreatingIdentifier = true;
|
||||
const newDid = await generateSaveAndActivateIdentity();
|
||||
this.isCreatingIdentifier = false;
|
||||
this.allMyDids = [newDid];
|
||||
logConsoleAndDb(`[HomeView] Created new identity: ${newDid}`);
|
||||
} catch (error) {
|
||||
this.isCreatingIdentifier = false;
|
||||
logConsoleAndDb(
|
||||
`[HomeView] Failed to create new identity: ${error}`,
|
||||
true,
|
||||
);
|
||||
throw new Error("Failed to create new identity. Please try again.");
|
||||
}
|
||||
}
|
||||
|
||||
// Load settings with better error context
|
||||
let settings;
|
||||
try {
|
||||
settings = await databaseUtil.retrieveSettingsForActiveAccount();
|
||||
if (USE_DEXIE_DB) {
|
||||
settings = await retrieveSettingsForActiveAccount();
|
||||
}
|
||||
logConsoleAndDb(
|
||||
`[HomeView] Retrieved settings for ${settings.activeDid || "no active DID"}`,
|
||||
);
|
||||
} catch (error) {
|
||||
logConsoleAndDb(
|
||||
`[HomeView] Failed to retrieve settings: ${error}`,
|
||||
true,
|
||||
);
|
||||
throw new Error(
|
||||
"Failed to load user settings. Some features may be limited.",
|
||||
);
|
||||
}
|
||||
|
||||
// Update component state
|
||||
this.apiServer = settings.apiServer || "";
|
||||
this.activeDid = settings.activeDid || "";
|
||||
this.allContacts = await db.contacts.toArray();
|
||||
|
||||
// Load contacts with graceful fallback
|
||||
try {
|
||||
const platformService = PlatformServiceFactory.getInstance();
|
||||
const dbContacts = await platformService.dbQuery(
|
||||
"SELECT * FROM contacts",
|
||||
);
|
||||
this.allContacts = databaseUtil.mapQueryResultToValues(
|
||||
dbContacts,
|
||||
) as Contact[];
|
||||
if (USE_DEXIE_DB) {
|
||||
this.allContacts = await db.contacts.toArray();
|
||||
}
|
||||
logConsoleAndDb(
|
||||
`[HomeView] Retrieved ${this.allContacts.length} contacts`,
|
||||
);
|
||||
} catch (error) {
|
||||
logConsoleAndDb(
|
||||
`[HomeView] Failed to retrieve contacts: ${error}`,
|
||||
true,
|
||||
);
|
||||
this.allContacts = []; // Ensure we have a valid empty array
|
||||
this.$notify(
|
||||
{
|
||||
group: "alert",
|
||||
type: "warning",
|
||||
title: "Contact Loading Issue",
|
||||
text: "Some contact information may be unavailable.",
|
||||
},
|
||||
5000,
|
||||
);
|
||||
}
|
||||
|
||||
// Update remaining settings
|
||||
this.feedLastViewedClaimId = settings.lastViewedClaimId;
|
||||
this.givenName = settings.firstName || "";
|
||||
this.isFeedFilteredByVisible = !!settings.filterFeedByVisible;
|
||||
@@ -458,16 +577,16 @@ export default class HomeView extends Vue {
|
||||
settings.lastAckedOfferToUserProjectsJwtId;
|
||||
this.searchBoxes = settings.searchBoxes || [];
|
||||
this.showShortcutBvc = !!settings.showShortcutBvc;
|
||||
|
||||
this.isAnyFeedFilterOn = checkIsAnyFeedFilterOn(settings);
|
||||
|
||||
// Check onboarding status
|
||||
if (!settings.finishedOnboarding) {
|
||||
(this.$refs.onboardingDialog as OnboardingDialog).open(
|
||||
OnboardPage.Home,
|
||||
);
|
||||
}
|
||||
|
||||
// someone may have have registered after sharing contact info, so recheck
|
||||
// Check registration status if needed
|
||||
if (!this.isRegistered && this.activeDid) {
|
||||
try {
|
||||
const resp = await fetchEndorserRateLimits(
|
||||
@@ -476,56 +595,86 @@ export default class HomeView extends Vue {
|
||||
this.activeDid,
|
||||
);
|
||||
if (resp.status === 200) {
|
||||
await updateAccountSettings(this.activeDid, {
|
||||
await databaseUtil.updateDidSpecificSettings(this.activeDid, {
|
||||
isRegistered: true,
|
||||
...(await retrieveSettingsForActiveAccount()),
|
||||
...(await databaseUtil.retrieveSettingsForActiveAccount()),
|
||||
});
|
||||
if (USE_DEXIE_DB) {
|
||||
await updateAccountSettings(this.activeDid, {
|
||||
isRegistered: true,
|
||||
...(await retrieveSettingsForActiveAccount()),
|
||||
});
|
||||
}
|
||||
this.isRegistered = true;
|
||||
logConsoleAndDb(
|
||||
`[HomeView] User ${this.activeDid} is now registered`,
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
// ignore the error... just keep us unregistered
|
||||
} catch (error) {
|
||||
logConsoleAndDb(
|
||||
`[HomeView] Registration check failed: ${error}`,
|
||||
true,
|
||||
);
|
||||
// Continue as unregistered - this is expected for new users
|
||||
}
|
||||
}
|
||||
|
||||
// this returns a Promise but we don't need to wait for it
|
||||
this.updateAllFeed();
|
||||
// Initialize feed and offers
|
||||
try {
|
||||
// Start feed update in background
|
||||
this.updateAllFeed().catch((error) => {
|
||||
logConsoleAndDb(
|
||||
`[HomeView] Background feed update failed: ${error}`,
|
||||
true,
|
||||
);
|
||||
});
|
||||
|
||||
if (this.activeDid) {
|
||||
const offersToUserData = await getNewOffersToUser(
|
||||
this.axios,
|
||||
this.apiServer,
|
||||
this.activeDid,
|
||||
this.lastAckedOfferToUserJwtId,
|
||||
// Load new offers if we have an active DID
|
||||
if (this.activeDid) {
|
||||
const [offersToUser, offersToProjects] = await Promise.all([
|
||||
getNewOffersToUser(
|
||||
this.axios,
|
||||
this.apiServer,
|
||||
this.activeDid,
|
||||
this.lastAckedOfferToUserJwtId,
|
||||
),
|
||||
getNewOffersToUserProjects(
|
||||
this.axios,
|
||||
this.apiServer,
|
||||
this.activeDid,
|
||||
this.lastAckedOfferToUserProjectsJwtId,
|
||||
),
|
||||
]);
|
||||
|
||||
this.numNewOffersToUser = offersToUser.data.length;
|
||||
this.newOffersToUserHitLimit = offersToUser.hitLimit;
|
||||
this.numNewOffersToUserProjects = offersToProjects.data.length;
|
||||
this.newOffersToUserProjectsHitLimit = offersToProjects.hitLimit;
|
||||
|
||||
logConsoleAndDb(
|
||||
`[HomeView] Retrieved ${this.numNewOffersToUser} user offers and ` +
|
||||
`${this.numNewOffersToUserProjects} project offers`,
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
logConsoleAndDb(
|
||||
`[HomeView] Failed to initialize feed/offers: ${error}`,
|
||||
true,
|
||||
);
|
||||
this.numNewOffersToUser = offersToUserData.data.length;
|
||||
this.newOffersToUserHitLimit = offersToUserData.hitLimit;
|
||||
}
|
||||
|
||||
if (this.activeDid) {
|
||||
const offersToUserProjects = await getNewOffersToUserProjects(
|
||||
this.axios,
|
||||
this.apiServer,
|
||||
this.activeDid,
|
||||
this.lastAckedOfferToUserProjectsJwtId,
|
||||
// Don't throw - we can continue with empty feed
|
||||
this.$notify(
|
||||
{
|
||||
group: "alert",
|
||||
type: "warning",
|
||||
title: "Feed Loading Issue",
|
||||
text: "Some feed data may be unavailable. Pull to refresh.",
|
||||
},
|
||||
5000,
|
||||
);
|
||||
this.numNewOffersToUserProjects = offersToUserProjects.data.length;
|
||||
this.newOffersToUserProjectsHitLimit = offersToUserProjects.hitLimit;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
} catch (err: any) {
|
||||
logConsoleAndDb("Error retrieving settings or feed: " + err, true);
|
||||
this.$notify(
|
||||
{
|
||||
group: "alert",
|
||||
type: "danger",
|
||||
title: "Error",
|
||||
text:
|
||||
(err as { userMessage?: string })?.userMessage ||
|
||||
"There was an error retrieving your settings or the latest activity.",
|
||||
},
|
||||
5000,
|
||||
);
|
||||
} catch (error) {
|
||||
this.handleError(error);
|
||||
throw error; // Re-throw to be caught by mounted()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -542,7 +691,10 @@ export default class HomeView extends Vue {
|
||||
* Called by mounted() and reloadFeedOnChange()
|
||||
*/
|
||||
private async loadSettings() {
|
||||
const settings = await retrieveSettingsForActiveAccount();
|
||||
let settings = await databaseUtil.retrieveSettingsForActiveAccount();
|
||||
if (USE_DEXIE_DB) {
|
||||
settings = await retrieveSettingsForActiveAccount();
|
||||
}
|
||||
this.apiServer = settings.apiServer || "";
|
||||
this.activeDid = settings.activeDid || "";
|
||||
this.feedLastViewedClaimId = settings.lastViewedClaimId;
|
||||
@@ -566,7 +718,14 @@ export default class HomeView extends Vue {
|
||||
* Called by mounted() and initializeIdentity()
|
||||
*/
|
||||
private async loadContacts() {
|
||||
this.allContacts = await db.contacts.toArray();
|
||||
const platformService = PlatformServiceFactory.getInstance();
|
||||
const dbContacts = await platformService.dbQuery("SELECT * FROM contacts");
|
||||
this.allContacts = databaseUtil.mapQueryResultToValues(
|
||||
dbContacts,
|
||||
) as unknown as Contact[];
|
||||
if (USE_DEXIE_DB) {
|
||||
this.allContacts = await db.contacts.toArray();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -587,11 +746,22 @@ export default class HomeView extends Vue {
|
||||
this.activeDid,
|
||||
);
|
||||
if (resp.status === 200) {
|
||||
await updateAccountSettings(this.activeDid, {
|
||||
let settings = await databaseUtil.retrieveSettingsForActiveAccount();
|
||||
if (USE_DEXIE_DB) {
|
||||
settings = await retrieveSettingsForActiveAccount();
|
||||
}
|
||||
await databaseUtil.updateDidSpecificSettings(this.activeDid, {
|
||||
apiServer: this.apiServer,
|
||||
isRegistered: true,
|
||||
...(await retrieveSettingsForActiveAccount()),
|
||||
...settings,
|
||||
});
|
||||
if (USE_DEXIE_DB) {
|
||||
await updateAccountSettings(this.activeDid, {
|
||||
apiServer: this.apiServer,
|
||||
isRegistered: true,
|
||||
...settings,
|
||||
});
|
||||
}
|
||||
this.isRegistered = true;
|
||||
}
|
||||
} catch (e) {
|
||||
@@ -652,7 +822,10 @@ export default class HomeView extends Vue {
|
||||
* Called by mounted()
|
||||
*/
|
||||
private async checkOnboarding() {
|
||||
const settings = await retrieveSettingsForActiveAccount();
|
||||
let settings = await databaseUtil.retrieveSettingsForActiveAccount();
|
||||
if (USE_DEXIE_DB) {
|
||||
settings = await retrieveSettingsForActiveAccount();
|
||||
}
|
||||
if (!settings.finishedOnboarding) {
|
||||
(this.$refs.onboardingDialog as OnboardingDialog).open(OnboardPage.Home);
|
||||
}
|
||||
@@ -664,19 +837,26 @@ export default class HomeView extends Vue {
|
||||
* - Displays user notification
|
||||
*
|
||||
* @internal
|
||||
* Called by mounted() and handleFeedError()
|
||||
* Called by mounted() and initializeIdentity()
|
||||
* @param err Error object with optional userMessage
|
||||
*/
|
||||
private handleError(err: unknown) {
|
||||
logConsoleAndDb("Error retrieving settings or feed: " + err, true);
|
||||
const errorMessage = err instanceof Error ? err.message : String(err);
|
||||
const userMessage = (err as { userMessage?: string })?.userMessage;
|
||||
|
||||
logConsoleAndDb(
|
||||
`[HomeView] Initialization error: ${errorMessage}${userMessage ? ` (${userMessage})` : ""}`,
|
||||
true,
|
||||
);
|
||||
|
||||
this.$notify(
|
||||
{
|
||||
group: "alert",
|
||||
type: "danger",
|
||||
title: "Error",
|
||||
text:
|
||||
(err as { userMessage?: string })?.userMessage ||
|
||||
"There was an error retrieving your settings or the latest activity.",
|
||||
userMessage ||
|
||||
"There was an error loading your data. Please try refreshing the page.",
|
||||
},
|
||||
5000,
|
||||
);
|
||||
@@ -714,7 +894,10 @@ export default class HomeView extends Vue {
|
||||
* Called by FeedFilters component when filters change
|
||||
*/
|
||||
async reloadFeedOnChange() {
|
||||
const settings = await retrieveSettingsForActiveAccount();
|
||||
let settings = await databaseUtil.retrieveSettingsForActiveAccount();
|
||||
if (USE_DEXIE_DB) {
|
||||
settings = await retrieveSettingsForActiveAccount();
|
||||
}
|
||||
this.isFeedFilteredByVisible = !!settings.filterFeedByVisible;
|
||||
this.isFeedFilteredByNearby = !!settings.filterFeedByNearby;
|
||||
this.isAnyFeedFilterOn = checkIsAnyFeedFilterOn(settings);
|
||||
@@ -952,7 +1135,7 @@ export default class HomeView extends Vue {
|
||||
* @param claim The claim object containing giver information
|
||||
* @returns The giver's DID
|
||||
*/
|
||||
private extractGiverDid(claim: any) {
|
||||
private extractGiverDid(claim: Claim) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
return claim.agent?.identifier || (claim.agent as any)?.did;
|
||||
}
|
||||
@@ -963,7 +1146,7 @@ export default class HomeView extends Vue {
|
||||
* @internal
|
||||
* Called by processRecord()
|
||||
*/
|
||||
private extractRecipientDid(claim: any) {
|
||||
private extractRecipientDid(claim: Claim) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
return claim.recipient?.identifier || (claim.recipient as any)?.did;
|
||||
}
|
||||
@@ -1021,7 +1204,7 @@ export default class HomeView extends Vue {
|
||||
*/
|
||||
private shouldIncludeRecord(
|
||||
record: GiveSummaryRecord,
|
||||
fulfillsPlan: any,
|
||||
fulfillsPlan?: FulfillsPlan,
|
||||
): boolean {
|
||||
if (!this.isAnyFeedFilterOn) {
|
||||
return true;
|
||||
@@ -1055,7 +1238,7 @@ export default class HomeView extends Vue {
|
||||
* @internal
|
||||
* Called by processRecord()
|
||||
*/
|
||||
private extractProvider(claim: any) {
|
||||
private extractProvider(claim: Claim): Provider | undefined {
|
||||
return Array.isArray(claim.provider) ? claim.provider[0] : claim.provider;
|
||||
}
|
||||
|
||||
@@ -1065,7 +1248,7 @@ export default class HomeView extends Vue {
|
||||
* @internal
|
||||
* Called by processRecord()
|
||||
*/
|
||||
private async getProvidedByPlan(provider: any) {
|
||||
private async getProvidedByPlan(provider: Provider | undefined) {
|
||||
return await getPlanFromCache(
|
||||
provider?.identifier as string,
|
||||
this.axios,
|
||||
@@ -1103,12 +1286,12 @@ export default class HomeView extends Vue {
|
||||
*/
|
||||
private createFeedRecord(
|
||||
record: GiveSummaryRecord,
|
||||
claim: any,
|
||||
claim: Claim,
|
||||
giverDid: string,
|
||||
recipientDid: string,
|
||||
provider: any,
|
||||
fulfillsPlan: any,
|
||||
providedByPlan: any,
|
||||
provider: Provider | undefined,
|
||||
fulfillsPlan?: FulfillsPlan,
|
||||
providedByPlan?: ProvidedByPlan,
|
||||
): GiveRecordWithContactInfo {
|
||||
return {
|
||||
...record,
|
||||
@@ -1154,10 +1337,15 @@ export default class HomeView extends Vue {
|
||||
this.feedLastViewedClaimId == null ||
|
||||
this.feedLastViewedClaimId < records[0].jwtId
|
||||
) {
|
||||
await db.open();
|
||||
await db.settings.update(MASTER_SETTINGS_KEY, {
|
||||
await databaseUtil.updateDefaultSettings({
|
||||
lastViewedClaimId: records[0].jwtId,
|
||||
});
|
||||
if (USE_DEXIE_DB) {
|
||||
await db.open();
|
||||
await db.settings.update(MASTER_SETTINGS_KEY, {
|
||||
lastViewedClaimId: records[0].jwtId,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1167,14 +1355,16 @@ export default class HomeView extends Vue {
|
||||
* @internal
|
||||
* Called by updateAllFeed()
|
||||
*/
|
||||
private handleFeedError(e: any) {
|
||||
private handleFeedError(e: unknown) {
|
||||
logger.error("Error with feed load:", e);
|
||||
this.$notify(
|
||||
{
|
||||
group: "alert",
|
||||
type: "danger",
|
||||
title: "Feed Error",
|
||||
text: e.userMessage || "There was an error retrieving feed data.",
|
||||
text:
|
||||
(e as FeedError)?.userMessage ||
|
||||
"There was an error retrieving feed data.",
|
||||
},
|
||||
-1,
|
||||
);
|
||||
@@ -1557,7 +1747,7 @@ export default class HomeView extends Vue {
|
||||
this.$router.push({ name: "onboard-meeting-list" });
|
||||
},
|
||||
onOption2: () => {
|
||||
this.$router.push({ name: "contact-qr" });
|
||||
this.handleQRCodeClick();
|
||||
},
|
||||
onOption3: () => {
|
||||
this.$router.push({ name: "share-my-contact-info" });
|
||||
@@ -1664,5 +1854,13 @@ export default class HomeView extends Vue {
|
||||
-1,
|
||||
);
|
||||
}
|
||||
|
||||
private handleQRCodeClick() {
|
||||
if (Capacitor.isNativePlatform()) {
|
||||
this.$router.push({ name: "contact-qr-scan-full" });
|
||||
} else {
|
||||
this.$router.push({ name: "contact-qr" });
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user