@ -448,8 +448,25 @@ import * as libsUtil from "../libs/util";
import { isGiveAction , retrieveAccountDids } from "../libs/util" ;
import TopMessage from "../components/TopMessage.vue" ;
/ * *
* ConfirmGiftView Component
*
* Displays details about a gift claim and allows users to confirm it if eligible .
* Shows gift details including giver , recipient , amount , description , and confirmation status .
* Handles visibility of hidden DIDs and provides access to detailed claim information .
*
* Key features :
* - Gift confirmation workflow
* - Detailed gift information display
* - Confirmation status tracking
* - Hidden DID handling
* - Claim details expansion
* /
@ Component ( {
components : { TopMessage , QuickNav } ,
components : {
QuickNav ,
TopMessage ,
} ,
} )
export default class ConfirmGiftView extends Vue {
$notify ! : ( notification : NotificationIface , timeout ? : number ) => void ;
@ -485,94 +502,92 @@ export default class ConfirmGiftView extends Vue {
serverUtil = serverUtil ;
displayAmount = displayAmount ;
resetThisValues ( ) {
this . confirmerIdList = [ ] ;
this . confsVisibleErrorMessage = "" ;
this . confsVisibleToIdList = [ ] ;
this . giveDetails = undefined ;
this . isRegistered = false ;
this . numConfsNotVisible = 0 ;
this . urlForNewGive = "" ;
this . veriClaim = serverUtil . BLANK_GENERIC_SERVER_RECORD ;
this . veriClaimDump = "" ;
}
/ * *
* Initializes the view with gift claim information
*
* Workflow :
* 1. Retrieves active account settings
* 2. Loads gift claim details from ID in URL
* 3. Processes claim information for display
* 4. Checks user ' s ability to confirm the gift
* /
async mounted ( ) {
this . isLoading = true ;
try {
await this . initializeSettings ( ) ;
await this . loadClaimFromUrl ( ) ;
} catch ( error ) {
console . error ( "Error in mounted:" , error ) ;
this . handleMountError ( error ) ;
} finally {
this . isLoading = false ;
}
}
/ * *
* Initializes component settings and user data
* /
private async initializeSettings ( ) {
const settings = await retrieveSettingsForActiveAccount ( ) ;
this . activeDid = settings . activeDid || "" ;
this . apiServer = settings . apiServer || "" ;
this . allContacts = await db . contacts . toArray ( ) ;
this . isRegistered = settings . isRegistered || false ;
this . allMyDids = await retrieveAccountDids ( ) ;
const pathParam = window . location . pathname . substring (
"/confirm-gift/" . length ,
) ;
let claimId ;
if ( pathParam ) {
claimId = decodeURIComponent ( pathParam ) ;
await this . loadClaim ( claimId , this . activeDid ) ;
} else {
this . $notify (
{
group : "alert" ,
type : "danger" ,
title : "Error" ,
text : "No claim ID was provided." ,
} ,
3000 ,
) ;
}
/ / C h e c k s h a r e c a p a b i l i t y
/ / W h e n C h r o m e c o m p a t i b i l i t y i s f i x e d h t t p s : / / d e v e l o p e r . m o z i l l a . o r g / e n - U S / d o c s / W e b / A P I / W e b _ S h a r e _ A P I # a p i . n a v i g a t o r . c a n s h a r e
/ / t h e n u s e t h i s t r u e r c h e c k : n a v i g a t o r . c a n S h a r e & & n a v i g a t o r . c a n S h a r e ( )
this . canShare = ! ! navigator . share ;
this . isLoading = false ;
}
/ / i n s e r t a s p a c e b e f o r e a n y c a p i t a l l e t t e r s e x c e p t t h e i n i t i a l l e t t e r
/ / ( a n d c a p i t a l i z e i n i t i a l l e t t e r , j u s t i n c a s e )
capitalizeAndInsertSpacesBeforeCaps ( text : string ) {
return ! text
? ""
: text [ 0 ] . toUpperCase ( ) + text . substr ( 1 ) . replace ( /([A-Z])/g , " $1" ) ;
/ * *
* Loads and processes claim from URL parameters
* /
private async loadClaimFromUrl ( ) {
const pathParam = window . location . pathname . substring ( "/confirm-gift/" . length ) ;
if ( ! pathParam ) {
throw new Error ( "No claim ID was provided." ) ;
}
capitalizeAndInsertSpacesBeforeCapsWithAPrefix ( text : string ) {
const word = this . capitalizeAndInsertSpacesBeforeCaps ( text ) ;
if ( word ) {
/ / i f t h e w o r d s t a r t s w i t h a v o w e l , u s e " a n " i n s t e a d o f " a "
const firstLetter = word [ 0 ] . toLowerCase ( ) ;
const vowels = [ "a" , "e" , "i" , "o" , "u" ] ;
const particle = vowels . includes ( firstLetter ) ? "an" : "a" ;
return particle + " " + word ;
} else {
return "" ;
}
const claimId = decodeURIComponent ( pathParam ) ;
await this . loadClaim ( claimId , this . activeDid ) ;
}
totalConfirmers ( ) {
return (
this . numConfsNotVisible +
this . confirmerIdList . length +
this . confsVisibleToIdList . length
/ * *
* Handles errors during component mounting
* /
private handleMountError ( error : unknown ) {
this . $notify (
{
group : "alert" ,
type : "danger" ,
title : "Error" ,
text : error instanceof Error ? error . message : "No claim ID was provided." ,
} ,
3000 ,
) ;
}
/ / I s n ' t t h e r e a b e t t e r w a y t o m a k e t h i s a v a i l a b l e t o t h e t e m p l a t e ?
didInfo ( did : string | undefined ) {
return serverUtil . didInfo (
did ,
this . activeDid ,
this . allMyDids ,
this . allContacts ,
) ;
/ * *
* Loads claim details and associated give information
*
* @ param claimId - ID of claim to load
* @ param userDid - User ' s DID
* /
private async loadClaim ( claimId : string , userDid : string ) {
await this . fetchClaimDetails ( claimId , userDid ) ;
if ( this . veriClaim . claimType === "GiveAction" ) {
await this . fetchGiveDetails ( claimId , userDid ) ;
await this . processGiveDetails ( ) ;
await this . fetchConfirmerInfo ( claimId , userDid ) ;
}
}
async loadClaim ( claimId : string , userDid : string ) {
/ * *
* Fetches basic claim details from server
* /
private async fetchClaimDetails ( claimId : string , userDid : string ) {
const urlPath = libsUtil . isGlobalUri ( claimId )
? "/api/claim/byHandle/"
: "/api/claim/" ;
@ -581,9 +596,7 @@ export default class ConfirmGiftView extends Vue {
try {
const headers = await serverUtil . getHeaders ( userDid ) ;
const resp = await this . axios . get ( url , { headers } ) ;
/ / r e s p . d a t a i s :
/ / - a J w t f r o m h t t p s : / / a p i . e n d o r s e r . c h / a p i - d o c s /
/ / - w i t h a G i v e f r o m h t t p s : / / e n d o r s e r . c h / d o c / h t m l / t r a n s a c t i o n s . h t m l # i d 3
if ( resp . status === 200 ) {
this . veriClaim = resp . data ;
this . veriClaimDump = yaml . dump ( this . veriClaim ) ;
@ -591,200 +604,182 @@ export default class ConfirmGiftView extends Vue {
this . veriClaim ,
true ,
) ;
this . issuerName = this . didInfo ( this . veriClaim . issuer ) ;
} else {
/ / a c t u a l l y , a x i o s t y p i c a l l y t h r o w s a n e r r o r s o w e n e v e r g e t h e r e
console . error ( "Error getting claim:" , resp ) ;
this . $notify (
{
group : "alert" ,
type : "danger" ,
title : "Error" ,
text : "There was a problem retrieving that claim." ,
} ,
3000 ,
) ;
return ;
throw new Error ( "Error getting claim: " + resp . status ) ;
}
} catch ( error ) {
console . error ( "Error getting claim:" , error ) ;
throw new Error ( "There was a problem retrieving that claim." ) ;
}
/ / r e t r i e v e m o r e d e t a i l s o n G i v e , O f f e r , o r P l a n
if ( this . veriClaim . claimType !== "GiveAction" ) {
/ / n o n e e d t o g o f u r t h e r . . . t h i s p a g e i s f o r g i f t s
return ;
}
this . issuerName = this . didInfo ( this . veriClaim . issuer ) ;
/ * *
* Fetches detailed give information
* /
private async fetchGiveDetails ( claimId : string , userDid : string ) {
const giveUrl = ` ${ this . apiServer } /api/v2/report/gives?handleId= ${ encodeURIComponent ( claimId ) } ` ;
/ / u s e g i v e r e c o r d w h e n p o s s i b l e s i n c e i t m a y i n c l u d e e d i t s
const giveUrl =
this . apiServer +
"/api/v2/report/gives?handleId=" +
encodeURIComponent ( this . veriClaim . handleId as string ) ;
const giveHeaders = await serverUtil . getHeaders ( userDid ) ;
const giveResp = await this . axios . get ( giveUrl , {
headers : giveHeaders ,
} ) ;
/ / g i v e R e s p . d a t a i s a G i v e f r o m h t t p s : / / a p i . e n d o r s e r . c h / a p i - d o c s /
if ( giveResp . status === 200 ) {
this . giveDetails = giveResp . data . data [ 0 ] ;
try {
const headers = await serverUtil . getHeaders ( userDid ) ;
const resp = await this . axios . get ( giveUrl , { headers } ) ;
if ( resp . status === 200 ) {
this . giveDetails = resp . data . data [ 0 ] ;
} else {
console . error ( "Error getting detailed give info:" , giveResp ) ;
this . $notify (
{
group : "alert" ,
type : "danger" ,
title : "Error" ,
text : "Something went wrong retrieving gift data." ,
} ,
3000 ,
) ;
return ;
throw new Error ( "Error getting detailed give info: " + resp . status ) ;
}
/ / t h e l o g i c a l r e a d y s t o p s e a r l i e r i f t h e c l a i m d o e s n ' t e x i s t b u t t h i s h e l p s w i t h t y p e c h e c k i n g
if ( ! this . giveDetails ) {
return ;
} catch ( error ) {
console . error ( "Error getting detailed give info:" , error ) ;
throw new Error ( "Something went wrong retrieving gift data." ) ;
}
}
/ * *
* Processes give details and builds URL for new give
* /
private async processGiveDetails ( ) {
if ( ! this . giveDetails ) return ;
this . urlForNewGive = "/gifted-details?" ;
if ( this . giveDetails . amount ) {
this . urlForNewGive +=
"&amountInput=" + encodeURIComponent ( String ( this . giveDetails . amount ) ) ;
this . addGiveDetailsToUrl ( ) ;
this . processParticipantInfo ( ) ;
this . processAdditionalDetails ( ) ;
}
/ * *
* Adds basic give details to URL
* /
private addGiveDetailsToUrl ( ) {
if ( this . giveDetails ? . amount ) {
this . urlForNewGive += ` &amountInput= ${ encodeURIComponent ( String ( this . giveDetails . amount ) ) } ` ;
}
if ( this . giveDetails . unit ) {
this . urlForNewGive +=
"&unitCode=" + encodeURIComponent ( this . giveDetails . unit ) ;
if ( this . giveDetails ? . unit ) {
this . urlForNewGive += ` &unitCode= ${ encodeURIComponent ( this . giveDetails . unit ) } ` ;
}
if ( this . giveDetails . description ) {
this . urlForNewGive +=
"&description=" + encodeURIComponent ( this . giveDetails . description ) ;
if ( this . giveDetails ? . description ) {
this . urlForNewGive += ` &description= ${ encodeURIComponent ( this . giveDetails . description ) } ` ;
}
}
/ * *
* Processes participant ( giver / recipient ) information
* /
private processParticipantInfo ( ) {
if ( this . giveDetails ? . agentDid ) {
this . giverName = this . didInfo ( this . giveDetails . agentDid ) ;
if ( this . giveDetails . agentDid ) {
this . urlForNewGive +=
"&giverDid=" +
encodeURIComponent ( this . giveDetails . agentDid ) +
"&giverName=" +
encodeURIComponent ( this . giverName ) ;
this . urlForNewGive += ` &giverDid= ${ encodeURIComponent ( this . giveDetails . agentDid ) } &giverName= ${ encodeURIComponent ( this . giverName ) } ` ;
}
if ( this . giveDetails ? . recipientDid ) {
this . recipientName = this . didInfo ( this . giveDetails . recipientDid ) ;
if ( this . giveDetails . recipientDid ) {
this . urlForNewGive +=
"&recipientDid=" +
encodeURIComponent ( this . giveDetails . recipientDid ) +
"&recipientName=" +
encodeURIComponent ( this . recipientName ) ;
}
if ( this . giveDetails . fullClaim . image ) {
this . urlForNewGive +=
"&image=" + encodeURIComponent ( this . giveDetails . fullClaim . image ) ;
}
if (
this . giveDetails . type == "Offer" &&
this . giveDetails . fulfillsHandleId
) {
this . urlForNewGive +=
"&offerId=" +
encodeURIComponent ( this . giveDetails ? . fulfillsHandleId as string ) ;
}
if ( this . giveDetails . fulfillsPlanHandleId ) {
this . urlForNewGive +=
"&fulfillsProjectId=" +
encodeURIComponent ( this . giveDetails . fulfillsPlanHandleId ) ;
}
/ / r e t r i e v e t h e l i s t o f c o n f i r m e r s
this . urlForNewGive += ` &recipientDid= ${ encodeURIComponent ( this . giveDetails . recipientDid ) } &recipientName= ${ encodeURIComponent ( this . recipientName ) } ` ;
}
}
/ * *
* Processes additional give details ( image , offer , plan )
* /
private processAdditionalDetails ( ) {
if ( this . giveDetails ? . fullClaim . image ) {
this . urlForNewGive += ` &image= ${ encodeURIComponent ( this . giveDetails . fullClaim . image ) } ` ;
}
if ( this . giveDetails ? . type === "Offer" && this . giveDetails ? . fulfillsHandleId ) {
this . urlForNewGive += ` &offerId= ${ encodeURIComponent ( this . giveDetails . fulfillsHandleId ) } ` ;
}
if ( this . giveDetails ? . fulfillsPlanHandleId ) {
this . urlForNewGive += ` &fulfillsProjectId= ${ encodeURIComponent ( this . giveDetails . fulfillsPlanHandleId ) } ` ;
}
}
/ * *
* Fetches confirmer information for the claim
* /
private async fetchConfirmerInfo ( claimId : string , userDid : string ) {
const confirmerInfo = await libsUtil . retrieveConfirmerIdList (
this . apiServer ,
claimId ,
this . veriClaim . issuer ,
userDid ,
) ;
if ( confirmerInfo ) {
this . confirmerIdList = confirmerInfo . confirmerIdList ;
this . confsVisibleToIdList = confirmerInfo . confsVisibleToIdList ;
this . numConfsNotVisible = confirmerInfo . numConfsNotVisible ;
} else {
this . confsVisibleErrorMessage =
"Had problems retrieving confirmations." ;
}
} catch ( error : unknown ) {
const serverError = error as AxiosError ;
console . error ( "Error retrieving claim:" , serverError ) ;
this . $notify (
{
group : "alert" ,
type : "danger" ,
title : "Error" ,
text : "Something went wrong retrieving claim data." ,
} ,
3000 ,
) ;
this . confsVisibleErrorMessage = "Had problems retrieving confirmations." ;
}
}
confirmConfirmClaim ( ) {
this . $notify (
{
group : "modal" ,
type : "confirm" ,
title : "Confirm" ,
text : "Do you personally confirm that this is true?" ,
onYes : async ( ) => {
await this . confirmClaim ( ) ;
} ,
} ,
- 1 ,
/ * *
* Calculates total number of confirmers for the gift
* Includes both direct confirmers and those visible through network
*
* @ returns Total number of confirmers
* /
totalConfirmers ( ) : number {
return (
this . numConfsNotVisible +
this . confirmerIdList . length +
this . confsVisibleToIdList . length
) ;
}
/ / s i m i l a r c o d e i s f o u n d i n P r o j e c t V i e w V i e w
async confirmClaim ( ) {
/ / s i m i l a r l o g i c i s f o u n d i n e n d o r s e r - m o b i l e
const goodClaim = serverUtil . removeSchemaContext (
serverUtil . removeVisibleToDids (
serverUtil . addLastClaimOrHandleAsIdIfMissing (
this . veriClaim . claim ,
this . veriClaim . id ,
this . veriClaim . handleId ,
) ,
) ,
) ;
const confirmationClaim : GenericVerifiableCredential = {
"@context" : "https://schema.org" ,
"@type" : "AgreeAction" ,
object : goodClaim ,
} ;
const result = await serverUtil . createAndSubmitClaim (
confirmationClaim ,
/ * *
* Formats display amount with proper unit
*
* @ param unit - Currency or unit code
* @ param amount - Numeric amount
* @ returns Formatted amount string
* /
displayAmount ( unit : string , amount : number ) : string {
return displayAmount ( unit , amount ) ;
}
/ * *
* Retrieves human - readable name for a DID
* Falls back to DID if no name available
*
* @ param did - DID to get name for
* @ returns Human - readable name
* /
didInfo ( did : string ) : string {
return serverUtil . didInfo (
did ,
this . activeDid ,
this . apiServer ,
this . axios ,
) ;
if ( result . type === "success" ) {
this . $notify (
{
group : "alert" ,
type : "success" ,
title : "Success" ,
text : "Confirmation submitted." ,
} ,
3000 ,
this . allMyDids ,
this . allContacts ,
) ;
} else {
console . error ( "Got error submitting the confirmation:" , result ) ;
}
/ * *
* Copies text to clipboard and shows notification
*
* @ param description - Description of copied content
* @ param text - Text to copy
* /
copyToClipboard ( description : string , text : string ) : void {
useClipboard ( )
. copy ( text )
. then ( ( ) => {
this . $notify (
{
group : "alert" ,
type : "danger" ,
title : "Error" ,
text : "There was a problem submitting the confirmation." ,
type : "toast " ,
title : "Copied " ,
text : ( description || "That" ) + " was copied to the clipboard .",
} ,
5000 ,
2 000 ,
) ;
}
} ) ;
}
showClaimPage ( claimId : string ) {
/ * *
* Navigates to claim page for detailed view
*
* @ param claimId - ID of claim to view
* /
showClaimPage ( claimId : string ) : void {
const route = {
path : "/claim/" + encodeURIComponent ( claimId ) ,
} ;
@ -794,23 +789,30 @@ export default class ConfirmGiftView extends Vue {
} ) ;
}
copyToClipboard ( name : string , text : string ) {
useClipboard ( )
. copy ( text )
. then ( ( ) => {
/ * *
* Initiates claim confirmation process
* Verifies user eligibility and handles confirmation workflow
* /
async confirmConfirmClaim ( ) : Promise < void > {
this . $notify (
{
group : "alert" ,
type : "toast" ,
title : "Copied" ,
text : ( name || "That" ) + " was copied to the clipboard." ,
group : "modal" ,
type : "confirm" ,
title : "Confirm" ,
text : "Do you personally confirm that this is true?" ,
onYes : async ( ) => {
await this . confirmClaim ( ) ;
} ,
2000 ,
} ,
- 1 ,
) ;
} ) ;
}
notifyWhyCannotConfirm ( ) {
/ * *
* Notifies user why they cannot confirm the gift
* Explains requirements or restrictions preventing confirmation
* /
notifyWhyCannotConfirm ( ) : void {
libsUtil . notifyWhyCannotConfirm (
this . $notify ,
this . isRegistered ,
@ -821,71 +823,35 @@ export default class ConfirmGiftView extends Vue {
) ;
}
notifyWhyCannotConfirmBak ( ) {
if ( ! this . isRegistered ) {
this . $notify (
{
group : "alert" ,
type : "info" ,
title : "Not Registered" ,
text : "Someone needs to register you before you can contribute." ,
} ,
3000 ,
) ;
} else if ( ! isGiveAction ( this . veriClaim ) ) {
this . $notify (
{
group : "alert" ,
type : "info" ,
title : "Not A Give" ,
text : "This is not a giving action to confirm." ,
} ,
3000 ,
) ;
} else if ( this . confirmerIdList . includes ( this . activeDid ) ) {
this . $notify (
{
group : "alert" ,
type : "info" ,
title : "Already Confirmed" ,
text : "You already confirmed this claim." ,
} ,
3000 ,
) ;
} else if ( this . giveDetails ? . issuerDid == this . activeDid ) {
this . $notify (
{
group : "alert" ,
type : "info" ,
title : "Cannot Confirm" ,
text : "You cannot confirm this because you issued this claim." ,
} ,
3000 ,
) ;
} else if ( serverUtil . containsHiddenDid ( this . giveDetails ? . fullClaim ) ) {
this . $notify (
{
group : "alert" ,
type : "info" ,
title : "Cannot Confirm" ,
text : "You cannot confirm this because some people are hidden." ,
} ,
3000 ,
) ;
/ * *
* Formats type string for display by adding spaces before capitals
* Optionally adds a prefix
*
* @ param text - Text to format
* @ param prefix - Optional prefix to add
* @ returns Formatted string
* /
capitalizeAndInsertSpacesBeforeCapsWithAPrefix (
text : string ,
prefix ? : string
) : string {
const word = this . capitalizeAndInsertSpacesBeforeCaps ( text ) ;
if ( word ) {
/ / i f t h e w o r d s t a r t s w i t h a v o w e l , u s e " a n " i n s t e a d o f " a "
const firstLetter = word [ 0 ] . toLowerCase ( ) ;
const vowels = [ "a" , "e" , "i" , "o" , "u" ] ;
const particle = vowels . includes ( firstLetter ) ? "an" : "a" ;
return particle + " " + word ;
} else {
this . $notify (
{
group : "alert" ,
type : "info" ,
title : "Cannot Confirm" ,
text : "You cannot confirm this claim. There are no other details, but we can help more if you contact us and send us screenshots." ,
} ,
3000 ,
) ;
return "" ;
}
}
onClickShareClaim ( ) {
/ * *
* Initiates sharing of claim information
* Handles share functionality based on platform capabilities
* /
async onClickShareClaim ( ) : Promise < void > {
this . copyToClipboard ( "A link to this page" , this . windowLocation ) ;
window . navigator . share ( {
title : "Help Connect Me" ,
@ -893,5 +859,23 @@ export default class ConfirmGiftView extends Vue {
url : this . windowLocation ,
} ) ;
}
resetThisValues ( ) {
this . confirmerIdList = [ ] ;
this . confsVisibleErrorMessage = "" ;
this . confsVisibleToIdList = [ ] ;
this . giveDetails = undefined ;
this . isRegistered = false ;
this . numConfsNotVisible = 0 ;
this . urlForNewGive = "" ;
this . veriClaim = serverUtil . BLANK_GENERIC_SERVER_RECORD ;
this . veriClaimDump = "" ;
}
capitalizeAndInsertSpacesBeforeCaps ( text : string ) {
return ! text
? ""
: text [ 0 ] . toUpperCase ( ) + text . substr ( 1 ) . replace ( /([A-Z])/g , " $1" ) ;
}
}
< / script >