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