@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,78 @@
1
+ import * as React from "react";
2
+ import { Button } from "__UI_BASE__/button";
3
+ import { useEscrowsMutations } from "@/components/tw-blocks/tanstak/useEscrowsMutations";
4
+ import { useWalletContext } from "@/components/tw-blocks/wallet-kit/WalletProvider";
5
+ import { ChangeMilestoneStatusPayload } from "@trustless-work/escrow/types";
6
+ import { toast } from "sonner";
7
+ import {
8
+ ErrorResponse,
9
+ handleError,
10
+ } from "@/components/tw-blocks/handle-errors/handle";
11
+ import { useEscrowContext } from "../../../escrow-context/EscrowProvider";
12
+ import { Loader2 } from "lucide-react";
13
+
14
+ type ChangeMilestoneStatusButtonProps = {
15
+ status: string;
16
+ evidence?: string;
17
+ milestoneIndex: number | string;
18
+ };
19
+
20
+ export default function ChangeMilestoneStatusButton({
21
+ status,
22
+ evidence,
23
+ milestoneIndex,
24
+ }: ChangeMilestoneStatusButtonProps) {
25
+ const { changeMilestoneStatus } = useEscrowsMutations();
26
+ const { selectedEscrow } = useEscrowContext();
27
+ const { walletAddress } = useWalletContext();
28
+ const [isSubmitting, setIsSubmitting] = React.useState(false);
29
+
30
+ async function handleClick() {
31
+ try {
32
+ if (!status || status.trim().length === 0) {
33
+ toast.error("Status is required");
34
+ return;
35
+ }
36
+
37
+ setIsSubmitting(true);
38
+
39
+ const payload: ChangeMilestoneStatusPayload = {
40
+ contractId: selectedEscrow?.contractId || "",
41
+ milestoneIndex: String(milestoneIndex),
42
+ newStatus: status,
43
+ newEvidence: evidence || undefined,
44
+ serviceProvider: walletAddress || "",
45
+ };
46
+
47
+ await changeMilestoneStatus.mutateAsync({
48
+ payload,
49
+ type: "single-release",
50
+ address: walletAddress || "",
51
+ });
52
+
53
+ toast.success("Milestone status updated successfully");
54
+ } catch (error) {
55
+ toast.error(handleError(error as ErrorResponse).message);
56
+ } finally {
57
+ setIsSubmitting(false);
58
+ }
59
+ }
60
+
61
+ return (
62
+ <Button
63
+ type="button"
64
+ disabled={isSubmitting}
65
+ onClick={handleClick}
66
+ className="cursor-pointer w-full"
67
+ >
68
+ {isSubmitting ? (
69
+ <div className="flex items-center">
70
+ <Loader2 className="h-5 w-5 animate-spin" />
71
+ <span className="ml-2">Updating...</span>
72
+ </div>
73
+ ) : (
74
+ "Update Status"
75
+ )}
76
+ </Button>
77
+ );
78
+ }
@@ -0,0 +1,167 @@
1
+ import * as React from "react";
2
+ import {
3
+ Form,
4
+ FormField,
5
+ FormItem,
6
+ FormLabel,
7
+ FormControl,
8
+ FormMessage,
9
+ } from "__UI_BASE__/form";
10
+ import { Input } from "__UI_BASE__/input";
11
+ import { Textarea } from "__UI_BASE__/textarea";
12
+ import { Button } from "__UI_BASE__/button";
13
+ import {
14
+ Dialog,
15
+ DialogContent,
16
+ DialogHeader,
17
+ DialogTitle,
18
+ DialogTrigger,
19
+ } from "__UI_BASE__/dialog";
20
+ import { Loader2 } from "lucide-react";
21
+ import { useChangeMilestoneStatus } from "./useChangeMilestoneStatus";
22
+ import { useEscrowContext } from "../../../escrow-context/EscrowProvider";
23
+ import {
24
+ Select,
25
+ SelectContent,
26
+ SelectItem,
27
+ SelectTrigger,
28
+ SelectValue,
29
+ } from "__UI_BASE__/select";
30
+
31
+ export default function ChangeMilestoneStatusDialog({
32
+ showSelectMilestone = false,
33
+ milestoneIndex,
34
+ }: {
35
+ showSelectMilestone?: boolean;
36
+ milestoneIndex?: number | string;
37
+ }) {
38
+ const { form, handleSubmit, isSubmitting } = useChangeMilestoneStatus();
39
+ const { selectedEscrow } = useEscrowContext();
40
+
41
+ React.useEffect(() => {
42
+ if (
43
+ !showSelectMilestone &&
44
+ milestoneIndex !== undefined &&
45
+ milestoneIndex !== null
46
+ ) {
47
+ form.setValue("milestoneIndex", String(milestoneIndex));
48
+ }
49
+ }, [showSelectMilestone, milestoneIndex, form]);
50
+
51
+ return (
52
+ <Dialog>
53
+ <DialogTrigger asChild>
54
+ <Button type="button" className="cursor-pointer w-full">
55
+ Update Status
56
+ </Button>
57
+ </DialogTrigger>
58
+ <DialogContent>
59
+ <DialogHeader>
60
+ <DialogTitle>Change Milestone Status</DialogTitle>
61
+ </DialogHeader>
62
+ <Form {...form}>
63
+ <form
64
+ onSubmit={handleSubmit}
65
+ className="flex flex-col space-y-6 w-full"
66
+ >
67
+ <div
68
+ className={`grid grid-cols-1 ${
69
+ showSelectMilestone ? "lg:grid-cols-2" : "lg:grid-cols-1"
70
+ } gap-4`}
71
+ >
72
+ {showSelectMilestone && (
73
+ <FormField
74
+ control={form.control}
75
+ name="milestoneIndex"
76
+ render={({ field }) => (
77
+ <FormItem>
78
+ <FormLabel className="flex items-center">
79
+ Milestone
80
+ <span className="text-destructive ml-1">*</span>
81
+ </FormLabel>
82
+ <FormControl>
83
+ <Select
84
+ value={field.value}
85
+ onValueChange={(e) => {
86
+ field.onChange(e);
87
+ }}
88
+ >
89
+ <SelectTrigger className="w-full">
90
+ <SelectValue placeholder="Select milestone" />
91
+ </SelectTrigger>
92
+ <SelectContent>
93
+ {(selectedEscrow?.milestones || []).map(
94
+ (m, idx) => (
95
+ <SelectItem
96
+ key={`ms-${idx}`}
97
+ value={String(idx)}
98
+ >
99
+ {m?.description || `Milestone ${idx + 1}`}
100
+ </SelectItem>
101
+ )
102
+ )}
103
+ </SelectContent>
104
+ </Select>
105
+ </FormControl>
106
+ <FormMessage />
107
+ </FormItem>
108
+ )}
109
+ />
110
+ )}
111
+
112
+ <FormField
113
+ control={form.control}
114
+ name="status"
115
+ render={({ field }) => (
116
+ <FormItem>
117
+ <FormLabel className="flex items-center">
118
+ Status<span className="text-destructive ml-1">*</span>
119
+ </FormLabel>
120
+ <FormControl>
121
+ <Input placeholder="Enter new status" {...field} />
122
+ </FormControl>
123
+ <FormMessage />
124
+ </FormItem>
125
+ )}
126
+ />
127
+ </div>
128
+
129
+ <FormField
130
+ control={form.control}
131
+ name="evidence"
132
+ render={({ field }) => (
133
+ <FormItem>
134
+ <FormLabel>Evidence</FormLabel>
135
+ <FormControl>
136
+ <Textarea
137
+ placeholder="Enter evidence (optional)"
138
+ {...field}
139
+ />
140
+ </FormControl>
141
+ <FormMessage />
142
+ </FormItem>
143
+ )}
144
+ />
145
+
146
+ <div className="mt-4">
147
+ <Button
148
+ type="submit"
149
+ disabled={isSubmitting}
150
+ className="cursor-pointer"
151
+ >
152
+ {isSubmitting ? (
153
+ <div className="flex items-center">
154
+ <Loader2 className="h-5 w-5 animate-spin" />
155
+ <span className="ml-2">Updating...</span>
156
+ </div>
157
+ ) : (
158
+ "Update"
159
+ )}
160
+ </Button>
161
+ </div>
162
+ </form>
163
+ </Form>
164
+ </DialogContent>
165
+ </Dialog>
166
+ );
167
+ }
@@ -0,0 +1,114 @@
1
+ import * as React from "react";
2
+ import {
3
+ Form,
4
+ FormField,
5
+ FormItem,
6
+ FormLabel,
7
+ FormControl,
8
+ FormMessage,
9
+ } from "__UI_BASE__/form";
10
+ import { Input } from "__UI_BASE__/input";
11
+ import { Textarea } from "__UI_BASE__/textarea";
12
+ import { Button } from "__UI_BASE__/button";
13
+ import { useChangeMilestoneStatus } from "./useChangeMilestoneStatus";
14
+ import { Loader2 } from "lucide-react";
15
+ import { useEscrowContext } from "../../../escrow-context/EscrowProvider";
16
+ import {
17
+ Select,
18
+ SelectContent,
19
+ SelectItem,
20
+ SelectTrigger,
21
+ SelectValue,
22
+ } from "__UI_BASE__/select";
23
+
24
+ export default function ChangeMilestoneStatusForm() {
25
+ const { form, handleSubmit, isSubmitting } = useChangeMilestoneStatus();
26
+ const { selectedEscrow } = useEscrowContext();
27
+
28
+ return (
29
+ <Form {...form}>
30
+ <form onSubmit={handleSubmit} className="flex flex-col space-y-6 w-full">
31
+ <div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
32
+ <FormField
33
+ control={form.control}
34
+ name="milestoneIndex"
35
+ render={({ field }) => (
36
+ <FormItem>
37
+ <FormLabel className="flex items-center">
38
+ Milestone<span className="text-destructive ml-1">*</span>
39
+ </FormLabel>
40
+ <FormControl>
41
+ <Select
42
+ value={field.value}
43
+ onValueChange={(e) => {
44
+ field.onChange(e);
45
+ }}
46
+ >
47
+ <SelectTrigger className="w-full">
48
+ <SelectValue placeholder="Select milestone" />
49
+ </SelectTrigger>
50
+ <SelectContent>
51
+ {(selectedEscrow?.milestones || []).map((m, idx) => (
52
+ <SelectItem key={`ms-${idx}`} value={String(idx)}>
53
+ {m?.description || `Milestone ${idx + 1}`}
54
+ </SelectItem>
55
+ ))}
56
+ </SelectContent>
57
+ </Select>
58
+ </FormControl>
59
+ <FormMessage />
60
+ </FormItem>
61
+ )}
62
+ />
63
+
64
+ <FormField
65
+ control={form.control}
66
+ name="status"
67
+ render={({ field }) => (
68
+ <FormItem>
69
+ <FormLabel className="flex items-center">
70
+ Status<span className="text-destructive ml-1">*</span>
71
+ </FormLabel>
72
+ <FormControl>
73
+ <Input placeholder="Enter new status" {...field} />
74
+ </FormControl>
75
+ <FormMessage />
76
+ </FormItem>
77
+ )}
78
+ />
79
+ </div>
80
+
81
+ <FormField
82
+ control={form.control}
83
+ name="evidence"
84
+ render={({ field }) => (
85
+ <FormItem>
86
+ <FormLabel>Evidence</FormLabel>
87
+ <FormControl>
88
+ <Textarea placeholder="Enter evidence (optional)" {...field} />
89
+ </FormControl>
90
+ <FormMessage />
91
+ </FormItem>
92
+ )}
93
+ />
94
+
95
+ <div className="mt-4">
96
+ <Button
97
+ type="submit"
98
+ disabled={isSubmitting}
99
+ className="cursor-pointer"
100
+ >
101
+ {isSubmitting ? (
102
+ <div className="flex items-center">
103
+ <Loader2 className="h-5 w-5 animate-spin" />
104
+ <span className="ml-2">Updating...</span>
105
+ </div>
106
+ ) : (
107
+ "Update"
108
+ )}
109
+ </Button>
110
+ </div>
111
+ </form>
112
+ </Form>
113
+ );
114
+ }
@@ -0,0 +1,15 @@
1
+ import { z } from "zod";
2
+
3
+ export const changeMilestoneStatusSchema = z.object({
4
+ milestoneIndex: z
5
+ .string({ required_error: "Milestone is required" })
6
+ .min(1, { message: "Milestone is required" }),
7
+ status: z
8
+ .string({ required_error: "Status is required" })
9
+ .min(1, { message: "Status is required" }),
10
+ evidence: z.string().optional(),
11
+ });
12
+
13
+ export type ChangeMilestoneStatusValues = z.infer<
14
+ typeof changeMilestoneStatusSchema
15
+ >;
@@ -0,0 +1,77 @@
1
+ import * as React from "react";
2
+ import { useForm } from "react-hook-form";
3
+ import { zodResolver } from "@hookform/resolvers/zod";
4
+ import {
5
+ changeMilestoneStatusSchema,
6
+ type ChangeMilestoneStatusValues,
7
+ } from "./schema";
8
+ import { toast } from "sonner";
9
+ import { ChangeMilestoneStatusPayload } from "@trustless-work/escrow";
10
+ import { useEscrowContext } from "../../../escrow-context/EscrowProvider";
11
+ import { useEscrowsMutations } from "@/components/tw-blocks/tanstak/useEscrowsMutations";
12
+ import {
13
+ ErrorResponse,
14
+ handleError,
15
+ } from "@/components/tw-blocks/handle-errors/handle";
16
+ import { useWalletContext } from "@/components/tw-blocks/wallet-kit/WalletProvider";
17
+
18
+ export function useChangeMilestoneStatus() {
19
+ const { changeMilestoneStatus } = useEscrowsMutations();
20
+ const { selectedEscrow, updateEscrow } = useEscrowContext();
21
+ const { walletAddress } = useWalletContext();
22
+
23
+ const form = useForm<ChangeMilestoneStatusValues>({
24
+ resolver: zodResolver(changeMilestoneStatusSchema),
25
+ defaultValues: {
26
+ milestoneIndex: "0",
27
+ status: "",
28
+ evidence: "",
29
+ },
30
+ mode: "onChange",
31
+ });
32
+
33
+ const [isSubmitting, setIsSubmitting] = React.useState(false);
34
+
35
+ const handleSubmit = form.handleSubmit(async (payload) => {
36
+ try {
37
+ setIsSubmitting(true);
38
+
39
+ const finalPayload: ChangeMilestoneStatusPayload = {
40
+ contractId: selectedEscrow?.contractId || "",
41
+ milestoneIndex: payload.milestoneIndex,
42
+ newStatus: payload.status,
43
+ newEvidence: payload.evidence || undefined,
44
+ serviceProvider: walletAddress || "",
45
+ };
46
+
47
+ await changeMilestoneStatus.mutateAsync({
48
+ payload: finalPayload,
49
+ type: "single-release",
50
+ address: walletAddress || "",
51
+ });
52
+
53
+ toast.success("Milestone status updated successfully");
54
+
55
+ updateEscrow({
56
+ ...selectedEscrow,
57
+ milestones: selectedEscrow?.milestones.map((milestone, index) => {
58
+ if (index === Number(payload.milestoneIndex)) {
59
+ return {
60
+ ...milestone,
61
+ status: payload.status,
62
+ evidence: payload.evidence || undefined,
63
+ };
64
+ }
65
+ return milestone;
66
+ }),
67
+ });
68
+ } catch (error) {
69
+ toast.error(handleError(error as ErrorResponse).message);
70
+ } finally {
71
+ setIsSubmitting(false);
72
+ form.reset();
73
+ }
74
+ });
75
+
76
+ return { form, handleSubmit, isSubmitting };
77
+ }
@@ -0,0 +1,68 @@
1
+ import * as React from "react";
2
+ import { Button } from "__UI_BASE__/button";
3
+ import { useEscrowsMutations } from "@/components/tw-blocks/tanstak/useEscrowsMutations";
4
+ import { useWalletContext } from "@/components/tw-blocks/wallet-kit/WalletProvider";
5
+ import { SingleReleaseStartDisputePayload } from "@trustless-work/escrow/types";
6
+ import { toast } from "sonner";
7
+ import {
8
+ ErrorResponse,
9
+ handleError,
10
+ } from "@/components/tw-blocks/handle-errors/handle";
11
+ import { useEscrowContext } from "../../../escrow-context/EscrowProvider";
12
+ import { Loader2 } from "lucide-react";
13
+
14
+ export default function DisputeEscrowButton() {
15
+ const { startDispute } = useEscrowsMutations();
16
+ const { selectedEscrow, updateEscrow } = useEscrowContext();
17
+ const { walletAddress } = useWalletContext();
18
+ const [isSubmitting, setIsSubmitting] = React.useState(false);
19
+
20
+ async function handleClick() {
21
+ try {
22
+ setIsSubmitting(true);
23
+
24
+ const payload: SingleReleaseStartDisputePayload = {
25
+ contractId: selectedEscrow?.contractId || "",
26
+ signer: walletAddress || "",
27
+ };
28
+
29
+ await startDispute.mutateAsync({
30
+ payload,
31
+ type: "single-release",
32
+ address: walletAddress || "",
33
+ });
34
+
35
+ toast.success("Escrow disputed successfully");
36
+
37
+ updateEscrow({
38
+ ...selectedEscrow,
39
+ flags: {
40
+ ...selectedEscrow?.flags,
41
+ disputed: true,
42
+ },
43
+ });
44
+ } catch (error) {
45
+ toast.error(handleError(error as ErrorResponse).message);
46
+ } finally {
47
+ setIsSubmitting(false);
48
+ }
49
+ }
50
+
51
+ return (
52
+ <Button
53
+ type="button"
54
+ disabled={isSubmitting || !selectedEscrow?.balance}
55
+ onClick={handleClick}
56
+ className="cursor-pointer w-full"
57
+ >
58
+ {isSubmitting ? (
59
+ <div className="flex items-center">
60
+ <Loader2 className="h-5 w-5 animate-spin" />
61
+ <span className="ml-2">Disputing...</span>
62
+ </div>
63
+ ) : (
64
+ "Dispute Escrow"
65
+ )}
66
+ </Button>
67
+ );
68
+ }
@@ -0,0 +1,84 @@
1
+ import * as React from "react";
2
+ import { Button } from "__UI_BASE__/button";
3
+ import { useEscrowsMutations } from "@/components/tw-blocks/tanstak/useEscrowsMutations";
4
+ import { useWalletContext } from "@/components/tw-blocks/wallet-kit/WalletProvider";
5
+ import { FundEscrowPayload } from "@trustless-work/escrow/types";
6
+ import { toast } from "sonner";
7
+ import {
8
+ ErrorResponse,
9
+ handleError,
10
+ } from "@/components/tw-blocks/handle-errors/handle";
11
+ import { useEscrowContext } from "../../../escrow-context/EscrowProvider";
12
+ import { cn } from "@/lib/utils";
13
+ import { Loader2 } from "lucide-react";
14
+
15
+ type FundEscrowButtonProps = {
16
+ amount: number;
17
+ className?: string;
18
+ };
19
+
20
+ export default function FundEscrowButton({
21
+ amount,
22
+ className,
23
+ }: FundEscrowButtonProps) {
24
+ const { fundEscrow } = useEscrowsMutations();
25
+ const { selectedEscrow, updateEscrow } = useEscrowContext();
26
+ const { walletAddress } = useWalletContext();
27
+ const [isSubmitting, setIsSubmitting] = React.useState(false);
28
+
29
+ async function handleClick() {
30
+ try {
31
+ if (!amount || Number.isNaN(amount)) {
32
+ toast.error("Amount is required");
33
+ return;
34
+ }
35
+
36
+ if (amount <= 0) {
37
+ toast.error("Amount must be greater than 0");
38
+ return;
39
+ }
40
+
41
+ setIsSubmitting(true);
42
+
43
+ const payload: FundEscrowPayload = {
44
+ amount: Number(amount),
45
+ contractId: selectedEscrow?.contractId || "",
46
+ signer: walletAddress || "",
47
+ };
48
+
49
+ await fundEscrow.mutateAsync({
50
+ payload,
51
+ type: "single-release",
52
+ address: walletAddress || "",
53
+ });
54
+
55
+ updateEscrow({
56
+ ...selectedEscrow,
57
+ amount: (selectedEscrow?.amount || 0) + payload.amount,
58
+ });
59
+ toast.success("Escrow funded successfully");
60
+ } catch (error) {
61
+ toast.error(handleError(error as ErrorResponse).message);
62
+ } finally {
63
+ setIsSubmitting(false);
64
+ }
65
+ }
66
+
67
+ return (
68
+ <Button
69
+ type="button"
70
+ disabled={isSubmitting}
71
+ onClick={handleClick}
72
+ className={cn(className, "cursor-pointer w-full")}
73
+ >
74
+ {isSubmitting ? (
75
+ <div className="flex items-center">
76
+ <Loader2 className="h-5 w-5 animate-spin" />
77
+ <span className="ml-2">Funding...</span>
78
+ </div>
79
+ ) : (
80
+ "Fund"
81
+ )}
82
+ </Button>
83
+ );
84
+ }
@@ -0,0 +1,77 @@
1
+ import * as React from "react";
2
+ import {
3
+ Form,
4
+ FormField,
5
+ FormItem,
6
+ FormLabel,
7
+ FormControl,
8
+ FormMessage,
9
+ } from "__UI_BASE__/form";
10
+ import { Input } from "__UI_BASE__/input";
11
+ import { Button } from "__UI_BASE__/button";
12
+ import {
13
+ Dialog,
14
+ DialogContent,
15
+ DialogHeader,
16
+ DialogTitle,
17
+ DialogTrigger,
18
+ } from "__UI_BASE__/dialog";
19
+ import { Loader2 } from "lucide-react";
20
+ import { useFundEscrow } from "./useFundEscrow";
21
+
22
+ export default function FundEscrowDialog() {
23
+ const { form, handleSubmit, isSubmitting } = useFundEscrow();
24
+
25
+ return (
26
+ <Dialog>
27
+ <DialogTrigger asChild>
28
+ <Button type="button" className="cursor-pointer w-full">
29
+ Fund
30
+ </Button>
31
+ </DialogTrigger>
32
+ <DialogContent>
33
+ <DialogHeader>
34
+ <DialogTitle>Fund Escrow</DialogTitle>
35
+ </DialogHeader>
36
+ <Form {...form}>
37
+ <form onSubmit={handleSubmit}>
38
+ <FormField
39
+ control={form.control}
40
+ name="amount"
41
+ render={({ field }) => (
42
+ <FormItem>
43
+ <FormLabel>Amount</FormLabel>
44
+ <FormControl>
45
+ <Input
46
+ type="number"
47
+ placeholder="Enter amount"
48
+ {...field}
49
+ />
50
+ </FormControl>
51
+ <FormMessage />
52
+ </FormItem>
53
+ )}
54
+ />
55
+
56
+ <div className="mt-4">
57
+ <Button
58
+ type="submit"
59
+ disabled={isSubmitting}
60
+ className="cursor-pointer"
61
+ >
62
+ {isSubmitting ? (
63
+ <div className="flex items-center">
64
+ <Loader2 className="h-5 w-5 animate-spin" />
65
+ <span className="ml-2">Funding...</span>
66
+ </div>
67
+ ) : (
68
+ "Fund"
69
+ )}
70
+ </Button>
71
+ </div>
72
+ </form>
73
+ </Form>
74
+ </DialogContent>
75
+ </Dialog>
76
+ );
77
+ }