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