@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,427 @@
1
+ "use client";
2
+
3
+ import React from "react";
4
+ import { Card } from "__UI_BASE__/card";
5
+ import { Button } from "__UI_BASE__/button";
6
+ import type {
7
+ GetEscrowsFromIndexerResponse as Escrow,
8
+ Role,
9
+ MultiReleaseMilestone,
10
+ } from "@trustless-work/escrow/types";
11
+ import {
12
+ ColumnDef,
13
+ flexRender,
14
+ getCoreRowModel,
15
+ useReactTable,
16
+ } from "@tanstack/react-table";
17
+ import {
18
+ Table,
19
+ TableBody,
20
+ TableCell,
21
+ TableHead,
22
+ TableHeader,
23
+ TableRow,
24
+ } from "__UI_BASE__/table";
25
+ import { FileX, Loader2, Wallet, RefreshCw, AlertTriangle } from "lucide-react";
26
+ import Filters from "./Filters";
27
+ import EscrowDetailDialog from "../details/EscrowDetailDialog";
28
+ import { useEscrowDialogs } from "../../escrow-context/EscrowDialogsProvider";
29
+ import { useEscrowContext } from "../../escrow-context/EscrowProvider";
30
+ import { useEscrowsByRole } from "../useEscrowsByRole.shared";
31
+ import { formatTimestamp } from "../../../helpers/format.helper";
32
+
33
+ export function EscrowsByRoleTable() {
34
+ const {
35
+ walletAddress,
36
+ data,
37
+ isLoading,
38
+ isError,
39
+ refetch,
40
+ isFetching,
41
+ nextData,
42
+ isFetchingNext,
43
+ page,
44
+ setPage,
45
+ orderBy,
46
+ setOrderBy,
47
+ orderDirection,
48
+ setOrderDirection,
49
+ sorting,
50
+ title,
51
+ setTitle,
52
+ engagementId,
53
+ setEngagementId,
54
+ isActive,
55
+ setIsActive,
56
+ validateOnChain,
57
+ setValidateOnChain,
58
+ type,
59
+ setType,
60
+ status,
61
+ setStatus,
62
+ minAmount,
63
+ setMinAmount,
64
+ maxAmount,
65
+ setMaxAmount,
66
+ dateRange,
67
+ setDateRange,
68
+ formattedRangeLabel,
69
+ role,
70
+ setRole,
71
+ onClearFilters,
72
+ handleSortingChange,
73
+ } = useEscrowsByRole();
74
+
75
+ const dialogStates = useEscrowDialogs();
76
+ const { setSelectedEscrow } = useEscrowContext();
77
+
78
+ const columns = React.useMemo<ColumnDef<Escrow>[]>(
79
+ () => [
80
+ {
81
+ header: "Title",
82
+ accessorKey: "title",
83
+ enableSorting: false,
84
+ cell: ({ row }) => (
85
+ <span
86
+ className="max-w-[220px] truncate block"
87
+ title={row.original.title}
88
+ >
89
+ {row.original.title}
90
+ </span>
91
+ ),
92
+ },
93
+ {
94
+ header: "Engagement ID",
95
+ accessorKey: "engagementId",
96
+ enableSorting: false,
97
+ meta: { className: "hidden sm:table-cell" },
98
+ cell: ({ row }) => (
99
+ <span
100
+ className="max-w-[180px] truncate block"
101
+ title={row.original.engagementId}
102
+ >
103
+ {row.original.engagementId}
104
+ </span>
105
+ ),
106
+ },
107
+ {
108
+ header: "Amount",
109
+ accessorKey: "amount",
110
+ enableSorting: true,
111
+ cell: ({ row }) => {
112
+ // single release
113
+ if (row.original.type === "single-release") {
114
+ return row.original.amount;
115
+ }
116
+ // multi release
117
+ return row.original.milestones.reduce(
118
+ (acc, milestone) =>
119
+ acc + (milestone as MultiReleaseMilestone).amount,
120
+ 0
121
+ );
122
+ },
123
+ },
124
+ {
125
+ header: "Balance",
126
+ accessorKey: "balance",
127
+ enableSorting: false,
128
+ meta: { className: "hidden md:table-cell" },
129
+ cell: ({ row }) => row.original.balance ?? 0,
130
+ },
131
+ {
132
+ header: "Type",
133
+ accessorKey: "type",
134
+ enableSorting: false,
135
+ meta: { className: "hidden lg:table-cell" },
136
+ },
137
+ {
138
+ header: "Active",
139
+ accessorKey: "isActive",
140
+ enableSorting: false,
141
+ meta: { className: "hidden md:table-cell" },
142
+ cell: ({ row }) => (row.original.isActive ? "Yes" : "No"),
143
+ },
144
+ {
145
+ header: "Trustline",
146
+ id: "trustline",
147
+ enableSorting: false,
148
+ meta: { className: "hidden lg:table-cell" },
149
+ cell: ({ row }) => (
150
+ <span
151
+ className="max-w-[220px] truncate block"
152
+ title={`${row.original.trustline.name} (${row.original.trustline.address})`}
153
+ >
154
+ {row.original.trustline.name}
155
+ </span>
156
+ ),
157
+ },
158
+ {
159
+ header: "Created",
160
+ accessorKey: "createdAt",
161
+ enableSorting: true,
162
+ meta: { className: "hidden sm:table-cell" },
163
+ cell: ({ row }) => formatTimestamp(row.original.createdAt),
164
+ },
165
+ {
166
+ header: "Updated",
167
+ accessorKey: "updatedAt",
168
+ enableSorting: true,
169
+ meta: { className: "hidden xl:table-cell" },
170
+ cell: ({ row }) => formatTimestamp(row.original.updatedAt),
171
+ },
172
+ {
173
+ header: "Actions",
174
+ id: "actions",
175
+ enableSorting: false,
176
+ cell: ({ row }) => (
177
+ <Button
178
+ variant="outline"
179
+ className="cursor-pointer"
180
+ onClick={() => {
181
+ setSelectedEscrow(row.original);
182
+ dialogStates.second.setIsOpen(true);
183
+ }}
184
+ >
185
+ Details
186
+ </Button>
187
+ ),
188
+ },
189
+ ],
190
+ []
191
+ );
192
+
193
+ const table = useReactTable({
194
+ data: React.useMemo(() => data ?? [], [data]),
195
+ columns,
196
+ getCoreRowModel: getCoreRowModel(),
197
+ state: {
198
+ sorting,
199
+ },
200
+ onSortingChange: handleSortingChange,
201
+ manualSorting: true,
202
+ enableSortingRemoval: true,
203
+ });
204
+
205
+ const activeRole: Role[] = role.split(",") as Role[];
206
+ const escrows = data ?? [];
207
+
208
+ return (
209
+ <>
210
+ <div className="w-full flex flex-col gap-4">
211
+ <Filters
212
+ title={title}
213
+ engagementId={engagementId}
214
+ isActive={isActive}
215
+ validateOnChain={validateOnChain}
216
+ type={type}
217
+ status={status}
218
+ minAmount={minAmount}
219
+ maxAmount={maxAmount}
220
+ dateRange={dateRange}
221
+ formattedRangeLabel={formattedRangeLabel}
222
+ role={role}
223
+ setTitle={setTitle}
224
+ setEngagementId={setEngagementId}
225
+ setIsActive={setIsActive}
226
+ setValidateOnChain={setValidateOnChain}
227
+ setType={(v) => setType(v as typeof type)}
228
+ setStatus={(v) => setStatus(v as typeof status)}
229
+ setMinAmount={setMinAmount}
230
+ setMaxAmount={setMaxAmount}
231
+ setDateRange={setDateRange}
232
+ setRole={(v) => setRole(v as Role)}
233
+ onClearFilters={onClearFilters}
234
+ onRefresh={() => refetch()}
235
+ isRefreshing={isFetching}
236
+ orderBy={orderBy}
237
+ orderDirection={orderDirection}
238
+ setOrderBy={(v) => setOrderBy(v)}
239
+ setOrderDirection={(v) => setOrderDirection(v)}
240
+ />
241
+
242
+ <Card className="w-full p-2 sm:p-4">
243
+ <div className="mt-2 sm:mt-4 overflow-x-auto">
244
+ <Table>
245
+ <TableHeader>
246
+ {table.getHeaderGroups().map((headerGroup) => (
247
+ <TableRow key={headerGroup.id} className="text-xs sm:text-sm">
248
+ {headerGroup.headers.map((header) => {
249
+ const isSortable = header.column.getCanSort();
250
+ const sorted = header.column.getIsSorted();
251
+ const className =
252
+ typeof header.column.columnDef.meta === "object" &&
253
+ header.column.columnDef.meta &&
254
+ "className" in (header.column.columnDef.meta as any)
255
+ ? (header.column.columnDef.meta as any).className
256
+ : "";
257
+ return (
258
+ <TableHead
259
+ key={header.id}
260
+ className={`select-none ${className}`}
261
+ onClick={
262
+ isSortable
263
+ ? header.column.getToggleSortingHandler()
264
+ : undefined
265
+ }
266
+ role={isSortable ? "button" : undefined}
267
+ aria-sort={
268
+ sorted
269
+ ? sorted === "desc"
270
+ ? "descending"
271
+ : "ascending"
272
+ : "none"
273
+ }
274
+ >
275
+ <div className="flex items-center gap-1">
276
+ {flexRender(
277
+ header.column.columnDef.header,
278
+ header.getContext()
279
+ )}
280
+ {isSortable && (
281
+ <span className="text-muted-foreground">
282
+ {sorted === "asc"
283
+ ? "▲"
284
+ : sorted === "desc"
285
+ ? "▼"
286
+ : ""}
287
+ </span>
288
+ )}
289
+ </div>
290
+ </TableHead>
291
+ );
292
+ })}
293
+ </TableRow>
294
+ ))}
295
+ </TableHeader>
296
+ <TableBody>
297
+ {!walletAddress ? (
298
+ <TableRow>
299
+ <TableCell colSpan={table.getAllLeafColumns().length}>
300
+ <div className="p-6 md:p-8 flex flex-col items-center justify-center text-center">
301
+ <Wallet className="h-8 w-8 md:h-12 md:w-12 text-primary mb-3" />
302
+ <h3 className="font-medium text-foreground mb-2">
303
+ Connect your wallet
304
+ </h3>
305
+ <p className="text-sm text-muted-foreground max-w-sm">
306
+ To continue, connect your wallet and authorize the
307
+ application.
308
+ </p>
309
+ </div>
310
+ </TableCell>
311
+ </TableRow>
312
+ ) : isLoading ? (
313
+ <TableRow>
314
+ <TableCell colSpan={table.getAllLeafColumns().length}>
315
+ <div className="p-6 md:p-8 flex flex-col items-center justify-center text-center">
316
+ <Loader2 className="h-6 w-6 md:h-8 md:w-8 animate-spin text-primary mb-3" />
317
+ <p className="text-sm text-muted-foreground">
318
+ Loading escrows…
319
+ </p>
320
+ </div>
321
+ </TableCell>
322
+ </TableRow>
323
+ ) : isError ? (
324
+ <TableRow>
325
+ <TableCell colSpan={table.getAllLeafColumns().length}>
326
+ <div className="p-6 md:p-8 flex flex-col items-center justify-center text-center">
327
+ <AlertTriangle className="h-8 w-8 md:h-10 md:w-10 text-destructive mb-3" />
328
+ <h3 className="font-medium text-foreground mb-2">
329
+ Error loading data
330
+ </h3>
331
+ <p className="text-sm text-muted-foreground max-w-sm mb-4">
332
+ An error occurred while loading the information.
333
+ Please try again.
334
+ </p>
335
+ <Button
336
+ variant="outline"
337
+ size="sm"
338
+ onClick={() => refetch()}
339
+ >
340
+ <RefreshCw className="h-4 w-4 mr-2" />
341
+ Retry
342
+ </Button>
343
+ </div>
344
+ </TableCell>
345
+ </TableRow>
346
+ ) : escrows.length === 0 ? (
347
+ <TableRow>
348
+ <TableCell colSpan={table.getAllLeafColumns().length}>
349
+ <div className="p-6 md:p-8 flex flex-col items-center justify-center text-center">
350
+ <FileX className="h-8 w-8 md:h-10 md:w-10 text-muted-foreground/60 mb-3" />
351
+ <h3 className="font-medium text-foreground mb-2">
352
+ No data available
353
+ </h3>
354
+ <p className="text-sm text-muted-foreground">
355
+ No escrows found for the selected filters.
356
+ </p>
357
+ </div>
358
+ </TableCell>
359
+ </TableRow>
360
+ ) : (
361
+ <>
362
+ {table.getRowModel().rows.map((row) => (
363
+ <TableRow key={row.id} className="text-xs sm:text-sm">
364
+ {row.getVisibleCells().map((cell) => {
365
+ const className =
366
+ typeof cell.column.columnDef.meta === "object" &&
367
+ cell.column.columnDef.meta &&
368
+ "className" in (cell.column.columnDef.meta as any)
369
+ ? (cell.column.columnDef.meta as any).className
370
+ : "";
371
+ return (
372
+ <TableCell key={cell.id} className={className}>
373
+ {flexRender(
374
+ cell.column.columnDef.cell,
375
+ cell.getContext()
376
+ )}
377
+ </TableCell>
378
+ );
379
+ })}
380
+ </TableRow>
381
+ ))}
382
+ </>
383
+ )}
384
+ </TableBody>
385
+ </Table>
386
+ </div>
387
+
388
+ <div className="mt-3 sm:mt-4 flex flex-col sm:flex-row sm:items-center sm:justify-between gap-2">
389
+ <div className="text-xs sm:text-sm text-muted-foreground">
390
+ Page {page}
391
+ </div>
392
+ <div className="flex items-center gap-2 self-end sm:self-auto">
393
+ <Button
394
+ variant="outline"
395
+ onClick={() => setPage((p) => Math.max(1, p - 1))}
396
+ disabled={page === 1 || isFetching}
397
+ >
398
+ Previous
399
+ </Button>
400
+ <Button
401
+ variant="outline"
402
+ onClick={() => setPage((p) => p + 1)}
403
+ disabled={
404
+ isFetching ||
405
+ !walletAddress ||
406
+ ((nextData?.length ?? 0) === 0 && !isFetchingNext)
407
+ }
408
+ >
409
+ Next
410
+ </Button>
411
+ </div>
412
+ </div>
413
+ </Card>
414
+ </div>
415
+
416
+ {/* Dialog */}
417
+ <EscrowDetailDialog
418
+ activeRole={activeRole}
419
+ isDialogOpen={dialogStates.second.isOpen}
420
+ setIsDialogOpen={dialogStates.second.setIsOpen}
421
+ setSelectedEscrow={setSelectedEscrow}
422
+ />
423
+ </>
424
+ );
425
+ }
426
+
427
+ export default EscrowsByRoleTable;