@trustless-work/blocks 0.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/README.md +96 -0
- package/bin/index.js +1123 -0
- package/package.json +44 -0
- package/templates/deps.json +29 -0
- package/templates/escrows/details/Actions.tsx +149 -0
- package/templates/escrows/details/Entities.tsx +48 -0
- package/templates/escrows/details/EntityCard.tsx +98 -0
- package/templates/escrows/details/EscrowDetailDialog.tsx +154 -0
- package/templates/escrows/details/GeneralInformation.tsx +329 -0
- package/templates/escrows/details/MilestoneCard.tsx +254 -0
- package/templates/escrows/details/MilestoneDetailDialog.tsx +276 -0
- package/templates/escrows/details/Milestones.tsx +87 -0
- package/templates/escrows/details/ProgressEscrow.tsx +191 -0
- package/templates/escrows/details/StatisticsCard.tsx +79 -0
- package/templates/escrows/details/SuccessReleaseDialog.tsx +101 -0
- package/templates/escrows/details/useDetailsEscrow.ts +126 -0
- package/templates/escrows/escrow-context/EscrowAmountProvider.tsx +86 -0
- package/templates/escrows/escrow-context/EscrowDialogsProvider.tsx +108 -0
- package/templates/escrows/escrow-context/EscrowProvider.tsx +124 -0
- package/templates/escrows/escrows-by-role/cards/EscrowsCards.tsx +503 -0
- package/templates/escrows/escrows-by-role/cards/Filters.tsx +421 -0
- package/templates/escrows/escrows-by-role/table/EscrowsTable.tsx +427 -0
- package/templates/escrows/escrows-by-role/table/Filters.tsx +421 -0
- package/templates/escrows/escrows-by-role/useEscrowsByRole.shared.ts +336 -0
- package/templates/escrows/escrows-by-signer/cards/EscrowsCards.tsx +502 -0
- package/templates/escrows/escrows-by-signer/cards/Filters.tsx +389 -0
- package/templates/escrows/escrows-by-signer/table/EscrowsTable.tsx +422 -0
- package/templates/escrows/escrows-by-signer/table/Filters.tsx +389 -0
- package/templates/escrows/escrows-by-signer/useEscrowsBySigner.shared.ts +320 -0
- package/templates/escrows/single-release/approve-milestone/button/ApproveMilestone.tsx +78 -0
- package/templates/escrows/single-release/approve-milestone/dialog/ApproveMilestone.tsx +102 -0
- package/templates/escrows/single-release/approve-milestone/form/ApproveMilestone.tsx +80 -0
- package/templates/escrows/single-release/approve-milestone/shared/schema.ts +9 -0
- package/templates/escrows/single-release/approve-milestone/shared/useApproveMilestone.ts +67 -0
- package/templates/escrows/single-release/change-milestone-status/button/ChangeMilestoneStatus.tsx +78 -0
- package/templates/escrows/single-release/change-milestone-status/dialog/ChangeMilestoneStatus.tsx +167 -0
- package/templates/escrows/single-release/change-milestone-status/form/ChangeMilestoneStatus.tsx +114 -0
- package/templates/escrows/single-release/change-milestone-status/shared/schema.ts +15 -0
- package/templates/escrows/single-release/change-milestone-status/shared/useChangeMilestoneStatus.ts +77 -0
- package/templates/escrows/single-release/dispute-escrow/button/DisputeEscrow.tsx +68 -0
- package/templates/escrows/single-release/fund-escrow/button/FundEscrow.tsx +84 -0
- package/templates/escrows/single-release/fund-escrow/dialog/FundEscrow.tsx +77 -0
- package/templates/escrows/single-release/fund-escrow/form/FundEscrow.tsx +54 -0
- package/templates/escrows/single-release/fund-escrow/shared/schema.ts +10 -0
- package/templates/escrows/single-release/fund-escrow/shared/useFundEscrow.ts +66 -0
- package/templates/escrows/single-release/initialize-escrow/dialog/InitializeEscrow.tsx +526 -0
- package/templates/escrows/single-release/initialize-escrow/form/InitializeEscrow.tsx +504 -0
- package/templates/escrows/single-release/initialize-escrow/shared/schema.ts +232 -0
- package/templates/escrows/single-release/initialize-escrow/shared/useInitializeEscrow.ts +115 -0
- package/templates/escrows/single-release/release-escrow/button/ReleaseEscrow.tsx +80 -0
- package/templates/escrows/single-release/resolve-dispute/button/ResolveDispute.tsx +94 -0
- package/templates/escrows/single-release/resolve-dispute/dialog/ResolveDispute.tsx +123 -0
- package/templates/escrows/single-release/resolve-dispute/form/ResolveDispute.tsx +82 -0
- package/templates/escrows/single-release/resolve-dispute/shared/schema.ts +82 -0
- package/templates/escrows/single-release/resolve-dispute/shared/useResolveDispute.ts +58 -0
- package/templates/escrows/single-release/update-escrow/dialog/UpdateEscrow.tsx +485 -0
- package/templates/escrows/single-release/update-escrow/form/UpdateEscrow.tsx +463 -0
- package/templates/escrows/single-release/update-escrow/shared/schema.ts +139 -0
- package/templates/escrows/single-release/update-escrow/shared/useUpdateEscrow.ts +211 -0
- package/templates/handle-errors/errors.enum.ts +6 -0
- package/templates/handle-errors/handle.ts +47 -0
- package/templates/helpers/format.helper.ts +27 -0
- package/templates/helpers/useCopy.ts +13 -0
- package/templates/providers/ReactQueryClientProvider.tsx +28 -0
- package/templates/providers/TrustlessWork.tsx +30 -0
- package/templates/tanstak/useEscrowsByRoleQuery.ts +87 -0
- package/templates/tanstak/useEscrowsBySignerQuery.ts +78 -0
- package/templates/tanstak/useEscrowsMutations.ts +411 -0
- package/templates/wallet-kit/WalletButtons.tsx +116 -0
- package/templates/wallet-kit/WalletProvider.tsx +94 -0
- package/templates/wallet-kit/trustlines.ts +40 -0
- package/templates/wallet-kit/useWallet.ts +77 -0
- package/templates/wallet-kit/validators.ts +12 -0
- package/templates/wallet-kit/wallet-kit.ts +30 -0
|
@@ -0,0 +1,463 @@
|
|
|
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 { useUpdateEscrow } from "./useUpdateEscrow";
|
|
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 default function UpdateEscrowForm() {
|
|
27
|
+
const {
|
|
28
|
+
form,
|
|
29
|
+
isSubmitting,
|
|
30
|
+
milestones,
|
|
31
|
+
isAnyMilestoneEmpty,
|
|
32
|
+
handleSubmit,
|
|
33
|
+
handleAddMilestone,
|
|
34
|
+
handleRemoveMilestone,
|
|
35
|
+
handleAmountChange,
|
|
36
|
+
handlePlatformFeeChange,
|
|
37
|
+
} = useUpdateEscrow();
|
|
38
|
+
|
|
39
|
+
return (
|
|
40
|
+
<Form {...form}>
|
|
41
|
+
<form onSubmit={handleSubmit} className="flex flex-col space-y-6">
|
|
42
|
+
<Card className="flex flex-col sm:flex-row justify-between items-start sm:items-center gap-4 p-4">
|
|
43
|
+
<Link
|
|
44
|
+
className="flex-1"
|
|
45
|
+
href="https://docs.trustlesswork.com/trustless-work/technology-overview/escrow-types"
|
|
46
|
+
target="_blank"
|
|
47
|
+
>
|
|
48
|
+
<div className="flex items-center gap-2">
|
|
49
|
+
<div className="h-2 w-2 rounded-full bg-primary" />
|
|
50
|
+
<h2 className="text-xl font-semibold">Single Release Escrow</h2>
|
|
51
|
+
</div>
|
|
52
|
+
<p className="text-muted-foreground mt-1">
|
|
53
|
+
Update escrow details and milestones
|
|
54
|
+
</p>
|
|
55
|
+
</Link>
|
|
56
|
+
</Card>
|
|
57
|
+
<div className="grid grid-cols-1 lg:grid-cols-3 gap-4">
|
|
58
|
+
<FormField
|
|
59
|
+
control={form.control}
|
|
60
|
+
name="title"
|
|
61
|
+
render={({ field }) => (
|
|
62
|
+
<FormItem>
|
|
63
|
+
<FormLabel className="flex items-center">
|
|
64
|
+
Title<span className="text-destructive ml-1">*</span>
|
|
65
|
+
</FormLabel>
|
|
66
|
+
<FormControl>
|
|
67
|
+
<Input
|
|
68
|
+
placeholder="Escrow title"
|
|
69
|
+
{...field}
|
|
70
|
+
onChange={(e) => {
|
|
71
|
+
field.onChange(e);
|
|
72
|
+
}}
|
|
73
|
+
/>
|
|
74
|
+
</FormControl>
|
|
75
|
+
<FormMessage />
|
|
76
|
+
</FormItem>
|
|
77
|
+
)}
|
|
78
|
+
/>
|
|
79
|
+
|
|
80
|
+
<FormField
|
|
81
|
+
control={form.control}
|
|
82
|
+
name="engagementId"
|
|
83
|
+
render={({ field }) => (
|
|
84
|
+
<FormItem>
|
|
85
|
+
<FormLabel className="flex items-center">
|
|
86
|
+
Engagement<span className="text-destructive ml-1">*</span>
|
|
87
|
+
</FormLabel>
|
|
88
|
+
<FormControl>
|
|
89
|
+
<Input
|
|
90
|
+
placeholder="Enter identifier"
|
|
91
|
+
{...field}
|
|
92
|
+
onChange={(e) => {
|
|
93
|
+
field.onChange(e);
|
|
94
|
+
}}
|
|
95
|
+
/>
|
|
96
|
+
</FormControl>
|
|
97
|
+
<FormMessage />
|
|
98
|
+
</FormItem>
|
|
99
|
+
)}
|
|
100
|
+
/>
|
|
101
|
+
|
|
102
|
+
<FormField
|
|
103
|
+
control={form.control}
|
|
104
|
+
name="trustline.address"
|
|
105
|
+
render={({ field }) => (
|
|
106
|
+
<FormItem>
|
|
107
|
+
<FormLabel className="flex items-center">
|
|
108
|
+
Trustline<span className="text-destructive ml-1">*</span>
|
|
109
|
+
</FormLabel>
|
|
110
|
+
<FormControl>
|
|
111
|
+
<Select
|
|
112
|
+
value={field.value}
|
|
113
|
+
onValueChange={(e) => {
|
|
114
|
+
field.onChange(e);
|
|
115
|
+
}}
|
|
116
|
+
>
|
|
117
|
+
<SelectTrigger className="w-full">
|
|
118
|
+
<SelectValue placeholder="Select trustline" />
|
|
119
|
+
</SelectTrigger>
|
|
120
|
+
<SelectContent>
|
|
121
|
+
{trustlineOptions
|
|
122
|
+
.filter((option) => option.value)
|
|
123
|
+
.map((option, index) => (
|
|
124
|
+
<SelectItem
|
|
125
|
+
key={`${option.value}-${index}`}
|
|
126
|
+
value={option.value}
|
|
127
|
+
>
|
|
128
|
+
{option.label}
|
|
129
|
+
</SelectItem>
|
|
130
|
+
))}
|
|
131
|
+
</SelectContent>
|
|
132
|
+
</Select>
|
|
133
|
+
</FormControl>
|
|
134
|
+
<FormMessage />
|
|
135
|
+
</FormItem>
|
|
136
|
+
)}
|
|
137
|
+
/>
|
|
138
|
+
</div>
|
|
139
|
+
|
|
140
|
+
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
|
|
141
|
+
<FormField
|
|
142
|
+
control={form.control}
|
|
143
|
+
name="roles.approver"
|
|
144
|
+
render={({ field }) => (
|
|
145
|
+
<FormItem>
|
|
146
|
+
<FormLabel className="flex items-center justify-between">
|
|
147
|
+
<span className="flex items-center">
|
|
148
|
+
Approver<span className="text-destructive ml-1">*</span>
|
|
149
|
+
</span>
|
|
150
|
+
</FormLabel>
|
|
151
|
+
|
|
152
|
+
<FormControl>
|
|
153
|
+
<Input
|
|
154
|
+
placeholder="Enter approver address"
|
|
155
|
+
{...field}
|
|
156
|
+
onChange={(e) => {
|
|
157
|
+
field.onChange(e);
|
|
158
|
+
}}
|
|
159
|
+
/>
|
|
160
|
+
</FormControl>
|
|
161
|
+
<FormMessage />
|
|
162
|
+
</FormItem>
|
|
163
|
+
)}
|
|
164
|
+
/>
|
|
165
|
+
|
|
166
|
+
<FormField
|
|
167
|
+
control={form.control}
|
|
168
|
+
name="roles.serviceProvider"
|
|
169
|
+
render={({ field }) => (
|
|
170
|
+
<FormItem>
|
|
171
|
+
<FormLabel className="flex items-center justify-between">
|
|
172
|
+
<span className="flex items-center">
|
|
173
|
+
Service Provider
|
|
174
|
+
<span className="text-destructive ml-1">*</span>
|
|
175
|
+
</span>
|
|
176
|
+
</FormLabel>
|
|
177
|
+
|
|
178
|
+
<FormControl>
|
|
179
|
+
<Input
|
|
180
|
+
placeholder="Enter service provider address"
|
|
181
|
+
{...field}
|
|
182
|
+
onChange={(e) => {
|
|
183
|
+
field.onChange(e);
|
|
184
|
+
}}
|
|
185
|
+
/>
|
|
186
|
+
</FormControl>
|
|
187
|
+
<FormMessage />
|
|
188
|
+
</FormItem>
|
|
189
|
+
)}
|
|
190
|
+
/>
|
|
191
|
+
</div>
|
|
192
|
+
|
|
193
|
+
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
|
|
194
|
+
<FormField
|
|
195
|
+
control={form.control}
|
|
196
|
+
name="roles.releaseSigner"
|
|
197
|
+
render={({ field }) => (
|
|
198
|
+
<FormItem>
|
|
199
|
+
<FormLabel className="flex items-center justify-between">
|
|
200
|
+
<span className="flex items-center">
|
|
201
|
+
Release Signer
|
|
202
|
+
<span className="text-destructive ml-1">*</span>
|
|
203
|
+
</span>
|
|
204
|
+
</FormLabel>
|
|
205
|
+
|
|
206
|
+
<FormControl>
|
|
207
|
+
<Input
|
|
208
|
+
placeholder="Enter release signer address"
|
|
209
|
+
{...field}
|
|
210
|
+
onChange={(e) => {
|
|
211
|
+
field.onChange(e);
|
|
212
|
+
}}
|
|
213
|
+
/>
|
|
214
|
+
</FormControl>
|
|
215
|
+
<FormMessage />
|
|
216
|
+
</FormItem>
|
|
217
|
+
)}
|
|
218
|
+
/>
|
|
219
|
+
|
|
220
|
+
<FormField
|
|
221
|
+
control={form.control}
|
|
222
|
+
name="roles.disputeResolver"
|
|
223
|
+
render={({ field }) => (
|
|
224
|
+
<FormItem>
|
|
225
|
+
<FormLabel className="flex items-center justify-between">
|
|
226
|
+
<span className="flex items-center">
|
|
227
|
+
Dispute Resolver
|
|
228
|
+
<span className="text-destructive ml-1">*</span>
|
|
229
|
+
</span>
|
|
230
|
+
</FormLabel>
|
|
231
|
+
|
|
232
|
+
<FormControl>
|
|
233
|
+
<Input
|
|
234
|
+
placeholder="Enter dispute resolver address"
|
|
235
|
+
{...field}
|
|
236
|
+
onChange={(e) => {
|
|
237
|
+
field.onChange(e);
|
|
238
|
+
}}
|
|
239
|
+
/>
|
|
240
|
+
</FormControl>
|
|
241
|
+
<FormMessage />
|
|
242
|
+
</FormItem>
|
|
243
|
+
)}
|
|
244
|
+
/>
|
|
245
|
+
</div>
|
|
246
|
+
|
|
247
|
+
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
|
|
248
|
+
<FormField
|
|
249
|
+
control={form.control}
|
|
250
|
+
name="roles.platformAddress"
|
|
251
|
+
render={({ field }) => (
|
|
252
|
+
<FormItem>
|
|
253
|
+
<FormLabel className="flex items-center justify-between">
|
|
254
|
+
<span className="flex items-center">
|
|
255
|
+
Platform Address
|
|
256
|
+
<span className="text-destructive ml-1">*</span>
|
|
257
|
+
</span>
|
|
258
|
+
</FormLabel>
|
|
259
|
+
|
|
260
|
+
<FormControl>
|
|
261
|
+
<Input
|
|
262
|
+
placeholder="Enter platform address"
|
|
263
|
+
{...field}
|
|
264
|
+
onChange={(e) => {
|
|
265
|
+
field.onChange(e);
|
|
266
|
+
}}
|
|
267
|
+
/>
|
|
268
|
+
</FormControl>
|
|
269
|
+
<FormMessage />
|
|
270
|
+
</FormItem>
|
|
271
|
+
)}
|
|
272
|
+
/>
|
|
273
|
+
<FormField
|
|
274
|
+
control={form.control}
|
|
275
|
+
name="roles.receiver"
|
|
276
|
+
render={({ field }) => (
|
|
277
|
+
<FormItem>
|
|
278
|
+
<FormLabel className="flex items-center justify-between">
|
|
279
|
+
<span className="flex items-center">
|
|
280
|
+
Receiver<span className="text-destructive ml-1">*</span>
|
|
281
|
+
</span>
|
|
282
|
+
</FormLabel>
|
|
283
|
+
|
|
284
|
+
<FormControl>
|
|
285
|
+
<Input
|
|
286
|
+
placeholder="Enter receiver address"
|
|
287
|
+
{...field}
|
|
288
|
+
onChange={(e) => {
|
|
289
|
+
field.onChange(e);
|
|
290
|
+
}}
|
|
291
|
+
/>
|
|
292
|
+
</FormControl>
|
|
293
|
+
<FormMessage />
|
|
294
|
+
</FormItem>
|
|
295
|
+
)}
|
|
296
|
+
/>
|
|
297
|
+
</div>
|
|
298
|
+
|
|
299
|
+
<div className="grid grid-cols-1 lg:grid-cols-3 gap-4">
|
|
300
|
+
<FormField
|
|
301
|
+
control={form.control}
|
|
302
|
+
name="platformFee"
|
|
303
|
+
render={() => (
|
|
304
|
+
<FormItem>
|
|
305
|
+
<FormLabel className="flex items-center">
|
|
306
|
+
Platform Fee<span className="text-destructive ml-1">*</span>
|
|
307
|
+
</FormLabel>
|
|
308
|
+
<FormControl>
|
|
309
|
+
<div className="relative">
|
|
310
|
+
<Percent
|
|
311
|
+
className="absolute left-3 top-1/2 -translate-y-1/2 text-gray-500"
|
|
312
|
+
size={18}
|
|
313
|
+
/>
|
|
314
|
+
<Input
|
|
315
|
+
placeholder="Enter platform fee"
|
|
316
|
+
className="pl-10"
|
|
317
|
+
value={form.watch("platformFee")?.toString() || ""}
|
|
318
|
+
onChange={handlePlatformFeeChange}
|
|
319
|
+
/>
|
|
320
|
+
</div>
|
|
321
|
+
</FormControl>
|
|
322
|
+
<FormMessage />
|
|
323
|
+
</FormItem>
|
|
324
|
+
)}
|
|
325
|
+
/>
|
|
326
|
+
|
|
327
|
+
<FormField
|
|
328
|
+
control={form.control}
|
|
329
|
+
name="amount"
|
|
330
|
+
render={() => (
|
|
331
|
+
<FormItem>
|
|
332
|
+
<FormLabel className="flex items-center">
|
|
333
|
+
Amount<span className="text-destructive ml-1">*</span>
|
|
334
|
+
</FormLabel>
|
|
335
|
+
<FormControl>
|
|
336
|
+
<div className="relative">
|
|
337
|
+
<DollarSign
|
|
338
|
+
className="absolute left-3 top-1/2 -translate-y-1/2 text-gray-500"
|
|
339
|
+
size={18}
|
|
340
|
+
/>
|
|
341
|
+
<Input
|
|
342
|
+
placeholder="Enter amount"
|
|
343
|
+
className="pl-10"
|
|
344
|
+
value={form.watch("amount")?.toString() || ""}
|
|
345
|
+
onChange={handleAmountChange}
|
|
346
|
+
/>
|
|
347
|
+
</div>
|
|
348
|
+
</FormControl>
|
|
349
|
+
<FormMessage />
|
|
350
|
+
</FormItem>
|
|
351
|
+
)}
|
|
352
|
+
/>
|
|
353
|
+
|
|
354
|
+
<FormField
|
|
355
|
+
control={form.control}
|
|
356
|
+
name="receiverMemo"
|
|
357
|
+
render={({ field }) => (
|
|
358
|
+
<FormItem>
|
|
359
|
+
<FormLabel className="flex items-center">
|
|
360
|
+
Receiver Memo (opcional)
|
|
361
|
+
</FormLabel>
|
|
362
|
+
<FormControl>
|
|
363
|
+
<Input
|
|
364
|
+
type="text"
|
|
365
|
+
placeholder="Enter the escrow receiver Memo"
|
|
366
|
+
{...field}
|
|
367
|
+
onChange={(e) => {
|
|
368
|
+
field.onChange(e);
|
|
369
|
+
}}
|
|
370
|
+
/>
|
|
371
|
+
</FormControl>
|
|
372
|
+
<FormMessage />
|
|
373
|
+
</FormItem>
|
|
374
|
+
)}
|
|
375
|
+
/>
|
|
376
|
+
</div>
|
|
377
|
+
|
|
378
|
+
<FormField
|
|
379
|
+
control={form.control}
|
|
380
|
+
name="description"
|
|
381
|
+
render={({ field }) => (
|
|
382
|
+
<FormItem>
|
|
383
|
+
<FormLabel className="flex items-center">
|
|
384
|
+
Description<span className="text-destructive ml-1">*</span>
|
|
385
|
+
</FormLabel>
|
|
386
|
+
<FormControl>
|
|
387
|
+
<Textarea
|
|
388
|
+
placeholder="Escrow description"
|
|
389
|
+
{...field}
|
|
390
|
+
onChange={(e) => {
|
|
391
|
+
field.onChange(e);
|
|
392
|
+
}}
|
|
393
|
+
/>
|
|
394
|
+
</FormControl>
|
|
395
|
+
<FormMessage />
|
|
396
|
+
</FormItem>
|
|
397
|
+
)}
|
|
398
|
+
/>
|
|
399
|
+
|
|
400
|
+
<div className="space-y-4">
|
|
401
|
+
<FormLabel className="flex items-center">
|
|
402
|
+
Milestones<span className="text-destructive ml-1">*</span>
|
|
403
|
+
</FormLabel>
|
|
404
|
+
{milestones.map((milestone, index) => (
|
|
405
|
+
<div key={index}>
|
|
406
|
+
<div className="flex flex-col sm:flex-row items-start sm:items-center space-y-2 sm:space-y-0 sm:space-x-4">
|
|
407
|
+
<Input
|
|
408
|
+
placeholder="Milestone Description"
|
|
409
|
+
value={milestone.description}
|
|
410
|
+
className="w-full sm:flex-1"
|
|
411
|
+
onChange={(e) => {
|
|
412
|
+
const updatedMilestones = [...milestones];
|
|
413
|
+
updatedMilestones[index].description = e.target.value;
|
|
414
|
+
form.setValue("milestones", updatedMilestones);
|
|
415
|
+
}}
|
|
416
|
+
/>
|
|
417
|
+
|
|
418
|
+
<Button
|
|
419
|
+
onClick={() => handleRemoveMilestone(index)}
|
|
420
|
+
className="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 self-start sm:self-center cursor-pointer"
|
|
421
|
+
disabled={milestones.length === 1}
|
|
422
|
+
>
|
|
423
|
+
<Trash2 className="h-5 w-5" />
|
|
424
|
+
</Button>
|
|
425
|
+
</div>
|
|
426
|
+
|
|
427
|
+
{index === milestones.length - 1 && (
|
|
428
|
+
<div className="flex justify-end mt-4">
|
|
429
|
+
<Button
|
|
430
|
+
disabled={isAnyMilestoneEmpty}
|
|
431
|
+
className="w-full md:w-1/4 cursor-pointer"
|
|
432
|
+
variant="outline"
|
|
433
|
+
onClick={handleAddMilestone}
|
|
434
|
+
type="button"
|
|
435
|
+
>
|
|
436
|
+
Add Item
|
|
437
|
+
</Button>
|
|
438
|
+
</div>
|
|
439
|
+
)}
|
|
440
|
+
</div>
|
|
441
|
+
))}
|
|
442
|
+
</div>
|
|
443
|
+
|
|
444
|
+
<div className="flex justify-start">
|
|
445
|
+
<Button
|
|
446
|
+
className="w-full md:w-1/4 cursor-pointer"
|
|
447
|
+
type="submit"
|
|
448
|
+
disabled={isAnyMilestoneEmpty || isSubmitting}
|
|
449
|
+
>
|
|
450
|
+
{isSubmitting ? (
|
|
451
|
+
<div className="flex items-center">
|
|
452
|
+
<Loader2 className="h-5 w-5 animate-spin" />
|
|
453
|
+
<span className="ml-2">Updating...</span>
|
|
454
|
+
</div>
|
|
455
|
+
) : (
|
|
456
|
+
"Update"
|
|
457
|
+
)}
|
|
458
|
+
</Button>
|
|
459
|
+
</div>
|
|
460
|
+
</form>
|
|
461
|
+
</Form>
|
|
462
|
+
);
|
|
463
|
+
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { isValidWallet } from "@/components/tw-blocks/wallet-kit/validators";
|
|
3
|
+
|
|
4
|
+
export const useUpdateEscrowSchema = () => {
|
|
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, { message: "Approver is required." })
|
|
17
|
+
.refine((value) => isValidWallet(value), {
|
|
18
|
+
message: "Approver must be a valid wallet.",
|
|
19
|
+
}),
|
|
20
|
+
serviceProvider: z
|
|
21
|
+
.string()
|
|
22
|
+
.min(1, { message: "Service provider is required." })
|
|
23
|
+
.refine((value) => isValidWallet(value), {
|
|
24
|
+
message: "Service provider must be a valid wallet.",
|
|
25
|
+
}),
|
|
26
|
+
platformAddress: z
|
|
27
|
+
.string()
|
|
28
|
+
.min(1, { message: "Platform address is required." })
|
|
29
|
+
.refine((value) => isValidWallet(value), {
|
|
30
|
+
message: "Platform address must be a valid wallet.",
|
|
31
|
+
}),
|
|
32
|
+
releaseSigner: z
|
|
33
|
+
.string()
|
|
34
|
+
.min(1, { message: "Release signer is required." })
|
|
35
|
+
.refine((value) => isValidWallet(value), {
|
|
36
|
+
message: "Release signer must be a valid wallet.",
|
|
37
|
+
}),
|
|
38
|
+
disputeResolver: z
|
|
39
|
+
.string()
|
|
40
|
+
.min(1, { message: "Dispute resolver is required." })
|
|
41
|
+
.refine((value) => isValidWallet(value), {
|
|
42
|
+
message: "Dispute resolver must be a valid wallet.",
|
|
43
|
+
}),
|
|
44
|
+
receiver: z
|
|
45
|
+
.string()
|
|
46
|
+
.min(1, { message: "Receiver address is required." })
|
|
47
|
+
.refine((value) => isValidWallet(value), {
|
|
48
|
+
message: "Receiver address must be a valid wallet.",
|
|
49
|
+
}),
|
|
50
|
+
}),
|
|
51
|
+
engagementId: z.string().min(1, { message: "Engagement is required." }),
|
|
52
|
+
title: z.string().min(1, { message: "Title is required." }),
|
|
53
|
+
description: z.string().min(10, {
|
|
54
|
+
message: "Description must be at least 10 characters long.",
|
|
55
|
+
}),
|
|
56
|
+
platformFee: z
|
|
57
|
+
.union([z.string(), z.number()])
|
|
58
|
+
.refine(
|
|
59
|
+
(val) => {
|
|
60
|
+
if (typeof val === "string") {
|
|
61
|
+
if (val === "" || val === "." || val.endsWith(".")) return true;
|
|
62
|
+
const n = Number(val);
|
|
63
|
+
return !isNaN(n) && n > 0;
|
|
64
|
+
}
|
|
65
|
+
return val > 0;
|
|
66
|
+
},
|
|
67
|
+
{ message: "Platform fee must be greater than 0." }
|
|
68
|
+
)
|
|
69
|
+
.refine(
|
|
70
|
+
(val) => {
|
|
71
|
+
if (typeof val === "string") {
|
|
72
|
+
if (val === "" || val === "." || val.endsWith(".")) return true;
|
|
73
|
+
const n = Number(val);
|
|
74
|
+
if (isNaN(n)) return false;
|
|
75
|
+
const dp = (n.toString().split(".")[1] || "").length;
|
|
76
|
+
return dp <= 2;
|
|
77
|
+
}
|
|
78
|
+
const dp = (val.toString().split(".")[1] || "").length;
|
|
79
|
+
return dp <= 2;
|
|
80
|
+
},
|
|
81
|
+
{ message: "Platform fee can have a maximum of 2 decimal places." }
|
|
82
|
+
),
|
|
83
|
+
receiverMemo: z
|
|
84
|
+
.string()
|
|
85
|
+
.optional()
|
|
86
|
+
.refine((val) => !val || val.length >= 1, {
|
|
87
|
+
message: "Receiver Memo must be at least 1.",
|
|
88
|
+
})
|
|
89
|
+
.refine((val) => !val || /^[1-9][0-9]*$/.test(val), {
|
|
90
|
+
message:
|
|
91
|
+
"Receiver Memo must be a whole number greater than 0 (no decimals).",
|
|
92
|
+
}),
|
|
93
|
+
});
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
const getSingleReleaseFormSchema = () => {
|
|
97
|
+
const baseSchema = getBaseSchema();
|
|
98
|
+
return baseSchema.extend({
|
|
99
|
+
amount: z
|
|
100
|
+
.union([z.string(), z.number()])
|
|
101
|
+
.refine(
|
|
102
|
+
(val) => {
|
|
103
|
+
if (typeof val === "string") {
|
|
104
|
+
if (val === "" || val === "." || val.endsWith(".")) return true;
|
|
105
|
+
const n = Number(val);
|
|
106
|
+
return !isNaN(n) && n > 0;
|
|
107
|
+
}
|
|
108
|
+
return val > 0;
|
|
109
|
+
},
|
|
110
|
+
{ message: "Amount must be greater than 0." }
|
|
111
|
+
)
|
|
112
|
+
.refine(
|
|
113
|
+
(val) => {
|
|
114
|
+
if (typeof val === "string") {
|
|
115
|
+
if (val === "" || val === "." || val.endsWith(".")) return true;
|
|
116
|
+
const n = Number(val);
|
|
117
|
+
if (isNaN(n)) return false;
|
|
118
|
+
const dp = (n.toString().split(".")[1] || "").length;
|
|
119
|
+
return dp <= 2;
|
|
120
|
+
}
|
|
121
|
+
const dp = (val.toString().split(".")[1] || "").length;
|
|
122
|
+
return dp <= 2;
|
|
123
|
+
},
|
|
124
|
+
{ message: "Amount can have a maximum of 2 decimal places." }
|
|
125
|
+
),
|
|
126
|
+
milestones: z
|
|
127
|
+
.array(
|
|
128
|
+
z.object({
|
|
129
|
+
description: z
|
|
130
|
+
.string()
|
|
131
|
+
.min(1, { message: "Milestone description is required." }),
|
|
132
|
+
})
|
|
133
|
+
)
|
|
134
|
+
.min(1, { message: "At least one milestone is required." }),
|
|
135
|
+
});
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
return { getSingleReleaseFormSchema };
|
|
139
|
+
};
|