Files
crowd-funder-for-time-pwa/src/views/ContactGiftingView.vue
Jose Olarte III 06f3a4c7c2 Refactor: simplify GiftedDialog with explicit entity type props
Replace complex updateEntityTypes() method with explicit giverEntityType and
recipientEntityType props. This makes the component more declarative and
maintainable by removing hidden logic and making entity type relationships
clear at the call site.

- Remove updateEntityTypes() method and related watchers
- Add explicit giverEntityType and recipientEntityType props with defaults
- Update all views to use inline logic for entity type determination
- Fix entity type preservation in navigation flows
- Enhance query parameter passing for better context preservation
- Fix recipient reset issue in ContactGiftingView
- Resolve entity type mismatch in HomeView project button flow

Files changed:
- GiftedDialog.vue: Remove complex logic, add explicit props
- EntitySelectionStep.vue: Enhanced query parameter handling
- ContactGiftingView.vue: Improved context preservation
- HomeView.vue, ProjectViewView.vue, ClaimView.vue, ContactsView.vue:
  Updated to use explicit entity type props
- Add refactoring documentation
2025-08-03 11:49:46 +08:00

324 lines
12 KiB
Vue

<template>
<QuickNav selected="Home"></QuickNav>
<!-- CONTENT -->
<section id="Content" class="p-6 pb-24 max-w-3xl mx-auto">
<!-- Breadcrumb -->
<div id="ViewBreadcrumb" class="mb-8">
<h1 class="text-2xl text-center font-semibold relative px-7">
<!-- Back -->
<router-link
:to="{ name: 'home' }"
class="text-lg text-center px-2 py-1 absolute -left-2 -top-1"
><font-awesome icon="chevron-left" class="fa-fw"></font-awesome>
</router-link>
{{ stepType === "giver" ? "Given by..." : "Given to..." }}
</h1>
</div>
<!-- Results List -->
<ul class="border-t border-slate-300">
<li class="border-b border-slate-300 py-3">
<h2 class="text-base flex gap-4 items-center">
<span class="grow flex gap-2 items-center font-medium">
<font-awesome
icon="circle-question"
class="text-slate-400 text-4xl"
/>
<span class="italic text-slate-400">(Unnamed/Unknown)</span>
</span>
<span class="text-right">
<button
type="button"
class="block w-full text-center text-sm 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-1.5 rounded-md"
@click="openDialog('Unnamed')"
>
<font-awesome icon="gift" class="fa-fw"></font-awesome>
</button>
</span>
</h2>
</li>
<li
v-for="contact in allContacts"
:key="contact.did"
class="border-b border-slate-300 py-3"
>
<h2 class="text-base flex gap-4 items-center">
<span class="grow flex gap-2 items-center font-medium">
<EntityIcon
:contact="contact"
:icon-size="34"
class="inline-block align-middle border border-slate-300 rounded-full overflow-hidden"
/>
<span v-if="contact.name">{{ contact.name }}</span>
<span v-else class="italic text-slate-400">(No name)</span>
</span>
<span class="text-right">
<button
type="button"
class="block w-full text-center text-sm 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-1.5 rounded-md"
@click="openDialog(contact)"
>
<font-awesome icon="gift" class="fa-fw"></font-awesome>
</button>
</span>
</h2>
</li>
</ul>
<GiftedDialog
ref="giftedDialog"
:giver-entity-type="giverEntityType"
:recipient-entity-type="recipientEntityType"
:from-project-id="fromProjectId"
:to-project-id="toProjectId"
:is-from-project-view="isFromProjectView"
/>
</section>
</template>
<script lang="ts">
import { Component, Vue } from "vue-facing-decorator";
import { RouteLocationNormalizedLoaded, Router } from "vue-router";
import GiftedDialog from "../components/GiftedDialog.vue";
import QuickNav from "../components/QuickNav.vue";
import EntityIcon from "../components/EntityIcon.vue";
import { NotificationIface } from "../constants/app";
import { Contact } from "../db/tables/contacts";
import { GiverReceiverInputInfo } from "../libs/util";
import { logger } from "../utils/logger";
import { PlatformServiceMixin } from "@/utils/PlatformServiceMixin";
import { createNotifyHelpers, TIMEOUTS } from "@/utils/notify";
@Component({
components: { GiftedDialog, QuickNav, EntityIcon },
mixins: [PlatformServiceMixin],
})
export default class ContactGiftingView extends Vue {
$notify!: (notification: NotificationIface, timeout?: number) => void;
$route!: RouteLocationNormalizedLoaded;
$router!: Router;
notify!: ReturnType<typeof createNotifyHelpers>;
activeDid = "";
allContacts: Array<Contact> = [];
apiServer = "";
projectId = "";
prompt = "";
description = "";
amountInput = "0";
unitCode = "HUR";
recipientProjectName = "";
recipientProjectImage = "";
recipientProjectHandleId = "";
// New context parameters
stepType = "giver";
giverEntityType = "person" as "person" | "project";
recipientEntityType = "person" as "person" | "project";
giverProjectId = "";
giverProjectName = "";
giverProjectImage = "";
giverProjectHandleId = "";
giverDid = "";
recipientDid = "";
fromProjectId = "";
toProjectId = "";
showProjects = false;
isFromProjectView = false;
offerId = "";
async created() {
this.notify = createNotifyHelpers(this.$notify);
try {
const settings = await this.$accountSettings();
this.apiServer = settings.apiServer || "";
this.activeDid = settings.activeDid || "";
this.allContacts = await this.$getAllContacts();
this.projectId =
(this.$route.query["recipientProjectId"] as string) || "";
this.recipientProjectName =
(this.$route.query["recipientProjectName"] as string) || "";
this.recipientProjectImage =
(this.$route.query["recipientProjectImage"] as string) || "";
this.recipientProjectHandleId =
(this.$route.query["recipientProjectHandleId"] as string) || "";
this.prompt = (this.$route.query["prompt"] as string) ?? this.prompt;
this.description = (this.$route.query["description"] as string) || "";
this.amountInput = (this.$route.query["amountInput"] as string) || "0";
this.unitCode = (this.$route.query["unitCode"] as string) || "HUR";
// Read new context parameters
this.stepType = (this.$route.query["stepType"] as string) || "giver";
this.giverEntityType =
(this.$route.query["giverEntityType"] as "person" | "project") ||
"person";
this.recipientEntityType =
(this.$route.query["recipientEntityType"] as "person" | "project") ||
"person";
this.giverProjectId =
(this.$route.query["giverProjectId"] as string) || "";
this.giverProjectName =
(this.$route.query["giverProjectName"] as string) || "";
this.giverProjectImage =
(this.$route.query["giverProjectImage"] as string) || "";
this.giverProjectHandleId =
(this.$route.query["giverProjectHandleId"] as string) || "";
this.giverDid = (this.$route.query["giverDid"] as string) || "";
this.recipientDid = (this.$route.query["recipientDid"] as string) || "";
this.fromProjectId = (this.$route.query["fromProjectId"] as string) || "";
this.toProjectId = (this.$route.query["toProjectId"] as string) || "";
this.showProjects =
(this.$route.query["showProjects"] as string) === "true";
this.isFromProjectView =
(this.$route.query["isFromProjectView"] as string) === "true";
this.offerId = (this.$route.query["offerId"] as string) || "";
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} catch (err: any) {
logger.error("Error retrieving settings & contacts:", err);
this.notify.error(
err.message ||
"There was an error retrieving your settings or contacts.",
TIMEOUTS.LONG,
);
}
}
openDialog(contact?: GiverReceiverInputInfo | "Unnamed") {
if (contact === "Unnamed") {
// Special case: Handle "Unnamed" contacts for both givers and recipients
let recipient: GiverReceiverInputInfo;
let giver: GiverReceiverInputInfo | undefined;
if (this.stepType === "giver") {
// We're selecting a giver, so recipient is either a project or the current user
if (this.recipientEntityType === "project") {
recipient = {
did: this.recipientProjectHandleId,
name: this.recipientProjectName,
image: this.recipientProjectImage,
handleId: this.recipientProjectHandleId,
};
} else {
recipient = { did: this.activeDid, name: "You" };
}
giver = undefined; // Will be set to "Unnamed" in GiftedDialog
} else {
// We're selecting a recipient, so recipient is "Unnamed" and giver is preserved from context
recipient = { did: "", name: "Unnamed" };
// Preserve the existing giver from the context
if (this.giverEntityType === "project") {
giver = {
did: this.giverProjectHandleId,
name: this.giverProjectName,
image: this.giverProjectImage,
handleId: this.giverProjectHandleId,
};
} else if (this.giverDid) {
giver = {
did: this.giverDid,
name: this.giverProjectName || "Someone",
};
} else {
giver = { did: this.activeDid, name: "You" };
}
}
(this.$refs.giftedDialog as GiftedDialog).open(
giver,
recipient,
this.offerId,
this.prompt,
this.description,
this.amountInput,
this.unitCode,
);
// Immediately select "Unnamed" and move to Step 2 based on stepType
if (this.stepType === "giver") {
(this.$refs.giftedDialog as GiftedDialog).selectGiver();
} else {
(this.$refs.giftedDialog as GiftedDialog).selectRecipient();
}
} else {
// Regular case: contact is a GiverReceiverInputInfo
let giver: GiverReceiverInputInfo;
let recipient: GiverReceiverInputInfo;
if (this.stepType === "giver") {
// We're selecting a giver, so the contact becomes the giver
giver = contact as GiverReceiverInputInfo; // Safe because we know contact is not "Unnamed" or undefined
// Preserve the existing recipient from the context
if (this.recipientEntityType === "project") {
recipient = {
did: this.recipientProjectHandleId,
name: this.recipientProjectName,
image: this.recipientProjectImage,
handleId: this.recipientProjectHandleId,
};
} else {
// Check if the preserved recipient was "Unnamed" (empty DID) or a regular contact
if (this.recipientDid === "") {
// Recipient was "Unnamed"
recipient = { did: "", name: "Unnamed" };
} else if (this.recipientDid) {
// Recipient was a regular contact
recipient = {
did: this.recipientDid,
name: this.recipientProjectName || "Someone",
};
} else {
// Fallback to current user
recipient = { did: this.activeDid, name: "You" };
}
}
} else {
// We're selecting a recipient, so the contact becomes the recipient
recipient = contact as GiverReceiverInputInfo; // Safe because we know contact is not "Unnamed" or undefined
// Preserve the existing giver from the context
if (this.giverEntityType === "project") {
giver = {
did: this.giverProjectHandleId,
name: this.giverProjectName,
image: this.giverProjectImage,
handleId: this.giverProjectHandleId,
};
} else {
// Check if the preserved giver was "Unnamed" (empty DID) or a regular contact
if (this.giverDid === "") {
// Giver was "Unnamed"
giver = { did: "", name: "Unnamed" };
} else if (this.giverDid) {
// Giver was a regular contact
giver = {
did: this.giverDid,
name: this.giverProjectName || "Someone",
};
} else {
// Fallback to current user
giver = { did: this.activeDid, name: "You" };
}
}
}
(this.$refs.giftedDialog as GiftedDialog).open(
giver,
recipient,
this.offerId,
this.prompt,
this.description,
this.amountInput,
this.unitCode,
);
}
}
}
</script>