-
Notifications
You must be signed in to change notification settings - Fork 684
[Dashboard] Chain integration request form #9004
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,62 @@ | ||
| "use server"; | ||
| import "server-only"; | ||
|
|
||
| import { getAuthToken } from "@/api/auth-token"; | ||
| import { NEXT_PUBLIC_THIRDWEB_API_HOST } from "@/constants/public-envs"; | ||
|
|
||
| export type ChainIntegrationRequest = { | ||
| companyName: string; | ||
| telegram: string; | ||
| email: string; | ||
| chainName: string; | ||
| chainShortName: string; | ||
| chainId: string; | ||
| publicRpc: string; | ||
| iconUrl: string; | ||
| iconWidth: string; | ||
| iconHeight: string; | ||
| iconFormat: string; | ||
| nativeCurrencyName: string; | ||
| nativeCurrencySymbol: string; | ||
| nativeCurrencyDecimals: string; | ||
| blockExplorerUrl: string; | ||
| blockExplorerStandard: string; | ||
| explorerIconWidth: string; | ||
| explorerIconHeight: string; | ||
| explorerIconFormat: string; | ||
| faucetOrBridge: string; | ||
| chainStack?: string; | ||
| }; | ||
|
|
||
| export async function submitChainIntegrationRequest( | ||
| request: ChainIntegrationRequest, | ||
| ): Promise<{ ok: true } | { ok: false; error: string }> { | ||
| const token = await getAuthToken(); | ||
| if (!token) { | ||
| return { error: "You are not logged in", ok: false }; | ||
| } | ||
|
|
||
| const res = await fetch( | ||
| new URL("/v1/chains/integration-requests", NEXT_PUBLIC_THIRDWEB_API_HOST), | ||
| { | ||
| body: JSON.stringify(request), | ||
| headers: { | ||
| Authorization: `Bearer ${token}`, | ||
| "Content-Type": "application/json", | ||
| }, | ||
| method: "POST", | ||
| }, | ||
| ); | ||
|
|
||
| if (!res.ok) { | ||
| return { | ||
| error: | ||
| res.status === 429 | ||
| ? "Too many requests, please try again in a minute" | ||
| : "Failed to submit request, please try again later", | ||
| ok: false, | ||
| }; | ||
| } | ||
|
|
||
| return { ok: true }; | ||
| } | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,245 @@ | ||||||||||||||||||||||||||||||||||||||||||
| "use client"; | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| import { zodResolver } from "@hookform/resolvers/zod"; | ||||||||||||||||||||||||||||||||||||||||||
| import { CheckCircle2Icon } from "lucide-react"; | ||||||||||||||||||||||||||||||||||||||||||
| import { useState } from "react"; | ||||||||||||||||||||||||||||||||||||||||||
| import { useForm } from "react-hook-form"; | ||||||||||||||||||||||||||||||||||||||||||
| import { toast } from "sonner"; | ||||||||||||||||||||||||||||||||||||||||||
| import { z } from "zod"; | ||||||||||||||||||||||||||||||||||||||||||
| import { submitChainIntegrationRequest } from "@/actions/chain-integration-request"; | ||||||||||||||||||||||||||||||||||||||||||
| import { Button } from "@/components/ui/button"; | ||||||||||||||||||||||||||||||||||||||||||
| import { | ||||||||||||||||||||||||||||||||||||||||||
| Form, | ||||||||||||||||||||||||||||||||||||||||||
| FormControl, | ||||||||||||||||||||||||||||||||||||||||||
| FormField, | ||||||||||||||||||||||||||||||||||||||||||
| FormItem, | ||||||||||||||||||||||||||||||||||||||||||
| FormLabel, | ||||||||||||||||||||||||||||||||||||||||||
| FormMessage, | ||||||||||||||||||||||||||||||||||||||||||
| } from "@/components/ui/form"; | ||||||||||||||||||||||||||||||||||||||||||
| import { Input } from "@/components/ui/input"; | ||||||||||||||||||||||||||||||||||||||||||
| import { | ||||||||||||||||||||||||||||||||||||||||||
| Select, | ||||||||||||||||||||||||||||||||||||||||||
| SelectContent, | ||||||||||||||||||||||||||||||||||||||||||
| SelectItem, | ||||||||||||||||||||||||||||||||||||||||||
| SelectTrigger, | ||||||||||||||||||||||||||||||||||||||||||
| SelectValue, | ||||||||||||||||||||||||||||||||||||||||||
| } from "@/components/ui/select"; | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| const CHAIN_STACKS = [ | ||||||||||||||||||||||||||||||||||||||||||
| "Optimism", | ||||||||||||||||||||||||||||||||||||||||||
| "Arbitrum", | ||||||||||||||||||||||||||||||||||||||||||
| "Polygon AggLayer", | ||||||||||||||||||||||||||||||||||||||||||
| "zkSync", | ||||||||||||||||||||||||||||||||||||||||||
| "Avalanche", | ||||||||||||||||||||||||||||||||||||||||||
| "Fully custom Bespoke Chain", | ||||||||||||||||||||||||||||||||||||||||||
| ] as const; | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| const text = (max = 300) => | ||||||||||||||||||||||||||||||||||||||||||
| z.string().trim().min(1, "Required").max(max, "Too long"); | ||||||||||||||||||||||||||||||||||||||||||
| const digits = z | ||||||||||||||||||||||||||||||||||||||||||
| .string() | ||||||||||||||||||||||||||||||||||||||||||
| .trim() | ||||||||||||||||||||||||||||||||||||||||||
| .regex(/^[0-9]{1,20}$/, "Must be a number"); | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| const formSchema = z.object({ | ||||||||||||||||||||||||||||||||||||||||||
| blockExplorerStandard: text(50), | ||||||||||||||||||||||||||||||||||||||||||
| blockExplorerUrl: text(2000), | ||||||||||||||||||||||||||||||||||||||||||
| chainId: digits, | ||||||||||||||||||||||||||||||||||||||||||
| chainName: text(), | ||||||||||||||||||||||||||||||||||||||||||
| chainShortName: text(50), | ||||||||||||||||||||||||||||||||||||||||||
| chainStack: z.enum(CHAIN_STACKS).optional(), | ||||||||||||||||||||||||||||||||||||||||||
| companyName: text(), | ||||||||||||||||||||||||||||||||||||||||||
| email: z.string().trim().email("Invalid email").max(254), | ||||||||||||||||||||||||||||||||||||||||||
| explorerIconFormat: text(20), | ||||||||||||||||||||||||||||||||||||||||||
| explorerIconHeight: digits, | ||||||||||||||||||||||||||||||||||||||||||
| explorerIconWidth: digits, | ||||||||||||||||||||||||||||||||||||||||||
| faucetOrBridge: text(2000), | ||||||||||||||||||||||||||||||||||||||||||
| iconFormat: text(20), | ||||||||||||||||||||||||||||||||||||||||||
| iconHeight: digits, | ||||||||||||||||||||||||||||||||||||||||||
| iconUrl: text(2000), | ||||||||||||||||||||||||||||||||||||||||||
| iconWidth: digits, | ||||||||||||||||||||||||||||||||||||||||||
| nativeCurrencyDecimals: digits, | ||||||||||||||||||||||||||||||||||||||||||
| nativeCurrencyName: text(), | ||||||||||||||||||||||||||||||||||||||||||
| nativeCurrencySymbol: text(20), | ||||||||||||||||||||||||||||||||||||||||||
| publicRpc: text(2000), | ||||||||||||||||||||||||||||||||||||||||||
| telegram: text(), | ||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| type FormValues = z.infer<typeof formSchema>; | ||||||||||||||||||||||||||||||||||||||||||
| type TextFieldName = Exclude<keyof FormValues, "chainStack">; | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| const SECTIONS: { | ||||||||||||||||||||||||||||||||||||||||||
| title: string; | ||||||||||||||||||||||||||||||||||||||||||
| fields: { name: TextFieldName; label: string; placeholder?: string }[]; | ||||||||||||||||||||||||||||||||||||||||||
| }[] = [ | ||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||
| fields: [ | ||||||||||||||||||||||||||||||||||||||||||
| { label: "Company name", name: "companyName" }, | ||||||||||||||||||||||||||||||||||||||||||
| { label: "Telegram", name: "telegram" }, | ||||||||||||||||||||||||||||||||||||||||||
| { label: "Email", name: "email" }, | ||||||||||||||||||||||||||||||||||||||||||
| ], | ||||||||||||||||||||||||||||||||||||||||||
| title: "Contact", | ||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||
| fields: [ | ||||||||||||||||||||||||||||||||||||||||||
| { label: "Chain Name", name: "chainName" }, | ||||||||||||||||||||||||||||||||||||||||||
| { label: "Chain Short Name", name: "chainShortName", placeholder: "ETH" }, | ||||||||||||||||||||||||||||||||||||||||||
| { label: "Chain Id", name: "chainId", placeholder: "1" }, | ||||||||||||||||||||||||||||||||||||||||||
| { label: "Public RPC", name: "publicRpc" }, | ||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||
| label: | ||||||||||||||||||||||||||||||||||||||||||
| "Icon URL (must be a publicly accessible/hosted link or IPFS URL)", | ||||||||||||||||||||||||||||||||||||||||||
| name: "iconUrl", | ||||||||||||||||||||||||||||||||||||||||||
| placeholder: | ||||||||||||||||||||||||||||||||||||||||||
| "ipfs://QmbYKZ1MuDa1hzwLGjdCZGapuhV7C9uyRDPJWD994qbocY/generic-icon.png", | ||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||
| { label: "Icon Width", name: "iconWidth", placeholder: "512" }, | ||||||||||||||||||||||||||||||||||||||||||
| { label: "Icon Height", name: "iconHeight", placeholder: "512" }, | ||||||||||||||||||||||||||||||||||||||||||
| { label: "Icon Format", name: "iconFormat", placeholder: "png" }, | ||||||||||||||||||||||||||||||||||||||||||
| ], | ||||||||||||||||||||||||||||||||||||||||||
| title: "Mainnet Chain Info", | ||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||
| fields: [ | ||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||
| label: "Native Currency", | ||||||||||||||||||||||||||||||||||||||||||
| name: "nativeCurrencyName", | ||||||||||||||||||||||||||||||||||||||||||
| placeholder: "Ether", | ||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||
| { label: "Symbol", name: "nativeCurrencySymbol", placeholder: "ETH" }, | ||||||||||||||||||||||||||||||||||||||||||
| { label: "Decimals", name: "nativeCurrencyDecimals", placeholder: "18" }, | ||||||||||||||||||||||||||||||||||||||||||
| ], | ||||||||||||||||||||||||||||||||||||||||||
| title: "Native Currency", | ||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||
| fields: [ | ||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||
| label: "Block Explorer", | ||||||||||||||||||||||||||||||||||||||||||
| name: "blockExplorerUrl", | ||||||||||||||||||||||||||||||||||||||||||
| placeholder: "https://etherscan.io/", | ||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||
| label: "Chain Standard", | ||||||||||||||||||||||||||||||||||||||||||
| name: "blockExplorerStandard", | ||||||||||||||||||||||||||||||||||||||||||
| placeholder: "EIP3091", | ||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||
| label: "Explorer Icon Width", | ||||||||||||||||||||||||||||||||||||||||||
| name: "explorerIconWidth", | ||||||||||||||||||||||||||||||||||||||||||
| placeholder: "83", | ||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||
| label: "Explorer Icon Height", | ||||||||||||||||||||||||||||||||||||||||||
| name: "explorerIconHeight", | ||||||||||||||||||||||||||||||||||||||||||
| placeholder: "83", | ||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||
| label: "Explorer Icon Format", | ||||||||||||||||||||||||||||||||||||||||||
| name: "explorerIconFormat", | ||||||||||||||||||||||||||||||||||||||||||
| placeholder: "svg", | ||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||
| { label: "Faucet/Bridge", name: "faucetOrBridge" }, | ||||||||||||||||||||||||||||||||||||||||||
| ], | ||||||||||||||||||||||||||||||||||||||||||
| title: "Block Explorer", | ||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||
| ]; | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| const defaultValues = Object.fromEntries( | ||||||||||||||||||||||||||||||||||||||||||
| SECTIONS.flatMap((section) => section.fields.map((f) => [f.name, ""])), | ||||||||||||||||||||||||||||||||||||||||||
| ) as Omit<FormValues, "chainStack">; | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| export function ChainIntegrationRequestForm() { | ||||||||||||||||||||||||||||||||||||||||||
| const [submitted, setSubmitted] = useState(false); | ||||||||||||||||||||||||||||||||||||||||||
| const form = useForm<FormValues>({ | ||||||||||||||||||||||||||||||||||||||||||
| defaultValues, | ||||||||||||||||||||||||||||||||||||||||||
| resolver: zodResolver(formSchema), | ||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| async function onSubmit(values: FormValues) { | ||||||||||||||||||||||||||||||||||||||||||
| const res = await submitChainIntegrationRequest(values); | ||||||||||||||||||||||||||||||||||||||||||
| if (!res.ok) { | ||||||||||||||||||||||||||||||||||||||||||
| toast.error(res.error); | ||||||||||||||||||||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
| setSubmitted(true); | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+158
to
+165
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win Handle thrown errors in If the server action throws, Proposed fix async function onSubmit(values: FormValues) {
- const res = await submitChainIntegrationRequest(values);
- if (!res.ok) {
- toast.error(res.error);
- return;
- }
- setSubmitted(true);
+ try {
+ const res = await submitChainIntegrationRequest(values);
+ if (!res.ok) {
+ toast.error(res.error);
+ return;
+ }
+ setSubmitted(true);
+ } catch {
+ toast.error("Failed to submit request, please try again later");
+ }
}Based on learnings: store submission failures and show user-facing feedback instead of failing silently. 📝 Committable suggestion
Suggested change
🤖 Prompt for AI AgentsSource: Learnings |
||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| if (submitted) { | ||||||||||||||||||||||||||||||||||||||||||
| return ( | ||||||||||||||||||||||||||||||||||||||||||
| <div className="flex items-center gap-3 rounded-lg border bg-card p-6"> | ||||||||||||||||||||||||||||||||||||||||||
| <CheckCircle2Icon className="size-5 text-success-text" /> | ||||||||||||||||||||||||||||||||||||||||||
| <p>Thank you for submitting your chain request!</p> | ||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| return ( | ||||||||||||||||||||||||||||||||||||||||||
| <Form {...form}> | ||||||||||||||||||||||||||||||||||||||||||
| <form | ||||||||||||||||||||||||||||||||||||||||||
| className="flex flex-col gap-8" | ||||||||||||||||||||||||||||||||||||||||||
| onSubmit={form.handleSubmit(onSubmit)} | ||||||||||||||||||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||||||||||||||||||
| {SECTIONS.map((section) => ( | ||||||||||||||||||||||||||||||||||||||||||
| <fieldset className="flex flex-col gap-4" key={section.title}> | ||||||||||||||||||||||||||||||||||||||||||
| <legend className="mb-4 font-semibold text-xl"> | ||||||||||||||||||||||||||||||||||||||||||
| {section.title} | ||||||||||||||||||||||||||||||||||||||||||
| </legend> | ||||||||||||||||||||||||||||||||||||||||||
| {section.fields.map((f) => ( | ||||||||||||||||||||||||||||||||||||||||||
| <FormField | ||||||||||||||||||||||||||||||||||||||||||
| control={form.control} | ||||||||||||||||||||||||||||||||||||||||||
| key={f.name} | ||||||||||||||||||||||||||||||||||||||||||
| name={f.name} | ||||||||||||||||||||||||||||||||||||||||||
| render={({ field }) => ( | ||||||||||||||||||||||||||||||||||||||||||
| <FormItem> | ||||||||||||||||||||||||||||||||||||||||||
| <FormLabel>{f.label}</FormLabel> | ||||||||||||||||||||||||||||||||||||||||||
| <FormControl> | ||||||||||||||||||||||||||||||||||||||||||
| <Input | ||||||||||||||||||||||||||||||||||||||||||
| className="bg-card" | ||||||||||||||||||||||||||||||||||||||||||
| placeholder={f.placeholder} | ||||||||||||||||||||||||||||||||||||||||||
| {...field} | ||||||||||||||||||||||||||||||||||||||||||
| /> | ||||||||||||||||||||||||||||||||||||||||||
| </FormControl> | ||||||||||||||||||||||||||||||||||||||||||
| <FormMessage /> | ||||||||||||||||||||||||||||||||||||||||||
| </FormItem> | ||||||||||||||||||||||||||||||||||||||||||
| )} | ||||||||||||||||||||||||||||||||||||||||||
| /> | ||||||||||||||||||||||||||||||||||||||||||
| ))} | ||||||||||||||||||||||||||||||||||||||||||
| </fieldset> | ||||||||||||||||||||||||||||||||||||||||||
| ))} | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| <FormField | ||||||||||||||||||||||||||||||||||||||||||
| control={form.control} | ||||||||||||||||||||||||||||||||||||||||||
| name="chainStack" | ||||||||||||||||||||||||||||||||||||||||||
| render={({ field }) => ( | ||||||||||||||||||||||||||||||||||||||||||
| <FormItem> | ||||||||||||||||||||||||||||||||||||||||||
| <FormLabel>Chain Stack</FormLabel> | ||||||||||||||||||||||||||||||||||||||||||
| <Select onValueChange={field.onChange} value={field.value}> | ||||||||||||||||||||||||||||||||||||||||||
| <FormControl> | ||||||||||||||||||||||||||||||||||||||||||
| <SelectTrigger className="bg-card"> | ||||||||||||||||||||||||||||||||||||||||||
| <SelectValue placeholder="Please select" /> | ||||||||||||||||||||||||||||||||||||||||||
| </SelectTrigger> | ||||||||||||||||||||||||||||||||||||||||||
| </FormControl> | ||||||||||||||||||||||||||||||||||||||||||
| <SelectContent> | ||||||||||||||||||||||||||||||||||||||||||
| {CHAIN_STACKS.map((stack) => ( | ||||||||||||||||||||||||||||||||||||||||||
| <SelectItem key={stack} value={stack}> | ||||||||||||||||||||||||||||||||||||||||||
| {stack} | ||||||||||||||||||||||||||||||||||||||||||
| </SelectItem> | ||||||||||||||||||||||||||||||||||||||||||
| ))} | ||||||||||||||||||||||||||||||||||||||||||
| </SelectContent> | ||||||||||||||||||||||||||||||||||||||||||
| </Select> | ||||||||||||||||||||||||||||||||||||||||||
| <FormMessage /> | ||||||||||||||||||||||||||||||||||||||||||
| </FormItem> | ||||||||||||||||||||||||||||||||||||||||||
| )} | ||||||||||||||||||||||||||||||||||||||||||
| /> | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| <Button | ||||||||||||||||||||||||||||||||||||||||||
| className="w-fit" | ||||||||||||||||||||||||||||||||||||||||||
| disabled={form.formState.isSubmitting} | ||||||||||||||||||||||||||||||||||||||||||
| type="submit" | ||||||||||||||||||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||||||||||||||||||
| {form.formState.isSubmitting ? "Submitting..." : "Submit"} | ||||||||||||||||||||||||||||||||||||||||||
| </Button> | ||||||||||||||||||||||||||||||||||||||||||
| </form> | ||||||||||||||||||||||||||||||||||||||||||
| </Form> | ||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| import type { Metadata } from "next"; | ||
| import { getAuthToken } from "@/api/auth-token"; | ||
| import { loginRedirect } from "@/utils/redirects"; | ||
| import { ChainIntegrationRequestForm } from "./chain-integration-request-form"; | ||
|
|
||
| export const metadata: Metadata = { | ||
| title: "Mainnet Chain Integration Request", | ||
| }; | ||
|
|
||
| export default async function ChainIntegrationRequestPage() { | ||
| const authToken = await getAuthToken(); | ||
| if (!authToken) { | ||
| loginRedirect("/chainlist/request"); | ||
| } | ||
|
|
||
| return ( | ||
| <section className="container mx-auto flex max-w-2xl flex-col px-4 py-10"> | ||
| <h1 className="font-semibold text-3xl tracking-tight"> | ||
| Mainnet Chain Integration Request | ||
| </h1> | ||
| <p className="mt-2 text-muted-foreground"> | ||
| Share your chain details and our team will get back to you. | ||
| </p> | ||
| <div className="mt-8"> | ||
| <ChainIntegrationRequestForm /> | ||
| </div> | ||
| </section> | ||
| ); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win
🔎 Supported by static analysis
🏁 Script executed:
Repository: thirdweb-dev/js
Length of output: 9152
🏁 Script executed:
Repository: thirdweb-dev/js
Length of output: 31892
🤖 get_repo_knowledge executed:
get_repo_knowledge thirdweb-dev/js /tmp/coderabbit-repo-knowledge/thirdweb-dev-js-e0b0fe64/architectureLength of output: 44248
Handle fetch failures in
submitChainIntegrationRequest.fetchhas no timeout or exception handling. A network failure or stalled upstream request can prevent the action from returning the{ ok: false, error }result thatChainIntegrationRequestFormexpects. Add bounded cancellation and return the existing error shape.Suggested fix
📝 Committable suggestion
🤖 Prompt for AI Agents