diff --git a/.changeset/little-pillows-divide.md b/.changeset/little-pillows-divide.md new file mode 100644 index 00000000000..caffb3f7ed2 --- /dev/null +++ b/.changeset/little-pillows-divide.md @@ -0,0 +1,5 @@ +--- +"@thirdweb-dev/service-utils": patch +--- + +add new capabilities to the type diff --git a/apps/dashboard/src/@/components/blocks/pricing-card.tsx b/apps/dashboard/src/@/components/blocks/pricing-card.tsx index c71909de405..c3cfbb76eb1 100644 --- a/apps/dashboard/src/@/components/blocks/pricing-card.tsx +++ b/apps/dashboard/src/@/components/blocks/pricing-card.tsx @@ -4,7 +4,7 @@ import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { ToolTipLabel } from "@/components/ui/tooltip"; import { cn } from "@/lib/utils"; -import { CheckIcon, CircleDollarSignIcon } from "lucide-react"; +import { CheckIcon, DollarSignIcon } from "lucide-react"; import Link from "next/link"; import type React from "react"; import { TEAM_PLANS } from "utils/pricing"; @@ -105,9 +105,12 @@ export const PricingCard: React.FC = ({ {/* Price */}
+ {plan.isStartingPriceOnly && ( + Starting at + )}
- ${plan.price} + ${plan.price.toLocaleString()} {!isCustomPrice && ( @@ -183,10 +186,10 @@ const billingPlanToSkuMap: Record = { starter: "plan:starter", growth: "plan:growth", - accelerate: "plan:accelerate", scale: "plan:scale", + pro: "plan:pro", // we can't render checkout buttons for these plans: - pro: undefined, + accelerate: undefined, free: undefined, growth_legacy: undefined, starter_legacy: undefined, @@ -201,16 +204,19 @@ function FeatureItem({ text }: FeatureItemProps) { return (
- + {Array.isArray(text) ? ( + + + + ) : ( + + )} {Array.isArray(text) ? (

{titleStr}{" "} {text[1]}

- - -
) : (

{titleStr}

diff --git a/apps/dashboard/src/@/lib/billing.ts b/apps/dashboard/src/@/lib/billing.ts index cca8d3385f8..992af032853 100644 --- a/apps/dashboard/src/@/lib/billing.ts +++ b/apps/dashboard/src/@/lib/billing.ts @@ -5,6 +5,7 @@ export type ProductSKU = | "plan:custom" | "plan:accelerate" | "plan:scale" + | "plan:pro" | "product:ecosystem_wallets" | "product:engine_standard" | "product:engine_premium" diff --git a/apps/dashboard/src/app/(app)/login/onboarding/team-onboarding/InviteTeamMembers.tsx b/apps/dashboard/src/app/(app)/login/onboarding/team-onboarding/InviteTeamMembers.tsx index 626e824c039..e527b169486 100644 --- a/apps/dashboard/src/app/(app)/login/onboarding/team-onboarding/InviteTeamMembers.tsx +++ b/apps/dashboard/src/app/(app)/login/onboarding/team-onboarding/InviteTeamMembers.tsx @@ -97,7 +97,8 @@ export function InviteTeamMembersUI(props: { recommendedMembers={[]} customCTASection={
- {props.team.billingPlan === "free" && ( + {(props.team.billingPlan === "free" || + props.team.billingPlan === "starter") && (
{/* Mobile */}
setPlanToShow("starter"), - isActive: planToShow === "starter", - }, { name: "Growth", onClick: () => setPlanToShow("growth"), isActive: planToShow === "growth", }, - { - name: "Accelerate", - onClick: () => setPlanToShow("accelerate"), - isActive: planToShow === "accelerate", - }, { name: "Scale", onClick: () => setPlanToShow("scale"), isActive: planToShow === "scale", }, + { + name: "Pro", + onClick: () => setPlanToShow("pro"), + isActive: planToShow === "pro", + }, ]} />
- {planToShow === "starter" && starterPlan} {planToShow === "growth" && growthPlan} - {planToShow === "accelerate" && acceleratePlan} {planToShow === "scale" && scalePlan} + {planToShow === "pro" && proPlan}
); diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/settings/members/InviteSection.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/settings/members/InviteSection.tsx index f8507997271..02579dd83a2 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/settings/members/InviteSection.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/settings/members/InviteSection.tsx @@ -85,7 +85,10 @@ export function InviteSection(props: { let bottomSection: React.ReactNode = null; const maxAllowedInvitesAtOnce = 10; // invites are enabled if user has edit permission and team plan is not "free" - const inviteEnabled = teamPlan !== "free" && props.userHasEditPermission; + const inviteEnabled = + teamPlan !== "free" && + teamPlan !== "starter" && + props.userHasEditPermission; const form = useForm({ resolver: zodResolver(inviteFormSchema), @@ -108,7 +111,7 @@ export function InviteSection(props: { }, }); - if (teamPlan === "free") { + if (teamPlan === "free" || teamPlan === "starter") { bottomSection = (

@@ -127,7 +130,7 @@ export function InviteSection(props: { ) : ( - - -

-
- ) : ( -
-

- Contact us to get a managed engine for your team -

- -
- )} -
- ); -} - export function EngineFooterCard(props: { teamPlan: Team["billingPlan"]; team_slug: string; @@ -152,16 +81,6 @@ export function EngineFooterCard(props: { }) { return (
- {props.teamPlan === "accelerate" || props.teamPlan === "scale" ? null : ( - <> - - - - )} - = planToTierRecordForGating[authRequiredPlan]; diff --git a/apps/dashboard/src/components/settings/Account/Billing/GatedSwitch.stories.tsx b/apps/dashboard/src/components/settings/Account/Billing/GatedSwitch.stories.tsx index 1991e7923b2..498c0eb5e5f 100644 --- a/apps/dashboard/src/components/settings/Account/Billing/GatedSwitch.stories.tsx +++ b/apps/dashboard/src/components/settings/Account/Billing/GatedSwitch.stories.tsx @@ -28,8 +28,8 @@ export const AllVariants: Story = { function Variants() { const [requiredPlan, setRequiredPlan] = useState< - "free" | "growth" | "accelerate" | "pro" - >("accelerate"); + "free" | "growth" | "scale" | "pro" + >("scale"); const plans: Team["billingPlan"][] = [ "free", @@ -38,6 +38,7 @@ function Variants() { "growth_legacy", "growth", "accelerate", + "scale", "pro", ]; @@ -57,7 +58,7 @@ function Variants() { Free Growth - Accelerate + Scale Pro diff --git a/apps/dashboard/src/components/settings/Account/Billing/Pricing.tsx b/apps/dashboard/src/components/settings/Account/Billing/Pricing.tsx index 1a2fa386e56..3504cba16b4 100644 --- a/apps/dashboard/src/components/settings/Account/Billing/Pricing.tsx +++ b/apps/dashboard/src/components/settings/Account/Billing/Pricing.tsx @@ -3,10 +3,7 @@ import type { Team } from "@/api/team"; import { PricingCard } from "@/components/blocks/pricing-card"; import { Spinner } from "@/components/ui/Spinner/Spinner"; -import { Button } from "@/components/ui/button"; import { useDashboardRouter } from "@/lib/DashboardRouter"; -import { CheckIcon } from "lucide-react"; -import Link from "next/link"; import { useTransition } from "react"; import { useStripeRedirectEvent } from "../../../../app/(app)/(stripe)/stripe-redirect/stripeRedirectChannel"; @@ -79,16 +76,16 @@ export const BillingPricing: React.FC = ({ (!highlightPlan && !isCurrentPlanScheduledToCancel && validTeamPlan === "starter_legacy"); - const highlightAcceleratePlan = - highlightPlan === "accelerate" || - (!highlightPlan && - !isCurrentPlanScheduledToCancel && - validTeamPlan === "growth"); const highlightScalePlan = highlightPlan === "scale" || (!highlightPlan && !isCurrentPlanScheduledToCancel && - validTeamPlan === "accelerate"); + (validTeamPlan === "accelerate" || validTeamPlan === "growth")); + const highlightProPlan = + highlightPlan === "pro" || + (!highlightPlan && + !isCurrentPlanScheduledToCancel && + validTeamPlan === "pro"); return (
@@ -139,89 +136,45 @@ export const BillingPricing: React.FC = ({ getTeam={getTeam} /> - {/* Accelerate */} + {/* Scale */} - {/* Scale */} + {/* Pro */}
-
-
); }; -const proFeatures = [ - "Custom rate limits for APIs & Infra", - "Guaranteed support response time", - "Direct access to solutions & engineering teams", - "Enterprise grade SLAs", -]; - -function ProCard() { - return ( -
-
-

- Need more? Upgrade to Pro -

-

- Ideal for teams that require more customization, SLAs, and support. -

- -
    - {proFeatures.map((feature, i) => ( - // biome-ignore lint/suspicious/noArrayIndexKey: -
  • - - {feature} -
  • - ))} -
-
- - -
- ); -} - function getPlanCta( currentPlan: Team["billingPlan"], targetPlan: Team["billingPlan"], isScheduledToCancel: boolean, ): CtaLink | undefined { - // if any of the plan is pro - show contact us - if (currentPlan === "pro" || targetPlan === "pro") { + // if the CURRENT plan is pro, show contact us link + if (currentPlan === "pro") { return { label: "Contact us", href: PRO_CONTACT_US_URL, diff --git a/apps/dashboard/src/stories/stubs.ts b/apps/dashboard/src/stories/stubs.ts index 17f64abf382..12fc9de81d5 100644 --- a/apps/dashboard/src/stories/stubs.ts +++ b/apps/dashboard/src/stories/stubs.ts @@ -57,6 +57,11 @@ export function teamStub(id: string, billingPlan: Team["billingPlan"]): Team { ], stripeCustomerId: "cus_1234567890", capabilities: { + platform: { + auditLogs: true, + ecosystemWallets: true, + seats: true, + }, rpc: { enabled: true, rateLimit: 1000, @@ -85,6 +90,10 @@ export function teamStub(id: string, billingPlan: Team["billingPlan"]): Team { enabled: true, customAuth: true, customBranding: true, + sms: { + domestic: true, + international: true, + }, }, nebula: { enabled: true, @@ -95,6 +104,10 @@ export function teamStub(id: string, billingPlan: Team["billingPlan"]): Team { rateLimit: 100, mainnetEnabled: true, }, + pay: { + enabled: true, + rateLimit: 1000, + }, }, planCancellationDate: null, unthreadCustomerId: null, diff --git a/apps/dashboard/src/utils/pricing.tsx b/apps/dashboard/src/utils/pricing.tsx index 264accc5e59..1b84718a829 100644 --- a/apps/dashboard/src/utils/pricing.tsx +++ b/apps/dashboard/src/utils/pricing.tsx @@ -3,13 +3,14 @@ import type { Team } from "../@/api/team"; type SelectivePlans = Exclude< Team["billingPlan"], // we will never show cards for these plans - so exclude it - "pro" | "growth_legacy" | "free" | "starter_legacy" + "accelerate" | "growth_legacy" | "free" | "starter_legacy" >; export const TEAM_PLANS: Record< SelectivePlans, { title: string; + isStartingPriceOnly?: boolean; price: string | number; subTitle: string | null; trialPeriodDays: number; @@ -18,61 +19,68 @@ export const TEAM_PLANS: Record< } > = { starter: { - price: 9, + price: 5, title: "Starter", subTitle: null, trialPeriodDays: 0, - description: "Ideal for hobbyists who require basic features.", + description: "Ideal for individual developers who require basic features.", features: [ - "$25 Usage Credits", - "Community Support", + "Community & AI Support", "Web, Mobile & Gaming SDKs", "Contract and Wallet APIs", "Audited smart contracts", + "Managed Infrastructure (RPC, IPFS)", "Account Abstraction", - "Blockchain Infra (RPC, IPFS)", + "Engine Cloud", ], }, growth: { - price: 79, + price: 99, title: "Growth", subTitle: "Everything in Starter, plus:", trialPeriodDays: 0, - description: "Ideal for scalable production-grade apps.", + description: "Ideal for teams building production-grade apps.", features: [ - "$100 Usage Credits", - "Email support", - "SMS Onboarding", - "Custom Wallet Branding & Auth", - "30d User Analytics", - "Gas grant for transactions", + "Email Support", + "48hr Guaranteed Response", + "Invite Team Members", + "Custom In-App Wallet Auth", ], }, - accelerate: { - price: 249, - title: "Accelerate", + scale: { + price: 499, + title: "Scale", + description: "For funded startups and mid-size businesses.", subTitle: "Everything in Growth, plus:", trialPeriodDays: 0, - description: "For funded startups and mid-size businesses.", features: [ - "$250 Usage Credits", - "24hr Guaranteed Support", - "90d User Analytics", + "Slack & Telegram Support", + "24hr Guaranteed Response", + "Remove thirdweb branding", + "Audit Logs", + [ + "Ecosystem Wallet Add-On", + "Unlocks the ability to deploy your own ecosystem wallets.", + ], + [ + "Dedicated Infrastructure Add-Ons", + "Dedicated RPC nodes, indexers, etc.", + ], ], }, - scale: { - price: 549, - title: "Scale", - description: "For large organizations with custom needs.", - subTitle: "Everything in Accelerate, plus:", + pro: { + price: 1499, + isStartingPriceOnly: true, + title: "Pro", + subTitle: "Everything in Scale, plus:", trialPeriodDays: 0, + description: "For large organizations with custom needs.", features: [ - "$500 Usage Credits", + "Dedicated Account Executive", "12hr Guaranteed Response", - "Slack Channel Support", - "Whitelabel Infra Support", - "Ultra-high Rate Limits", - "Ecosystem Wallets", + "No Rate Limits", + ["Custom Infrastructure Add-Ons", "Infrastructure for custom chains."], + ["Volume Discounts", "Negotiated volume discounts that fit your scale."], ], }, }; diff --git a/packages/service-utils/src/core/api.ts b/packages/service-utils/src/core/api.ts index 105a05eac56..844e3c04939 100644 --- a/packages/service-utils/src/core/api.ts +++ b/packages/service-utils/src/core/api.ts @@ -49,6 +49,11 @@ export type ApiResponse = { * This type should match the schema from API server. */ type TeamCapabilities = { + platform: { + auditLogs: boolean; + ecosystemWallets: boolean; + seats: boolean; + }; rpc: { enabled: boolean; rateLimit: number; @@ -81,12 +86,20 @@ type TeamCapabilities = { enabled: boolean; customAuth: boolean; customBranding: boolean; + sms: { + domestic: boolean; + international: boolean; + }; }; engineCloud: { enabled: boolean; mainnetEnabled: boolean; rateLimit: number; }; + pay: { + enabled: boolean; + rateLimit: number; + }; }; type TeamPlan = diff --git a/packages/service-utils/src/mocks.ts b/packages/service-utils/src/mocks.ts index 377a6f630c5..4f52effeaef 100644 --- a/packages/service-utils/src/mocks.ts +++ b/packages/service-utils/src/mocks.ts @@ -93,12 +93,25 @@ export const validTeamResponse: TeamResponse = { enabled: true, customAuth: true, customBranding: true, + sms: { + domestic: true, + international: true, + }, }, engineCloud: { enabled: true, mainnetEnabled: true, rateLimit: 100, }, + pay: { + enabled: true, + rateLimit: 1000, + }, + platform: { + auditLogs: true, + ecosystemWallets: true, + seats: true, + }, }, planCancellationDate: null, unthreadCustomerId: null,