forked from trent_larson/crowd-funder-for-time-pwa
- Convert AmountInput from @Emit("update:value") to onUpdateValue function prop
- Update GiftDetailsStep to use new function prop interface for amount handling
AmountInput now provides better parent control over validation and updates
436 lines
11 KiB
Vue
436 lines
11 KiB
Vue
/** * GiftDetailsStep.vue - Gift details step component * * Extracted from
|
|
GiftedDialog.vue to handle the complete step 2 * gift details form interface
|
|
with entity summaries and validation. * * Features: * - Entity summary display
|
|
with edit capability * - Gift description input with placeholder support * -
|
|
Amount input with increment/decrement controls * - Unit code selection (HUR,
|
|
USD, BTC, etc.) * - Photo & more options navigation * - Conflict detection and
|
|
warning display * - Form validation and submission * - Cancel functionality * -
|
|
Template streamlined with computed CSS properties * * @author Matthew Raymer */
|
|
<template>
|
|
<div id="sectionGiftedGift">
|
|
<!-- Entity Summary Buttons -->
|
|
<div class="grid grid-cols-2 gap-2 mb-4">
|
|
<!-- Giver Button -->
|
|
<EntitySummaryButton
|
|
:entity="giver"
|
|
:entity-type="giverEntityType"
|
|
:label="giverLabel"
|
|
:editable="canEditGiver"
|
|
@edit-requested="handleEditGiver"
|
|
/>
|
|
|
|
<!-- Recipient Button -->
|
|
<EntitySummaryButton
|
|
:entity="receiver"
|
|
:entity-type="recipientEntityType"
|
|
:label="recipientLabel"
|
|
:editable="canEditRecipient"
|
|
@edit-requested="handleEditRecipient"
|
|
/>
|
|
</div>
|
|
|
|
<!-- Gift Description Input -->
|
|
<input
|
|
v-model="localDescription"
|
|
type="text"
|
|
class="block w-full rounded border border-slate-400 px-3 py-2 mb-4 placeholder:italic"
|
|
:placeholder="prompt || 'What was given?'"
|
|
@input="handleDescriptionChange"
|
|
/>
|
|
|
|
<!-- Amount Input and Unit Selection -->
|
|
<div class="flex mb-4">
|
|
<AmountInput
|
|
:value="localAmount"
|
|
:min="0"
|
|
input-id="inputGivenAmount"
|
|
:on-update-value="handleAmountChange"
|
|
/>
|
|
|
|
<select
|
|
v-model="localUnitCode"
|
|
class="flex-1 rounded border border-slate-400 ms-2 px-3 py-2"
|
|
@change="handleUnitCodeChange"
|
|
>
|
|
<option value="HUR">Hours</option>
|
|
<option value="USD">US $</option>
|
|
<option value="BTC">BTC</option>
|
|
<option value="BX">BX</option>
|
|
<option value="ETH">ETH</option>
|
|
</select>
|
|
</div>
|
|
|
|
<!-- Photo & More Options Link -->
|
|
<router-link :to="photoOptionsRoute" :class="photoOptionsClasses">
|
|
Photo & more options…
|
|
</router-link>
|
|
|
|
<!-- Sign & Send Info -->
|
|
<p class="text-center text-sm mb-4">
|
|
<b class="font-medium">Sign & Send</b> to publish to the world
|
|
<font-awesome
|
|
icon="circle-info"
|
|
class="fa-fw text-blue-500 text-base cursor-pointer"
|
|
@click="handleExplainData"
|
|
/>
|
|
</p>
|
|
|
|
<!-- Conflict Warning -->
|
|
<div
|
|
v-if="hasConflict"
|
|
class="mb-4 p-3 bg-red-50 border border-red-200 rounded-md"
|
|
>
|
|
<p class="text-red-700 text-sm text-center">
|
|
<font-awesome icon="exclamation-triangle" class="fa-fw mr-1" />
|
|
Cannot record: Same person selected as both giver and recipient
|
|
</p>
|
|
</div>
|
|
|
|
<!-- Action Buttons -->
|
|
<div class="grid grid-cols-1 sm:grid-cols-2 gap-2">
|
|
<button
|
|
:disabled="hasConflict"
|
|
:class="submitButtonClasses"
|
|
@click="handleSubmit"
|
|
>
|
|
Sign & Send
|
|
</button>
|
|
<button :class="cancelButtonClasses" @click="handleCancel">Cancel</button>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script lang="ts">
|
|
import { Component, Prop, Vue, Watch, Emit } from "vue-facing-decorator";
|
|
import EntitySummaryButton from "./EntitySummaryButton.vue";
|
|
import AmountInput from "./AmountInput.vue";
|
|
import { RouteLocationRaw } from "vue-router";
|
|
import { logger } from "@/utils/logger";
|
|
|
|
/**
|
|
* Entity data interface for giver/receiver
|
|
*/
|
|
interface EntityData {
|
|
did?: string;
|
|
handleId?: string;
|
|
name?: string;
|
|
image?: string;
|
|
}
|
|
|
|
/**
|
|
* GiftDetailsStep - Complete step 2 gift details form interface
|
|
*
|
|
* Features:
|
|
* - Entity summary display with edit capability
|
|
* - Gift description input with placeholder support
|
|
* - Amount input with increment/decrement controls
|
|
* - Unit code selection (HUR, USD, BTC, etc.)
|
|
* - Photo & more options navigation
|
|
* - Conflict detection and warning display
|
|
* - Form validation and submission
|
|
* - Cancel functionality
|
|
* - Template streamlined with computed CSS properties
|
|
*/
|
|
@Component({
|
|
components: {
|
|
EntitySummaryButton,
|
|
AmountInput,
|
|
},
|
|
})
|
|
export default class GiftDetailsStep extends Vue {
|
|
/** Giver entity data */
|
|
@Prop({ required: true })
|
|
giver!: EntityData | null;
|
|
|
|
/** Receiver entity data */
|
|
@Prop({ required: true })
|
|
receiver!: EntityData | null;
|
|
|
|
/** Type of giver entity: 'person' or 'project' */
|
|
@Prop({ required: true })
|
|
giverEntityType!: "person" | "project";
|
|
|
|
/** Type of recipient entity: 'person' or 'project' */
|
|
@Prop({ required: true })
|
|
recipientEntityType!: "person" | "project";
|
|
|
|
/** Gift description */
|
|
@Prop({ default: "" })
|
|
description!: string;
|
|
|
|
/** Gift amount */
|
|
@Prop({ default: 0 })
|
|
amount!: number;
|
|
|
|
/** Unit code (HUR, USD, etc.) */
|
|
@Prop({ default: "HUR" })
|
|
unitCode!: string;
|
|
|
|
/** Input placeholder text */
|
|
@Prop({ default: "" })
|
|
prompt!: string;
|
|
|
|
/** Whether this is from a project view */
|
|
@Prop({ default: false })
|
|
isFromProjectView!: boolean;
|
|
|
|
/** Whether there's a conflict between giver and receiver */
|
|
@Prop({ default: false })
|
|
hasConflict!: boolean;
|
|
|
|
/** Offer ID for context */
|
|
@Prop({ default: "" })
|
|
offerId!: string;
|
|
|
|
/** Project ID for context (giver) */
|
|
@Prop({ default: "" })
|
|
fromProjectId!: string;
|
|
|
|
/** Project ID for context (recipient) */
|
|
@Prop({ default: "" })
|
|
toProjectId!: string;
|
|
|
|
/** Local reactive copies of props for v-model */
|
|
private localDescription: string = "";
|
|
private localAmount: number = 0;
|
|
private localUnitCode: string = "HUR";
|
|
|
|
/**
|
|
* CSS classes for the photo & more options link
|
|
*/
|
|
get photoOptionsClasses(): string {
|
|
return "block w-full text-center text-md uppercase bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1.5 py-2 rounded-lg mb-4";
|
|
}
|
|
|
|
/**
|
|
* CSS classes for the cancel button
|
|
*/
|
|
get cancelButtonClasses(): string {
|
|
return "block w-full text-center text-md uppercase bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1.5 py-2 rounded-lg";
|
|
}
|
|
|
|
/**
|
|
* Initialize local values from props
|
|
*/
|
|
mounted(): void {
|
|
this.localDescription = this.description;
|
|
this.localAmount = this.amount;
|
|
this.localUnitCode = this.unitCode;
|
|
}
|
|
|
|
/**
|
|
* Watch for external prop changes
|
|
*/
|
|
@Watch("description")
|
|
onDescriptionChange(newValue: string): void {
|
|
this.localDescription = newValue;
|
|
}
|
|
|
|
@Watch("amount")
|
|
onAmountChange(newValue: number): void {
|
|
this.localAmount = newValue;
|
|
}
|
|
|
|
@Watch("unitCode")
|
|
onUnitCodeChange(newValue: string): void {
|
|
this.localUnitCode = newValue;
|
|
}
|
|
|
|
/**
|
|
* Computed label for giver entity
|
|
*/
|
|
get giverLabel(): string {
|
|
return this.giverEntityType === "project"
|
|
? "Benefited from:"
|
|
: "Received from:";
|
|
}
|
|
|
|
/**
|
|
* Computed label for recipient entity
|
|
*/
|
|
get recipientLabel(): string {
|
|
return this.recipientEntityType === "project"
|
|
? "Given to project:"
|
|
: "Given to:";
|
|
}
|
|
|
|
/**
|
|
* Whether the giver can be edited
|
|
*/
|
|
get canEditGiver(): boolean {
|
|
return !(this.isFromProjectView && this.giverEntityType === "project");
|
|
}
|
|
|
|
/**
|
|
* Whether the recipient can be edited
|
|
*/
|
|
get canEditRecipient(): boolean {
|
|
return this.recipientEntityType === "person";
|
|
}
|
|
|
|
/**
|
|
* Computed CSS classes for submit button
|
|
*/
|
|
get submitButtonClasses(): string {
|
|
if (this.hasConflict) {
|
|
return "block w-full text-center text-md uppercase font-bold bg-gradient-to-b from-slate-300 to-slate-500 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-slate-400 px-1.5 py-2 rounded-lg cursor-not-allowed";
|
|
}
|
|
return "block w-full text-center text-md uppercase font-bold bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1.5 py-2 rounded-lg";
|
|
}
|
|
|
|
/**
|
|
* Computed route for photo & more options
|
|
*/
|
|
get photoOptionsRoute(): RouteLocationRaw {
|
|
return {
|
|
name: "gifted-details",
|
|
query: {
|
|
amountInput: this.localAmount.toString(),
|
|
description: this.localDescription,
|
|
giverDid:
|
|
this.giverEntityType === "person" ? this.giver?.did : undefined,
|
|
giverName: this.giver?.name,
|
|
offerId: this.offerId,
|
|
fulfillsProjectId:
|
|
this.giverEntityType === "person" &&
|
|
this.recipientEntityType === "project"
|
|
? this.toProjectId
|
|
: undefined,
|
|
providerProjectId:
|
|
this.giverEntityType === "project" &&
|
|
this.recipientEntityType === "person"
|
|
? this.giver?.handleId
|
|
: this.fromProjectId,
|
|
recipientDid: this.receiver?.did,
|
|
recipientName: this.receiver?.name,
|
|
unitCode: this.localUnitCode,
|
|
},
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Handle description input changes
|
|
*/
|
|
handleDescriptionChange(): void {
|
|
this.emitUpdateDescription(this.localDescription);
|
|
}
|
|
|
|
/**
|
|
* Handle amount input changes
|
|
*/
|
|
handleAmountChange(newAmount: number): void {
|
|
logger.debug("[GiftDetailsStep] handleAmountChange() called", {
|
|
oldAmount: this.localAmount,
|
|
newAmount,
|
|
});
|
|
this.localAmount = newAmount;
|
|
this.emitUpdateAmount(newAmount);
|
|
}
|
|
|
|
/**
|
|
* Handle unit code selection changes
|
|
*/
|
|
handleUnitCodeChange(): void {
|
|
this.emitUpdateUnitCode(this.localUnitCode);
|
|
}
|
|
|
|
/**
|
|
* Handle giver edit request
|
|
*/
|
|
handleEditGiver(): void {
|
|
this.emitEditEntity({
|
|
entityType: "giver",
|
|
currentEntity: this.giver,
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Handle recipient edit request
|
|
*/
|
|
handleEditRecipient(): void {
|
|
this.emitEditEntity({
|
|
entityType: "recipient",
|
|
currentEntity: this.receiver,
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Handle explain data info click
|
|
*/
|
|
handleExplainData(): void {
|
|
this.emitExplainData();
|
|
}
|
|
|
|
/**
|
|
* Handle form submission
|
|
*/
|
|
handleSubmit(): void {
|
|
if (!this.hasConflict) {
|
|
this.emitSubmit({
|
|
description: this.localDescription,
|
|
amount: this.localAmount,
|
|
unitCode: this.localUnitCode,
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handle cancel button click
|
|
*/
|
|
handleCancel(): void {
|
|
this.emitCancel();
|
|
}
|
|
|
|
// Emit methods using @Emit decorator
|
|
|
|
@Emit("update:description")
|
|
emitUpdateDescription(description: string): string {
|
|
return description;
|
|
}
|
|
|
|
@Emit("update:amount")
|
|
emitUpdateAmount(amount: number): number {
|
|
logger.debug("[GiftDetailsStep] emitUpdateAmount() - emitting amount", {
|
|
amount,
|
|
});
|
|
return amount;
|
|
}
|
|
|
|
@Emit("update:unitCode")
|
|
emitUpdateUnitCode(unitCode: string): string {
|
|
return unitCode;
|
|
}
|
|
|
|
@Emit("edit-entity")
|
|
emitEditEntity(data: {
|
|
entityType: string;
|
|
currentEntity: EntityData | null;
|
|
}): { entityType: string; currentEntity: EntityData | null } {
|
|
return data;
|
|
}
|
|
|
|
@Emit("explain-data")
|
|
emitExplainData(): void {
|
|
// No return value needed
|
|
}
|
|
|
|
@Emit("submit")
|
|
emitSubmit(data: { description: string; amount: number; unitCode: string }): {
|
|
description: string;
|
|
amount: number;
|
|
unitCode: string;
|
|
} {
|
|
return data;
|
|
}
|
|
|
|
@Emit("cancel")
|
|
emitCancel(): void {
|
|
// No return value needed
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style scoped>
|
|
/* Component-specific styles if needed */
|
|
</style>
|