@trustless-work/blocks 1.0.6 → 1.0.7

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 (33) 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 -3
  29. package/templates/escrows/single-release/update-escrow/shared/useUpdateEscrow.ts +28 -14
  30. package/templates/providers/EscrowAmountProvider.tsx +8 -0
  31. package/templates/tanstack/useEscrowsMutations.ts +1 -6
  32. package/templates/wallet-kit/WalletButtons.tsx +2 -2
  33. 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
  }}
@@ -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>
@@ -364,6 +392,7 @@ export const UpdateEscrowForm = () => {
364
392
  type="text"
365
393
  placeholder="Enter the escrow receiver Memo"
366
394
  {...field}
395
+ disabled={isEscrowLocked}
367
396
  onChange={(e) => {
368
397
  field.onChange(e);
369
398
  }}
@@ -387,6 +416,7 @@ export const UpdateEscrowForm = () => {
387
416
  <Textarea
388
417
  placeholder="Escrow description"
389
418
  {...field}
419
+ disabled={isEscrowLocked}
390
420
  onChange={(e) => {
391
421
  field.onChange(e);
392
422
  }}
@@ -407,6 +437,7 @@ export const UpdateEscrowForm = () => {
407
437
  <Input
408
438
  placeholder="Milestone Description"
409
439
  value={milestone.description}
440
+ disabled={isEscrowLocked && index < initialMilestonesCount}
410
441
  className="w-full sm:flex-1"
411
442
  onChange={(e) => {
412
443
  const updatedMilestones = [...milestones];
@@ -418,7 +449,11 @@ export const UpdateEscrowForm = () => {
418
449
  <Button
419
450
  onClick={() => handleRemoveMilestone(index)}
420
451
  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}
452
+ type="button"
453
+ disabled={
454
+ (isEscrowLocked && index < initialMilestonesCount) ||
455
+ milestones.length === 1
456
+ }
422
457
  >
423
458
  <Trash2 className="h-5 w-5" />
424
459
  </Button>
@@ -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,