Merge pull request 'allow changing of both giver and receiver to projects or people' (#228) from giver-receiver-selectable into master
Reviewed-on: #228
This commit was merged in pull request #228.
This commit is contained in:
@@ -155,6 +155,7 @@ projects, and special entities with selection. * * @author Matthew Raymer */
|
|||||||
:active-did="activeDid"
|
:active-did="activeDid"
|
||||||
:all-my-dids="allMyDids"
|
:all-my-dids="allMyDids"
|
||||||
:all-contacts="allContacts"
|
:all-contacts="allContacts"
|
||||||
|
:conflicted="isProjectConflicted(project.handleId)"
|
||||||
:notify="notify"
|
:notify="notify"
|
||||||
:conflict-context="conflictContext"
|
:conflict-context="conflictContext"
|
||||||
@project-selected="handleProjectSelected"
|
@project-selected="handleProjectSelected"
|
||||||
@@ -175,6 +176,7 @@ projects, and special entities with selection. * * @author Matthew Raymer */
|
|||||||
:active-did="activeDid"
|
:active-did="activeDid"
|
||||||
:all-my-dids="allMyDids"
|
:all-my-dids="allMyDids"
|
||||||
:all-contacts="allContacts"
|
:all-contacts="allContacts"
|
||||||
|
:conflicted="isProjectConflicted(project.handleId)"
|
||||||
:notify="notify"
|
:notify="notify"
|
||||||
:conflict-context="conflictContext"
|
:conflict-context="conflictContext"
|
||||||
@project-selected="handleProjectSelected"
|
@project-selected="handleProjectSelected"
|
||||||
@@ -190,6 +192,7 @@ projects, and special entities with selection. * * @author Matthew Raymer */
|
|||||||
:active-did="activeDid"
|
:active-did="activeDid"
|
||||||
:all-my-dids="allMyDids"
|
:all-my-dids="allMyDids"
|
||||||
:all-contacts="allContacts"
|
:all-contacts="allContacts"
|
||||||
|
:conflicted="isProjectConflicted(project.handleId)"
|
||||||
:notify="notify"
|
:notify="notify"
|
||||||
:conflict-context="conflictContext"
|
:conflict-context="conflictContext"
|
||||||
@project-selected="handleProjectSelected"
|
@project-selected="handleProjectSelected"
|
||||||
@@ -556,6 +559,13 @@ export default class EntityGrid extends Vue {
|
|||||||
return this.conflictChecker(did);
|
return this.conflictChecker(did);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a project handleId is conflicted
|
||||||
|
*/
|
||||||
|
isProjectConflicted(handleId: string): boolean {
|
||||||
|
return this.conflictChecker(handleId);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle person selection from PersonCard
|
* Handle person selection from PersonCard
|
||||||
*/
|
*/
|
||||||
@@ -1037,6 +1047,50 @@ export default class EntityGrid extends Vue {
|
|||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Watch for changes in entityType to load projects when switching to projects
|
||||||
|
*/
|
||||||
|
@Watch("entityType")
|
||||||
|
async onEntityTypeChange(newType: "people" | "projects"): Promise<void> {
|
||||||
|
// Reset displayed count and clear search when switching types
|
||||||
|
this.displayedCount = INITIAL_BATCH_SIZE;
|
||||||
|
this.searchTerm = "";
|
||||||
|
this.filteredEntities = [];
|
||||||
|
this.searchBeforeId = undefined;
|
||||||
|
this.infiniteScrollReset?.();
|
||||||
|
|
||||||
|
// When switching to projects, load them if not provided via entities prop
|
||||||
|
if (newType === "projects" && !this.entities) {
|
||||||
|
// Ensure apiServer is loaded
|
||||||
|
if (!this.apiServer) {
|
||||||
|
const settings = await this.$accountSettings();
|
||||||
|
this.apiServer = settings.apiServer || "";
|
||||||
|
this.starredPlanHandleIds = settings.starredPlanHandleIds || [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load projects if we have an API server
|
||||||
|
if (this.apiServer && this.allProjects.length === 0) {
|
||||||
|
this.isLoadingProjects = true;
|
||||||
|
try {
|
||||||
|
await this.fetchProjects();
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(
|
||||||
|
"Error loading projects when switching to projects:",
|
||||||
|
error,
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
this.isLoadingProjects = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear project state when switching away from projects
|
||||||
|
if (newType === "people") {
|
||||||
|
this.allProjects = [];
|
||||||
|
this.loadBeforeId = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Watch for changes in search term to reset displayed count and pagination
|
* Watch for changes in search term to reset displayed count and pagination
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -8,9 +8,19 @@ notifications for conflicted entities * - Template streamlined with computed CSS
|
|||||||
properties * * @author Matthew Raymer */
|
properties * * @author Matthew Raymer */
|
||||||
<template>
|
<template>
|
||||||
<div id="sectionGiftedGiver">
|
<div id="sectionGiftedGiver">
|
||||||
<label class="block font-bold mb-4">
|
<label class="block font-bold mb-1">
|
||||||
{{ stepLabel }}
|
{{ stepLabel }}
|
||||||
</label>
|
</label>
|
||||||
|
<!-- Toggle link for entity type selection -->
|
||||||
|
<div class="text-right mb-4">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="text-sm text-blue-600 hover:text-blue-800 underline font-medium"
|
||||||
|
@click="handleToggleEntityType"
|
||||||
|
>
|
||||||
|
{{ toggleLinkText }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<EntityGrid
|
<EntityGrid
|
||||||
:entity-type="shouldShowProjects ? 'projects' : 'people'"
|
:entity-type="shouldShowProjects ? 'projects' : 'people'"
|
||||||
@@ -19,7 +29,6 @@ properties * * @author Matthew Raymer */
|
|||||||
:all-my-dids="allMyDids"
|
:all-my-dids="allMyDids"
|
||||||
:all-contacts="allContacts"
|
:all-contacts="allContacts"
|
||||||
:conflict-checker="conflictChecker"
|
:conflict-checker="conflictChecker"
|
||||||
:show-you-entity="shouldShowYouEntity"
|
|
||||||
:you-selectable="youSelectable"
|
:you-selectable="youSelectable"
|
||||||
:notify="notify"
|
:notify="notify"
|
||||||
:conflict-context="conflictContext"
|
:conflict-context="conflictContext"
|
||||||
@@ -90,10 +99,6 @@ export default class EntitySelectionStep extends Vue {
|
|||||||
@Prop({ default: false })
|
@Prop({ default: false })
|
||||||
showProjects!: boolean;
|
showProjects!: boolean;
|
||||||
|
|
||||||
/** Whether this is from a project view */
|
|
||||||
@Prop({ default: false })
|
|
||||||
isFromProjectView!: boolean;
|
|
||||||
|
|
||||||
/** Array of available projects (optional - EntityGrid loads internally if not provided) */
|
/** Array of available projects (optional - EntityGrid loads internally if not provided) */
|
||||||
@Prop({ required: false })
|
@Prop({ required: false })
|
||||||
projects?: PlanData[];
|
projects?: PlanData[];
|
||||||
@@ -160,15 +165,15 @@ export default class EntitySelectionStep extends Vue {
|
|||||||
*/
|
*/
|
||||||
get stepLabel(): string {
|
get stepLabel(): string {
|
||||||
if (this.stepType === "recipient") {
|
if (this.stepType === "recipient") {
|
||||||
return "Choose who received the gift:";
|
return "Choose who received the gift";
|
||||||
} else if (this.stepType === "giver") {
|
} else if (this.stepType === "giver") {
|
||||||
if (this.shouldShowProjects) {
|
if (this.shouldShowProjects) {
|
||||||
return "Choose a project benefitted from:";
|
return "Choose a project benefitted from";
|
||||||
} else {
|
} else {
|
||||||
return "Choose a person received from:";
|
return "Choose a person received from";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return "Choose entity:";
|
return "Choose entity";
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -195,16 +200,6 @@ export default class EntitySelectionStep extends Vue {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Whether to show the "You" entity
|
|
||||||
*/
|
|
||||||
get shouldShowYouEntity(): boolean {
|
|
||||||
return (
|
|
||||||
this.stepType === "recipient" ||
|
|
||||||
(this.stepType === "giver" && this.isFromProjectView)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether the "You" entity is selectable
|
* Whether the "You" entity is selectable
|
||||||
*/
|
*/
|
||||||
@@ -212,6 +207,17 @@ export default class EntitySelectionStep extends Vue {
|
|||||||
return !this.conflictChecker(this.activeDid);
|
return !this.conflictChecker(this.activeDid);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Text for the toggle link
|
||||||
|
*/
|
||||||
|
get toggleLinkText(): string {
|
||||||
|
if (this.shouldShowProjects) {
|
||||||
|
return "... or choose a person instead →";
|
||||||
|
} else {
|
||||||
|
return "... or choose a project instead →";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle entity selection from EntityGrid
|
* Handle entity selection from EntityGrid
|
||||||
*/
|
*/
|
||||||
@@ -222,6 +228,13 @@ export default class EntitySelectionStep extends Vue {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle toggle entity type button click
|
||||||
|
*/
|
||||||
|
handleToggleEntityType(): void {
|
||||||
|
this.emitToggleEntityType();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle cancel button click
|
* Handle cancel button click
|
||||||
*/
|
*/
|
||||||
@@ -242,6 +255,11 @@ export default class EntitySelectionStep extends Vue {
|
|||||||
emitCancel(): void {
|
emitCancel(): void {
|
||||||
// No return value needed
|
// No return value needed
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Emit("toggle-entity-type")
|
||||||
|
emitToggleEntityType(): void {
|
||||||
|
// No return value needed
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -172,10 +172,6 @@ export default class GiftDetailsStep extends Vue {
|
|||||||
@Prop({ default: "" })
|
@Prop({ default: "" })
|
||||||
prompt!: string;
|
prompt!: string;
|
||||||
|
|
||||||
/** Whether this is from a project view */
|
|
||||||
@Prop({ default: false })
|
|
||||||
isFromProjectView!: boolean;
|
|
||||||
|
|
||||||
/** Whether there's a conflict between giver and receiver */
|
/** Whether there's a conflict between giver and receiver */
|
||||||
@Prop({ default: false })
|
@Prop({ default: false })
|
||||||
hasConflict!: boolean;
|
hasConflict!: boolean;
|
||||||
@@ -192,6 +188,14 @@ export default class GiftDetailsStep extends Vue {
|
|||||||
@Prop({ default: "" })
|
@Prop({ default: "" })
|
||||||
toProjectId!: string;
|
toProjectId!: string;
|
||||||
|
|
||||||
|
/** Whether the giver is locked and cannot be edited */
|
||||||
|
@Prop({ default: false })
|
||||||
|
isGiverLocked!: boolean;
|
||||||
|
|
||||||
|
/** Whether the recipient is locked and cannot be edited */
|
||||||
|
@Prop({ default: false })
|
||||||
|
isRecipientLocked!: boolean;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Function prop for handling description updates
|
* Function prop for handling description updates
|
||||||
* Called when the description input changes, allowing parent to control validation
|
* Called when the description input changes, allowing parent to control validation
|
||||||
@@ -281,14 +285,15 @@ export default class GiftDetailsStep extends Vue {
|
|||||||
* Whether the giver can be edited
|
* Whether the giver can be edited
|
||||||
*/
|
*/
|
||||||
get canEditGiver(): boolean {
|
get canEditGiver(): boolean {
|
||||||
return !(this.isFromProjectView && this.giverEntityType === "project");
|
// If giver is locked via prop, it cannot be edited
|
||||||
|
return !this.isGiverLocked;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether the recipient can be edited
|
* Whether the recipient can be edited
|
||||||
*/
|
*/
|
||||||
get canEditRecipient(): boolean {
|
get canEditRecipient(): boolean {
|
||||||
return this.recipientEntityType === "person";
|
return !this.isRecipientLocked;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -3,18 +3,18 @@
|
|||||||
<div
|
<div
|
||||||
class="dialog"
|
class="dialog"
|
||||||
data-testid="gifted-dialog"
|
data-testid="gifted-dialog"
|
||||||
:data-recipient-entity-type="recipientEntityType"
|
:data-recipient-entity-type="currentRecipientEntityType"
|
||||||
>
|
>
|
||||||
<!-- Step 1: Entity Selection -->
|
<!-- Step 1: Entity Selection -->
|
||||||
<EntitySelectionStep
|
<EntitySelectionStep
|
||||||
v-show="firstStep"
|
v-show="firstStep"
|
||||||
:step-type="stepType"
|
:step-type="stepType"
|
||||||
:giver-entity-type="giverEntityType"
|
:giver-entity-type="currentGiverEntityType"
|
||||||
:recipient-entity-type="recipientEntityType"
|
:recipient-entity-type="currentRecipientEntityType"
|
||||||
:show-projects="
|
:show-projects="
|
||||||
giverEntityType === 'project' || recipientEntityType === 'project'
|
currentGiverEntityType === 'project' ||
|
||||||
|
currentRecipientEntityType === 'project'
|
||||||
"
|
"
|
||||||
:is-from-project-view="isFromProjectView"
|
|
||||||
:all-contacts="allContacts"
|
:all-contacts="allContacts"
|
||||||
:active-did="activeDid"
|
:active-did="activeDid"
|
||||||
:all-my-dids="allMyDids"
|
:all-my-dids="allMyDids"
|
||||||
@@ -29,6 +29,7 @@
|
|||||||
:offer-id="offerId"
|
:offer-id="offerId"
|
||||||
:notify="$notify"
|
:notify="$notify"
|
||||||
@entity-selected="handleEntitySelected"
|
@entity-selected="handleEntitySelected"
|
||||||
|
@toggle-entity-type="handleToggleEntityType"
|
||||||
@cancel="cancel"
|
@cancel="cancel"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -37,17 +38,18 @@
|
|||||||
v-show="!firstStep"
|
v-show="!firstStep"
|
||||||
:giver="giver"
|
:giver="giver"
|
||||||
:receiver="receiver"
|
:receiver="receiver"
|
||||||
:giver-entity-type="giverEntityType"
|
:giver-entity-type="currentGiverEntityType"
|
||||||
:recipient-entity-type="recipientEntityType"
|
:recipient-entity-type="currentRecipientEntityType"
|
||||||
:description="description"
|
:description="description"
|
||||||
:amount="parseFloat(amountInput) || 0"
|
:amount="parseFloat(amountInput) || 0"
|
||||||
:unit-code="unitCode"
|
:unit-code="unitCode"
|
||||||
:prompt="prompt"
|
:prompt="prompt"
|
||||||
:is-from-project-view="isFromProjectView"
|
|
||||||
:has-conflict="hasPersonConflict"
|
:has-conflict="hasPersonConflict"
|
||||||
:offer-id="offerId"
|
:offer-id="offerId"
|
||||||
:from-project-id="fromProjectId"
|
:from-project-id="fromProjectId"
|
||||||
:to-project-id="toProjectId"
|
:to-project-id="toProjectId"
|
||||||
|
:is-giver-locked="isGiverLocked"
|
||||||
|
:is-recipient-locked="isRecipientLocked"
|
||||||
:on-update-description="(desc: string) => (description = desc)"
|
:on-update-description="(desc: string) => (description = desc)"
|
||||||
:on-update-amount="handleAmountUpdate"
|
:on-update-amount="handleAmountUpdate"
|
||||||
:on-update-unit-code="(code: string) => (unitCode = code)"
|
:on-update-unit-code="(code: string) => (unitCode = code)"
|
||||||
@@ -113,11 +115,10 @@ export default class GiftedDialog extends Vue {
|
|||||||
|
|
||||||
@Prop() fromProjectId = "";
|
@Prop() fromProjectId = "";
|
||||||
@Prop() toProjectId = "";
|
@Prop() toProjectId = "";
|
||||||
@Prop() isFromProjectView = false;
|
@Prop({ default: "person" }) initialGiverEntityType = "person" as
|
||||||
@Prop({ default: "person" }) giverEntityType = "person" as
|
|
||||||
| "person"
|
| "person"
|
||||||
| "project";
|
| "project";
|
||||||
@Prop({ default: "person" }) recipientEntityType = "person" as
|
@Prop({ default: "person" }) initialRecipientEntityType = "person" as
|
||||||
| "person"
|
| "person"
|
||||||
| "project";
|
| "project";
|
||||||
|
|
||||||
@@ -131,12 +132,16 @@ export default class GiftedDialog extends Vue {
|
|||||||
description = "";
|
description = "";
|
||||||
firstStep = true; // true = Step 1 (giver/recipient selection), false = Step 2 (amount/description)
|
firstStep = true; // true = Step 1 (giver/recipient selection), false = Step 2 (amount/description)
|
||||||
giver?: libsUtil.GiverReceiverInputInfo; // undefined means no identified giver agent
|
giver?: libsUtil.GiverReceiverInputInfo; // undefined means no identified giver agent
|
||||||
|
currentGiverEntityType: "person" | "project" = "person"; // Mutable version (can be toggled)
|
||||||
|
currentRecipientEntityType: "person" | "project" = "person"; // Mutable version (can be toggled)
|
||||||
offerId = "";
|
offerId = "";
|
||||||
prompt = "";
|
prompt = "";
|
||||||
receiver?: libsUtil.GiverReceiverInputInfo;
|
receiver?: libsUtil.GiverReceiverInputInfo;
|
||||||
stepType = "giver";
|
stepType = "giver";
|
||||||
unitCode = "HUR";
|
unitCode = "HUR";
|
||||||
visible = false;
|
visible = false;
|
||||||
|
isGiverLocked = false;
|
||||||
|
isRecipientLocked = false;
|
||||||
|
|
||||||
libsUtil = libsUtil;
|
libsUtil = libsUtil;
|
||||||
|
|
||||||
@@ -145,8 +150,10 @@ export default class GiftedDialog extends Vue {
|
|||||||
// Computed property to help debug template logic
|
// Computed property to help debug template logic
|
||||||
get shouldShowProjects() {
|
get shouldShowProjects() {
|
||||||
const result =
|
const result =
|
||||||
(this.stepType === "giver" && this.giverEntityType === "project") ||
|
(this.stepType === "giver" &&
|
||||||
(this.stepType === "recipient" && this.recipientEntityType === "project");
|
this.currentGiverEntityType === "project") ||
|
||||||
|
(this.stepType === "recipient" &&
|
||||||
|
this.currentRecipientEntityType === "project");
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -154,8 +161,8 @@ export default class GiftedDialog extends Vue {
|
|||||||
get hasPersonConflict() {
|
get hasPersonConflict() {
|
||||||
// Only check for conflicts when both entities are persons
|
// Only check for conflicts when both entities are persons
|
||||||
if (
|
if (
|
||||||
this.giverEntityType !== "person" ||
|
this.currentGiverEntityType !== "person" ||
|
||||||
this.recipientEntityType !== "person"
|
this.currentRecipientEntityType !== "person"
|
||||||
) {
|
) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -172,22 +179,56 @@ export default class GiftedDialog extends Vue {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Computed property to check if a contact would create a conflict when selected
|
// Computed property to check if current selection would create a project conflict
|
||||||
wouldCreateConflict(contactDid: string) {
|
get hasProjectConflict() {
|
||||||
// Only check for conflicts when both entities are persons
|
// Only check for conflicts when both entities are projects
|
||||||
if (
|
if (
|
||||||
this.giverEntityType !== "person" ||
|
this.currentGiverEntityType !== "project" ||
|
||||||
this.recipientEntityType !== "person"
|
this.currentRecipientEntityType !== "project"
|
||||||
) {
|
) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.stepType === "giver") {
|
// Check if giver and recipient are the same project
|
||||||
// If selecting as giver, check if it conflicts with current recipient
|
if (
|
||||||
return this.receiver?.did === contactDid;
|
this.giver?.handleId &&
|
||||||
} else if (this.stepType === "recipient") {
|
this.receiver?.handleId &&
|
||||||
// If selecting as recipient, check if it conflicts with current giver
|
this.giver.handleId === this.receiver.handleId
|
||||||
return this.giver?.did === contactDid;
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Computed property to check if a contact or project would create a conflict when selected
|
||||||
|
wouldCreateConflict(identifier: string) {
|
||||||
|
// Check for person conflicts when both entities are persons
|
||||||
|
if (
|
||||||
|
this.currentGiverEntityType === "person" &&
|
||||||
|
this.currentRecipientEntityType === "person"
|
||||||
|
) {
|
||||||
|
if (this.stepType === "giver") {
|
||||||
|
// If selecting as giver, check if it conflicts with current recipient
|
||||||
|
return this.receiver?.did === identifier;
|
||||||
|
} else if (this.stepType === "recipient") {
|
||||||
|
// If selecting as recipient, check if it conflicts with current giver
|
||||||
|
return this.giver?.did === identifier;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for project conflicts when both entities are projects
|
||||||
|
if (
|
||||||
|
this.currentGiverEntityType === "project" &&
|
||||||
|
this.currentRecipientEntityType === "project"
|
||||||
|
) {
|
||||||
|
if (this.stepType === "giver") {
|
||||||
|
// If selecting as giver, check if it conflicts with current recipient
|
||||||
|
return this.receiver?.handleId === identifier;
|
||||||
|
} else if (this.stepType === "recipient") {
|
||||||
|
// If selecting as recipient, check if it conflicts with current giver
|
||||||
|
return this.giver?.handleId === identifier;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
@@ -211,8 +252,9 @@ export default class GiftedDialog extends Vue {
|
|||||||
this.amountInput = amountInput || "0";
|
this.amountInput = amountInput || "0";
|
||||||
this.unitCode = unitCode || "HUR";
|
this.unitCode = unitCode || "HUR";
|
||||||
this.callbackOnSuccess = callbackOnSuccess;
|
this.callbackOnSuccess = callbackOnSuccess;
|
||||||
this.firstStep = !giver;
|
// Initialize current entity types from initial prop values
|
||||||
this.stepType = "giver";
|
this.currentGiverEntityType = this.initialGiverEntityType;
|
||||||
|
this.currentRecipientEntityType = this.initialRecipientEntityType;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const settings = await this.$accountSettings();
|
const settings = await this.$accountSettings();
|
||||||
@@ -223,6 +265,41 @@ export default class GiftedDialog extends Vue {
|
|||||||
const activeIdentity = await (this as any).$getActiveIdentity();
|
const activeIdentity = await (this as any).$getActiveIdentity();
|
||||||
this.activeDid = activeIdentity.activeDid || "";
|
this.activeDid = activeIdentity.activeDid || "";
|
||||||
|
|
||||||
|
// Determine if entities should be locked
|
||||||
|
// An entity is locked if it's provided as an input property (has did or handleId)
|
||||||
|
// For persons: locked if did is provided and not "You" (activeDid)
|
||||||
|
// For projects: locked if handleId is provided
|
||||||
|
// When entities come from ContactsView, ContactGiftingView, or ProjectViewView context,
|
||||||
|
// they should be locked if they have a valid identifier (did or handleId)
|
||||||
|
const isGiverProvided =
|
||||||
|
giver &&
|
||||||
|
((giver.did && giver.did !== this.activeDid) ||
|
||||||
|
(giver.handleId && giver.handleId !== ""));
|
||||||
|
const isReceiverProvided =
|
||||||
|
receiver &&
|
||||||
|
((receiver.did && receiver.did !== this.activeDid) ||
|
||||||
|
(receiver.handleId && receiver.handleId !== ""));
|
||||||
|
|
||||||
|
// Lock entities that are provided (from context or explicitly set)
|
||||||
|
// This ensures that when entities are chosen from ContactsView, ContactGiftingView,
|
||||||
|
// or ProjectViewView, the other entity (giver or recipient) that was already set
|
||||||
|
// from context is locked
|
||||||
|
this.isGiverLocked = !!isGiverProvided;
|
||||||
|
this.isRecipientLocked = !!isReceiverProvided;
|
||||||
|
|
||||||
|
// Determine if receiver should be locked (for step navigation logic)
|
||||||
|
// Receiver is locked only if it's provided AND it's not "You" (activeDid)
|
||||||
|
// "You" is treated as a default that can be changed
|
||||||
|
const isReceiverLocked =
|
||||||
|
receiver && receiver.did && receiver.did !== this.activeDid;
|
||||||
|
|
||||||
|
// Only skip Step 1 if both giver and receiver are provided AND receiver is locked
|
||||||
|
// If receiver is "You" (default), still show Step 1 so user can change it
|
||||||
|
this.firstStep = !(giver && isReceiverLocked);
|
||||||
|
// If giver is provided but receiver is not locked, start with recipient selection
|
||||||
|
// Otherwise, start with giver selection
|
||||||
|
this.stepType = giver && !isReceiverLocked ? "recipient" : "giver";
|
||||||
|
|
||||||
logger.debug("[GiftedDialog] Settings received:", {
|
logger.debug("[GiftedDialog] Settings received:", {
|
||||||
activeDid: this.activeDid,
|
activeDid: this.activeDid,
|
||||||
apiServer: this.apiServer,
|
apiServer: this.apiServer,
|
||||||
@@ -278,6 +355,11 @@ export default class GiftedDialog extends Vue {
|
|||||||
this.prompt = "";
|
this.prompt = "";
|
||||||
this.unitCode = "HUR";
|
this.unitCode = "HUR";
|
||||||
this.firstStep = true;
|
this.firstStep = true;
|
||||||
|
// Reset to initial prop values
|
||||||
|
this.currentGiverEntityType = this.initialGiverEntityType;
|
||||||
|
// Reset lock states
|
||||||
|
this.isGiverLocked = false;
|
||||||
|
this.isRecipientLocked = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
async confirm() {
|
async confirm() {
|
||||||
@@ -315,6 +397,15 @@ export default class GiftedDialog extends Vue {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check for project conflict
|
||||||
|
if (this.hasProjectConflict) {
|
||||||
|
this.safeNotify.error(
|
||||||
|
"You cannot select the same project as both giver and recipient.",
|
||||||
|
TIMEOUTS.STANDARD,
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.close();
|
this.close();
|
||||||
this.safeNotify.toast(
|
this.safeNotify.toast(
|
||||||
NOTIFY_GIFTED_DETAILS_RECORDING_GIVE.message,
|
NOTIFY_GIFTED_DETAILS_RECORDING_GIVE.message,
|
||||||
@@ -356,8 +447,8 @@ export default class GiftedDialog extends Vue {
|
|||||||
let providerPlanHandleId: string | undefined;
|
let providerPlanHandleId: string | undefined;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
this.giverEntityType === "project" &&
|
this.currentGiverEntityType === "project" &&
|
||||||
this.recipientEntityType === "person"
|
this.currentRecipientEntityType === "person"
|
||||||
) {
|
) {
|
||||||
// Project-to-person gift
|
// Project-to-person gift
|
||||||
fromDid = undefined; // No person giver
|
fromDid = undefined; // No person giver
|
||||||
@@ -365,8 +456,8 @@ export default class GiftedDialog extends Vue {
|
|||||||
fulfillsProjectHandleId = undefined; // No project recipient
|
fulfillsProjectHandleId = undefined; // No project recipient
|
||||||
providerPlanHandleId = this.giver?.handleId; // Project giver
|
providerPlanHandleId = this.giver?.handleId; // Project giver
|
||||||
} else if (
|
} else if (
|
||||||
this.giverEntityType === "person" &&
|
this.currentGiverEntityType === "person" &&
|
||||||
this.recipientEntityType === "project"
|
this.currentRecipientEntityType === "project"
|
||||||
) {
|
) {
|
||||||
// Person-to-project gift
|
// Person-to-project gift
|
||||||
fromDid = giverDid as string; // Person giver
|
fromDid = giverDid as string; // Person giver
|
||||||
@@ -526,17 +617,22 @@ export default class GiftedDialog extends Vue {
|
|||||||
return {
|
return {
|
||||||
amountInput: this.amountInput,
|
amountInput: this.amountInput,
|
||||||
description: this.description,
|
description: this.description,
|
||||||
giverDid: this.giverEntityType === "person" ? this.giver?.did : undefined,
|
giverDid:
|
||||||
|
this.currentGiverEntityType === "person" ? this.giver?.did : undefined,
|
||||||
giverName: this.giver?.name,
|
giverName: this.giver?.name,
|
||||||
offerId: this.offerId,
|
offerId: this.offerId,
|
||||||
fulfillsProjectId:
|
fulfillsProjectId:
|
||||||
this.recipientEntityType === "project" ? this.toProjectId : undefined,
|
this.currentRecipientEntityType === "project"
|
||||||
|
? this.toProjectId
|
||||||
|
: undefined,
|
||||||
providerProjectId:
|
providerProjectId:
|
||||||
this.giverEntityType === "project"
|
this.currentGiverEntityType === "project"
|
||||||
? this.giver?.handleId
|
? this.giver?.handleId
|
||||||
: this.fromProjectId,
|
: this.fromProjectId,
|
||||||
recipientDid:
|
recipientDid:
|
||||||
this.recipientEntityType === "person" ? this.receiver?.did : undefined,
|
this.currentRecipientEntityType === "person"
|
||||||
|
? this.receiver?.did
|
||||||
|
: undefined,
|
||||||
recipientName: this.receiver?.name,
|
recipientName: this.receiver?.name,
|
||||||
unitCode: this.unitCode,
|
unitCode: this.unitCode,
|
||||||
};
|
};
|
||||||
@@ -596,6 +692,13 @@ export default class GiftedDialog extends Vue {
|
|||||||
entityType: string;
|
entityType: string;
|
||||||
currentEntity: { did: string; name: string };
|
currentEntity: { did: string; name: string };
|
||||||
}) {
|
}) {
|
||||||
|
// Prevent editing if the entity is locked
|
||||||
|
if (data.entityType === "giver" && this.isGiverLocked) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (data.entityType === "recipient" && this.isRecipientLocked) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.goBackToStep1(data.entityType);
|
this.goBackToStep1(data.entityType);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -606,6 +709,24 @@ export default class GiftedDialog extends Vue {
|
|||||||
this.confirm();
|
this.confirm();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle toggle entity type request from EntitySelectionStep
|
||||||
|
*/
|
||||||
|
handleToggleEntityType() {
|
||||||
|
// Toggle the appropriate entity type based on current step
|
||||||
|
if (this.stepType === "giver") {
|
||||||
|
this.currentGiverEntityType =
|
||||||
|
this.currentGiverEntityType === "person" ? "project" : "person";
|
||||||
|
// Clear any selected giver when toggling
|
||||||
|
this.giver = undefined;
|
||||||
|
} else if (this.stepType === "recipient") {
|
||||||
|
this.currentRecipientEntityType =
|
||||||
|
this.currentRecipientEntityType === "person" ? "project" : "person";
|
||||||
|
// Clear any selected receiver when toggling
|
||||||
|
this.receiver = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle amount update from GiftDetailsStep
|
* Handle amount update from GiftDetailsStep
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1,11 +1,8 @@
|
|||||||
/** * ProjectCard.vue - Individual project display component * * Extracted from
|
/** * ProjectCard.vue - Individual project display component * * Extracted from
|
||||||
GiftedDialog.vue to handle project entity display * with selection states and
|
GiftedDialog.vue to handle project entity display * with selection states,
|
||||||
issuer information. * * @author Matthew Raymer */
|
conflict detection, and issuer information. * * @author Matthew Raymer */
|
||||||
<template>
|
<template>
|
||||||
<li
|
<li :class="cardClasses" @click="handleClick">
|
||||||
class="flex items-center gap-2 px-2 py-1.5 border-b border-slate-300 hover:bg-slate-50 cursor-pointer"
|
|
||||||
@click="handleClick"
|
|
||||||
>
|
|
||||||
<ProjectIcon
|
<ProjectIcon
|
||||||
:entity-id="project.handleId"
|
:entity-id="project.handleId"
|
||||||
:icon-size="30"
|
:icon-size="30"
|
||||||
@@ -14,8 +11,8 @@ issuer information. * * @author Matthew Raymer */
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<div class="overflow-hidden">
|
<div class="overflow-hidden">
|
||||||
<h3 class="text-sm font-semibold truncate">
|
<h3 :class="nameClasses">
|
||||||
{{ project.name || unnamedProject }}
|
{{ displayName }}
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
<div class="text-xs text-slate-500 truncate">
|
<div class="text-xs text-slate-500 truncate">
|
||||||
@@ -33,6 +30,7 @@ import { PlanData } from "../interfaces/records";
|
|||||||
import { Contact } from "../db/tables/contacts";
|
import { Contact } from "../db/tables/contacts";
|
||||||
import { didInfo } from "../libs/endorserServer";
|
import { didInfo } from "../libs/endorserServer";
|
||||||
import { UNNAMED_PROJECT } from "@/constants/entities";
|
import { UNNAMED_PROJECT } from "@/constants/entities";
|
||||||
|
import { NotificationIface } from "../constants/app";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ProjectCard - Displays a project entity with selection capability
|
* ProjectCard - Displays a project entity with selection capability
|
||||||
@@ -42,6 +40,8 @@ import { UNNAMED_PROJECT } from "@/constants/entities";
|
|||||||
* - Displays project name and issuer information
|
* - Displays project name and issuer information
|
||||||
* - Handles click events for selection
|
* - Handles click events for selection
|
||||||
* - Shows issuer name using didInfo utility
|
* - Shows issuer name using didInfo utility
|
||||||
|
* - Selection states (selectable, conflicted, disabled)
|
||||||
|
* - Warning notifications for conflicted entities
|
||||||
*/
|
*/
|
||||||
@Component({
|
@Component({
|
||||||
components: {
|
components: {
|
||||||
@@ -65,6 +65,18 @@ export default class ProjectCard extends Vue {
|
|||||||
@Prop({ required: true })
|
@Prop({ required: true })
|
||||||
allContacts!: Contact[];
|
allContacts!: Contact[];
|
||||||
|
|
||||||
|
/** Whether this project would create a conflict if selected */
|
||||||
|
@Prop({ default: false })
|
||||||
|
conflicted!: boolean;
|
||||||
|
|
||||||
|
/** Notification function from parent component */
|
||||||
|
@Prop()
|
||||||
|
notify?: (notification: NotificationIface, timeout?: number) => void;
|
||||||
|
|
||||||
|
/** Context for conflict messages (e.g., "giver", "recipient") */
|
||||||
|
@Prop({ default: "other party" })
|
||||||
|
conflictContext!: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the unnamed project constant
|
* Get the unnamed project constant
|
||||||
*/
|
*/
|
||||||
@@ -72,6 +84,51 @@ export default class ProjectCard extends Vue {
|
|||||||
return UNNAMED_PROJECT;
|
return UNNAMED_PROJECT;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Computed CSS classes for the card
|
||||||
|
*/
|
||||||
|
get cardClasses(): string {
|
||||||
|
const baseCardClasses =
|
||||||
|
"flex items-center gap-2 px-2 py-1.5 border-b border-slate-300";
|
||||||
|
|
||||||
|
if (this.conflicted) {
|
||||||
|
return `${baseCardClasses} *:opacity-50 cursor-not-allowed`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${baseCardClasses} cursor-pointer hover:bg-slate-50`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Computed CSS classes for the project name
|
||||||
|
*/
|
||||||
|
get nameClasses(): string {
|
||||||
|
const baseNameClasses = "text-sm font-semibold truncate";
|
||||||
|
|
||||||
|
if (this.conflicted) {
|
||||||
|
return `${baseNameClasses} text-slate-500`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add italic styling for entities without set names
|
||||||
|
if (!this.project.name) {
|
||||||
|
return `${baseNameClasses} italic text-slate-500`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return baseNameClasses;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Computed display name for the project
|
||||||
|
*/
|
||||||
|
get displayName(): string {
|
||||||
|
// If the project has a set name, use that name
|
||||||
|
if (this.project.name) {
|
||||||
|
return this.project.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the project does not have a set name
|
||||||
|
return this.unnamedProject;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Computed display name for the project issuer
|
* Computed display name for the project issuer
|
||||||
*/
|
*/
|
||||||
@@ -85,10 +142,23 @@ export default class ProjectCard extends Vue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle card click - emit project selection
|
* Handle card click - emit if not conflicted, show warning if conflicted
|
||||||
*/
|
*/
|
||||||
handleClick(): void {
|
handleClick(): void {
|
||||||
this.emitProjectSelected(this.project);
|
if (!this.conflicted) {
|
||||||
|
this.emitProjectSelected(this.project);
|
||||||
|
} else if (this.notify) {
|
||||||
|
// Show warning notification for conflicted entity
|
||||||
|
this.notify(
|
||||||
|
{
|
||||||
|
group: "alert",
|
||||||
|
type: "warning",
|
||||||
|
title: "Cannot Select",
|
||||||
|
text: `You cannot select "${this.displayName}" because it is already selected as the ${this.conflictContext}.`,
|
||||||
|
},
|
||||||
|
3000,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Emit methods using @Emit decorator
|
// Emit methods using @Emit decorator
|
||||||
|
|||||||
@@ -236,8 +236,8 @@
|
|||||||
</div>
|
</div>
|
||||||
<GiftedDialog
|
<GiftedDialog
|
||||||
ref="customGiveDialog"
|
ref="customGiveDialog"
|
||||||
:giver-entity-type="'person'"
|
:initial-giver-entity-type="'person'"
|
||||||
:recipient-entity-type="projectInfo ? 'project' : 'person'"
|
:initial-recipient-entity-type="projectInfo ? 'project' : 'person'"
|
||||||
:to-project-id="
|
:to-project-id="
|
||||||
detailsForGive?.fulfillsPlanHandleId ||
|
detailsForGive?.fulfillsPlanHandleId ||
|
||||||
detailsForOffer?.fulfillsPlanHandleId ||
|
detailsForOffer?.fulfillsPlanHandleId ||
|
||||||
|
|||||||
@@ -105,11 +105,10 @@
|
|||||||
|
|
||||||
<GiftedDialog
|
<GiftedDialog
|
||||||
ref="giftedDialog"
|
ref="giftedDialog"
|
||||||
:giver-entity-type="giverEntityType"
|
:initial-giver-entity-type="giverEntityType"
|
||||||
:recipient-entity-type="recipientEntityType"
|
:initial-recipient-entity-type="recipientEntityType"
|
||||||
:from-project-id="fromProjectId"
|
:from-project-id="fromProjectId"
|
||||||
:to-project-id="toProjectId"
|
:to-project-id="toProjectId"
|
||||||
:is-from-project-view="isFromProjectView"
|
|
||||||
:hide-show-all="true"
|
:hide-show-all="true"
|
||||||
/>
|
/>
|
||||||
</section>
|
</section>
|
||||||
@@ -165,7 +164,6 @@ export default class ContactGiftingView extends Vue {
|
|||||||
fromProjectId = "";
|
fromProjectId = "";
|
||||||
toProjectId = "";
|
toProjectId = "";
|
||||||
showProjects = false;
|
showProjects = false;
|
||||||
isFromProjectView = false;
|
|
||||||
offerId = "";
|
offerId = "";
|
||||||
|
|
||||||
async created() {
|
async created() {
|
||||||
@@ -217,8 +215,6 @@ export default class ContactGiftingView extends Vue {
|
|||||||
this.toProjectId = (this.$route.query["toProjectId"] as string) || "";
|
this.toProjectId = (this.$route.query["toProjectId"] as string) || "";
|
||||||
this.showProjects =
|
this.showProjects =
|
||||||
(this.$route.query["showProjects"] as string) === "true";
|
(this.$route.query["showProjects"] as string) === "true";
|
||||||
this.isFromProjectView =
|
|
||||||
(this.$route.query["isFromProjectView"] as string) === "true";
|
|
||||||
this.offerId = (this.$route.query["offerId"] as string) || "";
|
this.offerId = (this.$route.query["offerId"] as string) || "";
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
|||||||
@@ -120,8 +120,8 @@
|
|||||||
|
|
||||||
<GiftedDialog
|
<GiftedDialog
|
||||||
ref="customGivenDialog"
|
ref="customGivenDialog"
|
||||||
:giver-entity-type="'person'"
|
:initial-giver-entity-type="'person'"
|
||||||
:recipient-entity-type="'person'"
|
:initial-recipient-entity-type="'person'"
|
||||||
/>
|
/>
|
||||||
<OfferDialog ref="customOfferDialog" />
|
<OfferDialog ref="customOfferDialog" />
|
||||||
<ContactNameDialog ref="contactNameDialog" />
|
<ContactNameDialog ref="contactNameDialog" />
|
||||||
|
|||||||
@@ -111,7 +111,34 @@ Raymer * @version 1.0.0 */
|
|||||||
<!-- Record Quick-Action -->
|
<!-- Record Quick-Action -->
|
||||||
<div class="mb-6">
|
<div class="mb-6">
|
||||||
<div class="flex gap-2 items-center mb-2">
|
<div class="flex gap-2 items-center mb-2">
|
||||||
<h2 class="font-bold">Record something given by:</h2>
|
<!-- Thank button - always visible and unchanged -->
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="text-center text-base uppercase bg-gradient-to-b from-green-400 to-green-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white flex items-center justify-center gap-2 px-4 py-3 rounded-full"
|
||||||
|
@click="openPersonDialog()"
|
||||||
|
>
|
||||||
|
<font-awesome icon="plus" />
|
||||||
|
<span>Thank</span>
|
||||||
|
</button>
|
||||||
|
<!-- Plus button - appears when scrolled, positioned over house-chimney icon -->
|
||||||
|
<transition
|
||||||
|
enter-active-class="transition-all duration-1000 ease-out"
|
||||||
|
leave-active-class="transition-all duration-1000 ease-in"
|
||||||
|
enter-from-class="scale-0"
|
||||||
|
enter-to-class="scale-100"
|
||||||
|
leave-from-class="scale-100"
|
||||||
|
leave-to-class="scale-0"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
v-if="isScrolled"
|
||||||
|
type="button"
|
||||||
|
class="fixed bottom-10 p-4 w-14 h-14 z-50 text-center bg-gradient-to-b from-green-400 to-green-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white rounded-full flex items-center justify-center"
|
||||||
|
:style="getButtonPosition()"
|
||||||
|
@click="openPersonDialog()"
|
||||||
|
>
|
||||||
|
<font-awesome icon="plus" />
|
||||||
|
</button>
|
||||||
|
</transition>
|
||||||
<button
|
<button
|
||||||
class="block ms-auto text-sm text-center text-white bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] p-1.5 rounded-full"
|
class="block ms-auto text-sm text-center text-white bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] p-1.5 rounded-full"
|
||||||
@click="openGiftedPrompts()"
|
@click="openGiftedPrompts()"
|
||||||
@@ -122,25 +149,6 @@ Raymer * @version 1.0.0 */
|
|||||||
/>
|
/>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="grid grid-cols-2 gap-2">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="text-center text-base uppercase 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-3 py-2 rounded-lg"
|
|
||||||
@click="openPersonDialog()"
|
|
||||||
>
|
|
||||||
<font-awesome icon="user" />
|
|
||||||
Person
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="text-center text-base uppercase 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-3 py-2 rounded-lg"
|
|
||||||
@click="openProjectDialog()"
|
|
||||||
>
|
|
||||||
<font-awesome icon="folder-open" />
|
|
||||||
Project
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -148,8 +156,8 @@ Raymer * @version 1.0.0 */
|
|||||||
|
|
||||||
<GiftedDialog
|
<GiftedDialog
|
||||||
ref="giftedDialog"
|
ref="giftedDialog"
|
||||||
:giver-entity-type="showProjectsDialog ? 'project' : 'person'"
|
:initial-giver-entity-type="'person'"
|
||||||
:recipient-entity-type="'person'"
|
:initial-recipient-entity-type="'person'"
|
||||||
/>
|
/>
|
||||||
<GiftedPrompts ref="giftedPrompts" />
|
<GiftedPrompts ref="giftedPrompts" />
|
||||||
<FeedFilters ref="feedFilters" />
|
<FeedFilters ref="feedFilters" />
|
||||||
@@ -446,7 +454,8 @@ export default class HomeView extends Vue {
|
|||||||
userAgentInfo = new UAParser(); // see https://docs.uaparser.js.org/v2/api/ua-parser-js/get-os.html
|
userAgentInfo = new UAParser(); // see https://docs.uaparser.js.org/v2/api/ua-parser-js/get-os.html
|
||||||
selectedImage = "";
|
selectedImage = "";
|
||||||
isImageViewerOpen = false;
|
isImageViewerOpen = false;
|
||||||
showProjectsDialog = false;
|
isScrolled = false;
|
||||||
|
scrollHandler?: () => void;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* CRITICAL VUE REACTIVITY BUG WORKAROUND
|
* CRITICAL VUE REACTIVITY BUG WORKAROUND
|
||||||
@@ -547,11 +556,44 @@ export default class HomeView extends Vue {
|
|||||||
this.numNewOffersToUser + this.numNewOffersToUserProjects > 0,
|
this.numNewOffersToUser + this.numNewOffersToUserProjects > 0,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Add scroll listener for button collapse
|
||||||
|
// Note: Scrolling happens on #app element, not window (see tailwind.css)
|
||||||
|
const appElement = document.getElementById("app");
|
||||||
|
const scrollElement = appElement || window;
|
||||||
|
|
||||||
|
this.scrollHandler = () => {
|
||||||
|
const scrollTop = appElement
|
||||||
|
? appElement.scrollTop
|
||||||
|
: window.pageYOffset || document.documentElement.scrollTop || 0;
|
||||||
|
const shouldBeScrolled = scrollTop > 100;
|
||||||
|
if (this.isScrolled !== shouldBeScrolled) {
|
||||||
|
this.isScrolled = shouldBeScrolled;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Set initial state
|
||||||
|
this.scrollHandler();
|
||||||
|
// Listen on scroll element (prefer #app, fallback to window)
|
||||||
|
scrollElement.addEventListener("scroll", this.scrollHandler, {
|
||||||
|
passive: true,
|
||||||
|
});
|
||||||
} catch (err: unknown) {
|
} catch (err: unknown) {
|
||||||
this.handleError(err);
|
this.handleError(err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cleanup scroll listener on component unmount
|
||||||
|
*/
|
||||||
|
beforeUnmount() {
|
||||||
|
if (this.scrollHandler) {
|
||||||
|
const appElement = document.getElementById("app");
|
||||||
|
const scrollElement = appElement || window;
|
||||||
|
scrollElement.removeEventListener("scroll", this.scrollHandler);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Watch for changes in the current activeDid
|
* Watch for changes in the current activeDid
|
||||||
* Reload settings when user switches identities
|
* Reload settings when user switches identities
|
||||||
@@ -1811,17 +1853,19 @@ export default class HomeView extends Vue {
|
|||||||
* - this.activeDid
|
* - this.activeDid
|
||||||
*
|
*
|
||||||
* @param giver Optional contact info for giver
|
* @param giver Optional contact info for giver
|
||||||
* @param description Optional gift description
|
* @param prompt Optional gift prompt
|
||||||
*/
|
*/
|
||||||
openDialog(giver?: GiverReceiverInputInfo, prompt?: string) {
|
openDialog(giver?: GiverReceiverInputInfo, prompt?: string) {
|
||||||
// Determine the giver entity based on DID logic
|
// Determine the giver entity based on DID logic
|
||||||
const giverEntity = this.createGiverEntity(giver);
|
const giverEntity = this.createGiverEntity(giver);
|
||||||
|
|
||||||
|
// In HomeView, "You" is the default recipient but it's not locked
|
||||||
|
// User can still change it in Step 1 if they want
|
||||||
(this.$refs.giftedDialog as GiftedDialog).open(
|
(this.$refs.giftedDialog as GiftedDialog).open(
|
||||||
giverEntity,
|
giverEntity,
|
||||||
{
|
{
|
||||||
did: this.activeDid,
|
did: this.activeDid,
|
||||||
name: "You", // In HomeView, we always use "You" as the giver
|
name: "You",
|
||||||
} as GiverReceiverInputInfo,
|
} as GiverReceiverInputInfo,
|
||||||
undefined,
|
undefined,
|
||||||
prompt,
|
prompt,
|
||||||
@@ -1919,15 +1963,9 @@ export default class HomeView extends Vue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
openPersonDialog(giver?: GiverReceiverInputInfo, prompt?: string) {
|
openPersonDialog(giver?: GiverReceiverInputInfo, prompt?: string) {
|
||||||
this.showProjectsDialog = false;
|
|
||||||
this.openDialog(giver, prompt);
|
this.openDialog(giver, prompt);
|
||||||
}
|
}
|
||||||
|
|
||||||
openProjectDialog() {
|
|
||||||
this.showProjectsDialog = true;
|
|
||||||
(this.$refs.giftedDialog as GiftedDialog).open();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Computed property for registration status
|
* Computed property for registration status
|
||||||
*
|
*
|
||||||
@@ -1937,5 +1975,39 @@ export default class HomeView extends Vue {
|
|||||||
get isUserRegistered() {
|
get isUserRegistered() {
|
||||||
return this.isRegistered;
|
return this.isRegistered;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates the horizontal position for the button to align with house button center
|
||||||
|
*/
|
||||||
|
getButtonPosition() {
|
||||||
|
const quickNav = document.getElementById("QuickNav");
|
||||||
|
if (!quickNav) {
|
||||||
|
return { left: "1.5rem" }; // Fallback to left-6
|
||||||
|
}
|
||||||
|
|
||||||
|
const navList = quickNav.querySelector("ul");
|
||||||
|
if (!navList) {
|
||||||
|
return { left: "1.5rem" };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the first nav item (house button)
|
||||||
|
const firstItem = navList.querySelector("li:first-child");
|
||||||
|
if (!firstItem) {
|
||||||
|
return { left: "1.5rem" };
|
||||||
|
}
|
||||||
|
|
||||||
|
const itemRect = firstItem.getBoundingClientRect();
|
||||||
|
const buttonWidth = 56; // w-14 = 3.5rem = 56px
|
||||||
|
|
||||||
|
// Calculate center of house button
|
||||||
|
const houseButtonCenter = itemRect.left + itemRect.width / 2;
|
||||||
|
|
||||||
|
// Position button so its center aligns with house button center
|
||||||
|
const buttonLeft = houseButtonCenter - buttonWidth / 2;
|
||||||
|
|
||||||
|
return {
|
||||||
|
left: `${buttonLeft}px`,
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -238,10 +238,9 @@
|
|||||||
|
|
||||||
<GiftedDialog
|
<GiftedDialog
|
||||||
ref="giveDialogToThis"
|
ref="giveDialogToThis"
|
||||||
:giver-entity-type="'person'"
|
:initial-giver-entity-type="'person'"
|
||||||
:recipient-entity-type="'project'"
|
:initial-recipient-entity-type="'project'"
|
||||||
:to-project-id="projectId"
|
:to-project-id="projectId"
|
||||||
:is-from-project-view="true"
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- Offers & Gifts to & from this -->
|
<!-- Offers & Gifts to & from this -->
|
||||||
@@ -521,10 +520,9 @@
|
|||||||
</div>
|
</div>
|
||||||
<GiftedDialog
|
<GiftedDialog
|
||||||
ref="giveDialogFromThis"
|
ref="giveDialogFromThis"
|
||||||
:giver-entity-type="'project'"
|
:initial-giver-entity-type="'project'"
|
||||||
:recipient-entity-type="'person'"
|
:initial-recipient-entity-type="'person'"
|
||||||
:from-project-id="projectId"
|
:from-project-id="projectId"
|
||||||
:is-from-project-view="true"
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<h3 class="text-lg font-bold leading-tight mb-3">
|
<h3 class="text-lg font-bold leading-tight mb-3">
|
||||||
|
|||||||
@@ -58,7 +58,8 @@
|
|||||||
<div
|
<div
|
||||||
v-if="
|
v-if="
|
||||||
profile.issuerDid !== activeDid && // only show neighbors if they're not current user
|
profile.issuerDid !== activeDid && // only show neighbors if they're not current user
|
||||||
profile.issuerDid !== neighbors?.[0]?.did // and they're not directly connected (since there's no in-between)
|
profile.issuerDid !== neighbors?.[0]?.did // and they're not directly connected
|
||||||
|
// (which we know because there is no neighbor in-between them)
|
||||||
"
|
"
|
||||||
class="mt-6"
|
class="mt-6"
|
||||||
>
|
>
|
||||||
|
|||||||
Reference in New Issue
Block a user