diff --git a/apps/dashboard/src/app/team/[team_slug]/(team)/_components/BillingAlertBanner.stories.tsx b/apps/dashboard/src/app/team/[team_slug]/(team)/_components/BillingAlertBanner.stories.tsx new file mode 100644 index 00000000000..c85080c19c7 --- /dev/null +++ b/apps/dashboard/src/app/team/[team_slug]/(team)/_components/BillingAlertBanner.stories.tsx @@ -0,0 +1,32 @@ +import type { Meta, StoryObj } from "@storybook/react"; +import { + PastDueBannerUI, + ServiceCutOffBannerUI, +} from "./BillingAlertBannersUI"; + +const meta = { + title: "Banners/Billing Alerts", + parameters: { + layout: "centered", + }, +} satisfies Meta; + +export default meta; + +type Story = StoryObj; + +export const PaymentAlerts: Story = { + render: () => ( +
+ Promise.resolve({ status: 200 })} + /> + + Promise.resolve({ status: 200 })} + /> +
+ ), +}; diff --git a/apps/dashboard/src/app/team/[team_slug]/(team)/_components/BillingAlertBanners.tsx b/apps/dashboard/src/app/team/[team_slug]/(team)/_components/BillingAlertBanners.tsx new file mode 100644 index 00000000000..ca2871db09e --- /dev/null +++ b/apps/dashboard/src/app/team/[team_slug]/(team)/_components/BillingAlertBanners.tsx @@ -0,0 +1,25 @@ +"use client"; + +import { redirectToBillingPortal } from "@/actions/billing"; +import { + PastDueBannerUI, + ServiceCutOffBannerUI, +} from "./BillingAlertBannersUI"; + +export function PastDueBanner(props: { teamSlug: string }) { + return ( + + ); +} + +export function ServiceCutOffBanner(props: { teamSlug: string }) { + return ( + + ); +} diff --git a/apps/dashboard/src/app/team/[team_slug]/(team)/_components/BillingAlertBannersUI.tsx b/apps/dashboard/src/app/team/[team_slug]/(team)/_components/BillingAlertBannersUI.tsx new file mode 100644 index 00000000000..541a7889a93 --- /dev/null +++ b/apps/dashboard/src/app/team/[team_slug]/(team)/_components/BillingAlertBannersUI.tsx @@ -0,0 +1,94 @@ +"use client"; + +import type { BillingBillingPortalAction } from "@/actions/billing"; +import { BillingPortalButton } from "@/components/billing"; +import { Spinner } from "@/components/ui/Spinner/Spinner"; +import { cn } from "@/lib/utils"; +import { useState } from "react"; + +function BillingAlertBanner(props: { + title: string; + description: React.ReactNode; + teamSlug: string; + variant: "error" | "warning"; + ctaLabel: string; + redirectToBillingPortal: BillingBillingPortalAction; +}) { + const [isRouteLoading, setIsRouteLoading] = useState(false); + + return ( +
+

{props.title}

+

{props.description}

+ { + setIsRouteLoading(true); + }} + > + {props.ctaLabel} + {isRouteLoading ? : null} + +
+ ); +} + +export function PastDueBannerUI(props: { + teamSlug: string; + redirectToBillingPortal: BillingBillingPortalAction; +}) { + return ( + + You have unpaid invoices. Service may be suspended if not paid + promptly. + + } + teamSlug={props.teamSlug} + /> + ); +} + +export function ServiceCutOffBannerUI(props: { + teamSlug: string; + redirectToBillingPortal: BillingBillingPortalAction; +}) { + return ( + + Your service has been suspended due to unpaid invoices. Pay now to + resume service. + + } + teamSlug={props.teamSlug} + /> + ); +} diff --git a/apps/dashboard/src/app/team/[team_slug]/layout.tsx b/apps/dashboard/src/app/team/[team_slug]/layout.tsx index bca4fd7c6bb..262f263ed83 100644 --- a/apps/dashboard/src/app/team/[team_slug]/layout.tsx +++ b/apps/dashboard/src/app/team/[team_slug]/layout.tsx @@ -3,6 +3,10 @@ import { AppFooter } from "@/components/blocks/app-footer"; import { redirect } from "next/navigation"; import { TWAutoConnect } from "../../components/autoconnect"; import { SaveLastVisitedTeamPage } from "../components/last-visited-page/SaveLastVisitedPage"; +import { + PastDueBanner, + ServiceCutOffBanner, +} from "./(team)/_components/BillingAlertBanners"; export default async function RootTeamLayout(props: { children: React.ReactNode; @@ -17,8 +21,19 @@ export default async function RootTeamLayout(props: { return (
-
{props.children}
+
+ {team.billingStatus === "pastDue" && ( + + )} + + {team.billingStatus === "invalidPayment" && ( + + )} + + {props.children} +
+