@trustless-work/blocks 1.1.0 → 1.1.2

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.
@@ -1,119 +1,119 @@
1
- "use client";
2
-
3
- import React from "react";
4
- import { useWalletContext } from "../../wallet-kit/WalletProvider";
5
- import { useEscrowsBySignerQuery } from "../../tanstack/useEscrowsBySignerQuery";
6
- import type { GetEscrowsFromIndexerResponse as Escrow } from "@trustless-work/escrow/types";
7
-
8
- type AmountsByDatePoint = { date: string; amount: number };
9
- type CreatedByDatePoint = { date: string; count: number };
10
- type DonutSlice = { type: "single" | "multi"; value: number; fill: string };
11
-
12
- function getCreatedDateKey(createdAt: Escrow["createdAt"]): string {
13
- // createdAt is a Firestore-like timestamp: { _seconds, _nanoseconds }
14
- const seconds = (createdAt as unknown as { _seconds?: number })?._seconds;
15
- const d = seconds ? new Date(seconds * 1000) : new Date();
16
- // YYYY-MM-DD
17
- return d.toISOString().slice(0, 10);
18
- }
19
-
20
- function getSingleReleaseAmount(escrow: Escrow): number {
21
- // Single release stores total in .amount
22
- const raw = (escrow as unknown as { amount?: number | string }).amount;
23
- const n = Number(raw ?? 0);
24
- return Number.isFinite(n) ? n : 0;
25
- }
26
-
27
- function getMultiReleaseAmount(escrow: Escrow): number {
28
- // Multi release accumulates across milestones
29
- const milestones = (
30
- escrow as unknown as {
31
- milestones?: Array<{ amount?: number | string }>;
32
- }
33
- ).milestones;
34
- if (!Array.isArray(milestones)) return 0;
35
- return milestones.reduce((acc: number, m) => {
36
- const n = Number(m?.amount ?? 0);
37
- return acc + (Number.isFinite(n) ? n : 0);
38
- }, 0);
39
- }
40
-
41
- function getEscrowAmount(escrow: Escrow): number {
42
- if (escrow.type === "single-release") return getSingleReleaseAmount(escrow);
43
- if (escrow.type === "multi-release") return getMultiReleaseAmount(escrow);
44
- return 0;
45
- }
46
-
47
- export function useDashboard() {
48
- const { walletAddress } = useWalletContext();
49
-
50
- const {
51
- data = [],
52
- isLoading,
53
- isFetching,
54
- isError,
55
- refetch,
56
- } = useEscrowsBySignerQuery({
57
- signer: walletAddress ?? "",
58
- enabled: !!walletAddress,
59
- });
60
-
61
- const totalEscrows = React.useMemo<number>(() => data.length, [data.length]);
62
-
63
- const totalAmount = React.useMemo<number>(() => {
64
- return data.reduce((acc: number, e) => acc + getEscrowAmount(e), 0);
65
- }, [data]);
66
-
67
- const totalBalance = React.useMemo<number>(() => {
68
- return data.reduce((acc: number, e) => acc + Number(e?.balance ?? 0), 0);
69
- }, [data]);
70
-
71
- const typeSlices = React.useMemo<DonutSlice[]>(() => {
72
- let single = 0;
73
- let multi = 0;
74
- for (const e of data) {
75
- if (e.type === "single-release") single += 1;
76
- else if (e.type === "multi-release") multi += 1;
77
- }
78
- return [
79
- { type: "single", value: single, fill: "var(--color-single)" },
80
- { type: "multi", value: multi, fill: "var(--color-multi)" },
81
- ];
82
- }, [data]);
83
-
84
- const amountsByDate = React.useMemo<AmountsByDatePoint[]>(() => {
85
- const map = new Map<string, number>();
86
- for (const e of data) {
87
- const key = getCreatedDateKey(e.createdAt);
88
- const current = map.get(key) ?? 0;
89
- map.set(key, current + getEscrowAmount(e));
90
- }
91
- return Array.from(map.entries())
92
- .map(([date, amount]) => ({ date, amount }))
93
- .sort((a, b) => (a.date < b.date ? -1 : a.date > b.date ? 1 : 0));
94
- }, [data]);
95
-
96
- const createdByDate = React.useMemo<CreatedByDatePoint[]>(() => {
97
- const map = new Map<string, number>();
98
- for (const e of data) {
99
- const key = getCreatedDateKey(e.createdAt);
100
- map.set(key, (map.get(key) ?? 0) + 1);
101
- }
102
- return Array.from(map.entries())
103
- .map(([date, count]) => ({ date, count }))
104
- .sort((a, b) => (a.date < b.date ? -1 : a.date > b.date ? 1 : 0));
105
- }, [data]);
106
-
107
- return {
108
- isLoading,
109
- isFetching,
110
- isError,
111
- refetch,
112
- totalEscrows,
113
- totalAmount,
114
- totalBalance,
115
- typeSlices,
116
- amountsByDate,
117
- createdByDate,
118
- } as const;
119
- }
1
+ "use client";
2
+
3
+ import React from "react";
4
+ import { useWalletContext } from "../../wallet-kit/WalletProvider";
5
+ import { useEscrowsBySignerQuery } from "../../tanstack/useEscrowsBySignerQuery";
6
+ import type { GetEscrowsFromIndexerResponse as Escrow } from "@trustless-work/escrow/types";
7
+
8
+ type AmountsByDatePoint = { date: string; amount: number };
9
+ type CreatedByDatePoint = { date: string; count: number };
10
+ type DonutSlice = { type: "single" | "multi"; value: number; fill: string };
11
+
12
+ function getCreatedDateKey(createdAt: Escrow["createdAt"]): string {
13
+ // createdAt is a Firestore-like timestamp: { _seconds, _nanoseconds }
14
+ const seconds = (createdAt as unknown as { _seconds?: number })?._seconds;
15
+ const d = seconds ? new Date(seconds * 1000) : new Date();
16
+ // YYYY-MM-DD
17
+ return d.toISOString().slice(0, 10);
18
+ }
19
+
20
+ function getSingleReleaseAmount(escrow: Escrow): number {
21
+ // Single release stores total in .amount
22
+ const raw = (escrow as unknown as { amount?: number | string }).amount;
23
+ const n = Number(raw ?? 0);
24
+ return Number.isFinite(n) ? n : 0;
25
+ }
26
+
27
+ function getMultiReleaseAmount(escrow: Escrow): number {
28
+ // Multi release accumulates across milestones
29
+ const milestones = (
30
+ escrow as unknown as {
31
+ milestones?: Array<{ amount?: number | string }>;
32
+ }
33
+ ).milestones;
34
+ if (!Array.isArray(milestones)) return 0;
35
+ return milestones.reduce((acc: number, m) => {
36
+ const n = Number(m?.amount ?? 0);
37
+ return acc + (Number.isFinite(n) ? n : 0);
38
+ }, 0);
39
+ }
40
+
41
+ function getEscrowAmount(escrow: Escrow): number {
42
+ if (escrow.type === "single-release") return getSingleReleaseAmount(escrow);
43
+ if (escrow.type === "multi-release") return getMultiReleaseAmount(escrow);
44
+ return 0;
45
+ }
46
+
47
+ export function useDashboard() {
48
+ const { walletAddress } = useWalletContext();
49
+
50
+ const {
51
+ data = [],
52
+ isLoading,
53
+ isFetching,
54
+ isError,
55
+ refetch,
56
+ } = useEscrowsBySignerQuery({
57
+ signer: walletAddress ?? "",
58
+ enabled: !!walletAddress,
59
+ });
60
+
61
+ const totalEscrows = React.useMemo<number>(() => data.length, [data.length]);
62
+
63
+ const totalAmount = React.useMemo<number>(() => {
64
+ return data.reduce((acc: number, e) => acc + getEscrowAmount(e), 0);
65
+ }, [data]);
66
+
67
+ const totalBalance = React.useMemo<number>(() => {
68
+ return data.reduce((acc: number, e) => acc + Number(e?.balance ?? 0), 0);
69
+ }, [data]);
70
+
71
+ const typeSlices = React.useMemo<DonutSlice[]>(() => {
72
+ let single = 0;
73
+ let multi = 0;
74
+ for (const e of data) {
75
+ if (e.type === "single-release") single += 1;
76
+ else if (e.type === "multi-release") multi += 1;
77
+ }
78
+ return [
79
+ { type: "single", value: single, fill: "var(--color-single)" },
80
+ { type: "multi", value: multi, fill: "var(--color-multi)" },
81
+ ];
82
+ }, [data]);
83
+
84
+ const amountsByDate = React.useMemo<AmountsByDatePoint[]>(() => {
85
+ const map = new Map<string, number>();
86
+ for (const e of data) {
87
+ const key = getCreatedDateKey(e.createdAt);
88
+ const current = map.get(key) ?? 0;
89
+ map.set(key, current + getEscrowAmount(e));
90
+ }
91
+ return Array.from(map.entries())
92
+ .map(([date, amount]) => ({ date, amount }))
93
+ .sort((a, b) => (a.date < b.date ? -1 : a.date > b.date ? 1 : 0));
94
+ }, [data]);
95
+
96
+ const createdByDate = React.useMemo<CreatedByDatePoint[]>(() => {
97
+ const map = new Map<string, number>();
98
+ for (const e of data) {
99
+ const key = getCreatedDateKey(e.createdAt);
100
+ map.set(key, (map.get(key) ?? 0) + 1);
101
+ }
102
+ return Array.from(map.entries())
103
+ .map(([date, count]) => ({ date, count }))
104
+ .sort((a, b) => (a.date < b.date ? -1 : a.date > b.date ? 1 : 0));
105
+ }, [data]);
106
+
107
+ return {
108
+ isLoading,
109
+ isFetching,
110
+ isError,
111
+ refetch,
112
+ totalEscrows,
113
+ totalAmount,
114
+ totalBalance,
115
+ typeSlices,
116
+ amountsByDate,
117
+ createdByDate,
118
+ } as const;
119
+ }
@@ -4,7 +4,7 @@
4
4
  "react-dom": "^18.2.0",
5
5
  "react-hook-form": "^7.53.0",
6
6
  "zod": "^3.23.8",
7
- "@trustless-work/escrow": "^3.0.2",
7
+ "@trustless-work/escrow": "^3.0.3",
8
8
  "@tanstack/react-query": "^5.75.0",
9
9
  "@tanstack/react-query-devtools": "^5.75.0",
10
10
  "tailwindcss": "^3.3.3",
@@ -1,166 +1,166 @@
1
- import {
2
- DollarSign,
3
- CheckCircle,
4
- CheckSquare,
5
- AlertTriangle,
6
- Edit,
7
- Scale,
8
- Unlock,
9
- Wallet,
10
- Settings,
11
- Briefcase,
12
- } from "lucide-react";
13
- import {
14
- GetEscrowsFromIndexerResponse as Escrow,
15
- Role,
16
- } from "@trustless-work/escrow/types";
17
- import { FundEscrowDialog } from "../../single-multi-release/fund-escrow/dialog/FundEscrow";
18
-
19
- interface ActionsProps {
20
- selectedEscrow: Escrow;
21
- userRolesInEscrow: string[];
22
- areAllMilestonesApproved: boolean;
23
- }
24
-
25
- export const roleActions: {
26
- role: Role;
27
- actions: string[];
28
- icon: React.ReactNode;
29
- color: string;
30
- }[] = [
31
- {
32
- role: "signer",
33
- actions: ["fundEscrow"],
34
- icon: <Wallet className="h-6 w-6 text-primary" />,
35
- color: "",
36
- },
37
- {
38
- role: "approver",
39
- actions: ["fundEscrow", "approveMilestone", "startDispute"],
40
- icon: <CheckCircle className="h-6 w-6 text-primary" />,
41
- color: "0",
42
- },
43
- {
44
- role: "serviceProvider",
45
- actions: ["fundEscrow", "completeMilestone", "startDispute"],
46
- icon: <Briefcase className="h-6 w-6 text-primary" />,
47
- color: "0",
48
- },
49
- {
50
- role: "disputeResolver",
51
- actions: ["fundEscrow", "resolveDispute"],
52
- icon: <Scale className="h-6 w-6 text-primary" />,
53
- color: "00",
54
- },
55
- {
56
- role: "releaseSigner",
57
- actions: ["fundEscrow", "releasePayment"],
58
- icon: <Unlock className="h-6 w-6 text-primary" />,
59
- color: "",
60
- },
61
- {
62
- role: "platformAddress",
63
- actions: ["fundEscrow", "editEscrow"],
64
- icon: <Settings className="h-6 w-6 text-primary" />,
65
- color: "0",
66
- },
67
- {
68
- role: "receiver",
69
- actions: ["fundEscrow"],
70
- icon: <DollarSign className="h-6 w-6 text-primary" />,
71
- color: "",
72
- },
73
- ];
74
-
75
- export const actionIcons: Record<string, React.ReactNode> = {
76
- fundEscrow: <DollarSign className="h-6 w-6 text-primary/60" />,
77
- approveMilestone: <CheckCircle className="h-6 w-6 text-primary/60" />,
78
- completeMilestone: <CheckSquare className="h-6 w-6 text-primary/60" />,
79
- startDispute: <AlertTriangle className="h-6 w-6 text-primary/60" />,
80
- resolveDispute: <Scale className="h-6 w-6 text-primary/60" />,
81
- releasePayment: <Unlock className="h-6 w-6 text-primary/60" />,
82
- editEscrow: <Edit className="h-6 w-6 text-primary/60" />,
83
- };
84
-
85
- export const Actions = ({
86
- selectedEscrow,
87
- userRolesInEscrow,
88
- areAllMilestonesApproved,
89
- }: ActionsProps) => {
90
- const shouldShowEditButton =
91
- userRolesInEscrow.includes("platformAddress") &&
92
- !selectedEscrow?.flags?.disputed &&
93
- !selectedEscrow?.flags?.resolved &&
94
- !selectedEscrow?.flags?.released;
95
-
96
- const shouldShowDisputeButton =
97
- selectedEscrow.type === "single-release" &&
98
- (userRolesInEscrow.includes("approver") ||
99
- userRolesInEscrow.includes("serviceProvider")) &&
100
- !selectedEscrow?.flags?.disputed &&
101
- !selectedEscrow?.flags?.resolved;
102
-
103
- const shouldShowResolveButton =
104
- selectedEscrow.type === "single-release" &&
105
- userRolesInEscrow.includes("disputeResolver") &&
106
- !selectedEscrow?.flags?.resolved &&
107
- selectedEscrow?.flags?.disputed;
108
-
109
- const shouldShowReleaseFundsButton =
110
- selectedEscrow.type === "single-release" &&
111
- areAllMilestonesApproved &&
112
- userRolesInEscrow.includes("releaseSigner") &&
113
- !selectedEscrow.flags?.released;
114
-
115
- const shouldShowWithdrawRemaining = (() => {
116
- if (selectedEscrow.type !== "multi-release") return false;
117
- if (!userRolesInEscrow.includes("disputeResolver")) return false;
118
- if ((selectedEscrow.balance ?? 0) === 0) return false;
119
- const milestones = (selectedEscrow.milestones || []) as Array<{
120
- flags?: { resolved?: boolean; released?: boolean; disputed?: boolean };
121
- }>;
122
- return (
123
- milestones.length > 0 &&
124
- milestones.every((m) => {
125
- const f = m.flags || {};
126
- return !!(f.resolved || f.released || f.disputed);
127
- })
128
- );
129
- })();
130
-
131
- const hasConditionalButtons =
132
- shouldShowEditButton ||
133
- shouldShowDisputeButton ||
134
- shouldShowResolveButton ||
135
- shouldShowReleaseFundsButton ||
136
- shouldShowWithdrawRemaining;
137
-
138
- return (
139
- <div className="flex items-start justify-start flex-col gap-2 w-full">
140
- {/* You can add the buttons here, using the buttons from the blocks. These actions are conditional based on the escrow flags and the user roles. */}
141
- {hasConditionalButtons && (
142
- <div className="flex flex-col gap-2 w-full">
143
- {/* UpdateEscrowDialog component should be rendered based on the escrow type. It means that if the selectedEscrow.type is "single-release", then the UpdateEscrowDialog (from the single-release block) component should be rendered. If the selectedEscrow.type is "multi-release", then the UpdateEscrowDialog (from the multi-release block) component should be rendered. */}
144
- {/* {shouldShowEditButton && <UpdateEscrowDialog />} */}
145
-
146
- {/* Works only with single-release escrows */}
147
- {/* Only appears if the escrow has balance */}
148
- {/* {shouldShowDisputeButton && <DisputeEscrowButton />} */}
149
-
150
- {/* Works only with single-release escrows */}
151
- {/* Only appears if the escrow is disputed */}
152
- {/* {shouldShowResolveButton && <ResolveDisputeDialog />} */}
153
-
154
- {/* Works only with single-release escrows */}
155
- {/* Only appears if all the milestones are approved */}
156
- {/* {shouldShowReleaseFundsButton && <ReleaseEscrowButton />} */}
157
-
158
- {/* Multi-release: Withdraw Remaining Funds */}
159
- {/* {shouldShowWithdrawRemaining && <WithdrawRemainingFundsDialog />} */}
160
- </div>
161
- )}
162
-
163
- <FundEscrowDialog />
164
- </div>
165
- );
166
- };
1
+ import {
2
+ DollarSign,
3
+ CheckCircle,
4
+ CheckSquare,
5
+ AlertTriangle,
6
+ Edit,
7
+ Scale,
8
+ Unlock,
9
+ Wallet,
10
+ Settings,
11
+ Briefcase,
12
+ } from "lucide-react";
13
+ import {
14
+ GetEscrowsFromIndexerResponse as Escrow,
15
+ Role,
16
+ } from "@trustless-work/escrow/types";
17
+ import { FundEscrowDialog } from "../../single-multi-release/fund-escrow/dialog/FundEscrow";
18
+
19
+ interface ActionsProps {
20
+ selectedEscrow: Escrow;
21
+ userRolesInEscrow: string[];
22
+ areAllMilestonesApproved: boolean;
23
+ }
24
+
25
+ export const roleActions: {
26
+ role: Role;
27
+ actions: string[];
28
+ icon: React.ReactNode;
29
+ color: string;
30
+ }[] = [
31
+ {
32
+ role: "signer",
33
+ actions: ["fundEscrow"],
34
+ icon: <Wallet className="h-6 w-6 text-primary" />,
35
+ color: "",
36
+ },
37
+ {
38
+ role: "approver",
39
+ actions: ["fundEscrow", "approveMilestone", "startDispute"],
40
+ icon: <CheckCircle className="h-6 w-6 text-primary" />,
41
+ color: "0",
42
+ },
43
+ {
44
+ role: "serviceProvider",
45
+ actions: ["fundEscrow", "completeMilestone", "startDispute"],
46
+ icon: <Briefcase className="h-6 w-6 text-primary" />,
47
+ color: "0",
48
+ },
49
+ {
50
+ role: "disputeResolver",
51
+ actions: ["fundEscrow", "resolveDispute"],
52
+ icon: <Scale className="h-6 w-6 text-primary" />,
53
+ color: "00",
54
+ },
55
+ {
56
+ role: "releaseSigner",
57
+ actions: ["fundEscrow", "releasePayment"],
58
+ icon: <Unlock className="h-6 w-6 text-primary" />,
59
+ color: "",
60
+ },
61
+ {
62
+ role: "platformAddress",
63
+ actions: ["fundEscrow", "editEscrow"],
64
+ icon: <Settings className="h-6 w-6 text-primary" />,
65
+ color: "0",
66
+ },
67
+ {
68
+ role: "receiver",
69
+ actions: ["fundEscrow"],
70
+ icon: <DollarSign className="h-6 w-6 text-primary" />,
71
+ color: "",
72
+ },
73
+ ];
74
+
75
+ export const actionIcons: Record<string, React.ReactNode> = {
76
+ fundEscrow: <DollarSign className="h-6 w-6 text-primary/60" />,
77
+ approveMilestone: <CheckCircle className="h-6 w-6 text-primary/60" />,
78
+ completeMilestone: <CheckSquare className="h-6 w-6 text-primary/60" />,
79
+ startDispute: <AlertTriangle className="h-6 w-6 text-primary/60" />,
80
+ resolveDispute: <Scale className="h-6 w-6 text-primary/60" />,
81
+ releasePayment: <Unlock className="h-6 w-6 text-primary/60" />,
82
+ editEscrow: <Edit className="h-6 w-6 text-primary/60" />,
83
+ };
84
+
85
+ export const Actions = ({
86
+ selectedEscrow,
87
+ userRolesInEscrow,
88
+ areAllMilestonesApproved,
89
+ }: ActionsProps) => {
90
+ const shouldShowEditButton =
91
+ userRolesInEscrow.includes("platformAddress") &&
92
+ !selectedEscrow?.flags?.disputed &&
93
+ !selectedEscrow?.flags?.resolved &&
94
+ !selectedEscrow?.flags?.released;
95
+
96
+ const shouldShowDisputeButton =
97
+ selectedEscrow.type === "single-release" &&
98
+ (userRolesInEscrow.includes("approver") ||
99
+ userRolesInEscrow.includes("serviceProvider")) &&
100
+ !selectedEscrow?.flags?.disputed &&
101
+ !selectedEscrow?.flags?.resolved;
102
+
103
+ const shouldShowResolveButton =
104
+ selectedEscrow.type === "single-release" &&
105
+ userRolesInEscrow.includes("disputeResolver") &&
106
+ !selectedEscrow?.flags?.resolved &&
107
+ selectedEscrow?.flags?.disputed;
108
+
109
+ const shouldShowReleaseFundsButton =
110
+ selectedEscrow.type === "single-release" &&
111
+ areAllMilestonesApproved &&
112
+ userRolesInEscrow.includes("releaseSigner") &&
113
+ !selectedEscrow.flags?.released;
114
+
115
+ const shouldShowWithdrawRemaining = (() => {
116
+ if (selectedEscrow.type !== "multi-release") return false;
117
+ if (!userRolesInEscrow.includes("disputeResolver")) return false;
118
+ if ((selectedEscrow.balance ?? 0) === 0) return false;
119
+ const milestones = (selectedEscrow.milestones || []) as Array<{
120
+ flags?: { resolved?: boolean; released?: boolean; disputed?: boolean };
121
+ }>;
122
+ return (
123
+ milestones.length > 0 &&
124
+ milestones.every((m) => {
125
+ const f = m.flags || {};
126
+ return !!(f.resolved || f.released || f.disputed);
127
+ })
128
+ );
129
+ })();
130
+
131
+ const hasConditionalButtons =
132
+ shouldShowEditButton ||
133
+ shouldShowDisputeButton ||
134
+ shouldShowResolveButton ||
135
+ shouldShowReleaseFundsButton ||
136
+ shouldShowWithdrawRemaining;
137
+
138
+ return (
139
+ <div className="flex items-start justify-start flex-col gap-2 w-full">
140
+ {/* You can add the buttons here, using the buttons from the blocks. These actions are conditional based on the escrow flags and the user roles. */}
141
+ {hasConditionalButtons && (
142
+ <div className="flex flex-col gap-2 w-full">
143
+ {/* UpdateEscrowDialog component should be rendered based on the escrow type. It means that if the selectedEscrow.type is "single-release", then the UpdateEscrowDialog (from the single-release block) component should be rendered. If the selectedEscrow.type is "multi-release", then the UpdateEscrowDialog (from the multi-release block) component should be rendered. */}
144
+ {/* {shouldShowEditButton && <UpdateEscrowDialog />} */}
145
+
146
+ {/* Works only with single-release escrows */}
147
+ {/* Only appears if the escrow has balance */}
148
+ {/* {shouldShowDisputeButton && <DisputeEscrowButton />} */}
149
+
150
+ {/* Works only with single-release escrows */}
151
+ {/* Only appears if the escrow is disputed */}
152
+ {/* {shouldShowResolveButton && <ResolveDisputeDialog />} */}
153
+
154
+ {/* Works only with single-release escrows */}
155
+ {/* Only appears if all the milestones are approved */}
156
+ {/* {shouldShowReleaseFundsButton && <ReleaseEscrowButton />} */}
157
+
158
+ {/* Multi-release: Withdraw Remaining Funds */}
159
+ {/* {shouldShowWithdrawRemaining && <WithdrawRemainingFundsDialog />} */}
160
+ </div>
161
+ )}
162
+
163
+ <FundEscrowDialog />
164
+ </div>
165
+ );
166
+ };
@@ -1,4 +1,3 @@
1
- // @ts-nocheck
2
1
  import * as React from "react";
3
2
  import { useGetMultipleEscrowBalancesQuery } from "@/components/tw-blocks/tanstack/useGetMultipleEscrowBalances";
4
3
  import { formatCurrency } from "@/components/tw-blocks/helpers/format.helper";
@@ -59,16 +59,20 @@ export const EscrowProvider = ({ children }: { children: ReactNode }) => {
59
59
  const [userRolesInEscrow, setUserRolesInEscrowState] = useState<string[]>([]);
60
60
 
61
61
  /**
62
- * Get the selected escrow from the local storage
62
+ * Load saved wallet information from localStorage when the component mounts
63
+ * This ensures the wallet state persists across browser sessions
63
64
  */
64
65
  useEffect(() => {
65
66
  try {
66
67
  const stored = localStorage.getItem(LOCAL_STORAGE_KEY);
67
68
  if (stored) {
68
69
  const parsed: Escrow = JSON.parse(stored);
70
+ // This effect initializes state from localStorage once on mount.
71
+ // It is a controlled sync from an external store, so we allow setState here.
72
+ // eslint-disable-next-line react-hooks/set-state-in-effect
69
73
  setSelectedEscrowState(parsed);
70
74
  }
71
- } catch (_err) {
75
+ } catch {
72
76
  // ignore malformed localStorage content
73
77
  }
74
78
  }, []);
@@ -41,7 +41,11 @@ export const WalletProvider = ({ children }: { children: ReactNode }) => {
41
41
  const storedAddress = localStorage.getItem("walletAddress");
42
42
  const storedName = localStorage.getItem("walletName");
43
43
 
44
+ // This effect initializes state from localStorage once on mount.
45
+ // It is a controlled sync from an external store, so we allow setState here.
46
+ // eslint-disable-next-line react-hooks/set-state-in-effect
44
47
  if (storedAddress) setWalletAddress(storedAddress);
48
+ // eslint-disable-next-line react-hooks/set-state-in-effect
45
49
  if (storedName) setWalletName(storedName);
46
50
  }, []);
47
51