You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
408 lines
10 KiB
408 lines
10 KiB
/**
|
|
* 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.
|
|
*
|
|
* @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"
|
|
@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="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"
|
|
>
|
|
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="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"
|
|
@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";
|
|
|
|
/**
|
|
* 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
|
|
*/
|
|
@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";
|
|
|
|
/**
|
|
* 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 {
|
|
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 {
|
|
return amount;
|
|
}
|
|
|
|
@Emit("update:unitCode")
|
|
emitUpdateUnitCode(unitCode: string): string {
|
|
return unitCode;
|
|
}
|
|
|
|
@Emit("edit-entity")
|
|
emitEditEntity(data: any): any {
|
|
return data;
|
|
}
|
|
|
|
@Emit("explain-data")
|
|
emitExplainData(): void {
|
|
// No return value needed
|
|
}
|
|
|
|
@Emit("submit")
|
|
emitSubmit(data: any): any {
|
|
return data;
|
|
}
|
|
|
|
@Emit("cancel")
|
|
emitCancel(): void {
|
|
// No return value needed
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style scoped>
|
|
/* Component-specific styles if needed */
|
|
</style>
|