Skip to content
Merged
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
36 changes: 0 additions & 36 deletions apps/dashboard/src/@/api/chain.ts
Original file line number Diff line number Diff line change
@@ -1,44 +1,8 @@
import "server-only";
import type { ChainMetadata } from "thirdweb/chains";
import { NEXT_PUBLIC_THIRDWEB_API_HOST } from "@/constants/public-envs";
import { API_SERVER_SECRET } from "@/constants/server-envs";
import type { ChainService } from "@/types/chain";

export async function getGasSponsoredChains() {
if (!API_SERVER_SECRET) {
throw new Error("API_SERVER_SECRET is not set");
}
const res = await fetch(
`${NEXT_PUBLIC_THIRDWEB_API_HOST}/v1/chains/gas-sponsored`,
{
headers: {
"Content-Type": "application/json",
"x-service-api-key": API_SERVER_SECRET,
},
next: {
revalidate: 15 * 60, //15 minutes
},
},
);

if (!res.ok) {
console.error(
"Failed to fetch gas sponsored chains",
res.status,
res.statusText,
);
res.body?.cancel();
return [];
}

try {
return (await res.json()).data as number[];
} catch (e) {
console.error("Failed to parse gas sponsored chains", e);
return [];
}
}

export function getChains() {
return fetch(
`${NEXT_PUBLIC_THIRDWEB_API_HOST}/v1/chains`,
Expand Down
69 changes: 4 additions & 65 deletions apps/dashboard/src/@/components/billing/CreditsItem.tsx
Original file line number Diff line number Diff line change
@@ -1,45 +1,18 @@
import { formatDistance } from "date-fns";
import { CircleAlertIcon } from "lucide-react";
import Image from "next/image";
import Link from "next/link";
import type { ThirdwebClient } from "thirdweb";
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
import { Button } from "@/components/ui/button";
import type { Account, BillingCredit } from "@/hooks/useApi";
import { useLocalStorage } from "@/hooks/useLocalStorage";
import { ChainIconClient } from "@/icons/ChainIcon";
import type { BillingCredit } from "@/hooks/useApi";
import { formatToDollars } from "./formatToDollars";

interface CreditsItemProps {
credit?: BillingCredit;
isOpCreditDefault?: boolean;
onClickApply?: () => void;
twAccount: Account;
client: ThirdwebClient;
teamSlug: string;
}

export const CreditsItem: React.FC<CreditsItemProps> = ({
credit,
isOpCreditDefault,
onClickApply,
twAccount,
client,
teamSlug,
}) => {
const [hasAppliedForOpGrant] = useLocalStorage(
`appliedForOpGrant-${twAccount.id}`,
false,
);

const isOpCredit = credit?.name.startsWith("OP -") || isOpCreditDefault;
export const CreditsItem: React.FC<CreditsItemProps> = ({ credit }) => {
const isTwCredit = credit?.name.startsWith("TW -");
const isStartupCredit = credit?.name.startsWith("SU -");

let creditTitle = credit?.name ?? "thirdweb credits";
if (isOpCredit) {
creditTitle = "Optimism sponsorship credits";
} else if (isTwCredit) {
if (isTwCredit) {
creditTitle = "thirdweb credits";
} else if (isStartupCredit) {
creditTitle = "Startup grant credits";
Expand All @@ -50,13 +23,7 @@ export const CreditsItem: React.FC<CreditsItemProps> = ({
<div className="flex flex-col gap-3 p-4 lg:p-6">
<div className="relative">
<div className="absolute top-0 right-0">
{isOpCredit ? (
<ChainIconClient
className="size-6"
client={client}
src="ipfs://QmcxZHpyJa8T4i63xqjPYrZ6tKrt55tZJpbXcjSDKuKaf9/optimism/512.png"
/>
) : isTwCredit ? (
{isTwCredit ? (
<Image
alt="tw-credit"
className="size-6"
Expand Down Expand Up @@ -112,35 +79,7 @@ export const CreditsItem: React.FC<CreditsItemProps> = ({
</div>
)}
</div>

{hasAppliedForOpGrant && !credit && isOpCredit && (
<Alert variant="info">
<CircleAlertIcon className="size-5" />
<AlertTitle>Grant application pending approval</AlertTitle>
<AlertDescription>
You will receive an email once your application&apos;s status
changes.
</AlertDescription>
</Alert>
)}
</div>

{!hasAppliedForOpGrant && isOpCredit && (
<div className="mt-2 flex justify-end border-t px-4 py-4 lg:px-6">
<Button asChild size="sm" variant="outline">
<Link
href={`/team/${teamSlug}/~/settings/credits`}
onClick={() => {
if (onClickApply) {
onClickApply();
}
}}
>
Apply Now
</Link>
</Button>
</div>
)}
</div>
);
};
31 changes: 8 additions & 23 deletions apps/dashboard/src/@/components/billing/PlanCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,41 +2,26 @@

// TODO - convert to RSC

import type { ThirdwebClient } from "thirdweb";
import { type Account, useAccountCredits } from "@/hooks/useApi";
import { useAccountCredits } from "@/hooks/useApi";
import { CreditsItem } from "./CreditsItem";

export const CreditsInfoCard = (props: {
twAccount: Account;
client: ThirdwebClient;
teamSlug: string;
}) => {
export const CreditsInfoCard = () => {
const { data: credits } = useAccountCredits();

if (!credits) {
return null;
}

const opCredit = credits.find((crd) => crd.name.startsWith("OP -"));
const restCredits = credits.filter((crd) => !crd.name.startsWith("OP -"));

if (restCredits.length === 0) {
return null;
}

return (
<section className="flex flex-col gap-4">
<CreditsItem
client={props.client}
credit={opCredit}
isOpCreditDefault={true}
teamSlug={props.teamSlug}
twAccount={props.twAccount}
/>
{restCredits?.map((credit) => (
<CreditsItem
client={props.client}
credit={credit}
key={credit.couponId}
teamSlug={props.teamSlug}
twAccount={props.twAccount}
/>
{restCredits.map((credit) => (
<CreditsItem credit={credit} key={credit.couponId} />
))}
</section>
);
Expand Down
28 changes: 1 addition & 27 deletions apps/dashboard/src/app/(app)/(dashboard)/(chain)/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,7 @@ import "server-only";

import { notFound } from "next/navigation";
import type { ChainMetadata } from "thirdweb/chains";
import {
getChainServices,
getChains,
getGasSponsoredChains,
} from "@/api/chain";
import { getChainServices, getChains } from "@/api/chain";
import { NEXT_PUBLIC_THIRDWEB_API_HOST } from "@/constants/public-envs";
import type { ChainMetadataWithServices, ChainServices } from "@/types/chain";
import type { ChainCTAProps } from "./[chain_id]/(chainPage)/components/server/cta-card";
Expand All @@ -25,7 +21,6 @@ import cotiBanner from "./temp-assets/COTI_Banner.jpg";
import cotiCTA from "./temp-assets/COTI_CTA.jpg";
import creatorBanner from "./temp-assets/creatorBanner.png";
import creatorCTA from "./temp-assets/creatorCTA.png";
import superchainCTABG from "./temp-assets/cta-bg-superchain.png";
import xaiCTABg from "./temp-assets/cta-bg-xai-connect.png";
import thirdwebCTA from "./temp-assets/cta-thirdweb.png";
import cyberChainBanner from "./temp-assets/cyberChainBanner.png";
Expand Down Expand Up @@ -135,17 +130,6 @@ type ExtraChainMetadata = Partial<{
cta: ChainCTAProps;
}>;

// TEMPORARY

const OP_CTA = {
backgroundImageUrl: superchainCTABG.src,
buttonLink: "/team/~/~/settings/credits",
buttonText: "Apply now",
description:
"Successful applicants receive gas grants for use across all supported Optimism Superchain networks. These grants can sponsor gas fees for any onchain activity using our Account Abstraction tools.",
title: "Optimism Superchain App Accelerator",
} satisfies ChainCTAProps;

const chainMetaRecord = {
// Flare
14: {
Expand Down Expand Up @@ -933,22 +917,12 @@ const chainMetaRecord = {
export async function getChainMetadata(
chainId: number,
): Promise<(ExtraChainMetadata & { gasSponsored?: true }) | null> {
const gasSponsoredChains = await getGasSponsoredChains();

const isGasSponsored = gasSponsoredChains.includes(chainId);

// TODO: fetch this from the API
if (chainId in chainMetaRecord) {
return {
...(isGasSponsored ? { cta: OP_CTA, gasSponsored: true } : {}),
// this will OVERRIDE the op CTA if there is a custom one configured
...chainMetaRecord[chainId as keyof typeof chainMetaRecord],
};
} else if (isGasSponsored) {
return {
cta: OP_CTA,
gasSponsored: true,
};
}
return null;
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
import { redirect } from "next/navigation";
import { getStripeBalance } from "@/actions/stripe-actions";
import { getAuthToken } from "@/api/auth-token";
import { getTeamBySlug, type Team } from "@/api/team";
import { getMemberByAccountId } from "@/api/team-members";
import { getTeamSubscriptions } from "@/api/team-subscription";
import { CreditsInfoCard } from "@/components/billing/PlanCard";
import { Coupons } from "@/components/billing/SubscriptionCoupons/Coupons";
import { getClientThirdwebClient } from "@/constants/thirdweb-client.client";
import { getValidAccount } from "../../../../../account/settings/getAccount";
import { CreditBalanceSection } from "./components/credit-balance-section.client";
import { PlanInfoCardClient } from "./components/PlanInfoCard.client";
Expand All @@ -28,9 +26,8 @@ export default async function Page(props: {

const account = await getValidAccount(pagePath);

const [team, authToken, teamMember] = await Promise.all([
const [team, teamMember] = await Promise.all([
getTeamBySlug(params.team_slug),
getAuthToken(),
getMemberByAccountId(params.team_slug, account.id),
]);

Expand All @@ -50,11 +47,6 @@ export default async function Page(props: {
);
}

const client = getClientThirdwebClient({
jwt: authToken,
teamId: team.id,
});

const highlightPlan =
typeof searchParams.highlight === "string"
? (searchParams.highlight as Team["billingPlan"])
Expand Down Expand Up @@ -84,11 +76,7 @@ export default async function Page(props: {
/>
)}

<CreditsInfoCard
client={client}
teamSlug={team.slug}
twAccount={account}
/>
<CreditsInfoCard />
<Coupons isPaymentSetup={validPayment} teamId={team.id} />
</div>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
"use client";

import { SmartWalletsBillingAlert } from "@app/team/[team_slug]/[project_slug]/(sidebar)/account-abstraction/Alerts";
import { ArrowRightIcon } from "lucide-react";
import Link from "next/link";
import { DismissibleAlert } from "@/components/blocks/dismissible-alert";
import { Button } from "@/components/ui/button";
import { TabPathLinks } from "@/components/ui/tabs";
import { UnderlineLink } from "@/components/ui/UnderlineLink";
import { AAFooter } from "./AAFooterSection";
Expand Down Expand Up @@ -44,10 +40,6 @@ export function AccountAbstractionLayout(props: {
{props.hasSmartWalletsWithoutBilling && (
<SmartWalletsBillingAlert teamSlug={props.teamSlug} />
)}
<GasCreditAlert
projectId={props.projectId}
teamSlug={props.teamSlug}
/>
</div>

<div className="h-4" />
Expand Down Expand Up @@ -89,40 +81,3 @@ export function AccountAbstractionLayout(props: {
</div>
);
}

function GasCreditAlert(props: { teamSlug: string; projectId: string }) {
return (
<DismissibleAlert
description={
<>
Redeem credits towards gas Sponsorship. <br className="lg:hidden" />
<UnderlineLink
href="https://thirdweb.com/superchain"
rel="noopener noreferrer"
target="_blank"
>
Learn More
</UnderlineLink>
<div className="mt-4 flex items-center gap-4">
<Button
asChild
className="bg-background"
size="sm"
variant="outline"
>
<Link
className="gap-2"
href={`/team/${props.teamSlug}/~/settings/credits`}
target="_blank"
>
Claim your credits <ArrowRightIcon className="size-4" />
</Link>
</Button>
</div>
</>
}
localStorageId={`${props.projectId}-gas-credit-alert`}
title="OP Gas Credit Program"
/>
);
}
Loading