@trustless-work/blocks 1.0.6 → 1.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.
Files changed (34) hide show
  1. package/bin/index.js +1930 -1921
  2. package/package.json +1 -1
  3. package/templates/deps.json +1 -1
  4. package/templates/escrows/details/Actions.tsx +1 -2
  5. package/templates/escrows/details/Entities.tsx +23 -2
  6. package/templates/escrows/details/GeneralInformation.tsx +1 -13
  7. package/templates/escrows/details/MilestoneCard.tsx +1 -1
  8. package/templates/escrows/details/MilestoneDetailDialog.tsx +38 -19
  9. package/templates/escrows/details/SuccessReleaseDialog.tsx +84 -28
  10. package/templates/escrows/details/useDetailsEscrow.ts +15 -2
  11. package/templates/escrows/multi-release/initialize-escrow/dialog/InitializeEscrow.tsx +76 -101
  12. package/templates/escrows/multi-release/initialize-escrow/form/InitializeEscrow.tsx +78 -102
  13. package/templates/escrows/multi-release/initialize-escrow/shared/schema.ts +8 -18
  14. package/templates/escrows/multi-release/initialize-escrow/shared/useInitializeEscrow.ts +21 -12
  15. package/templates/escrows/multi-release/release-milestone/button/ReleaseMilestone.tsx +5 -1
  16. package/templates/escrows/multi-release/update-escrow/dialog/UpdateEscrow.tsx +112 -101
  17. package/templates/escrows/multi-release/update-escrow/form/UpdateEscrow.tsx +103 -101
  18. package/templates/escrows/multi-release/update-escrow/shared/schema.ts +8 -16
  19. package/templates/escrows/multi-release/update-escrow/shared/useUpdateEscrow.ts +33 -14
  20. package/templates/escrows/multi-release/withdraw-remaining-funds/button/WithdrawRemainingFunds.tsx +0 -1
  21. package/templates/escrows/multi-release/withdraw-remaining-funds/shared/useWithdrawRemainingFunds.ts +0 -1
  22. package/templates/escrows/single-release/initialize-escrow/dialog/InitializeEscrow.tsx +2 -25
  23. package/templates/escrows/single-release/initialize-escrow/form/InitializeEscrow.tsx +3 -26
  24. package/templates/escrows/single-release/initialize-escrow/shared/schema.ts +0 -10
  25. package/templates/escrows/single-release/initialize-escrow/shared/useInitializeEscrow.ts +0 -4
  26. package/templates/escrows/single-release/release-escrow/button/ReleaseEscrow.tsx +5 -1
  27. package/templates/escrows/single-release/update-escrow/dialog/UpdateEscrow.tsx +41 -27
  28. package/templates/escrows/single-release/update-escrow/form/UpdateEscrow.tsx +38 -27
  29. package/templates/escrows/single-release/update-escrow/shared/schema.ts +0 -10
  30. package/templates/escrows/single-release/update-escrow/shared/useUpdateEscrow.ts +28 -14
  31. package/templates/providers/EscrowAmountProvider.tsx +8 -0
  32. package/templates/tanstack/useEscrowsMutations.ts +1 -6
  33. package/templates/wallet-kit/WalletButtons.tsx +2 -2
  34. package/templates/wallet-kit/WalletProvider.tsx +0 -1
@@ -19,7 +19,7 @@ import {
19
19
  } from "__UI_BASE__/select";
20
20
  import { Textarea } from "__UI_BASE__/textarea";
21
21
  import { useUpdateEscrow } from "./useUpdateEscrow";
22
- import { Trash2, DollarSign, Percent, Loader2 } from "lucide-react";
22
+ import { Trash2, DollarSign, Percent, Loader2, Lock } from "lucide-react";
23
23
  import Link from "next/link";
24
24
  import { trustlineOptions } from "@/components/tw-blocks/wallet-kit/trustlines";
25
25
 
@@ -34,6 +34,8 @@ export const UpdateEscrowForm = () => {
34
34
  handleRemoveMilestone,
35
35
  handleAmountChange,
36
36
  handlePlatformFeeChange,
37
+ isEscrowLocked,
38
+ initialMilestonesCount,
37
39
  } = useUpdateEscrow();
38
40
 
39
41
  return (
@@ -52,6 +54,22 @@ export const UpdateEscrowForm = () => {
52
54
  <p className="text-muted-foreground mt-1">
53
55
  Update escrow details and milestones
54
56
  </p>
57
+
58
+ {isEscrowLocked && (
59
+ <div className="flex flex-col gap-2 text-sm bg-yellow-50 dark:bg-yellow-900/20 p-2 rounded-md border border-yellow-200 dark:border-yellow-800 mt-3 px-4">
60
+ <div className="flex items-center gap-2">
61
+ <Lock className="w-4 h-4 text-yellow-600 dark:text-yellow-500 font-medium" />
62
+ <span className="text-yellow-600 dark:text-yellow-500 font-medium">
63
+ Escrow is locked
64
+ </span>
65
+ </div>
66
+
67
+ <p className="text-muted-foreground font-medium">
68
+ When the escrow has balance, it cannot be updated in all
69
+ fields, just adding new milestones is allowed.
70
+ </p>
71
+ </div>
72
+ )}
55
73
  </Link>
56
74
  </Card>
57
75
  <div className="grid grid-cols-1 lg:grid-cols-3 gap-4">
@@ -67,6 +85,7 @@ export const UpdateEscrowForm = () => {
67
85
  <Input
68
86
  placeholder="Escrow title"
69
87
  {...field}
88
+ disabled={isEscrowLocked}
70
89
  onChange={(e) => {
71
90
  field.onChange(e);
72
91
  }}
@@ -89,6 +108,7 @@ export const UpdateEscrowForm = () => {
89
108
  <Input
90
109
  placeholder="Enter identifier"
91
110
  {...field}
111
+ disabled={isEscrowLocked}
92
112
  onChange={(e) => {
93
113
  field.onChange(e);
94
114
  }}
@@ -110,6 +130,7 @@ export const UpdateEscrowForm = () => {
110
130
  <FormControl>
111
131
  <Select
112
132
  value={field.value}
133
+ disabled={isEscrowLocked}
113
134
  onValueChange={(e) => {
114
135
  field.onChange(e);
115
136
  }}
@@ -153,6 +174,7 @@ export const UpdateEscrowForm = () => {
153
174
  <Input
154
175
  placeholder="Enter approver address"
155
176
  {...field}
177
+ disabled={isEscrowLocked}
156
178
  onChange={(e) => {
157
179
  field.onChange(e);
158
180
  }}
@@ -179,6 +201,7 @@ export const UpdateEscrowForm = () => {
179
201
  <Input
180
202
  placeholder="Enter service provider address"
181
203
  {...field}
204
+ disabled={isEscrowLocked}
182
205
  onChange={(e) => {
183
206
  field.onChange(e);
184
207
  }}
@@ -207,6 +230,7 @@ export const UpdateEscrowForm = () => {
207
230
  <Input
208
231
  placeholder="Enter release signer address"
209
232
  {...field}
233
+ disabled={isEscrowLocked}
210
234
  onChange={(e) => {
211
235
  field.onChange(e);
212
236
  }}
@@ -233,6 +257,7 @@ export const UpdateEscrowForm = () => {
233
257
  <Input
234
258
  placeholder="Enter dispute resolver address"
235
259
  {...field}
260
+ disabled={isEscrowLocked}
236
261
  onChange={(e) => {
237
262
  field.onChange(e);
238
263
  }}
@@ -252,7 +277,7 @@ export const UpdateEscrowForm = () => {
252
277
  <FormItem>
253
278
  <FormLabel className="flex items-center justify-between">
254
279
  <span className="flex items-center">
255
- Platform Address
280
+ Platform
256
281
  <span className="text-destructive ml-1">*</span>
257
282
  </span>
258
283
  </FormLabel>
@@ -285,6 +310,7 @@ export const UpdateEscrowForm = () => {
285
310
  <Input
286
311
  placeholder="Enter receiver address"
287
312
  {...field}
313
+ disabled={isEscrowLocked}
288
314
  onChange={(e) => {
289
315
  field.onChange(e);
290
316
  }}
@@ -296,7 +322,7 @@ export const UpdateEscrowForm = () => {
296
322
  />
297
323
  </div>
298
324
 
299
- <div className="grid grid-cols-1 lg:grid-cols-3 gap-4">
325
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
300
326
  <FormField
301
327
  control={form.control}
302
328
  name="platformFee"
@@ -316,6 +342,7 @@ export const UpdateEscrowForm = () => {
316
342
  className="pl-10"
317
343
  value={form.watch("platformFee")?.toString() || ""}
318
344
  onChange={handlePlatformFeeChange}
345
+ disabled={isEscrowLocked}
319
346
  />
320
347
  </div>
321
348
  </FormControl>
@@ -343,6 +370,7 @@ export const UpdateEscrowForm = () => {
343
370
  className="pl-10"
344
371
  value={form.watch("amount")?.toString() || ""}
345
372
  onChange={handleAmountChange}
373
+ disabled={isEscrowLocked}
346
374
  />
347
375
  </div>
348
376
  </FormControl>
@@ -350,29 +378,6 @@ export const UpdateEscrowForm = () => {
350
378
  </FormItem>
351
379
  )}
352
380
  />
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
381
  </div>
377
382
 
378
383
  <FormField
@@ -387,6 +392,7 @@ export const UpdateEscrowForm = () => {
387
392
  <Textarea
388
393
  placeholder="Escrow description"
389
394
  {...field}
395
+ disabled={isEscrowLocked}
390
396
  onChange={(e) => {
391
397
  field.onChange(e);
392
398
  }}
@@ -407,6 +413,7 @@ export const UpdateEscrowForm = () => {
407
413
  <Input
408
414
  placeholder="Milestone Description"
409
415
  value={milestone.description}
416
+ disabled={isEscrowLocked && index < initialMilestonesCount}
410
417
  className="w-full sm:flex-1"
411
418
  onChange={(e) => {
412
419
  const updatedMilestones = [...milestones];
@@ -418,7 +425,11 @@ export const UpdateEscrowForm = () => {
418
425
  <Button
419
426
  onClick={() => handleRemoveMilestone(index)}
420
427
  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}
428
+ type="button"
429
+ disabled={
430
+ (isEscrowLocked && index < initialMilestonesCount) ||
431
+ milestones.length === 1
432
+ }
422
433
  >
423
434
  <Trash2 className="h-5 w-5" />
424
435
  </Button>
@@ -79,16 +79,6 @@ export const useUpdateEscrowSchema = () => {
79
79
  },
80
80
  { message: "Platform fee can have a maximum of 2 decimal places." }
81
81
  ),
82
- receiverMemo: z
83
- .string()
84
- .optional()
85
- .refine((val) => !val || val.length >= 1, {
86
- message: "Receiver Memo must be at least 1.",
87
- })
88
- .refine((val) => !val || /^[1-9][0-9]*$/.test(val), {
89
- message:
90
- "Receiver Memo must be a whole number greater than 0 (no decimals).",
91
- }),
92
82
  });
93
83
  };
94
84
 
@@ -7,6 +7,9 @@ import {
7
7
  UpdateSingleReleaseEscrowPayload,
8
8
  UpdateSingleReleaseEscrowResponse,
9
9
  SingleReleaseMilestone,
10
+ Roles,
11
+ GetEscrowsFromIndexerResponse,
12
+ MultiReleaseMilestone,
10
13
  } from "@trustless-work/escrow/types";
11
14
  import { toast } from "sonner";
12
15
  import { useEscrowContext } from "@/components/tw-blocks/providers/EscrowProvider";
@@ -16,7 +19,6 @@ import {
16
19
  ErrorResponse,
17
20
  handleError,
18
21
  } from "@/components/tw-blocks/handle-errors/handle";
19
- import { GetEscrowsFromIndexerResponse } from "@trustless-work/escrow/types";
20
22
 
21
23
  export function useUpdateEscrow() {
22
24
  const [isSubmitting, setIsSubmitting] = React.useState(false);
@@ -28,6 +30,11 @@ export function useUpdateEscrow() {
28
30
  const { selectedEscrow, setSelectedEscrow } = useEscrowContext();
29
31
  const { updateEscrow } = useEscrowsMutations();
30
32
 
33
+ const isEscrowLocked = Number(selectedEscrow?.balance || 0) > 0;
34
+ const initialMilestonesCountRef = React.useRef<number>(
35
+ ((selectedEscrow?.milestones as MultiReleaseMilestone[]) || []).length
36
+ );
37
+
31
38
  const form = useForm<z.infer<typeof formSchema>>({
32
39
  resolver: zodResolver(formSchema),
33
40
  defaultValues: {
@@ -39,9 +46,6 @@ export function useUpdateEscrow() {
39
46
  | string
40
47
  | undefined,
41
48
  amount: selectedEscrow?.amount as unknown as number | string | undefined,
42
- receiverMemo: selectedEscrow?.receiverMemo
43
- ? String(selectedEscrow.receiverMemo)
44
- : "",
45
49
  trustline: {
46
50
  address: selectedEscrow?.trustline?.address || "",
47
51
  },
@@ -49,7 +53,9 @@ export function useUpdateEscrow() {
49
53
  approver: selectedEscrow?.roles?.approver || "",
50
54
  serviceProvider: selectedEscrow?.roles?.serviceProvider || "",
51
55
  platformAddress: selectedEscrow?.roles?.platformAddress || "",
52
- receiver: selectedEscrow?.roles?.receiver || "",
56
+ receiver:
57
+ (selectedEscrow?.roles as Roles & { receiver?: string })?.receiver ||
58
+ "",
53
59
  releaseSigner: selectedEscrow?.roles?.releaseSigner || "",
54
60
  disputeResolver: selectedEscrow?.roles?.disputeResolver || "",
55
61
  },
@@ -76,9 +82,6 @@ export function useUpdateEscrow() {
76
82
  amount:
77
83
  (selectedEscrow?.amount as unknown as number | string | undefined) ||
78
84
  "",
79
- receiverMemo: selectedEscrow?.receiverMemo
80
- ? String(selectedEscrow.receiverMemo)
81
- : "",
82
85
  trustline: {
83
86
  address: selectedEscrow?.trustline?.address || "",
84
87
  },
@@ -86,7 +89,9 @@ export function useUpdateEscrow() {
86
89
  approver: selectedEscrow?.roles?.approver || "",
87
90
  serviceProvider: selectedEscrow?.roles?.serviceProvider || "",
88
91
  platformAddress: selectedEscrow?.roles?.platformAddress || "",
89
- receiver: selectedEscrow?.roles?.receiver || "",
92
+ receiver:
93
+ (selectedEscrow?.roles as Roles & { receiver?: string })?.receiver ||
94
+ "",
90
95
  releaseSigner: selectedEscrow?.roles?.releaseSigner || "",
91
96
  disputeResolver: selectedEscrow?.roles?.disputeResolver || "",
92
97
  },
@@ -99,7 +104,13 @@ export function useUpdateEscrow() {
99
104
  }, [selectedEscrow, form]);
100
105
 
101
106
  const milestones = form.watch("milestones");
102
- const isAnyMilestoneEmpty = milestones.some((m) => m.description === "");
107
+ const isAnyMilestoneEmpty = milestones.some((m, index) => {
108
+ const shouldValidate =
109
+ !isEscrowLocked || index >= initialMilestonesCountRef.current;
110
+ if (!shouldValidate) return false;
111
+
112
+ return m.description === "";
113
+ });
103
114
 
104
115
  const handleAddMilestone = () => {
105
116
  const current = form.getValues("milestones");
@@ -164,14 +175,15 @@ export function useUpdateEscrow() {
164
175
  typeof payload.amount === "string"
165
176
  ? Number(payload.amount)
166
177
  : payload.amount,
167
- receiverMemo: payload.receiverMemo
168
- ? Number(payload.receiverMemo)
169
- : undefined,
170
178
  trustline: {
171
179
  address: payload.trustline.address,
172
180
  },
173
181
  roles: payload.roles,
174
- milestones: payload.milestones,
182
+ milestones: payload.milestones.map((milestone, index) => ({
183
+ ...milestone,
184
+ evidence: selectedEscrow?.milestones?.[index]?.evidence || "",
185
+ status: selectedEscrow?.milestones?.[index]?.status || "",
186
+ })),
175
187
  },
176
188
  };
177
189
 
@@ -221,5 +233,7 @@ export function useUpdateEscrow() {
221
233
  handleRemoveMilestone,
222
234
  handleAmountChange,
223
235
  handlePlatformFeeChange,
236
+ isEscrowLocked,
237
+ initialMilestonesCount: initialMilestonesCountRef.current,
224
238
  };
225
239
  }
@@ -21,6 +21,8 @@ export type AmountEscrowStore = {
21
21
  setReceiverResolve: (value: number) => void;
22
22
  setApproverResolve: (value: number) => void;
23
23
  setAmountMoonpay: (value: number) => void;
24
+ lastReleasedMilestoneIndex: number | null;
25
+ setLastReleasedMilestoneIndex: (index: number | null) => void;
24
26
  };
25
27
 
26
28
  const EscrowAmountContext = createContext<AmountEscrowStore | undefined>(
@@ -34,6 +36,9 @@ export const EscrowAmountProvider = ({ children }: { children: ReactNode }) => {
34
36
  const [receiverResolve, setReceiverResolve] = useState(0);
35
37
  const [approverResolve, setApproverResolve] = useState(0);
36
38
  const [amountMoonpay, setAmountMoonpay] = useState(0);
39
+ const [lastReleasedMilestoneIndex, setLastReleasedMilestoneIndex] = useState<
40
+ number | null
41
+ >(null);
37
42
 
38
43
  const setAmounts: AmountEscrowStore["setAmounts"] = useCallback(
39
44
  (totalAmount, platformFee) => {
@@ -62,6 +67,8 @@ export const EscrowAmountProvider = ({ children }: { children: ReactNode }) => {
62
67
  setReceiverResolve,
63
68
  setApproverResolve,
64
69
  setAmountMoonpay,
70
+ lastReleasedMilestoneIndex,
71
+ setLastReleasedMilestoneIndex,
65
72
  }),
66
73
  [
67
74
  receiverAmount,
@@ -70,6 +77,7 @@ export const EscrowAmountProvider = ({ children }: { children: ReactNode }) => {
70
77
  receiverResolve,
71
78
  approverResolve,
72
79
  amountMoonpay,
80
+ lastReleasedMilestoneIndex,
73
81
  ]
74
82
  );
75
83
 
@@ -443,17 +443,12 @@ export const useEscrowsMutations = () => {
443
443
  const withdrawRemainingFundsMutation = useMutation({
444
444
  mutationFn: async ({
445
445
  payload,
446
- type,
447
446
  address,
448
447
  }: {
449
448
  payload: WithdrawRemainingFundsPayload;
450
- type: EscrowType;
451
449
  address: string;
452
450
  }) => {
453
- const { unsignedTransaction } = await withdrawRemainingFunds(
454
- payload,
455
- type
456
- );
451
+ const { unsignedTransaction } = await withdrawRemainingFunds(payload);
457
452
 
458
453
  if (!unsignedTransaction) {
459
454
  throw new Error(
@@ -29,7 +29,7 @@ export const WalletButton = () => {
29
29
  setCopied(true);
30
30
  setTimeout(() => setCopied(false), 1500);
31
31
  } catch (_) {
32
- // noop
32
+ console.error("Error copying address to clipboard", _);
33
33
  }
34
34
  };
35
35
 
@@ -39,7 +39,7 @@ export const WalletButton = () => {
39
39
  <PopoverTrigger asChild>
40
40
  <Button
41
41
  variant="outline"
42
- className="h-10 px-4 gap-2 font-medium bg-transparent"
42
+ className="h-10 px-4 gap-2 font-medium bg-transparent cursor-pointer"
43
43
  >
44
44
  <Wallet className="h-4 w-4" />
45
45
  <span className="hidden sm:inline">{walletName}</span>
@@ -1,6 +1,5 @@
1
1
  "use client";
2
2
 
3
- import * as React from "react";
4
3
  import {
5
4
  createContext,
6
5
  useContext,