@ -1,45 +1,32 @@
< template >
< template >
< QuickNav selected = "Contacts" > < / QuickNav >
< QuickNav selected = "Contacts" / >
< section id = "Content" class = "p-6 pb-24 max-w-3xl mx-auto" >
<!-- Breadcrumb -- >
< section class = "p-6 pb-24 max-w-3xl mx-auto" >
<!-- Header -- >
< div class = "mb-8" >
< div class = "mb-8" >
< h1
< router -link
id = "ViewBreadcrumb "
: to = "{ name: 'contacts' } "
class = "text-lg text-center font-light relative px-7 "
class = "text-lg text-center px-2 py-1 absolute -left-2 -top-1 "
>
>
<!-- Back -- >
< font -awesome icon = "chevron-left" class = "fa-fw" / >
< router -link
< / r o u t e r - l i n k >
: to = "{ name: 'contacts' }"
class = "text-lg text-center px-2 py-1 absolute -left-2 -top-1"
> < font -awesome icon = "chevron-left" class = "fa-fw" > < / f o n t - a w e s o m e >
< / r o u t e r - l i n k >
< / h1 >
< h1 id = "ViewHeading" class = "text-4xl text-center font-light pt-4" >
< h1 class = "text-4xl text-center font-light pt-4" >
Transferred with { { contact ? . name } }
Transferred with { { contact ? . name } }
< / h1 >
< / h1 >
< / div >
< / div >
< div class = "flex justify-around" >
<!-- Info Messages -- >
< span / >
< div class = "text-center text-sm text-slate-600 mb-6 space-y-1" >
< span class = "justify-around" > ( Only 50 most recent ) < / span >
< p > ( Only 50 most recent ) < / p >
< span / >
< p > ( This does not include claims by them if they ' re not visible to you . ) < / p >
< / div >
< div class = "flex justify-around" >
< span / >
< span class = "justify-around" >
( This does not include claims by them if they ' re not visible to you . )
< / span >
< span / >
< / div >
< / div >
<!-- Results List -- >
<!-- Transfer History Table -- >
< table
< table class = "table-auto w-full border-t border-slate-300 text-sm sm:text-base text-center" >
class = "table-auto w-full border-t border-slate-300 text-sm sm:text-base text-center"
>
< thead class = "bg-slate-100" >
< thead class = "bg-slate-100" >
< tr class = "border-b border-slate-300" >
< tr class = "border-b border-slate-300" >
< th > < / th >
< th class = "px-1 py-2" > Date < / th >
< th class = "px-1 py-2" > From Them < / th >
< th class = "px-1 py-2" > From Them < / th >
< th > < / th >
< th > < / th >
< th class = "px-1 py-2" > To Them < / th >
< th class = "px-1 py-2" > To Them < / th >
@ -51,50 +38,59 @@
: key = "record.jwtId"
: key = "record.jwtId"
class = "border-b border-slate-300"
class = "border-b border-slate-300"
>
>
<!-- Date -- >
< td class = "p-1 text-xs sm:text-sm text-left text-slate-500" >
< td class = "p-1 text-xs sm:text-sm text-left text-slate-500" >
{ { new Date ( record . issuedAt ) . toLocaleString ( ) } }
{ { new Date ( record . issuedAt ) . toLocaleString ( ) } }
< / td >
< / td >
<!-- From Them -- >
< td class = "p-1" >
< td class = "p-1" >
< span v-if ="record.agentDid == contact?.did" >
< div v-if ="record.agentDid = == contact?.did" >
< div class = "font-bold" >
< div class = "font-bold" >
{ { displayAmount ( record . unit , record . amount ) } }
{ { displayAmount ( record . unit , record . amount ) } }
< span v-if ="record.amountConfirmed" title="Confirmed" >
< font -awesome
< font -awesome
v - if = "record.amountConfirmed"
icon = "circle-check"
icon = "circle-check"
class = "text-green-600 fa-fw"
class = "text-green-600 fa-fw"
/ >
title = "Confirmed"
< / span >
/ >
< button v -else title = "Unconfirmed" @click ="confirm(record)" >
< button
v - else
@ click = "confirm(record)"
title = "Unconfirmed"
>
< font -awesome icon = "circle" class = "text-blue-600 fa-fw" / >
< font -awesome icon = "circle" class = "text-blue-600 fa-fw" / >
< / button >
< / button >
< / div >
< / div >
< div class = "italic text-xs sm:text-sm text-slate-500" >
< div class = "italic text-xs sm:text-sm text-slate-500" >
{ { record . description } }
{ { record . description } }
< / div >
< / div >
< / span >
< / div >
< / td >
< / td >
<!-- Direction Arrow -- >
< td class = "p-1" >
< td class = "p-1" >
< span v-if ="record.agentDid == contact?.did" >
< font -awesome
< font -awesome icon = "arrow-left" class = "text-slate-400 fa-fw" / >
: icon = "record.agentDid === contact?.did ? 'arrow-left' : 'arrow-right'"
< / span >
class = "text-slate-400 fa-fw"
< span v-else >
/ >
< font -awesome icon = "arrow-right" class = "text-slate-400 fa-fw" / >
< / span >
< / td >
< / td >
<!-- To Them -- >
< td class = "p-1" >
< td class = "p-1" >
< span v-if ="record.agentDid != contact?.did" >
< div v-if ="record.agentDid != = contact?.did" >
< div class = "font-bold" >
< div class = "font-bold" >
{ { displayAmount ( record . unit , record . amount ) } }
{ { displayAmount ( record . unit , record . amount ) } }
< span v-if ="record.amountConfirmed" title="Confirmed" >
< font -awesome
< font -awesome
v - if = "record.amountConfirmed"
icon = "circle-check"
icon = "circle-check"
class = "text-green-600 fa-fw"
class = "text-green-600 fa-fw"
/ >
title = "Confirmed"
< / span >
/ >
< button
< button
v - else
v - else
title = "Unconfirmed"
@ click = "cannotConfirmMessage()"
@ click = "cannotConfirmMessage()"
title = "Unconfirmed"
>
>
< font -awesome icon = "circle" class = "text-slate-600 fa-fw" / >
< font -awesome icon = "circle" class = "text-slate-600 fa-fw" / >
< / button >
< / button >
@ -102,7 +98,7 @@
< div class = "italic text-xs sm:text-sm text-slate-500" >
< div class = "italic text-xs sm:text-sm text-slate-500" >
{ { record . description } }
{ { record . description } }
< / div >
< / div >
< / span >
< / div >
< / td >
< / td >
< / tr >
< / tr >
< / tbody >
< / tbody >
@ -118,8 +114,15 @@ import { RouteLocationNormalizedLoaded, Router } from "vue-router";
import QuickNav from "../components/QuickNav.vue" ;
import QuickNav from "../components/QuickNav.vue" ;
import { NotificationIface } from "../constants/app" ;
import { NotificationIface } from "../constants/app" ;
import { PlatformServiceMixin } from "../utils/PlatformServiceMixin" ;
import { createNotifyHelpers , TIMEOUTS } from "../utils/notify" ;
import {
NOTIFY_SETTINGS_RETRIEVAL_ERROR ,
NOTIFY_SERVER_RETRIEVAL_ERROR ,
NOTIFY_CONFIRMATION_RESTRICTION
} from "../constants/notifications" ;
import { Contact } from "../db/tables/contacts" ;
import { Contact } from "../db/tables/contacts" ;
import * as databaseUtil from "../db/databaseUtil" ;
import { MASTER_SETTINGS_KEY } from "../db/tables/settings ";
import { GiveSummaryRecord , GiveActionClaim } from "../interfaces" ;
import { GiveSummaryRecord , GiveActionClaim } from "../interfaces" ;
import { AgreeActionClaim } from "../interfaces/claims" ;
import { AgreeActionClaim } from "../interfaces/claims" ;
import {
import {
@ -129,39 +132,96 @@ import {
SCHEMA_ORG_CONTEXT ,
SCHEMA_ORG_CONTEXT ,
} from "../libs/endorserServer" ;
} from "../libs/endorserServer" ;
import { retrieveAccountCount } from "../libs/util" ;
import { retrieveAccountCount } from "../libs/util" ;
import { logger } from "../utils/logger" ;
import { PlatformServiceFactory } from "@/services/PlatformServiceFactory" ;
/ * *
@ Component ( { components : { QuickNav } } )
* Contact Amounts View Component
* @ author Matthew Raymer
*
* This component displays the transfer history between the current user and a specific contact .
* It shows both incoming and outgoing transfers with confirmation status and allows users
* to confirm unconfirmed transfers .
*
* Features :
* - Displays transfer history in a table format
* - Shows confirmation status for each transfer
* - Allows confirmation of unconfirmed transfers
* - Handles server communication for transfer data
* - Provides error handling and user feedback
*
* Workflow :
* 1. Component loads with contact DID from route query
* 2. Fetches contact information from database via PlatformServiceMixin
* 3. Loads transfer history from server API
* 4. Displays transfers in chronological order
* 5. Allows user to confirm unconfirmed transfers
*
* Transfer Types :
* - From Them : Transfers received from the contact
* - To Them : Transfers sent to the contact
*
* Confirmation Status :
* - Confirmed : Transfer has been acknowledged by recipient
* - Unconfirmed : Transfer pending confirmation
* - Cannot Confirm : User is not the recipient of the transfer
* /
@ Component ( {
components : { QuickNav } ,
mixins : [ PlatformServiceMixin ] ,
} )
export default class ContactAmountssView extends Vue {
export default class ContactAmountssView extends Vue {
/** Notification function injected by Vue */
$notify ! : ( notification : NotificationIface , timeout ? : number ) => void ;
$notify ! : ( notification : NotificationIface , timeout ? : number ) => void ;
/** Current route instance */
$route ! : RouteLocationNormalizedLoaded ;
$route ! : RouteLocationNormalizedLoaded ;
/** Router instance for navigation */
$router ! : Router ;
$router ! : Router ;
/** Notification helpers */
notify ! : ReturnType < typeof createNotifyHelpers > ;
/** Active user DID */
activeDid = "" ;
activeDid = "" ;
/** API server URL */
apiServer = "" ;
apiServer = "" ;
/** Current contact data */
contact : Contact | null = null ;
contact : Contact | null = null ;
/** Array of transfer records */
giveRecords : Array < GiveSummaryRecord > = [ ] ;
giveRecords : Array < GiveSummaryRecord > = [ ] ;
/** Number of user accounts */
numAccounts = 0 ;
numAccounts = 0 ;
/** Display amount utility function */
displayAmount = displayAmount ;
displayAmount = displayAmount ;
/ * *
* Component lifecycle hook that initializes account count
* /
async beforeCreate ( ) {
async beforeCreate ( ) {
this . numAccounts = await retrieveAccountCount ( ) ;
this . numAccounts = await retrieveAccountCount ( ) ;
}
}
/ * *
* Component lifecycle hook that initializes the contact amounts view
*
* Workflow :
* 1. Extracts contact DID from route query parameters
* 2. Queries database for contact information via PlatformServiceMixin
* 3. Retrieves user settings for active DID and API server
* 4. Loads transfer history if both active DID and contact are available
* 5. Handles errors with appropriate user notifications
*
* @ throws Will not throw but notifies on errors
* @ emits Notification on database or settings errors
* /
async created ( ) {
async created ( ) {
this . notify = createNotifyHelpers ( this . $notify ) ;
try {
try {
const contactDid = this . $route . query [ "contactDid" ] as string ;
const contactDid = this . $route . query [ "contactDid" ] as string ;
const platformService = PlatformServiceFactory . getInstance ( ) ;
const contact = await this . $getContact ( contactDid ) ;
const dbContact = await platformService . dbQuery (
this . contact = contact ;
"SELECT * FROM contacts WHERE did = ?" ,
[ contactDid ] ,
) ;
this . contact = databaseUtil . mapQueryResultToValues (
dbContact ,
) [ 0 ] as unknown as Contact ;
const settings = await databaseUtil . retrieveSettingsForActiveAccount ( ) ;
const settings = await this . $getSettings ( MASTER_SETTINGS_KEY ) ;
this . activeDid = settings ? . activeDid || "" ;
this . activeDid = settings ? . activeDid || "" ;
this . apiServer = settings ? . apiServer || "" ;
this . apiServer = settings ? . apiServer || "" ;
@ -170,21 +230,26 @@ export default class ContactAmountssView extends Vue {
}
}
/ / 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
} catch ( err : any ) {
} catch ( err : any ) {
logger . error ( "Error retrieving settings or gives." , err ) ;
await this . $logError ( "Error retrieving settings or gives." ) ;
this . $notify (
this . notify . error (
{
err . userMessage ||
group : "alert" ,
NOTIFY_SETTINGS_RETRIEVAL_ERROR . message ,
type : "danger" ,
TIMEOUTS . LONG
title : "Error" ,
text :
err . userMessage ||
"There was an error retrieving your settings or contacts or gives." ,
} ,
5000 ,
) ;
) ;
}
}
}
}
/ * *
* Loads transfer history from the server API
*
* Fetches both incoming and outgoing transfers between the active user and contact ,
* then sorts them by date and updates the component state .
*
* @ param activeDid The active user ' s DID
* @ param contact The contact to load transfers with
* @ throws Will not throw but notifies on server errors
* @ emits Notification on server communication errors
* /
async loadGives ( activeDid : string , contact : Contact ) {
async loadGives ( activeDid : string , contact : Contact ) {
try {
try {
let result : Array < GiveSummaryRecord > = [ ] ;
let result : Array < GiveSummaryRecord > = [ ] ;
@ -199,19 +264,12 @@ export default class ContactAmountssView extends Vue {
if ( resp . status === 200 ) {
if ( resp . status === 200 ) {
result = resp . data . data ;
result = resp . data . data ;
} else {
} else {
logger . error (
await this . $logError (
"Got bad response status & data of" ,
` Got bad response status & data of ${ resp . status } ${ JSON . stringify ( resp . data ) } `
resp . status ,
resp . data ,
) ;
) ;
this . $notify (
this . notify . error (
{
NOTIFY_SERVER_RETRIEVAL_ERROR . message ,
group : "alert" ,
TIMEOUTS . LONG
type : "danger" ,
title : "Error With Server" ,
text : "Got an error retrieving your given time from the server." ,
} ,
5000 ,
) ;
) ;
}
}
@ -226,19 +284,12 @@ export default class ContactAmountssView extends Vue {
if ( resp2 . status === 200 ) {
if ( resp2 . status === 200 ) {
result = R . concat ( result , resp2 . data . data ) ;
result = R . concat ( result , resp2 . data . data ) ;
} else {
} else {
logger . error (
await this . $logError (
"Got bad response status & data of" ,
` Got bad response status & data of ${ resp2 . status } ${ JSON . stringify ( resp2 . data ) } `
resp2 . status ,
resp2 . data ,
) ;
) ;
this . $notify (
this . notify . error (
{
NOTIFY_SERVER_RETRIEVAL_ERROR . message ,
group : "alert" ,
TIMEOUTS . LONG
type : "danger" ,
title : "Error With Server" ,
text : "Got an error retrieving your given time from the server." ,
} ,
5000 ,
) ;
) ;
}
}
@ -249,18 +300,23 @@ export default class ContactAmountssView extends Vue {
) ;
) ;
this . giveRecords = sortedResult ;
this . giveRecords = sortedResult ;
} catch ( error ) {
} catch ( error ) {
this . $notify (
this . notify . error (
{
error as string ,
group : "alert" ,
TIMEOUTS . LONG
type : "danger" ,
title : "Error With Server" ,
text : error as string ,
} ,
5000 ,
) ;
) ;
}
}
}
}
/ * *
* Confirms an unconfirmed transfer by creating and submitting a confirmation claim
*
* Creates an AgreeAction claim for the transfer and submits it to the server
* to confirm receipt of the transfer .
*
* @ param record The transfer record to confirm
* @ throws Will not throw but notifies on server errors
* @ emits Notification on confirmation success or failure
* /
async confirm ( record : GiveSummaryRecord ) {
async confirm ( record : GiveSummaryRecord ) {
/ / M a k e c l a i m
/ / M a k e c l a i m
/ / I u s e c l o n e h e r e b e c a u s e o t h e r w i s e i t g e t s a P r o x y o b j e c t .
/ / I u s e c l o n e h e r e b e c a u s e o t h e r w i s e i t g e t s a P r o x y o b j e c t .
@ -305,27 +361,26 @@ export default class ContactAmountssView extends Vue {
userMessage = error as string ;
userMessage = error as string ;
}
}
/ / N o w s e t t h a t e r r o r f o r t h e u s e r t o s e e .
/ / N o w s e t t h a t e r r o r f o r t h e u s e r t o s e e .
this . $notify (
this . notify . error (
{
userMessage ,
group : "alert" ,
TIMEOUTS . LONG
type : "danger" ,
title : "Error With Server" ,
text : userMessage ,
} ,
5000 ,
) ;
) ;
}
}
}
}
/ * *
* Shows notification that user cannot confirm a transfer they didn ' t receive
*
* Only the recipient of a transfer can confirm its receipt .
* This method notifies users when they try to confirm a transfer
* that was sent to someone else .
*
* @ emits Notification explaining confirmation restrictions
* /
cannotConfirmMessage ( ) {
cannotConfirmMessage ( ) {
this . $notify (
this . notify . error (
{
NOTIFY_CONFIRMATION_RESTRICTION . message ,
group : "alert" ,
TIMEOUTS . STANDARD
type : "danger" ,
title : "Not Allowed" ,
text : "Only the recipient can confirm final receipt." ,
} ,
5000 ,
) ;
) ;
}
}
}
}