diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/infrastructure/[chain_id]/_components/service-card.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/infrastructure/[chain_id]/_components/service-card.tsx index fb85f10e96d..52e8206ffb3 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/infrastructure/[chain_id]/_components/service-card.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/infrastructure/[chain_id]/_components/service-card.tsx @@ -14,11 +14,11 @@ type InfraServiceCardProps = { export function InfraServiceCard({ title, status }: InfraServiceCardProps) { return ( -
+
{/* Header row with status and optional action */}
-
-

{title}

+
+

{title}

- {/* Chain header */} -
-

- Infrastructure for -

- - - - {chain.icon && ( - - )} - {cleanChainName(chain.name)} - - - - Chain ID - {chain.chainId} - - - -
- - {PRODUCTS.map((product) => { - const hasSku = chainSubscription.skus.includes(product.sku); - - // Map sku to chain service key - const skuToServiceKey: Record = { - "chain:infra:account_abstraction": "account-abstraction", - "chain:infra:insight": "insight", - "chain:infra:rpc": "rpc-edge", - }; - - const serviceKey = skuToServiceKey[product.sku]; - const chainService = chain.services.find( - (s) => s.service === serviceKey, - ); - const serviceEnabled = - chainService?.enabled ?? chainService?.status === "enabled"; - - let status: "active" | "pending" | "inactive"; - if (hasSku && serviceEnabled) { - status = "active"; - } else if (hasSku && !serviceEnabled) { - status = "pending"; - } else { - status = "inactive"; - } - - return ( - - ); - })} - - - {/* Subscription summary */} - - - {/* Left: header + info */} -
-
-

Subscription details

- {chainSubscription.isLegacy && ( - - Enterprise - - This subscription is part of an enterprise agreement and - cannot be modified through the dashboard. Please contact - your account executive for any modifications. - - } - > - - - - )} +
+
+ {/* header */} +
+
+
+
+
+ +

+ {cleanChainName(chain.name)} Infrastructure +

+
+
-
-
- Renews on - {formattedRenewalDate} +
+ {PRODUCTS.map((product) => { + const hasSku = chainSubscription.skus.includes(product.sku); + + // Map sku to chain service key + const skuToServiceKey: Record = { + "chain:infra:account_abstraction": "account-abstraction", + "chain:infra:insight": "insight", + "chain:infra:rpc": "rpc-edge", + }; + + const serviceKey = skuToServiceKey[product.sku]; + const chainService = chain.services.find( + (s) => s.service === serviceKey, + ); + const serviceEnabled = + chainService?.enabled ?? chainService?.status === "enabled"; + + let status: "active" | "pending" | "inactive"; + if (hasSku && serviceEnabled) { + status = "active"; + } else if (hasSku && !serviceEnabled) { + status = "pending"; + } else { + status = "inactive"; + } + + return ( + + ); + })} + + + {/* Subscription summary */} + + + {/* Left: header + info */} +
+
+

Subscription details

+ {chainSubscription.isLegacy && ( + + Enterprise + + This subscription is part of an enterprise agreement + and cannot be modified through the dashboard. Please + contact your account executive for any modifications. + + } + > + + + + )}
-
- Amount due - {formattedAmountDue} +
+
+ Renews on + {formattedRenewalDate} +
+ +
+ Amount due + {formattedAmountDue} +
-
-
-
+ + +
); } diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/infrastructure/deploy/[chain_id]/page.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/infrastructure/deploy/[chain_id]/page.tsx index 267fa7ff82f..d105a33e647 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/infrastructure/deploy/[chain_id]/page.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/infrastructure/deploy/[chain_id]/page.tsx @@ -1,11 +1,13 @@ -import { ArrowUpDownIcon } from "lucide-react"; -import Link from "next/link"; import { notFound, redirect } from "next/navigation"; import { getValidAccount } from "@/api/account/get-account"; import { getMembers } from "@/api/team/team-members"; -import { Badge } from "@/components/ui/badge"; -import { Button } from "@/components/ui/button"; -import { Card, CardContent } from "@/components/ui/card"; +import { + Breadcrumb, + BreadcrumbItem, + BreadcrumbLink, + BreadcrumbList, + BreadcrumbSeparator, +} from "@/components/ui/breadcrumb"; import { getClientThirdwebClient } from "@/constants/thirdweb-client.client"; import { ChainIconClient } from "@/icons/ChainIcon"; import { getChain } from "../../../../../../../(dashboard)/(chain)/utils"; @@ -45,47 +47,55 @@ export default async function DeployInfrastructureOnChainPage(props: { const client = getClientThirdwebClient(); return ( -
-
-

- Deploy Infrastructure on -

- - - - {chain.icon && ( - - )} - {cleanChainName(chain.name)} - +
+ {/* breadcrumb */} +
+ + + + + {" "} + Deploy Infrastructure{" "} + + + + + {cleanChainName(chain.name)} + + + +
+ + {/* header */} +
+
+
+
+ +
+
+ +

+ Deploy Infrastructure on {cleanChainName(chain.name)} +

+
+
- - Chain ID - {chain.chainId} - - - - + {/* form */} +
+
-
); } diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/infrastructure/deploy/_components/deploy-infrastructure-form.client.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/infrastructure/deploy/_components/deploy-infrastructure-form.client.tsx index 4b0af00bdf1..e47288c61b8 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/infrastructure/deploy/_components/deploy-infrastructure-form.client.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/infrastructure/deploy/_components/deploy-infrastructure-form.client.tsx @@ -1,10 +1,12 @@ "use client"; +import { CheckIcon, InfoIcon } from "lucide-react"; import { useQueryState } from "nuqs"; import { useMemo, useState, useTransition } from "react"; import { toast } from "sonner"; import { getChainInfraCheckoutURL } from "@/actions/billing"; import { reportChainInfraRpcOmissionAgreed } from "@/analytics/report"; +import { Alert, AlertTitle } from "@/components/ui/alert"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { @@ -115,11 +117,11 @@ export function DeployInfrastructureForm(props: { const bundleHint = useMemo(() => { if (selectedCount === 1) { - return "Add one more add-on to unlock a 10% bundle discount."; + return "Add one more add-on to unlock a 10% bundle discount"; } else if (selectedCount === 2) { - return "Add another add-on to increase your bundle discount to 15%."; + return "Add another add-on to increase your bundle discount to 15%"; } else if (selectedCount >= 3) { - return "🎉 Congrats! You unlocked the maximum 15% bundle discount."; + return "Congrats! You unlocked the maximum 15% bundle discount"; } return null; }, [selectedCount]); @@ -234,8 +236,8 @@ export function DeployInfrastructureForm(props: { return (
{/* Left column: service selection + frequency */} -
-

Select Services

+
+

Services

{/* RPC (now optional) */}
@@ -258,13 +260,11 @@ export function DeployInfrastructureForm(props: { {/* Optional add-ons */}
-
-

Add-ons

- {bundleHint && ( -

{bundleHint}

- )} +
+

Add-ons

-
+ +
{/* Insight */} + + {bundleHint && ( + + + {bundleHint} + + )}
{/* Right column: order summary */} -
-

Order Summary

-
+
+

Order Summary

+
{selectedOrder.map((key) => (
{SERVICE_CONFIG[key].label} @@ -329,15 +336,16 @@ export function DeployInfrastructureForm(props: {
))} -
+
Subtotal {formatUSD(subtotal)} {periodLabel}
+ {bundleDiscount > 0 && ( -
+
Bundle Discount ( {Object.values(selectedServices).filter(Boolean).length === 2 @@ -485,50 +493,52 @@ function ServiceCard(props: { icon, onToggle, } = props; + + const IconComp = getIcon(icon); return ( - - -
    -
  1. - Option 1: Submit a PR to  - - ethereum-lists/chains - {" "} - to add your chain.{" "} - - (automatically added on PR merge) - -
  2. -
  3. - Option 2: Share your chain details via  - - this short form - - .
    - - (multiple days for your chain to be included) - -
  4. -
-
- -
+ {/* body */} +
+ {/* card */} +
+
+ {/* Header */} +
+

+ Choose your chain +

+

+ Select the chain you'd like to deploy infrastructure on.
{" "} + In the next step you'll pick which services you want to enable + for all developers on this chain +

+
+ + {/* Chain selector */} +
+ +
+
-
- {chainId === undefined ? ( - - ) : ( - - )} + + ) : ( + + )} +
diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/infrastructure/layout.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/infrastructure/layout.tsx deleted file mode 100644 index 47f49919abb..00000000000 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/infrastructure/layout.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import { redirect } from "next/navigation"; -import { getTeamBySlug } from "@/api/team/get-team"; - -export default async function Layout(props: { - children: React.ReactNode; - params: Promise<{ - team_slug: string; - }>; -}) { - const params = await props.params; - const team = await getTeamBySlug(params.team_slug); - if (!team) { - redirect("/team"); - } - return
{props.children}
; -}