Browse Source

refactor: Improve settings and feed handling in HomeView

- Split feed initialization into separate methods
- Add registration status verification
- Improve error handling and notifications
- Add JSDoc comments for better code documentation
- Make apiServer optional in settings type

The changes improve code organization by:
1. Breaking down monolithic initialization into focused methods
2. Adding proper type safety for optional settings
3. Improving error handling and user feedback
4. Adding clear documentation for methods
5. Separating concerns for feed, contacts and registration
pull/127/head
Matthew Raymer 1 week ago
parent
commit
d14431161a
  1. 2
      src/db/tables/settings.ts
  2. 302
      src/views/HomeView.vue

2
src/db/tables/settings.ts

@ -20,7 +20,7 @@ export type Settings = {
// active Decentralized ID
activeDid?: string; // only used in the MASTER_SETTINGS_KEY entry
apiServer: string; // API server URL
apiServer?: string; // API server URL
filterFeedByNearby?: boolean; // filter by nearby
filterFeedByVisible?: boolean; // filter by visible users ie. anyone not hidden

302
src/views/HomeView.vue

@ -423,7 +423,6 @@ import {
getNewOffersToUser,
getNewOffersToUserProjects,
getPlanFromCache,
GiveSummaryRecord,
} from "../libs/endorserServer";
import {
generateSaveAndActivateIdentity,
@ -432,7 +431,7 @@ import {
OnboardPage,
registerSaveAndActivatePasskey,
} from "../libs/util";
import { GiveSummaryRecord} from "../interfaces";
interface GiveRecordWithContactInfo extends GiveSummaryRecord {
jwtId: string;
giver: {
@ -450,6 +449,15 @@ interface GiveRecordWithContactInfo extends GiveSummaryRecord {
};
}
/**
* HomeView - Main view component for the application's home page
*
* Workflow:
* 1. On mount, initializes user identity, settings, and data
* 2. Handles user registration status
* 3. Manages feed of activities and offers
* 4. Provides interface for creating and viewing claims
*/
@Component({
components: {
EntityIcon,
@ -503,127 +511,198 @@ export default class HomeView extends Vue {
isImageViewerOpen = false;
imageCache: Map<string, Blob | null> = new Map();
/**
* Initializes the component on mount
* Sequence:
* 1. Initialize identity (create if needed)
* 2. Load user settings
* 3. Load contacts
* 4. Check registration status
* 5. Load feed data
* 6. Load new offers
* 7. Check onboarding status
*/
async mounted() {
try {
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
}
await this.initializeIdentity();
await this.loadSettings();
await this.loadContacts();
await this.checkRegistrationStatus();
await this.loadFeedData();
await this.loadNewOffers();
await this.checkOnboarding();
} catch (err: any) {
this.handleError(err);
}
}
const settings = await retrieveSettingsForActiveAccount();
this.apiServer = settings.apiServer || "";
this.activeDid = settings.activeDid || "";
this.allContacts = await db.contacts.toArray();
this.feedLastViewedClaimId = settings.lastViewedClaimId;
this.givenName = settings.firstName || "";
this.isFeedFilteredByVisible = !!settings.filterFeedByVisible;
this.isFeedFilteredByNearby = !!settings.filterFeedByNearby;
this.isRegistered = !!settings.isRegistered;
this.lastAckedOfferToUserJwtId = settings.lastAckedOfferToUserJwtId;
this.lastAckedOfferToUserProjectsJwtId =
settings.lastAckedOfferToUserProjectsJwtId;
this.searchBoxes = settings.searchBoxes || [];
this.showShortcutBvc = !!settings.showShortcutBvc;
this.isAnyFeedFilterOn = checkIsAnyFeedFilterOn(settings);
if (!settings.finishedOnboarding) {
(this.$refs.onboardingDialog as OnboardingDialog).open(
OnboardPage.Home,
);
/**
* Initializes user identity
* - Retrieves existing DIDs
* - Creates new DID if none exists
* @throws Logs error if DID retrieval fails
*/
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];
}
} catch (error) {
logConsoleAndDb("Error retrieving all account DIDs on home page:" + error, true);
}
}
// someone may have have registered after sharing contact info, so recheck
if (!this.isRegistered && this.activeDid) {
try {
const resp = await fetchEndorserRateLimits(
this.apiServer,
this.axios,
this.activeDid,
);
if (resp.status === 200) {
await updateAccountSettings(this.activeDid, {
isRegistered: true,
});
this.isRegistered = true;
}
} catch (e) {
// ignore the error... just keep us unregistered
}
}
/**
* Loads user settings from storage
* Sets component state for:
* - API server
* - Active DID
* - Feed filters and view settings
* - Registration status
* - Notification acknowledgments
*/
private async loadSettings() {
const settings = await retrieveSettingsForActiveAccount();
this.apiServer = settings.apiServer || "";
this.activeDid = settings.activeDid || "";
this.feedLastViewedClaimId = settings.lastViewedClaimId;
this.givenName = settings.firstName || "";
this.isFeedFilteredByVisible = !!settings.filterFeedByVisible;
this.isFeedFilteredByNearby = !!settings.filterFeedByNearby;
this.isRegistered = !!settings.isRegistered;
this.lastAckedOfferToUserJwtId = settings.lastAckedOfferToUserJwtId;
this.lastAckedOfferToUserProjectsJwtId = settings.lastAckedOfferToUserProjectsJwtId;
this.searchBoxes = settings.searchBoxes || [];
this.showShortcutBvc = !!settings.showShortcutBvc;
this.isAnyFeedFilterOn = checkIsAnyFeedFilterOn(settings);
}
// this returns a Promise but we don't need to wait for it
this.updateAllFeed();
/**
* Loads user contacts from database
* Used for displaying contact info in feed and actions
*/
private async loadContacts() {
this.allContacts = await db.contacts.toArray();
}
if (this.activeDid) {
const offersToUserData = await getNewOffersToUser(
this.axios,
this.apiServer,
this.activeDid,
this.lastAckedOfferToUserJwtId,
);
this.numNewOffersToUser = offersToUserData.data.length;
this.newOffersToUserHitLimit = offersToUserData.hitLimit;
/**
* Verifies user registration status with endorser service
* - Checks if unregistered user can access API
* - Updates registration status if successful
* - Preserves unregistered state on failure
*/
private async checkRegistrationStatus() {
if (!this.isRegistered && this.activeDid) {
try {
const resp = await fetchEndorserRateLimits(this.apiServer, this.axios, this.activeDid);
if (resp.status === 200) {
await updateAccountSettings(this.activeDid, {
apiServer: this.apiServer,
isRegistered: true,
...await retrieveSettingsForActiveAccount()
});
this.isRegistered = true;
}
} catch (e) {
// ignore the error... just keep us unregistered
}
}
}
if (this.activeDid) {
const offersToUserProjects = await getNewOffersToUserProjects(
this.axios,
this.apiServer,
this.activeDid,
this.lastAckedOfferToUserProjectsJwtId,
);
this.numNewOffersToUserProjects = offersToUserProjects.data.length;
this.newOffersToUserProjectsHitLimit = offersToUserProjects.hitLimit;
}
/**
* Initializes feed data
* Triggers updateAllFeed() to populate activity feed
*/
private async loadFeedData() {
await this.updateAllFeed();
}
// 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.userMessage ||
"There was an error retrieving your settings or the latest activity.",
},
5000,
/**
* Loads new offers for user and their projects
* Updates:
* - Number of new direct offers
* - Number of new project offers
* - Rate limit status for both
* @requires Active DID
*/
private async loadNewOffers() {
if (this.activeDid) {
const offersToUserData = await getNewOffersToUser(
this.axios,
this.apiServer,
this.activeDid,
this.lastAckedOfferToUserJwtId
);
this.numNewOffersToUser = offersToUserData.data.length;
this.newOffersToUserHitLimit = offersToUserData.hitLimit;
const offersToUserProjects = await getNewOffersToUserProjects(
this.axios,
this.apiServer,
this.activeDid,
this.lastAckedOfferToUserProjectsJwtId
);
this.numNewOffersToUserProjects = offersToUserProjects.data.length;
this.newOffersToUserProjectsHitLimit = offersToUserProjects.hitLimit;
}
}
async generatePasskeyIdentifier() {
this.isCreatingIdentifier = true;
const account = await registerSaveAndActivatePasskey(
AppString.APP_NAME + (this.givenName ? " - " + this.givenName : ""),
/**
* Checks if user needs onboarding
* Opens onboarding dialog if not completed
*/
private async checkOnboarding() {
const settings = await retrieveSettingsForActiveAccount();
if (!settings.finishedOnboarding) {
(this.$refs.onboardingDialog as OnboardingDialog).open(OnboardPage.Home);
}
}
/**
* Handles errors during initialization
* - Logs error to console and database
* - Displays user notification
* @param err Error object with optional userMessage
*/
private handleError(err: any) {
logConsoleAndDb("Error retrieving settings or feed: " + err, true);
this.$notify(
{
group: "alert",
type: "danger",
title: "Error",
text: err.userMessage || "There was an error retrieving your settings or the latest activity.",
},
5000
);
this.activeDid = account.did;
this.allMyDids = this.allMyDids.concat(this.activeDid);
this.isCreatingIdentifier = false;
}
/**
* Checks if feed results are being filtered
* @returns true if visible or nearby filters are active
*/
resultsAreFiltered() {
return this.isFeedFilteredByVisible || this.isFeedFilteredByNearby;
}
/**
* Checks if browser notifications are supported
* @returns true if Notification API is available
*/
notificationsSupported() {
return "Notification" in window;
}
// only called when a setting was changed
/**
* Reloads feed when filter settings change
* - Updates filter states
* - Clears existing feed data
* - Triggers new feed load
*/
async reloadFeedOnChange() {
const settings = await retrieveSettingsForActiveAccount();
this.isFeedFilteredByVisible = !!settings.filterFeedByVisible;
@ -636,9 +715,9 @@ export default class HomeView extends Vue {
}
/**
* Data loader used by infinite scroller
* @param payload is the flag from the InfiniteScroll indicating if it should load
**/
* Loads more feed items for infinite scroll
* @param payload Boolean indicating if more items should be loaded
*/
async loadMoreGives(payload: boolean) {
// Since feed now loads projects along the way, it takes longer
// and the InfiniteScroll component triggers a load before finished.
@ -661,6 +740,12 @@ export default class HomeView extends Vue {
}
}
/**
* Updates feed with latest activity
* - Handles filtering of results
* - Updates last viewed claim ID
* - Manages loading state
*/
async updateAllFeed() {
this.isFeedLoading = true;
let endOfResults = true;
@ -917,6 +1002,11 @@ export default class HomeView extends Vue {
return unitCode === "HUR" ? (single ? "hour" : "hours") : unitCode;
}
/**
* Opens dialog for creating new gift/claim
* @param giver Optional contact info for giver
* @param description Optional gift description
*/
openDialog(giver?: GiverReceiverInputInfo, description?: string) {
(this.$refs.customDialog as GiftedDialog).open(
giver,
@ -930,12 +1020,20 @@ export default class HomeView extends Vue {
);
}
/**
* Opens prompts for gift ideas
* Links to openDialog for selected prompt
*/
openGiftedPrompts() {
(this.$refs.giftedPrompts as GiftedPrompts).open((giver, description) =>
this.openDialog(giver as GiverReceiverInputInfo, description),
);
}
/**
* Opens feed filter configuration
* @param reloadFeedOnChange Callback for when filters are updated
*/
openFeedFilters() {
(this.$refs.feedFilters as FeedFilters).open(this.reloadFeedOnChange);
}

Loading…
Cancel
Save