@ -321,6 +321,7 @@ import {
db ,
logConsoleAndDb ,
retrieveSettingsForActiveAccount ,
updateDefaultSettings ,
updateAccountSettings ,
} from "../db/index" ;
import { Contact } from "../db/tables/contacts" ;
@ -414,7 +415,8 @@ export default class HomeView extends Vue {
isCreatingIdentifier = false ;
isFeedFilteredByVisible = false ;
isFeedFilteredByNearby = false ;
isFeedLoading = true ;
isFeedLoading = false ;
isFeedLoadingInProgress = false ;
isRegistered = false ;
lastAckedOfferToUserJwtId ? : string ; / / t h e l a s t J W T I D f o r o f f e r - t o - u s e r t h a t t h e y ' v e a c k n o w l e d g e d s e e i n g
lastAckedOfferToUserProjectsJwtId ? : string ; / / t h e l a s t J W T I D f o r o f f e r s - t o - u s e r ' s - p r o j e c t s t h a t t h e y ' v e a c k n o w l e d g e d s e e i n g
@ -506,7 +508,16 @@ export default class HomeView extends Vue {
/ / U p d a t e s t a t e w i t h l o a d e d d a t a
this . apiServer = settings . apiServer || "" ;
/ / E n s u r e a c t i v e D i d i s s e t c o r r e c t l y
if ( ! settings . activeDid && this . allMyDids . length > 0 ) {
/ / I f n o a c t i v e D i d i s s e t b u t w e h a v e D I D s , u s e t h e f i r s t o n e
await updateDefaultSettings ( { activeDid : this . allMyDids [ 0 ] } ) ;
this . activeDid = this . allMyDids [ 0 ] ;
} else {
this . activeDid = settings . activeDid || "" ;
}
this . allContacts = contacts ;
this . feedLastViewedClaimId = settings . lastViewedClaimId ;
this . givenName = settings . firstName || "" ;
@ -624,7 +635,13 @@ export default class HomeView extends Vue {
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
/ / 4 0 0 e r r o r s a r e e x p e c t e d f o r u n r e g i s t e r e d u s e r s - l o g a s i n f o i n s t e a d o f w a r n i n g
if ( e instanceof Error && e . message . includes ( "400" ) ) {
logger . log ( "User is not registered (expected 400 response)" ) ;
} else {
logger . warn ( "Unexpected error checking rate limits:" , e ) ;
}
/ / K e e p t h e u n r e g i s t e r e d s t a t e
}
}
}
@ -767,7 +784,7 @@ export default class HomeView extends Vue {
clearTimeout ( this . loadMoreTimeout ) ;
}
this . loadMoreTimeout = setTimeout ( async ( ) => {
await this . updateAllFeed ( ) ;
await this . updateAllFeed ( ) ;
} , 300 ) ;
}
}
@ -832,29 +849,113 @@ export default class HomeView extends Vue {
* - this . feedLastViewedClaimId ( via updateFeedLastViewedId )
* /
async updateAllFeed ( ) {
logger . log ( "Starting updateAllFeed..." ) ;
/ / P r e v e n t m u l t i p l e s i m u l t a n e o u s f e e d l o a d s
if ( this . isFeedLoadingInProgress ) {
logger . log ( "Feed load already in progress, skipping..." ) ;
return ;
}
this . isFeedLoading = true ;
this . isFeedLoadingInProgress = true ;
let endOfResults = true ;
const MAX_RETRIES = 3 ;
let retryCount = 0 ;
const MIN_REQUIRED_ITEMS = 10 ; / / M i n i m u m n u m b e r o f i t e m s w e w a n t t o l o a d
try {
logger . log ( ` Attempting to connect to server: ${ this . apiServer } ` ) ;
logger . log ( ` Using active DID: ${ this . activeDid } ` ) ;
logger . log ( ` Previous oldest ID: ${ this . feedPreviousOldestId || "none" } ` ) ;
const results = await this . retrieveGives (
this . apiServer ,
this . feedPreviousOldestId ,
) ;
if ( results . data . length > 0 ) {
endOfResults = false ;
await this . processFeedResults ( results . data ) ;
await this . updateFeedLastViewedId ( results . data ) ;
logger . log ( ` Server response status: ${ results . status || "unknown" } ` ) ;
logger . log ( ` Retrieved ${ results . data . length } feed items ` ) ;
logger . log ( ` Hit limit: ${ results . hitLimit } ` ) ;
logger . log ( ` Response headers: ${ JSON . stringify ( results . headers || { } )} ` ) ;
/ / I f w e g o t a 3 0 4 r e s p o n s e , w e s h o u l d s t o p h e r e
if ( results . data . length === 0 && ! results . hitLimit ) {
logger . log ( "Received 304 response - no new data" ) ;
this . isFeedLoading = false ;
this . isFeedLoadingInProgress = false ;
return ;
}
if ( results . data . length > 0 ) {
endOfResults = false ;
try {
logger . log ( ` Processing ${ results . data . length } feed results... ` ) ;
await this . processFeedResults ( results . data ) ;
logger . log ( "Successfully processed feed results" ) ;
await this . updateFeedLastViewedId ( results . data ) ;
logger . log ( "Updated feed last viewed ID" ) ;
/ / I f w e d o n ' t h a v e e n o u g h i t e m s a n d h a v e n ' t h i t t h e l i m i t , t r y t o l o a d m o r e
if ( this . feedData . length < MIN_REQUIRED_ITEMS && ! results . hitLimit ) {
logger . log (
` Only have ${ this . feedData . length } items, loading more... ` ,
) ;
this . feedPreviousOldestId =
results . data [ results . data . length - 1 ] . jwtId ;
await this . updateAllFeed ( ) ;
}
} catch ( processError ) {
logger . error ( "Error in feed processing:" , processError ) ;
throw new Error (
` Feed processing error: ${ processError instanceof Error ? processError . message : String ( processError ) } ` ,
) ;
}
} else {
logger . log ( "No new feed data received" ) ;
}
} catch ( err ) {
const error = err as TimeSafariError ;
/ / D o n ' t l o g e m p t y e r r o r o b j e c t s
if ( err && typeof err === "object" && Object . keys ( err ) . length === 0 ) {
logger . log ( "Received empty error object - likely a 304 response" ) ;
this . isFeedLoading = false ;
this . isFeedLoadingInProgress = false ;
return ;
}
logger . error ( "Error in updateAllFeed:" , err ) ;
logger . error ( "Error details:" , {
message : err instanceof Error ? err . message : String ( err ) ,
stack : err instanceof Error ? err . stack : undefined ,
status : ( err as any ) ? . response ? . status ,
statusText : ( err as any ) ? . response ? . statusText ,
headers : ( err as any ) ? . response ? . headers ,
apiServer : this . apiServer ,
activeDid : this . activeDid ,
feedDataLength : this . feedData . length ,
isFeedLoading : this . isFeedLoading ,
isFeedLoadingInProgress : this . isFeedLoadingInProgress ,
} ) ;
const error = err instanceof Error ? err : new Error ( String ( err ) ) ;
this . handleFeedError ( error ) ;
}
if ( this . feedData . length === 0 && ! endOfResults ) {
/ / O n l y r e t r y i f w e h a v e n o d a t a a n d h a v e n ' t h i t t h e l i m i t
if (
this . feedData . length === 0 &&
! endOfResults &&
retryCount < MAX_RETRIES
) {
retryCount ++ ;
logger . log (
` Retrying feed update (attempt ${ retryCount } / ${ MAX_RETRIES } )... ` ,
) ;
await this . updateAllFeed ( ) ;
}
this . isFeedLoading = false ;
this . isFeedLoadingInProgress = false ;
logger . log ( ` Feed update completed with ${ this . feedData . length } items ` ) ;
}
/ * *
@ -879,22 +980,70 @@ export default class HomeView extends Vue {
* @ param records Array of feed records to process
* /
private async processFeedResults ( records : GiveSummaryRecord [ ] ) {
/ / P r o c e s s r e c o r d s i n c h u n k s t o a v o i d b l o c k i n g m a i n t h r e a d
const CHUNK_SIZE = 5 ;
logger . log ( ` Starting to process ${ records . length } feed records... ` ) ;
/ / P r o c e s s r e c o r d s i n l a r g e r c h u n k s t o i m p r o v e p e r f o r m a n c e
const CHUNK_SIZE = 10 ; / / I n c r e a s e d f r o m 5 t o 1 0
let processedCount = 0 ;
for ( let i = 0 ; i < records . length ; i += CHUNK_SIZE ) {
const chunk = records . slice ( i , i + CHUNK_SIZE ) ;
await Promise . all (
chunk . map ( async ( record ) => {
const processedRecord = await this . processRecord ( record ) ;
if ( processedRecord ) {
this . feedData . push ( processedRecord ) ;
}
} ) ,
logger . log (
` Processing chunk ${ i / CHUNK_SIZE + 1 } of ${ Math . ceil ( records . length / CHUNK_SIZE ) } ( ${ chunk . length } records)... ` ,
) ;
/ / A l l o w U I t o u p d a t e b e t w e e n c h u n k s
await new Promise ( ( resolve ) => setTimeout ( resolve , 0 ) ) ;
try {
await Promise . all (
chunk . map ( async ( record ) => {
try {
/ / S k i p i f w e a l r e a d y h a v e t h i s r e c o r d
if ( this . feedData . some ( ( r ) => r . jwtId === record . jwtId ) ) {
logger . log ( ` Skipping duplicate record ${ record . jwtId } ` ) ;
return ;
}
logger . log ( ` Processing record ${ record . jwtId } ... ` ) ;
const processedRecord = await this . processRecord ( record ) ;
if ( processedRecord ) {
this . feedData . push ( processedRecord ) ;
processedCount ++ ;
logger . log (
` Successfully added record ${ record . jwtId } to feed (total processed: ${ processedCount } ) ` ,
) ;
} else {
logger . log ( ` Record ${ record . jwtId } filtered out ` ) ;
}
} catch ( recordError ) {
logger . error (
` Error processing record ${ record . jwtId } : ` ,
recordError ,
) ;
throw recordError ;
}
} ) ,
) ;
/ / A l l o w U I t o u p d a t e b e t w e e n c h u n k s
await new Promise ( ( resolve ) => setTimeout ( resolve , 0 ) ) ;
} catch ( chunkError ) {
logger . error ( "Error processing chunk:" , chunkError ) ;
throw chunkError ;
}
}
logger . log (
` Completed processing ${ processedCount } records out of ${ records . length } total records ` ,
) ;
if ( records . length > 0 ) {
/ / U p d a t e t h e o l d e s t I D o n l y i f w e h a v e n e w r e c o r d s
const oldestId = records [ records . length - 1 ] . jwtId ;
if ( ! this . feedPreviousOldestId || oldestId < this . feedPreviousOldestId ) {
this . feedPreviousOldestId = oldestId ;
logger . log (
` Updated feedPreviousOldestId to ${ this . feedPreviousOldestId } ` ,
) ;
}
}
this . feedPreviousOldestId = records [ records . length - 1 ] . jwtId ;
}
/ * *
@ -932,27 +1081,118 @@ export default class HomeView extends Vue {
private async processRecord (
record : GiveSummaryRecord ,
) : Promise < GiveRecordWithContactInfo | null > {
const claim = this . extractClaim ( record ) ;
const giverDid = this . extractGiverDid ( claim ) ;
const recipientDid = this . extractRecipientDid ( claim ) ;
try {
logger . log ( ` Starting to process record ${ record . jwtId } ... ` ) ;
const fulfillsPlan = await this . getFulfillsPlan ( record ) ;
if ( ! this . shouldIncludeRecord ( record , fulfillsPlan ) ) {
return null ;
}
const claim = this . extractClaim ( record ) ;
logger . log ( ` Extracted claim for ${ record . jwtId } : ` , claim ) ;
const provider = this . extractProvider ( claim ) ;
const providedByPlan = await this . getProvidedByPlan ( provider ) ;
return this . createFeedRecord (
record ,
claim ,
giverDid ,
recipientDid ,
provider ,
fulfillsPlan ,
providedByPlan ,
) ;
/ / F o r h i d d e n c l a i m s , w e c a n u s e t h e p r o v i d e r ' s i d e n t i f i e r a s a f a l l b a c k
let giverDid : string ;
try {
giverDid = this . extractGiverDid ( claim ) ;
logger . log ( ` Extracted giver DID for ${ record . jwtId } : ${ giverDid } ` ) ;
} catch ( giverError ) {
if ( claim . provider ? . identifier ) {
logger . log (
` Using provider identifier as fallback for giver DID: ${ claim . provider . identifier } ` ,
) ;
giverDid = claim . provider . identifier as string ;
} else {
logger . error (
` Error extracting giver DID for ${ record . jwtId } : ` ,
giverError ,
) ;
return null ; / / S k i p t h i s r e c o r d i n s t e a d o f t h r o w i n g
}
}
let recipientDid : string ;
try {
recipientDid = this . extractRecipientDid ( claim ) ;
logger . log (
` Extracted recipient DID for ${ record . jwtId } : ${ recipientDid } ` ,
) ;
} catch ( recipientError ) {
logger . error (
` Error extracting recipient DID for ${ record . jwtId } : ` ,
recipientError ,
) ;
return null ; / / S k i p t h i s r e c o r d i n s t e a d o f t h r o w i n g
}
let fulfillsPlan : PlanSummaryRecord | null | undefined ;
try {
fulfillsPlan = await this . getFulfillsPlan ( record ) ;
logger . log (
` Retrieved fulfills plan for ${ record . jwtId } : ` ,
fulfillsPlan ,
) ;
} catch ( planError ) {
logger . error (
` Error retrieving fulfills plan for ${ record . jwtId } : ` ,
planError ,
) ;
return null ; / / S k i p t h i s r e c o r d i n s t e a d o f t h r o w i n g
}
if ( ! this . shouldIncludeRecord ( record , fulfillsPlan ) ) {
logger . log (
` Record ${ record . jwtId } filtered out by shouldIncludeRecord ` ,
) ;
return null ;
}
let provider : GenericVerifiableCredential | null ;
try {
provider = this . extractProvider ( claim ) ;
logger . log ( ` Extracted provider for ${ record . jwtId } : ` , provider ) ;
} catch ( providerError ) {
logger . error (
` Error extracting provider for ${ record . jwtId } : ` ,
providerError ,
) ;
return null ; / / S k i p t h i s r e c o r d i n s t e a d o f t h r o w i n g
}
let providedByPlan : PlanSummaryRecord | null | undefined ;
try {
providedByPlan = await this . getProvidedByPlan ( provider ) ;
logger . log (
` Retrieved provided by plan for ${ record . jwtId } : ` ,
providedByPlan ,
) ;
} catch ( providedByError ) {
logger . error (
` Error retrieving provided by plan for ${ record . jwtId } : ` ,
providedByError ,
) ;
return null ; / / S k i p t h i s r e c o r d i n s t e a d o f t h r o w i n g
}
try {
const feedRecord = this . createFeedRecord (
record ,
claim ,
giverDid ,
recipientDid ,
provider ,
fulfillsPlan ,
providedByPlan ,
) ;
logger . log ( ` Successfully created feed record for ${ record . jwtId } ` ) ;
return feedRecord ;
} catch ( createError ) {
logger . error (
` Error creating feed record for ${ record . jwtId } : ` ,
createError ,
) ;
return null ; / / S k i p t h i s r e c o r d i n s t e a d o f t h r o w i n g
}
} catch ( error ) {
logger . error ( ` Error processing record ${ record . jwtId } : ` , error ) ;
return null ; / / S k i p t h i s r e c o r d i n s t e a d o f t h r o w i n g
}
}
/ * *
@ -973,7 +1213,7 @@ export default class HomeView extends Vue {
* @ returns The extracted claim object
* /
private extractClaim ( record : GiveSummaryRecord ) {
/ / 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
/ / 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
return ( record . fullClaim as any ) . claim || record . fullClaim ;
}
@ -995,10 +1235,29 @@ export default class HomeView extends Vue {
* @ returns The giver ' s DID
* /
private extractGiverDid ( claim : GiveVerifiableCredential ) : string {
if ( ! claim . agent ? . identifier ) {
throw new Error ( "Agent identifier is missing in claim" ) ;
try {
if ( ! claim . agent ? . identifier ) {
logger . log ( "Agent identifier is missing in claim. Claim structure:" , {
type : claim [ "@type" ] ,
hasAgent : ! ! claim . agent ,
agentIdentifier : claim . agent ? . identifier ,
provider : claim . provider ,
recipient : claim . recipient ,
} ) ;
/ / F o r h i d d e n c l a i m s , w e c a n u s e t h e p r o v i d e r ' s i d e n t i f i e r a s a f a l l b a c k
if ( claim . provider ? . identifier ) {
logger . log ( "Using provider identifier as fallback for giver DID" ) ;
return claim . provider . identifier as string ;
}
/ / I f n o p r o v i d e r i d e n t i f i e r , r e t u r n a d e f a u l t v a l u e i n s t e a d o f t h r o w i n g
return "did:none:HIDDEN" ;
}
return claim . agent . identifier ;
} catch ( error ) {
logger . error ( "Error extracting giver DID:" , error ) ;
/ / R e t u r n a d e f a u l t v a l u e i n s t e a d o f t h r o w i n g
return "did:none:HIDDEN" ;
}
return claim . agent . identifier ;
}
/ * *
@ -1008,10 +1267,16 @@ export default class HomeView extends Vue {
* Called by processRecord ( )
* /
private extractRecipientDid ( claim : GiveVerifiableCredential ) : string {
if ( ! claim . recipient ? . identifier ) {
throw new Error ( "Recipient identifier is missing in claim" ) ;
try {
if ( ! claim . recipient ? . identifier ) {
logger . error ( "Recipient identifier is missing in claim:" , claim ) ;
throw new Error ( "Recipient identifier is missing in claim" ) ;
}
return claim . recipient . identifier ;
} catch ( error ) {
logger . error ( "Error extracting recipient DID:" , error ) ;
throw error ;
}
return claim . recipient . identifier ;
}
/ * *
@ -1035,11 +1300,11 @@ export default class HomeView extends Vue {
* /
private async getFulfillsPlan ( record : GiveSummaryRecord ) {
return await getPlanFromCache (
record . fulfillsPlanHandleId ,
this . axios ,
this . apiServer ,
this . activeDid ,
) ;
record . fulfillsPlanHandleId ,
this . axios ,
this . apiServer ,
this . activeDid ,
) ;
}
/ * *
@ -1074,7 +1339,8 @@ export default class HomeView extends Vue {
}
let anyMatch = false ;
if ( this . isFeedFilteredByVisible && containsNonHiddenDid ( record ) ) {
if ( this . isFeedFilteredByVisible ) {
/ / D o n ' t f i l t e r o u t r e c o r d s w i t h h i d d e n D I D s
anyMatch = true ;
}
@ -1115,11 +1381,11 @@ export default class HomeView extends Vue {
provider : GenericVerifiableCredential | null ,
) {
return await getPlanFromCache (
provider ? . identifier as string ,
this . axios ,
this . apiServer ,
this . activeDid ,
) ;
provider ? . identifier as string ,
this . axios ,
this . apiServer ,
this . activeDid ,
) ;
}
/ * *
@ -1159,35 +1425,35 @@ export default class HomeView extends Vue {
providedByPlan : PlanSummaryRecord | null | undefined ,
) : GiveRecordWithContactInfo {
return {
... record ,
jwtId : record . jwtId ,
... record ,
jwtId : record . jwtId ,
fullClaim : record . fullClaim ,
description : record . description || "" ,
handleId : record . handleId ,
issuerDid : record . issuerDid ,
fulfillsPlanHandleId : record . fulfillsPlanHandleId ,
giver : didInfoForContact (
giverDid ,
this . activeDid ,
contactForDid ( giverDid , this . allContacts ) ,
this . allMyDids ,
) ,
image : claim . image ,
issuer : didInfoForContact (
record . issuerDid ,
this . activeDid ,
contactForDid ( record . issuerDid , this . allContacts ) ,
this . allMyDids ,
) ,
providerPlanHandleId : provider ? . identifier as string ,
providerPlanName : providedByPlan ? . name as string ,
recipientProjectName : fulfillsPlan ? . name as string ,
receiver : didInfoForContact (
recipientDid ,
this . activeDid ,
contactForDid ( recipientDid , this . allContacts ) ,
this . allMyDids ,
) ,
giver : didInfoForContact (
giverDid ,
this . activeDid ,
contactForDid ( giverDid , this . allContacts ) ,
this . allMyDids ,
) ,
image : claim . image ,
issuer : didInfoForContact (
record . issuerDid ,
this . activeDid ,
contactForDid ( record . issuerDid , this . allContacts ) ,
this . allMyDids ,
) ,
providerPlanHandleId : provider ? . identifier as string ,
providerPlanName : providedByPlan ? . name as string ,
recipientProjectName : fulfillsPlan ? . name as string ,
receiver : didInfoForContact (
recipientDid ,
this . activeDid ,
contactForDid ( recipientDid , this . allContacts ) ,
this . allMyDids ,
) ,
} as GiveRecordWithContactInfo ;
}
@ -1198,16 +1464,16 @@ export default class HomeView extends Vue {
* Called by updateAllFeed ( )
* /
private async updateFeedLastViewedId ( records : GiveSummaryRecord [ ] ) {
if (
this . feedLastViewedClaimId == null ||
if (
this . feedLastViewedClaimId == null ||
this . feedLastViewedClaimId < records [ 0 ] . jwtId
) {
await db . open ( ) ;
await db . settings . update ( MASTER_SETTINGS_KEY , {
) {
await db . open ( ) ;
await db . settings . update ( MASTER_SETTINGS_KEY , {
lastViewedClaimId : records [ 0 ] . jwtId ,
} ) ;
}
}
} ) ;
}
}
/ * *
* Handles feed error and shows notification
@ -1215,16 +1481,40 @@ export default class HomeView extends Vue {
* @ internal
* Called by updateAllFeed ( )
* /
private handleFeedError ( error : TimeSafariError ) {
private handleFeedError ( error : TimeSafariError | unknown ) {
/ / S k i p l o g g i n g e m p t y e r r o r o b j e c t s
if ( error && typeof error === "object" && Object . keys ( error ) . length === 0 ) {
logger . log ( "Received empty error object - likely a 304 response" ) ;
return ;
}
logger . error ( "Error with feed load:" , error ) ;
this . $notify (
{
group : "alert" ,
type : "danger" ,
title : "Feed Error" ,
text : error . userMessage || "There was an error retrieving feed data." ,
/ / B e t t e r e r r o r m e s s a g e c o n s t r u c t i o n
let errorMessage = "There was an error retrieving feed data." ;
if ( error ) {
if ( typeof error === "string" ) {
errorMessage = error ;
} else if ( error instanceof Error ) {
errorMessage = error . message ;
} else if ( typeof error === "object" && error !== null ) {
const timeSafariError = error as TimeSafariError ;
if ( timeSafariError . userMessage ) {
errorMessage = timeSafariError . userMessage ;
} else if ( timeSafariError . message ) {
errorMessage = timeSafariError . message ;
}
}
}
this . $notify (
{
group : "alert" ,
type : "danger" ,
title : "Feed Error" ,
text : errorMessage ,
} ,
- 1 ,
5000 ,
) ;
}
@ -1238,33 +1528,63 @@ export default class HomeView extends Vue {
* @ returns claims in reverse chronological order
* /
async retrieveGives ( endorserApiServer : string , beforeId ? : string ) {
logger . log ( "Starting retrieveGives..." ) ;
const beforeQuery = beforeId == null ? "" : "&beforeId=" + beforeId ;
const doNotShowErrorAgain = ! ! beforeId ; / / d o n ' t s h o w e r r o r a g a i n i f w e ' r e l o a d i n g m o r e
const headers = await getHeaders (
this . activeDid ,
doNotShowErrorAgain ? undefined : this . $notify ,
) ;
logger . log ( "Retrieved headers for retrieveGives" ) ;
/ / r e t r i e v e h e a d e r s f o r t h i s u s e r , b u t i f a n e r r o r h a p p e n s t h e n r e p o r t i t b u t p r o c e e d w i t h t h e f e t c h w i t h n o h e a d e r
const response = await fetch (
const url =
endorserApiServer +
"/api/v2/report/gives?giftNotTrade=true" +
beforeQuery ,
{
beforeQuery ;
logger . log ( "Making request to URL:" , url ) ;
const response = await fetch ( url , {
method : "GET" ,
headers : headers ,
} ,
} ) ;
logger . log ( "RetrieveGives response status:" , response . status ) ;
logger . log (
"RetrieveGives response headers:" ,
Object . fromEntries ( response . headers . entries ( ) ) ,
) ;
if ( ! response . ok ) {
throw await response . text ( ) ;
/ / 3 0 4 N o t M o d i f i e d i s a s u c c e s s f u l r e s p o n s e
if ( ! response . ok && response . status !== 304 ) {
const errorText = await response . text ( ) ;
logger . error ( "RetrieveGives error response:" , errorText ) ;
throw errorText ;
}
/ / F o r 3 0 4 r e s p o n s e s , w e c a n u s e t h e c a c h e d d a t a
if ( response . status === 304 ) {
logger . log ( "RetrieveGives: Got 304 Not Modified response" ) ;
return { data : [ ] , hitLimit : false } ;
}
try {
const results = await response . json ( ) ;
logger . log (
"RetrieveGives response data length:" ,
results . data ? . length || 0 ,
) ;
if ( results . data ) {
logger . log ( "Successfully parsed response data" ) ;
return results ;
} else {
logger . error ( "RetrieveGives: Invalid response format:" , results ) ;
throw JSON . stringify ( results ) ;
}
} catch ( parseError ) {
logger . error ( "Error parsing response JSON:" , parseError ) ;
throw parseError ;
}
}
@ -1376,7 +1696,35 @@ export default class HomeView extends Vue {
* Called by template click handler
* /
goToActivityToUserPage ( ) {
this . $router . push ( { name : "new-activity" } ) ;
try {
if ( ! this . $router ) {
logger . error ( "Router not initialized" ) ;
return ;
}
this . $router . push ( { name : "new-activity" } ) . catch ( ( err ) => {
logger . error ( "Navigation error:" , err ) ;
this . $notify (
{
group : "alert" ,
type : "danger" ,
title : "Navigation Error" ,
text : "Unable to navigate to activity page. Please try again." ,
} ,
5000 ,
) ;
} ) ;
} catch ( err ) {
logger . error ( "Error in goToActivityToUserPage:" , err ) ;
this . $notify (
{
group : "alert" ,
type : "danger" ,
title : "Error" ,
text : "An unexpected error occurred. Please try again." ,
} ,
5000 ,
) ;
}
}
/ * *
@ -1634,8 +1982,8 @@ export default class HomeView extends Vue {
const cachedData = this . imageCache . get ( imageUrl ) ;
if ( cachedData ) {
this . selectedImageData = cachedData ;
this . selectedImage = imageUrl ;
this . isImageViewerOpen = true ;
this . selectedImage = imageUrl ;
this . isImageViewerOpen = true ;
return ;
}