forked from jsnbuchanan/crowd-funder-for-time-pwa
Fix TypeScript any types and remove deprecated Dexie code
- Replace Record<string, any> with Record<string, string> for query params - Fix error handling from catch(err: any) to catch(err: unknown) - Add EntityData interface for proper entity typing - Update EntitySelectionEvent interface with union types - Remove USE_DEXIE_DB conditionals and unused import - Clean up database service calls removing Dexie fallbacks Resolves 9 TypeScript any type warnings, improves type safety across entity selection components, and removes deprecated database migration code.
This commit is contained in:
@@ -1,113 +1,108 @@
|
||||
<template>
|
||||
<div v-if="visible" class="dialog-overlay">
|
||||
<div class="dialog">
|
||||
<h1 class="text-xl font-bold text-center mb-4">
|
||||
{{ customTitle }}
|
||||
</h1>
|
||||
<input
|
||||
v-model="description"
|
||||
type="text"
|
||||
class="block w-full rounded border border-slate-400 mb-2 px-3 py-2"
|
||||
:placeholder="prompt || 'What was given?'"
|
||||
<!-- Step 1: Entity Selection -->
|
||||
<EntitySelectionStep
|
||||
v-show="currentStep === 1"
|
||||
:step-type="stepType"
|
||||
:giver-entity-type="giverEntityType"
|
||||
:recipient-entity-type="recipientEntityType"
|
||||
:show-projects="showProjects"
|
||||
:is-from-project-view="isFromProjectView"
|
||||
:projects="projects"
|
||||
:all-contacts="allContacts"
|
||||
:active-did="activeDid"
|
||||
:all-my-dids="allMyDids"
|
||||
:conflict-checker="wouldCreateConflict"
|
||||
:from-project-id="fromProjectId"
|
||||
:to-project-id="toProjectId"
|
||||
:giver="giver"
|
||||
:receiver="receiver"
|
||||
@entity-selected="handleEntitySelected"
|
||||
@cancel="cancel"
|
||||
/>
|
||||
|
||||
<!-- Step 2: Gift Details -->
|
||||
<GiftDetailsStep
|
||||
v-show="currentStep === 2"
|
||||
:giver="giver"
|
||||
:receiver="receiver"
|
||||
:giver-entity-type="giverEntityType"
|
||||
:recipient-entity-type="recipientEntityType"
|
||||
:description="description"
|
||||
:amount="parseFloat(amountInput) || 0"
|
||||
:unit-code="unitCode"
|
||||
:prompt="prompt"
|
||||
:is-from-project-view="isFromProjectView"
|
||||
:has-conflict="hasPersonConflict"
|
||||
:offer-id="offerId"
|
||||
:from-project-id="fromProjectId"
|
||||
:to-project-id="toProjectId"
|
||||
@update:description="description = $event"
|
||||
@update:amount="handleAmountUpdate"
|
||||
@update:unit-code="unitCode = $event"
|
||||
@edit-entity="handleEditEntity"
|
||||
@explain-data="explainData"
|
||||
@submit="handleSubmit"
|
||||
@cancel="cancel"
|
||||
/>
|
||||
<div class="flex flex-row justify-center">
|
||||
<span
|
||||
class="rounded-l border border-r-0 border-slate-400 bg-slate-200 text-center text-blue-500 px-2 py-2 w-20"
|
||||
@click="changeUnitCode()"
|
||||
>
|
||||
{{ libsUtil.UNIT_SHORT[unitCode] || unitCode }}
|
||||
</span>
|
||||
<div
|
||||
class="border border-r-0 border-slate-400 bg-slate-200 px-4 py-2"
|
||||
@click="amountInput === '0' ? null : decrement()"
|
||||
>
|
||||
<font-awesome icon="chevron-left" />
|
||||
</div>
|
||||
<input
|
||||
id="inputGivenAmount"
|
||||
v-model="amountInput"
|
||||
type="number"
|
||||
class="border border-r-0 border-slate-400 px-2 py-2 text-center w-20"
|
||||
/>
|
||||
<div
|
||||
class="rounded-r border border-slate-400 bg-slate-200 px-4 py-2"
|
||||
@click="increment()"
|
||||
>
|
||||
<font-awesome icon="chevron-right" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-4 flex justify-center">
|
||||
<span>
|
||||
<router-link
|
||||
:to="{
|
||||
name: 'gifted-details',
|
||||
query: {
|
||||
amountInput,
|
||||
description,
|
||||
giverDid: giver?.did,
|
||||
giverName: giver?.name,
|
||||
offerId,
|
||||
fulfillsProjectId: toProjectId,
|
||||
providerProjectId: fromProjectId,
|
||||
recipientDid: receiver?.did,
|
||||
recipientName: receiver?.name,
|
||||
unitCode,
|
||||
},
|
||||
}"
|
||||
class="text-blue-500"
|
||||
>
|
||||
Photo & more options ...
|
||||
</router-link>
|
||||
</span>
|
||||
</div>
|
||||
<p class="text-center mb-2 mt-6 italic">
|
||||
Sign & Send to publish to the world
|
||||
<font-awesome
|
||||
icon="circle-info"
|
||||
class="pl-2 text-blue-500 cursor-pointer"
|
||||
@click="explainData()"
|
||||
/>
|
||||
</p>
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 gap-2">
|
||||
<button
|
||||
class="block w-full text-center text-lg font-bold 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-2 py-3 rounded-md"
|
||||
@click="confirm"
|
||||
>
|
||||
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-md"
|
||||
@click="cancel"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Vue, Component, Prop } from "vue-facing-decorator";
|
||||
import { Vue, Component, Prop, Watch } from "vue-facing-decorator";
|
||||
|
||||
import { NotificationIface } from "../constants/app";
|
||||
import {
|
||||
createAndSubmitGive,
|
||||
didInfo,
|
||||
serverMessageForUser,
|
||||
getHeaders,
|
||||
} from "../libs/endorserServer";
|
||||
import * as libsUtil from "../libs/util";
|
||||
import { db, retrieveSettingsForActiveAccount } from "../db/index";
|
||||
import { Contact } from "../db/tables/contacts";
|
||||
import * as databaseUtil from "../db/databaseUtil";
|
||||
import { retrieveAccountDids } from "../libs/util";
|
||||
import { logger } from "../utils/logger";
|
||||
import { PlatformServiceFactory } from "@/services/PlatformServiceFactory";
|
||||
import EntityIcon from "../components/EntityIcon.vue";
|
||||
import ProjectIcon from "../components/ProjectIcon.vue";
|
||||
import EntitySelectionStep from "../components/EntitySelectionStep.vue";
|
||||
import GiftDetailsStep from "../components/GiftDetailsStep.vue";
|
||||
import { PlanData } from "../interfaces/records";
|
||||
|
||||
@Component
|
||||
@Component({
|
||||
components: {
|
||||
EntityIcon,
|
||||
ProjectIcon,
|
||||
EntitySelectionStep,
|
||||
GiftDetailsStep,
|
||||
},
|
||||
})
|
||||
export default class GiftedDialog extends Vue {
|
||||
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
||||
|
||||
@Prop() fromProjectId = "";
|
||||
@Prop() toProjectId = "";
|
||||
@Prop({ default: false }) showProjects = false;
|
||||
@Prop() isFromProjectView = false;
|
||||
|
||||
@Watch("showProjects")
|
||||
onShowProjectsChange() {
|
||||
this.updateEntityTypes();
|
||||
}
|
||||
|
||||
@Watch("fromProjectId")
|
||||
onFromProjectIdChange() {
|
||||
this.updateEntityTypes();
|
||||
}
|
||||
|
||||
@Watch("toProjectId")
|
||||
onToProjectIdChange() {
|
||||
this.updateEntityTypes();
|
||||
}
|
||||
|
||||
activeDid = "";
|
||||
allContacts: Array<Contact> = [];
|
||||
@@ -124,9 +119,94 @@ export default class GiftedDialog extends Vue {
|
||||
receiver?: libsUtil.GiverReceiverInputInfo;
|
||||
unitCode = "HUR";
|
||||
visible = false;
|
||||
currentStep = 1;
|
||||
|
||||
libsUtil = libsUtil;
|
||||
|
||||
projects: PlanData[] = [];
|
||||
|
||||
didInfo = didInfo;
|
||||
|
||||
// Computed property to help debug template logic
|
||||
get shouldShowProjects() {
|
||||
const result =
|
||||
(this.stepType === "giver" && this.giverEntityType === "project") ||
|
||||
(this.stepType === "recipient" && this.recipientEntityType === "project");
|
||||
return result;
|
||||
}
|
||||
|
||||
// Computed property to check if current selection would create a conflict
|
||||
get hasPersonConflict() {
|
||||
// Only check for conflicts when both entities are persons
|
||||
if (
|
||||
this.giverEntityType !== "person" ||
|
||||
this.recipientEntityType !== "person"
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if giver and recipient are the same person
|
||||
if (
|
||||
this.giver?.did &&
|
||||
this.receiver?.did &&
|
||||
this.giver.did === this.receiver.did
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Computed property to check if a contact would create a conflict when selected
|
||||
wouldCreateConflict(contactDid: string) {
|
||||
// Only check for conflicts when both entities are persons
|
||||
if (
|
||||
this.giverEntityType !== "person" ||
|
||||
this.recipientEntityType !== "person"
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.stepType === "giver") {
|
||||
// If selecting as giver, check if it conflicts with current recipient
|
||||
return this.receiver?.did === contactDid;
|
||||
} else if (this.stepType === "recipient") {
|
||||
// If selecting as recipient, check if it conflicts with current giver
|
||||
return this.giver?.did === contactDid;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
stepType = "giver";
|
||||
giverEntityType = "person" as "person" | "project";
|
||||
recipientEntityType = "person" as "person" | "project";
|
||||
|
||||
updateEntityTypes() {
|
||||
// Reset and set entity types based on current context
|
||||
this.giverEntityType = "person";
|
||||
this.recipientEntityType = "person";
|
||||
|
||||
// Determine entity types based on current context
|
||||
if (this.showProjects) {
|
||||
// HomeView "Project" button or ProjectViewView "Given by This"
|
||||
this.giverEntityType = "project";
|
||||
this.recipientEntityType = "person";
|
||||
} else if (this.fromProjectId) {
|
||||
// ProjectViewView "Given by This" button (project is giver)
|
||||
this.giverEntityType = "project";
|
||||
this.recipientEntityType = "person";
|
||||
} else if (this.toProjectId) {
|
||||
// ProjectViewView "Given to This" button (project is recipient)
|
||||
this.giverEntityType = "person";
|
||||
this.recipientEntityType = "project";
|
||||
} else {
|
||||
// HomeView "Person" button
|
||||
this.giverEntityType = "person";
|
||||
this.recipientEntityType = "person";
|
||||
}
|
||||
}
|
||||
|
||||
async open(
|
||||
giver?: libsUtil.GiverReceiverInputInfo,
|
||||
receiver?: libsUtil.GiverReceiverInputInfo,
|
||||
@@ -139,13 +219,17 @@ export default class GiftedDialog extends Vue {
|
||||
this.giver = giver;
|
||||
this.prompt = prompt || "";
|
||||
this.receiver = receiver;
|
||||
// if we show "given to user" selection, default checkbox to true
|
||||
this.amountInput = "0";
|
||||
this.callbackOnSuccess = callbackOnSuccess;
|
||||
this.offerId = offerId || "";
|
||||
this.currentStep = giver ? 2 : 1;
|
||||
this.stepType = "giver";
|
||||
|
||||
// Update entity types based on current props
|
||||
this.updateEntityTypes();
|
||||
|
||||
try {
|
||||
const settings = await databaseUtil.retrieveSettingsForActiveAccount();
|
||||
let settings = await databaseUtil.retrieveSettingsForActiveAccount();
|
||||
this.apiServer = settings.apiServer || "";
|
||||
this.activeDid = settings.activeDid || "";
|
||||
|
||||
@@ -167,15 +251,27 @@ export default class GiftedDialog extends Vue {
|
||||
this.allContacts,
|
||||
);
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
} catch (err: any) {
|
||||
|
||||
if (
|
||||
this.giverEntityType === "project" ||
|
||||
this.recipientEntityType === "project"
|
||||
) {
|
||||
await this.loadProjects();
|
||||
} else {
|
||||
// Clear projects array when not needed
|
||||
this.projects = [];
|
||||
}
|
||||
} catch (err: unknown) {
|
||||
logger.error("Error retrieving settings from database:", err);
|
||||
this.$notify(
|
||||
{
|
||||
group: "alert",
|
||||
type: "danger",
|
||||
title: "Error",
|
||||
text: err.message || "There was an error retrieving your settings.",
|
||||
text:
|
||||
err instanceof Error
|
||||
? err.message
|
||||
: "There was an error retrieving your settings.",
|
||||
},
|
||||
-1,
|
||||
);
|
||||
@@ -217,6 +313,7 @@ export default class GiftedDialog extends Vue {
|
||||
this.amountInput = "0";
|
||||
this.prompt = "";
|
||||
this.unitCode = "HUR";
|
||||
this.currentStep = 1;
|
||||
}
|
||||
|
||||
async confirm() {
|
||||
@@ -259,6 +356,20 @@ export default class GiftedDialog extends Vue {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check for person conflict
|
||||
if (this.hasPersonConflict) {
|
||||
this.$notify(
|
||||
{
|
||||
group: "alert",
|
||||
type: "danger",
|
||||
title: "Error",
|
||||
text: "You cannot select the same person as both giver and recipient.",
|
||||
},
|
||||
3000,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
this.close();
|
||||
this.$notify(
|
||||
{
|
||||
@@ -297,24 +408,56 @@ export default class GiftedDialog extends Vue {
|
||||
unitCode: string = "HUR",
|
||||
) {
|
||||
try {
|
||||
// Determine the correct parameters based on entity types
|
||||
let fromDid: string | undefined;
|
||||
let toDid: string | undefined;
|
||||
let fulfillsProjectHandleId: string | undefined;
|
||||
let providerPlanHandleId: string | undefined;
|
||||
|
||||
if (
|
||||
this.giverEntityType === "project" &&
|
||||
this.recipientEntityType === "person"
|
||||
) {
|
||||
// Project-to-person gift
|
||||
fromDid = undefined; // No person giver
|
||||
toDid = recipientDid as string; // Person recipient
|
||||
fulfillsProjectHandleId = undefined; // No project recipient
|
||||
providerPlanHandleId = this.giver?.handleId; // Project giver
|
||||
} else if (
|
||||
this.giverEntityType === "person" &&
|
||||
this.recipientEntityType === "project"
|
||||
) {
|
||||
// Person-to-project gift
|
||||
fromDid = giverDid as string; // Person giver
|
||||
toDid = undefined; // No person recipient
|
||||
fulfillsProjectHandleId = this.toProjectId; // Project recipient
|
||||
providerPlanHandleId = undefined; // No project giver
|
||||
} else {
|
||||
// Person-to-person gift
|
||||
fromDid = giverDid as string;
|
||||
toDid = recipientDid as string;
|
||||
fulfillsProjectHandleId = undefined;
|
||||
providerPlanHandleId = undefined;
|
||||
}
|
||||
|
||||
const result = await createAndSubmitGive(
|
||||
this.axios,
|
||||
this.apiServer,
|
||||
this.activeDid,
|
||||
giverDid as string,
|
||||
recipientDid as string,
|
||||
fromDid,
|
||||
toDid,
|
||||
description,
|
||||
amount,
|
||||
unitCode,
|
||||
this.toProjectId,
|
||||
fulfillsProjectHandleId,
|
||||
this.offerId,
|
||||
false,
|
||||
undefined,
|
||||
this.fromProjectId,
|
||||
providerPlanHandleId,
|
||||
);
|
||||
|
||||
if (!result.success) {
|
||||
const errorMessage = result.error;
|
||||
const errorMessage = this.getGiveCreationErrorMessage(result);
|
||||
logger.error("Error with give creation result:", result);
|
||||
this.$notify(
|
||||
{
|
||||
@@ -360,6 +503,19 @@ export default class GiftedDialog extends Vue {
|
||||
|
||||
// Helper functions for readability
|
||||
|
||||
/**
|
||||
* @param result direct response eg. ErrorResult or SuccessResult (potentially with embedded "data")
|
||||
* @returns best guess at an error message
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
getGiveCreationErrorMessage(result: any) {
|
||||
return (
|
||||
result.error?.userMessage ||
|
||||
result.error?.error ||
|
||||
result.response?.data?.error?.message
|
||||
);
|
||||
}
|
||||
|
||||
explainData() {
|
||||
this.$notify(
|
||||
{
|
||||
@@ -371,6 +527,174 @@ export default class GiftedDialog extends Vue {
|
||||
-1,
|
||||
);
|
||||
}
|
||||
|
||||
selectGiver(contact?: Contact) {
|
||||
if (contact) {
|
||||
this.giver = {
|
||||
did: contact.did,
|
||||
name: contact.name || contact.did,
|
||||
};
|
||||
} else {
|
||||
this.giver = {
|
||||
did: "",
|
||||
name: "Unnamed",
|
||||
};
|
||||
}
|
||||
this.currentStep = 2;
|
||||
}
|
||||
|
||||
goBackToStep1(step: string) {
|
||||
this.stepType = step;
|
||||
this.currentStep = 1;
|
||||
}
|
||||
|
||||
async loadProjects() {
|
||||
try {
|
||||
const response = await fetch(this.apiServer + "/api/v2/report/plans", {
|
||||
method: "GET",
|
||||
headers: await getHeaders(this.activeDid),
|
||||
});
|
||||
|
||||
if (response.status !== 200) {
|
||||
throw new Error("Failed to load projects");
|
||||
}
|
||||
|
||||
const results = await response.json();
|
||||
if (results.data) {
|
||||
this.projects = results.data;
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error("Error loading projects:", error);
|
||||
this.$notify(
|
||||
{
|
||||
group: "alert",
|
||||
type: "danger",
|
||||
title: "Error",
|
||||
text: "Failed to load projects",
|
||||
},
|
||||
3000,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
selectProject(project: PlanData) {
|
||||
this.giver = {
|
||||
did: project.handleId,
|
||||
name: project.name,
|
||||
image: project.image,
|
||||
handleId: project.handleId,
|
||||
};
|
||||
this.receiver = {
|
||||
did: this.activeDid,
|
||||
name: "You",
|
||||
};
|
||||
this.currentStep = 2;
|
||||
}
|
||||
|
||||
selectRecipient(contact?: Contact) {
|
||||
if (contact) {
|
||||
this.receiver = {
|
||||
did: contact.did,
|
||||
name: contact.name || contact.did,
|
||||
};
|
||||
} else {
|
||||
this.receiver = {
|
||||
did: "",
|
||||
name: "Unnamed",
|
||||
};
|
||||
}
|
||||
this.currentStep = 2;
|
||||
}
|
||||
|
||||
selectRecipientProject(project: PlanData) {
|
||||
this.receiver = {
|
||||
did: project.handleId,
|
||||
name: project.name,
|
||||
image: project.image,
|
||||
handleId: project.handleId,
|
||||
};
|
||||
this.currentStep = 2;
|
||||
}
|
||||
|
||||
// Computed property for the query parameters
|
||||
get giftedDetailsQuery() {
|
||||
return {
|
||||
amountInput: this.amountInput,
|
||||
description: this.description,
|
||||
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.unitCode,
|
||||
};
|
||||
}
|
||||
|
||||
// New event handlers for component integration
|
||||
|
||||
/**
|
||||
* Handle entity selection from EntitySelectionStep
|
||||
* @param entity - The selected entity (person or project)
|
||||
*/
|
||||
handleEntitySelected(entity: {
|
||||
type: "person" | "project";
|
||||
data: Contact | PlanData;
|
||||
}) {
|
||||
if (entity.type === "person") {
|
||||
const contact = entity.data as Contact;
|
||||
if (this.stepType === "giver") {
|
||||
this.selectGiver(contact);
|
||||
} else {
|
||||
this.selectRecipient(contact);
|
||||
}
|
||||
} else {
|
||||
const project = entity.data as PlanData;
|
||||
if (this.stepType === "giver") {
|
||||
this.selectProject(project);
|
||||
} else {
|
||||
this.selectRecipientProject(project);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle edit entity request from GiftDetailsStep
|
||||
* @param entityType - 'giver' or 'recipient'
|
||||
*/
|
||||
handleEditEntity(entityType: "giver" | "recipient") {
|
||||
this.goBackToStep1(entityType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle form submission from GiftDetailsStep
|
||||
*/
|
||||
handleSubmit() {
|
||||
this.confirm();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle amount update from GiftDetailsStep
|
||||
*/
|
||||
handleAmountUpdate(newAmount: number) {
|
||||
logger.debug("[GiftedDialog] handleAmountUpdate() called", {
|
||||
oldAmount: this.amountInput,
|
||||
newAmount,
|
||||
});
|
||||
this.amountInput = newAmount.toString();
|
||||
logger.debug("[GiftedDialog] handleAmountUpdate() - amountInput updated", {
|
||||
amountInput: this.amountInput,
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user