@ -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 ) {
/ / c o n t i n u e b e c a u s e w e w a n t t h e f e e d t o w o r k , e v e n a n o n y m o u s l y
logConsoleAndDb (
"Error retrieving all account DIDs on home page:" + error ,
true ,
) ;
/ / s o m e o t h e r p i e c e w i l l d i s p l a y a n e r r o r a b o u t p e r s o n a l i n f o
}
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 ) ;
}
}
/ / s o m e o n e m a y h a v e h a v e r e g i s t e r e d a f t e r s h a r i n g c o n t a c t i n f o , s o r e c h e c k
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 ) {
/ / i g n o r e t h e e r r o r . . . j u s t k e e p u s u n r e g i s t e r e d
}
}
/ * *
* 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 ) ;
}
/ / t h i s r e t u r n s a P r o m i s e b u t w e d o n ' t n e e d t o w a i t f o r i t
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 ) {
/ / i g n o r e t h e e r r o r . . . j u s t k e e p u s u n r e g i s t e r e d
}
}
}
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 ( ) ;
}
/ / e s l i n t - d i s a b l e - n e x t - l i n e @ t y p e s c r i p t - e s l i n t / n o - e x p l i c i t - a n y
} 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 ;
}
/ / o n l y c a l l e d w h e n a s e t t i n g w a s c h a n g e d
/ * *
* 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 loa d
* * /
* Loads more feed items for infinite scroll
* @ param payload Boolean indicating if more items should be loade d
* /
async loadMoreGives ( payload : boolean ) {
/ / S i n c e f e e d n o w l o a d s p r o j e c t s a l o n g t h e w a y , i t t a k e s l o n g e r
/ / a n d t h e I n f i n i t e S c r o l l c o m p o n e n t t r i g g e r s a l o a d b e f o r e f i n i s h e d .
@ -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 ) ;
}