@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,506 @@
|
|
|
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 { Card } from "__UI_BASE__/card";
|
|
13
|
+
import {
|
|
14
|
+
Select,
|
|
15
|
+
SelectTrigger,
|
|
16
|
+
SelectValue,
|
|
17
|
+
SelectContent,
|
|
18
|
+
SelectItem,
|
|
19
|
+
} from "__UI_BASE__/select";
|
|
20
|
+
import { Textarea } from "__UI_BASE__/textarea";
|
|
21
|
+
import { useInitializeEscrow } from "./useInitializeEscrow";
|
|
22
|
+
import { Trash2, DollarSign, Percent, Loader2 } from "lucide-react";
|
|
23
|
+
import Link from "next/link";
|
|
24
|
+
import { trustlineOptions } from "@/components/tw-blocks/wallet-kit/trustlines";
|
|
25
|
+
|
|
26
|
+
export const InitializeEscrowForm = () => {
|
|
27
|
+
const {
|
|
28
|
+
form,
|
|
29
|
+
isSubmitting,
|
|
30
|
+
milestones,
|
|
31
|
+
isAnyMilestoneEmpty,
|
|
32
|
+
handleSubmit,
|
|
33
|
+
handleAddMilestone,
|
|
34
|
+
handleRemoveMilestone,
|
|
35
|
+
fillTemplateForm,
|
|
36
|
+
} = useInitializeEscrow();
|
|
37
|
+
|
|
38
|
+
const handleMilestoneAmountChange = (
|
|
39
|
+
index: number,
|
|
40
|
+
e: React.ChangeEvent<HTMLInputElement>
|
|
41
|
+
) => {
|
|
42
|
+
let rawValue = e.target.value;
|
|
43
|
+
rawValue = rawValue.replace(/[^0-9.]/g, "");
|
|
44
|
+
|
|
45
|
+
if (rawValue.split(".").length > 2) {
|
|
46
|
+
rawValue = rawValue.slice(0, -1);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Limit to 2 decimal places
|
|
50
|
+
if (rawValue.includes(".")) {
|
|
51
|
+
const parts = rawValue.split(".");
|
|
52
|
+
if (parts[1] && parts[1].length > 2) {
|
|
53
|
+
rawValue = parts[0] + "." + parts[1].slice(0, 2);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Always keep as string to allow partial input like "0." or "0.5"
|
|
58
|
+
const updatedMilestones = [...milestones];
|
|
59
|
+
updatedMilestones[index] = {
|
|
60
|
+
...updatedMilestones[index],
|
|
61
|
+
amount: rawValue,
|
|
62
|
+
};
|
|
63
|
+
form.setValue("milestones", updatedMilestones);
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const handlePlatformFeeChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
67
|
+
let rawValue = e.target.value;
|
|
68
|
+
rawValue = rawValue.replace(/[^0-9.]/g, "");
|
|
69
|
+
|
|
70
|
+
if (rawValue.split(".").length > 2) {
|
|
71
|
+
rawValue = rawValue.slice(0, -1);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Limit to 2 decimal places
|
|
75
|
+
if (rawValue.includes(".")) {
|
|
76
|
+
const parts = rawValue.split(".");
|
|
77
|
+
if (parts[1] && parts[1].length > 2) {
|
|
78
|
+
rawValue = parts[0] + "." + parts[1].slice(0, 2);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Always keep as string to allow partial input like "0." or "0.5"
|
|
83
|
+
form.setValue("platformFee", rawValue);
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
return (
|
|
87
|
+
<Form {...form}>
|
|
88
|
+
<form onSubmit={handleSubmit} className="flex flex-col space-y-6">
|
|
89
|
+
<Card className="flex flex-col sm:flex-row justify-between items-start sm:items-center gap-4 p-4">
|
|
90
|
+
<Link
|
|
91
|
+
className="flex-1"
|
|
92
|
+
href="https://docs.trustlesswork.com/trustless-work/technology-overview/escrow-types"
|
|
93
|
+
target="_blank"
|
|
94
|
+
>
|
|
95
|
+
<div className="flex items-center gap-2">
|
|
96
|
+
<div className="h-2 w-2 rounded-full bg-primary" />
|
|
97
|
+
<h2 className="text-xl font-semibold">Multi Release Escrow</h2>
|
|
98
|
+
</div>
|
|
99
|
+
<p className="text-muted-foreground mt-1">
|
|
100
|
+
Fill out the form to initialize a multi release escrow
|
|
101
|
+
</p>
|
|
102
|
+
</Link>
|
|
103
|
+
{process.env.NODE_ENV !== "production" && (
|
|
104
|
+
<Button
|
|
105
|
+
type="button"
|
|
106
|
+
variant="outline"
|
|
107
|
+
onClick={fillTemplateForm}
|
|
108
|
+
className="cursor-pointer"
|
|
109
|
+
>
|
|
110
|
+
Autofill
|
|
111
|
+
</Button>
|
|
112
|
+
)}
|
|
113
|
+
</Card>
|
|
114
|
+
<div className="grid grid-cols-1 lg:grid-cols-3 gap-4">
|
|
115
|
+
<FormField
|
|
116
|
+
control={form.control}
|
|
117
|
+
name="title"
|
|
118
|
+
render={({ field }) => (
|
|
119
|
+
<FormItem>
|
|
120
|
+
<FormLabel className="flex items-center">
|
|
121
|
+
Title<span className="text-destructive ml-1">*</span>
|
|
122
|
+
</FormLabel>
|
|
123
|
+
<FormControl>
|
|
124
|
+
<Input
|
|
125
|
+
placeholder="Escrow title"
|
|
126
|
+
{...field}
|
|
127
|
+
onChange={(e) => {
|
|
128
|
+
field.onChange(e);
|
|
129
|
+
}}
|
|
130
|
+
/>
|
|
131
|
+
</FormControl>
|
|
132
|
+
<FormMessage />
|
|
133
|
+
</FormItem>
|
|
134
|
+
)}
|
|
135
|
+
/>
|
|
136
|
+
|
|
137
|
+
<FormField
|
|
138
|
+
control={form.control}
|
|
139
|
+
name="engagementId"
|
|
140
|
+
render={({ field }) => (
|
|
141
|
+
<FormItem>
|
|
142
|
+
<FormLabel className="flex items-center">
|
|
143
|
+
Engagement<span className="text-destructive ml-1">*</span>
|
|
144
|
+
</FormLabel>
|
|
145
|
+
<FormControl>
|
|
146
|
+
<Input
|
|
147
|
+
placeholder="Enter identifier"
|
|
148
|
+
{...field}
|
|
149
|
+
onChange={(e) => {
|
|
150
|
+
field.onChange(e);
|
|
151
|
+
}}
|
|
152
|
+
/>
|
|
153
|
+
</FormControl>
|
|
154
|
+
<FormMessage />
|
|
155
|
+
</FormItem>
|
|
156
|
+
)}
|
|
157
|
+
/>
|
|
158
|
+
|
|
159
|
+
<FormField
|
|
160
|
+
control={form.control}
|
|
161
|
+
name="trustline.address"
|
|
162
|
+
render={({ field }) => (
|
|
163
|
+
<FormItem>
|
|
164
|
+
<FormLabel className="flex items-center">
|
|
165
|
+
Trustline<span className="text-destructive ml-1">*</span>
|
|
166
|
+
</FormLabel>
|
|
167
|
+
<FormControl>
|
|
168
|
+
<Select
|
|
169
|
+
value={field.value}
|
|
170
|
+
onValueChange={(e) => {
|
|
171
|
+
field.onChange(e);
|
|
172
|
+
}}
|
|
173
|
+
>
|
|
174
|
+
<SelectTrigger className="w-full">
|
|
175
|
+
<SelectValue placeholder="Select trustline" />
|
|
176
|
+
</SelectTrigger>
|
|
177
|
+
<SelectContent>
|
|
178
|
+
{trustlineOptions
|
|
179
|
+
.filter((option) => option.value)
|
|
180
|
+
.map((option, index) => (
|
|
181
|
+
<SelectItem
|
|
182
|
+
key={`${option.value}-${index}`}
|
|
183
|
+
value={option.value}
|
|
184
|
+
>
|
|
185
|
+
{option.label}
|
|
186
|
+
</SelectItem>
|
|
187
|
+
))}
|
|
188
|
+
</SelectContent>
|
|
189
|
+
</Select>
|
|
190
|
+
</FormControl>
|
|
191
|
+
<FormMessage />
|
|
192
|
+
</FormItem>
|
|
193
|
+
)}
|
|
194
|
+
/>
|
|
195
|
+
</div>
|
|
196
|
+
|
|
197
|
+
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
|
|
198
|
+
<FormField
|
|
199
|
+
control={form.control}
|
|
200
|
+
name="roles.approver"
|
|
201
|
+
render={({ field }) => (
|
|
202
|
+
<FormItem>
|
|
203
|
+
<FormLabel className="flex items-center justify-between">
|
|
204
|
+
<span className="flex items-center">
|
|
205
|
+
Approver<span className="text-destructive ml-1">*</span>
|
|
206
|
+
</span>
|
|
207
|
+
</FormLabel>
|
|
208
|
+
|
|
209
|
+
<FormControl>
|
|
210
|
+
<Input
|
|
211
|
+
placeholder="Enter approver address"
|
|
212
|
+
{...field}
|
|
213
|
+
onChange={(e) => {
|
|
214
|
+
field.onChange(e);
|
|
215
|
+
}}
|
|
216
|
+
/>
|
|
217
|
+
</FormControl>
|
|
218
|
+
<FormMessage />
|
|
219
|
+
</FormItem>
|
|
220
|
+
)}
|
|
221
|
+
/>
|
|
222
|
+
|
|
223
|
+
<FormField
|
|
224
|
+
control={form.control}
|
|
225
|
+
name="roles.serviceProvider"
|
|
226
|
+
render={({ field }) => (
|
|
227
|
+
<FormItem>
|
|
228
|
+
<FormLabel className="flex items-center justify-between">
|
|
229
|
+
<span className="flex items-center">
|
|
230
|
+
Service Provider
|
|
231
|
+
<span className="text-destructive ml-1">*</span>
|
|
232
|
+
</span>
|
|
233
|
+
</FormLabel>
|
|
234
|
+
|
|
235
|
+
<FormControl>
|
|
236
|
+
<Input
|
|
237
|
+
placeholder="Enter service provider address"
|
|
238
|
+
{...field}
|
|
239
|
+
onChange={(e) => {
|
|
240
|
+
field.onChange(e);
|
|
241
|
+
}}
|
|
242
|
+
/>
|
|
243
|
+
</FormControl>
|
|
244
|
+
<FormMessage />
|
|
245
|
+
</FormItem>
|
|
246
|
+
)}
|
|
247
|
+
/>
|
|
248
|
+
</div>
|
|
249
|
+
|
|
250
|
+
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
|
|
251
|
+
<FormField
|
|
252
|
+
control={form.control}
|
|
253
|
+
name="roles.releaseSigner"
|
|
254
|
+
render={({ field }) => (
|
|
255
|
+
<FormItem>
|
|
256
|
+
<FormLabel className="flex items-center justify-between">
|
|
257
|
+
<span className="flex items-center">
|
|
258
|
+
Release Signer
|
|
259
|
+
<span className="text-destructive ml-1">*</span>
|
|
260
|
+
</span>
|
|
261
|
+
</FormLabel>
|
|
262
|
+
|
|
263
|
+
<FormControl>
|
|
264
|
+
<Input
|
|
265
|
+
placeholder="Enter release signer address"
|
|
266
|
+
{...field}
|
|
267
|
+
onChange={(e) => {
|
|
268
|
+
field.onChange(e);
|
|
269
|
+
}}
|
|
270
|
+
/>
|
|
271
|
+
</FormControl>
|
|
272
|
+
<FormMessage />
|
|
273
|
+
</FormItem>
|
|
274
|
+
)}
|
|
275
|
+
/>
|
|
276
|
+
|
|
277
|
+
<FormField
|
|
278
|
+
control={form.control}
|
|
279
|
+
name="roles.disputeResolver"
|
|
280
|
+
render={({ field }) => (
|
|
281
|
+
<FormItem>
|
|
282
|
+
<FormLabel className="flex items-center justify-between">
|
|
283
|
+
<span className="flex items-center">
|
|
284
|
+
Dispute Resolver
|
|
285
|
+
<span className="text-destructive ml-1">*</span>
|
|
286
|
+
</span>
|
|
287
|
+
</FormLabel>
|
|
288
|
+
|
|
289
|
+
<FormControl>
|
|
290
|
+
<Input
|
|
291
|
+
placeholder="Enter dispute resolver address"
|
|
292
|
+
{...field}
|
|
293
|
+
onChange={(e) => {
|
|
294
|
+
field.onChange(e);
|
|
295
|
+
}}
|
|
296
|
+
/>
|
|
297
|
+
</FormControl>
|
|
298
|
+
<FormMessage />
|
|
299
|
+
</FormItem>
|
|
300
|
+
)}
|
|
301
|
+
/>
|
|
302
|
+
</div>
|
|
303
|
+
|
|
304
|
+
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
|
|
305
|
+
<FormField
|
|
306
|
+
control={form.control}
|
|
307
|
+
name="roles.platformAddress"
|
|
308
|
+
render={({ field }) => (
|
|
309
|
+
<FormItem>
|
|
310
|
+
<FormLabel className="flex items-center justify-between">
|
|
311
|
+
<span className="flex items-center">
|
|
312
|
+
Platform Address
|
|
313
|
+
<span className="text-destructive ml-1">*</span>
|
|
314
|
+
</span>
|
|
315
|
+
</FormLabel>
|
|
316
|
+
|
|
317
|
+
<FormControl>
|
|
318
|
+
<Input
|
|
319
|
+
placeholder="Enter platform address"
|
|
320
|
+
{...field}
|
|
321
|
+
onChange={(e) => {
|
|
322
|
+
field.onChange(e);
|
|
323
|
+
}}
|
|
324
|
+
/>
|
|
325
|
+
</FormControl>
|
|
326
|
+
<FormMessage />
|
|
327
|
+
</FormItem>
|
|
328
|
+
)}
|
|
329
|
+
/>
|
|
330
|
+
<FormField
|
|
331
|
+
control={form.control}
|
|
332
|
+
name="roles.receiver"
|
|
333
|
+
render={({ field }) => (
|
|
334
|
+
<FormItem>
|
|
335
|
+
<FormLabel className="flex items-center justify-between">
|
|
336
|
+
<span className="flex items-center">
|
|
337
|
+
Receiver<span className="text-destructive ml-1">*</span>
|
|
338
|
+
</span>
|
|
339
|
+
</FormLabel>
|
|
340
|
+
|
|
341
|
+
<FormControl>
|
|
342
|
+
<Input
|
|
343
|
+
placeholder="Enter receiver address"
|
|
344
|
+
{...field}
|
|
345
|
+
onChange={(e) => {
|
|
346
|
+
field.onChange(e);
|
|
347
|
+
}}
|
|
348
|
+
/>
|
|
349
|
+
</FormControl>
|
|
350
|
+
<FormMessage />
|
|
351
|
+
</FormItem>
|
|
352
|
+
)}
|
|
353
|
+
/>
|
|
354
|
+
</div>
|
|
355
|
+
|
|
356
|
+
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
|
|
357
|
+
<FormField
|
|
358
|
+
control={form.control}
|
|
359
|
+
name="platformFee"
|
|
360
|
+
render={() => (
|
|
361
|
+
<FormItem>
|
|
362
|
+
<FormLabel className="flex items-center">
|
|
363
|
+
Platform Fee<span className="text-destructive ml-1">*</span>
|
|
364
|
+
</FormLabel>
|
|
365
|
+
<FormControl>
|
|
366
|
+
<div className="relative">
|
|
367
|
+
<Percent
|
|
368
|
+
className="absolute left-3 top-1/2 -translate-y-1/2 text-gray-500"
|
|
369
|
+
size={18}
|
|
370
|
+
/>
|
|
371
|
+
<Input
|
|
372
|
+
placeholder="Enter platform fee"
|
|
373
|
+
className="pl-10"
|
|
374
|
+
value={form.watch("platformFee")?.toString() || ""}
|
|
375
|
+
onChange={handlePlatformFeeChange}
|
|
376
|
+
/>
|
|
377
|
+
</div>
|
|
378
|
+
</FormControl>
|
|
379
|
+
<FormMessage />
|
|
380
|
+
</FormItem>
|
|
381
|
+
)}
|
|
382
|
+
/>
|
|
383
|
+
|
|
384
|
+
<FormField
|
|
385
|
+
control={form.control}
|
|
386
|
+
name="receiverMemo"
|
|
387
|
+
render={({ field }) => (
|
|
388
|
+
<FormItem>
|
|
389
|
+
<FormLabel className="flex items-center">
|
|
390
|
+
Receiver Memo (opcional)
|
|
391
|
+
</FormLabel>
|
|
392
|
+
<FormControl>
|
|
393
|
+
<Input
|
|
394
|
+
type="text"
|
|
395
|
+
placeholder="Enter the escrow receiver Memo"
|
|
396
|
+
{...field}
|
|
397
|
+
onChange={(e) => {
|
|
398
|
+
field.onChange(e);
|
|
399
|
+
}}
|
|
400
|
+
/>
|
|
401
|
+
</FormControl>
|
|
402
|
+
<FormMessage />
|
|
403
|
+
</FormItem>
|
|
404
|
+
)}
|
|
405
|
+
/>
|
|
406
|
+
</div>
|
|
407
|
+
|
|
408
|
+
<FormField
|
|
409
|
+
control={form.control}
|
|
410
|
+
name="description"
|
|
411
|
+
render={({ field }) => (
|
|
412
|
+
<FormItem>
|
|
413
|
+
<FormLabel className="flex items-center">
|
|
414
|
+
Description<span className="text-destructive ml-1">*</span>
|
|
415
|
+
</FormLabel>
|
|
416
|
+
<FormControl>
|
|
417
|
+
<Textarea
|
|
418
|
+
placeholder="Escrow description"
|
|
419
|
+
{...field}
|
|
420
|
+
onChange={(e) => {
|
|
421
|
+
field.onChange(e);
|
|
422
|
+
}}
|
|
423
|
+
/>
|
|
424
|
+
</FormControl>
|
|
425
|
+
<FormMessage />
|
|
426
|
+
</FormItem>
|
|
427
|
+
)}
|
|
428
|
+
/>
|
|
429
|
+
|
|
430
|
+
<div className="space-y-4">
|
|
431
|
+
<FormLabel className="flex items-center">
|
|
432
|
+
Milestones<span className="text-destructive ml-1">*</span>
|
|
433
|
+
</FormLabel>
|
|
434
|
+
{milestones.map((milestone, index) => (
|
|
435
|
+
<div key={index} className="space-y-4">
|
|
436
|
+
<div className="flex flex-col sm:flex-row items-start sm:items-center space-y-2 sm:space-y-0 sm:space-x-4">
|
|
437
|
+
<Input
|
|
438
|
+
placeholder="Milestone Description"
|
|
439
|
+
value={milestone.description}
|
|
440
|
+
className="w-full sm:w-3/5"
|
|
441
|
+
onChange={(e) => {
|
|
442
|
+
const updatedMilestones = [...milestones];
|
|
443
|
+
updatedMilestones[index].description = e.target.value;
|
|
444
|
+
form.setValue("milestones", updatedMilestones);
|
|
445
|
+
}}
|
|
446
|
+
/>
|
|
447
|
+
|
|
448
|
+
<div className="relative w-full sm:w-2/5">
|
|
449
|
+
<DollarSign
|
|
450
|
+
className="absolute left-3 top-1/2 -translate-y-1/2 text-gray-500"
|
|
451
|
+
size={18}
|
|
452
|
+
/>
|
|
453
|
+
<Input
|
|
454
|
+
className="pl-10"
|
|
455
|
+
placeholder="Enter amount"
|
|
456
|
+
value={milestone.amount?.toString() || ""}
|
|
457
|
+
onChange={(e) => handleMilestoneAmountChange(index, e)}
|
|
458
|
+
/>
|
|
459
|
+
</div>
|
|
460
|
+
|
|
461
|
+
<Button
|
|
462
|
+
onClick={() => handleRemoveMilestone(index)}
|
|
463
|
+
className="p-2 bg-transparent text-red-500 rounded-md border-none shadow-none hover:bg-transparent hover:shadow-none hover:text-red-500 focus:ring-0 active:ring-0 self-start sm:self-center"
|
|
464
|
+
disabled={milestones.length === 1}
|
|
465
|
+
>
|
|
466
|
+
<Trash2 className="h-5 w-5" />
|
|
467
|
+
</Button>
|
|
468
|
+
</div>
|
|
469
|
+
|
|
470
|
+
{index === milestones.length - 1 && (
|
|
471
|
+
<div className="flex justify-end mt-4">
|
|
472
|
+
<Button
|
|
473
|
+
disabled={isAnyMilestoneEmpty}
|
|
474
|
+
className="w-full md:w-1/4"
|
|
475
|
+
variant="outline"
|
|
476
|
+
onClick={handleAddMilestone}
|
|
477
|
+
type="button"
|
|
478
|
+
>
|
|
479
|
+
Add Item
|
|
480
|
+
</Button>
|
|
481
|
+
</div>
|
|
482
|
+
)}
|
|
483
|
+
</div>
|
|
484
|
+
))}
|
|
485
|
+
</div>
|
|
486
|
+
|
|
487
|
+
<div className="flex justify-start">
|
|
488
|
+
<Button
|
|
489
|
+
className="w-full md:w-1/4 cursor-pointer"
|
|
490
|
+
type="submit"
|
|
491
|
+
disabled={isAnyMilestoneEmpty || isSubmitting}
|
|
492
|
+
>
|
|
493
|
+
{isSubmitting ? (
|
|
494
|
+
<div className="flex items-center">
|
|
495
|
+
<Loader2 className="h-5 w-5 animate-spin" />
|
|
496
|
+
<span className="ml-2">Deploying...</span>
|
|
497
|
+
</div>
|
|
498
|
+
) : (
|
|
499
|
+
"Deploy"
|
|
500
|
+
)}
|
|
501
|
+
</Button>
|
|
502
|
+
</div>
|
|
503
|
+
</form>
|
|
504
|
+
</Form>
|
|
505
|
+
);
|
|
506
|
+
};
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { isValidWallet } from "../../../../wallet-kit/validators";
|
|
3
|
+
|
|
4
|
+
export const useInitializeEscrowSchema = () => {
|
|
5
|
+
const getBaseSchema = () => {
|
|
6
|
+
return z.object({
|
|
7
|
+
trustline: z.object({
|
|
8
|
+
address: z.string().min(1, {
|
|
9
|
+
message: "Trustline address is required.",
|
|
10
|
+
}),
|
|
11
|
+
decimals: z.number().default(10000000),
|
|
12
|
+
}),
|
|
13
|
+
roles: z.object({
|
|
14
|
+
approver: z
|
|
15
|
+
.string()
|
|
16
|
+
.min(1, {
|
|
17
|
+
message: "Approver is required.",
|
|
18
|
+
})
|
|
19
|
+
.refine((value) => isValidWallet(value), {
|
|
20
|
+
message: "Approver must be a valid wallet.",
|
|
21
|
+
}),
|
|
22
|
+
serviceProvider: z
|
|
23
|
+
.string()
|
|
24
|
+
.min(1, {
|
|
25
|
+
message: "Service provider is required.",
|
|
26
|
+
})
|
|
27
|
+
.refine((value) => isValidWallet(value), {
|
|
28
|
+
message: "Service provider must be a valid wallet.",
|
|
29
|
+
}),
|
|
30
|
+
platformAddress: z
|
|
31
|
+
.string()
|
|
32
|
+
.min(1, {
|
|
33
|
+
message: "Platform address is required.",
|
|
34
|
+
})
|
|
35
|
+
.refine((value) => isValidWallet(value), {
|
|
36
|
+
message: "Platform address must be a valid wallet.",
|
|
37
|
+
}),
|
|
38
|
+
releaseSigner: z
|
|
39
|
+
.string()
|
|
40
|
+
.min(1, {
|
|
41
|
+
message: "Release signer is required.",
|
|
42
|
+
})
|
|
43
|
+
.refine((value) => isValidWallet(value), {
|
|
44
|
+
message: "Release signer must be a valid wallet.",
|
|
45
|
+
}),
|
|
46
|
+
disputeResolver: z
|
|
47
|
+
.string()
|
|
48
|
+
.min(1, {
|
|
49
|
+
message: "Dispute resolver is required.",
|
|
50
|
+
})
|
|
51
|
+
.refine((value) => isValidWallet(value), {
|
|
52
|
+
message: "Dispute resolver must be a valid wallet.",
|
|
53
|
+
}),
|
|
54
|
+
receiver: z
|
|
55
|
+
.string()
|
|
56
|
+
.min(1, {
|
|
57
|
+
message: "Receiver address is required.",
|
|
58
|
+
})
|
|
59
|
+
.refine((value) => isValidWallet(value), {
|
|
60
|
+
message: "Receiver address must be a valid wallet.",
|
|
61
|
+
}),
|
|
62
|
+
}),
|
|
63
|
+
engagementId: z.string().min(1, {
|
|
64
|
+
message: "Engagement is required.",
|
|
65
|
+
}),
|
|
66
|
+
title: z.string().min(1, {
|
|
67
|
+
message: "Title is required.",
|
|
68
|
+
}),
|
|
69
|
+
description: z.string().min(10, {
|
|
70
|
+
message: "Description must be at least 10 characters long.",
|
|
71
|
+
}),
|
|
72
|
+
platformFee: z
|
|
73
|
+
.union([z.string(), z.number()])
|
|
74
|
+
.refine(
|
|
75
|
+
(val) => {
|
|
76
|
+
if (typeof val === "string") {
|
|
77
|
+
if (val === "" || val === "." || val.endsWith(".")) {
|
|
78
|
+
return true;
|
|
79
|
+
}
|
|
80
|
+
const numVal = Number(val);
|
|
81
|
+
return !isNaN(numVal) && numVal > 0;
|
|
82
|
+
}
|
|
83
|
+
return val > 0;
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
message: "Platform fee must be greater than 0.",
|
|
87
|
+
}
|
|
88
|
+
)
|
|
89
|
+
.refine(
|
|
90
|
+
(val) => {
|
|
91
|
+
if (typeof val === "string") {
|
|
92
|
+
if (val === "" || val === "." || val.endsWith(".")) {
|
|
93
|
+
return true;
|
|
94
|
+
}
|
|
95
|
+
const numVal = Number(val);
|
|
96
|
+
if (isNaN(numVal)) return false;
|
|
97
|
+
const decimalPlaces = (numVal.toString().split(".")[1] || "")
|
|
98
|
+
.length;
|
|
99
|
+
return decimalPlaces <= 2;
|
|
100
|
+
}
|
|
101
|
+
const decimalPlaces = (val.toString().split(".")[1] || "").length;
|
|
102
|
+
return decimalPlaces <= 2;
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
message: "Platform fee can have a maximum of 2 decimal places.",
|
|
106
|
+
}
|
|
107
|
+
),
|
|
108
|
+
receiverMemo: z
|
|
109
|
+
.string()
|
|
110
|
+
.optional()
|
|
111
|
+
.refine((val) => !val || val.length >= 1, {
|
|
112
|
+
message: "Receiver Memo must be at least 1.",
|
|
113
|
+
})
|
|
114
|
+
.refine((val) => !val || /^[1-9][0-9]*$/.test(val), {
|
|
115
|
+
message:
|
|
116
|
+
"Receiver Memo must be a whole number greater than 0 (no decimals).",
|
|
117
|
+
}),
|
|
118
|
+
});
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
const getMultiReleaseFormSchema = () => {
|
|
122
|
+
const baseSchema = getBaseSchema();
|
|
123
|
+
|
|
124
|
+
return baseSchema.extend({
|
|
125
|
+
milestones: z
|
|
126
|
+
.array(
|
|
127
|
+
z.object({
|
|
128
|
+
description: z.string().min(1, {
|
|
129
|
+
message: "Milestone description is required.",
|
|
130
|
+
}),
|
|
131
|
+
amount: z
|
|
132
|
+
.union([z.string(), z.number()])
|
|
133
|
+
.refine(
|
|
134
|
+
(val) => {
|
|
135
|
+
if (typeof val === "string") {
|
|
136
|
+
if (val === "" || val === "." || val.endsWith(".")) {
|
|
137
|
+
return true;
|
|
138
|
+
}
|
|
139
|
+
const numVal = Number(val);
|
|
140
|
+
return !isNaN(numVal) && numVal > 0;
|
|
141
|
+
}
|
|
142
|
+
return val > 0;
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
message: "Milestone amount must be greater than 0.",
|
|
146
|
+
}
|
|
147
|
+
)
|
|
148
|
+
.refine(
|
|
149
|
+
(val) => {
|
|
150
|
+
if (typeof val === "string") {
|
|
151
|
+
if (val === "" || val === "." || val.endsWith(".")) {
|
|
152
|
+
return true;
|
|
153
|
+
}
|
|
154
|
+
const numVal = Number(val);
|
|
155
|
+
if (isNaN(numVal)) return false;
|
|
156
|
+
const decimalPlaces = (
|
|
157
|
+
numVal.toString().split(".")[1] || ""
|
|
158
|
+
).length;
|
|
159
|
+
return decimalPlaces <= 2;
|
|
160
|
+
}
|
|
161
|
+
const decimalPlaces = (val.toString().split(".")[1] || "")
|
|
162
|
+
.length;
|
|
163
|
+
return decimalPlaces <= 2;
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
message:
|
|
167
|
+
"Milestone amount can have a maximum of 2 decimal places.",
|
|
168
|
+
}
|
|
169
|
+
),
|
|
170
|
+
})
|
|
171
|
+
)
|
|
172
|
+
.min(1, { message: "At least one milestone is required." }),
|
|
173
|
+
});
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
return {
|
|
177
|
+
getMultiReleaseFormSchema,
|
|
178
|
+
};
|
|
179
|
+
};
|