Skip to content

Commit 932b9db

Browse files
committed
Adding initial form
1 parent 706156b commit 932b9db

File tree

3 files changed

+177
-51
lines changed

3 files changed

+177
-51
lines changed
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
"use server";
2+
import { getAuthToken } from "app/(app)/api/lib/getAuthToken";
3+
4+
export type TokenMetadata = {
5+
name: string;
6+
symbol: string;
7+
address: string;
8+
decimals: number;
9+
chainId: number;
10+
iconUri?: string;
11+
};
12+
13+
export async function addUniversalBridgeTokenRoute(props: {
14+
chainId?: number;
15+
tokenAddress?: string;
16+
}) {
17+
const authToken = await getAuthToken();
18+
const url = new URL(`${UB_BASE_URL}/v1/tokens`);
19+
20+
const res = await fetch(url.toString(), {
21+
method: "POST",
22+
headers: {
23+
"Content-Type": "application/json",
24+
Authorization: `Bearer ${authToken}`,
25+
} as Record<string, string>,
26+
body: JSON.stringify({
27+
chainId: props.chainId,
28+
tokenAddress: props.tokenAddress,
29+
}),
30+
});
31+
32+
if (!res.ok) {
33+
const text = await res.text();
34+
throw new Error(text);
35+
}
36+
37+
const json = await res.json();
38+
return json.data as Array<TokenMetadata>;
39+
}
Lines changed: 137 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"use client";
22

33
import type { Project } from "@/api/projects";
4+
import { addUniversalBridgeTokenRoute } from "@/api/universal-bridge/addRoute"; // Adjust the import path
45
import type { Fee } from "@/api/universal-bridge/developer";
56
import { RouteDiscoveryCard } from "@/components/blocks/RouteDiscoveryCard";
67
import {
@@ -12,6 +13,7 @@ import {
1213
} from "@/components/ui/form";
1314
import { Input } from "@/components/ui/input";
1415
import { zodResolver } from "@hookform/resolvers/zod";
16+
import { useMutation } from "@tanstack/react-query";
1517
import { NetworkSelectorButton } from "components/selects/NetworkSelectorButton";
1618
import {
1719
type RouteDiscoveryValidationSchema,
@@ -20,95 +22,179 @@ import {
2022
import { useTrack } from "hooks/analytics/useTrack";
2123
import { useState } from "react";
2224
import { useForm } from "react-hook-form";
25+
import { toast } from "sonner";
2326

24-
interface PayConfigProps {
27+
interface RouteDiscoveryProps {
2528
project: Project;
2629
teamId: string;
2730
teamSlug: string;
2831
fees: Fee;
2932
}
3033

31-
const TRACKING_CATEGORY = "pay";
34+
const TRACKING_CATEGORY = "token_discovery";
3235

33-
export const RouteDiscovery: React.FC<PayConfigProps> = (props) => {
36+
export const RouteDiscovery: React.FC<RouteDiscoveryProps> = () => {
3437
const [isSubmitSuccess, setIsSubmitSuccess] = useState(false);
38+
const [isSubmitFail, setIsSubmitFailed] = useState(false);
39+
40+
// State to track the selected chain ID directly from the NetworkSelectorButton
41+
const [selectedChainId, setSelectedChainId] = useState<number | undefined>(
42+
undefined,
43+
);
44+
3545
const form = useForm<RouteDiscoveryValidationSchema>({
3646
resolver: zodResolver(routeDiscoveryValidationSchema),
3747
defaultValues: {
48+
chainId: 1,
3849
tokenAddress: "",
3950
},
4051
});
4152

4253
const trackEvent = useTrack();
4354

55+
const submitDiscoveryMutation = useMutation({
56+
mutationFn: async (values: {
57+
tokenAddress: string;
58+
}) => {
59+
try {
60+
// Call the API to add the route
61+
const result = await addUniversalBridgeTokenRoute({
62+
chainId: selectedChainId,
63+
tokenAddress: values.tokenAddress,
64+
});
65+
66+
return result;
67+
} catch (error) {
68+
console.error("Error adding route:", error);
69+
throw error; // Re-throw to trigger onError handler
70+
}
71+
},
72+
});
73+
4474
const handleSubmit = form.handleSubmit(
45-
() => {
46-
console.log("Button pressed");
47-
setIsSubmitSuccess(true);
75+
({ tokenAddress }) => {
76+
console.log("selectedChainId", selectedChainId);
77+
submitDiscoveryMutation.mutate(
78+
{
79+
tokenAddress,
80+
},
81+
{
82+
onSuccess: (data) => {
83+
setIsSubmitSuccess(true);
84+
toast.success("Token submitted for discovery");
85+
console.log("Token route added successfully:", data);
86+
trackEvent({
87+
category: TRACKING_CATEGORY,
88+
action: "token-discovery-submit",
89+
label: "success",
90+
data: {
91+
tokenAddress,
92+
tokenCount: data?.length || 0,
93+
},
94+
});
95+
},
96+
onError: (err) => {
97+
setIsSubmitFailed(true);
98+
toast.error("Token Submission Failed");
99+
console.error("Token route addition failed:", err);
100+
101+
// Get appropriate error message
102+
let errorMessage = "An unknown error occurred";
103+
if (err instanceof Error) {
104+
errorMessage = err.message;
105+
}
106+
107+
trackEvent({
108+
category: TRACKING_CATEGORY,
109+
action: "token-discovery-submit",
110+
label: "error",
111+
error: errorMessage,
112+
});
113+
},
114+
},
115+
);
48116
},
49117
(errors) => {
50-
console.log(errors);
118+
console.log("Form validation errors:", errors);
119+
toast.error("Please fix the form errors before submitting");
51120
},
52121
);
53122

54123
// Success component shown after successful submission
55124
const SuccessComponent = () => (
56-
<div className="bg-green-50 border border-green-200 rounded-md p-4 mt-4">
57-
<h4 className="text-green-600 font-medium text-lg">
125+
<div className="mt-4 rounded-md border border-green-200 bg-green-50 p-4">
126+
<h4 className="font-medium text-green-600 text-lg">
58127
Token submitted successfully!
59128
</h4>
60-
<p className="text-green-600">
129+
<p className="mb-3 text-green-600">
61130
Thank you for your submission. If you still do not see your token listed
62131
after some time, please reach out to our team for support.
63132
</p>
64133
</div>
65134
);
66135

136+
// Failure component shown after submission fails
137+
const FailComponent = () => (
138+
<div className="mt-4 rounded-md border border-red-200 bg-red-50 p-4">
139+
<h4 className="font-medium text-lg text-red-600">
140+
Token submission failed!
141+
</h4>
142+
<p className="mb-2 text-red-600">
143+
Please double check the network and token address. If issues persist,
144+
please reach out to our support team.
145+
{submitDiscoveryMutation.error instanceof Error && (
146+
<span className="mt-1 block text-sm">
147+
Error: {submitDiscoveryMutation.error.message}
148+
</span>
149+
)}
150+
</p>
151+
</div>
152+
);
153+
67154
return (
68-
<RouteDiscoveryCard
69-
bottomText=""
70-
saveButton={
71-
!isSubmitSuccess
72-
? {
73-
type: "submit",
74-
form: "route-discovery-form", // Connect to form by ID
75-
disabled: !form.formState.isDirty || form.formState.isSubmitting,
76-
variant: "primary",
77-
}
78-
: undefined
79-
}
80-
noPermissionText={undefined}
81-
>
82-
<div>
83-
<h3 className="font-semibold text-xl tracking-tight">
84-
Don't see your token listed?
85-
</h3>
86-
<p className="mt-1.5 mb-4 text-foreground text-sm">
87-
Select your chain and input the token address to automatically
88-
kick-off the token route discovery process. Please check back on this
89-
page within 20-40 minutes of submitting this form.
90-
</p>
91-
92-
{isSubmitSuccess ? (
93-
// Show success message after successful submission
94-
<SuccessComponent />
95-
) : (
96-
// Show form when not yet successfully submitted
97-
<Form {...form}>
98-
<form onSubmit={handleSubmit} autoComplete="off">
99-
autoComplete="off"
100-
>
155+
<Form {...form}>
156+
<form onSubmit={handleSubmit} autoComplete="off">
157+
<RouteDiscoveryCard
158+
bottomText=""
159+
errorText={form.getFieldState("tokenAddress").error?.message}
160+
saveButton={{
161+
type: "submit",
162+
disabled: !form.formState.isDirty,
163+
isPending: submitDiscoveryMutation.isPending,
164+
}}
165+
noPermissionText={undefined}
166+
>
167+
<div>
168+
<h3 className="font-semibold text-xl tracking-tight">
169+
Don't see your token listed?
170+
</h3>
171+
<p className="mt-1.5 mb-4 text-foreground text-sm">
172+
Select your chain and input the token address to automatically
173+
kick-off the token route discovery process. Please check back on
174+
this page within 20-40 minutes of submitting this form.
175+
</p>
176+
177+
{isSubmitSuccess ? (
178+
<SuccessComponent />
179+
) : isSubmitFail ? (
180+
<FailComponent />
181+
) : (
101182
<div className="grid grid-cols-1 gap-4 lg:grid-cols-2">
102183
<FormField
103184
control={form.control}
104-
name="blockchain"
185+
name="chainId"
105186
render={({ field }) => (
106187
<FormItem>
107188
<FormLabel>Blockchain</FormLabel>
108189
<FormControl>
109190
<NetworkSelectorButton
110-
value={field.value}
111-
onChange={field.onChange}
191+
onSwitchChain={(chain) => {
192+
// When a chain is selected, capture its ID and name
193+
setSelectedChainId(chain.chainId);
194+
195+
// Update the form field value
196+
field.onChange(chain.chainId);
197+
}}
112198
/>
113199
</FormControl>
114200
</FormItem>
@@ -129,10 +215,10 @@ export const RouteDiscovery: React.FC<PayConfigProps> = (props) => {
129215
)}
130216
/>
131217
</div>
132-
</form>
133-
</Form>
134-
)}
135-
</div>
136-
</RouteDiscoveryCard>
218+
)}
219+
</div>
220+
</RouteDiscoveryCard>
221+
</form>
222+
</Form>
137223
);
138224
};

apps/dashboard/src/components/settings/ApiKeys/validations.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ export const apiKeyPayConfigValidationSchema = z.object({
124124
});
125125

126126
export const routeDiscoveryValidationSchema = z.object({
127+
chainId: z.number(),
127128
tokenAddress: z
128129
.string({
129130
required_error: "Token address is required",

0 commit comments

Comments
 (0)