Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
"use client";

import { Button } from "@/components/ui/button";
import { useDashboardRouter } from "@/lib/DashboardRouter";
import { usePathname, useSearchParams } from "next/navigation";
import { useCallback } from "react";

export type OnrampProvider = "stripe" | "coinbase" | "transak";

export const ProviderSelector: React.FC<{ activeProvider: OnrampProvider }> = ({
activeProvider,
}) => {
const pathname = usePathname();
const searchParams = useSearchParams();
const router = useDashboardRouter();

const createPageURL = useCallback(
(provider: OnrampProvider) => {
const params = new URLSearchParams(searchParams || undefined);
params.set("provider", provider);
return `${pathname}?${params.toString()}`;
},
[pathname, searchParams],
);

const providers: OnrampProvider[] = ["coinbase", "stripe", "transak"];

return (
<div className="flex flex-row gap-2">
{providers.map((p) => (
<Button
key={p}
size="sm"
variant={activeProvider === p ? "default" : "outline"}
onClick={() => router.replace(createPageURL(p))}
className="capitalize"
>
{p}
</Button>
))}
</div>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import {
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table";
import { getOnrampCountrySupport } from "../../../../utils";
import type { OnrampProvider } from "../client/provider";

export async function CountriesTable(props: { provider: OnrampProvider }) {
const data = await getOnrampCountrySupport(props.provider);
const countries = data.supportedCountries;
Comment on lines +13 to +15
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add error handling for the async data fetching.

The component directly awaits getOnrampCountrySupport without error handling. If the API call fails, this will cause the entire page to error.

Consider wrapping the data fetching in a try-catch block or creating an error boundary:

export async function CountriesTable(props: { provider: OnrampProvider }) {
+  try {
    const data = await getOnrampCountrySupport(props.provider);
    const countries = data.supportedCountries;
+  } catch (error) {
+    return (
+      <div className="flex items-center justify-center p-8">
+        <p className="text-muted-foreground">
+          Failed to load country data for {props.provider}
+        </p>
+      </div>
+    );
+  }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export async function CountriesTable(props: { provider: OnrampProvider }) {
const data = await getOnrampCountrySupport(props.provider);
const countries = data.supportedCountries;
export async function CountriesTable(props: { provider: OnrampProvider }) {
try {
const data = await getOnrampCountrySupport(props.provider);
const countries = data.supportedCountries;
} catch (error) {
return (
<div className="flex items-center justify-center p-8">
<p className="text-muted-foreground">
Failed to load country data for {props.provider}
</p>
</div>
);
}
// ...rest of the component using `countries`
}
🤖 Prompt for AI Agents
In
apps/dashboard/src/app/(app)/(dashboard)/(bridge)/onramp/countries/components/server/countries-table.tsx
around lines 13 to 15, the async call to getOnrampCountrySupport lacks error
handling, which can cause the entire page to crash if the API call fails. Wrap
the await call in a try-catch block to catch any errors during data fetching,
and handle the error gracefully, such as by logging the error and returning
fallback UI or empty data to prevent the page from breaking.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Validate data structure before accessing properties.

The code assumes data.supportedCountries exists without validation. This could cause runtime errors if the API response structure changes.

Add validation for the data structure:

  const data = await getOnrampCountrySupport(props.provider);
- const countries = data.supportedCountries;
+ const countries = data.supportedCountries || [];
+ 
+ if (!Array.isArray(countries)) {
+   console.warn('Invalid country data structure received:', data);
+   return (
+     <div className="flex items-center justify-center p-8">
+       <p className="text-muted-foreground">Invalid country data format</p>
+     </div>
+   );
+ }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const countries = data.supportedCountries;
const data = await getOnrampCountrySupport(props.provider);
const countries = data.supportedCountries || [];
if (!Array.isArray(countries)) {
console.warn('Invalid country data structure received:', data);
return (
<div className="flex items-center justify-center p-8">
<p className="text-muted-foreground">Invalid country data format</p>
</div>
);
}
🤖 Prompt for AI Agents
In
apps/dashboard/src/app/(app)/(dashboard)/(bridge)/onramp/countries/components/server/countries-table.tsx
at line 15, the code accesses data.supportedCountries without checking if data
is defined or if supportedCountries exists. To fix this, add validation to
ensure data is not null or undefined and that supportedCountries is a valid
array before accessing it. Use conditional checks or optional chaining to safely
access supportedCountries and handle cases where the data structure might differ
or be missing.


return (
<TableContainer className="overflow-hidden rounded-xl border border-border/50 bg-card/50 shadow-sm transition-all">
<Table>
<TableHeader className="z-0">
<TableRow className="border-border/50 border-b bg-muted/50">
<TableHead className="py-4 font-medium text-muted-foreground/80 text-xs uppercase tracking-wider">
Country
</TableHead>
<TableHead className="py-4 font-medium text-muted-foreground/80 text-xs uppercase tracking-wider">
Currencies
</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{countries.map((country) => (
<TableRow key={country.code} className="hover:bg-accent/50">
<TableCell className="font-medium">{country.name}</TableCell>
<TableCell className="text-muted-foreground">
{country.currencies.join(", ")}
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import type { Metadata } from "next";
import {
type OnrampProvider,
ProviderSelector,
} from "./components/client/provider";
import { CountriesTable } from "./components/server/countries-table";

const title = "Onramp Country Support";
const description = "Countries and currencies supported by onramp providers.";

export const metadata: Metadata = {
title,
description,
openGraph: {
title,
description,
},
};

export default async function OnrampCountriesPage(props: {
searchParams: Promise<{ provider?: OnrampProvider }>;
}) {
const searchParams = await props.searchParams;
const activeProvider: OnrampProvider =
(searchParams.provider as OnrampProvider) || "coinbase";
Comment on lines +23 to +25
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add type safety for search params provider validation.

The type assertion (searchParams.provider as OnrampProvider) doesn't validate if the provider is actually a valid OnrampProvider value. Invalid values could cause issues downstream.

Add validation for the provider parameter:

  const searchParams = await props.searchParams;
- const activeProvider: OnrampProvider =
-   (searchParams.provider as OnrampProvider) || "coinbase";
+ const validProviders: OnrampProvider[] = ["stripe", "coinbase", "transak"];
+ const activeProvider: OnrampProvider = 
+   validProviders.includes(searchParams.provider as OnrampProvider) 
+     ? (searchParams.provider as OnrampProvider)
+     : "coinbase";
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const searchParams = await props.searchParams;
const activeProvider: OnrampProvider =
(searchParams.provider as OnrampProvider) || "coinbase";
const searchParams = await props.searchParams;
const validProviders: OnrampProvider[] = ["stripe", "coinbase", "transak"];
const activeProvider: OnrampProvider =
validProviders.includes(searchParams.provider as OnrampProvider)
? (searchParams.provider as OnrampProvider)
: "coinbase";
🤖 Prompt for AI Agents
In apps/dashboard/src/app/(app)/(dashboard)/(bridge)/onramp/countries/page.tsx
around lines 23 to 25, the code uses a type assertion for searchParams.provider
without validating if it is a valid OnrampProvider, which risks invalid values
causing issues. To fix this, implement a validation step that checks if
searchParams.provider is one of the allowed OnrampProvider values before
assigning it to activeProvider. If it is invalid or missing, default to
"coinbase". This ensures type safety and prevents invalid provider values from
propagating.


return (
<section className="container mx-auto flex h-full flex-col px-4 py-10">
<header className="flex flex-col gap-6">
<div className="flex flex-col gap-6 lg:flex-row lg:items-center lg:justify-between">
<div className="flex flex-col gap-2">
<h1 className="font-semibold text-4xl tracking-tighter lg:text-5xl">
Onramp Countries
</h1>
</div>
<div className="flex flex-row items-end gap-4 lg:flex-col">
<div className="flex w-full flex-row items-center gap-4">
<ProviderSelector activeProvider={activeProvider} />
</div>
</div>
</div>
</header>
<div className="h-10" />
<CountriesTable provider={activeProvider} />
</section>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
export type OnrampCountryToken = {
chainId: number;
address: string;
symbol: string;
};

export type OnrampCountryDetails = {
code: string;
name: string;
currencies: string[];
tokens: OnrampCountryToken[];
};

export type OnrampCountrySupport = {
provider: "stripe" | "coinbase" | "transak";
supportedCountries: OnrampCountryDetails[];
};
22 changes: 22 additions & 0 deletions apps/dashboard/src/app/(app)/(dashboard)/(bridge)/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,3 +68,25 @@ export async function getRoutes({

return routes;
}

export async function getOnrampCountrySupport(
provider: "stripe" | "coinbase" | "transak",
) {
const url = new URL(
`${NEXT_PUBLIC_THIRDWEB_BRIDGE_HOST}/v1/onramp/countries`,
);
url.searchParams.set("provider", provider);
const res = await fetch(url.toString(), {
headers: {
"x-secret-key": DASHBOARD_THIRDWEB_SECRET_KEY,
},
next: { revalidate: 60 * 60 },
});

if (!res.ok) {
throw new Error("Failed to fetch onramp countries");
}

const json = await res.json();
return json.data as import("./types/onramp-country").OnrampCountrySupport;
}
Loading