@trustless-work/blocks 0.0.7 → 0.0.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/index.js +485 -17
- package/package.json +1 -1
- package/templates/escrows/details/Actions.tsx +144 -149
- package/templates/escrows/details/Entities.tsx +1 -1
- package/templates/escrows/details/EntityCard.tsx +1 -3
- package/templates/escrows/details/EscrowDetailDialog.tsx +16 -16
- package/templates/escrows/details/GeneralInformation.tsx +19 -22
- package/templates/escrows/details/MilestoneCard.tsx +46 -47
- package/templates/escrows/details/MilestoneDetailDialog.tsx +1 -2
- package/templates/escrows/details/Milestones.tsx +0 -5
- package/templates/escrows/details/SuccessReleaseDialog.tsx +4 -6
- package/templates/escrows/escrows-by-role/cards/EscrowsCards.tsx +84 -49
- package/templates/escrows/escrows-by-role/cards/Filters.tsx +3 -5
- package/templates/escrows/escrows-by-role/table/EscrowsTable.tsx +8 -26
- package/templates/escrows/escrows-by-role/table/Filters.tsx +3 -5
- package/templates/escrows/escrows-by-signer/cards/EscrowsCards.tsx +89 -55
- package/templates/escrows/escrows-by-signer/cards/Filters.tsx +3 -5
- package/templates/escrows/escrows-by-signer/table/EscrowsTable.tsx +8 -24
- package/templates/escrows/escrows-by-signer/table/Filters.tsx +3 -5
- package/templates/escrows/multi-release/dispute-milestone/button/DisputeEscrow.tsx +98 -0
- package/templates/escrows/multi-release/initialize-escrow/dialog/InitializeEscrow.tsx +528 -0
- package/templates/escrows/multi-release/initialize-escrow/form/InitializeEscrow.tsx +506 -0
- package/templates/escrows/multi-release/initialize-escrow/shared/schema.ts +179 -0
- package/templates/escrows/multi-release/initialize-escrow/shared/useInitializeEscrow.ts +175 -0
- package/templates/escrows/multi-release/release-milestone/button/ReleaseEscrow.tsx +116 -0
- package/templates/escrows/multi-release/resolve-dispute/button/ResolveDispute.tsx +122 -0
- package/templates/escrows/multi-release/resolve-dispute/dialog/ResolveDispute.tsx +178 -0
- package/templates/escrows/multi-release/resolve-dispute/form/ResolveDispute.tsx +156 -0
- package/templates/escrows/multi-release/resolve-dispute/shared/schema.ts +85 -0
- package/templates/escrows/multi-release/resolve-dispute/shared/useResolveDispute.ts +105 -0
- package/templates/escrows/multi-release/update-escrow/dialog/UpdateEscrow.tsx +471 -0
- package/templates/escrows/multi-release/update-escrow/form/UpdateEscrow.tsx +449 -0
- package/templates/escrows/multi-release/update-escrow/shared/schema.ts +152 -0
- package/templates/escrows/multi-release/update-escrow/shared/useUpdateEscrow.ts +254 -0
- package/templates/escrows/{single-release → single-multi-release}/approve-milestone/button/ApproveMilestone.tsx +20 -7
- package/templates/escrows/{single-release → single-multi-release}/approve-milestone/dialog/ApproveMilestone.tsx +3 -3
- package/templates/escrows/{single-release → single-multi-release}/approve-milestone/form/ApproveMilestone.tsx +3 -3
- package/templates/escrows/{single-release/approve-milestone/shared → single-multi-release/approve-milestone}/useApproveMilestone.ts +16 -16
- package/templates/escrows/{single-release → single-multi-release}/change-milestone-status/button/ChangeMilestoneStatus.tsx +4 -4
- package/templates/escrows/{single-release → single-multi-release}/change-milestone-status/dialog/ChangeMilestoneStatus.tsx +4 -4
- package/templates/escrows/{single-release → single-multi-release}/change-milestone-status/form/ChangeMilestoneStatus.tsx +3 -3
- package/templates/escrows/{single-release/change-milestone-status/shared → single-multi-release/change-milestone-status}/useChangeMilestoneStatus.ts +1 -1
- package/templates/escrows/{single-release → single-multi-release}/fund-escrow/button/FundEscrow.tsx +3 -3
- package/templates/escrows/{single-release → single-multi-release}/fund-escrow/dialog/FundEscrow.tsx +3 -3
- package/templates/escrows/{single-release → single-multi-release}/fund-escrow/form/FundEscrow.tsx +3 -3
- package/templates/escrows/{single-release/fund-escrow/shared → single-multi-release/fund-escrow}/useFundEscrow.ts +1 -1
- package/templates/escrows/single-release/dispute-escrow/button/DisputeEscrow.tsx +2 -2
- package/templates/escrows/single-release/initialize-escrow/dialog/InitializeEscrow.tsx +14 -6
- package/templates/escrows/single-release/initialize-escrow/form/InitializeEscrow.tsx +14 -6
- package/templates/escrows/single-release/initialize-escrow/shared/schema.ts +0 -57
- package/templates/escrows/single-release/initialize-escrow/shared/useInitializeEscrow.ts +42 -1
- package/templates/escrows/single-release/release-escrow/button/ReleaseEscrow.tsx +2 -2
- package/templates/escrows/single-release/resolve-dispute/button/ResolveDispute.tsx +3 -3
- package/templates/escrows/single-release/resolve-dispute/dialog/ResolveDispute.tsx +3 -6
- package/templates/escrows/single-release/resolve-dispute/form/ResolveDispute.tsx +2 -2
- package/templates/escrows/single-release/resolve-dispute/shared/useResolveDispute.ts +14 -1
- package/templates/escrows/single-release/update-escrow/dialog/UpdateEscrow.tsx +2 -2
- package/templates/escrows/single-release/update-escrow/form/UpdateEscrow.tsx +2 -2
- package/templates/escrows/single-release/update-escrow/shared/useUpdateEscrow.ts +12 -7
- package/templates/providers/EscrowDialogsProvider.tsx +1 -3
- package/templates/providers/EscrowProvider.tsx +27 -4
- package/templates/providers/TrustlessWork.tsx +1 -1
- package/templates/escrows/details/ProgressEscrow.tsx +0 -191
- /package/templates/escrows/{single-release/approve-milestone/shared → single-multi-release/approve-milestone}/schema.ts +0 -0
- /package/templates/escrows/{single-release/change-milestone-status/shared → single-multi-release/change-milestone-status}/schema.ts +0 -0
- /package/templates/escrows/{single-release/fund-escrow/shared → single-multi-release/fund-escrow}/schema.ts +0 -0
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { useForm } from "react-hook-form";
|
|
3
|
+
import { zodResolver } from "@hookform/resolvers/zod";
|
|
4
|
+
import { useInitializeEscrowSchema } from "./schema";
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
import {
|
|
7
|
+
InitializeMultiReleaseEscrowPayload,
|
|
8
|
+
InitializeMultiReleaseEscrowResponse,
|
|
9
|
+
} from "@trustless-work/escrow/types";
|
|
10
|
+
import { toast } from "sonner";
|
|
11
|
+
import { useWalletContext } from "@/components/tw-blocks/wallet-kit/WalletProvider";
|
|
12
|
+
import { useEscrowsMutations } from "@/components/tw-blocks/tanstack/useEscrowsMutations";
|
|
13
|
+
import {
|
|
14
|
+
ErrorResponse,
|
|
15
|
+
handleError,
|
|
16
|
+
} from "@/components/tw-blocks/handle-errors/handle";
|
|
17
|
+
import { useEscrowContext } from "@/components/tw-blocks/providers/EscrowProvider";
|
|
18
|
+
import { trustlineOptions } from "@/components/tw-blocks/wallet-kit/trustlines";
|
|
19
|
+
|
|
20
|
+
export function useInitializeEscrow() {
|
|
21
|
+
const [isSubmitting, setIsSubmitting] = React.useState(false);
|
|
22
|
+
|
|
23
|
+
const { getMultiReleaseFormSchema } = useInitializeEscrowSchema();
|
|
24
|
+
const formSchema = getMultiReleaseFormSchema();
|
|
25
|
+
const { setSelectedEscrow } = useEscrowContext();
|
|
26
|
+
|
|
27
|
+
const { walletAddress } = useWalletContext();
|
|
28
|
+
const { deployEscrow } = useEscrowsMutations();
|
|
29
|
+
|
|
30
|
+
const form = useForm<z.infer<typeof formSchema>>({
|
|
31
|
+
resolver: zodResolver(formSchema),
|
|
32
|
+
defaultValues: {
|
|
33
|
+
engagementId: "",
|
|
34
|
+
title: "",
|
|
35
|
+
description: "",
|
|
36
|
+
platformFee: undefined,
|
|
37
|
+
receiverMemo: "",
|
|
38
|
+
trustline: {
|
|
39
|
+
address: "",
|
|
40
|
+
decimals: 10000000,
|
|
41
|
+
},
|
|
42
|
+
roles: {
|
|
43
|
+
approver: "",
|
|
44
|
+
serviceProvider: "",
|
|
45
|
+
platformAddress: "",
|
|
46
|
+
receiver: "",
|
|
47
|
+
releaseSigner: "",
|
|
48
|
+
disputeResolver: "",
|
|
49
|
+
},
|
|
50
|
+
milestones: [{ description: "", amount: "" }],
|
|
51
|
+
},
|
|
52
|
+
mode: "onChange",
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
const milestones = form.watch("milestones");
|
|
56
|
+
const isAnyMilestoneEmpty = milestones.some(
|
|
57
|
+
(milestone) => milestone.description === ""
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
const handleAddMilestone = () => {
|
|
61
|
+
const currentMilestones = form.getValues("milestones");
|
|
62
|
+
const updatedMilestones = [
|
|
63
|
+
...currentMilestones,
|
|
64
|
+
{ description: "", amount: "" },
|
|
65
|
+
];
|
|
66
|
+
form.setValue("milestones", updatedMilestones);
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const handleRemoveMilestone = (index: number) => {
|
|
70
|
+
const currentMilestones = form.getValues("milestones");
|
|
71
|
+
const updatedMilestones = currentMilestones.filter((_, i) => i !== index);
|
|
72
|
+
form.setValue("milestones", updatedMilestones);
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const fillTemplateForm = () => {
|
|
76
|
+
const usdc = trustlineOptions.find((t) => t.label === "USDC");
|
|
77
|
+
|
|
78
|
+
const templateData: z.infer<typeof formSchema> = {
|
|
79
|
+
engagementId: "ENG-001",
|
|
80
|
+
title: "Design Landing Page",
|
|
81
|
+
description: "Landing for the new product of the company.",
|
|
82
|
+
platformFee: 5,
|
|
83
|
+
receiverMemo: "123",
|
|
84
|
+
trustline: {
|
|
85
|
+
address: usdc?.value || "",
|
|
86
|
+
decimals: 10000000,
|
|
87
|
+
},
|
|
88
|
+
roles: {
|
|
89
|
+
approver: walletAddress || "",
|
|
90
|
+
serviceProvider: walletAddress || "",
|
|
91
|
+
platformAddress: walletAddress || "",
|
|
92
|
+
receiver: walletAddress || "",
|
|
93
|
+
releaseSigner: walletAddress || "",
|
|
94
|
+
disputeResolver: walletAddress || "",
|
|
95
|
+
},
|
|
96
|
+
milestones: [
|
|
97
|
+
{ description: "Design the wireframe", amount: 2 },
|
|
98
|
+
{ description: "Develop the wireframe", amount: 2 },
|
|
99
|
+
{ description: "Deploy the wireframe", amount: 2 },
|
|
100
|
+
],
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
// Set form values
|
|
104
|
+
Object.entries(templateData).forEach(([key, value]) => {
|
|
105
|
+
form.setValue(key as keyof z.infer<typeof formSchema>, value);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
// Explicitly set the trustline field
|
|
109
|
+
form.setValue("trustline.address", usdc?.value || "");
|
|
110
|
+
form.setValue("trustline.decimals", 10000000);
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
const handleSubmit = form.handleSubmit(async (payload) => {
|
|
114
|
+
try {
|
|
115
|
+
setIsSubmitting(true);
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Create the final payload for the initialize escrow mutation
|
|
119
|
+
*
|
|
120
|
+
* @param payload - The payload from the form
|
|
121
|
+
* @returns The final payload for the initialize escrow mutation
|
|
122
|
+
*/
|
|
123
|
+
const finalPayload: InitializeMultiReleaseEscrowPayload = {
|
|
124
|
+
...payload,
|
|
125
|
+
platformFee:
|
|
126
|
+
typeof payload.platformFee === "string"
|
|
127
|
+
? Number(payload.platformFee)
|
|
128
|
+
: payload.platformFee,
|
|
129
|
+
receiverMemo: Number(payload.receiverMemo) ?? 0,
|
|
130
|
+
signer: walletAddress || "",
|
|
131
|
+
milestones: payload.milestones.map((milestone) => ({
|
|
132
|
+
...milestone,
|
|
133
|
+
amount:
|
|
134
|
+
typeof milestone.amount === "string"
|
|
135
|
+
? Number(milestone.amount)
|
|
136
|
+
: milestone.amount,
|
|
137
|
+
})),
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Call the initialize escrow mutation
|
|
142
|
+
*
|
|
143
|
+
* @param payload - The final payload for the initialize escrow mutation
|
|
144
|
+
* @param type - The type of the escrow
|
|
145
|
+
* @param address - The address of the escrow
|
|
146
|
+
*/
|
|
147
|
+
const response: InitializeMultiReleaseEscrowResponse =
|
|
148
|
+
(await deployEscrow.mutateAsync({
|
|
149
|
+
payload: finalPayload,
|
|
150
|
+
type: "multi-release",
|
|
151
|
+
address: walletAddress || "",
|
|
152
|
+
})) as InitializeMultiReleaseEscrowResponse;
|
|
153
|
+
|
|
154
|
+
toast.success("Escrow initialized successfully");
|
|
155
|
+
|
|
156
|
+
setSelectedEscrow({ ...finalPayload, contractId: response.contractId });
|
|
157
|
+
} catch (error) {
|
|
158
|
+
toast.error(handleError(error as ErrorResponse).message);
|
|
159
|
+
} finally {
|
|
160
|
+
setIsSubmitting(false);
|
|
161
|
+
form.reset();
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
return {
|
|
166
|
+
form,
|
|
167
|
+
isSubmitting,
|
|
168
|
+
milestones,
|
|
169
|
+
isAnyMilestoneEmpty,
|
|
170
|
+
fillTemplateForm,
|
|
171
|
+
handleSubmit,
|
|
172
|
+
handleAddMilestone,
|
|
173
|
+
handleRemoveMilestone,
|
|
174
|
+
};
|
|
175
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { Button } from "__UI_BASE__/button";
|
|
3
|
+
import { useEscrowsMutations } from "@/components/tw-blocks/tanstack/useEscrowsMutations";
|
|
4
|
+
import { useWalletContext } from "@/components/tw-blocks/wallet-kit/WalletProvider";
|
|
5
|
+
import {
|
|
6
|
+
MultiReleaseReleaseFundsPayload,
|
|
7
|
+
MultiReleaseMilestone,
|
|
8
|
+
} from "@trustless-work/escrow/types";
|
|
9
|
+
import { toast } from "sonner";
|
|
10
|
+
import {
|
|
11
|
+
ErrorResponse,
|
|
12
|
+
handleError,
|
|
13
|
+
} from "@/components/tw-blocks/handle-errors/handle";
|
|
14
|
+
import { useEscrowContext } from "@/components/tw-blocks/providers/EscrowProvider";
|
|
15
|
+
import { useEscrowDialogs } from "@/components/tw-blocks/providers/EscrowDialogsProvider";
|
|
16
|
+
import { useEscrowAmountContext } from "@/components/tw-blocks/providers/EscrowAmountProvider";
|
|
17
|
+
import { Loader2 } from "lucide-react";
|
|
18
|
+
|
|
19
|
+
type ReleaseEscrowButtonProps = {
|
|
20
|
+
milestoneIndex: number | string;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export const ReleaseEscrowButton = ({
|
|
24
|
+
milestoneIndex,
|
|
25
|
+
}: ReleaseEscrowButtonProps) => {
|
|
26
|
+
const { releaseFunds } = useEscrowsMutations();
|
|
27
|
+
const { selectedEscrow, updateEscrow } = useEscrowContext();
|
|
28
|
+
const dialogStates = useEscrowDialogs();
|
|
29
|
+
const { setAmounts } = useEscrowAmountContext();
|
|
30
|
+
const { walletAddress } = useWalletContext();
|
|
31
|
+
const [isSubmitting, setIsSubmitting] = React.useState(false);
|
|
32
|
+
|
|
33
|
+
async function handleClick() {
|
|
34
|
+
try {
|
|
35
|
+
setIsSubmitting(true);
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Create the payload for the release escrow mutation
|
|
39
|
+
*
|
|
40
|
+
* @returns The payload for the release escrow mutation
|
|
41
|
+
*/
|
|
42
|
+
const payload: MultiReleaseReleaseFundsPayload = {
|
|
43
|
+
contractId: selectedEscrow?.contractId || "",
|
|
44
|
+
releaseSigner: walletAddress || "",
|
|
45
|
+
milestoneIndex: String(milestoneIndex),
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Call the release escrow mutation
|
|
50
|
+
*
|
|
51
|
+
* @param payload - The payload for the release escrow mutation
|
|
52
|
+
* @param type - The type of the escrow
|
|
53
|
+
* @param address - The address of the escrow
|
|
54
|
+
*/
|
|
55
|
+
await releaseFunds.mutateAsync({
|
|
56
|
+
payload,
|
|
57
|
+
type: "multi-release",
|
|
58
|
+
address: walletAddress || "",
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
toast.success("Escrow released successfully");
|
|
62
|
+
|
|
63
|
+
// Ensure amounts are up to date for the success dialog
|
|
64
|
+
if (selectedEscrow) {
|
|
65
|
+
const milestone = selectedEscrow.milestones?.[Number(milestoneIndex)];
|
|
66
|
+
const releasedAmount = Number(
|
|
67
|
+
(milestone as MultiReleaseMilestone | undefined)?.amount || 0
|
|
68
|
+
);
|
|
69
|
+
const platformFee = Number(selectedEscrow.platformFee || 0);
|
|
70
|
+
setAmounts(releasedAmount, platformFee);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
updateEscrow({
|
|
74
|
+
...selectedEscrow,
|
|
75
|
+
milestones: selectedEscrow?.milestones.map((milestone, index) => {
|
|
76
|
+
if (index === Number(milestoneIndex)) {
|
|
77
|
+
return {
|
|
78
|
+
...milestone,
|
|
79
|
+
flags: {
|
|
80
|
+
...(milestone as MultiReleaseMilestone).flags,
|
|
81
|
+
released: true,
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
return milestone;
|
|
86
|
+
}),
|
|
87
|
+
balance: (selectedEscrow?.balance || 0) - (selectedEscrow?.amount || 0),
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
// Open success dialog
|
|
91
|
+
dialogStates.successRelease.setIsOpen(true);
|
|
92
|
+
} catch (error) {
|
|
93
|
+
toast.error(handleError(error as ErrorResponse).message);
|
|
94
|
+
} finally {
|
|
95
|
+
setIsSubmitting(false);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return (
|
|
100
|
+
<Button
|
|
101
|
+
type="button"
|
|
102
|
+
disabled={isSubmitting}
|
|
103
|
+
onClick={handleClick}
|
|
104
|
+
className="cursor-pointer w-full"
|
|
105
|
+
>
|
|
106
|
+
{isSubmitting ? (
|
|
107
|
+
<div className="flex items-center">
|
|
108
|
+
<Loader2 className="h-5 w-5 animate-spin" />
|
|
109
|
+
<span className="ml-2">Releasing...</span>
|
|
110
|
+
</div>
|
|
111
|
+
) : (
|
|
112
|
+
"Release Milestone"
|
|
113
|
+
)}
|
|
114
|
+
</Button>
|
|
115
|
+
);
|
|
116
|
+
};
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { Button } from "__UI_BASE__/button";
|
|
3
|
+
import { useEscrowsMutations } from "@/components/tw-blocks/tanstack/useEscrowsMutations";
|
|
4
|
+
import { useWalletContext } from "@/components/tw-blocks/wallet-kit/WalletProvider";
|
|
5
|
+
import {
|
|
6
|
+
MultiReleaseResolveDisputePayload,
|
|
7
|
+
MultiReleaseMilestone,
|
|
8
|
+
} from "@trustless-work/escrow/types";
|
|
9
|
+
import { toast } from "sonner";
|
|
10
|
+
import {
|
|
11
|
+
ErrorResponse,
|
|
12
|
+
handleError,
|
|
13
|
+
} from "@/components/tw-blocks/handle-errors/handle";
|
|
14
|
+
import { useEscrowContext } from "@/components/tw-blocks/providers/EscrowProvider";
|
|
15
|
+
import { Loader2 } from "lucide-react";
|
|
16
|
+
|
|
17
|
+
type ResolveDisputeButtonProps = {
|
|
18
|
+
approverFunds: number;
|
|
19
|
+
receiverFunds: number;
|
|
20
|
+
milestoneIndex: number | string;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export const ResolveDisputeButton = ({
|
|
24
|
+
approverFunds,
|
|
25
|
+
receiverFunds,
|
|
26
|
+
milestoneIndex,
|
|
27
|
+
}: ResolveDisputeButtonProps) => {
|
|
28
|
+
const { resolveDispute } = useEscrowsMutations();
|
|
29
|
+
const { selectedEscrow, updateEscrow } = useEscrowContext();
|
|
30
|
+
const { walletAddress } = useWalletContext();
|
|
31
|
+
const [isSubmitting, setIsSubmitting] = React.useState(false);
|
|
32
|
+
|
|
33
|
+
async function handleClick() {
|
|
34
|
+
try {
|
|
35
|
+
if (
|
|
36
|
+
approverFunds == null ||
|
|
37
|
+
Number.isNaN(approverFunds) ||
|
|
38
|
+
receiverFunds == null ||
|
|
39
|
+
Number.isNaN(receiverFunds)
|
|
40
|
+
) {
|
|
41
|
+
toast.error("Both amounts are required");
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (approverFunds < 0 || receiverFunds < 0) {
|
|
46
|
+
toast.error("Amounts must be >= 0");
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
setIsSubmitting(true);
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Create the payload for the resolve dispute mutation
|
|
54
|
+
*
|
|
55
|
+
* @returns The payload for the resolve dispute mutation
|
|
56
|
+
*/
|
|
57
|
+
const payload: MultiReleaseResolveDisputePayload = {
|
|
58
|
+
contractId: selectedEscrow?.contractId || "",
|
|
59
|
+
disputeResolver: walletAddress || "",
|
|
60
|
+
approverFunds: Number(approverFunds),
|
|
61
|
+
receiverFunds: Number(receiverFunds),
|
|
62
|
+
milestoneIndex: String(milestoneIndex),
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Call the resolve dispute mutation
|
|
67
|
+
*
|
|
68
|
+
* @param payload - The payload for the resolve dispute mutation
|
|
69
|
+
* @param type - The type of the escrow
|
|
70
|
+
* @param address - The address of the escrow
|
|
71
|
+
*/
|
|
72
|
+
await resolveDispute.mutateAsync({
|
|
73
|
+
payload,
|
|
74
|
+
type: "multi-release",
|
|
75
|
+
address: walletAddress || "",
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
toast.success("Dispute resolved successfully");
|
|
79
|
+
updateEscrow({
|
|
80
|
+
...selectedEscrow,
|
|
81
|
+
milestones: selectedEscrow?.milestones.map((milestone, index) => {
|
|
82
|
+
if (index === Number(milestoneIndex)) {
|
|
83
|
+
return {
|
|
84
|
+
...milestone,
|
|
85
|
+
flags: {
|
|
86
|
+
...(milestone as MultiReleaseMilestone).flags,
|
|
87
|
+
disputed: false,
|
|
88
|
+
resolved: true,
|
|
89
|
+
},
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
return milestone;
|
|
93
|
+
}),
|
|
94
|
+
balance:
|
|
95
|
+
selectedEscrow?.balance ||
|
|
96
|
+
Number(approverFunds) + Number(receiverFunds),
|
|
97
|
+
});
|
|
98
|
+
} catch (error) {
|
|
99
|
+
toast.error(handleError(error as ErrorResponse).message);
|
|
100
|
+
} finally {
|
|
101
|
+
setIsSubmitting(false);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return (
|
|
106
|
+
<Button
|
|
107
|
+
type="button"
|
|
108
|
+
disabled={isSubmitting}
|
|
109
|
+
onClick={handleClick}
|
|
110
|
+
className="cursor-pointer w-full"
|
|
111
|
+
>
|
|
112
|
+
{isSubmitting ? (
|
|
113
|
+
<div className="flex items-center">
|
|
114
|
+
<Loader2 className="h-5 w-5 animate-spin" />
|
|
115
|
+
<span className="ml-2">Resolving...</span>
|
|
116
|
+
</div>
|
|
117
|
+
) : (
|
|
118
|
+
"Resolve Dispute"
|
|
119
|
+
)}
|
|
120
|
+
</Button>
|
|
121
|
+
);
|
|
122
|
+
};
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import {
|
|
3
|
+
Form,
|
|
4
|
+
FormField,
|
|
5
|
+
FormItem,
|
|
6
|
+
FormLabel,
|
|
7
|
+
FormControl,
|
|
8
|
+
FormMessage,
|
|
9
|
+
} from "__UI_BASE__/form";
|
|
10
|
+
import { Input } from "__UI_BASE__/input";
|
|
11
|
+
import { Button } from "__UI_BASE__/button";
|
|
12
|
+
import {
|
|
13
|
+
Dialog,
|
|
14
|
+
DialogContent,
|
|
15
|
+
DialogHeader,
|
|
16
|
+
DialogTitle,
|
|
17
|
+
DialogTrigger,
|
|
18
|
+
} from "__UI_BASE__/dialog";
|
|
19
|
+
import { Loader2 } from "lucide-react";
|
|
20
|
+
import { useResolveDispute } from "./useResolveDispute";
|
|
21
|
+
import { useEscrowContext } from "@/components/tw-blocks/providers/EscrowProvider";
|
|
22
|
+
import { formatCurrency } from "../../../../helpers/format.helper";
|
|
23
|
+
import {
|
|
24
|
+
Select,
|
|
25
|
+
SelectContent,
|
|
26
|
+
SelectItem,
|
|
27
|
+
SelectTrigger,
|
|
28
|
+
SelectValue,
|
|
29
|
+
} from "__UI_BASE__/select";
|
|
30
|
+
|
|
31
|
+
export const ResolveDisputeDialog = ({
|
|
32
|
+
showSelectMilestone = false,
|
|
33
|
+
milestoneIndex,
|
|
34
|
+
}: {
|
|
35
|
+
showSelectMilestone?: boolean;
|
|
36
|
+
milestoneIndex?: number | string;
|
|
37
|
+
}) => {
|
|
38
|
+
const { form, handleSubmit, isSubmitting, totalAmount } = useResolveDispute();
|
|
39
|
+
const { selectedEscrow } = useEscrowContext();
|
|
40
|
+
|
|
41
|
+
React.useEffect(() => {
|
|
42
|
+
if (
|
|
43
|
+
!showSelectMilestone &&
|
|
44
|
+
milestoneIndex !== undefined &&
|
|
45
|
+
milestoneIndex !== null
|
|
46
|
+
) {
|
|
47
|
+
form.setValue("milestoneIndex", String(milestoneIndex));
|
|
48
|
+
}
|
|
49
|
+
}, [showSelectMilestone, milestoneIndex, form]);
|
|
50
|
+
|
|
51
|
+
return (
|
|
52
|
+
<Dialog>
|
|
53
|
+
<DialogTrigger asChild>
|
|
54
|
+
<Button type="button" className="cursor-pointer w-full">
|
|
55
|
+
Resolve Dispute
|
|
56
|
+
</Button>
|
|
57
|
+
</DialogTrigger>
|
|
58
|
+
<DialogContent>
|
|
59
|
+
<DialogHeader>
|
|
60
|
+
<DialogTitle>Resolve Dispute</DialogTitle>
|
|
61
|
+
</DialogHeader>
|
|
62
|
+
<Form {...form}>
|
|
63
|
+
<form onSubmit={handleSubmit}>
|
|
64
|
+
{showSelectMilestone && (
|
|
65
|
+
<FormField
|
|
66
|
+
control={form.control}
|
|
67
|
+
name="milestoneIndex"
|
|
68
|
+
render={({ field }) => (
|
|
69
|
+
<FormItem>
|
|
70
|
+
<FormLabel className="flex items-center">
|
|
71
|
+
Milestone
|
|
72
|
+
<span className="text-destructive ml-1">*</span>
|
|
73
|
+
</FormLabel>
|
|
74
|
+
<FormControl>
|
|
75
|
+
<Select
|
|
76
|
+
value={field.value}
|
|
77
|
+
onValueChange={(e) => {
|
|
78
|
+
field.onChange(e);
|
|
79
|
+
}}
|
|
80
|
+
>
|
|
81
|
+
<SelectTrigger className="w-full">
|
|
82
|
+
<SelectValue placeholder="Select milestone" />
|
|
83
|
+
</SelectTrigger>
|
|
84
|
+
<SelectContent>
|
|
85
|
+
{(selectedEscrow?.milestones || []).map((m, idx) => (
|
|
86
|
+
<SelectItem key={`ms-${idx}`} value={String(idx)}>
|
|
87
|
+
{m?.description || `Milestone ${idx + 1}`}
|
|
88
|
+
</SelectItem>
|
|
89
|
+
))}
|
|
90
|
+
</SelectContent>
|
|
91
|
+
</Select>
|
|
92
|
+
</FormControl>
|
|
93
|
+
<FormMessage />
|
|
94
|
+
</FormItem>
|
|
95
|
+
)}
|
|
96
|
+
/>
|
|
97
|
+
)}
|
|
98
|
+
|
|
99
|
+
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
|
|
100
|
+
<FormField
|
|
101
|
+
control={form.control}
|
|
102
|
+
name="approverFunds"
|
|
103
|
+
render={({ field }) => (
|
|
104
|
+
<FormItem>
|
|
105
|
+
<FormLabel>Approver Funds</FormLabel>
|
|
106
|
+
<FormControl>
|
|
107
|
+
<Input
|
|
108
|
+
type="text"
|
|
109
|
+
inputMode="decimal"
|
|
110
|
+
placeholder="Enter approver funds"
|
|
111
|
+
value={field.value as unknown as string}
|
|
112
|
+
onChange={(e) => field.onChange(e.target.value)}
|
|
113
|
+
/>
|
|
114
|
+
</FormControl>
|
|
115
|
+
<FormMessage />
|
|
116
|
+
</FormItem>
|
|
117
|
+
)}
|
|
118
|
+
/>
|
|
119
|
+
|
|
120
|
+
<FormField
|
|
121
|
+
control={form.control}
|
|
122
|
+
name="receiverFunds"
|
|
123
|
+
render={({ field }) => (
|
|
124
|
+
<FormItem>
|
|
125
|
+
<FormLabel>Receiver Funds</FormLabel>
|
|
126
|
+
<FormControl>
|
|
127
|
+
<Input
|
|
128
|
+
type="text"
|
|
129
|
+
inputMode="decimal"
|
|
130
|
+
placeholder="Enter receiver funds"
|
|
131
|
+
value={field.value as unknown as string}
|
|
132
|
+
onChange={(e) => field.onChange(e.target.value)}
|
|
133
|
+
/>
|
|
134
|
+
</FormControl>
|
|
135
|
+
<FormMessage />
|
|
136
|
+
</FormItem>
|
|
137
|
+
)}
|
|
138
|
+
/>
|
|
139
|
+
</div>
|
|
140
|
+
|
|
141
|
+
<div className="mt-4 flex justify-between items-center">
|
|
142
|
+
<Button
|
|
143
|
+
type="submit"
|
|
144
|
+
disabled={isSubmitting}
|
|
145
|
+
className="cursor-pointer"
|
|
146
|
+
>
|
|
147
|
+
{isSubmitting ? (
|
|
148
|
+
<div className="flex items-center">
|
|
149
|
+
<Loader2 className="h-5 w-5 animate-spin" />
|
|
150
|
+
<span className="ml-2">Resolving...</span>
|
|
151
|
+
</div>
|
|
152
|
+
) : (
|
|
153
|
+
"Resolve"
|
|
154
|
+
)}
|
|
155
|
+
</Button>
|
|
156
|
+
|
|
157
|
+
<p className="text-xs text-muted-foreground">
|
|
158
|
+
<span className="font-bold">Total Amount: </span>
|
|
159
|
+
{formatCurrency(
|
|
160
|
+
totalAmount,
|
|
161
|
+
selectedEscrow?.trustline.name || ""
|
|
162
|
+
)}
|
|
163
|
+
</p>
|
|
164
|
+
|
|
165
|
+
<p className="text-xs text-muted-foreground">
|
|
166
|
+
<span className="font-bold">Total Balance: </span>
|
|
167
|
+
{formatCurrency(
|
|
168
|
+
selectedEscrow?.balance || 0,
|
|
169
|
+
selectedEscrow?.trustline.name || ""
|
|
170
|
+
)}
|
|
171
|
+
</p>
|
|
172
|
+
</div>
|
|
173
|
+
</form>
|
|
174
|
+
</Form>
|
|
175
|
+
</DialogContent>
|
|
176
|
+
</Dialog>
|
|
177
|
+
);
|
|
178
|
+
};
|