@trustless-work/blocks 0.0.7 → 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 (66) hide show
  1. package/bin/index.js +485 -17
  2. package/package.json +1 -1
  3. package/templates/escrows/details/Actions.tsx +144 -149
  4. package/templates/escrows/details/Entities.tsx +1 -1
  5. package/templates/escrows/details/EntityCard.tsx +1 -3
  6. package/templates/escrows/details/EscrowDetailDialog.tsx +16 -16
  7. package/templates/escrows/details/GeneralInformation.tsx +19 -22
  8. package/templates/escrows/details/MilestoneCard.tsx +46 -47
  9. package/templates/escrows/details/MilestoneDetailDialog.tsx +1 -2
  10. package/templates/escrows/details/Milestones.tsx +0 -5
  11. package/templates/escrows/details/SuccessReleaseDialog.tsx +4 -6
  12. package/templates/escrows/escrows-by-role/cards/EscrowsCards.tsx +84 -49
  13. package/templates/escrows/escrows-by-role/cards/Filters.tsx +3 -5
  14. package/templates/escrows/escrows-by-role/table/EscrowsTable.tsx +8 -26
  15. package/templates/escrows/escrows-by-role/table/Filters.tsx +3 -5
  16. package/templates/escrows/escrows-by-signer/cards/EscrowsCards.tsx +89 -55
  17. package/templates/escrows/escrows-by-signer/cards/Filters.tsx +3 -5
  18. package/templates/escrows/escrows-by-signer/table/EscrowsTable.tsx +8 -24
  19. package/templates/escrows/escrows-by-signer/table/Filters.tsx +3 -5
  20. package/templates/escrows/multi-release/dispute-milestone/button/DisputeEscrow.tsx +98 -0
  21. package/templates/escrows/multi-release/initialize-escrow/dialog/InitializeEscrow.tsx +528 -0
  22. package/templates/escrows/multi-release/initialize-escrow/form/InitializeEscrow.tsx +506 -0
  23. package/templates/escrows/multi-release/initialize-escrow/shared/schema.ts +179 -0
  24. package/templates/escrows/multi-release/initialize-escrow/shared/useInitializeEscrow.ts +175 -0
  25. package/templates/escrows/multi-release/release-milestone/button/ReleaseEscrow.tsx +116 -0
  26. package/templates/escrows/multi-release/resolve-dispute/button/ResolveDispute.tsx +122 -0
  27. package/templates/escrows/multi-release/resolve-dispute/dialog/ResolveDispute.tsx +178 -0
  28. package/templates/escrows/multi-release/resolve-dispute/form/ResolveDispute.tsx +156 -0
  29. package/templates/escrows/multi-release/resolve-dispute/shared/schema.ts +85 -0
  30. package/templates/escrows/multi-release/resolve-dispute/shared/useResolveDispute.ts +105 -0
  31. package/templates/escrows/multi-release/update-escrow/dialog/UpdateEscrow.tsx +471 -0
  32. package/templates/escrows/multi-release/update-escrow/form/UpdateEscrow.tsx +449 -0
  33. package/templates/escrows/multi-release/update-escrow/shared/schema.ts +152 -0
  34. package/templates/escrows/multi-release/update-escrow/shared/useUpdateEscrow.ts +254 -0
  35. package/templates/escrows/{single-release → single-multi-release}/approve-milestone/button/ApproveMilestone.tsx +20 -7
  36. package/templates/escrows/{single-release → single-multi-release}/approve-milestone/dialog/ApproveMilestone.tsx +3 -3
  37. package/templates/escrows/{single-release → single-multi-release}/approve-milestone/form/ApproveMilestone.tsx +3 -3
  38. package/templates/escrows/{single-release/approve-milestone/shared → single-multi-release/approve-milestone}/useApproveMilestone.ts +16 -16
  39. package/templates/escrows/{single-release → single-multi-release}/change-milestone-status/button/ChangeMilestoneStatus.tsx +4 -4
  40. package/templates/escrows/{single-release → single-multi-release}/change-milestone-status/dialog/ChangeMilestoneStatus.tsx +4 -4
  41. package/templates/escrows/{single-release → single-multi-release}/change-milestone-status/form/ChangeMilestoneStatus.tsx +3 -3
  42. package/templates/escrows/{single-release/change-milestone-status/shared → single-multi-release/change-milestone-status}/useChangeMilestoneStatus.ts +1 -1
  43. package/templates/escrows/{single-release → single-multi-release}/fund-escrow/button/FundEscrow.tsx +3 -3
  44. package/templates/escrows/{single-release → single-multi-release}/fund-escrow/dialog/FundEscrow.tsx +3 -3
  45. package/templates/escrows/{single-release → single-multi-release}/fund-escrow/form/FundEscrow.tsx +3 -3
  46. package/templates/escrows/{single-release/fund-escrow/shared → single-multi-release/fund-escrow}/useFundEscrow.ts +1 -1
  47. package/templates/escrows/single-release/dispute-escrow/button/DisputeEscrow.tsx +2 -2
  48. package/templates/escrows/single-release/initialize-escrow/dialog/InitializeEscrow.tsx +14 -6
  49. package/templates/escrows/single-release/initialize-escrow/form/InitializeEscrow.tsx +14 -6
  50. package/templates/escrows/single-release/initialize-escrow/shared/schema.ts +0 -57
  51. package/templates/escrows/single-release/initialize-escrow/shared/useInitializeEscrow.ts +42 -1
  52. package/templates/escrows/single-release/release-escrow/button/ReleaseEscrow.tsx +2 -2
  53. package/templates/escrows/single-release/resolve-dispute/button/ResolveDispute.tsx +3 -3
  54. package/templates/escrows/single-release/resolve-dispute/dialog/ResolveDispute.tsx +3 -6
  55. package/templates/escrows/single-release/resolve-dispute/form/ResolveDispute.tsx +2 -2
  56. package/templates/escrows/single-release/resolve-dispute/shared/useResolveDispute.ts +14 -1
  57. package/templates/escrows/single-release/update-escrow/dialog/UpdateEscrow.tsx +2 -2
  58. package/templates/escrows/single-release/update-escrow/form/UpdateEscrow.tsx +2 -2
  59. package/templates/escrows/single-release/update-escrow/shared/useUpdateEscrow.ts +12 -7
  60. package/templates/providers/EscrowDialogsProvider.tsx +1 -3
  61. package/templates/providers/EscrowProvider.tsx +27 -4
  62. package/templates/providers/TrustlessWork.tsx +1 -1
  63. package/templates/escrows/details/ProgressEscrow.tsx +0 -191
  64. /package/templates/escrows/{single-release/approve-milestone/shared → single-multi-release/approve-milestone}/schema.ts +0 -0
  65. /package/templates/escrows/{single-release/change-milestone-status/shared → single-multi-release/change-milestone-status}/schema.ts +0 -0
  66. /package/templates/escrows/{single-release/fund-escrow/shared → single-multi-release/fund-escrow}/schema.ts +0 -0
@@ -23,7 +23,7 @@ import { Trash2, DollarSign, Percent, Loader2 } from "lucide-react";
23
23
  import Link from "next/link";
24
24
  import { trustlineOptions } from "@/components/tw-blocks/wallet-kit/trustlines";
25
25
 
26
- export function InitializeEscrowForm() {
26
+ export const InitializeEscrowForm = () => {
27
27
  const {
28
28
  form,
29
29
  isSubmitting,
@@ -32,6 +32,7 @@ export function InitializeEscrowForm() {
32
32
  handleSubmit,
33
33
  handleAddMilestone,
34
34
  handleRemoveMilestone,
35
+ fillTemplateForm,
35
36
  } = useInitializeEscrow();
36
37
 
37
38
  const handleAmountChange = (e: React.ChangeEvent<HTMLInputElement>) => {
@@ -88,10 +89,19 @@ export function InitializeEscrowForm() {
88
89
  <h2 className="text-xl font-semibold">Single Release Escrow</h2>
89
90
  </div>
90
91
  <p className="text-muted-foreground mt-1">
91
- A single payment will be released upon completion of all
92
- milestones
92
+ Fill out the form to initialize a single release escrow milestones
93
93
  </p>
94
94
  </Link>
95
+ {process.env.NODE_ENV !== "production" && (
96
+ <Button
97
+ type="button"
98
+ variant="outline"
99
+ onClick={fillTemplateForm}
100
+ className="cursor-pointer"
101
+ >
102
+ Autofill
103
+ </Button>
104
+ )}
95
105
  </Card>
96
106
  <div className="grid grid-cols-1 lg:grid-cols-3 gap-4">
97
107
  <FormField
@@ -499,6 +509,4 @@ export function InitializeEscrowForm() {
499
509
  </form>
500
510
  </Form>
501
511
  );
502
- }
503
-
504
- export default InitializeEscrowForm;
512
+ };
@@ -169,64 +169,7 @@ export const useInitializeEscrowSchema = () => {
169
169
  .min(1, { message: "At least one milestone is required." }),
170
170
  });
171
171
  };
172
-
173
- const getMultiReleaseFormSchema = () => {
174
- const baseSchema = getBaseSchema();
175
-
176
- return baseSchema.extend({
177
- milestones: z
178
- .array(
179
- z.object({
180
- description: z.string().min(1, {
181
- message: "Milestone description is required.",
182
- }),
183
- amount: z
184
- .union([z.string(), z.number()])
185
- .refine(
186
- (val) => {
187
- if (typeof val === "string") {
188
- if (val === "" || val === "." || val.endsWith(".")) {
189
- return true;
190
- }
191
- const numVal = Number(val);
192
- return !isNaN(numVal) && numVal > 0;
193
- }
194
- return val > 0;
195
- },
196
- {
197
- message: "Milestone amount must be greater than 0.",
198
- }
199
- )
200
- .refine(
201
- (val) => {
202
- if (typeof val === "string") {
203
- if (val === "" || val === "." || val.endsWith(".")) {
204
- return true;
205
- }
206
- const numVal = Number(val);
207
- if (isNaN(numVal)) return false;
208
- const decimalPlaces = (
209
- numVal.toString().split(".")[1] || ""
210
- ).length;
211
- return decimalPlaces <= 2;
212
- }
213
- const decimalPlaces = (val.toString().split(".")[1] || "")
214
- .length;
215
- return decimalPlaces <= 2;
216
- },
217
- {
218
- message:
219
- "Milestone amount can have a maximum of 2 decimal places.",
220
- }
221
- ),
222
- })
223
- )
224
- .min(1, { message: "At least one milestone is required." }),
225
- });
226
- };
227
-
228
172
  return {
229
173
  getSingleReleaseFormSchema,
230
- getMultiReleaseFormSchema,
231
174
  };
232
175
  };
@@ -15,6 +15,7 @@ import {
15
15
  handleError,
16
16
  } from "@/components/tw-blocks/handle-errors/handle";
17
17
  import { useEscrowContext } from "@/components/tw-blocks/providers/EscrowProvider";
18
+ import { trustlineOptions } from "@/components/tw-blocks/wallet-kit/trustlines";
18
19
 
19
20
  export function useInitializeEscrow() {
20
21
  const [isSubmitting, setIsSubmitting] = React.useState(false);
@@ -69,6 +70,45 @@ export function useInitializeEscrow() {
69
70
  form.setValue("milestones", updatedMilestones);
70
71
  };
71
72
 
73
+ const fillTemplateForm = () => {
74
+ const usdc = trustlineOptions.find((t) => t.label === "USDC");
75
+
76
+ const templateData: z.infer<typeof formSchema> = {
77
+ engagementId: "ENG-001",
78
+ title: "Design Landing Page",
79
+ description: "Landing for the new product of the company.",
80
+ platformFee: 5,
81
+ amount: 5,
82
+ receiverMemo: "123",
83
+ trustline: {
84
+ address: usdc?.value || "",
85
+ decimals: 10000000,
86
+ },
87
+ roles: {
88
+ approver: walletAddress || "",
89
+ serviceProvider: walletAddress || "",
90
+ platformAddress: walletAddress || "",
91
+ receiver: walletAddress || "",
92
+ releaseSigner: walletAddress || "",
93
+ disputeResolver: walletAddress || "",
94
+ },
95
+ milestones: [
96
+ { description: "Design the wireframe" },
97
+ { description: "Develop the wireframe" },
98
+ { description: "Deploy the wireframe" },
99
+ ],
100
+ };
101
+
102
+ // Set form values
103
+ Object.entries(templateData).forEach(([key, value]) => {
104
+ form.setValue(key as keyof z.infer<typeof formSchema>, value);
105
+ });
106
+
107
+ // Explicitly set the trustline field
108
+ form.setValue("trustline.address", usdc?.value || "");
109
+ form.setValue("trustline.decimals", 10000000);
110
+ };
111
+
72
112
  const handleSubmit = form.handleSubmit(async (payload) => {
73
113
  try {
74
114
  setIsSubmitting(true);
@@ -110,7 +150,7 @@ export function useInitializeEscrow() {
110
150
 
111
151
  toast.success("Escrow initialized successfully");
112
152
 
113
- setSelectedEscrow(response);
153
+ setSelectedEscrow({ ...finalPayload, contractId: response.contractId });
114
154
  } catch (error) {
115
155
  toast.error(handleError(error as ErrorResponse).message);
116
156
  } finally {
@@ -124,6 +164,7 @@ export function useInitializeEscrow() {
124
164
  isSubmitting,
125
165
  milestones,
126
166
  isAnyMilestoneEmpty,
167
+ fillTemplateForm,
127
168
  handleSubmit,
128
169
  handleAddMilestone,
129
170
  handleRemoveMilestone,
@@ -13,7 +13,7 @@ import { useEscrowDialogs } from "@/components/tw-blocks/providers/EscrowDialogs
13
13
  import { useEscrowAmountContext } from "@/components/tw-blocks/providers/EscrowAmountProvider";
14
14
  import { Loader2 } from "lucide-react";
15
15
 
16
- export default function ReleaseEscrowButton() {
16
+ export const ReleaseEscrowButton = () => {
17
17
  const { releaseFunds } = useEscrowsMutations();
18
18
  const { selectedEscrow, updateEscrow } = useEscrowContext();
19
19
  const dialogStates = useEscrowDialogs();
@@ -89,4 +89,4 @@ export default function ReleaseEscrowButton() {
89
89
  )}
90
90
  </Button>
91
91
  );
92
- }
92
+ };
@@ -16,10 +16,10 @@ type ResolveDisputeButtonProps = {
16
16
  receiverFunds: number;
17
17
  };
18
18
 
19
- export default function ResolveDisputeButton({
19
+ export const ResolveDisputeButton = ({
20
20
  approverFunds,
21
21
  receiverFunds,
22
- }: ResolveDisputeButtonProps) {
22
+ }: ResolveDisputeButtonProps) => {
23
23
  const { resolveDispute } = useEscrowsMutations();
24
24
  const { selectedEscrow, updateEscrow } = useEscrowContext();
25
25
  const { walletAddress } = useWalletContext();
@@ -103,4 +103,4 @@ export default function ResolveDisputeButton({
103
103
  )}
104
104
  </Button>
105
105
  );
106
- }
106
+ };
@@ -19,15 +19,12 @@ import {
19
19
  import { Loader2 } from "lucide-react";
20
20
  import { useResolveDispute } from "./useResolveDispute";
21
21
  import { useEscrowContext } from "@/components/tw-blocks/providers/EscrowProvider";
22
+ import { formatCurrency } from "../../../../helpers/format.helper";
22
23
 
23
- export default function ResolveDisputeDialog() {
24
+ export const ResolveDisputeDialog = () => {
24
25
  const { form, handleSubmit, isSubmitting } = useResolveDispute();
25
26
  const { selectedEscrow } = useEscrowContext();
26
27
 
27
- function formatCurrency(amount: number, currency: string) {
28
- return `${currency} ${amount.toFixed(2)}`;
29
- }
30
-
31
28
  return (
32
29
  <Dialog>
33
30
  <DialogTrigger asChild>
@@ -120,4 +117,4 @@ export default function ResolveDisputeDialog() {
120
117
  </DialogContent>
121
118
  </Dialog>
122
119
  );
123
- }
120
+ };
@@ -12,7 +12,7 @@ import { Button } from "__UI_BASE__/button";
12
12
  import { useResolveDispute } from "./useResolveDispute";
13
13
  import { Loader2 } from "lucide-react";
14
14
 
15
- export default function ResolveDisputeForm() {
15
+ export const ResolveDisputeForm = () => {
16
16
  const { form, handleSubmit, isSubmitting } = useResolveDispute();
17
17
 
18
18
  return (
@@ -79,4 +79,4 @@ export default function ResolveDisputeForm() {
79
79
  </form>
80
80
  </Form>
81
81
  );
82
- }
82
+ };
@@ -14,7 +14,7 @@ import { useWalletContext } from "@/components/tw-blocks/wallet-kit/WalletProvid
14
14
 
15
15
  export function useResolveDispute() {
16
16
  const { resolveDispute } = useEscrowsMutations();
17
- const { selectedEscrow } = useEscrowContext();
17
+ const { selectedEscrow, updateEscrow } = useEscrowContext();
18
18
  const { walletAddress } = useWalletContext();
19
19
 
20
20
  const form = useForm<ResolveDisputeValues>({
@@ -59,6 +59,19 @@ export function useResolveDispute() {
59
59
  });
60
60
 
61
61
  toast.success("Dispute resolved successfully");
62
+
63
+ updateEscrow({
64
+ ...selectedEscrow,
65
+ flags: {
66
+ ...selectedEscrow?.flags,
67
+ disputed: false,
68
+ resolved: true,
69
+ },
70
+ balance:
71
+ (selectedEscrow?.balance || 0) -
72
+ (Number(payload.approverFunds) + Number(payload.receiverFunds)) ||
73
+ 0,
74
+ });
62
75
  } catch (error) {
63
76
  toast.error(handleError(error as ErrorResponse).message);
64
77
  } finally {
@@ -30,7 +30,7 @@ import {
30
30
  DialogTrigger,
31
31
  } from "__UI_BASE__/dialog";
32
32
 
33
- export default function UpdateEscrowDialog() {
33
+ export const UpdateEscrowDialog = () => {
34
34
  const {
35
35
  form,
36
36
  isSubmitting,
@@ -482,4 +482,4 @@ export default function UpdateEscrowDialog() {
482
482
  </DialogContent>
483
483
  </Dialog>
484
484
  );
485
- }
485
+ };
@@ -23,7 +23,7 @@ import { Trash2, DollarSign, Percent, Loader2 } from "lucide-react";
23
23
  import Link from "next/link";
24
24
  import { trustlineOptions } from "@/components/tw-blocks/wallet-kit/trustlines";
25
25
 
26
- export default function UpdateEscrowForm() {
26
+ export const UpdateEscrowForm = () => {
27
27
  const {
28
28
  form,
29
29
  isSubmitting,
@@ -460,4 +460,4 @@ export default function UpdateEscrowForm() {
460
460
  </form>
461
461
  </Form>
462
462
  );
463
- }
463
+ };
@@ -6,6 +6,7 @@ import { z } from "zod";
6
6
  import {
7
7
  UpdateSingleReleaseEscrowPayload,
8
8
  UpdateSingleReleaseEscrowResponse,
9
+ SingleReleaseMilestone,
9
10
  } from "@trustless-work/escrow/types";
10
11
  import { toast } from "sonner";
11
12
  import { useEscrowContext } from "@/components/tw-blocks/providers/EscrowProvider";
@@ -53,9 +54,11 @@ export function useUpdateEscrow() {
53
54
  releaseSigner: selectedEscrow?.roles?.releaseSigner || "",
54
55
  disputeResolver: selectedEscrow?.roles?.disputeResolver || "",
55
56
  },
56
- milestones: (selectedEscrow?.milestones || []).map((m: any) => ({
57
- description: m?.description || "",
58
- })),
57
+ milestones: (selectedEscrow?.milestones || []).map(
58
+ (m: SingleReleaseMilestone) => ({
59
+ description: m?.description || "",
60
+ })
61
+ ),
59
62
  },
60
63
  mode: "onChange",
61
64
  });
@@ -89,9 +92,11 @@ export function useUpdateEscrow() {
89
92
  releaseSigner: selectedEscrow?.roles?.releaseSigner || "",
90
93
  disputeResolver: selectedEscrow?.roles?.disputeResolver || "",
91
94
  },
92
- milestones: (selectedEscrow?.milestones || []).map((m: any) => ({
93
- description: m?.description || "",
94
- })),
95
+ milestones: (selectedEscrow?.milestones || []).map(
96
+ (m: SingleReleaseMilestone) => ({
97
+ description: m?.description || "",
98
+ })
99
+ ),
95
100
  });
96
101
  }, [selectedEscrow, form]);
97
102
 
@@ -168,7 +173,7 @@ export function useUpdateEscrow() {
168
173
  address: payload.trustline.address,
169
174
  decimals: 10000000,
170
175
  },
171
- roles: payload.roles as any,
176
+ roles: payload.roles,
172
177
  milestones: payload.milestones,
173
178
  },
174
179
  };
@@ -13,9 +13,7 @@ export type DialogStates = {
13
13
  successRelease: DialogState;
14
14
  };
15
15
 
16
- export type StatusStates = {};
17
-
18
- type EscrowDialogsContextType = DialogStates & StatusStates;
16
+ type EscrowDialogsContextType = DialogStates;
19
17
 
20
18
  const EscrowDialogsContext = createContext<
21
19
  EscrowDialogsContextType | undefined
@@ -10,7 +10,28 @@ import {
10
10
  ReactNode,
11
11
  useCallback,
12
12
  } from "react";
13
- import { GetEscrowsFromIndexerResponse as Escrow } from "@trustless-work/escrow/types";
13
+ import {
14
+ GetEscrowsFromIndexerResponse as Escrow,
15
+ Trustline,
16
+ } from "@trustless-work/escrow/types";
17
+
18
+ type SingleEscrowPayload = Omit<
19
+ Escrow,
20
+ "type" | "updatedAt" | "createdAt" | "user" | "trustline"
21
+ > &
22
+ Partial<Pick<Escrow, "type" | "updatedAt" | "createdAt" | "user">> & {
23
+ trustline: Trustline & { name?: string };
24
+ };
25
+
26
+ type MultiEscrowPayload = Omit<
27
+ Escrow,
28
+ "type" | "updatedAt" | "createdAt" | "user" | "trustline" | "amount"
29
+ > &
30
+ Partial<
31
+ Pick<Escrow, "type" | "updatedAt" | "createdAt" | "user" | "amount">
32
+ > & {
33
+ trustline: Trustline & { name?: string };
34
+ };
14
35
 
15
36
  type EscrowContextType = {
16
37
  selectedEscrow: Escrow | null;
@@ -21,7 +42,9 @@ type EscrowContextType = {
21
42
  ) => void;
22
43
  setEscrowField: <K extends keyof Escrow>(key: K, value: Escrow[K]) => void;
23
44
  clearEscrow: () => void;
24
- setSelectedEscrow: (escrow?: Escrow) => void;
45
+ setSelectedEscrow: (
46
+ escrow?: SingleEscrowPayload | MultiEscrowPayload
47
+ ) => void;
25
48
  setUserRolesInEscrow: (roles: string[]) => void;
26
49
  };
27
50
 
@@ -134,8 +157,8 @@ export const EscrowProvider = ({ children }: { children: ReactNode }) => {
134
157
  updateEscrow,
135
158
  setEscrowField,
136
159
  clearEscrow,
137
- setSelectedEscrow: (value?: Escrow) =>
138
- setSelectedEscrowState(value ?? null),
160
+ setSelectedEscrow: (value?: SingleEscrowPayload | MultiEscrowPayload) =>
161
+ setSelectedEscrowState((value as unknown as Escrow) ?? null),
139
162
  setUserRolesInEscrow,
140
163
  userRolesInEscrow,
141
164
  }}
@@ -6,7 +6,7 @@ import {
6
6
  development,
7
7
 
8
8
  // mainnet environment = "https://api.trustlesswork.com"
9
- mainNet,
9
+ // mainNet,
10
10
  TrustlessWorkConfig,
11
11
  } from "@trustless-work/escrow";
12
12
 
@@ -1,191 +0,0 @@
1
- import { GetEscrowsFromIndexerResponse } from "@trustless-work/escrow/types";
2
- import { cn } from "@/lib/utils";
3
- import {
4
- MultiReleaseMilestone,
5
- SingleReleaseMilestone,
6
- } from "@trustless-work/escrow";
7
-
8
- type Escrow = {
9
- [K in keyof Omit<
10
- GetEscrowsFromIndexerResponse,
11
- "type" | "updatedAt" | "createdAt" | "user"
12
- >]: K extends "trustline"
13
- ? Omit<NonNullable<GetEscrowsFromIndexerResponse["trustline"]>, "name">
14
- : GetEscrowsFromIndexerResponse[K];
15
- };
16
-
17
- interface ProgressEscrowProps {
18
- escrow: Escrow;
19
- className?: string;
20
- }
21
-
22
- const ProgressCircle = ({
23
- percentage,
24
- color,
25
- size = 44,
26
- strokeWidth = 3,
27
- }: {
28
- percentage: number;
29
- color: string;
30
- size?: number;
31
- strokeWidth?: number;
32
- }) => {
33
- const radius = (size - strokeWidth) / 2;
34
- const circumference = 2 * Math.PI * radius;
35
- const strokeDashoffset = circumference - (percentage / 100) * circumference;
36
-
37
- return (
38
- <div className="relative inline-flex">
39
- <svg
40
- width={size}
41
- height={size}
42
- viewBox={`0 0 ${size} ${size}`}
43
- className="transform -rotate-90"
44
- >
45
- <circle
46
- cx={size / 2}
47
- cy={size / 2}
48
- r={radius}
49
- fill="none"
50
- stroke="rgba(226, 232, 240, 0.3)"
51
- strokeWidth={strokeWidth}
52
- />
53
- <circle
54
- cx={size / 2}
55
- cy={size / 2}
56
- r={radius}
57
- fill="none"
58
- stroke={color}
59
- strokeWidth={strokeWidth}
60
- strokeDasharray={circumference}
61
- strokeDashoffset={strokeDashoffset}
62
- strokeLinecap="round"
63
- />
64
- </svg>
65
- <div className="absolute inset-0 flex items-center justify-center text-xs font-medium">
66
- {Math.round(percentage)}%
67
- </div>
68
- </div>
69
- );
70
- };
71
-
72
- const ProgressEscrow = ({ escrow, className }: ProgressEscrowProps) => {
73
- const completedMilestones = escrow.milestones.filter(
74
- (milestone) => milestone.status === "completed"
75
- ).length;
76
- const approvedMilestones = escrow.milestones.filter(
77
- (milestone: SingleReleaseMilestone | MultiReleaseMilestone) =>
78
- ("flags" in milestone && milestone.flags?.approved === true) ||
79
- (!("flags" in milestone) &&
80
- (milestone as SingleReleaseMilestone).approved === true)
81
- ).length;
82
- const disputedMilestones = escrow.milestones.filter(
83
- (milestone: SingleReleaseMilestone | MultiReleaseMilestone) =>
84
- "flags" in milestone && milestone.flags?.disputed === true
85
- ).length;
86
- const resolvedMilestones = escrow.milestones.filter(
87
- (milestone: SingleReleaseMilestone | MultiReleaseMilestone) =>
88
- "flags" in milestone && milestone.flags?.resolved === true
89
- ).length;
90
- const releasedMilestones = escrow.milestones.filter(
91
- (milestone: SingleReleaseMilestone | MultiReleaseMilestone) =>
92
- "flags" in milestone && milestone.flags?.released === true
93
- ).length;
94
- const totalMilestones = escrow.milestones.length;
95
-
96
- const progressPercentageCompleted =
97
- totalMilestones > 0 ? (completedMilestones / totalMilestones) * 100 : 0;
98
- const progressPercentageApproved =
99
- totalMilestones > 0 ? (approvedMilestones / totalMilestones) * 100 : 0;
100
- const progressPercentageDisputed =
101
- totalMilestones > 0 ? (disputedMilestones / totalMilestones) * 100 : 0;
102
- const progressPercentageResolved =
103
- totalMilestones > 0 ? (resolvedMilestones / totalMilestones) * 100 : 0;
104
- const progressPercentageReleased =
105
- totalMilestones > 0 ? (releasedMilestones / totalMilestones) * 100 : 0;
106
-
107
- const shouldHideProgress = escrow.flags?.released || escrow.flags?.resolved;
108
- const isMultiRelease =
109
- escrow.milestones[0] && "flags" in escrow.milestones[0];
110
-
111
- if (shouldHideProgress || totalMilestones === 0) {
112
- return null;
113
- }
114
-
115
- return (
116
- <div className={cn("space-y-4 px-10 w-full", className)}>
117
- <div className="flex flex-wrap items-center justify-center gap-6">
118
- <div className="flex items-center gap-3">
119
- <ProgressCircle
120
- percentage={progressPercentageCompleted}
121
- color="#006be4"
122
- />
123
- <div className="text-xs">
124
- <div className="font-medium">Completed</div>
125
- <div className="text-muted-foreground">
126
- {completedMilestones}/{totalMilestones}
127
- </div>
128
- </div>
129
- </div>
130
-
131
- <div className="flex items-center gap-3">
132
- <ProgressCircle
133
- percentage={progressPercentageApproved}
134
- color="#15803d"
135
- />
136
- <div className="text-xs">
137
- <div className="font-medium">Approved</div>
138
- <div className="text-muted-foreground">
139
- {approvedMilestones}/{totalMilestones}
140
- </div>
141
- </div>
142
- </div>
143
-
144
- {isMultiRelease && (
145
- <>
146
- <div className="flex items-center gap-3">
147
- <ProgressCircle
148
- percentage={progressPercentageDisputed}
149
- color="#dc2626"
150
- />
151
- <div className="text-xs">
152
- <div className="font-medium">Disputed</div>
153
- <div className="text-muted-foreground">
154
- {disputedMilestones}/{totalMilestones}
155
- </div>
156
- </div>
157
- </div>
158
-
159
- <div className="flex items-center gap-3">
160
- <ProgressCircle
161
- percentage={progressPercentageResolved}
162
- color="#15803d"
163
- />
164
- <div className="text-xs">
165
- <div className="font-medium">Resolved</div>
166
- <div className="text-muted-foreground">
167
- {resolvedMilestones}/{totalMilestones}
168
- </div>
169
- </div>
170
- </div>
171
-
172
- <div className="flex items-center gap-3">
173
- <ProgressCircle
174
- percentage={progressPercentageReleased}
175
- color="#15803d"
176
- />
177
- <div className="text-xs">
178
- <div className="font-medium">Released</div>
179
- <div className="text-muted-foreground">
180
- {releasedMilestones}/{totalMilestones}
181
- </div>
182
- </div>
183
- </div>
184
- </>
185
- )}
186
- </div>
187
- </div>
188
- );
189
- };
190
-
191
- export default ProgressEscrow;