@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.
Files changed (74) hide show
  1. package/README.md +96 -0
  2. package/bin/index.js +1123 -0
  3. package/package.json +44 -0
  4. package/templates/deps.json +29 -0
  5. package/templates/escrows/details/Actions.tsx +149 -0
  6. package/templates/escrows/details/Entities.tsx +48 -0
  7. package/templates/escrows/details/EntityCard.tsx +98 -0
  8. package/templates/escrows/details/EscrowDetailDialog.tsx +154 -0
  9. package/templates/escrows/details/GeneralInformation.tsx +329 -0
  10. package/templates/escrows/details/MilestoneCard.tsx +254 -0
  11. package/templates/escrows/details/MilestoneDetailDialog.tsx +276 -0
  12. package/templates/escrows/details/Milestones.tsx +87 -0
  13. package/templates/escrows/details/ProgressEscrow.tsx +191 -0
  14. package/templates/escrows/details/StatisticsCard.tsx +79 -0
  15. package/templates/escrows/details/SuccessReleaseDialog.tsx +101 -0
  16. package/templates/escrows/details/useDetailsEscrow.ts +126 -0
  17. package/templates/escrows/escrow-context/EscrowAmountProvider.tsx +86 -0
  18. package/templates/escrows/escrow-context/EscrowDialogsProvider.tsx +108 -0
  19. package/templates/escrows/escrow-context/EscrowProvider.tsx +124 -0
  20. package/templates/escrows/escrows-by-role/cards/EscrowsCards.tsx +503 -0
  21. package/templates/escrows/escrows-by-role/cards/Filters.tsx +421 -0
  22. package/templates/escrows/escrows-by-role/table/EscrowsTable.tsx +427 -0
  23. package/templates/escrows/escrows-by-role/table/Filters.tsx +421 -0
  24. package/templates/escrows/escrows-by-role/useEscrowsByRole.shared.ts +336 -0
  25. package/templates/escrows/escrows-by-signer/cards/EscrowsCards.tsx +502 -0
  26. package/templates/escrows/escrows-by-signer/cards/Filters.tsx +389 -0
  27. package/templates/escrows/escrows-by-signer/table/EscrowsTable.tsx +422 -0
  28. package/templates/escrows/escrows-by-signer/table/Filters.tsx +389 -0
  29. package/templates/escrows/escrows-by-signer/useEscrowsBySigner.shared.ts +320 -0
  30. package/templates/escrows/single-release/approve-milestone/button/ApproveMilestone.tsx +78 -0
  31. package/templates/escrows/single-release/approve-milestone/dialog/ApproveMilestone.tsx +102 -0
  32. package/templates/escrows/single-release/approve-milestone/form/ApproveMilestone.tsx +80 -0
  33. package/templates/escrows/single-release/approve-milestone/shared/schema.ts +9 -0
  34. package/templates/escrows/single-release/approve-milestone/shared/useApproveMilestone.ts +67 -0
  35. package/templates/escrows/single-release/change-milestone-status/button/ChangeMilestoneStatus.tsx +78 -0
  36. package/templates/escrows/single-release/change-milestone-status/dialog/ChangeMilestoneStatus.tsx +167 -0
  37. package/templates/escrows/single-release/change-milestone-status/form/ChangeMilestoneStatus.tsx +114 -0
  38. package/templates/escrows/single-release/change-milestone-status/shared/schema.ts +15 -0
  39. package/templates/escrows/single-release/change-milestone-status/shared/useChangeMilestoneStatus.ts +77 -0
  40. package/templates/escrows/single-release/dispute-escrow/button/DisputeEscrow.tsx +68 -0
  41. package/templates/escrows/single-release/fund-escrow/button/FundEscrow.tsx +84 -0
  42. package/templates/escrows/single-release/fund-escrow/dialog/FundEscrow.tsx +77 -0
  43. package/templates/escrows/single-release/fund-escrow/form/FundEscrow.tsx +54 -0
  44. package/templates/escrows/single-release/fund-escrow/shared/schema.ts +10 -0
  45. package/templates/escrows/single-release/fund-escrow/shared/useFundEscrow.ts +66 -0
  46. package/templates/escrows/single-release/initialize-escrow/dialog/InitializeEscrow.tsx +526 -0
  47. package/templates/escrows/single-release/initialize-escrow/form/InitializeEscrow.tsx +504 -0
  48. package/templates/escrows/single-release/initialize-escrow/shared/schema.ts +232 -0
  49. package/templates/escrows/single-release/initialize-escrow/shared/useInitializeEscrow.ts +115 -0
  50. package/templates/escrows/single-release/release-escrow/button/ReleaseEscrow.tsx +80 -0
  51. package/templates/escrows/single-release/resolve-dispute/button/ResolveDispute.tsx +94 -0
  52. package/templates/escrows/single-release/resolve-dispute/dialog/ResolveDispute.tsx +123 -0
  53. package/templates/escrows/single-release/resolve-dispute/form/ResolveDispute.tsx +82 -0
  54. package/templates/escrows/single-release/resolve-dispute/shared/schema.ts +82 -0
  55. package/templates/escrows/single-release/resolve-dispute/shared/useResolveDispute.ts +58 -0
  56. package/templates/escrows/single-release/update-escrow/dialog/UpdateEscrow.tsx +485 -0
  57. package/templates/escrows/single-release/update-escrow/form/UpdateEscrow.tsx +463 -0
  58. package/templates/escrows/single-release/update-escrow/shared/schema.ts +139 -0
  59. package/templates/escrows/single-release/update-escrow/shared/useUpdateEscrow.ts +211 -0
  60. package/templates/handle-errors/errors.enum.ts +6 -0
  61. package/templates/handle-errors/handle.ts +47 -0
  62. package/templates/helpers/format.helper.ts +27 -0
  63. package/templates/helpers/useCopy.ts +13 -0
  64. package/templates/providers/ReactQueryClientProvider.tsx +28 -0
  65. package/templates/providers/TrustlessWork.tsx +30 -0
  66. package/templates/tanstak/useEscrowsByRoleQuery.ts +87 -0
  67. package/templates/tanstak/useEscrowsBySignerQuery.ts +78 -0
  68. package/templates/tanstak/useEscrowsMutations.ts +411 -0
  69. package/templates/wallet-kit/WalletButtons.tsx +116 -0
  70. package/templates/wallet-kit/WalletProvider.tsx +94 -0
  71. package/templates/wallet-kit/trustlines.ts +40 -0
  72. package/templates/wallet-kit/useWallet.ts +77 -0
  73. package/templates/wallet-kit/validators.ts +12 -0
  74. package/templates/wallet-kit/wallet-kit.ts +30 -0
@@ -0,0 +1,503 @@
1
+ "use client";
2
+
3
+ import React from "react";
4
+ import { Button } from "__UI_BASE__/button";
5
+ import type {
6
+ GetEscrowsFromIndexerResponse as Escrow,
7
+ MultiReleaseMilestone,
8
+ SingleReleaseMilestone,
9
+ Role,
10
+ } from "@trustless-work/escrow/types";
11
+ import { Card, CardContent, CardHeader, CardTitle } from "__UI_BASE__/card";
12
+ import { Badge } from "__UI_BASE__/badge";
13
+ import { Separator } from "__UI_BASE__/separator";
14
+ import {
15
+ Goal,
16
+ Wallet,
17
+ Loader2,
18
+ AlertTriangle,
19
+ RefreshCw,
20
+ FileX,
21
+ } from "lucide-react";
22
+ 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";
27
+ import {
28
+ formatCurrency,
29
+ formatTimestamp,
30
+ } from "../../../helpers/format.helper";
31
+
32
+ export function EscrowsByRoleCards() {
33
+ const {
34
+ walletAddress,
35
+ data,
36
+ isLoading,
37
+ isError,
38
+ refetch,
39
+ isFetching,
40
+ nextData,
41
+ isFetchingNext,
42
+ page,
43
+ setPage,
44
+ orderBy,
45
+ setOrderBy,
46
+ orderDirection,
47
+ setOrderDirection,
48
+ sorting,
49
+ title,
50
+ setTitle,
51
+ engagementId,
52
+ setEngagementId,
53
+ isActive,
54
+ setIsActive,
55
+ validateOnChain,
56
+ setValidateOnChain,
57
+ type,
58
+ setType,
59
+ status,
60
+ setStatus,
61
+ minAmount,
62
+ setMinAmount,
63
+ maxAmount,
64
+ setMaxAmount,
65
+ dateRange,
66
+ setDateRange,
67
+ formattedRangeLabel,
68
+ role,
69
+ setRole,
70
+ onClearFilters,
71
+ handleSortingChange,
72
+ } = useEscrowsByRole();
73
+
74
+ const { setSelectedEscrow } = useEscrowContext();
75
+
76
+ const dialogStates = useEscrowDialogs();
77
+
78
+ function allMilestonesReleasedOrResolved(
79
+ milestones: MultiReleaseMilestone[]
80
+ ) {
81
+ return milestones.every(
82
+ (milestone) => milestone.flags?.released || milestone.flags?.resolved
83
+ );
84
+ }
85
+
86
+ function allMilestonesApproved(milestones: SingleReleaseMilestone[]) {
87
+ return milestones.every((milestone) => milestone.approved);
88
+ }
89
+
90
+ function getSingleReleaseStatus(
91
+ flags: { disputed?: boolean; resolved?: boolean; released?: boolean } = {}
92
+ ) {
93
+ if (flags.disputed) return { label: "Disputed", variant: "destructive" };
94
+ if (flags.resolved) return { label: "Resolved", variant: "outline" };
95
+ if (flags.released) return { label: "Released", variant: "outline" };
96
+ return { label: "Working", variant: "outline" };
97
+ }
98
+
99
+ const escrows: Escrow[] = data ?? [];
100
+
101
+ const currentSort = sorting?.[0];
102
+ const sortField =
103
+ (currentSort?.id as "amount" | "createdAt" | "updatedAt" | undefined) ??
104
+ undefined;
105
+ const sortDesc = currentSort?.desc ?? true;
106
+
107
+ const setSort = (field: "amount" | "createdAt" | "updatedAt") => {
108
+ if (sortField === field) {
109
+ handleSortingChange([{ id: field, desc: !sortDesc }]);
110
+ } else {
111
+ handleSortingChange([{ id: field, desc: true }]);
112
+ }
113
+ };
114
+
115
+ const clearSort = () => handleSortingChange([]);
116
+
117
+ const onCardClick = (escrow: Escrow) => {
118
+ setSelectedEscrow(escrow);
119
+ dialogStates.second.setIsOpen(true);
120
+ };
121
+
122
+ const activeRole: Role[] = role.split(",") as Role[];
123
+
124
+ return (
125
+ <>
126
+ <div className="w-full flex flex-col gap-4">
127
+ <Filters
128
+ title={title}
129
+ engagementId={engagementId}
130
+ isActive={isActive}
131
+ validateOnChain={validateOnChain}
132
+ type={type}
133
+ status={status}
134
+ minAmount={minAmount}
135
+ maxAmount={maxAmount}
136
+ dateRange={dateRange}
137
+ formattedRangeLabel={formattedRangeLabel}
138
+ role={role}
139
+ setTitle={setTitle}
140
+ setEngagementId={setEngagementId}
141
+ setIsActive={setIsActive}
142
+ setValidateOnChain={setValidateOnChain}
143
+ setType={(v) => setType(v as typeof type)}
144
+ setStatus={(v) => setStatus(v as typeof status)}
145
+ setMinAmount={setMinAmount}
146
+ setMaxAmount={setMaxAmount}
147
+ setDateRange={setDateRange}
148
+ setRole={(v) => setRole(v as Role)}
149
+ onClearFilters={onClearFilters}
150
+ onRefresh={() => refetch()}
151
+ isRefreshing={isFetching}
152
+ orderBy={orderBy}
153
+ orderDirection={orderDirection}
154
+ setOrderBy={(v) => setOrderBy(v)}
155
+ setOrderDirection={(v) => setOrderDirection(v)}
156
+ />
157
+
158
+ <div className="w-full p-2 sm:p-4">
159
+ <div className="mb-2 sm:mb-3 flex items-center justify-end gap-2">
160
+ <span className="text-xs text-muted-foreground">Sort</span>
161
+ <Button
162
+ className="cursor-pointer"
163
+ variant={sortField === "createdAt" ? "default" : "outline"}
164
+ size="sm"
165
+ onClick={() => setSort("createdAt")}
166
+ >
167
+ Created {sortField === "createdAt" ? (sortDesc ? "▼" : "▲") : ""}
168
+ </Button>
169
+ <Button
170
+ className="cursor-pointer"
171
+ variant={sortField === "updatedAt" ? "default" : "outline"}
172
+ size="sm"
173
+ onClick={() => setSort("updatedAt")}
174
+ >
175
+ Updated {sortField === "updatedAt" ? (sortDesc ? "▼" : "▲") : ""}
176
+ </Button>
177
+ <Button
178
+ className="cursor-pointer"
179
+ variant={sortField === "amount" ? "default" : "outline"}
180
+ size="sm"
181
+ onClick={() => setSort("amount")}
182
+ >
183
+ Amount {sortField === "amount" ? (sortDesc ? "▼" : "▲") : ""}
184
+ </Button>
185
+ <Button
186
+ className="cursor-pointer"
187
+ variant="ghost"
188
+ size="sm"
189
+ onClick={clearSort}
190
+ disabled={!currentSort}
191
+ >
192
+ Reset
193
+ </Button>
194
+ </div>
195
+ <div className="mt-2 sm:mt-4 overflow-x-auto">
196
+ {!walletAddress ? (
197
+ <div>
198
+ <div className="p-6 md:p-8 flex flex-col items-center justify-center text-center">
199
+ <Wallet className="h-8 w-8 md:h-12 md:w-12 text-primary mb-3" />
200
+ <h3 className="font-medium text-foreground mb-2">
201
+ Connect your wallet
202
+ </h3>
203
+ <p className="text-sm text-muted-foreground max-w-sm">
204
+ To continue, connect your wallet and authorize the
205
+ application.
206
+ </p>
207
+ </div>
208
+ </div>
209
+ ) : isLoading ? (
210
+ <div>
211
+ <div className="p-6 md:p-8 flex flex-col items-center justify-center text-center">
212
+ <Loader2 className="h-6 w-6 md:h-8 md:w-8 animate-spin text-primary mb-3" />
213
+ <p className="text-sm text-muted-foreground">
214
+ Loading escrows…
215
+ </p>
216
+ </div>
217
+ </div>
218
+ ) : isError ? (
219
+ <div>
220
+ <div className="p-6 md:p-8 flex flex-col items-center justify-center text-center">
221
+ <AlertTriangle className="h-8 w-8 md:h-10 md:w-10 text-destructive mb-3" />
222
+ <h3 className="font-medium text-foreground mb-2">
223
+ Error loading data
224
+ </h3>
225
+ <p className="text-sm text-muted-foreground max-w-sm mb-4">
226
+ An error occurred while loading the information. Please try
227
+ again.
228
+ </p>
229
+ <Button variant="outline" size="sm" onClick={() => refetch()}>
230
+ <RefreshCw className="h-4 w-4 mr-2" />
231
+ Retry
232
+ </Button>
233
+ </div>
234
+ </div>
235
+ ) : escrows.length === 0 ? (
236
+ <div>
237
+ <div className="p-6 md:p-8 flex flex-col items-center justify-center text-center">
238
+ <FileX className="h-8 w-8 md:h-10 md:w-10 text-muted-foreground/60 mb-3" />
239
+ <h3 className="font-medium text-foreground mb-2">
240
+ No data available
241
+ </h3>
242
+ <p className="text-sm text-muted-foreground">
243
+ No escrows found for the selected filters.
244
+ </p>
245
+ </div>
246
+ </div>
247
+ ) : (
248
+ <div className="w-full grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4 py-2">
249
+ {escrows.map((escrow) => (
250
+ <React.Fragment key={escrow.contractId}>
251
+ <Card
252
+ className="w-full max-w-md mx-auto hover:shadow-lg cursor-pointer"
253
+ onClick={(e) => {
254
+ e.stopPropagation();
255
+ onCardClick(escrow);
256
+ }}
257
+ >
258
+ <CardHeader className="pb-3">
259
+ <div className="flex items-start justify-between gap-2">
260
+ <CardTitle className="text-lg font-semibold leading-tight line-clamp-2">
261
+ {escrow.title}
262
+ </CardTitle>
263
+ <Badge
264
+ variant={isActive ? "default" : "destructive"}
265
+ className="shrink-0"
266
+ >
267
+ {isActive ? "Active" : "Inactive"}
268
+ </Badge>
269
+ </div>
270
+ <p className="text-sm text-muted-foreground line-clamp-2 mt-1">
271
+ {escrow.description}
272
+ </p>
273
+ </CardHeader>
274
+
275
+ <CardContent className="space-y-4">
276
+ {/* Amount Section */}
277
+ <div className="space-y-2">
278
+ <div className="flex items-center justify-between">
279
+ <span className="text-sm font-medium">Amount</span>
280
+ <span className="font-semibold">
281
+ {escrow.type === "single-release"
282
+ ? formatCurrency(
283
+ escrow.amount,
284
+ escrow.trustline.name
285
+ )
286
+ : formatCurrency(
287
+ escrow.milestones.reduce(
288
+ (acc, milestone) =>
289
+ acc +
290
+ (milestone as MultiReleaseMilestone)
291
+ .amount,
292
+ 0
293
+ ),
294
+ escrow.trustline.name
295
+ )}
296
+ </span>
297
+ </div>
298
+
299
+ {escrow.balance !== undefined && (
300
+ <div className="flex items-center justify-between text-sm">
301
+ <span className="text-muted-foreground">
302
+ Balance
303
+ </span>
304
+ <span className="font-medium text-green-800 dark:text-green-600">
305
+ {formatCurrency(
306
+ escrow.balance,
307
+ escrow.trustline.name
308
+ )}
309
+ </span>
310
+ </div>
311
+ )}
312
+
313
+ <div className="flex items-center justify-between text-sm">
314
+ <span className="text-muted-foreground">
315
+ Platform Fee
316
+ </span>
317
+ <span className="text-muted-foreground">
318
+ {escrow.platformFee}%
319
+ </span>
320
+ </div>
321
+ </div>
322
+
323
+ <Separator />
324
+
325
+ {/* Details Section */}
326
+ <div className="space-y-3">
327
+ <div className="flex flex-col gap-2">
328
+ <div className="flex items-center gap-2">
329
+ <Goal className="h-4 w-4 text-muted-foreground" />
330
+ <span className="text-sm font-medium">
331
+ Milestones
332
+ </span>
333
+ </div>
334
+ <ul className="list-disc list-inside flex flex-col gap-1">
335
+ {escrow.milestones
336
+ .slice(0, 3)
337
+ .map((milestone) => (
338
+ <li
339
+ key={`milestone-${milestone.description}-${milestone.status}`}
340
+ className="text-xs flex justify-between"
341
+ >
342
+ {milestone.description}
343
+
344
+ {escrow.type === "multi-release" &&
345
+ "amount" in milestone && (
346
+ <>
347
+ <div className="flex items-center gap-1">
348
+ <span className="text-muted-foreground">
349
+ {formatCurrency(
350
+ milestone.amount,
351
+ escrow.trustline.name
352
+ )}
353
+ </span>
354
+
355
+ <span
356
+ className={`bg-red-800 rounded-full h-2 w-2 ml-1 ${
357
+ milestone.flags?.disputed
358
+ ? "block"
359
+ : "hidden"
360
+ }`}
361
+ />
362
+
363
+ <span
364
+ className={`bg-green-800 rounded-full h-2 w-2 ml-1 ${
365
+ milestone.flags?.resolved ||
366
+ milestone.flags?.released
367
+ ? "block"
368
+ : "hidden"
369
+ }`}
370
+ />
371
+
372
+ <span
373
+ className={`bg-yellow-800 rounded-full h-2 w-2 ml-1 ${
374
+ milestone.flags?.approved &&
375
+ !milestone.flags?.disputed &&
376
+ !milestone.flags?.resolved &&
377
+ !milestone.flags?.released
378
+ ? "block"
379
+ : "hidden"
380
+ }`}
381
+ />
382
+ </div>
383
+ </>
384
+ )}
385
+ </li>
386
+ ))}
387
+
388
+ {escrow.milestones.length > 3 && (
389
+ <li className="text-xs">
390
+ {escrow.milestones.length - 3} more
391
+ </li>
392
+ )}
393
+ </ul>
394
+ </div>
395
+ </div>
396
+
397
+ {/* Type Badge */}
398
+ <div className="pt-2 flex items-end justify-between">
399
+ <div className="flex flex-col gap-2">
400
+ <Badge variant="secondary" className="text-xs">
401
+ {escrow.type
402
+ .replace("_", " ")
403
+ .toLowerCase()
404
+ .replace(/\b\w/g, (l) => l.toUpperCase())}
405
+ </Badge>
406
+
407
+ {escrow.type === "single-release" && (
408
+ <Badge
409
+ variant={
410
+ getSingleReleaseStatus(escrow.flags ?? {})
411
+ .variant as "destructive" | "outline"
412
+ }
413
+ className="text-xs"
414
+ >
415
+ {
416
+ getSingleReleaseStatus(escrow.flags ?? {})
417
+ .label
418
+ }
419
+ </Badge>
420
+ )}
421
+
422
+ {escrow.type === "single-release" &&
423
+ allMilestonesApproved(
424
+ escrow.milestones as SingleReleaseMilestone[]
425
+ ) &&
426
+ !escrow.flags?.released &&
427
+ !escrow.flags?.resolved &&
428
+ !escrow.flags?.disputed && (
429
+ <Badge variant="outline" className="text-xs">
430
+ Pending Release
431
+ </Badge>
432
+ )}
433
+
434
+ {escrow.type === "multi-release" &&
435
+ allMilestonesReleasedOrResolved(
436
+ escrow.milestones as MultiReleaseMilestone[]
437
+ ) && (
438
+ <Badge variant="outline" className="text-xs">
439
+ Finished
440
+ </Badge>
441
+ )}
442
+ {escrow.type === "multi-release" &&
443
+ !allMilestonesReleasedOrResolved(
444
+ escrow.milestones as MultiReleaseMilestone[]
445
+ ) && (
446
+ <Badge variant="outline" className="text-xs">
447
+ Working
448
+ </Badge>
449
+ )}
450
+ </div>
451
+
452
+ <span className="text-xs text-muted-foreground">
453
+ {formatTimestamp(escrow.createdAt)}
454
+ </span>
455
+ </div>
456
+ </CardContent>
457
+ </Card>
458
+ </React.Fragment>
459
+ ))}
460
+ </div>
461
+ )}
462
+ </div>
463
+
464
+ <div className="mt-3 sm:mt-4 flex flex-col sm:flex-row sm:items-center sm:justify-between gap-2">
465
+ <div className="text-xs sm:text-sm text-muted-foreground">
466
+ Page {page}
467
+ </div>
468
+ <div className="flex items-center gap-2 self-end sm:self-auto">
469
+ <Button
470
+ variant="outline"
471
+ onClick={() => setPage((p) => Math.max(1, p - 1))}
472
+ disabled={page === 1 || isFetching}
473
+ >
474
+ Previous
475
+ </Button>
476
+ <Button
477
+ variant="outline"
478
+ onClick={() => setPage((p) => p + 1)}
479
+ disabled={
480
+ isFetching ||
481
+ !walletAddress ||
482
+ ((nextData?.length ?? 0) === 0 && !isFetchingNext)
483
+ }
484
+ >
485
+ Next
486
+ </Button>
487
+ </div>
488
+ </div>
489
+ </div>
490
+ </div>
491
+
492
+ {/* Dialog */}
493
+ <EscrowDetailDialog
494
+ activeRole={activeRole}
495
+ isDialogOpen={dialogStates.second.isOpen}
496
+ setIsDialogOpen={dialogStates.second.setIsOpen}
497
+ setSelectedEscrow={setSelectedEscrow}
498
+ />
499
+ </>
500
+ );
501
+ }
502
+
503
+ export default EscrowsByRoleCards;