@ -193,6 +193,38 @@ import {
import * as libsUtil from "../libs/util" ;
import { retrieveAccountDids } from "../libs/util" ;
/ * *
* Offer Details View Component
* @ author Matthew Raymer
*
* This component handles the creation and editing of offers within the platform .
* It supports both new offers and editing existing ones , with validation and
* submission handling .
*
* Features :
* - Offer amount and unit selection
* - Item description
* - Conditional requirements
* - Expiration date setting
* - Project or recipient targeting
* - Raw claim editing option
*
* Data Flow :
* 1. Component loads with optional previous offer data
* 2. Retrieves account settings and contact information
* 3. Populates form with existing or default values
* 4. Validates and submits offer to server
* 5. Redirects on success or shows error
*
* Security Features :
* - DID validation
* - JWT handling for edits
* - Server - side validation
* - Privacy controls for data sharing
*
* @ see GiftedDialog for related gift creation
* @ see ClaimAddRawView for raw claim editing
* /
@ Component ( {
components : {
QuickNav ,
@ -200,35 +232,96 @@ import { retrieveAccountDids } from "../libs/util";
} ,
} )
export default class OfferDetailsView extends Vue {
/** Notification function injected by Vue */
$notify ! : ( notification : NotificationIface , timeout ? : number ) => void ;
/** Current route instance */
$route ! : RouteLocationNormalizedLoaded ;
/** Router instance for navigation */
$router ! : Router ;
/** Currently active DID */
activeDid = "" ;
/** API server endpoint */
apiServer = "" ;
/** Offer amount input field */
amountInput = "0" ;
/** Conditions for the offer */
descriptionOfCondition = "" ;
/** Description of offered item */
descriptionOfItem = "" ;
/** Path to redirect after completion */
destinationPathAfter = "" ;
/** Controls back button visibility */
hideBackButton = false ;
/** Additional message to display */
message = "" ;
/** Flag for project assignment */
offeredToProject = false ;
/** Flag for recipient assignment */
offeredToRecipient = false ;
/** DID of offer creator */
offererDid : string | undefined ;
/** Offer ID for editing */
offerId = "" ;
/** Previous offer data for editing */
prevCredToEdit ? : GenericCredWrapper < OfferVerifiableCredential > ;
/** Project ID if offer is for project */
projectId = "" ;
/** Project name display */
projectName = "a project" ;
/** Recipient DID if offer is for person */
recipientDid = "" ;
/** Recipient name display */
recipientName = "" ;
/** Advanced features visibility flag */
showGeneralAdvanced = false ;
/** Unit type for offer amount */
unitCode = "HUR" ;
/** Expiration date input */
validThroughDateInput = "" ;
/** Utility library reference */
libsUtil = libsUtil ;
/ * *
* Component lifecycle hook that initializes the offer form
*
* Workflow :
* 1. Extracts previous offer data if editing
* 2. Sets initial form values from route or previous offer
* 3. Loads account settings and contacts
* 4. Retrieves project information if needed
* 5. Sets offer assignment flags
*
* @ throws Will not throw but shows notifications
* @ emits Notifications on loading errors
* /
async mounted ( ) {
try {
await this . loadPreviousOffer ( ) ;
await this . initializeFormValues ( ) ;
await this . loadAccountSettings ( ) ;
await this . loadRecipientInfo ( ) ;
await this . loadProjectInfo ( ) ;
} catch ( err : any ) {
console . error ( "Error in mounted:" , err ) ;
this . $notify (
{
group : "alert" ,
type : "danger" ,
title : "Error" ,
text : err . message || "There was an error loading the offer details." ,
} ,
5000 ,
) ;
}
}
/ * *
* Loads previous offer data if editing an existing offer
* @ throws Will not throw but shows notifications
* /
private async loadPreviousOffer ( ) {
try {
this . prevCredToEdit = ( this . $route . query [ "prevCredToEdit" ] as string )
? ( JSON . parse (
@ -246,34 +339,38 @@ export default class OfferDetailsView extends Vue {
5000 ,
) ;
}
}
/ * *
* Initializes form values from route params or previous offer
* /
private async initializeFormValues ( ) {
const prevAmount =
this . prevCredToEdit ? . claim ? . includesObject ? . amountOfThisGood ;
this . amountInput =
( this . $route . query [ "amountInput" ] as string ) ||
( prevAmount ? String ( prevAmount ) : "" ) ||
this . amountInput ;
this . unitCode = ( ( this . $route . query [ "unitCode" ] as string ) ||
this . prevCredToEdit ? . claim ? . includesObject ? . unitCode ||
this . unitCode ) as string ;
this . descriptionOfCondition =
this . prevCredToEdit ? . claim ? . description || this . descriptionOfCondition ;
this . descriptionOfItem =
( this . $route . query [ "description" ] as string ) ||
this . prevCredToEdit ? . claim ? . itemOffered ? . description ||
this . descriptionOfItem ;
this . destinationPathAfter =
( this . $route . query [ "destinationPathAfter" ] as string ) || "" ;
this . offererDid = ( ( this . $route . query [ "offererDid" ] as string ) ||
( this . prevCredToEdit ? . claim ? . agent as unknown as { identifier : string } )
? . identifier ||
this . offererDid ) as string ;
this . hideBackButton =
( this . $route . query [ "hideBackButton" ] as string ) === "true" ;
this . message = ( this . $route . query [ "message" ] as string ) || "" ;
/ / f i n d a n y p r o j e c t I D
/ / S e t p r o j e c t i n f o f r o m p r e v i o u s o f f e r o r r o u t e
let project ;
if (
this . prevCredToEdit ? . claim ? . itemOffered ? . isPartOf ? . [ "@type" ] ===
@ -294,13 +391,23 @@ export default class OfferDetailsView extends Vue {
this . validThroughDateInput =
this . prevCredToEdit ? . claim ? . validThrough || this . validThroughDateInput ;
}
try {
/ * *
* Loads account settings and updates component state
* @ throws Will not throw but logs errors
* /
private async loadAccountSettings ( ) {
const settings = await retrieveSettingsForActiveAccount ( ) ;
this . apiServer = settings . apiServer ? ? "" ;
this . activeDid = settings . activeDid ? ? "" ;
this . showGeneralAdvanced = settings . showGeneralAdvanced ? ? false ;
}
/ * *
* Loads recipient information if recipient DID exists
* /
private async loadRecipientInfo ( ) {
if ( this . recipientDid && ! this . recipientName ) {
const allContacts = await db . contacts . toArray ( ) ;
const allMyDids = await retrieveAccountDids ( ) ;
@ -311,26 +418,16 @@ export default class OfferDetailsView extends Vue {
allContacts ,
) ;
}
/ / t h e s e s h o u l d b e f u n c t i o n s b u t s o m e t h i n g ' s w r o n g w i t h t h e s y n t a x i n t h e < > c o n d i t i o n a l
/ / S e t a s s i g n m e n t f l a g s
this . offeredToProject = ! ! this . projectId ;
this . offeredToRecipient = ! this . offeredToProject && ! ! this . recipientDid ;
/ / 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 ) {
console . error ( "Error retrieving settings from database:" , err ) ;
this . $notify (
{
group : "alert" ,
type : "danger" ,
title : "Error" ,
text : err . message || "There was an error retrieving your settings." ,
} ,
5000 ,
) ;
}
/ * *
* Loads project information if project ID exists
* /
private async loadProjectInfo ( ) {
if ( this . projectId && ! this . projectName ) {
/ / c o n s o l e . l o g ( " G e t t i n g p r o j e c t n a m e f r o m c a c h e " , t h i s . p r o j e c t I d ) ;
const project = await getPlanFromCache (
this . projectId ,
this . axios ,
@ -343,16 +440,32 @@ export default class OfferDetailsView extends Vue {
}
}
/ * *
* Changes the unit type for the offer amount
*
* Cycles through available unit types in UNIT_SHORT .
* Updates display and internal state .
* /
changeUnitCode ( ) {
const units = Object . keys ( this . libsUtil . UNIT_SHORT ) ;
const index = units . indexOf ( this . unitCode ) ;
this . unitCode = units [ ( index + 1 ) % units . length ] ;
}
/ * *
* Increments the offer amount by 1
*
* Handles string to number conversion and updates display .
* /
increment ( ) {
this . amountInput = ` ${ ( parseFloat ( this . amountInput ) || 0 ) + 1 } ` ;
}
/ * *
* Decrements the offer amount by 1
*
* Prevents negative values and handles string to number conversion .
* /
decrement ( ) {
this . amountInput = ` ${ Math . max (
0 ,
@ -360,6 +473,15 @@ export default class OfferDetailsView extends Vue {
) } ` ;
}
/ * *
* Handles cancellation of offer creation / editing
*
* Workflow :
* 1. Checks for destination path
* 2. Navigates to destination or previous page
*
* @ emits Router navigation
* /
cancel ( ) {
if ( this . destinationPathAfter ) {
( this . $router as Router ) . push ( { path : this . destinationPathAfter } ) ;
@ -368,10 +490,28 @@ export default class OfferDetailsView extends Vue {
}
}
/ * *
* Handles back button navigation
*
* @ emits Router navigation to previous page
* /
cancelBack ( ) {
( this . $router as Router ) . back ( ) ;
}
/ * *
* Validates and initiates offer submission
*
* Workflow :
* 1. Validates active DID exists
* 2. Checks for negative amounts
* 3. Ensures description or amount exists
* 4. Shows processing notification
* 5. Calls recordOffer for submission
*
* @ throws Will not throw but shows notifications
* @ emits Notifications for validation errors or processing
* /
async confirm ( ) {
if ( ! this . activeDid ) {
this . $notify (
@ -426,6 +566,15 @@ export default class OfferDetailsView extends Vue {
await this . recordOffer ( ) ;
}
/ * *
* Notifies user about project assignment restrictions
*
* Shows appropriate error message based on :
* - Missing project ID
* - Conflict with recipient assignment
*
* @ emits Notification with error message
* /
notifyUserOfProject ( ) {
if ( ! this . projectId ) {
this . $notify (
@ -451,6 +600,15 @@ export default class OfferDetailsView extends Vue {
}
}
/ * *
* Notifies user about recipient assignment restrictions
*
* Shows appropriate error message based on :
* - Missing recipient DID
* - Conflict with project assignment
*
* @ emits Notification with error message
* /
notifyUserOfRecipient ( ) {
if ( ! this . recipientDid ) {
this . $notify (
@ -477,11 +635,18 @@ export default class OfferDetailsView extends Vue {
}
/ * *
* Records the offer to the server
*
* Workflow :
* 1. Determines if editing existing or creating new
* 2. Prepares offer data with assignments
* 3. Submits to server via appropriate method
* 4. Handles success / error responses
* 5. Navigates on success
*
* @ param offererDid may be null
* @ param description may be an empty string
* @ param amountInput may be 0
* @ param unitCode may be omitted , defaults to "HUR"
* @ throws Will not throw but shows notifications
* @ emits Notifications for success / failure
* @ emits Router navigation on success
* /
public async recordOffer ( ) {
try {
@ -568,6 +733,17 @@ export default class OfferDetailsView extends Vue {
}
}
/ * *
* Constructs offer parameters for raw editing
*
* Creates a JSON string containing :
* - Offer details
* - Assignments
* - Conditions
* - Expiration
*
* @ returns JSON string of offer parameters
* /
constructOfferParam ( ) {
const recipientDid = this . offeredToRecipient
? this . recipientDid
@ -589,11 +765,11 @@ export default class OfferDetailsView extends Vue {
return claimStr ;
}
/ / H e l p e r f u n c t i o n s f o r r e a d a b i l i t y
/ * *
* @ param result response "data" from the server
* @ returns true if the result indicates an error
* Checks if server response indicates an error
*
* @ param result Response data from server
* @ returns true if response indicates error
* /
/ / 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
isCreationError ( result : any ) {
@ -601,8 +777,10 @@ export default class OfferDetailsView extends Vue {
}
/ * *
* @ param result direct response eg . ErrorResult or SuccessResult ( potentially with embedded "data" )
* @ returns best guess at an error message
* Extracts error message from server response
*
* @ param result Server response object
* @ returns Best available error message
* /
/ / 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
getCreationErrorMessage ( result : any ) {
@ -613,6 +791,13 @@ export default class OfferDetailsView extends Vue {
) ;
}
/ * *
* Shows privacy information dialog
*
* Displays standard privacy message about data sharing .
*
* @ emits Notification with privacy message
* /
explainData ( ) {
this . $notify (
{