Browse Source

fix: AmountInput increment/decrement buttons not working

- Fixed input field binding from :value to v-model for proper two-way binding
- Input field now properly reflects programmatic value changes from buttons
- Simplified handleInput method to work with v-model synchronization
- Fixed vite.config.mts fs alias to resolve worker import issues
- Applied ESLint formatting fixes across all component files
- Maintains comprehensive debugging logs for event flow tracing

The issue was that :value creates one-way binding, so when increment/decrement
methods updated displayValue programmatically, Vue wasn't updating the DOM.
v-model creates proper two-way binding that synchronizes both directions.
matthew-scratch-2025-06-28
Matthew Raymer 2 weeks ago
parent
commit
f4856f48aa
  1. BIN
      public/wasm/sql-wasm.wasm
  2. 67
      src/components/AmountInput.vue
  3. 29
      src/components/EntityGrid.vue
  4. 17
      src/components/EntitySelectionStep.vue
  5. 23
      src/components/EntitySummaryButton.vue
  6. 50
      src/components/GiftDetailsStep.vue
  7. 80
      src/components/GiftedDialog.vue
  8. 40
      src/components/PersonCard.vue
  9. 32
      src/components/ProjectCard.vue
  10. 29
      src/components/ShowAllCard.vue
  11. 42
      src/components/SpecialEntityCard.vue
  12. 2
      vite.config.mts

BIN
public/wasm/sql-wasm.wasm

Binary file not shown.

67
src/components/AmountInput.vue

@ -1,25 +1,20 @@
/** /** * AmountInput.vue - Specialized amount input with increment/decrement
* AmountInput.vue - Specialized amount input with increment/decrement controls controls * * Extracted from GiftedDialog.vue to handle numeric amount input *
* with increment/decrement buttons and validation. * * @author Matthew Raymer */
* Extracted from GiftedDialog.vue to handle numeric amount input
* with increment/decrement buttons and validation.
*
* @author Matthew Raymer
*/
<template> <template>
<div class="flex"> <div class="flex">
<button <button
class="rounded-s border border-e-0 border-slate-400 bg-slate-200 px-4 py-2" class="rounded-s border border-e-0 border-slate-400 bg-slate-200 px-4 py-2"
:disabled="isAtMinimum" :disabled="isAtMinimum"
@click.prevent="decrement"
type="button" type="button"
@click.prevent="decrement"
> >
<font-awesome icon="chevron-left" /> <font-awesome icon="chevron-left" />
</button> </button>
<input <input
:id="inputId" :id="inputId"
:value="displayValue" v-model="displayValue"
type="number" type="number"
:min="min" :min="min"
:max="max" :max="max"
@ -28,12 +23,12 @@
@input="handleInput" @input="handleInput"
@blur="handleBlur" @blur="handleBlur"
/> />
<button <button
class="rounded-e border border-slate-400 bg-slate-200 px-4 py-2" class="rounded-e border border-slate-400 bg-slate-200 px-4 py-2"
:disabled="isAtMaximum" :disabled="isAtMaximum"
@click.prevent="increment"
type="button" type="button"
@click.prevent="increment"
> >
<font-awesome icon="chevron-right" /> <font-awesome icon="chevron-right" />
</button> </button>
@ -45,7 +40,7 @@ import { Component, Prop, Vue, Watch, Emit } from "vue-facing-decorator";
/** /**
* AmountInput - Numeric input with increment/decrement controls * AmountInput - Numeric input with increment/decrement controls
* *
* Features: * Features:
* - Increment/decrement buttons with validation * - Increment/decrement buttons with validation
* - Configurable min/max values and step size * - Configurable min/max values and step size
@ -82,7 +77,13 @@ export default class AmountInput extends Vue {
* Initialize display value from prop * Initialize display value from prop
*/ */
mounted(): void { mounted(): void {
console.log(
`[AmountInput] mounted() - initial value: ${this.value}, min: ${this.min}, max: ${this.max}, step: ${this.step}`,
);
this.displayValue = this.value.toString(); this.displayValue = this.value.toString();
console.log(
`[AmountInput] mounted() - displayValue set to: ${this.displayValue}`,
);
} }
/** /**
@ -97,21 +98,33 @@ export default class AmountInput extends Vue {
* Check if current value is at minimum * Check if current value is at minimum
*/ */
get isAtMinimum(): boolean { get isAtMinimum(): boolean {
return this.value <= this.min; const result = this.value <= this.min;
console.log(
`[AmountInput] isAtMinimum - value: ${this.value}, min: ${this.min}, result: ${result}`,
);
return result;
} }
/** /**
* Check if current value is at maximum * Check if current value is at maximum
*/ */
get isAtMaximum(): boolean { get isAtMaximum(): boolean {
return this.value >= this.max; const result = this.value >= this.max;
console.log(
`[AmountInput] isAtMaximum - value: ${this.value}, max: ${this.max}, result: ${result}`,
);
return result;
} }
/** /**
* Increment the value by step size * Increment the value by step size
*/ */
increment(): void { increment(): void {
console.log(
`[AmountInput] increment() called - current value: ${this.value}, step: ${this.step}`,
);
const newValue = Math.min(this.value + this.step, this.max); const newValue = Math.min(this.value + this.step, this.max);
console.log(`[AmountInput] increment() calculated newValue: ${newValue}`);
this.updateValue(newValue); this.updateValue(newValue);
} }
@ -119,18 +132,19 @@ export default class AmountInput extends Vue {
* Decrement the value by step size * Decrement the value by step size
*/ */
decrement(): void { decrement(): void {
console.log(
`[AmountInput] decrement() called - current value: ${this.value}, step: ${this.step}`,
);
const newValue = Math.max(this.value - this.step, this.min); const newValue = Math.max(this.value - this.step, this.min);
console.log(`[AmountInput] decrement() calculated newValue: ${newValue}`);
this.updateValue(newValue); this.updateValue(newValue);
} }
/** /**
* Handle direct input changes * Handle direct input changes
*/ */
handleInput(event: Event): void { handleInput(): void {
const target = event.target as HTMLInputElement; const numericValue = parseFloat(this.displayValue);
this.displayValue = target.value;
const numericValue = parseFloat(target.value);
if (!isNaN(numericValue)) { if (!isNaN(numericValue)) {
const clampedValue = Math.max(this.min, Math.min(numericValue, this.max)); const clampedValue = Math.max(this.min, Math.min(numericValue, this.max));
this.updateValue(clampedValue); this.updateValue(clampedValue);
@ -148,9 +162,17 @@ export default class AmountInput extends Vue {
* Update the value and emit change event * Update the value and emit change event
*/ */
private updateValue(newValue: number): void { private updateValue(newValue: number): void {
console.log(
`[AmountInput] updateValue() called - oldValue: ${this.value}, newValue: ${newValue}`,
);
if (newValue !== this.value) { if (newValue !== this.value) {
console.log(
`[AmountInput] updateValue() - values different, updating and emitting`,
);
this.displayValue = newValue.toString(); this.displayValue = newValue.toString();
this.emitUpdateValue(newValue); this.emitUpdateValue(newValue);
} else {
console.log(`[AmountInput] updateValue() - values same, skipping update`);
} }
} }
@ -159,6 +181,7 @@ export default class AmountInput extends Vue {
*/ */
@Emit("update:value") @Emit("update:value")
emitUpdateValue(value: number): number { emitUpdateValue(value: number): number {
console.log(`[AmountInput] emitUpdateValue() - emitting value: ${value}`);
return value; return value;
} }
} }
@ -181,4 +204,4 @@ button:disabled {
opacity: 0.5; opacity: 0.5;
cursor: not-allowed; cursor: not-allowed;
} }
</style> </style>

29
src/components/EntityGrid.vue

@ -1,11 +1,6 @@
/** /** * EntityGrid.vue - Unified entity grid layout component * * Extracted from
* EntityGrid.vue - Unified entity grid layout component GiftedDialog.vue to provide a reusable grid layout * for displaying people,
* projects, and special entities with selection. * * @author Matthew Raymer */
* Extracted from GiftedDialog.vue to provide a reusable grid layout
* for displaying people, projects, and special entities with selection.
*
* @author Matthew Raymer
*/
<template> <template>
<ul :class="gridClasses"> <ul :class="gridClasses">
<!-- Special entities (You, Unnamed) for people grids --> <!-- Special entities (You, Unnamed) for people grids -->
@ -21,7 +16,7 @@
:entity-data="youEntityData" :entity-data="youEntityData"
@entity-selected="handleEntitySelected" @entity-selected="handleEntitySelected"
/> />
<!-- "Unnamed" entity --> <!-- "Unnamed" entity -->
<SpecialEntityCard <SpecialEntityCard
entity-type="unnamed" entity-type="unnamed"
@ -51,7 +46,7 @@
@person-selected="handlePersonSelected" @person-selected="handlePersonSelected"
/> />
</template> </template>
<template v-else-if="entityType === 'projects'"> <template v-else-if="entityType === 'projects'">
<ProjectCard <ProjectCard
v-for="project in displayedEntities" v-for="project in displayedEntities"
@ -85,7 +80,7 @@ import { PlanData } from "../interfaces/records";
/** /**
* EntityGrid - Unified grid layout for displaying people or projects * EntityGrid - Unified grid layout for displaying people or projects
* *
* Features: * Features:
* - Responsive grid layout for people/projects * - Responsive grid layout for people/projects
* - Special entity integration (You, Unnamed) * - Special entity integration (You, Unnamed)
@ -100,7 +95,7 @@ import { PlanData } from "../interfaces/records";
ProjectCard, ProjectCard,
SpecialEntityCard, SpecialEntityCard,
ShowAllCard, ShowAllCard,
} },
}) })
export default class EntityGrid extends Vue { export default class EntityGrid extends Vue {
/** Type of entities to display */ /** Type of entities to display */
@ -152,7 +147,7 @@ export default class EntityGrid extends Vue {
*/ */
get gridClasses(): string { get gridClasses(): string {
const baseClasses = "grid gap-x-2 gap-y-4 text-center mb-4"; const baseClasses = "grid gap-x-2 gap-y-4 text-center mb-4";
if (this.entityType === "projects") { if (this.entityType === "projects") {
return `${baseClasses} grid-cols-3 md:grid-cols-4`; return `${baseClasses} grid-cols-3 md:grid-cols-4`;
} else { } else {
@ -243,7 +238,11 @@ export default class EntityGrid extends Vue {
/** /**
* Handle special entity selection from SpecialEntityCard * Handle special entity selection from SpecialEntityCard
*/ */
handleEntitySelected(event: { type: string; entityType: string; data: any }): void { handleEntitySelected(event: {
type: string;
entityType: string;
data: any;
}): void {
this.emitEntitySelected({ this.emitEntitySelected({
type: "special", type: "special",
entityType: event.entityType, entityType: event.entityType,
@ -262,4 +261,4 @@ export default class EntityGrid extends Vue {
<style scoped> <style scoped>
/* Grid-specific styles if needed */ /* Grid-specific styles if needed */
</style> </style>

17
src/components/EntitySelectionStep.vue

@ -1,11 +1,6 @@
/** /** * EntitySelectionStep.vue - Entity selection step component * * Extracted
* EntitySelectionStep.vue - Entity selection step component from GiftedDialog.vue to handle the complete step 1 * entity selection interface
* with dynamic labeling and grid display. * * @author Matthew Raymer */
* Extracted from GiftedDialog.vue to handle the complete step 1
* entity selection interface with dynamic labeling and grid display.
*
* @author Matthew Raymer
*/
<template> <template>
<div id="sectionGiftedGiver"> <div id="sectionGiftedGiver">
<label class="block font-bold mb-4"> <label class="block font-bold mb-4">
@ -53,7 +48,7 @@ interface EntitySelectionEvent {
/** /**
* EntitySelectionStep - Complete step 1 entity selection interface * EntitySelectionStep - Complete step 1 entity selection interface
* *
* Features: * Features:
* - Dynamic step labeling based on context * - Dynamic step labeling based on context
* - EntityGrid integration for unified entity display * - EntityGrid integration for unified entity display
@ -66,7 +61,7 @@ interface EntitySelectionEvent {
@Component({ @Component({
components: { components: {
EntityGrid, EntityGrid,
} },
}) })
export default class EntitySelectionStep extends Vue { export default class EntitySelectionStep extends Vue {
/** Type of step: 'giver' or 'recipient' */ /** Type of step: 'giver' or 'recipient' */
@ -244,4 +239,4 @@ export default class EntitySelectionStep extends Vue {
<style scoped> <style scoped>
/* Component-specific styles if needed */ /* Component-specific styles if needed */
</style> </style>

23
src/components/EntitySummaryButton.vue

@ -1,11 +1,6 @@
/** /** * EntitySummaryButton.vue - Displays selected entity with edit capability *
* EntitySummaryButton.vue - Displays selected entity with edit capability * Extracted from GiftedDialog.vue to handle entity summary display * in the gift
* details step with edit functionality. * * @author Matthew Raymer */
* Extracted from GiftedDialog.vue to handle entity summary display
* in the gift details step with edit functionality.
*
* @author Matthew Raymer
*/
<template> <template>
<component <component
:is="editable ? 'button' : 'div'" :is="editable ? 'button' : 'div'"
@ -49,9 +44,9 @@
<!-- Edit/Lock Icon --> <!-- Edit/Lock Icon -->
<p class="ms-auto text-sm pe-1" :class="iconClasses"> <p class="ms-auto text-sm pe-1" :class="iconClasses">
<font-awesome <font-awesome
:icon="editable ? 'pen' : 'lock'" :icon="editable ? 'pen' : 'lock'"
:title="editable ? 'Change' : 'Can\'t be changed'" :title="editable ? 'Change' : 'Can\'t be changed'"
/> />
</p> </p>
</component> </component>
@ -75,7 +70,7 @@ interface EntityData {
/** /**
* EntitySummaryButton - Displays selected entity with optional edit capability * EntitySummaryButton - Displays selected entity with optional edit capability
* *
* Features: * Features:
* - Shows entity avatar (person or project) * - Shows entity avatar (person or project)
* - Displays entity name and role label * - Displays entity name and role label
@ -87,7 +82,7 @@ interface EntityData {
components: { components: {
EntityIcon, EntityIcon,
ProjectIcon, ProjectIcon,
} },
}) })
export default class EntitySummaryButton extends Vue { export default class EntitySummaryButton extends Vue {
/** Entity data to display */ /** Entity data to display */
@ -147,4 +142,4 @@ button:hover {
div { div {
cursor: default; cursor: default;
} }
</style> </style>

50
src/components/GiftDetailsStep.vue

@ -1,11 +1,6 @@
/** /** * GiftDetailsStep.vue - Gift details step component * * Extracted from
* GiftDetailsStep.vue - Gift details step component GiftedDialog.vue to handle the complete step 2 * gift details form interface
* with entity summaries and validation. * * @author Matthew Raymer */
* Extracted from GiftedDialog.vue to handle the complete step 2
* gift details form interface with entity summaries and validation.
*
* @author Matthew Raymer
*/
<template> <template>
<div id="sectionGiftedGift"> <div id="sectionGiftedGift">
<!-- Entity Summary Buttons --> <!-- Entity Summary Buttons -->
@ -79,8 +74,8 @@
</p> </p>
<!-- Conflict Warning --> <!-- Conflict Warning -->
<div <div
v-if="hasConflict" v-if="hasConflict"
class="mb-4 p-3 bg-red-50 border border-red-200 rounded-md" class="mb-4 p-3 bg-red-50 border border-red-200 rounded-md"
> >
<p class="text-red-700 text-sm text-center"> <p class="text-red-700 text-sm text-center">
@ -126,7 +121,7 @@ interface EntityData {
/** /**
* GiftDetailsStep - Complete step 2 gift details form interface * GiftDetailsStep - Complete step 2 gift details form interface
* *
* Features: * Features:
* - Entity summary display with edit capability * - Entity summary display with edit capability
* - Gift description input with placeholder support * - Gift description input with placeholder support
@ -141,7 +136,7 @@ interface EntityData {
components: { components: {
EntitySummaryButton, EntitySummaryButton,
AmountInput, AmountInput,
} },
}) })
export default class GiftDetailsStep extends Vue { export default class GiftDetailsStep extends Vue {
/** Giver entity data */ /** Giver entity data */
@ -232,8 +227,8 @@ export default class GiftDetailsStep extends Vue {
* Computed label for giver entity * Computed label for giver entity
*/ */
get giverLabel(): string { get giverLabel(): string {
return this.giverEntityType === "project" return this.giverEntityType === "project"
? "Benefited from:" ? "Benefited from:"
: "Received from:"; : "Received from:";
} }
@ -279,15 +274,20 @@ export default class GiftDetailsStep extends Vue {
query: { query: {
amountInput: this.localAmount.toString(), amountInput: this.localAmount.toString(),
description: this.localDescription, description: this.localDescription,
giverDid: this.giverEntityType === "person" ? this.giver?.did : undefined, giverDid:
this.giverEntityType === "person" ? this.giver?.did : undefined,
giverName: this.giver?.name, giverName: this.giver?.name,
offerId: this.offerId, offerId: this.offerId,
fulfillsProjectId: this.giverEntityType === "person" && this.recipientEntityType === "project" fulfillsProjectId:
? this.toProjectId this.giverEntityType === "person" &&
: undefined, this.recipientEntityType === "project"
providerProjectId: this.giverEntityType === "project" && this.recipientEntityType === "person" ? this.toProjectId
? this.giver?.handleId : undefined,
: this.fromProjectId, providerProjectId:
this.giverEntityType === "project" &&
this.recipientEntityType === "person"
? this.giver?.handleId
: this.fromProjectId,
recipientDid: this.receiver?.did, recipientDid: this.receiver?.did,
recipientName: this.receiver?.name, recipientName: this.receiver?.name,
unitCode: this.localUnitCode, unitCode: this.localUnitCode,
@ -306,6 +306,9 @@ export default class GiftDetailsStep extends Vue {
* Handle amount input changes * Handle amount input changes
*/ */
handleAmountChange(newAmount: number): void { handleAmountChange(newAmount: number): void {
console.log(
`[GiftDetailsStep] handleAmountChange() called - oldAmount: ${this.localAmount}, newAmount: ${newAmount}`,
);
this.localAmount = newAmount; this.localAmount = newAmount;
this.emitUpdateAmount(newAmount); this.emitUpdateAmount(newAmount);
} }
@ -373,6 +376,9 @@ export default class GiftDetailsStep extends Vue {
@Emit("update:amount") @Emit("update:amount")
emitUpdateAmount(amount: number): number { emitUpdateAmount(amount: number): number {
console.log(
`[GiftDetailsStep] emitUpdateAmount() - emitting amount: ${amount}`,
);
return amount; return amount;
} }
@ -405,4 +411,4 @@ export default class GiftDetailsStep extends Vue {
<style scoped> <style scoped>
/* Component-specific styles if needed */ /* Component-specific styles if needed */
</style> </style>

80
src/components/GiftedDialog.vue

@ -39,7 +39,7 @@
:from-project-id="fromProjectId" :from-project-id="fromProjectId"
:to-project-id="toProjectId" :to-project-id="toProjectId"
@update:description="description = $event" @update:description="description = $event"
@update:amount="amountInput = $event.toString()" @update:amount="handleAmountUpdate"
@update:unit-code="unitCode = $event" @update:unit-code="unitCode = $event"
@edit-entity="handleEditEntity" @edit-entity="handleEditEntity"
@explain-data="explainData" @explain-data="explainData"
@ -138,25 +138,35 @@ export default class GiftedDialog extends Vue {
// Computed property to check if current selection would create a conflict // Computed property to check if current selection would create a conflict
get hasPersonConflict() { get hasPersonConflict() {
// Only check for conflicts when both entities are persons // Only check for conflicts when both entities are persons
if (this.giverEntityType !== "person" || this.recipientEntityType !== "person") { if (
this.giverEntityType !== "person" ||
this.recipientEntityType !== "person"
) {
return false; return false;
} }
// Check if giver and recipient are the same person // Check if giver and recipient are the same person
if (this.giver?.did && this.receiver?.did && this.giver.did === this.receiver.did) { if (
this.giver?.did &&
this.receiver?.did &&
this.giver.did === this.receiver.did
) {
return true; return true;
} }
return false; return false;
} }
// Computed property to check if a contact would create a conflict when selected // Computed property to check if a contact would create a conflict when selected
wouldCreateConflict(contactDid: string) { wouldCreateConflict(contactDid: string) {
// Only check for conflicts when both entities are persons // Only check for conflicts when both entities are persons
if (this.giverEntityType !== "person" || this.recipientEntityType !== "person") { if (
this.giverEntityType !== "person" ||
this.recipientEntityType !== "person"
) {
return false; return false;
} }
if (this.stepType === "giver") { if (this.stepType === "giver") {
// If selecting as giver, check if it conflicts with current recipient // If selecting as giver, check if it conflicts with current recipient
return this.receiver?.did === contactDid; return this.receiver?.did === contactDid;
@ -164,7 +174,7 @@ export default class GiftedDialog extends Vue {
// If selecting as recipient, check if it conflicts with current giver // If selecting as recipient, check if it conflicts with current giver
return this.giver?.did === contactDid; return this.giver?.did === contactDid;
} }
return false; return false;
} }
@ -348,7 +358,7 @@ export default class GiftedDialog extends Vue {
); );
return; return;
} }
// Check for person conflict // Check for person conflict
if (this.hasPersonConflict) { if (this.hasPersonConflict) {
this.$notify( this.$notify(
@ -407,13 +417,19 @@ export default class GiftedDialog extends Vue {
let fulfillsProjectHandleId: string | undefined; let fulfillsProjectHandleId: string | undefined;
let providerPlanHandleId: string | undefined; let providerPlanHandleId: string | undefined;
if (this.giverEntityType === "project" && this.recipientEntityType === "person") { if (
this.giverEntityType === "project" &&
this.recipientEntityType === "person"
) {
// Project-to-person gift // Project-to-person gift
fromDid = undefined; // No person giver fromDid = undefined; // No person giver
toDid = recipientDid as string; // Person recipient toDid = recipientDid as string; // Person recipient
fulfillsProjectHandleId = undefined; // No project recipient fulfillsProjectHandleId = undefined; // No project recipient
providerPlanHandleId = this.giver?.handleId; // Project giver providerPlanHandleId = this.giver?.handleId; // Project giver
} else if (this.giverEntityType === "person" && this.recipientEntityType === "project") { } else if (
this.giverEntityType === "person" &&
this.recipientEntityType === "project"
) {
// Person-to-project gift // Person-to-project gift
fromDid = giverDid as string; // Person giver fromDid = giverDid as string; // Person giver
toDid = undefined; // No person recipient toDid = undefined; // No person recipient
@ -611,12 +627,16 @@ export default class GiftedDialog extends Vue {
giverDid: this.giverEntityType === "person" ? this.giver?.did : undefined, giverDid: this.giverEntityType === "person" ? this.giver?.did : undefined,
giverName: this.giver?.name, giverName: this.giver?.name,
offerId: this.offerId, offerId: this.offerId,
fulfillsProjectId: this.giverEntityType === "person" && this.recipientEntityType === "project" fulfillsProjectId:
? this.toProjectId this.giverEntityType === "person" &&
: undefined, this.recipientEntityType === "project"
providerProjectId: this.giverEntityType === "project" && this.recipientEntityType === "person" ? this.toProjectId
? this.giver?.handleId : undefined,
: this.fromProjectId, providerProjectId:
this.giverEntityType === "project" &&
this.recipientEntityType === "person"
? this.giver?.handleId
: this.fromProjectId,
recipientDid: this.receiver?.did, recipientDid: this.receiver?.did,
recipientName: this.receiver?.name, recipientName: this.receiver?.name,
unitCode: this.unitCode, unitCode: this.unitCode,
@ -629,17 +649,20 @@ export default class GiftedDialog extends Vue {
* Handle entity selection from EntitySelectionStep * Handle entity selection from EntitySelectionStep
* @param entity - The selected entity (person or project) * @param entity - The selected entity (person or project)
*/ */
handleEntitySelected(entity: { type: 'person' | 'project', data: Contact | PlanData }) { handleEntitySelected(entity: {
if (entity.type === 'person') { type: "person" | "project";
data: Contact | PlanData;
}) {
if (entity.type === "person") {
const contact = entity.data as Contact; const contact = entity.data as Contact;
if (this.stepType === 'giver') { if (this.stepType === "giver") {
this.selectGiver(contact); this.selectGiver(contact);
} else { } else {
this.selectRecipient(contact); this.selectRecipient(contact);
} }
} else { } else {
const project = entity.data as PlanData; const project = entity.data as PlanData;
if (this.stepType === 'giver') { if (this.stepType === "giver") {
this.selectProject(project); this.selectProject(project);
} else { } else {
this.selectRecipientProject(project); this.selectRecipientProject(project);
@ -651,7 +674,7 @@ export default class GiftedDialog extends Vue {
* Handle edit entity request from GiftDetailsStep * Handle edit entity request from GiftDetailsStep
* @param entityType - 'giver' or 'recipient' * @param entityType - 'giver' or 'recipient'
*/ */
handleEditEntity(entityType: 'giver' | 'recipient') { handleEditEntity(entityType: "giver" | "recipient") {
this.goBackToStep1(entityType); this.goBackToStep1(entityType);
} }
@ -661,6 +684,19 @@ export default class GiftedDialog extends Vue {
handleSubmit() { handleSubmit() {
this.confirm(); this.confirm();
} }
/**
* Handle amount update from GiftDetailsStep
*/
handleAmountUpdate(newAmount: number) {
console.log(
`[GiftedDialog] handleAmountUpdate() called - oldAmount: ${this.amountInput}, newAmount: ${newAmount}`,
);
this.amountInput = newAmount.toString();
console.log(
`[GiftedDialog] handleAmountUpdate() - amountInput updated to: ${this.amountInput}`,
);
}
} }
</script> </script>

40
src/components/PersonCard.vue

@ -1,16 +1,8 @@
/** /** * PersonCard.vue - Individual person display component * * Extracted from
* PersonCard.vue - Individual person display component GiftedDialog.vue to handle person entity display * with selection states and
* conflict detection. * * @author Matthew Raymer */
* Extracted from GiftedDialog.vue to handle person entity display
* with selection states and conflict detection.
*
* @author Matthew Raymer
*/
<template> <template>
<li <li :class="cardClasses" @click="handleClick">
:class="cardClasses"
@click="handleClick"
>
<div class="relative w-fit mx-auto"> <div class="relative w-fit mx-auto">
<EntityIcon <EntityIcon
v-if="person.did" v-if="person.did"
@ -22,21 +14,18 @@
icon="circle-question" icon="circle-question"
class="text-slate-400 text-5xl mb-1" class="text-slate-400 text-5xl mb-1"
/> />
<!-- Time icon overlay for contacts --> <!-- Time icon overlay for contacts -->
<div <div
v-if="person.did && showTimeIcon" v-if="person.did && showTimeIcon"
class="rounded-full bg-slate-400 absolute bottom-0 right-0 p-1 translate-x-1/3" class="rounded-full bg-slate-400 absolute bottom-0 right-0 p-1 translate-x-1/3"
> >
<font-awesome <font-awesome icon="clock" class="block text-white text-xs w-[1em]" />
icon="clock"
class="block text-white text-xs w-[1em]"
/>
</div> </div>
</div> </div>
<h3 :class="nameClasses"> <h3 :class="nameClasses">
{{ person.name || person.did || 'Unnamed' }} {{ person.name || person.did || "Unnamed" }}
</h3> </h3>
</li> </li>
</template> </template>
@ -48,7 +37,7 @@ import { Contact } from "../db/tables/contacts";
/** /**
* PersonCard - Individual person display with selection capability * PersonCard - Individual person display with selection capability
* *
* Features: * Features:
* - Person avatar using EntityIcon * - Person avatar using EntityIcon
* - Selection states (selectable, conflicted, disabled) * - Selection states (selectable, conflicted, disabled)
@ -59,7 +48,7 @@ import { Contact } from "../db/tables/contacts";
@Component({ @Component({
components: { components: {
EntityIcon, EntityIcon,
} },
}) })
export default class PersonCard extends Vue { export default class PersonCard extends Vue {
/** Contact data to display */ /** Contact data to display */
@ -92,12 +81,13 @@ export default class PersonCard extends Vue {
* Computed CSS classes for the person name * Computed CSS classes for the person name
*/ */
get nameClasses(): string { get nameClasses(): string {
const baseClasses = "text-xs font-medium text-ellipsis whitespace-nowrap overflow-hidden"; const baseClasses =
"text-xs font-medium text-ellipsis whitespace-nowrap overflow-hidden";
if (this.conflicted) { if (this.conflicted) {
return `${baseClasses} text-slate-400`; return `${baseClasses} text-slate-400`;
} }
return baseClasses; return baseClasses;
} }
@ -121,4 +111,4 @@ export default class PersonCard extends Vue {
<style scoped> <style scoped>
/* Component-specific styles if needed */ /* Component-specific styles if needed */
</style> </style>

32
src/components/ProjectCard.vue

@ -1,16 +1,8 @@
/** /** * ProjectCard.vue - Individual project display component * * Extracted from
* ProjectCard.vue - Individual project display component GiftedDialog.vue to handle project entity display * with selection states and
* issuer information. * * @author Matthew Raymer */
* Extracted from GiftedDialog.vue to handle project entity display
* with selection states and issuer information.
*
* @author Matthew Raymer
*/
<template> <template>
<li <li class="cursor-pointer" @click="handleClick">
class="cursor-pointer"
@click="handleClick"
>
<div class="relative w-fit mx-auto"> <div class="relative w-fit mx-auto">
<ProjectIcon <ProjectIcon
:entity-id="project.handleId" :entity-id="project.handleId"
@ -19,11 +11,13 @@
class="!size-[3rem] mx-auto border border-slate-300 bg-white overflow-hidden rounded-full mb-1" class="!size-[3rem] mx-auto border border-slate-300 bg-white overflow-hidden rounded-full mb-1"
/> />
</div> </div>
<h3 class="text-xs font-medium text-ellipsis whitespace-nowrap overflow-hidden"> <h3
class="text-xs font-medium text-ellipsis whitespace-nowrap overflow-hidden"
>
{{ project.name }} {{ project.name }}
</h3> </h3>
<div class="text-xs text-slate-500 truncate"> <div class="text-xs text-slate-500 truncate">
<font-awesome icon="user" class="fa-fw text-slate-400" /> <font-awesome icon="user" class="fa-fw text-slate-400" />
{{ issuerDisplayName }} {{ issuerDisplayName }}
@ -40,7 +34,7 @@ import { didInfo } from "../libs/endorserServer";
/** /**
* ProjectCard - Displays a project entity with selection capability * ProjectCard - Displays a project entity with selection capability
* *
* Features: * Features:
* - Shows project icon using ProjectIcon * - Shows project icon using ProjectIcon
* - Displays project name and issuer information * - Displays project name and issuer information
@ -50,7 +44,7 @@ import { didInfo } from "../libs/endorserServer";
@Component({ @Component({
components: { components: {
ProjectIcon, ProjectIcon,
} },
}) })
export default class ProjectCard extends Vue { export default class ProjectCard extends Vue {
/** Project entity to display */ /** Project entity to display */
@ -77,7 +71,7 @@ export default class ProjectCard extends Vue {
this.project.issuerDid, this.project.issuerDid,
this.activeDid, this.activeDid,
this.allMyDids, this.allMyDids,
this.allContacts this.allContacts,
); );
} }
@ -99,4 +93,4 @@ export default class ProjectCard extends Vue {
<style scoped> <style scoped>
/* Component-specific styles if needed */ /* Component-specific styles if needed */
</style> </style>

29
src/components/ShowAllCard.vue

@ -1,22 +1,13 @@
/** /** * ShowAllCard.vue - Show All navigation card component * * Extracted from
* ShowAllCard.vue - Show All navigation card component GiftedDialog.vue to handle "Show All" navigation * for both people and projects
* entity types. * * @author Matthew Raymer */
* Extracted from GiftedDialog.vue to handle "Show All" navigation
* for both people and projects entity types.
*
* @author Matthew Raymer
*/
<template> <template>
<li class="cursor-pointer"> <li class="cursor-pointer">
<router-link <router-link :to="navigationRoute" class="block text-center">
:to="navigationRoute" <font-awesome icon="circle-right" class="text-blue-500 text-5xl mb-1" />
class="block text-center" <h3
> class="text-xs text-slate-500 font-medium italic text-ellipsis whitespace-nowrap overflow-hidden"
<font-awesome >
icon="circle-right"
class="text-blue-500 text-5xl mb-1"
/>
<h3 class="text-xs text-slate-500 font-medium italic text-ellipsis whitespace-nowrap overflow-hidden">
Show All Show All
</h3> </h3>
</router-link> </router-link>
@ -29,7 +20,7 @@ import { RouteLocationRaw } from "vue-router";
/** /**
* ShowAllCard - Displays "Show All" navigation for entity grids * ShowAllCard - Displays "Show All" navigation for entity grids
* *
* Features: * Features:
* - Provides navigation to full entity listings * - Provides navigation to full entity listings
* - Supports different routes based on entity type * - Supports different routes based on entity type
@ -72,4 +63,4 @@ a:hover .fa-circle-right {
transform: scale(1.1); transform: scale(1.1);
transition: transform 0.2s ease; transition: transform 0.2s ease;
} }
</style> </style>

42
src/components/SpecialEntityCard.vue

@ -1,20 +1,9 @@
/** /** * SpecialEntityCard.vue - Special entity display component * * Extracted
* SpecialEntityCard.vue - Special entity display component from GiftedDialog.vue to handle special entities like "You" * and "Unnamed" with
* conflict detection and selection capability. * * @author Matthew Raymer */
* Extracted from GiftedDialog.vue to handle special entities like "You"
* and "Unnamed" with conflict detection and selection capability.
*
* @author Matthew Raymer
*/
<template> <template>
<li <li :class="cardClasses" @click="handleClick">
:class="cardClasses" <font-awesome :icon="icon" :class="iconClasses" />
@click="handleClick"
>
<font-awesome
:icon="icon"
:class="iconClasses"
/>
<h3 :class="nameClasses"> <h3 :class="nameClasses">
{{ label }} {{ label }}
</h3> </h3>
@ -27,7 +16,7 @@ import { Emit } from "vue-facing-decorator";
/** /**
* SpecialEntityCard - Displays special entities with selection capability * SpecialEntityCard - Displays special entities with selection capability
* *
* Features: * Features:
* - Displays special entities like "You" and "Unnamed" * - Displays special entities like "You" and "Unnamed"
* - Shows appropriate FontAwesome icons * - Shows appropriate FontAwesome icons
@ -36,7 +25,7 @@ import { Emit } from "vue-facing-decorator";
* - Configurable styling based on entity type * - Configurable styling based on entity type
*/ */
@Component({ @Component({
emits: ['entity-selected'] emits: ["entity-selected"],
}) })
export default class SpecialEntityCard extends Vue { export default class SpecialEntityCard extends Vue {
/** Type of special entity */ /** Type of special entity */
@ -68,11 +57,11 @@ export default class SpecialEntityCard extends Vue {
*/ */
get cardClasses(): string { get cardClasses(): string {
const baseClasses = "block"; const baseClasses = "block";
if (!this.selectable || this.conflicted) { if (!this.selectable || this.conflicted) {
return `${baseClasses} cursor-not-allowed opacity-50`; return `${baseClasses} cursor-not-allowed opacity-50`;
} }
return `${baseClasses} cursor-pointer`; return `${baseClasses} cursor-pointer`;
} }
@ -81,11 +70,11 @@ export default class SpecialEntityCard extends Vue {
*/ */
get iconClasses(): string { get iconClasses(): string {
const baseClasses = "text-5xl mb-1"; const baseClasses = "text-5xl mb-1";
if (this.conflicted) { if (this.conflicted) {
return `${baseClasses} text-slate-400`; return `${baseClasses} text-slate-400`;
} }
// Different colors for different entity types // Different colors for different entity types
switch (this.entityType) { switch (this.entityType) {
case "you": case "you":
@ -101,12 +90,13 @@ export default class SpecialEntityCard extends Vue {
* Computed CSS classes for the entity name/label * Computed CSS classes for the entity name/label
*/ */
get nameClasses(): string { get nameClasses(): string {
const baseClasses = "text-xs font-medium text-ellipsis whitespace-nowrap overflow-hidden"; const baseClasses =
"text-xs font-medium text-ellipsis whitespace-nowrap overflow-hidden";
if (this.conflicted) { if (this.conflicted) {
return `${baseClasses} text-slate-400`; return `${baseClasses} text-slate-400`;
} }
// Different colors for different entity types // Different colors for different entity types
switch (this.entityType) { switch (this.entityType) {
case "you": case "you":
@ -142,4 +132,4 @@ export default class SpecialEntityCard extends Vue {
<style scoped> <style scoped>
/* Component-specific styles if needed */ /* Component-specific styles if needed */
</style> </style>

2
vite.config.mts

@ -22,7 +22,7 @@ export default defineConfig({
url: 'url/', url: 'url/',
zlib: 'browserify-zlib', zlib: 'browserify-zlib',
path: 'path-browserify', path: 'path-browserify',
fs: false, fs: path.resolve(__dirname, 'src/utils/node-modules/fs.js'),
tty: 'tty-browserify', tty: 'tty-browserify',
net: false, net: false,
dns: false, dns: false,

Loading…
Cancel
Save