@trustless-work/blocks 1.0.0 → 1.0.2

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.
Files changed (36) hide show
  1. package/bin/index.js +78 -1
  2. package/package.json +1 -1
  3. package/templates/deps.json +1 -1
  4. package/templates/escrows/details/Actions.tsx +21 -1
  5. package/templates/escrows/indicators/balance-progress/bar/BalanceProgress.tsx +55 -0
  6. package/templates/escrows/indicators/balance-progress/donut/BalanceProgress.tsx +99 -0
  7. package/templates/escrows/multi-release/initialize-escrow/dialog/InitializeEscrow.tsx +1 -0
  8. package/templates/escrows/multi-release/initialize-escrow/form/InitializeEscrow.tsx +1 -0
  9. package/templates/escrows/multi-release/initialize-escrow/shared/schema.ts +0 -1
  10. package/templates/escrows/multi-release/initialize-escrow/shared/useInitializeEscrow.ts +0 -2
  11. package/templates/escrows/multi-release/resolve-dispute/button/ResolveDispute.tsx +10 -20
  12. package/templates/escrows/multi-release/resolve-dispute/dialog/ResolveDispute.tsx +117 -60
  13. package/templates/escrows/multi-release/resolve-dispute/form/ResolveDispute.tsx +111 -55
  14. package/templates/escrows/multi-release/resolve-dispute/shared/schema.ts +68 -71
  15. package/templates/escrows/multi-release/resolve-dispute/shared/useResolveDispute.ts +107 -21
  16. package/templates/escrows/multi-release/update-escrow/shared/schema.ts +0 -1
  17. package/templates/escrows/multi-release/update-escrow/shared/useUpdateEscrow.ts +0 -4
  18. package/templates/escrows/multi-release/withdraw-remaining-funds/button/WithdrawRemainingFunds.tsx +85 -0
  19. package/templates/escrows/multi-release/withdraw-remaining-funds/dialog/WithdrawRemainingFunds.tsx +176 -0
  20. package/templates/escrows/multi-release/withdraw-remaining-funds/form/WithdrawRemainingFunds.tsx +153 -0
  21. package/templates/escrows/multi-release/withdraw-remaining-funds/shared/schema.ts +81 -0
  22. package/templates/escrows/multi-release/withdraw-remaining-funds/shared/useWithdrawRemainingFunds.ts +160 -0
  23. package/templates/escrows/single-multi-release/approve-milestone/button/ApproveMilestone.tsx +0 -1
  24. package/templates/escrows/single-multi-release/approve-milestone/shared/useApproveMilestone.ts +0 -1
  25. package/templates/escrows/single-release/initialize-escrow/shared/schema.ts +0 -1
  26. package/templates/escrows/single-release/initialize-escrow/shared/useInitializeEscrow.ts +0 -2
  27. package/templates/escrows/single-release/resolve-dispute/button/ResolveDispute.tsx +15 -31
  28. package/templates/escrows/single-release/resolve-dispute/dialog/ResolveDispute.tsx +116 -60
  29. package/templates/escrows/single-release/resolve-dispute/form/ResolveDispute.tsx +98 -43
  30. package/templates/escrows/single-release/resolve-dispute/shared/schema.ts +65 -68
  31. package/templates/escrows/single-release/resolve-dispute/shared/useResolveDispute.ts +100 -22
  32. package/templates/escrows/single-release/update-escrow/shared/schema.ts +0 -1
  33. package/templates/escrows/single-release/update-escrow/shared/useUpdateEscrow.ts +0 -4
  34. package/templates/tanstack/useEscrowsMutations.ts +53 -0
  35. package/templates/tanstack/useGetMultipleEscrowBalances.ts +41 -0
  36. package/templates/wallet-kit/trustlines.ts +0 -4
@@ -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
- approverFunds: number;
16
- receiverFunds: number;
17
+ distributions: Distribution[];
17
18
  };
18
19
 
19
20
  export const ResolveDisputeButton = ({
20
- approverFunds,
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
- if (
31
- approverFunds == null ||
32
- Number.isNaN(approverFunds) ||
33
- receiverFunds == null ||
34
- Number.isNaN(receiverFunds)
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
- approverFunds: Number(approverFunds),
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);
@@ -16,13 +16,27 @@ import {
16
16
  DialogTitle,
17
17
  DialogTrigger,
18
18
  } from "__UI_BASE__/dialog";
19
- import { Loader2 } from "lucide-react";
19
+ import { Loader2, Trash2 } from "lucide-react";
20
20
  import { useResolveDispute } from "./useResolveDispute";
21
21
  import { useEscrowContext } from "@/components/tw-blocks/providers/EscrowProvider";
22
22
  import { formatCurrency } from "../../../../helpers/format.helper";
23
23
 
24
24
  export const ResolveDisputeDialog = () => {
25
- const { form, handleSubmit, isSubmitting } = useResolveDispute();
25
+ const {
26
+ form,
27
+ handleSubmit,
28
+ isSubmitting,
29
+ distributions,
30
+ handleAddDistribution,
31
+ handleRemoveDistribution,
32
+ handleDistributionAddressChange,
33
+ handleDistributionAmountChange,
34
+ isAnyDistributionEmpty,
35
+ allowedAmount,
36
+ distributedSum,
37
+ isExactMatch,
38
+ difference,
39
+ } = useResolveDispute();
26
40
  const { selectedEscrow } = useEscrowContext();
27
41
 
28
42
  return (
@@ -32,58 +46,116 @@ export const ResolveDisputeDialog = () => {
32
46
  Resolve Dispute
33
47
  </Button>
34
48
  </DialogTrigger>
35
- <DialogContent>
49
+ <DialogContent className="!w-full sm:!max-w-3xl max-h-[95vh] overflow-y-auto">
36
50
  <DialogHeader>
37
51
  <DialogTitle>Resolve Dispute</DialogTitle>
38
52
  </DialogHeader>
39
53
  <Form {...form}>
40
54
  <form onSubmit={handleSubmit}>
41
- <div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
42
- <FormField
43
- control={form.control}
44
- name="approverFunds"
45
- render={({ field }) => (
46
- <FormItem>
47
- <FormLabel>Approver Funds</FormLabel>
48
- <FormControl>
49
- <Input
50
- type="text"
51
- inputMode="decimal"
52
- placeholder="Enter approver funds"
53
- value={field.value as unknown as string}
54
- onChange={(e) => field.onChange(e.target.value)}
55
- />
56
- </FormControl>
57
- <FormMessage />
58
- </FormItem>
59
- )}
60
- />
55
+ <FormLabel className="flex items-center my-4">
56
+ Distributions<span className="text-destructive ml-1">*</span>
57
+ </FormLabel>
61
58
 
62
- <FormField
63
- control={form.control}
64
- name="receiverFunds"
65
- render={({ field }) => (
66
- <FormItem>
67
- <FormLabel>Receiver Funds</FormLabel>
68
- <FormControl>
69
- <Input
70
- type="text"
71
- inputMode="decimal"
72
- placeholder="Enter receiver funds"
73
- value={field.value as unknown as string}
74
- onChange={(e) => field.onChange(e.target.value)}
75
- />
76
- </FormControl>
77
- <FormMessage />
78
- </FormItem>
79
- )}
80
- />
59
+ {distributions.map((d, idx) => (
60
+ <div
61
+ key={`dist-${idx}`}
62
+ 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"
63
+ >
64
+ <FormField
65
+ control={form.control}
66
+ name={`distributions.${idx}.address` as const}
67
+ render={() => (
68
+ <FormItem className="sm:col-span-2 lg:col-span-1">
69
+ <FormLabel>Address</FormLabel>
70
+ <FormControl>
71
+ <Input
72
+ type="text"
73
+ placeholder="Receiver address"
74
+ value={d.address}
75
+ onChange={(e) =>
76
+ handleDistributionAddressChange(idx, e.target.value)
77
+ }
78
+ />
79
+ </FormControl>
80
+ <FormMessage />
81
+ </FormItem>
82
+ )}
83
+ />
84
+
85
+ <FormField
86
+ control={form.control}
87
+ name={`distributions.${idx}.amount` as const}
88
+ render={() => (
89
+ <FormItem>
90
+ <FormLabel>Amount</FormLabel>
91
+ <FormControl>
92
+ <Input
93
+ type="text"
94
+ inputMode="decimal"
95
+ placeholder="0.00"
96
+ value={(d.amount as string) ?? ""}
97
+ onChange={(e) =>
98
+ handleDistributionAmountChange(idx, e)
99
+ }
100
+ />
101
+ </FormControl>
102
+ <FormMessage />
103
+ </FormItem>
104
+ )}
105
+ />
106
+
107
+ <Button
108
+ type="button"
109
+ onClick={() => handleRemoveDistribution(idx)}
110
+ 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"
111
+ disabled={distributions.length <= 2}
112
+ >
113
+ <Trash2 className="h-5 w-5" />
114
+ </Button>
115
+ </div>
116
+ ))}
117
+
118
+ <div className="flex justify-between items-center mt-4">
119
+ <Button
120
+ type="button"
121
+ variant="outline"
122
+ onClick={handleAddDistribution}
123
+ disabled={isAnyDistributionEmpty}
124
+ className="cursor-pointer"
125
+ >
126
+ Add Item
127
+ </Button>
128
+
129
+ <div className="flex items-center gap-4">
130
+ <div className="text-xs text-muted-foreground">
131
+ <p>
132
+ <span className="font-bold">Total Amount: </span>
133
+ {distributedSum.toFixed(2)} / {allowedAmount.toFixed(2)}
134
+ </p>
135
+ {!isExactMatch && (
136
+ <p className="text-destructive">
137
+ <span className="font-bold">Difference: </span>
138
+ {difference.toFixed(2)}
139
+ </p>
140
+ )}
141
+ </div>
142
+
143
+ <p className="text-xs text-muted-foreground">
144
+ <span className="font-bold">Total Balance: </span>
145
+ {formatCurrency(
146
+ selectedEscrow?.balance || 0,
147
+ selectedEscrow?.trustline.name || ""
148
+ )}
149
+ </p>
150
+ </div>
81
151
  </div>
82
152
 
83
- <div className="mt-4 flex justify-between items-center">
153
+ <div className="mt-4 flex justify-start items-center">
84
154
  <Button
85
155
  type="submit"
86
- disabled={isSubmitting}
156
+ disabled={
157
+ isSubmitting || isAnyDistributionEmpty || !isExactMatch
158
+ }
87
159
  className="cursor-pointer"
88
160
  >
89
161
  {isSubmitting ? (
@@ -95,22 +167,6 @@ export const ResolveDisputeDialog = () => {
95
167
  "Resolve"
96
168
  )}
97
169
  </Button>
98
-
99
- <p className="text-xs text-muted-foreground">
100
- <span className="font-bold">Total Amount: </span>
101
- {formatCurrency(
102
- selectedEscrow?.amount || 0,
103
- selectedEscrow?.trustline.name || ""
104
- )}
105
- </p>
106
-
107
- <p className="text-xs text-muted-foreground">
108
- <span className="font-bold">Total Balance: </span>
109
- {formatCurrency(
110
- selectedEscrow?.balance || 0,
111
- selectedEscrow?.trustline.name || ""
112
- )}
113
- </p>
114
170
  </div>
115
171
  </form>
116
172
  </Form>
@@ -10,60 +10,115 @@ import {
10
10
  import { Input } from "__UI_BASE__/input";
11
11
  import { Button } from "__UI_BASE__/button";
12
12
  import { useResolveDispute } from "./useResolveDispute";
13
- import { Loader2 } from "lucide-react";
13
+ import { Loader2, Trash2 } from "lucide-react";
14
14
 
15
15
  export const ResolveDisputeForm = () => {
16
- const { form, handleSubmit, isSubmitting } = useResolveDispute();
16
+ const {
17
+ form,
18
+ handleSubmit,
19
+ isSubmitting,
20
+ distributions,
21
+ handleAddDistribution,
22
+ handleRemoveDistribution,
23
+ handleDistributionAddressChange,
24
+ handleDistributionAmountChange,
25
+ isAnyDistributionEmpty,
26
+ allowedAmount,
27
+ distributedSum,
28
+ isExactMatch,
29
+ difference,
30
+ } = useResolveDispute();
17
31
 
18
32
  return (
19
33
  <Form {...form}>
20
34
  <form onSubmit={handleSubmit} className="flex flex-col space-y-6 w-full">
21
- <div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
22
- <FormField
23
- control={form.control}
24
- name="approverFunds"
25
- render={({ field }) => (
26
- <FormItem>
27
- <FormLabel>Approver Funds</FormLabel>
28
- <FormControl>
29
- <Input
30
- type="text"
31
- inputMode="decimal"
32
- placeholder="Enter approver funds"
33
- value={field.value as unknown as string}
34
- onChange={(e) => field.onChange(e.target.value)}
35
- />
36
- </FormControl>
37
- <FormMessage />
38
- </FormItem>
39
- )}
40
- />
35
+ <FormLabel className="flex items-center my-4">
36
+ Distributions<span className="text-destructive ml-1">*</span>
37
+ </FormLabel>
41
38
 
42
- <FormField
43
- control={form.control}
44
- name="receiverFunds"
45
- render={({ field }) => (
46
- <FormItem>
47
- <FormLabel>Receiver Funds</FormLabel>
48
- <FormControl>
49
- <Input
50
- type="text"
51
- inputMode="decimal"
52
- placeholder="Enter receiver funds"
53
- value={field.value as unknown as string}
54
- onChange={(e) => field.onChange(e.target.value)}
55
- />
56
- </FormControl>
57
- <FormMessage />
58
- </FormItem>
59
- )}
60
- />
39
+ {distributions.map((d, idx) => (
40
+ <div
41
+ key={`dist-${idx}`}
42
+ 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"
43
+ >
44
+ <FormField
45
+ control={form.control}
46
+ name={`distributions.${idx}.address` as const}
47
+ render={() => (
48
+ <FormItem className="sm:col-span-2 lg:col-span-1">
49
+ <FormLabel>Address</FormLabel>
50
+ <FormControl>
51
+ <Input
52
+ type="text"
53
+ placeholder="Receiver address"
54
+ value={d.address}
55
+ onChange={(e) =>
56
+ handleDistributionAddressChange(idx, e.target.value)
57
+ }
58
+ />
59
+ </FormControl>
60
+ <FormMessage />
61
+ </FormItem>
62
+ )}
63
+ />
64
+
65
+ <FormField
66
+ control={form.control}
67
+ name={`distributions.${idx}.amount` as const}
68
+ render={() => (
69
+ <FormItem>
70
+ <FormLabel>Amount</FormLabel>
71
+ <FormControl>
72
+ <Input
73
+ type="text"
74
+ inputMode="decimal"
75
+ placeholder="0.00"
76
+ value={(d.amount as string) ?? ""}
77
+ onChange={(e) => handleDistributionAmountChange(idx, e)}
78
+ />
79
+ </FormControl>
80
+ <FormMessage />
81
+ </FormItem>
82
+ )}
83
+ />
84
+
85
+ <Button
86
+ type="button"
87
+ onClick={() => handleRemoveDistribution(idx)}
88
+ 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"
89
+ disabled={distributions.length <= 2}
90
+ >
91
+ <Trash2 className="h-5 w-5" />
92
+ </Button>
93
+ </div>
94
+ ))}
95
+
96
+ <div className="flex justify-end">
97
+ <Button
98
+ type="button"
99
+ variant="outline"
100
+ onClick={handleAddDistribution}
101
+ disabled={isAnyDistributionEmpty}
102
+ className="cursor-pointer"
103
+ >
104
+ Add Item
105
+ </Button>
61
106
  </div>
62
107
 
63
- <div className="mt-4">
108
+ <div className="mt-4 space-y-2">
109
+ <p className="text-xs text-muted-foreground">
110
+ <span className="font-bold">Total Amount: </span>
111
+ {distributedSum.toFixed(2)} / {allowedAmount.toFixed(2)}
112
+ </p>
113
+ {!isExactMatch && (
114
+ <p className="text-xs text-destructive">
115
+ <span className="font-bold">Difference: </span>
116
+ {difference.toFixed(2)}
117
+ </p>
118
+ )}
64
119
  <Button
65
120
  type="submit"
66
- disabled={isSubmitting}
121
+ disabled={isSubmitting || isAnyDistributionEmpty || !isExactMatch}
67
122
  className="cursor-pointer"
68
123
  >
69
124
  {isSubmitting ? (
@@ -1,80 +1,77 @@
1
1
  import { z } from "zod";
2
+ import { isValidWallet } from "../../../../wallet-kit/validators";
2
3
 
3
4
  export const getFormSchema = () => {
4
- return z.object({
5
- approverFunds: 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;
12
- }
13
- const numVal = Number(val);
14
- return !isNaN(numVal) && numVal >= 0;
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
15
12
  }
16
- return val >= 0;
17
- },
18
- {
19
- message: "Approver funds must be 0 or greater.",
13
+ const numVal = Number(val);
14
+ return !isNaN(numVal) && numVal >= 0;
20
15
  }
21
- )
22
- .refine(
23
- (val) => {
24
- if (typeof val === "string") {
25
- if (val === "" || val === "." || val.endsWith(".")) {
26
- return true;
27
- }
28
- const numVal = Number(val);
29
- if (isNaN(numVal)) return false;
30
- const decimalPlaces = (numVal.toString().split(".")[1] || "")
31
- .length;
32
- return decimalPlaces <= 2;
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
33
25
  }
34
- const decimalPlaces = (val.toString().split(".")[1] || "").length;
26
+ const numVal = Number(val);
27
+ if (isNaN(numVal)) return false;
28
+ const decimalPlaces = (numVal.toString().split(".")[1] || "").length;
35
29
  return decimalPlaces <= 2;
36
- },
37
- {
38
- message: "Approver funds can have a maximum of 2 decimal places.",
39
30
  }
40
- ),
41
- receiverFunds: z
42
- .union([z.string(), z.number()])
43
- .refine(
44
- (val) => {
45
- if (typeof val === "string") {
46
- if (val === "" || val === "." || val.endsWith(".")) {
47
- return true;
48
- }
49
- const numVal = Number(val);
50
- return !isNaN(numVal) && numVal >= 0;
51
- }
52
- return val >= 0;
53
- },
54
- {
55
- message: "Receiver funds must be 0 or greater.",
56
- }
57
- )
58
- .refine(
59
- (val) => {
60
- if (typeof val === "string") {
61
- if (val === "" || val === "." || val.endsWith(".")) {
62
- return true;
63
- }
64
- const numVal = Number(val);
65
- if (isNaN(numVal)) return false;
66
- const decimalPlaces = (numVal.toString().split(".")[1] || "")
67
- .length;
68
- return decimalPlaces <= 2;
69
- }
70
- const decimalPlaces = (val.toString().split(".")[1] || "").length;
71
- return decimalPlaces <= 2;
72
- },
73
- {
74
- message: "Receiver funds can have a maximum of 2 decimal places.",
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);
75
72
  }
76
- ),
77
- });
73
+ });
74
+ });
78
75
  };
79
76
 
80
77
  export const resolveDisputeSchema = getFormSchema();