@trustless-work/blocks 0.0.1
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.
- package/README.md +96 -0
- package/bin/index.js +1123 -0
- package/package.json +44 -0
- package/templates/deps.json +29 -0
- package/templates/escrows/details/Actions.tsx +149 -0
- package/templates/escrows/details/Entities.tsx +48 -0
- package/templates/escrows/details/EntityCard.tsx +98 -0
- package/templates/escrows/details/EscrowDetailDialog.tsx +154 -0
- package/templates/escrows/details/GeneralInformation.tsx +329 -0
- package/templates/escrows/details/MilestoneCard.tsx +254 -0
- package/templates/escrows/details/MilestoneDetailDialog.tsx +276 -0
- package/templates/escrows/details/Milestones.tsx +87 -0
- package/templates/escrows/details/ProgressEscrow.tsx +191 -0
- package/templates/escrows/details/StatisticsCard.tsx +79 -0
- package/templates/escrows/details/SuccessReleaseDialog.tsx +101 -0
- package/templates/escrows/details/useDetailsEscrow.ts +126 -0
- package/templates/escrows/escrow-context/EscrowAmountProvider.tsx +86 -0
- package/templates/escrows/escrow-context/EscrowDialogsProvider.tsx +108 -0
- package/templates/escrows/escrow-context/EscrowProvider.tsx +124 -0
- package/templates/escrows/escrows-by-role/cards/EscrowsCards.tsx +503 -0
- package/templates/escrows/escrows-by-role/cards/Filters.tsx +421 -0
- package/templates/escrows/escrows-by-role/table/EscrowsTable.tsx +427 -0
- package/templates/escrows/escrows-by-role/table/Filters.tsx +421 -0
- package/templates/escrows/escrows-by-role/useEscrowsByRole.shared.ts +336 -0
- package/templates/escrows/escrows-by-signer/cards/EscrowsCards.tsx +502 -0
- package/templates/escrows/escrows-by-signer/cards/Filters.tsx +389 -0
- package/templates/escrows/escrows-by-signer/table/EscrowsTable.tsx +422 -0
- package/templates/escrows/escrows-by-signer/table/Filters.tsx +389 -0
- package/templates/escrows/escrows-by-signer/useEscrowsBySigner.shared.ts +320 -0
- package/templates/escrows/single-release/approve-milestone/button/ApproveMilestone.tsx +78 -0
- package/templates/escrows/single-release/approve-milestone/dialog/ApproveMilestone.tsx +102 -0
- package/templates/escrows/single-release/approve-milestone/form/ApproveMilestone.tsx +80 -0
- package/templates/escrows/single-release/approve-milestone/shared/schema.ts +9 -0
- package/templates/escrows/single-release/approve-milestone/shared/useApproveMilestone.ts +67 -0
- package/templates/escrows/single-release/change-milestone-status/button/ChangeMilestoneStatus.tsx +78 -0
- package/templates/escrows/single-release/change-milestone-status/dialog/ChangeMilestoneStatus.tsx +167 -0
- package/templates/escrows/single-release/change-milestone-status/form/ChangeMilestoneStatus.tsx +114 -0
- package/templates/escrows/single-release/change-milestone-status/shared/schema.ts +15 -0
- package/templates/escrows/single-release/change-milestone-status/shared/useChangeMilestoneStatus.ts +77 -0
- package/templates/escrows/single-release/dispute-escrow/button/DisputeEscrow.tsx +68 -0
- package/templates/escrows/single-release/fund-escrow/button/FundEscrow.tsx +84 -0
- package/templates/escrows/single-release/fund-escrow/dialog/FundEscrow.tsx +77 -0
- package/templates/escrows/single-release/fund-escrow/form/FundEscrow.tsx +54 -0
- package/templates/escrows/single-release/fund-escrow/shared/schema.ts +10 -0
- package/templates/escrows/single-release/fund-escrow/shared/useFundEscrow.ts +66 -0
- package/templates/escrows/single-release/initialize-escrow/dialog/InitializeEscrow.tsx +526 -0
- package/templates/escrows/single-release/initialize-escrow/form/InitializeEscrow.tsx +504 -0
- package/templates/escrows/single-release/initialize-escrow/shared/schema.ts +232 -0
- package/templates/escrows/single-release/initialize-escrow/shared/useInitializeEscrow.ts +115 -0
- package/templates/escrows/single-release/release-escrow/button/ReleaseEscrow.tsx +80 -0
- package/templates/escrows/single-release/resolve-dispute/button/ResolveDispute.tsx +94 -0
- package/templates/escrows/single-release/resolve-dispute/dialog/ResolveDispute.tsx +123 -0
- package/templates/escrows/single-release/resolve-dispute/form/ResolveDispute.tsx +82 -0
- package/templates/escrows/single-release/resolve-dispute/shared/schema.ts +82 -0
- package/templates/escrows/single-release/resolve-dispute/shared/useResolveDispute.ts +58 -0
- package/templates/escrows/single-release/update-escrow/dialog/UpdateEscrow.tsx +485 -0
- package/templates/escrows/single-release/update-escrow/form/UpdateEscrow.tsx +463 -0
- package/templates/escrows/single-release/update-escrow/shared/schema.ts +139 -0
- package/templates/escrows/single-release/update-escrow/shared/useUpdateEscrow.ts +211 -0
- package/templates/handle-errors/errors.enum.ts +6 -0
- package/templates/handle-errors/handle.ts +47 -0
- package/templates/helpers/format.helper.ts +27 -0
- package/templates/helpers/useCopy.ts +13 -0
- package/templates/providers/ReactQueryClientProvider.tsx +28 -0
- package/templates/providers/TrustlessWork.tsx +30 -0
- package/templates/tanstak/useEscrowsByRoleQuery.ts +87 -0
- package/templates/tanstak/useEscrowsBySignerQuery.ts +78 -0
- package/templates/tanstak/useEscrowsMutations.ts +411 -0
- package/templates/wallet-kit/WalletButtons.tsx +116 -0
- package/templates/wallet-kit/WalletProvider.tsx +94 -0
- package/templates/wallet-kit/trustlines.ts +40 -0
- package/templates/wallet-kit/useWallet.ts +77 -0
- package/templates/wallet-kit/validators.ts +12 -0
- package/templates/wallet-kit/wallet-kit.ts +30 -0
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import React, { useMemo } from "react";
|
|
4
|
+
import { Card } from "__UI_BASE__/card";
|
|
5
|
+
import { cn } from "@/lib/utils";
|
|
6
|
+
import { MultiReleaseMilestone } from "@trustless-work/escrow";
|
|
7
|
+
import {
|
|
8
|
+
Ban,
|
|
9
|
+
CircleCheckBig,
|
|
10
|
+
CircleDollarSign,
|
|
11
|
+
Handshake,
|
|
12
|
+
Wallet,
|
|
13
|
+
Info,
|
|
14
|
+
Users,
|
|
15
|
+
Check,
|
|
16
|
+
Copy,
|
|
17
|
+
} from "lucide-react";
|
|
18
|
+
import { Actions, roleActions } from "./Actions";
|
|
19
|
+
import type {
|
|
20
|
+
DialogStates,
|
|
21
|
+
StatusStates,
|
|
22
|
+
} from "../../escrow-context/EscrowDialogsProvider";
|
|
23
|
+
import {
|
|
24
|
+
GetEscrowsFromIndexerResponse,
|
|
25
|
+
Role,
|
|
26
|
+
} from "@trustless-work/escrow/types";
|
|
27
|
+
import { useEscrowAmountContext } from "../../escrow-context/EscrowAmountProvider";
|
|
28
|
+
import { StatisticsCard } from "./StatisticsCard";
|
|
29
|
+
import {
|
|
30
|
+
formatAddress,
|
|
31
|
+
formatCurrency,
|
|
32
|
+
} from "@/components/tw-blocks/helpers/format.helper";
|
|
33
|
+
import { useCopy } from "@/components/tw-blocks/helpers/useCopy";
|
|
34
|
+
|
|
35
|
+
interface GeneralInformationProps {
|
|
36
|
+
selectedEscrow: GetEscrowsFromIndexerResponse;
|
|
37
|
+
userRolesInEscrow: string[];
|
|
38
|
+
dialogStates: DialogStates & StatusStates;
|
|
39
|
+
areAllMilestonesApproved: boolean;
|
|
40
|
+
activeRole: Role[];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export const GeneralInformation = ({
|
|
44
|
+
selectedEscrow,
|
|
45
|
+
userRolesInEscrow,
|
|
46
|
+
dialogStates,
|
|
47
|
+
areAllMilestonesApproved,
|
|
48
|
+
activeRole,
|
|
49
|
+
}: GeneralInformationProps) => {
|
|
50
|
+
const { trustlessWorkAmount, receiverAmount, platformFeeAmount } =
|
|
51
|
+
useEscrowAmountContext();
|
|
52
|
+
const { copiedKeyId, copyToClipboard } = useCopy();
|
|
53
|
+
|
|
54
|
+
const totalAmount = useMemo(() => {
|
|
55
|
+
if (!selectedEscrow) return 0;
|
|
56
|
+
const milestones = selectedEscrow.milestones as MultiReleaseMilestone[];
|
|
57
|
+
if (selectedEscrow?.type === "single-release") {
|
|
58
|
+
return selectedEscrow.amount;
|
|
59
|
+
} else {
|
|
60
|
+
return milestones.reduce(
|
|
61
|
+
(acc, milestone) => acc + Number(milestone.amount),
|
|
62
|
+
0
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
}, [selectedEscrow]);
|
|
66
|
+
|
|
67
|
+
return (
|
|
68
|
+
<div className="space-y-6 h-full">
|
|
69
|
+
<div className="flex flex-col md:flex-row gap-4">
|
|
70
|
+
<div className="flex flex-col md:flex-row w-full mdw-4/5 gap-4">
|
|
71
|
+
{selectedEscrow.flags?.disputed && (
|
|
72
|
+
<StatisticsCard
|
|
73
|
+
title="Status"
|
|
74
|
+
icon={Ban}
|
|
75
|
+
iconColor="text-destructive"
|
|
76
|
+
value="In Dispute"
|
|
77
|
+
/>
|
|
78
|
+
)}
|
|
79
|
+
|
|
80
|
+
{selectedEscrow.flags?.released && (
|
|
81
|
+
<StatisticsCard
|
|
82
|
+
title="Status"
|
|
83
|
+
icon={CircleCheckBig}
|
|
84
|
+
iconColor="text-green-800"
|
|
85
|
+
value="Released"
|
|
86
|
+
actionLabel="See Details"
|
|
87
|
+
onAction={() => dialogStates.successRelease.setIsOpen(true)}
|
|
88
|
+
/>
|
|
89
|
+
)}
|
|
90
|
+
|
|
91
|
+
{selectedEscrow.flags?.resolved && (
|
|
92
|
+
<StatisticsCard
|
|
93
|
+
title="Status"
|
|
94
|
+
icon={Handshake}
|
|
95
|
+
iconColor="text-green-800"
|
|
96
|
+
value="Resolved"
|
|
97
|
+
/>
|
|
98
|
+
)}
|
|
99
|
+
|
|
100
|
+
<StatisticsCard
|
|
101
|
+
title="Amount"
|
|
102
|
+
icon={CircleDollarSign}
|
|
103
|
+
value={formatCurrency(totalAmount, selectedEscrow.trustline?.name)}
|
|
104
|
+
/>
|
|
105
|
+
|
|
106
|
+
<StatisticsCard
|
|
107
|
+
title="Balance"
|
|
108
|
+
icon={Wallet}
|
|
109
|
+
value={formatCurrency(
|
|
110
|
+
selectedEscrow.balance ?? 0,
|
|
111
|
+
selectedEscrow.trustline?.name
|
|
112
|
+
)}
|
|
113
|
+
/>
|
|
114
|
+
</div>
|
|
115
|
+
<div className="flex w-full md:w-1/5">
|
|
116
|
+
<Actions
|
|
117
|
+
selectedEscrow={selectedEscrow}
|
|
118
|
+
userRolesInEscrow={userRolesInEscrow}
|
|
119
|
+
areAllMilestonesApproved={areAllMilestonesApproved}
|
|
120
|
+
activeRole={activeRole}
|
|
121
|
+
/>
|
|
122
|
+
</div>
|
|
123
|
+
</div>
|
|
124
|
+
<div
|
|
125
|
+
className={cn(
|
|
126
|
+
"grid gap-6 h-full",
|
|
127
|
+
!selectedEscrow.flags?.released && !selectedEscrow.flags?.resolved
|
|
128
|
+
? selectedEscrow?.type === "multi-release"
|
|
129
|
+
? "grid-cols-1 md:grid-cols-1 mx-auto"
|
|
130
|
+
: "grid-cols-1 lg:grid-cols-4"
|
|
131
|
+
: "grid-cols-1 md:grid-cols-1 w-full mx-auto"
|
|
132
|
+
)}
|
|
133
|
+
>
|
|
134
|
+
<div className="lg:col-span-3">
|
|
135
|
+
<Card className="px-6 py-4 h-full">
|
|
136
|
+
<h3 className="text-lg font-semibold">Basic Information</h3>
|
|
137
|
+
<div className="grid gap-4">
|
|
138
|
+
<div className="p-4 bg-muted/50 rounded-lg border">
|
|
139
|
+
<div className="flex items-center gap-3">
|
|
140
|
+
<Info className="h-5 w-5 text-primary flex-shrink-0" />
|
|
141
|
+
<div className="flex-1 min-w-0">
|
|
142
|
+
<div className="flex items-center justify-between mb-2">
|
|
143
|
+
<span className="text-sm font-medium text-muted-foreground">
|
|
144
|
+
{selectedEscrow.trustline?.name || "No Trustline"} |
|
|
145
|
+
Escrow ID
|
|
146
|
+
</span>
|
|
147
|
+
<button
|
|
148
|
+
onClick={() =>
|
|
149
|
+
copyToClipboard(selectedEscrow?.contractId || "")
|
|
150
|
+
}
|
|
151
|
+
className="p-1.5 hover:bg-muted rounded-md transition-colors flex-shrink-0"
|
|
152
|
+
>
|
|
153
|
+
{copiedKeyId ? (
|
|
154
|
+
<Check className="h-4 w-4 text-green-700" />
|
|
155
|
+
) : (
|
|
156
|
+
<Copy className="h-4 w-4" />
|
|
157
|
+
)}
|
|
158
|
+
</button>
|
|
159
|
+
</div>
|
|
160
|
+
<span className="font-mono text-sm break-all text-foreground">
|
|
161
|
+
{formatAddress(selectedEscrow.contractId || "")}
|
|
162
|
+
</span>
|
|
163
|
+
</div>
|
|
164
|
+
</div>
|
|
165
|
+
</div>
|
|
166
|
+
|
|
167
|
+
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
168
|
+
<div className="p-4 bg-muted/50 rounded-lg border">
|
|
169
|
+
<div className="flex items-center gap-3 mb-3">
|
|
170
|
+
<Users className="h-5 w-5 text-primary flex-shrink-0" />
|
|
171
|
+
<span className="text-sm font-medium text-muted-foreground">
|
|
172
|
+
Roles
|
|
173
|
+
</span>
|
|
174
|
+
</div>
|
|
175
|
+
<div className="flex flex-wrap gap-2">
|
|
176
|
+
{userRolesInEscrow.map((role) => {
|
|
177
|
+
const roleData = roleActions.find((r) => r.role === role);
|
|
178
|
+
return (
|
|
179
|
+
<div
|
|
180
|
+
key={role}
|
|
181
|
+
className="p-2 bg-primary/10 rounded-md hover:bg-primary/20 transition-colors"
|
|
182
|
+
>
|
|
183
|
+
{roleData?.icon || (
|
|
184
|
+
<Users className="h-4 w-4 text-primary" />
|
|
185
|
+
)}
|
|
186
|
+
</div>
|
|
187
|
+
);
|
|
188
|
+
})}
|
|
189
|
+
</div>
|
|
190
|
+
</div>
|
|
191
|
+
|
|
192
|
+
<div className="p-4 bg-muted/50 rounded-lg border">
|
|
193
|
+
<div className="flex items-center gap-3 mb-2">
|
|
194
|
+
<Info className="h-5 w-5 text-primary flex-shrink-0" />
|
|
195
|
+
<span className="text-sm font-medium text-muted-foreground">
|
|
196
|
+
Memo
|
|
197
|
+
</span>
|
|
198
|
+
</div>
|
|
199
|
+
<span className="font-medium text-foreground">
|
|
200
|
+
{selectedEscrow?.receiverMemo || "No Memo"}
|
|
201
|
+
</span>
|
|
202
|
+
</div>
|
|
203
|
+
</div>
|
|
204
|
+
|
|
205
|
+
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
206
|
+
<div className="p-4 bg-muted/50 rounded-lg border">
|
|
207
|
+
<div className="flex items-center gap-3 mb-2">
|
|
208
|
+
<CircleDollarSign className="h-5 w-5 text-primary flex-shrink-0" />
|
|
209
|
+
<span className="text-sm font-medium text-muted-foreground">
|
|
210
|
+
Engagement ID
|
|
211
|
+
</span>
|
|
212
|
+
</div>
|
|
213
|
+
<span className="font-medium text-foreground">
|
|
214
|
+
{selectedEscrow?.engagementId || "No Engagement"}
|
|
215
|
+
</span>
|
|
216
|
+
</div>
|
|
217
|
+
|
|
218
|
+
<div className="p-4 bg-muted/50 rounded-lg border">
|
|
219
|
+
<div className="flex items-center gap-3 mb-2">
|
|
220
|
+
<CircleDollarSign className="h-5 w-5 text-primary flex-shrink-0" />
|
|
221
|
+
<span className="text-sm font-medium text-muted-foreground">
|
|
222
|
+
Type
|
|
223
|
+
</span>
|
|
224
|
+
</div>
|
|
225
|
+
<span className="font-medium text-foreground">
|
|
226
|
+
{selectedEscrow?.type === "multi-release"
|
|
227
|
+
? "Multi Release"
|
|
228
|
+
: "Single Release"}
|
|
229
|
+
</span>
|
|
230
|
+
</div>
|
|
231
|
+
</div>
|
|
232
|
+
</div>
|
|
233
|
+
</Card>
|
|
234
|
+
</div>
|
|
235
|
+
|
|
236
|
+
{selectedEscrow?.type !== "multi-release" &&
|
|
237
|
+
!selectedEscrow.flags?.released &&
|
|
238
|
+
!selectedEscrow.flags?.resolved && (
|
|
239
|
+
<div className="lg:col-span-1">
|
|
240
|
+
<Card className="p-4 h-full">
|
|
241
|
+
<h3 className="text-lg font-semibold">
|
|
242
|
+
Release Amount Distribution
|
|
243
|
+
</h3>
|
|
244
|
+
<div className="space-y-4">
|
|
245
|
+
<div className="flex items-center justify-between p-3 bg-muted/50 rounded-lg border">
|
|
246
|
+
<div className="flex items-center gap-2">
|
|
247
|
+
<CircleDollarSign className="h-4 w-4 text-primary" />
|
|
248
|
+
<div className="flex flex-col">
|
|
249
|
+
<span className="text-sm text-muted-foreground">
|
|
250
|
+
Total Amount
|
|
251
|
+
</span>
|
|
252
|
+
<span className="font-medium">
|
|
253
|
+
{formatCurrency(
|
|
254
|
+
selectedEscrow.amount,
|
|
255
|
+
selectedEscrow.trustline?.name
|
|
256
|
+
)}
|
|
257
|
+
</span>
|
|
258
|
+
</div>
|
|
259
|
+
</div>
|
|
260
|
+
<div className="text-sm text-muted-foreground">100%</div>
|
|
261
|
+
</div>
|
|
262
|
+
|
|
263
|
+
<div className="space-y-2">
|
|
264
|
+
<div className="flex items-center justify-between p-3 bg-muted/50 rounded-lg border">
|
|
265
|
+
<div className="flex items-center gap-2">
|
|
266
|
+
<Users className="h-4 w-4 text-primary" />
|
|
267
|
+
<div className="flex flex-col">
|
|
268
|
+
<span className="text-sm text-muted-foreground">
|
|
269
|
+
Receiver
|
|
270
|
+
</span>
|
|
271
|
+
<span className="font-medium">
|
|
272
|
+
{formatCurrency(
|
|
273
|
+
Number(receiverAmount.toFixed(2)),
|
|
274
|
+
selectedEscrow.trustline?.name
|
|
275
|
+
)}
|
|
276
|
+
</span>
|
|
277
|
+
</div>
|
|
278
|
+
</div>
|
|
279
|
+
<div className="text-sm text-muted-foreground">
|
|
280
|
+
{100 - selectedEscrow.platformFee - 0.3}%
|
|
281
|
+
</div>
|
|
282
|
+
</div>
|
|
283
|
+
|
|
284
|
+
<div className="flex items-center justify-between p-3 bg-muted/50 rounded-lg border">
|
|
285
|
+
<div className="flex items-center gap-2">
|
|
286
|
+
<Wallet className="h-4 w-4 text-primary" />
|
|
287
|
+
<div className="flex flex-col">
|
|
288
|
+
<span className="text-sm text-muted-foreground">
|
|
289
|
+
Platform Fee
|
|
290
|
+
</span>
|
|
291
|
+
<span className="font-medium">
|
|
292
|
+
{formatCurrency(
|
|
293
|
+
Number(platformFeeAmount.toFixed(2)),
|
|
294
|
+
selectedEscrow.trustline?.name
|
|
295
|
+
)}
|
|
296
|
+
</span>
|
|
297
|
+
</div>
|
|
298
|
+
</div>
|
|
299
|
+
<div className="text-sm text-muted-foreground">
|
|
300
|
+
{selectedEscrow.platformFee}%
|
|
301
|
+
</div>
|
|
302
|
+
</div>
|
|
303
|
+
</div>
|
|
304
|
+
|
|
305
|
+
<div className="flex items-center justify-between p-3 bg-muted/50 rounded-lg border">
|
|
306
|
+
<div className="flex items-center gap-2">
|
|
307
|
+
<Wallet className="h-4 w-4 text-primary" />
|
|
308
|
+
<div className="flex flex-col">
|
|
309
|
+
<span className="text-sm text-muted-foreground">
|
|
310
|
+
Trustless Work
|
|
311
|
+
</span>
|
|
312
|
+
<span className="font-medium">
|
|
313
|
+
{formatCurrency(
|
|
314
|
+
Number(trustlessWorkAmount.toFixed(2)),
|
|
315
|
+
selectedEscrow.trustline?.name
|
|
316
|
+
)}
|
|
317
|
+
</span>
|
|
318
|
+
</div>
|
|
319
|
+
</div>
|
|
320
|
+
<div className="text-sm text-muted-foreground">0.3%</div>
|
|
321
|
+
</div>
|
|
322
|
+
</div>
|
|
323
|
+
</Card>
|
|
324
|
+
</div>
|
|
325
|
+
)}
|
|
326
|
+
</div>
|
|
327
|
+
</div>
|
|
328
|
+
);
|
|
329
|
+
};
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import React from "react";
|
|
3
|
+
import { Button } from "__UI_BASE__/button";
|
|
4
|
+
import { Card, CardContent, CardHeader, CardTitle } from "__UI_BASE__/card";
|
|
5
|
+
import {
|
|
6
|
+
FileCheck2,
|
|
7
|
+
Eye,
|
|
8
|
+
CircleAlert,
|
|
9
|
+
CircleCheckBig,
|
|
10
|
+
Handshake,
|
|
11
|
+
CheckCheck,
|
|
12
|
+
Layers,
|
|
13
|
+
} from "lucide-react";
|
|
14
|
+
import {
|
|
15
|
+
GetEscrowsFromIndexerResponse as Escrow,
|
|
16
|
+
Role,
|
|
17
|
+
} from "@trustless-work/escrow/types";
|
|
18
|
+
import {
|
|
19
|
+
MultiReleaseMilestone,
|
|
20
|
+
SingleReleaseMilestone,
|
|
21
|
+
} from "@trustless-work/escrow";
|
|
22
|
+
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";
|
|
28
|
+
import { formatCurrency } from "@/components/tw-blocks/helpers/format.helper";
|
|
29
|
+
|
|
30
|
+
interface MilestoneCardProps {
|
|
31
|
+
milestone: SingleReleaseMilestone | MultiReleaseMilestone;
|
|
32
|
+
milestoneIndex: number;
|
|
33
|
+
selectedEscrow: Escrow;
|
|
34
|
+
userRolesInEscrow: string[];
|
|
35
|
+
activeRole: Role[];
|
|
36
|
+
onViewDetails: (
|
|
37
|
+
milestone: SingleReleaseMilestone | MultiReleaseMilestone,
|
|
38
|
+
index: number
|
|
39
|
+
) => void;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const MilestoneCardComponent = ({
|
|
43
|
+
milestone,
|
|
44
|
+
milestoneIndex,
|
|
45
|
+
selectedEscrow,
|
|
46
|
+
userRolesInEscrow,
|
|
47
|
+
activeRole,
|
|
48
|
+
onViewDetails,
|
|
49
|
+
}: MilestoneCardProps) => {
|
|
50
|
+
const getMilestoneStatusBadge = (
|
|
51
|
+
milestone: SingleReleaseMilestone | MultiReleaseMilestone
|
|
52
|
+
) => {
|
|
53
|
+
if ("flags" in milestone && milestone.flags?.disputed) {
|
|
54
|
+
return (
|
|
55
|
+
<Badge variant="destructive">
|
|
56
|
+
<CircleAlert className="h-3.5 w-3.5" />
|
|
57
|
+
<span>Disputed</span>
|
|
58
|
+
</Badge>
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
if ("flags" in milestone && milestone.flags?.released) {
|
|
62
|
+
return (
|
|
63
|
+
<Badge variant="default">
|
|
64
|
+
<CircleCheckBig className="h-3.5 w-3.5" />
|
|
65
|
+
<span>Released</span>
|
|
66
|
+
</Badge>
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
if (
|
|
70
|
+
"flags" in milestone &&
|
|
71
|
+
milestone.flags?.resolved &&
|
|
72
|
+
!milestone.flags?.disputed
|
|
73
|
+
) {
|
|
74
|
+
return (
|
|
75
|
+
<Badge variant="default">
|
|
76
|
+
<Handshake className="h-3.5 w-3.5" />
|
|
77
|
+
<span>Resolved</span>
|
|
78
|
+
</Badge>
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
if (
|
|
82
|
+
("flags" in milestone && milestone.flags?.approved) ||
|
|
83
|
+
("approved" in milestone && milestone.approved)
|
|
84
|
+
) {
|
|
85
|
+
return (
|
|
86
|
+
<Badge variant="default">
|
|
87
|
+
<CheckCheck className="h-3.5 w-3.5" />
|
|
88
|
+
<span>Approved</span>
|
|
89
|
+
</Badge>
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
return (
|
|
93
|
+
<Badge variant="outline">
|
|
94
|
+
<Layers className="h-3.5 w-3.5" />
|
|
95
|
+
<span className="uppercase">
|
|
96
|
+
{milestone.status
|
|
97
|
+
? milestone.status.match(/[a-z][A-Z]/)
|
|
98
|
+
? milestone.status.replace(/([A-Z])/g, " $1").toLowerCase()
|
|
99
|
+
: milestone.status.toLowerCase()
|
|
100
|
+
: ""}
|
|
101
|
+
</span>
|
|
102
|
+
</Badge>
|
|
103
|
+
);
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
const getActionButtons = (
|
|
107
|
+
milestone: SingleReleaseMilestone | MultiReleaseMilestone,
|
|
108
|
+
milestoneIndex: number,
|
|
109
|
+
userRolesInEscrow: string[],
|
|
110
|
+
activeRole: Role[]
|
|
111
|
+
) => {
|
|
112
|
+
const buttons = [] as React.ReactNode[];
|
|
113
|
+
if (
|
|
114
|
+
userRolesInEscrow.includes("serviceProvider") &&
|
|
115
|
+
activeRole.includes("serviceProvider") &&
|
|
116
|
+
milestone.status !== "completed" &&
|
|
117
|
+
!("flags" in milestone && milestone.flags?.approved)
|
|
118
|
+
) {
|
|
119
|
+
buttons.push(
|
|
120
|
+
<ChangeMilestoneStatusDialog
|
|
121
|
+
key={`change-status-${milestoneIndex}`}
|
|
122
|
+
milestoneIndex={milestoneIndex}
|
|
123
|
+
/>
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (
|
|
128
|
+
userRolesInEscrow.includes("releaseSigner") &&
|
|
129
|
+
activeRole.includes("releaseSigner") &&
|
|
130
|
+
"flags" in milestone &&
|
|
131
|
+
!milestone.flags?.disputed &&
|
|
132
|
+
milestone.flags?.approved &&
|
|
133
|
+
!milestone.flags?.released
|
|
134
|
+
) {
|
|
135
|
+
buttons.push(<ReleaseEscrowButton key={`release-${milestoneIndex}`} />);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (
|
|
139
|
+
(userRolesInEscrow.includes("serviceProvider") ||
|
|
140
|
+
userRolesInEscrow.includes("approver")) &&
|
|
141
|
+
(activeRole.includes("serviceProvider") ||
|
|
142
|
+
activeRole.includes("approver")) &&
|
|
143
|
+
"flags" in milestone &&
|
|
144
|
+
!milestone.flags?.disputed &&
|
|
145
|
+
!milestone.flags?.released &&
|
|
146
|
+
!milestone.flags?.resolved
|
|
147
|
+
) {
|
|
148
|
+
buttons.push(<DisputeEscrowButton key={`dispute-${milestoneIndex}`} />);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (
|
|
152
|
+
userRolesInEscrow.includes("disputeResolver") &&
|
|
153
|
+
activeRole.includes("disputeResolver") &&
|
|
154
|
+
"flags" in milestone &&
|
|
155
|
+
milestone.flags?.disputed
|
|
156
|
+
) {
|
|
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
|
+
);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return buttons;
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
return (
|
|
182
|
+
<Card
|
|
183
|
+
key={`milestone-${milestoneIndex}-${milestone.description}-${milestone.status}`}
|
|
184
|
+
className="hover:shadow-lg transition-all duration-200"
|
|
185
|
+
>
|
|
186
|
+
<CardHeader className="pb-4">
|
|
187
|
+
<div className="flex items-center justify-between">
|
|
188
|
+
<CardTitle className="text-base font-semibold text-foreground truncate">
|
|
189
|
+
{milestone.description}
|
|
190
|
+
</CardTitle>
|
|
191
|
+
{getMilestoneStatusBadge(milestone)}
|
|
192
|
+
</div>
|
|
193
|
+
</CardHeader>
|
|
194
|
+
|
|
195
|
+
<CardContent className="pt-0 space-y-4">
|
|
196
|
+
{"amount" in milestone && (
|
|
197
|
+
<div className="flex items-center gap-2 py-2">
|
|
198
|
+
<span className="text-2xl font-bold text-foreground">
|
|
199
|
+
{formatCurrency(milestone.amount, selectedEscrow.trustline?.name)}
|
|
200
|
+
</span>
|
|
201
|
+
</div>
|
|
202
|
+
)}
|
|
203
|
+
|
|
204
|
+
{milestone.evidence && (
|
|
205
|
+
<div className="flex items-center gap-2 p-2 border-primary/20 rounded-lg">
|
|
206
|
+
<FileCheck2 className="w-4 h-4 text-primary flex-shrink-0" />
|
|
207
|
+
<span className="text-xs text-muted-foreground font-medium">
|
|
208
|
+
Evidence provided
|
|
209
|
+
</span>
|
|
210
|
+
</div>
|
|
211
|
+
)}
|
|
212
|
+
|
|
213
|
+
{getActionButtons(
|
|
214
|
+
milestone,
|
|
215
|
+
milestoneIndex,
|
|
216
|
+
userRolesInEscrow,
|
|
217
|
+
activeRole
|
|
218
|
+
)}
|
|
219
|
+
|
|
220
|
+
<Button
|
|
221
|
+
size="sm"
|
|
222
|
+
variant="outline"
|
|
223
|
+
className="w-full border-border text-muted-foreground"
|
|
224
|
+
onClick={(e) => {
|
|
225
|
+
e.stopPropagation();
|
|
226
|
+
onViewDetails(milestone, milestoneIndex);
|
|
227
|
+
}}
|
|
228
|
+
>
|
|
229
|
+
<Eye className="w-3 h-3 mr-2 flex-shrink-0" />
|
|
230
|
+
View Details
|
|
231
|
+
</Button>
|
|
232
|
+
</CardContent>
|
|
233
|
+
</Card>
|
|
234
|
+
);
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
export const MilestoneCard = React.memo(
|
|
238
|
+
MilestoneCardComponent,
|
|
239
|
+
(prev, next) => {
|
|
240
|
+
if (
|
|
241
|
+
prev.milestone === next.milestone &&
|
|
242
|
+
prev.milestoneIndex === next.milestoneIndex &&
|
|
243
|
+
prev.selectedEscrow?.contractId === next.selectedEscrow?.contractId &&
|
|
244
|
+
prev.onViewDetails === next.onViewDetails &&
|
|
245
|
+
prev.activeRole.length === next.activeRole.length &&
|
|
246
|
+
prev.userRolesInEscrow.length === next.userRolesInEscrow.length &&
|
|
247
|
+
prev.activeRole.every((r, i) => r === next.activeRole[i]) &&
|
|
248
|
+
prev.userRolesInEscrow.every((r, i) => r === next.userRolesInEscrow[i])
|
|
249
|
+
) {
|
|
250
|
+
return true;
|
|
251
|
+
}
|
|
252
|
+
return false;
|
|
253
|
+
}
|
|
254
|
+
);
|