@trustless-work/blocks 1.0.8 → 1.1.0

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 (29) hide show
  1. package/bin/index.js +38 -1
  2. package/package.json +1 -1
  3. package/templates/deps.json +1 -1
  4. package/templates/escrows/details/EscrowDetailDialog.tsx +10 -12
  5. package/templates/escrows/escrows-by-role/cards/EscrowsCards.tsx +3 -1
  6. package/templates/escrows/escrows-by-signer/cards/EscrowsCards.tsx +3 -1
  7. package/templates/escrows/multi-release/initialize-escrow/dialog/InitializeEscrow.tsx +29 -11
  8. package/templates/escrows/multi-release/initialize-escrow/form/InitializeEscrow.tsx +26 -9
  9. package/templates/escrows/multi-release/initialize-escrow/shared/useInitializeEscrow.ts +3 -1
  10. package/templates/escrows/multi-release/resolve-dispute/dialog/ResolveDispute.tsx +3 -2
  11. package/templates/escrows/multi-release/resolve-dispute/shared/useResolveDispute.ts +3 -1
  12. package/templates/escrows/multi-release/update-escrow/dialog/UpdateEscrow.tsx +3 -2
  13. package/templates/escrows/multi-release/update-escrow/shared/useUpdateEscrow.ts +2 -1
  14. package/templates/escrows/multi-release/withdraw-remaining-funds/dialog/WithdrawRemainingFunds.tsx +3 -2
  15. package/templates/escrows/multi-release/withdraw-remaining-funds/shared/useWithdrawRemainingFunds.ts +5 -1
  16. package/templates/escrows/single-multi-release/approve-milestone/dialog/ApproveMilestone.tsx +5 -2
  17. package/templates/escrows/single-multi-release/approve-milestone/shared/useApproveMilestone.ts +5 -1
  18. package/templates/escrows/single-multi-release/change-milestone-status/dialog/ChangeMilestoneStatus.tsx +5 -2
  19. package/templates/escrows/single-multi-release/change-milestone-status/shared/useChangeMilestoneStatus.ts +5 -1
  20. package/templates/escrows/single-multi-release/fund-escrow/dialog/FundEscrow.tsx +5 -2
  21. package/templates/escrows/single-multi-release/fund-escrow/shared/useFundEscrow.ts +3 -1
  22. package/templates/escrows/single-release/initialize-escrow/dialog/InitializeEscrow.tsx +3 -2
  23. package/templates/escrows/single-release/initialize-escrow/shared/useInitializeEscrow.ts +3 -1
  24. package/templates/escrows/single-release/resolve-dispute/dialog/ResolveDispute.tsx +3 -2
  25. package/templates/escrows/single-release/resolve-dispute/shared/useResolveDispute.ts +3 -1
  26. package/templates/escrows/single-release/update-escrow/dialog/UpdateEscrow.tsx +3 -2
  27. package/templates/escrows/single-release/update-escrow/shared/useUpdateEscrow.ts +2 -1
  28. package/templates/wallet-kit/useWallet.ts +23 -16
  29. package/templates/wallet-kit/wallet-kit.ts +79 -24
package/bin/index.js CHANGED
@@ -167,6 +167,7 @@ function installDeps({ dependencies = {}, devDependencies = {} }) {
167
167
  "postcss",
168
168
  "autoprefixer",
169
169
  "postcss-import",
170
+ "@creit-tech/stellar-wallets-kit",
170
171
  ]);
171
172
  const depList = Object.entries(dependencies)
172
173
  .filter(([k]) => !BLOCKED.has(k))
@@ -421,6 +422,10 @@ function copyTemplate(name, { uiBase, shouldInstall = false } = {}) {
421
422
  if (shouldInstall && fs.existsSync(GLOBAL_DEPS_FILE)) {
422
423
  const meta = JSON.parse(fs.readFileSync(GLOBAL_DEPS_FILE, "utf8"));
423
424
  installDeps(meta);
425
+ // Install @creit-tech/stellar-wallets-kit using jsr after all other deps are installed
426
+ if (meta.dependencies && meta.dependencies["@creit-tech/stellar-wallets-kit"]) {
427
+ run("npx", ["jsr", "add", "@creit-tech/stellar-wallets-kit"]);
428
+ }
424
429
  }
425
430
  currentEscrowType = null;
426
431
  return;
@@ -1302,6 +1307,10 @@ function copyTemplate(name, { uiBase, shouldInstall = false } = {}) {
1302
1307
  if (shouldInstall && fs.existsSync(GLOBAL_DEPS_FILE)) {
1303
1308
  const meta = JSON.parse(fs.readFileSync(GLOBAL_DEPS_FILE, "utf8"));
1304
1309
  installDeps(meta);
1310
+ // Install @creit-tech/stellar-wallets-kit using jsr after all other deps are installed
1311
+ if (meta.dependencies && meta.dependencies["@creit-tech/stellar-wallets-kit"]) {
1312
+ run("npx", ["jsr", "add", "@creit-tech/stellar-wallets-kit"]);
1313
+ }
1305
1314
  }
1306
1315
  }
1307
1316
 
@@ -1579,7 +1588,29 @@ function injectProvidersIntoLayout(
1579
1588
  }
1580
1589
  }
1581
1590
 
1591
+ function cleanJsrDeps() {
1592
+ const pkgPath = path.join(PROJECT_ROOT, "package.json");
1593
+ if (!fs.existsSync(pkgPath)) return;
1594
+ try {
1595
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
1596
+ let modified = false;
1597
+ const target = "@creit-tech/stellar-wallets-kit";
1598
+ if (pkg.dependencies && pkg.dependencies[target]) {
1599
+ delete pkg.dependencies[target];
1600
+ modified = true;
1601
+ }
1602
+ if (pkg.devDependencies && pkg.devDependencies[target]) {
1603
+ delete pkg.devDependencies[target];
1604
+ modified = true;
1605
+ }
1606
+ if (modified) {
1607
+ fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2), "utf8");
1608
+ }
1609
+ } catch (e) {}
1610
+ }
1611
+
1582
1612
  if (args[0] === "init") {
1613
+ cleanJsrDeps();
1583
1614
  console.log("\n▶ Setting up shadcn/ui components...");
1584
1615
  const doInit = await promptYesNo("Run shadcn init now?", true);
1585
1616
  if (doInit) {
@@ -1632,13 +1663,19 @@ if (args[0] === "init") {
1632
1663
  }
1633
1664
  const meta = JSON.parse(fs.readFileSync(GLOBAL_DEPS_FILE, "utf8"));
1634
1665
  const installLibs = await promptYesNo(
1635
- "Install (react-hook-form, @tanstack/react-query, @tanstack/react-query-devtools, @trustless-work/escrow, @hookform/resolvers, axios, @creit.tech/stellar-wallets-kit, react-day-picker, recharts & zod) dependencies now?",
1666
+ "Install (react-hook-form, @tanstack/react-query, @tanstack/react-query-devtools, @trustless-work/escrow, @hookform/resolvers, axios, @creit-tech/stellar-wallets-kit, react-day-picker, recharts & zod) dependencies now?",
1636
1667
  true
1637
1668
  );
1638
1669
  if (installLibs) {
1639
1670
  await withSpinner("Installing required dependencies", async () => {
1640
1671
  installDeps(meta);
1641
1672
  });
1673
+ // Install @creit-tech/stellar-wallets-kit using jsr after all other deps are installed
1674
+ if (meta.dependencies && meta.dependencies["@creit-tech/stellar-wallets-kit"]) {
1675
+ await withSpinner("Installing @creit-tech/stellar-wallets-kit from JSR", async () => {
1676
+ run("npx", ["jsr", "add", "@creit-tech/stellar-wallets-kit"]);
1677
+ });
1678
+ }
1642
1679
  } else {
1643
1680
  console.log("\x1b[90m– Skipped installing required dependencies\x1b[0m");
1644
1681
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trustless-work/blocks",
3
- "version": "1.0.8",
3
+ "version": "1.1.0",
4
4
  "author": "Trustless Work",
5
5
  "keywords": [
6
6
  "react",
@@ -11,7 +11,7 @@
11
11
  "tsup": "^8.3.5",
12
12
  "typescript": "^5.6.3",
13
13
  "@hookform/resolvers": "^3.10.0",
14
- "@creit.tech/stellar-wallets-kit": "^1.8.0",
14
+ "@creit-tech/stellar-wallets-kit": "^1.8.0",
15
15
  "axios": "^1.7.9",
16
16
  "@tanstack/react-table": "^8.21.3",
17
17
  "react-day-picker": "^9.5.0",
@@ -55,7 +55,7 @@ export const EscrowDetailDialog = ({
55
55
  selectedEscrow,
56
56
  });
57
57
 
58
- const stellarExplorerUrl = `https://stellar.expert/explorer/testnet/contract/${selectedEscrow?.contractId}`;
58
+ const viewerUrl = `https://viewer.trustlesswork.com/${selectedEscrow?.contractId}`;
59
59
 
60
60
  if (!isDialogOpen || !selectedEscrow) return null;
61
61
  return (
@@ -65,17 +65,15 @@ export const EscrowDetailDialog = ({
65
65
  <DialogHeader className="flex-shrink-0">
66
66
  <div className="w-full">
67
67
  <div className="flex flex-col gap-2">
68
- <div className="w-full">
69
- <Link
70
- href={stellarExplorerUrl}
71
- target="_blank"
72
- className="hover:underline"
73
- >
74
- <DialogTitle className="text-xl">
75
- {selectedEscrow.title}
76
- </DialogTitle>
77
- </Link>
78
- </div>
68
+ <Link
69
+ href={viewerUrl}
70
+ target="_blank"
71
+ className="hover:underline w-fit"
72
+ >
73
+ <DialogTitle className="text-xl">
74
+ {selectedEscrow.title}
75
+ </DialogTitle>
76
+ </Link>
79
77
 
80
78
  <DialogDescription>
81
79
  {selectedEscrow.description}
@@ -351,7 +351,9 @@ export const EscrowsByRoleCards = () => {
351
351
  key={`milestone-${milestone.description}-${milestone.status}-${index}`}
352
352
  className="text-xs flex justify-between"
353
353
  >
354
- {milestone.description}
354
+ <p className="truncate mr-4">
355
+ {milestone.description}
356
+ </p>
355
357
 
356
358
  {escrow.type === "multi-release" &&
357
359
  "amount" in milestone && (
@@ -339,7 +339,9 @@ export const EscrowsBySignerCards = () => {
339
339
  key={`milestone-${milestone.description}-${milestone.status}-${index}`}
340
340
  className="text-xs flex justify-between"
341
341
  >
342
- {milestone.description}
342
+ <p className="truncate mr-4">
343
+ {milestone.description}
344
+ </p>
343
345
 
344
346
  {escrow.type === "multi-release" &&
345
347
  "amount" in milestone && (
@@ -32,6 +32,7 @@ import {
32
32
  import { Separator } from "__UI_BASE__/separator";
33
33
 
34
34
  export const InitializeEscrowDialog = () => {
35
+ const [isOpen, setIsOpen] = React.useState(false);
35
36
  const {
36
37
  form,
37
38
  isSubmitting,
@@ -41,7 +42,7 @@ export const InitializeEscrowDialog = () => {
41
42
  handleAddMilestone,
42
43
  handleRemoveMilestone,
43
44
  fillTemplateForm,
44
- } = useInitializeEscrow();
45
+ } = useInitializeEscrow({ onSuccess: () => setIsOpen(false) });
45
46
 
46
47
  const handleMilestoneAmountChange = (
47
48
  index: number,
@@ -92,7 +93,7 @@ export const InitializeEscrowDialog = () => {
92
93
  };
93
94
 
94
95
  return (
95
- <Dialog>
96
+ <Dialog open={isOpen} onOpenChange={setIsOpen}>
96
97
  <DialogTrigger asChild>
97
98
  <Button type="button" className="cursor-pointer w-full">
98
99
  Initialize
@@ -402,19 +403,36 @@ export const InitializeEscrowDialog = () => {
402
403
  />
403
404
 
404
405
  <div className="space-y-4">
405
- <FormLabel className="flex items-center">
406
- Milestones<span className="text-destructive ml-1">*</span>
407
- </FormLabel>
406
+ <FormLabel className="flex items-center">Milestones</FormLabel>
407
+
408
+ <div className="grid grid-cols-1 md:grid-cols-12 gap-4 items-center">
409
+ <div className="md:col-span-4">
410
+ <FormLabel className="flex items-center">
411
+ Description<span className="text-destructive ml-1">*</span>
412
+ </FormLabel>
413
+ </div>
414
+ <div className="md:col-span-4">
415
+ <FormLabel className="flex items-center">
416
+ Receiver<span className="text-destructive ml-1">*</span>
417
+ </FormLabel>
418
+ </div>
419
+ <div className="md:col-span-3">
420
+ <FormLabel className="flex items-center">
421
+ Amount<span className="text-destructive ml-1">*</span>
422
+ </FormLabel>
423
+ </div>
424
+ </div>
425
+
408
426
  {milestones.map((milestone, index) => (
409
427
  <div key={index} className="space-y-4">
410
428
  <div className="grid grid-cols-1 md:grid-cols-12 gap-4 items-center">
411
429
  <div className="md:col-span-4">
412
430
  <Input
413
- placeholder="Enter receiver address"
414
- value={milestone.receiver}
431
+ placeholder="Milestone description"
432
+ value={milestone.description}
415
433
  onChange={(e) => {
416
434
  const updatedMilestones = [...milestones];
417
- updatedMilestones[index].receiver = e.target.value;
435
+ updatedMilestones[index].description = e.target.value;
418
436
  form.setValue("milestones", updatedMilestones);
419
437
  }}
420
438
  />
@@ -422,11 +440,11 @@ export const InitializeEscrowDialog = () => {
422
440
 
423
441
  <div className="md:col-span-4">
424
442
  <Input
425
- placeholder="Milestone description"
426
- value={milestone.description}
443
+ placeholder="Enter receiver address"
444
+ value={milestone.receiver}
427
445
  onChange={(e) => {
428
446
  const updatedMilestones = [...milestones];
429
- updatedMilestones[index].description = e.target.value;
447
+ updatedMilestones[index].receiver = e.target.value;
430
448
  form.setValue("milestones", updatedMilestones);
431
449
  }}
432
450
  />
@@ -383,19 +383,36 @@ export const InitializeEscrowForm = () => {
383
383
  />
384
384
 
385
385
  <div className="space-y-4">
386
- <FormLabel className="flex items-center">
387
- Milestones<span className="text-destructive ml-1">*</span>
388
- </FormLabel>
386
+ <FormLabel className="flex items-center">Milestones</FormLabel>
387
+
388
+ <div className="grid grid-cols-1 md:grid-cols-12 gap-4 items-center">
389
+ <div className="md:col-span-4">
390
+ <FormLabel className="flex items-center">
391
+ Description<span className="text-destructive ml-1">*</span>
392
+ </FormLabel>
393
+ </div>
394
+ <div className="md:col-span-4">
395
+ <FormLabel className="flex items-center">
396
+ Receiver<span className="text-destructive ml-1">*</span>
397
+ </FormLabel>
398
+ </div>
399
+ <div className="md:col-span-3">
400
+ <FormLabel className="flex items-center">
401
+ Amount<span className="text-destructive ml-1">*</span>
402
+ </FormLabel>
403
+ </div>
404
+ </div>
405
+
389
406
  {milestones.map((milestone, index) => (
390
407
  <div key={index} className="space-y-4 max-w-3xl">
391
408
  <div className="grid grid-cols-1 md:grid-cols-12 gap-4 items-center">
392
409
  <div className="md:col-span-4">
393
410
  <Input
394
- placeholder="Enter receiver address"
395
- value={milestone.receiver}
411
+ placeholder="Milestone description"
412
+ value={milestone.description}
396
413
  onChange={(e) => {
397
414
  const updatedMilestones = [...milestones];
398
- updatedMilestones[index].receiver = e.target.value;
415
+ updatedMilestones[index].description = e.target.value;
399
416
  form.setValue("milestones", updatedMilestones);
400
417
  }}
401
418
  />
@@ -403,11 +420,11 @@ export const InitializeEscrowForm = () => {
403
420
 
404
421
  <div className="md:col-span-4">
405
422
  <Input
406
- placeholder="Milestone description"
407
- value={milestone.description}
423
+ placeholder="Enter receiver address"
424
+ value={milestone.receiver}
408
425
  onChange={(e) => {
409
426
  const updatedMilestones = [...milestones];
410
- updatedMilestones[index].description = e.target.value;
427
+ updatedMilestones[index].receiver = e.target.value;
411
428
  form.setValue("milestones", updatedMilestones);
412
429
  }}
413
430
  />
@@ -17,7 +17,7 @@ import {
17
17
  import { useEscrowContext } from "@/components/tw-blocks/providers/EscrowProvider";
18
18
  import { trustlineOptions } from "@/components/tw-blocks/wallet-kit/trustlines";
19
19
 
20
- export function useInitializeEscrow() {
20
+ export function useInitializeEscrow({ onSuccess }: { onSuccess?: () => void } = {}) {
21
21
  const [isSubmitting, setIsSubmitting] = React.useState(false);
22
22
 
23
23
  const { getMultiReleaseFormSchema } = useInitializeEscrowSchema();
@@ -161,6 +161,8 @@ export function useInitializeEscrow() {
161
161
  toast.success("Escrow initialized successfully");
162
162
 
163
163
  setSelectedEscrow({ ...finalPayload, contractId: response.contractId });
164
+
165
+ onSuccess?.();
164
166
  } catch (error) {
165
167
  toast.error(handleError(error as ErrorResponse).message);
166
168
  } finally {
@@ -35,6 +35,7 @@ export const ResolveDisputeDialog = ({
35
35
  showSelectMilestone?: boolean;
36
36
  milestoneIndex?: number | string;
37
37
  }) => {
38
+ const [isOpen, setIsOpen] = React.useState(false);
38
39
  const {
39
40
  form,
40
41
  handleSubmit,
@@ -50,7 +51,7 @@ export const ResolveDisputeDialog = ({
50
51
  distributedSum,
51
52
  isExactMatch,
52
53
  difference,
53
- } = useResolveDispute();
54
+ } = useResolveDispute({ onSuccess: () => setIsOpen(false) });
54
55
  const { selectedEscrow } = useEscrowContext();
55
56
 
56
57
  React.useEffect(() => {
@@ -64,7 +65,7 @@ export const ResolveDisputeDialog = ({
64
65
  }, [showSelectMilestone, milestoneIndex, form]);
65
66
 
66
67
  return (
67
- <Dialog>
68
+ <Dialog open={isOpen} onOpenChange={setIsOpen}>
68
69
  <DialogTrigger asChild>
69
70
  <Button type="button" className="cursor-pointer w-full">
70
71
  Resolve Dispute
@@ -17,7 +17,7 @@ import { useWalletContext } from "@/components/tw-blocks/wallet-kit/WalletProvid
17
17
 
18
18
  type DistributionInput = { address: string; amount: string | number };
19
19
 
20
- export function useResolveDispute() {
20
+ export function useResolveDispute({ onSuccess }: { onSuccess?: () => void } = {}) {
21
21
  const { resolveDispute } = useEscrowsMutations();
22
22
  const { selectedEscrow, updateEscrow } = useEscrowContext();
23
23
  const { walletAddress } = useWalletContext();
@@ -142,6 +142,8 @@ export function useResolveDispute() {
142
142
 
143
143
  toast.success("Dispute resolved successfully");
144
144
 
145
+ onSuccess?.();
146
+
145
147
  const sumDistributed = payload.distributions.reduce((acc, d) => {
146
148
  const n = Number(d.amount || 0);
147
149
  return acc + (isNaN(n) ? 0 : n);
@@ -31,6 +31,7 @@ import {
31
31
  } from "__UI_BASE__/dialog";
32
32
 
33
33
  export const UpdateEscrowDialog = () => {
34
+ const [isOpen, setIsOpen] = React.useState(false);
34
35
  const {
35
36
  form,
36
37
  isSubmitting,
@@ -43,10 +44,10 @@ export const UpdateEscrowDialog = () => {
43
44
  handleMilestoneAmountChange,
44
45
  isEscrowLocked,
45
46
  initialMilestonesCount,
46
- } = useUpdateEscrow();
47
+ } = useUpdateEscrow({ onSuccess: () => setIsOpen(false) });
47
48
 
48
49
  return (
49
- <Dialog>
50
+ <Dialog open={isOpen} onOpenChange={setIsOpen}>
50
51
  <DialogTrigger asChild>
51
52
  <Button type="button" className="cursor-pointer w-full">
52
53
  Update
@@ -18,7 +18,7 @@ import {
18
18
  } from "@/components/tw-blocks/handle-errors/handle";
19
19
  import { GetEscrowsFromIndexerResponse } from "@trustless-work/escrow/types";
20
20
 
21
- export function useUpdateEscrow() {
21
+ export function useUpdateEscrow({ onSuccess }: { onSuccess?: () => void } = {}) {
22
22
  const [isSubmitting, setIsSubmitting] = React.useState(false);
23
23
 
24
24
  const { getMultiReleaseFormSchema } = useUpdateEscrowSchema();
@@ -246,6 +246,7 @@ export function useUpdateEscrow() {
246
246
 
247
247
  setSelectedEscrow(nextSelectedEscrow);
248
248
  toast.success("Escrow updated successfully");
249
+ onSuccess?.();
249
250
  } catch (error) {
250
251
  toast.error(handleError(error as ErrorResponse).message);
251
252
  } finally {
@@ -22,6 +22,7 @@ import { useEscrowContext } from "@/components/tw-blocks/providers/EscrowProvide
22
22
  import { formatCurrency } from "../../../../helpers/format.helper";
23
23
 
24
24
  export const WithdrawRemainingFundsDialog = () => {
25
+ const [isOpen, setIsOpen] = React.useState(false);
25
26
  const {
26
27
  form,
27
28
  handleSubmit,
@@ -36,11 +37,11 @@ export const WithdrawRemainingFundsDialog = () => {
36
37
  distributedSum,
37
38
  isExactMatch,
38
39
  difference,
39
- } = useWithdrawRemainingFunds();
40
+ } = useWithdrawRemainingFunds({ onSuccess: () => setIsOpen(false) });
40
41
  const { selectedEscrow } = useEscrowContext();
41
42
 
42
43
  return (
43
- <Dialog>
44
+ <Dialog open={isOpen} onOpenChange={setIsOpen}>
44
45
  <DialogTrigger asChild>
45
46
  <Button type="button" className="cursor-pointer w-full">
46
47
  Withdraw Remaining
@@ -17,7 +17,9 @@ import { useWalletContext } from "@/components/tw-blocks/wallet-kit/WalletProvid
17
17
 
18
18
  type DistributionInput = { address: string; amount: string | number };
19
19
 
20
- export function useWithdrawRemainingFunds() {
20
+ export function useWithdrawRemainingFunds({
21
+ onSuccess,
22
+ }: { onSuccess?: () => void } = {}) {
21
23
  const { withdrawRemainingFunds } = useEscrowsMutations();
22
24
  const { selectedEscrow, updateEscrow } = useEscrowContext();
23
25
  const { walletAddress } = useWalletContext();
@@ -124,6 +126,8 @@ export function useWithdrawRemainingFunds() {
124
126
 
125
127
  toast.success("Withdraw successful");
126
128
 
129
+ onSuccess?.();
130
+
127
131
  const sumDistributed = payload.distributions.reduce((acc, d) => {
128
132
  const n = Number(d.amount || 0);
129
133
  return acc + (isNaN(n) ? 0 : n);
@@ -27,11 +27,14 @@ import {
27
27
  } from "__UI_BASE__/select";
28
28
 
29
29
  export const ApproveMilestoneDialog = () => {
30
- const { form, handleSubmit, isSubmitting } = useApproveMilestone();
30
+ const [isOpen, setIsOpen] = React.useState(false);
31
+ const { form, handleSubmit, isSubmitting } = useApproveMilestone({
32
+ onSuccess: () => setIsOpen(false),
33
+ });
31
34
  const { selectedEscrow } = useEscrowContext();
32
35
 
33
36
  return (
34
- <Dialog>
37
+ <Dialog open={isOpen} onOpenChange={setIsOpen}>
35
38
  <DialogTrigger asChild>
36
39
  <Button type="button" className="cursor-pointer w-full">
37
40
  Approve Milestone
@@ -15,7 +15,9 @@ import {
15
15
  } from "@/components/tw-blocks/handle-errors/handle";
16
16
  import { useWalletContext } from "@/components/tw-blocks/wallet-kit/WalletProvider";
17
17
 
18
- export function useApproveMilestone() {
18
+ export function useApproveMilestone({
19
+ onSuccess,
20
+ }: { onSuccess?: () => void } = {}) {
19
21
  const { approveMilestone } = useEscrowsMutations();
20
22
  const { selectedEscrow, updateEscrow } = useEscrowContext();
21
23
  const { walletAddress } = useWalletContext();
@@ -48,6 +50,8 @@ export function useApproveMilestone() {
48
50
 
49
51
  toast.success("Milestone approved flag updated successfully");
50
52
 
53
+ onSuccess?.();
54
+
51
55
  updateEscrow({
52
56
  ...selectedEscrow,
53
57
  milestones: selectedEscrow?.milestones.map((milestone, index) => {
@@ -35,7 +35,10 @@ export const ChangeMilestoneStatusDialog = ({
35
35
  showSelectMilestone?: boolean;
36
36
  milestoneIndex?: number | string;
37
37
  }) => {
38
- const { form, handleSubmit, isSubmitting } = useChangeMilestoneStatus();
38
+ const [isOpen, setIsOpen] = React.useState(false);
39
+ const { form, handleSubmit, isSubmitting } = useChangeMilestoneStatus({
40
+ onSuccess: () => setIsOpen(false),
41
+ });
39
42
  const { selectedEscrow } = useEscrowContext();
40
43
 
41
44
  React.useEffect(() => {
@@ -49,7 +52,7 @@ export const ChangeMilestoneStatusDialog = ({
49
52
  }, [showSelectMilestone, milestoneIndex, form]);
50
53
 
51
54
  return (
52
- <Dialog>
55
+ <Dialog open={isOpen} onOpenChange={setIsOpen}>
53
56
  <DialogTrigger asChild>
54
57
  <Button type="button" className="cursor-pointer w-full">
55
58
  Update Status
@@ -15,7 +15,9 @@ import {
15
15
  } from "@/components/tw-blocks/handle-errors/handle";
16
16
  import { useWalletContext } from "@/components/tw-blocks/wallet-kit/WalletProvider";
17
17
 
18
- export function useChangeMilestoneStatus() {
18
+ export function useChangeMilestoneStatus({
19
+ onSuccess,
20
+ }: { onSuccess?: () => void } = {}) {
19
21
  const { changeMilestoneStatus } = useEscrowsMutations();
20
22
  const { selectedEscrow, updateEscrow } = useEscrowContext();
21
23
  const { walletAddress } = useWalletContext();
@@ -65,6 +67,8 @@ export function useChangeMilestoneStatus() {
65
67
 
66
68
  toast.success("Milestone status updated successfully");
67
69
 
70
+ onSuccess?.();
71
+
68
72
  updateEscrow({
69
73
  ...selectedEscrow,
70
74
  milestones: selectedEscrow?.milestones.map((milestone, index) => {
@@ -20,10 +20,13 @@ import { Loader2 } from "lucide-react";
20
20
  import { useFundEscrow } from "./useFundEscrow";
21
21
 
22
22
  export const FundEscrowDialog = () => {
23
- const { form, handleSubmit, isSubmitting } = useFundEscrow();
23
+ const [isOpen, setIsOpen] = React.useState(false);
24
+ const { form, handleSubmit, isSubmitting } = useFundEscrow({
25
+ onSuccess: () => setIsOpen(false),
26
+ });
24
27
 
25
28
  return (
26
- <Dialog>
29
+ <Dialog open={isOpen} onOpenChange={setIsOpen}>
27
30
  <DialogTrigger asChild>
28
31
  <Button type="button" className="cursor-pointer w-full">
29
32
  Fund
@@ -12,7 +12,7 @@ import {
12
12
  } from "@/components/tw-blocks/handle-errors/handle";
13
13
  import { useWalletContext } from "@/components/tw-blocks/wallet-kit/WalletProvider";
14
14
 
15
- export function useFundEscrow() {
15
+ export function useFundEscrow({ onSuccess }: { onSuccess?: () => void } = {}) {
16
16
  const { fundEscrow } = useEscrowsMutations();
17
17
  const { selectedEscrow, updateEscrow } = useEscrowContext();
18
18
  const { walletAddress } = useWalletContext();
@@ -66,6 +66,8 @@ export function useFundEscrow() {
66
66
 
67
67
  toast.success("Escrow funded successfully");
68
68
 
69
+ onSuccess?.();
70
+
69
71
  // do something with the response ...
70
72
  } catch (error) {
71
73
  toast.error(handleError(error as ErrorResponse).message);
@@ -31,6 +31,7 @@ import {
31
31
  } from "__UI_BASE__/dialog";
32
32
 
33
33
  export const InitializeEscrowDialog = () => {
34
+ const [isOpen, setIsOpen] = React.useState(false);
34
35
  const {
35
36
  form,
36
37
  isSubmitting,
@@ -40,7 +41,7 @@ export const InitializeEscrowDialog = () => {
40
41
  handleAddMilestone,
41
42
  handleRemoveMilestone,
42
43
  fillTemplateForm,
43
- } = useInitializeEscrow();
44
+ } = useInitializeEscrow({ onSuccess: () => setIsOpen(false) });
44
45
 
45
46
  const handleAmountChange = (e: React.ChangeEvent<HTMLInputElement>) => {
46
47
  let rawValue = e.target.value;
@@ -83,7 +84,7 @@ export const InitializeEscrowDialog = () => {
83
84
  };
84
85
 
85
86
  return (
86
- <Dialog>
87
+ <Dialog open={isOpen} onOpenChange={setIsOpen}>
87
88
  <DialogTrigger asChild>
88
89
  <Button type="button" className="cursor-pointer w-full">
89
90
  Initialize
@@ -17,7 +17,7 @@ import {
17
17
  import { useEscrowContext } from "@/components/tw-blocks/providers/EscrowProvider";
18
18
  import { trustlineOptions } from "@/components/tw-blocks/wallet-kit/trustlines";
19
19
 
20
- export function useInitializeEscrow() {
20
+ export function useInitializeEscrow({ onSuccess }: { onSuccess?: () => void } = {}) {
21
21
  const [isSubmitting, setIsSubmitting] = React.useState(false);
22
22
 
23
23
  const { getSingleReleaseFormSchema } = useInitializeEscrowSchema();
@@ -145,6 +145,8 @@ export function useInitializeEscrow() {
145
145
  toast.success("Escrow initialized successfully");
146
146
 
147
147
  setSelectedEscrow({ ...finalPayload, contractId: response.contractId });
148
+
149
+ onSuccess?.();
148
150
  } catch (error) {
149
151
  toast.error(handleError(error as ErrorResponse).message);
150
152
  } finally {
@@ -22,6 +22,7 @@ import { useEscrowContext } from "@/components/tw-blocks/providers/EscrowProvide
22
22
  import { formatCurrency } from "../../../../helpers/format.helper";
23
23
 
24
24
  export const ResolveDisputeDialog = () => {
25
+ const [isOpen, setIsOpen] = React.useState(false);
25
26
  const {
26
27
  form,
27
28
  handleSubmit,
@@ -36,11 +37,11 @@ export const ResolveDisputeDialog = () => {
36
37
  distributedSum,
37
38
  isExactMatch,
38
39
  difference,
39
- } = useResolveDispute();
40
+ } = useResolveDispute({ onSuccess: () => setIsOpen(false) });
40
41
  const { selectedEscrow } = useEscrowContext();
41
42
 
42
43
  return (
43
- <Dialog>
44
+ <Dialog open={isOpen} onOpenChange={setIsOpen}>
44
45
  <DialogTrigger asChild>
45
46
  <Button type="button" className="cursor-pointer w-full">
46
47
  Resolve Dispute
@@ -14,7 +14,7 @@ import { useWalletContext } from "@/components/tw-blocks/wallet-kit/WalletProvid
14
14
 
15
15
  type DistributionInput = { address: string; amount: string | number };
16
16
 
17
- export function useResolveDispute() {
17
+ export function useResolveDispute({ onSuccess }: { onSuccess?: () => void } = {}) {
18
18
  const { resolveDispute } = useEscrowsMutations();
19
19
  const { selectedEscrow, updateEscrow } = useEscrowContext();
20
20
  const { walletAddress } = useWalletContext();
@@ -122,6 +122,8 @@ export function useResolveDispute() {
122
122
 
123
123
  toast.success("Dispute resolved successfully");
124
124
 
125
+ onSuccess?.();
126
+
125
127
  const sumDistributed = payload.distributions.reduce((acc, d) => {
126
128
  const n = Number(d.amount || 0);
127
129
  return acc + (isNaN(n) ? 0 : n);
@@ -31,6 +31,7 @@ import {
31
31
  } from "__UI_BASE__/dialog";
32
32
 
33
33
  export const UpdateEscrowDialog = () => {
34
+ const [isOpen, setIsOpen] = React.useState(false);
34
35
  const {
35
36
  form,
36
37
  isSubmitting,
@@ -43,10 +44,10 @@ export const UpdateEscrowDialog = () => {
43
44
  handlePlatformFeeChange,
44
45
  isEscrowLocked,
45
46
  initialMilestonesCount,
46
- } = useUpdateEscrow();
47
+ } = useUpdateEscrow({ onSuccess: () => setIsOpen(false) });
47
48
 
48
49
  return (
49
- <Dialog>
50
+ <Dialog open={isOpen} onOpenChange={setIsOpen}>
50
51
  <DialogTrigger asChild>
51
52
  <Button type="button" className="cursor-pointer w-full">
52
53
  Update
@@ -20,7 +20,7 @@ import {
20
20
  handleError,
21
21
  } from "@/components/tw-blocks/handle-errors/handle";
22
22
 
23
- export function useUpdateEscrow() {
23
+ export function useUpdateEscrow({ onSuccess }: { onSuccess?: () => void } = {}) {
24
24
  const [isSubmitting, setIsSubmitting] = React.useState(false);
25
25
 
26
26
  const { getSingleReleaseFormSchema } = useUpdateEscrowSchema();
@@ -216,6 +216,7 @@ export function useUpdateEscrow() {
216
216
 
217
217
  setSelectedEscrow(nextSelectedEscrow);
218
218
  toast.success("Escrow updated successfully");
219
+ onSuccess?.();
219
220
  } catch (error) {
220
221
  toast.error(handleError(error as ErrorResponse).message);
221
222
  } finally {
@@ -1,6 +1,9 @@
1
- import { kit } from "./wallet-kit";
1
+ import {
2
+ openAuthModal,
3
+ disconnectWalletKit,
4
+ getSelectedWallet,
5
+ } from "./wallet-kit";
2
6
  import { useWalletContext } from "./WalletProvider";
3
- import { ISupportedWallet } from "@creit.tech/stellar-wallets-kit";
4
7
 
5
8
  /**
6
9
  * Custom hook that provides wallet connection and disconnection functionality
@@ -16,20 +19,14 @@ export const useWallet = () => {
16
19
  * Automatically sets wallet information in the context upon successful connection
17
20
  */
18
21
  const connectWallet = async () => {
19
- await kit.openModal({
20
- modalTitle: "Connect to your favorite wallet",
21
- onWalletSelected: async (option: ISupportedWallet) => {
22
- // Set the selected wallet as the active wallet
23
- kit.setWallet(option.id);
22
+ // Open the auth modal and wait for the user to connect
23
+ const { address } = await openAuthModal();
24
24
 
25
- // Get the wallet address and name
26
- const { address } = await kit.getAddress();
27
- const { name } = option;
25
+ // Get the selected wallet details (name)
26
+ const { productName } = await getSelectedWallet();
28
27
 
29
- // Store wallet information in the context and localStorage
30
- setWalletInfo(address, name);
31
- },
32
- });
28
+ // Store wallet information in the context and localStorage
29
+ setWalletInfo(address, productName);
33
30
  };
34
31
 
35
32
  /**
@@ -38,7 +35,7 @@ export const useWallet = () => {
38
35
  * Disconnects the wallet from the Stellar Wallet Kit
39
36
  */
40
37
  const disconnectWallet = async () => {
41
- await kit.disconnect();
38
+ await disconnectWalletKit();
42
39
  clearWalletInfo();
43
40
  };
44
41
 
@@ -49,7 +46,17 @@ export const useWallet = () => {
49
46
  const handleConnect = async () => {
50
47
  try {
51
48
  await connectWallet();
52
- } catch (error) {
49
+ } catch (error: unknown) {
50
+ // Skip error if the user closes the modal
51
+ if (
52
+ typeof error === "object" &&
53
+ error !== null &&
54
+ "code" in error &&
55
+ (error as { code: number }).code === -1
56
+ ) {
57
+ return;
58
+ }
59
+
53
60
  console.error("Error connecting wallet:", error);
54
61
  // You can add additional error handling here, such as showing user notifications
55
62
  }
@@ -1,23 +1,52 @@
1
- import {
2
- StellarWalletsKit,
3
- WalletNetwork,
4
- FREIGHTER_ID,
5
- AlbedoModule,
6
- FreighterModule,
7
- } from "@creit.tech/stellar-wallets-kit";
1
+ import type { ModuleInterface } from "@creit-tech/stellar-wallets-kit/types";
2
+ type SdkModule = typeof import("@creit-tech/stellar-wallets-kit/sdk");
3
+ type TypesModule = typeof import("@creit-tech/stellar-wallets-kit/types");
4
+ type ModulesUtilsModule =
5
+ typeof import("@creit-tech/stellar-wallets-kit/modules/utils");
6
+ type StellarWalletsKitStatic = SdkModule["StellarWalletsKit"];
7
+ type NetworksEnum = TypesModule["Networks"];
8
8
 
9
9
  /**
10
- * Stellar Wallet Kit
10
+ * Stellar Wallet Kit helpers
11
11
  *
12
- * @description The Stellar Wallet Kit is used to connect to the wallet
13
- * @description The Stellar Wallet Kit is used to sign transactions
14
- * @description The Stellar Wallet Kit is used to get the wallet address
12
+ * We only load and initialize the kit
13
+ * on the client, and only in response to effects or user actions.
15
14
  */
16
- export const kit: StellarWalletsKit = new StellarWalletsKit({
17
- network: WalletNetwork.TESTNET,
18
- selectedWalletId: FREIGHTER_ID,
19
- modules: [new FreighterModule(), new AlbedoModule()],
20
- });
15
+ let walletKitPromise: Promise<{
16
+ StellarWalletsKit: StellarWalletsKitStatic;
17
+ Networks: NetworksEnum;
18
+ }> | null = null;
19
+
20
+ const loadWalletKit = async () => {
21
+ if (typeof window === "undefined") {
22
+ throw new Error("StellarWalletsKit is only available in the browser");
23
+ }
24
+
25
+ if (!walletKitPromise) {
26
+ walletKitPromise = (async () => {
27
+ const [sdk, types, modules] = await Promise.all([
28
+ import("@creit-tech/stellar-wallets-kit/sdk") as Promise<SdkModule>,
29
+ import("@creit-tech/stellar-wallets-kit/types") as Promise<TypesModule>,
30
+ import(
31
+ "@creit-tech/stellar-wallets-kit/modules/utils"
32
+ ) as Promise<ModulesUtilsModule>,
33
+ ]);
34
+
35
+ const { StellarWalletsKit } = sdk;
36
+ const { Networks } = types;
37
+ const { defaultModules } = modules;
38
+
39
+ StellarWalletsKit.init({
40
+ network: Networks.TESTNET, // Adjust this to the network you are using
41
+ modules: defaultModules(),
42
+ });
43
+
44
+ return { StellarWalletsKit, Networks };
45
+ })();
46
+ }
47
+
48
+ return walletKitPromise;
49
+ };
21
50
 
22
51
  interface SignTransactionParams {
23
52
  unsignedTransaction: string;
@@ -25,19 +54,45 @@ interface SignTransactionParams {
25
54
  }
26
55
 
27
56
  /**
28
- * Sign Transaction Params
29
- *
30
- * @param unsignedTransaction - The unsigned transaction
31
- * @param address - The address of the wallet
57
+ * Open the authentication modal and request the user's address.
58
+ */
59
+ export const openAuthModal = async (): Promise<{ address: string }> => {
60
+ const { StellarWalletsKit } = await loadWalletKit();
61
+ return StellarWalletsKit.authModal();
62
+ };
63
+
64
+ /**
65
+ * Get the currently selected wallet module.
66
+ */
67
+ export const getSelectedWallet = async (): Promise<ModuleInterface> => {
68
+ const { StellarWalletsKit } = await loadWalletKit();
69
+ return StellarWalletsKit.selectedModule;
70
+ };
71
+
72
+ /**
73
+ * Disconnect the current wallet.
74
+ */
75
+ export const disconnectWalletKit = async (): Promise<void> => {
76
+ const { StellarWalletsKit } = await loadWalletKit();
77
+ return StellarWalletsKit.disconnect();
78
+ };
79
+
80
+ /**
81
+ * Helper to sign a transaction XDR with the active wallet.
32
82
  */
33
83
  export const signTransaction = async ({
34
84
  unsignedTransaction,
35
85
  address,
36
86
  }: SignTransactionParams): Promise<string> => {
37
- const { signedTxXdr } = await kit.signTransaction(unsignedTransaction, {
38
- address,
39
- networkPassphrase: WalletNetwork.TESTNET,
40
- });
87
+ const { StellarWalletsKit, Networks } = await loadWalletKit();
88
+
89
+ const { signedTxXdr } = await StellarWalletsKit.signTransaction(
90
+ unsignedTransaction,
91
+ {
92
+ address,
93
+ networkPassphrase: Networks.TESTNET, // Adjust this to the network you are using
94
+ }
95
+ );
41
96
 
42
97
  return signedTxXdr;
43
98
  };