@trustless-work/blocks 0.0.6 → 0.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 (72) hide show
  1. package/README.md +39 -13
  2. package/bin/index.js +1596 -1137
  3. package/package.json +44 -44
  4. package/templates/escrows/details/Actions.tsx +144 -149
  5. package/templates/escrows/details/Entities.tsx +1 -1
  6. package/templates/escrows/details/EntityCard.tsx +1 -3
  7. package/templates/escrows/details/EscrowDetailDialog.tsx +18 -18
  8. package/templates/escrows/details/GeneralInformation.tsx +20 -23
  9. package/templates/escrows/details/MilestoneCard.tsx +46 -47
  10. package/templates/escrows/details/MilestoneDetailDialog.tsx +1 -2
  11. package/templates/escrows/details/Milestones.tsx +0 -5
  12. package/templates/escrows/details/SuccessReleaseDialog.tsx +6 -9
  13. package/templates/escrows/details/useDetailsEscrow.ts +2 -2
  14. package/templates/escrows/escrows-by-role/cards/EscrowsCards.tsx +111 -60
  15. package/templates/escrows/escrows-by-role/cards/Filters.tsx +3 -5
  16. package/templates/escrows/escrows-by-role/table/EscrowsTable.tsx +36 -38
  17. package/templates/escrows/escrows-by-role/table/Filters.tsx +3 -5
  18. package/templates/escrows/escrows-by-role/useEscrowsByRole.shared.ts +33 -25
  19. package/templates/escrows/escrows-by-signer/cards/EscrowsCards.tsx +107 -67
  20. package/templates/escrows/escrows-by-signer/cards/Filters.tsx +3 -5
  21. package/templates/escrows/escrows-by-signer/table/EscrowsTable.tsx +28 -38
  22. package/templates/escrows/escrows-by-signer/table/Filters.tsx +3 -5
  23. package/templates/escrows/escrows-by-signer/useEscrowsBySigner.shared.ts +32 -25
  24. package/templates/escrows/multi-release/dispute-milestone/button/DisputeEscrow.tsx +98 -0
  25. package/templates/escrows/multi-release/initialize-escrow/dialog/InitializeEscrow.tsx +528 -0
  26. package/templates/escrows/multi-release/initialize-escrow/form/InitializeEscrow.tsx +506 -0
  27. package/templates/escrows/multi-release/initialize-escrow/shared/schema.ts +179 -0
  28. package/templates/escrows/multi-release/initialize-escrow/shared/useInitializeEscrow.ts +175 -0
  29. package/templates/escrows/multi-release/release-milestone/button/ReleaseEscrow.tsx +116 -0
  30. package/templates/escrows/multi-release/resolve-dispute/button/ResolveDispute.tsx +122 -0
  31. package/templates/escrows/multi-release/resolve-dispute/dialog/ResolveDispute.tsx +178 -0
  32. package/templates/escrows/multi-release/resolve-dispute/form/ResolveDispute.tsx +156 -0
  33. package/templates/escrows/multi-release/resolve-dispute/shared/schema.ts +85 -0
  34. package/templates/escrows/multi-release/resolve-dispute/shared/useResolveDispute.ts +105 -0
  35. package/templates/escrows/multi-release/update-escrow/dialog/UpdateEscrow.tsx +471 -0
  36. package/templates/escrows/multi-release/update-escrow/form/UpdateEscrow.tsx +449 -0
  37. package/templates/escrows/multi-release/update-escrow/shared/schema.ts +152 -0
  38. package/templates/escrows/multi-release/update-escrow/shared/useUpdateEscrow.ts +254 -0
  39. package/templates/escrows/{single-release → single-multi-release}/approve-milestone/button/ApproveMilestone.tsx +21 -8
  40. package/templates/escrows/{single-release → single-multi-release}/approve-milestone/dialog/ApproveMilestone.tsx +4 -4
  41. package/templates/escrows/{single-release → single-multi-release}/approve-milestone/form/ApproveMilestone.tsx +4 -4
  42. package/templates/escrows/{single-release/approve-milestone/shared → single-multi-release/approve-milestone}/useApproveMilestone.ts +17 -17
  43. package/templates/escrows/{single-release → single-multi-release}/change-milestone-status/button/ChangeMilestoneStatus.tsx +5 -5
  44. package/templates/escrows/{single-release → single-multi-release}/change-milestone-status/dialog/ChangeMilestoneStatus.tsx +5 -5
  45. package/templates/escrows/{single-release → single-multi-release}/change-milestone-status/form/ChangeMilestoneStatus.tsx +4 -4
  46. package/templates/escrows/{single-release/change-milestone-status/shared → single-multi-release/change-milestone-status}/useChangeMilestoneStatus.ts +2 -2
  47. package/templates/escrows/{single-release → single-multi-release}/fund-escrow/button/FundEscrow.tsx +4 -4
  48. package/templates/escrows/{single-release → single-multi-release}/fund-escrow/dialog/FundEscrow.tsx +3 -3
  49. package/templates/escrows/{single-release → single-multi-release}/fund-escrow/form/FundEscrow.tsx +3 -3
  50. package/templates/escrows/{single-release/fund-escrow/shared → single-multi-release/fund-escrow}/useFundEscrow.ts +2 -2
  51. package/templates/escrows/single-release/dispute-escrow/button/DisputeEscrow.tsx +3 -3
  52. package/templates/escrows/single-release/initialize-escrow/dialog/InitializeEscrow.tsx +14 -6
  53. package/templates/escrows/single-release/initialize-escrow/form/InitializeEscrow.tsx +14 -6
  54. package/templates/escrows/single-release/initialize-escrow/shared/schema.ts +0 -57
  55. package/templates/escrows/single-release/initialize-escrow/shared/useInitializeEscrow.ts +43 -2
  56. package/templates/escrows/single-release/release-escrow/button/ReleaseEscrow.tsx +5 -5
  57. package/templates/escrows/single-release/resolve-dispute/button/ResolveDispute.tsx +4 -4
  58. package/templates/escrows/single-release/resolve-dispute/dialog/ResolveDispute.tsx +4 -7
  59. package/templates/escrows/single-release/resolve-dispute/form/ResolveDispute.tsx +2 -2
  60. package/templates/escrows/single-release/resolve-dispute/shared/useResolveDispute.ts +15 -2
  61. package/templates/escrows/single-release/update-escrow/dialog/UpdateEscrow.tsx +2 -2
  62. package/templates/escrows/single-release/update-escrow/form/UpdateEscrow.tsx +2 -2
  63. package/templates/escrows/single-release/update-escrow/shared/useUpdateEscrow.ts +229 -224
  64. package/templates/{escrows/escrow-context → providers}/EscrowDialogsProvider.tsx +1 -3
  65. package/templates/{escrows/escrow-context → providers}/EscrowProvider.tsx +27 -4
  66. package/templates/providers/ReactQueryClientProvider.tsx +3 -1
  67. package/templates/providers/TrustlessWork.tsx +1 -1
  68. package/templates/escrows/details/ProgressEscrow.tsx +0 -191
  69. /package/templates/escrows/{single-release/approve-milestone/shared → single-multi-release/approve-milestone}/schema.ts +0 -0
  70. /package/templates/escrows/{single-release/change-milestone-status/shared → single-multi-release/change-milestone-status}/schema.ts +0 -0
  71. /package/templates/escrows/{single-release/fund-escrow/shared → single-multi-release/fund-escrow}/schema.ts +0 -0
  72. /package/templates/{escrows/escrow-context → providers}/EscrowAmountProvider.tsx +0 -0
@@ -11,20 +11,14 @@ import {
11
11
  CheckCheck,
12
12
  Layers,
13
13
  } from "lucide-react";
14
- import {
15
- GetEscrowsFromIndexerResponse as Escrow,
16
- Role,
17
- } from "@trustless-work/escrow/types";
14
+ import { GetEscrowsFromIndexerResponse as Escrow } from "@trustless-work/escrow/types";
18
15
  import {
19
16
  MultiReleaseMilestone,
20
17
  SingleReleaseMilestone,
21
18
  } from "@trustless-work/escrow";
22
19
  import { Badge } from "__UI_BASE__/badge";
23
- import ApproveMilestoneButton from "../../single-release/approve-milestone/button/ApproveMilestone";
24
- import ResolveDisputeDialog from "../../single-release/resolve-dispute/dialog/ResolveDispute";
25
- import DisputeEscrowButton from "../../single-release/dispute-escrow/button/DisputeEscrow";
26
- import ReleaseEscrowButton from "../../single-release/release-escrow/button/ReleaseEscrow";
27
- import ChangeMilestoneStatusDialog from "../../single-release/change-milestone-status/dialog/ChangeMilestoneStatus";
20
+ import { ChangeMilestoneStatusDialog } from "../../single-multi-release/change-milestone-status/dialog/ChangeMilestoneStatus";
21
+ import { ApproveMilestoneButton } from "../../single-multi-release/approve-milestone/button/ApproveMilestone";
28
22
  import { formatCurrency } from "@/components/tw-blocks/helpers/format.helper";
29
23
 
30
24
  interface MilestoneCardProps {
@@ -32,7 +26,6 @@ interface MilestoneCardProps {
32
26
  milestoneIndex: number;
33
27
  selectedEscrow: Escrow;
34
28
  userRolesInEscrow: string[];
35
- activeRole: Role[];
36
29
  onViewDetails: (
37
30
  milestone: SingleReleaseMilestone | MultiReleaseMilestone,
38
31
  index: number
@@ -44,7 +37,6 @@ const MilestoneCardComponent = ({
44
37
  milestoneIndex,
45
38
  selectedEscrow,
46
39
  userRolesInEscrow,
47
- activeRole,
48
40
  onViewDetails,
49
41
  }: MilestoneCardProps) => {
50
42
  const getMilestoneStatusBadge = (
@@ -106,13 +98,11 @@ const MilestoneCardComponent = ({
106
98
  const getActionButtons = (
107
99
  milestone: SingleReleaseMilestone | MultiReleaseMilestone,
108
100
  milestoneIndex: number,
109
- userRolesInEscrow: string[],
110
- activeRole: Role[]
101
+ userRolesInEscrow: string[]
111
102
  ) => {
112
103
  const buttons = [] as React.ReactNode[];
113
104
  if (
114
105
  userRolesInEscrow.includes("serviceProvider") &&
115
- activeRole.includes("serviceProvider") &&
116
106
  milestone.status !== "completed" &&
117
107
  !("flags" in milestone && milestone.flags?.approved)
118
108
  ) {
@@ -124,55 +114,71 @@ const MilestoneCardComponent = ({
124
114
  );
125
115
  }
126
116
 
117
+ if (
118
+ userRolesInEscrow.includes("approver") &&
119
+ (("approved" in milestone && !milestone.approved) ||
120
+ ("flags" in milestone &&
121
+ !milestone.flags?.approved &&
122
+ !milestone.flags?.disputed &&
123
+ !milestone.flags?.released &&
124
+ !milestone.flags?.resolved))
125
+ ) {
126
+ buttons.push(
127
+ <ApproveMilestoneButton
128
+ key={`approve-${milestoneIndex}`}
129
+ milestoneIndex={milestoneIndex}
130
+ />
131
+ );
132
+ }
133
+
127
134
  if (
128
135
  userRolesInEscrow.includes("releaseSigner") &&
129
- activeRole.includes("releaseSigner") &&
130
136
  "flags" in milestone &&
131
137
  !milestone.flags?.disputed &&
132
138
  milestone.flags?.approved &&
133
139
  !milestone.flags?.released
134
140
  ) {
135
- buttons.push(<ReleaseEscrowButton key={`release-${milestoneIndex}`} />);
141
+ buttons
142
+ .push
143
+ // You can add the button here, using the button from the blocks. This button is conditional based on the milestone status and the user roles. Works only with multi-release escrows.
144
+ // <ReleaseEscrowButton
145
+ // key={`release-${milestoneIndex}`}
146
+ // milestoneIndex={milestoneIndex}
147
+ // />
148
+ ();
136
149
  }
137
150
 
138
151
  if (
139
152
  (userRolesInEscrow.includes("serviceProvider") ||
140
153
  userRolesInEscrow.includes("approver")) &&
141
- (activeRole.includes("serviceProvider") ||
142
- activeRole.includes("approver")) &&
143
154
  "flags" in milestone &&
144
155
  !milestone.flags?.disputed &&
145
156
  !milestone.flags?.released &&
146
157
  !milestone.flags?.resolved
147
158
  ) {
148
- buttons.push(<DisputeEscrowButton key={`dispute-${milestoneIndex}`} />);
159
+ buttons
160
+ .push
161
+ // You can add the button here, using the button from the blocks. This button is conditional based on the milestone status and the user roles. Works only with multi-release escrows.
162
+ // <DisputeEscrowButton
163
+ // key={`dispute-${milestoneIndex}`}
164
+ // milestoneIndex={milestoneIndex}
165
+ // />
166
+ ();
149
167
  }
150
168
 
151
169
  if (
152
170
  userRolesInEscrow.includes("disputeResolver") &&
153
- activeRole.includes("disputeResolver") &&
154
171
  "flags" in milestone &&
155
172
  milestone.flags?.disputed
156
173
  ) {
157
- buttons.push(<ResolveDisputeDialog key={`resolve-${milestoneIndex}`} />);
158
- }
159
-
160
- if (
161
- userRolesInEscrow.includes("approver") &&
162
- activeRole.includes("approver") &&
163
- (("approved" in milestone && !milestone.approved) ||
164
- ("flags" in milestone &&
165
- !milestone.flags?.approved &&
166
- !milestone.flags?.disputed &&
167
- !milestone.flags?.released &&
168
- !milestone.flags?.resolved))
169
- ) {
170
- buttons.push(
171
- <ApproveMilestoneButton
172
- key={`approve-${milestoneIndex}`}
173
- milestoneIndex={milestoneIndex}
174
- />
175
- );
174
+ buttons
175
+ .push
176
+ // You can add the button here, using the button from the blocks. This button is conditional based on the milestone status and the user roles. Works only with multi-release escrows.
177
+ // <ResolveDisputeDialog
178
+ // key={`resolve-${milestoneIndex}`}
179
+ // milestoneIndex={milestoneIndex}
180
+ // />
181
+ ();
176
182
  }
177
183
 
178
184
  return buttons;
@@ -210,12 +216,7 @@ const MilestoneCardComponent = ({
210
216
  </div>
211
217
  )}
212
218
 
213
- {getActionButtons(
214
- milestone,
215
- milestoneIndex,
216
- userRolesInEscrow,
217
- activeRole
218
- )}
219
+ {getActionButtons(milestone, milestoneIndex, userRolesInEscrow)}
219
220
 
220
221
  <Button
221
222
  size="sm"
@@ -242,9 +243,7 @@ export const MilestoneCard = React.memo(
242
243
  prev.milestoneIndex === next.milestoneIndex &&
243
244
  prev.selectedEscrow?.contractId === next.selectedEscrow?.contractId &&
244
245
  prev.onViewDetails === next.onViewDetails &&
245
- prev.activeRole.length === next.activeRole.length &&
246
246
  prev.userRolesInEscrow.length === next.userRolesInEscrow.length &&
247
- prev.activeRole.every((r, i) => r === next.activeRole[i]) &&
248
247
  prev.userRolesInEscrow.every((r, i) => r === next.userRolesInEscrow[i])
249
248
  ) {
250
249
  return true;
@@ -25,7 +25,6 @@ import {
25
25
  import {
26
26
  GetEscrowsFromIndexerResponse,
27
27
  MultiReleaseMilestone,
28
- Role,
29
28
  SingleReleaseMilestone,
30
29
  } from "@trustless-work/escrow";
31
30
  import Link from "next/link";
@@ -39,7 +38,6 @@ interface MilestoneDetailDialogProps {
39
38
  } | null;
40
39
  selectedEscrow: GetEscrowsFromIndexerResponse;
41
40
  userRolesInEscrow: string[];
42
- activeRole: Role[];
43
41
  evidenceVisibleMap: Record<number, boolean>;
44
42
  setEvidenceVisibleMap: React.Dispatch<
45
43
  React.SetStateAction<Record<number, boolean>>
@@ -112,6 +110,7 @@ export const MilestoneDetailDialog = ({
112
110
  new URL(url);
113
111
  return true;
114
112
  } catch (error) {
113
+ console.error(error);
115
114
  return false;
116
115
  }
117
116
  };
@@ -4,7 +4,6 @@ import { useCallback, useState } from "react";
4
4
  import { GetEscrowsFromIndexerResponse as Escrow } from "@trustless-work/escrow/types";
5
5
  import {
6
6
  MultiReleaseMilestone,
7
- Role,
8
7
  SingleReleaseMilestone,
9
8
  } from "@trustless-work/escrow";
10
9
  import { MilestoneCard } from "./MilestoneCard";
@@ -17,7 +16,6 @@ interface MilestonesProps {
17
16
  React.SetStateAction<Record<number, boolean>>
18
17
  >;
19
18
  evidenceVisibleMap: Record<number, boolean>;
20
- activeRole: Role[];
21
19
  }
22
20
 
23
21
  export const Milestones = ({
@@ -25,7 +23,6 @@ export const Milestones = ({
25
23
  userRolesInEscrow,
26
24
  setEvidenceVisibleMap,
27
25
  evidenceVisibleMap,
28
- activeRole,
29
26
  }: MilestonesProps) => {
30
27
  const [selectedMilestoneForDetail, setSelectedMilestoneForDetail] = useState<{
31
28
  milestone: SingleReleaseMilestone | MultiReleaseMilestone;
@@ -65,7 +62,6 @@ export const Milestones = ({
65
62
  milestoneIndex={milestoneIndex}
66
63
  selectedEscrow={selectedEscrow}
67
64
  userRolesInEscrow={userRolesInEscrow}
68
- activeRole={activeRole}
69
65
  onViewDetails={handleViewDetails}
70
66
  />
71
67
  ))}
@@ -77,7 +73,6 @@ export const Milestones = ({
77
73
  selectedMilestone={selectedMilestoneForDetail}
78
74
  selectedEscrow={selectedEscrow}
79
75
  userRolesInEscrow={userRolesInEscrow}
80
- activeRole={activeRole}
81
76
  evidenceVisibleMap={evidenceVisibleMap}
82
77
  setEvidenceVisibleMap={setEvidenceVisibleMap}
83
78
  />
@@ -8,10 +8,9 @@ import {
8
8
  DialogHeader,
9
9
  DialogTitle,
10
10
  } from "__UI_BASE__/dialog";
11
- import { Card } from "__UI_BASE__/card";
12
- import EntityCard from "./EntityCard";
13
- import { useEscrowContext } from "../../escrow-context/EscrowProvider";
14
- import { useEscrowAmountContext } from "../../escrow-context/EscrowAmountProvider";
11
+ import { EntityCard } from "./EntityCard";
12
+ import { useEscrowContext } from "@/components/tw-blocks/providers/EscrowProvider";
13
+ import { useEscrowAmountContext } from "@/components/tw-blocks/providers/EscrowAmountProvider";
15
14
  import { CircleCheckBig } from "lucide-react";
16
15
 
17
16
  interface SuccessReleaseDialogProps {
@@ -19,10 +18,10 @@ interface SuccessReleaseDialogProps {
19
18
  onOpenChange: (open: boolean) => void;
20
19
  }
21
20
 
22
- export function SuccessReleaseDialog({
21
+ export const SuccessReleaseDialog = ({
23
22
  isOpen,
24
23
  onOpenChange,
25
- }: SuccessReleaseDialogProps) {
24
+ }: SuccessReleaseDialogProps) => {
26
25
  const { selectedEscrow } = useEscrowContext();
27
26
  const { receiverAmount, platformFeeAmount, trustlessWorkAmount } =
28
27
  useEscrowAmountContext();
@@ -96,6 +95,4 @@ export function SuccessReleaseDialog({
96
95
  </DialogContent>
97
96
  </Dialog>
98
97
  );
99
- }
100
-
101
- export default SuccessReleaseDialog;
98
+ };
@@ -1,8 +1,8 @@
1
1
  import { useCallback, useEffect, useRef, useState } from "react";
2
2
  import { GetEscrowsFromIndexerResponse as Escrow } from "@trustless-work/escrow/types";
3
3
  import { useWalletContext } from "@/components/tw-blocks/wallet-kit/WalletProvider";
4
- import { useEscrowContext } from "../../escrow-context/EscrowProvider";
5
- import { useEscrowAmountContext } from "../../escrow-context/EscrowAmountProvider";
4
+ import { useEscrowContext } from "@/components/tw-blocks/providers/EscrowProvider";
5
+ import { useEscrowAmountContext } from "@/components/tw-blocks/providers/EscrowAmountProvider";
6
6
 
7
7
  interface EscrowDetailDialogProps {
8
8
  setIsDialogOpen: (value: boolean) => void;
@@ -11,6 +11,7 @@ import type {
11
11
  import { Card, CardContent, CardHeader, CardTitle } from "__UI_BASE__/card";
12
12
  import { Badge } from "__UI_BASE__/badge";
13
13
  import { Separator } from "__UI_BASE__/separator";
14
+ import { Tooltip, TooltipContent, TooltipTrigger } from "__UI_BASE__/tooltip";
14
15
  import {
15
16
  Goal,
16
17
  Wallet,
@@ -20,16 +21,16 @@ import {
20
21
  FileX,
21
22
  } from "lucide-react";
22
23
  import { useEscrowsByRole } from "../useEscrowsByRole.shared";
23
- import Filters from "./Filters";
24
- import EscrowDetailDialog from "../details/EscrowDetailDialog";
25
- import { useEscrowContext } from "../../escrow-context/EscrowProvider";
26
- import { useEscrowDialogs } from "../../escrow-context/EscrowDialogsProvider";
24
+ import { Filters } from "./Filters";
25
+ import { useEscrowContext } from "@/components/tw-blocks/providers/EscrowProvider";
26
+ import { useEscrowDialogs } from "@/components/tw-blocks/providers/EscrowDialogsProvider";
27
27
  import {
28
28
  formatCurrency,
29
29
  formatTimestamp,
30
30
  } from "../../../helpers/format.helper";
31
+ import { EscrowDetailDialog } from "../details/EscrowDetailDialog";
31
32
 
32
- export function EscrowsByRoleCards() {
33
+ export const EscrowsByRoleCards = () => {
33
34
  const {
34
35
  walletAddress,
35
36
  data,
@@ -75,6 +76,17 @@ export function EscrowsByRoleCards() {
75
76
 
76
77
  const dialogStates = useEscrowDialogs();
77
78
 
79
+ const handleRefresh = React.useCallback(() => {
80
+ void refetch();
81
+ }, [refetch]);
82
+
83
+ const setRoleStable = React.useCallback(
84
+ (v: Role | undefined) => {
85
+ if (v) setRole(v);
86
+ },
87
+ [setRole]
88
+ );
89
+
78
90
  function allMilestonesReleasedOrResolved(
79
91
  milestones: MultiReleaseMilestone[]
80
92
  ) {
@@ -119,18 +131,6 @@ export function EscrowsByRoleCards() {
119
131
  dialogStates.second.setIsOpen(true);
120
132
  };
121
133
 
122
- /**
123
- * Based on the provided roles -> https://docs.trustlesswork.com/trustless-work/technology-overview/roles-in-trustless-work
124
- *
125
- * You must pass one or more roles according to requirements. Acctually it's coming from the select filter.
126
- *
127
- * For example:
128
- * - If the user is a freelancer, you must pass the "serviceProvider" and "receiver" role
129
- *
130
- * Depending of the role, you'll have different actions buttons
131
- */
132
- const activeRole: Role[] = role.split(",") as Role[];
133
-
134
134
  return (
135
135
  <>
136
136
  <div className="w-full flex flex-col gap-4">
@@ -150,19 +150,19 @@ export function EscrowsByRoleCards() {
150
150
  setEngagementId={setEngagementId}
151
151
  setIsActive={setIsActive}
152
152
  setValidateOnChain={setValidateOnChain}
153
- setType={(v) => setType(v as typeof type)}
154
- setStatus={(v) => setStatus(v as typeof status)}
153
+ setType={setType}
154
+ setStatus={setStatus}
155
155
  setMinAmount={setMinAmount}
156
156
  setMaxAmount={setMaxAmount}
157
157
  setDateRange={setDateRange}
158
- setRole={(v) => setRole(v as Role)}
158
+ setRole={setRoleStable}
159
159
  onClearFilters={onClearFilters}
160
- onRefresh={() => refetch()}
160
+ onRefresh={handleRefresh}
161
161
  isRefreshing={isFetching}
162
162
  orderBy={orderBy}
163
163
  orderDirection={orderDirection}
164
- setOrderBy={(v) => setOrderBy(v)}
165
- setOrderDirection={(v) => setOrderDirection(v)}
164
+ setOrderBy={setOrderBy}
165
+ setOrderDirection={setOrderDirection}
166
166
  />
167
167
 
168
168
  <div className="w-full py-2 sm:py-4">
@@ -346,9 +346,9 @@ export function EscrowsByRoleCards() {
346
346
  <ul className="list-disc list-inside flex flex-col gap-1">
347
347
  {escrow.milestones
348
348
  .slice(0, 3)
349
- .map((milestone) => (
349
+ .map((milestone, index) => (
350
350
  <li
351
- key={`milestone-${milestone.description}-${milestone.status}`}
351
+ key={`milestone-${milestone.description}-${milestone.status}-${index}`}
352
352
  className="text-xs flex justify-between"
353
353
  >
354
354
  {milestone.description}
@@ -364,33 +364,83 @@ export function EscrowsByRoleCards() {
364
364
  )}
365
365
  </span>
366
366
 
367
- <span
368
- className={`bg-red-800 rounded-full h-2 w-2 ml-1 ${
369
- milestone.flags?.disputed
370
- ? "block"
371
- : "hidden"
372
- }`}
373
- />
374
-
375
- <span
376
- className={`bg-green-800 rounded-full h-2 w-2 ml-1 ${
377
- milestone.flags?.resolved ||
378
- milestone.flags?.released
379
- ? "block"
380
- : "hidden"
381
- }`}
382
- />
383
-
384
- <span
385
- className={`bg-yellow-800 rounded-full h-2 w-2 ml-1 ${
386
- milestone.flags?.approved &&
387
- !milestone.flags?.disputed &&
388
- !milestone.flags?.resolved &&
389
- !milestone.flags?.released
390
- ? "block"
391
- : "hidden"
392
- }`}
393
- />
367
+ {milestone.flags?.disputed && (
368
+ <Tooltip>
369
+ <TooltipTrigger>
370
+ <span
371
+ className={`bg-red-800 rounded-full h-2 w-2 ml-1 ${
372
+ milestone.flags?.disputed
373
+ ? "block"
374
+ : "hidden"
375
+ }`}
376
+ />
377
+ </TooltipTrigger>
378
+ <TooltipContent>
379
+ Disputed
380
+ </TooltipContent>
381
+ </Tooltip>
382
+ )}
383
+
384
+ {milestone.flags?.resolved && (
385
+ <Tooltip>
386
+ <TooltipTrigger>
387
+ <span
388
+ className={`bg-green-800 rounded-full h-2 w-2 ml-1 ${
389
+ milestone.flags?.resolved
390
+ ? "block"
391
+ : "hidden"
392
+ }`}
393
+ />
394
+ </TooltipTrigger>
395
+ <TooltipContent>
396
+ Resolved
397
+ </TooltipContent>
398
+ </Tooltip>
399
+ )}
400
+
401
+ {milestone.flags?.released && (
402
+ <Tooltip>
403
+ <TooltipTrigger>
404
+ <span
405
+ className={`bg-green-800 rounded-full h-2 w-2 ml-1 ${
406
+ milestone.flags?.released
407
+ ? "block"
408
+ : "hidden"
409
+ }`}
410
+ />
411
+ </TooltipTrigger>
412
+ <TooltipContent>
413
+ Released
414
+ </TooltipContent>
415
+ </Tooltip>
416
+ )}
417
+
418
+ {milestone.flags?.approved &&
419
+ !milestone.flags?.disputed &&
420
+ !milestone.flags?.resolved &&
421
+ !milestone.flags?.released && (
422
+ <Tooltip>
423
+ <TooltipTrigger>
424
+ <span
425
+ className={`bg-yellow-600 rounded-full h-2 w-2 ml-1 ${
426
+ milestone.flags
427
+ ?.approved &&
428
+ !milestone.flags
429
+ ?.disputed &&
430
+ !milestone.flags
431
+ ?.resolved &&
432
+ !milestone.flags
433
+ ?.released
434
+ ? "block"
435
+ : "hidden"
436
+ }`}
437
+ />
438
+ </TooltipTrigger>
439
+ <TooltipContent>
440
+ Pending Release
441
+ </TooltipContent>
442
+ </Tooltip>
443
+ )}
394
444
  </div>
395
445
  </>
396
446
  )}
@@ -502,14 +552,15 @@ export function EscrowsByRoleCards() {
502
552
  </div>
503
553
 
504
554
  {/* Dialog */}
505
- <EscrowDetailDialog
506
- activeRole={activeRole}
507
- isDialogOpen={dialogStates.second.isOpen}
508
- setIsDialogOpen={dialogStates.second.setIsOpen}
509
- setSelectedEscrow={setSelectedEscrow}
510
- />
555
+ {dialogStates.second.isOpen ? (
556
+ <EscrowDetailDialog
557
+ isDialogOpen={dialogStates.second.isOpen}
558
+ setIsDialogOpen={dialogStates.second.setIsOpen}
559
+ setSelectedEscrow={setSelectedEscrow}
560
+ />
561
+ ) : null}
511
562
  </>
512
563
  );
513
- }
564
+ };
514
565
 
515
- export default EscrowsByRoleCards;
566
+ export default React.memo(EscrowsByRoleCards);
@@ -84,7 +84,7 @@ type FiltersProps = {
84
84
  setOrderDirection: (v: "asc" | "desc") => void;
85
85
  };
86
86
 
87
- function Filters({
87
+ export const Filters = ({
88
88
  title,
89
89
  engagementId,
90
90
  isActive,
@@ -113,7 +113,7 @@ function Filters({
113
113
  onRefresh,
114
114
  setOrderBy,
115
115
  setOrderDirection,
116
- }: FiltersProps) {
116
+ }: FiltersProps) => {
117
117
  return (
118
118
  <div className="w-full bg-card/50 backdrop-blur-sm border border-border/50 rounded-lg p-4 shadow-sm">
119
119
  {/* Header Section */}
@@ -416,6 +416,4 @@ function Filters({
416
416
  </div>
417
417
  </div>
418
418
  );
419
- }
420
-
421
- export default React.memo(Filters);
419
+ };