@trustless-work/blocks 1.0.0 → 1.0.1
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 +57 -0
- package/package.json +1 -1
- package/templates/deps.json +1 -1
- package/templates/escrows/details/Actions.tsx +21 -1
- package/templates/escrows/multi-release/initialize-escrow/dialog/InitializeEscrow.tsx +1 -0
- package/templates/escrows/multi-release/initialize-escrow/form/InitializeEscrow.tsx +1 -0
- package/templates/escrows/multi-release/initialize-escrow/shared/schema.ts +0 -1
- package/templates/escrows/multi-release/initialize-escrow/shared/useInitializeEscrow.ts +0 -2
- package/templates/escrows/multi-release/resolve-dispute/button/ResolveDispute.tsx +10 -20
- package/templates/escrows/multi-release/resolve-dispute/dialog/ResolveDispute.tsx +117 -60
- package/templates/escrows/multi-release/resolve-dispute/form/ResolveDispute.tsx +111 -55
- package/templates/escrows/multi-release/resolve-dispute/shared/schema.ts +68 -71
- package/templates/escrows/multi-release/resolve-dispute/shared/useResolveDispute.ts +107 -21
- package/templates/escrows/multi-release/update-escrow/shared/schema.ts +0 -1
- package/templates/escrows/multi-release/update-escrow/shared/useUpdateEscrow.ts +0 -4
- package/templates/escrows/multi-release/withdraw-remaining-funds/button/WithdrawRemainingFunds.tsx +85 -0
- package/templates/escrows/multi-release/withdraw-remaining-funds/dialog/WithdrawRemainingFunds.tsx +176 -0
- package/templates/escrows/multi-release/withdraw-remaining-funds/form/WithdrawRemainingFunds.tsx +153 -0
- package/templates/escrows/multi-release/withdraw-remaining-funds/shared/schema.ts +81 -0
- package/templates/escrows/multi-release/withdraw-remaining-funds/shared/useWithdrawRemainingFunds.ts +160 -0
- package/templates/escrows/single-multi-release/approve-milestone/button/ApproveMilestone.tsx +0 -1
- package/templates/escrows/single-multi-release/approve-milestone/shared/useApproveMilestone.ts +0 -1
- package/templates/escrows/single-release/initialize-escrow/shared/schema.ts +0 -1
- package/templates/escrows/single-release/initialize-escrow/shared/useInitializeEscrow.ts +0 -2
- package/templates/escrows/single-release/resolve-dispute/button/ResolveDispute.tsx +15 -31
- package/templates/escrows/single-release/resolve-dispute/dialog/ResolveDispute.tsx +116 -60
- package/templates/escrows/single-release/resolve-dispute/form/ResolveDispute.tsx +98 -43
- package/templates/escrows/single-release/resolve-dispute/shared/schema.ts +65 -68
- package/templates/escrows/single-release/resolve-dispute/shared/useResolveDispute.ts +100 -22
- package/templates/escrows/single-release/update-escrow/shared/schema.ts +0 -1
- package/templates/escrows/single-release/update-escrow/shared/useUpdateEscrow.ts +0 -4
- package/templates/tanstack/useEscrowsMutations.ts +53 -0
- package/templates/wallet-kit/trustlines.ts +0 -4
package/templates/escrows/multi-release/withdraw-remaining-funds/form/WithdrawRemainingFunds.tsx
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
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 { useWithdrawRemainingFunds } from "./useWithdrawRemainingFunds";
|
|
13
|
+
import { useEscrowContext } from "@/components/tw-blocks/providers/EscrowProvider";
|
|
14
|
+
import { Loader2, Trash2 } from "lucide-react";
|
|
15
|
+
import { formatCurrency } from "../../../../helpers/format.helper";
|
|
16
|
+
|
|
17
|
+
export const WithdrawRemainingFundsForm = () => {
|
|
18
|
+
const {
|
|
19
|
+
form,
|
|
20
|
+
handleSubmit,
|
|
21
|
+
isSubmitting,
|
|
22
|
+
distributions,
|
|
23
|
+
handleAddDistribution,
|
|
24
|
+
handleRemoveDistribution,
|
|
25
|
+
handleDistributionAddressChange,
|
|
26
|
+
handleDistributionAmountChange,
|
|
27
|
+
isAnyDistributionEmpty,
|
|
28
|
+
allowedAmount,
|
|
29
|
+
distributedSum,
|
|
30
|
+
isExactMatch,
|
|
31
|
+
difference,
|
|
32
|
+
} = useWithdrawRemainingFunds();
|
|
33
|
+
const { selectedEscrow } = useEscrowContext();
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<Form {...form}>
|
|
37
|
+
<form onSubmit={handleSubmit} className="flex flex-col space-y-6 w-full">
|
|
38
|
+
<FormLabel className="flex items-center my-4">
|
|
39
|
+
Distributions<span className="text-destructive ml-1">*</span>
|
|
40
|
+
</FormLabel>
|
|
41
|
+
|
|
42
|
+
{distributions.map((d, idx) => (
|
|
43
|
+
<div
|
|
44
|
+
key={`dist-${idx}`}
|
|
45
|
+
className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-[1fr_minmax(140px,220px)_auto] gap-3 sm:gap-4 items-end mb-2"
|
|
46
|
+
>
|
|
47
|
+
<FormField
|
|
48
|
+
control={form.control}
|
|
49
|
+
name={`distributions.${idx}.address` as const}
|
|
50
|
+
render={() => (
|
|
51
|
+
<FormItem className="sm:col-span-2 lg:col-span-1">
|
|
52
|
+
<FormLabel>Address</FormLabel>
|
|
53
|
+
<FormControl>
|
|
54
|
+
<Input
|
|
55
|
+
type="text"
|
|
56
|
+
placeholder="Receiver address"
|
|
57
|
+
value={d.address}
|
|
58
|
+
onChange={(e) =>
|
|
59
|
+
handleDistributionAddressChange(idx, e.target.value)
|
|
60
|
+
}
|
|
61
|
+
/>
|
|
62
|
+
</FormControl>
|
|
63
|
+
<FormMessage />
|
|
64
|
+
</FormItem>
|
|
65
|
+
)}
|
|
66
|
+
/>
|
|
67
|
+
|
|
68
|
+
<FormField
|
|
69
|
+
control={form.control}
|
|
70
|
+
name={`distributions.${idx}.amount` as const}
|
|
71
|
+
render={() => (
|
|
72
|
+
<FormItem>
|
|
73
|
+
<FormLabel>Amount</FormLabel>
|
|
74
|
+
<FormControl>
|
|
75
|
+
<Input
|
|
76
|
+
type="text"
|
|
77
|
+
inputMode="decimal"
|
|
78
|
+
placeholder="0.00"
|
|
79
|
+
value={(d.amount as string) ?? ""}
|
|
80
|
+
onChange={(e) => handleDistributionAmountChange(idx, e)}
|
|
81
|
+
/>
|
|
82
|
+
</FormControl>
|
|
83
|
+
<FormMessage />
|
|
84
|
+
</FormItem>
|
|
85
|
+
)}
|
|
86
|
+
/>
|
|
87
|
+
|
|
88
|
+
<Button
|
|
89
|
+
type="button"
|
|
90
|
+
onClick={() => handleRemoveDistribution(idx)}
|
|
91
|
+
className="justify-self-end self-end p-2 bg-transparent text-destructive rounded-md border-none shadow-none hover:bg-transparent hover:shadow-none hover:text-destructive focus:ring-0 active:ring-0"
|
|
92
|
+
disabled={distributions.length <= 2}
|
|
93
|
+
>
|
|
94
|
+
<Trash2 className="h-5 w-5" />
|
|
95
|
+
</Button>
|
|
96
|
+
</div>
|
|
97
|
+
))}
|
|
98
|
+
|
|
99
|
+
<div className="flex justify-between items-center">
|
|
100
|
+
<Button
|
|
101
|
+
type="button"
|
|
102
|
+
variant="outline"
|
|
103
|
+
onClick={handleAddDistribution}
|
|
104
|
+
disabled={isAnyDistributionEmpty}
|
|
105
|
+
className="cursor-pointer"
|
|
106
|
+
>
|
|
107
|
+
Add Item
|
|
108
|
+
</Button>
|
|
109
|
+
|
|
110
|
+
<div className="flex items-center gap-4">
|
|
111
|
+
<div className="text-xs text-muted-foreground">
|
|
112
|
+
<p>
|
|
113
|
+
<span className="font-bold">Total Amount: </span>
|
|
114
|
+
{distributedSum.toFixed(2)} / {allowedAmount.toFixed(2)}
|
|
115
|
+
</p>
|
|
116
|
+
{!isExactMatch && (
|
|
117
|
+
<p className="text-destructive">
|
|
118
|
+
<span className="font-bold">Difference: </span>
|
|
119
|
+
{difference.toFixed(2)}
|
|
120
|
+
</p>
|
|
121
|
+
)}
|
|
122
|
+
</div>
|
|
123
|
+
|
|
124
|
+
<p className="text-xs text-muted-foreground">
|
|
125
|
+
<span className="font-bold">Total Balance: </span>
|
|
126
|
+
{formatCurrency(
|
|
127
|
+
selectedEscrow?.balance || 0,
|
|
128
|
+
selectedEscrow?.trustline.name || ""
|
|
129
|
+
)}
|
|
130
|
+
</p>
|
|
131
|
+
</div>
|
|
132
|
+
</div>
|
|
133
|
+
|
|
134
|
+
<div className="mt-4">
|
|
135
|
+
<Button
|
|
136
|
+
type="submit"
|
|
137
|
+
disabled={isSubmitting || isAnyDistributionEmpty || !isExactMatch}
|
|
138
|
+
className="cursor-pointer"
|
|
139
|
+
>
|
|
140
|
+
{isSubmitting ? (
|
|
141
|
+
<div className="flex items-center">
|
|
142
|
+
<Loader2 className="h-5 w-5 animate-spin" />
|
|
143
|
+
<span className="ml-2">Withdrawing...</span>
|
|
144
|
+
</div>
|
|
145
|
+
) : (
|
|
146
|
+
"Withdraw"
|
|
147
|
+
)}
|
|
148
|
+
</Button>
|
|
149
|
+
</div>
|
|
150
|
+
</form>
|
|
151
|
+
</Form>
|
|
152
|
+
);
|
|
153
|
+
};
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { isValidWallet } from "../../../../wallet-kit/validators";
|
|
3
|
+
|
|
4
|
+
export const getFormSchema = () => {
|
|
5
|
+
const amountSchema = z
|
|
6
|
+
.union([z.string(), z.number()])
|
|
7
|
+
.refine(
|
|
8
|
+
(val) => {
|
|
9
|
+
if (typeof val === "string") {
|
|
10
|
+
if (val === "" || val === "." || val.endsWith(".")) {
|
|
11
|
+
return true; // Allow partial input
|
|
12
|
+
}
|
|
13
|
+
const numVal = Number(val);
|
|
14
|
+
return !isNaN(numVal) && numVal >= 0;
|
|
15
|
+
}
|
|
16
|
+
return val >= 0;
|
|
17
|
+
},
|
|
18
|
+
{ message: "Amount must be 0 or greater." }
|
|
19
|
+
)
|
|
20
|
+
.refine(
|
|
21
|
+
(val) => {
|
|
22
|
+
if (typeof val === "string") {
|
|
23
|
+
if (val === "" || val === "." || val.endsWith(".")) {
|
|
24
|
+
return true; // Allow partial input
|
|
25
|
+
}
|
|
26
|
+
const numVal = Number(val);
|
|
27
|
+
if (isNaN(numVal)) return false;
|
|
28
|
+
const decimalPlaces = (numVal.toString().split(".")[1] || "").length;
|
|
29
|
+
return decimalPlaces <= 2;
|
|
30
|
+
}
|
|
31
|
+
const decimalPlaces = (val.toString().split(".")[1] || "").length;
|
|
32
|
+
return decimalPlaces <= 2;
|
|
33
|
+
},
|
|
34
|
+
{ message: "Amount can have a maximum of 2 decimal places." }
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
return z
|
|
38
|
+
.object({
|
|
39
|
+
distributions: z
|
|
40
|
+
.array(
|
|
41
|
+
z.object({
|
|
42
|
+
address: z
|
|
43
|
+
.string()
|
|
44
|
+
.min(1, { message: "Address is required." })
|
|
45
|
+
.refine((addr) => isValidWallet(addr), {
|
|
46
|
+
message: "Invalid Stellar address.",
|
|
47
|
+
}),
|
|
48
|
+
amount: amountSchema,
|
|
49
|
+
})
|
|
50
|
+
)
|
|
51
|
+
.min(2, { message: "At least two distributions are required." }),
|
|
52
|
+
})
|
|
53
|
+
.superRefine((data, ctx) => {
|
|
54
|
+
const seen = new Map<string, number>();
|
|
55
|
+
data.distributions.forEach((item, idx) => {
|
|
56
|
+
const key = (item.address || "").trim().toUpperCase();
|
|
57
|
+
if (!key) return;
|
|
58
|
+
if (seen.has(key)) {
|
|
59
|
+
const firstIdx = seen.get(key)!;
|
|
60
|
+
ctx.addIssue({
|
|
61
|
+
code: z.ZodIssueCode.custom,
|
|
62
|
+
path: ["distributions", idx, "address"],
|
|
63
|
+
message: "Duplicate address. Each recipient must be unique.",
|
|
64
|
+
});
|
|
65
|
+
ctx.addIssue({
|
|
66
|
+
code: z.ZodIssueCode.custom,
|
|
67
|
+
path: ["distributions", firstIdx, "address"],
|
|
68
|
+
message: "Duplicate address. Each recipient must be unique.",
|
|
69
|
+
});
|
|
70
|
+
} else {
|
|
71
|
+
seen.set(key, idx);
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
export const withdrawRemainingFundsSchema = getFormSchema();
|
|
78
|
+
|
|
79
|
+
export type WithdrawRemainingFundsValues = z.infer<
|
|
80
|
+
typeof withdrawRemainingFundsSchema
|
|
81
|
+
>;
|
package/templates/escrows/multi-release/withdraw-remaining-funds/shared/useWithdrawRemainingFunds.ts
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { useForm } from "react-hook-form";
|
|
3
|
+
import { zodResolver } from "@hookform/resolvers/zod";
|
|
4
|
+
import {
|
|
5
|
+
withdrawRemainingFundsSchema,
|
|
6
|
+
type WithdrawRemainingFundsValues,
|
|
7
|
+
} from "./schema";
|
|
8
|
+
import { toast } from "sonner";
|
|
9
|
+
import { WithdrawRemainingFundsPayload } from "@trustless-work/escrow";
|
|
10
|
+
import { useEscrowContext } from "@/components/tw-blocks/providers/EscrowProvider";
|
|
11
|
+
import { useEscrowsMutations } from "@/components/tw-blocks/tanstack/useEscrowsMutations";
|
|
12
|
+
import {
|
|
13
|
+
ErrorResponse,
|
|
14
|
+
handleError,
|
|
15
|
+
} from "@/components/tw-blocks/handle-errors/handle";
|
|
16
|
+
import { useWalletContext } from "@/components/tw-blocks/wallet-kit/WalletProvider";
|
|
17
|
+
|
|
18
|
+
type DistributionInput = { address: string; amount: string | number };
|
|
19
|
+
|
|
20
|
+
export function useWithdrawRemainingFunds() {
|
|
21
|
+
const { withdrawRemainingFunds } = useEscrowsMutations();
|
|
22
|
+
const { selectedEscrow, updateEscrow } = useEscrowContext();
|
|
23
|
+
const { walletAddress } = useWalletContext();
|
|
24
|
+
|
|
25
|
+
const form = useForm<WithdrawRemainingFundsValues>({
|
|
26
|
+
resolver: zodResolver(withdrawRemainingFundsSchema),
|
|
27
|
+
defaultValues: {
|
|
28
|
+
distributions: [
|
|
29
|
+
{ address: "", amount: "" },
|
|
30
|
+
{ address: "", amount: "" },
|
|
31
|
+
],
|
|
32
|
+
},
|
|
33
|
+
mode: "onChange",
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
const distributions = form.watch("distributions") as DistributionInput[];
|
|
37
|
+
|
|
38
|
+
const [isSubmitting, setIsSubmitting] = React.useState(false);
|
|
39
|
+
|
|
40
|
+
const allowedAmount = React.useMemo(() => {
|
|
41
|
+
return Number(selectedEscrow?.balance || 0);
|
|
42
|
+
}, [selectedEscrow]);
|
|
43
|
+
|
|
44
|
+
const distributedSum = React.useMemo(() => {
|
|
45
|
+
return (distributions || []).reduce((acc, d) => {
|
|
46
|
+
const n = Number(d?.amount ?? 0);
|
|
47
|
+
return acc + (isNaN(n) ? 0 : n);
|
|
48
|
+
}, 0);
|
|
49
|
+
}, [distributions]);
|
|
50
|
+
|
|
51
|
+
const isExactMatch = React.useMemo(() => {
|
|
52
|
+
return Number(allowedAmount) === Number(distributedSum);
|
|
53
|
+
}, [allowedAmount, distributedSum]);
|
|
54
|
+
|
|
55
|
+
const difference = React.useMemo(() => {
|
|
56
|
+
return Math.abs(Number(allowedAmount) - Number(distributedSum));
|
|
57
|
+
}, [allowedAmount, distributedSum]);
|
|
58
|
+
|
|
59
|
+
const handleDistributionAddressChange = (index: number, value: string) => {
|
|
60
|
+
const updated = [...distributions];
|
|
61
|
+
updated[index] = { ...updated[index], address: value };
|
|
62
|
+
form.setValue("distributions", updated);
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const handleDistributionAmountChange = (
|
|
66
|
+
index: number,
|
|
67
|
+
e: React.ChangeEvent<HTMLInputElement>
|
|
68
|
+
) => {
|
|
69
|
+
let rawValue = e.target.value;
|
|
70
|
+
rawValue = rawValue.replace(/[^0-9.]/g, "");
|
|
71
|
+
if (rawValue.split(".").length > 2) {
|
|
72
|
+
rawValue = rawValue.slice(0, -1);
|
|
73
|
+
}
|
|
74
|
+
if (rawValue.includes(".")) {
|
|
75
|
+
const parts = rawValue.split(".");
|
|
76
|
+
if (parts[1] && parts[1].length > 2) {
|
|
77
|
+
rawValue = parts[0] + "." + parts[1].slice(0, 2);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
const updated = [...distributions];
|
|
81
|
+
updated[index] = { ...updated[index], amount: rawValue };
|
|
82
|
+
form.setValue("distributions", updated);
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const handleAddDistribution = () => {
|
|
86
|
+
const updated = [...distributions, { address: "", amount: "" }];
|
|
87
|
+
form.setValue("distributions", updated);
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const handleRemoveDistribution = (index: number) => {
|
|
91
|
+
if (distributions.length <= 2) return;
|
|
92
|
+
const updated = distributions.filter((_, i) => i !== index);
|
|
93
|
+
form.setValue("distributions", updated);
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
const isAnyDistributionEmpty = React.useMemo(() => {
|
|
97
|
+
if (!distributions.length) return true;
|
|
98
|
+
const last = distributions[distributions.length - 1];
|
|
99
|
+
return (last.address || "").trim() === "" || (last.amount ?? "") === "";
|
|
100
|
+
}, [distributions]);
|
|
101
|
+
|
|
102
|
+
const handleSubmit = form.handleSubmit(async (payload) => {
|
|
103
|
+
try {
|
|
104
|
+
setIsSubmitting(true);
|
|
105
|
+
|
|
106
|
+
if (!isExactMatch) {
|
|
107
|
+
toast.error("The total distributions must equal the remaining amount");
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const finalPayload: WithdrawRemainingFundsPayload = {
|
|
112
|
+
contractId: selectedEscrow?.contractId || "",
|
|
113
|
+
disputeResolver: walletAddress || "",
|
|
114
|
+
distributions: payload.distributions.map((d) => ({
|
|
115
|
+
address: d.address,
|
|
116
|
+
amount: Number(d.amount || 0),
|
|
117
|
+
})) as [{ address: string; amount: number }],
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
await withdrawRemainingFunds.mutateAsync({
|
|
121
|
+
payload: finalPayload,
|
|
122
|
+
type: "multi-release",
|
|
123
|
+
address: walletAddress || "",
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
toast.success("Withdraw successful");
|
|
127
|
+
|
|
128
|
+
const sumDistributed = payload.distributions.reduce((acc, d) => {
|
|
129
|
+
const n = Number(d.amount || 0);
|
|
130
|
+
return acc + (isNaN(n) ? 0 : n);
|
|
131
|
+
}, 0);
|
|
132
|
+
|
|
133
|
+
updateEscrow({
|
|
134
|
+
...selectedEscrow,
|
|
135
|
+
balance: (selectedEscrow?.balance || 0) - sumDistributed || 0,
|
|
136
|
+
});
|
|
137
|
+
} catch (error) {
|
|
138
|
+
toast.error(handleError(error as ErrorResponse).message);
|
|
139
|
+
} finally {
|
|
140
|
+
setIsSubmitting(false);
|
|
141
|
+
form.reset();
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
return {
|
|
146
|
+
form,
|
|
147
|
+
handleSubmit,
|
|
148
|
+
isSubmitting,
|
|
149
|
+
distributions,
|
|
150
|
+
handleAddDistribution,
|
|
151
|
+
handleRemoveDistribution,
|
|
152
|
+
handleDistributionAddressChange,
|
|
153
|
+
handleDistributionAmountChange,
|
|
154
|
+
isAnyDistributionEmpty,
|
|
155
|
+
allowedAmount,
|
|
156
|
+
distributedSum,
|
|
157
|
+
isExactMatch,
|
|
158
|
+
difference,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
@@ -38,7 +38,6 @@ export function useInitializeEscrow() {
|
|
|
38
38
|
receiverMemo: "",
|
|
39
39
|
trustline: {
|
|
40
40
|
address: "",
|
|
41
|
-
decimals: 10000000,
|
|
42
41
|
},
|
|
43
42
|
roles: {
|
|
44
43
|
approver: "",
|
|
@@ -82,7 +81,6 @@ export function useInitializeEscrow() {
|
|
|
82
81
|
receiverMemo: "123",
|
|
83
82
|
trustline: {
|
|
84
83
|
address: usdc?.value || "",
|
|
85
|
-
decimals: 10000000,
|
|
86
84
|
},
|
|
87
85
|
roles: {
|
|
88
86
|
approver: walletAddress || "",
|
|
@@ -11,14 +11,14 @@ import {
|
|
|
11
11
|
import { useEscrowContext } from "@/components/tw-blocks/providers/EscrowProvider";
|
|
12
12
|
import { Loader2 } from "lucide-react";
|
|
13
13
|
|
|
14
|
+
type Distribution = { address: string; amount: number };
|
|
15
|
+
|
|
14
16
|
type ResolveDisputeButtonProps = {
|
|
15
|
-
|
|
16
|
-
receiverFunds: number;
|
|
17
|
+
distributions: Distribution[];
|
|
17
18
|
};
|
|
18
19
|
|
|
19
20
|
export const ResolveDisputeButton = ({
|
|
20
|
-
|
|
21
|
-
receiverFunds,
|
|
21
|
+
distributions,
|
|
22
22
|
}: ResolveDisputeButtonProps) => {
|
|
23
23
|
const { resolveDispute } = useEscrowsMutations();
|
|
24
24
|
const { selectedEscrow, updateEscrow } = useEscrowContext();
|
|
@@ -27,42 +27,22 @@ export const ResolveDisputeButton = ({
|
|
|
27
27
|
|
|
28
28
|
async function handleClick() {
|
|
29
29
|
try {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
) {
|
|
36
|
-
toast.error("Both amounts are required");
|
|
37
|
-
return;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
if (approverFunds < 0 || receiverFunds < 0) {
|
|
41
|
-
toast.error("Amounts must be >= 0");
|
|
30
|
+
const hasInvalid = distributions.some(
|
|
31
|
+
(d) => !d.address || Number.isNaN(d.amount) || d.amount < 0
|
|
32
|
+
);
|
|
33
|
+
if (hasInvalid) {
|
|
34
|
+
toast.error("Invalid distributions");
|
|
42
35
|
return;
|
|
43
36
|
}
|
|
44
37
|
|
|
45
38
|
setIsSubmitting(true);
|
|
46
39
|
|
|
47
|
-
/**
|
|
48
|
-
* Create the payload for the resolve dispute mutation
|
|
49
|
-
*
|
|
50
|
-
* @returns The payload for the resolve dispute mutation
|
|
51
|
-
*/
|
|
52
40
|
const payload: SingleReleaseResolveDisputePayload = {
|
|
53
41
|
contractId: selectedEscrow?.contractId || "",
|
|
54
42
|
disputeResolver: walletAddress || "",
|
|
55
|
-
|
|
56
|
-
receiverFunds: Number(receiverFunds),
|
|
43
|
+
distributions: distributions as [Distribution],
|
|
57
44
|
};
|
|
58
45
|
|
|
59
|
-
/**
|
|
60
|
-
* Call the resolve dispute mutation
|
|
61
|
-
*
|
|
62
|
-
* @param payload - The payload for the resolve dispute mutation
|
|
63
|
-
* @param type - The type of the escrow
|
|
64
|
-
* @param address - The address of the escrow
|
|
65
|
-
*/
|
|
66
46
|
await resolveDispute.mutateAsync({
|
|
67
47
|
payload,
|
|
68
48
|
type: "single-release",
|
|
@@ -70,6 +50,10 @@ export const ResolveDisputeButton = ({
|
|
|
70
50
|
});
|
|
71
51
|
|
|
72
52
|
toast.success("Dispute resolved successfully");
|
|
53
|
+
const sumDistributed = distributions.reduce(
|
|
54
|
+
(acc, d) => acc + Number(d.amount || 0),
|
|
55
|
+
0
|
|
56
|
+
);
|
|
73
57
|
updateEscrow({
|
|
74
58
|
...selectedEscrow,
|
|
75
59
|
flags: {
|
|
@@ -77,7 +61,7 @@ export const ResolveDisputeButton = ({
|
|
|
77
61
|
disputed: false,
|
|
78
62
|
resolved: true,
|
|
79
63
|
},
|
|
80
|
-
balance: selectedEscrow?.balance || 0,
|
|
64
|
+
balance: (selectedEscrow?.balance || 0) - sumDistributed || 0,
|
|
81
65
|
});
|
|
82
66
|
} catch (error) {
|
|
83
67
|
toast.error(handleError(error as ErrorResponse).message);
|