diff --git a/apps/dashboard/src/components/engine/create/CreateEnginePage.tsx b/apps/dashboard/src/components/engine/create/CreateEnginePage.tsx index 05817eeca7c..789a86a06aa 100644 --- a/apps/dashboard/src/components/engine/create/CreateEnginePage.tsx +++ b/apps/dashboard/src/components/engine/create/CreateEnginePage.tsx @@ -93,10 +93,7 @@ export const CreateEnginePage = () => { /> )} - setIsBillingModalOpen(false)} - > + { if (!selectedTier) { diff --git a/apps/dashboard/src/components/onboarding/ApplyForOpCreditsModal.tsx b/apps/dashboard/src/components/onboarding/ApplyForOpCreditsModal.tsx index 8822a191020..6918e1269bb 100644 --- a/apps/dashboard/src/components/onboarding/ApplyForOpCreditsModal.tsx +++ b/apps/dashboard/src/components/onboarding/ApplyForOpCreditsModal.tsx @@ -216,10 +216,7 @@ export const ApplyForOpCreditsModal: React.FC = () => { /> )} {/* // Add Payment Method Modal */} - + { setHasAddedPaymentMethod(true); diff --git a/apps/dashboard/src/components/onboarding/Billing.tsx b/apps/dashboard/src/components/onboarding/Billing.tsx index 9acfdf64372..5477e91b942 100644 --- a/apps/dashboard/src/components/onboarding/Billing.tsx +++ b/apps/dashboard/src/components/onboarding/Billing.tsx @@ -1,14 +1,13 @@ import { accountKeys } from "@3rdweb-sdk/react/cache-keys"; import { useUpdateAccount } from "@3rdweb-sdk/react/hooks/useApi"; import { useLoggedInUser } from "@3rdweb-sdk/react/hooks/useLoggedInUser"; -import { Flex, FocusLock } from "@chakra-ui/react"; import { Elements } from "@stripe/react-stripe-js"; import { loadStripe } from "@stripe/stripe-js"; import { useQueryClient } from "@tanstack/react-query"; import { useTrack } from "hooks/analytics/useTrack"; import { useTheme } from "next-themes"; import { OnboardingPaymentForm } from "./PaymentForm"; -import { OnboardingTitle } from "./Title"; +import { TitleAndDescription } from "./Title"; // only load stripe if the key is available const stripePromise = process.env.NEXT_PUBLIC_STRIPE_KEY @@ -65,40 +64,37 @@ const OnboardingBilling: React.FC = ({ }; return ( - - - + +
+ + { + queryClient.invalidateQueries({ + queryKey: accountKeys.me(user?.address as string), + }); + onSave(); + }} + onCancel={handleCancel} /> - - - { - queryClient.invalidateQueries({ - queryKey: accountKeys.me(user?.address as string), - }); - onSave(); - }} - onCancel={handleCancel} - /> - - - - + +
); }; diff --git a/apps/dashboard/src/components/onboarding/ChoosePlan.tsx b/apps/dashboard/src/components/onboarding/ChoosePlan.tsx index 0bbc21ece85..65a01a3288b 100644 --- a/apps/dashboard/src/components/onboarding/ChoosePlan.tsx +++ b/apps/dashboard/src/components/onboarding/ChoosePlan.tsx @@ -2,10 +2,9 @@ import { AccountPlan, useUpdateAccountPlan, } from "@3rdweb-sdk/react/hooks/useApi"; -import { SimpleGrid } from "@chakra-ui/react"; import { PricingCard } from "components/homepage/sections/PricingCard"; import { useTrack } from "hooks/analytics/useTrack"; -import { OnboardingTitle } from "./Title"; +import { TitleAndDescription } from "./Title"; interface OnboardingChoosePlanProps { onSave: () => void; @@ -70,11 +69,11 @@ const OnboardingChoosePlan: React.FC = ({ return ( <> - - +
= ({ }} onDashboard /> - +
); }; diff --git a/apps/dashboard/src/components/onboarding/ConfirmEmail.tsx b/apps/dashboard/src/components/onboarding/ConfirmEmail.tsx index e64cab118db..8e8ee329a8a 100644 --- a/apps/dashboard/src/components/onboarding/ConfirmEmail.tsx +++ b/apps/dashboard/src/components/onboarding/ConfirmEmail.tsx @@ -17,7 +17,7 @@ import { useForm } from "react-hook-form"; import OtpInput from "react-otp-input"; import { Button, Text } from "tw-components"; import { shortenString } from "utils/usedapp-external"; -import { OnboardingTitle } from "./Title"; +import { TitleAndDescription } from "./Title"; interface OnboardingConfirmEmailProps { email: string; @@ -158,7 +158,7 @@ const OnboardingConfirmEmail: React.FC = ({ return ( <> - = ({ return ( - = ({ return ( <> - diff --git a/apps/dashboard/src/components/onboarding/Modal.tsx b/apps/dashboard/src/components/onboarding/Modal.tsx index 283a871196b..ce3a61e5f81 100644 --- a/apps/dashboard/src/components/onboarding/Modal.tsx +++ b/apps/dashboard/src/components/onboarding/Modal.tsx @@ -1,52 +1,34 @@ -import { - Flex, - Modal, - ModalBody, - ModalContent, - ModalOverlay, - useBreakpointValue, -} from "@chakra-ui/react"; +import { Dialog, DialogContent } from "@/components/ui/dialog"; +import { cn } from "@/lib/utils"; import { IconLogo } from "components/logo"; import type { ComponentWithChildren } from "types/component-with-children"; interface OnboardingModalProps { isOpen: boolean; - onClose: () => void; wide?: boolean; } export const OnboardingModal: ComponentWithChildren = ({ children, isOpen, - onClose, - wide = false, + wide, }) => { - const isMobile = useBreakpointValue({ base: true, md: false }); - return ( - - - - -
+ + +
+
- - - {children} - - - - +
{children}
+
+
+
); }; diff --git a/apps/dashboard/src/components/onboarding/PaymentForm.tsx b/apps/dashboard/src/components/onboarding/PaymentForm.tsx index 6c054abb73d..6fe0b8e5a9a 100644 --- a/apps/dashboard/src/components/onboarding/PaymentForm.tsx +++ b/apps/dashboard/src/components/onboarding/PaymentForm.tsx @@ -1,11 +1,7 @@ +import { Spinner } from "@/components/ui/Spinner/Spinner"; +import { Alert, AlertTitle } from "@/components/ui/alert"; +import { Button } from "@/components/ui/button"; import { useCreatePaymentMethod } from "@3rdweb-sdk/react/hooks/useApi"; -import { - Alert, - AlertDescription, - AlertIcon, - Flex, - Spinner, -} from "@chakra-ui/react"; import { PaymentElement, useElements, @@ -15,7 +11,6 @@ import { PaymentVerificationFailureAlert } from "components/settings/Account/Bil import { useErrorHandler } from "contexts/error-handler"; import { useTrack } from "hooks/analytics/useTrack"; import { type FormEvent, useState } from "react"; -import { Button, Text } from "tw-components"; interface OnboardingPaymentForm { onSave: () => void; @@ -101,69 +96,44 @@ export const OnboardingPaymentForm: React.FC = ({ return (
- +
setLoading(false)} options={{ terms: { card: "never" } }} /> {loading ? ( -
- +
+
) : ( - +
{paymentFailureCode ? ( ) : ( - -
- - - - A temporary hold will be placed and immediately released - on your payment method. - - -
+ + + A temporary hold will be placed and immediately released on + your payment method. + )} - - - +
+ + + +
+
)} -
+
); }; diff --git a/apps/dashboard/src/components/onboarding/Title.tsx b/apps/dashboard/src/components/onboarding/Title.tsx index 93eaa4be543..d7c0537bda6 100644 --- a/apps/dashboard/src/components/onboarding/Title.tsx +++ b/apps/dashboard/src/components/onboarding/Title.tsx @@ -1,22 +1,20 @@ -import { Heading, Text } from "tw-components"; - -interface OnboardingTitleProps { +type TitleAndDescriptionProps = { heading: string | JSX.Element; - description?: string | JSX.Element; -} + description: string | JSX.Element; +}; -export const OnboardingTitle: React.FC = ({ +export const TitleAndDescription: React.FC = ({ heading, description, }) => { return ( -
- {heading} +
+

+ {heading} +

{description && ( - - {description} - +
{description}
)}
); diff --git a/apps/dashboard/src/components/onboarding/index.tsx b/apps/dashboard/src/components/onboarding/index.tsx index 4b97733aa96..923d76c953c 100644 --- a/apps/dashboard/src/components/onboarding/index.tsx +++ b/apps/dashboard/src/components/onboarding/index.tsx @@ -202,11 +202,7 @@ export const Onboarding: React.FC = () => { } return ( - setState("skipped")} - wide={state === "plan"} - > + {state === "onboarding" && ( }> void; - buttonProps?: { - variant?: "outline" | "solid"; - colorScheme?: "primary"; - }; } export const ManageBillingButton: React.FC = ({ account, loading, loadingText, - buttonProps = { variant: "outline", color: loading ? "gray" : "blue.500" }, onClick, }) => { + const trackEvent = useTrack(); + const [buttonLabel, buttonText] = useMemo(() => { switch (account.status) { case AccountStatus.InvalidPayment: @@ -52,31 +52,40 @@ export const ManageBillingButton: React.FC = ({ } }, [query.data, buttonLabel, account.stripePaymentActionUrl]); - const handleClick = (e: MouseEvent) => { - if (loading) { - e.preventDefault(); - return; - } - - if (!["verifyPaymentMethod", "manage"].includes(buttonLabel)) { - e.preventDefault(); - onClick?.(); - } - }; + if (url) { + return ( + + ); + } return ( - { + trackEvent({ + category: "billingAccount", + label: buttonLabel, + action: "click", + }); + + if (loading) { + e.preventDefault(); + return; + } + + if (!["verifyPaymentMethod", "manage"].includes(buttonLabel)) { + e.preventDefault(); + onClick?.(); + } + }} + className="gap-2" > - {buttonText} - + {loading && } + {loading ? loadingText : buttonText} + ); }; diff --git a/apps/dashboard/src/components/settings/Account/Billing/alerts/Alert.tsx b/apps/dashboard/src/components/settings/Account/Billing/alerts/Alert.tsx index 499b17f17c7..6ec37f4c90f 100644 --- a/apps/dashboard/src/components/settings/Account/Billing/alerts/Alert.tsx +++ b/apps/dashboard/src/components/settings/Account/Billing/alerts/Alert.tsx @@ -1,26 +1,24 @@ +"use client"; + +import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; +import { Button } from "@/components/ui/button"; +import { TrackedLinkTW } from "@/components/ui/tracked-link"; import { + type Account, AccountStatus, + type UsageBillableByService, useAccount, useAccountUsage, } from "@3rdweb-sdk/react/hooks/useApi"; import { useLoggedInUser } from "@3rdweb-sdk/react/hooks/useLoggedInUser"; -import { - Alert, - AlertDescription, - AlertIcon, - AlertTitle, - Flex, - IconButton, - useDisclosure, -} from "@chakra-ui/react"; +import { useDisclosure } from "@chakra-ui/react"; import { OnboardingModal } from "components/onboarding/Modal"; import { format } from "date-fns"; import { useTrack } from "hooks/analytics/useTrack"; import { useLocalStorage } from "hooks/useLocalStorage"; -import { useRouter } from "next/router"; +import { ExternalLinkIcon, XIcon } from "lucide-react"; +import { usePathname } from "next/navigation"; import { useCallback, useMemo, useState } from "react"; -import { FiX } from "react-icons/fi"; -import { Heading, Text, TrackedLinkButton } from "tw-components"; import { LazyOnboardingBilling } from "../../../../onboarding/LazyOnboardingBilling"; import { ManageBillingButton } from "../ManageButton"; import { RecurringPaymentFailureAlert } from "./RecurringPaymentFailureAlert"; @@ -39,6 +37,7 @@ type AlertConditionType = { }; export const BillingAlerts = () => { + const pathname = usePathname(); const { isLoggedIn } = useLoggedInUser(); const usageQuery = useAccountUsage(); const meQuery = useAccount({ @@ -51,13 +50,34 @@ export const BillingAlerts = () => { ? 1000 : false, }); - const { data: account } = meQuery; - const router = useRouter(); + + if ( + !isLoggedIn || + !meQuery.data || + !usageQuery.data || + pathname?.includes("/support") + ) { + return null; + } + + return ( + + ); +}; + +export function BillingAlertsUI(props: { + usageData: UsageBillableByService; + dashboardAccount: Account; +}) { + const { usageData, dashboardAccount } = props; const trackEvent = useTrack(); const [dismissedAlerts, setDismissedAlerts] = useLocalStorage< Record | undefined - >(`dismissed-billing-alert-${account?.id}`, undefined, {}); + >(`dismissed-billing-alert-${dashboardAccount.id}`, undefined, {}); const handleDismiss = useCallback( (key: string) => { @@ -81,13 +101,13 @@ export const BillingAlerts = () => { // Alert shouldShowAlerts based on the possible states of the account const alertConditions = useMemo(() => { - const hasUsageData = !!usageQuery.data; - const { usage, limits, rateLimitedAt } = usageQuery.data || {}; + const { usage, limits, rateLimitedAt } = usageData; // Define alert shouldShowAlerts including the directly computed ones const paymentFailureAlerts: AlertConditionType[] = [ { - shouldShowAlert: account?.status === AccountStatus.PaymentVerification, + shouldShowAlert: + dashboardAccount.status === AccountStatus.PaymentVerification, key: "verifyPaymentAlert", title: "Your payment method requires verification", description: @@ -96,7 +116,8 @@ export const BillingAlerts = () => { componentType: "paymentVerification", }, { - shouldShowAlert: account?.status === AccountStatus.InvalidPaymentMethod, + shouldShowAlert: + dashboardAccount.status === AccountStatus.InvalidPaymentMethod, key: "invalidPaymentMethodAlert", title: "Your payment method is invalid", description: @@ -104,7 +125,7 @@ export const BillingAlerts = () => { status: "error", componentType: "paymentVerification", }, - ...(account?.recurringPaymentFailures?.map((failure) => { + ...(dashboardAccount.recurringPaymentFailures?.map((failure) => { const serviceCutoffDate = failure.serviceCutoffDate ? format(new Date(failure.serviceCutoffDate), "MMMM d, yyyy") : null; @@ -127,23 +148,18 @@ export const BillingAlerts = () => { // Directly compute usage and rate limit shouldShowAlerts within useMemo const exceededUsage_50 = - hasUsageData && - usage && - limits && - (usage.embeddedWallets.countWalletAddresses >= + usage.embeddedWallets.countWalletAddresses >= limits.embeddedWallets / 2 || - usage.storage.sumFileSizeBytes >= limits.storage / 2); + usage.storage.sumFileSizeBytes >= limits.storage / 2; const exceededUsage_100 = - hasUsageData && - usage && - limits && - (usage.embeddedWallets.countWalletAddresses >= limits.embeddedWallets || - usage.storage.sumFileSizeBytes >= limits.storage); - - const hasHardLimits = account?.status !== AccountStatus.ValidPayment; - const isFreePlan = account?.plan === "free"; - const isGrowthPlan = account?.plan === "growth"; + usage.embeddedWallets.countWalletAddresses >= limits.embeddedWallets || + usage.storage.sumFileSizeBytes >= limits.storage; + + const hasHardLimits = + dashboardAccount.status !== AccountStatus.ValidPayment; + const isFreePlan = dashboardAccount.plan === "free"; + const isGrowthPlan = dashboardAccount.plan === "growth"; const usageAlerts: AlertConditionType[] = [ { // Show alert if user has exceeded 50% of their usage limit and has not yet exceeded 100% of their usage limit and has hard limits @@ -178,7 +194,7 @@ export const BillingAlerts = () => { }, { // only show RPC warning if the user has exceeded their RPC rate limit and has hard limits - shouldShowAlert: hasUsageData && !!rateLimitedAt?.rpc && hasHardLimits, + shouldShowAlert: !!rateLimitedAt?.rpc && hasHardLimits, key: "rate_rpc_alert", title: "You have exceeded your RPC rate limit", description: @@ -188,8 +204,7 @@ export const BillingAlerts = () => { }, { // only show Storage warning if the user has exceeded their Storage rate limit and has hard limits - shouldShowAlert: - hasUsageData && !!rateLimitedAt?.storage && hasHardLimits, + shouldShowAlert: !!rateLimitedAt?.storage && hasHardLimits, key: "rate_storage_alert", title: "You have exceeded your Storage Gateway rate limit", description: @@ -200,17 +215,9 @@ export const BillingAlerts = () => { ]; return [...paymentFailureAlerts, ...usageAlerts]; - }, [account, usageQuery.data]); + }, [dashboardAccount, usageData]); - if ( - !isLoggedIn || - meQuery.isPending || - !account || - usageQuery.isPending || - !usageQuery.data || - router.pathname.includes("/support") || - alertConditions.length === 0 - ) { + if (alertConditions.length === 0) { return null; } @@ -221,6 +228,7 @@ export const BillingAlerts = () => { const isDismissedMoreThanAWeekAgo = (dismissedAlerts?.[alert.key] ?? 0) < Date.now() - 1000 * 60 * 60 * 24 * 7; + if (shouldShowAlert && (!isDismissed || isDismissedMoreThanAWeekAgo)) { switch (alert.componentType) { case "recurringPayment": { @@ -230,6 +238,7 @@ export const BillingAlerts = () => { key={index} affectedServices={[alert.description].filter((v) => v)} paymentFailureCode={alert.key} + dashboardAccount={dashboardAccount} /> ); } @@ -241,12 +250,13 @@ export const BillingAlerts = () => { key={index} affectedServices={[alert.description].filter((v) => v)} paymentFailureCode={alert.key} + dashboardAccount={dashboardAccount} /> ); } case "paymentVerification": { return ( - { ctaText="Verify payment method" ctaHref="/dashboard/settings/billing" label="verifyPaymentAlert" + dashboardAccount={dashboardAccount} /> ); } case "usage": { return ( - { ctaText="Upgrade your plan" ctaHref="/dashboard/settings/billing" label="upgradePlanAlert" + dashboardAccount={dashboardAccount} /> ); } @@ -282,10 +294,11 @@ export const BillingAlerts = () => { if (alerts.length === 0) { return null; } - return
{alerts}
; -}; -type BillingAlertNotificationProps = { + return
{alerts}
; +} + +type AddPaymentNotificationProps = { status: "error" | "warning"; onDismiss?: () => void; title: string; @@ -294,9 +307,10 @@ type BillingAlertNotificationProps = { ctaText?: string; ctaHref?: string; label?: string; + dashboardAccount: Account; }; -const BillingAlertNotification: React.FC = ({ +const AddPaymentNotification: React.FC = ({ status, onDismiss, title, @@ -305,12 +319,11 @@ const BillingAlertNotification: React.FC = ({ ctaHref = "/dashboard/settings/billing", label = "addPaymentAlert", showCTAs = true, + dashboardAccount, }) => { // TODO: We should find a way to move this deeper into the // TODO: ManageBillingButton component and set an optional field to override const [paymentMethodSaving, setPaymentMethodSaving] = useState(false); - const meQuery = useAccount(); - const { data: account } = meQuery; const { onOpen: onPaymentMethodOpen, @@ -327,85 +340,67 @@ const BillingAlertNotification: React.FC = ({ return ( - + -
- - - - - {title} - - - - {description} - {showCTAs && ( -
- {isBilling && account ? ( - - ) : ( - - {ctaText} - - )} - - Contact Support - -
- )} -
-
-
+ {title} + {description} + + {showCTAs && ( +
+ {isBilling ? ( + + ) : ( + + )} + + +
+ )} {onDismiss && ( - } - color="bgBlack" + )}
); diff --git a/apps/dashboard/src/components/settings/Account/Billing/alerts/BillingAlerts.stories.tsx b/apps/dashboard/src/components/settings/Account/Billing/alerts/BillingAlerts.stories.tsx new file mode 100644 index 00000000000..bceb177f0e3 --- /dev/null +++ b/apps/dashboard/src/components/settings/Account/Billing/alerts/BillingAlerts.stories.tsx @@ -0,0 +1,134 @@ +import { ChakraProviderSetup } from "@/components/ChakraProviderSetup"; +import type { Meta, StoryObj } from "@storybook/react"; +import { ThirdwebProvider } from "thirdweb/react"; +import { AccountStatus } from "../../../../../@3rdweb-sdk/react/hooks/useApi"; +import { + createBillableServiceUsageDataStub, + createDashboardAccountStub, +} from "../../../../../stories/stubs"; +import { BadgeContainer, mobileViewport } from "../../../../../stories/utils"; +import { BillingAlertsUI } from "./Alert"; + +const meta = { + title: "blocks/BillingAlerts", + component: Story, + parameters: { + nextjs: { + appDirectory: true, + }, + }, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Desktop: Story = { + args: {}, +}; + +export const Mobile: Story = { + args: {}, + parameters: { + viewport: mobileViewport("iphone14"), + }, +}; + +function yesterday(d: Date) { + return new Date(d.getTime() - 24 * 60 * 60 * 1000); +} + +function tomorrow(d: Date) { + return new Date(d.getTime() + 24 * 60 * 60 * 1000); +} + +function Story() { + return ( +
+ + +
+ + + + + + + + + + + + + + + + + + + +
+
+
+
+ ); +} diff --git a/apps/dashboard/src/components/settings/Account/Billing/alerts/PaymentVerificationFailureAlert.tsx b/apps/dashboard/src/components/settings/Account/Billing/alerts/PaymentVerificationFailureAlert.tsx index 8b548d2af1f..f6463150be1 100644 --- a/apps/dashboard/src/components/settings/Account/Billing/alerts/PaymentVerificationFailureAlert.tsx +++ b/apps/dashboard/src/components/settings/Account/Billing/alerts/PaymentVerificationFailureAlert.tsx @@ -1,12 +1,7 @@ -import { - Alert, - AlertDescription, - AlertIcon, - AlertTitle, - Flex, -} from "@chakra-ui/react"; +import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; +import { TrackedLinkTW } from "@/components/ui/tracked-link"; import { getBillingPaymentMethodVerificationFailureResponse } from "lib/billing"; -import { Heading, Text, TrackedLinkButton } from "tw-components"; +import { ExternalLinkIcon } from "lucide-react"; type PaymentVerificationFailureAlertProps = { onDismiss?: () => void; @@ -20,44 +15,25 @@ export const PaymentVerificationFailureAlert: React.FC< getBillingPaymentMethodVerificationFailureResponse({ paymentFailureCode }); return ( - -
- - - - - ERROR: {title} - - - - - {reason ? `${reason}. ` : ""} - {resolution ? `${resolution}.` : ""} - - - - Contact Support - - - - -
+ + {title} + +

+ {reason ? `${reason}. ` : ""} + {resolution ? `${resolution}.` : ""} +

+ + + Contact Support + + +
); }; diff --git a/apps/dashboard/src/components/settings/Account/Billing/alerts/RecurringPaymentFailureAlert.tsx b/apps/dashboard/src/components/settings/Account/Billing/alerts/RecurringPaymentFailureAlert.tsx index b88830e20b2..1427a48fb1d 100644 --- a/apps/dashboard/src/components/settings/Account/Billing/alerts/RecurringPaymentFailureAlert.tsx +++ b/apps/dashboard/src/components/settings/Account/Billing/alerts/RecurringPaymentFailureAlert.tsx @@ -1,19 +1,12 @@ -import { AccountStatus, useAccount } from "@3rdweb-sdk/react/hooks/useApi"; -import { - Alert, - AlertDescription, - AlertIcon, - AlertTitle, - Flex, - IconButton, - UnorderedList, - useDisclosure, -} from "@chakra-ui/react"; +import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; +import { Button } from "@/components/ui/button"; +import { TrackedLinkTW } from "@/components/ui/tracked-link"; +import { type Account, AccountStatus } from "@3rdweb-sdk/react/hooks/useApi"; import { OnboardingModal } from "components/onboarding/Modal"; import { getRecurringPaymentFailureResponse } from "lib/billing"; +import { ExternalLinkIcon, XIcon } from "lucide-react"; import { useState } from "react"; -import { FiX } from "react-icons/fi"; -import { Heading, Text, TrackedLinkButton } from "tw-components"; +import { Text } from "tw-components"; import { LazyOnboardingBilling } from "../../../../onboarding/LazyOnboardingBilling"; import { ManageBillingButton } from "../ManageButton"; @@ -22,11 +15,13 @@ type RecurringPaymentFailureAlertProps = { onDismiss?: () => void; affectedServices?: string[]; paymentFailureCode: string; + dashboardAccount: Account; }; export const RecurringPaymentFailureAlert: React.FC< RecurringPaymentFailureAlertProps > = ({ + dashboardAccount, isServiceCutoff = false, onDismiss, affectedServices = [], @@ -35,18 +30,11 @@ export const RecurringPaymentFailureAlert: React.FC< // TODO: We should find a way to move this deeper into the // TODO: ManageBillingButton component and set an optional field to override const [paymentMethodSaving, setPaymentMethodSaving] = useState(false); - const meQuery = useAccount(); - const { data: account } = meQuery; - - const { - onOpen: onPaymentMethodOpen, - onClose: onPaymentMethodClose, - isOpen: isPaymentMethodOpen, - } = useDisclosure(); + const [isPaymentMethodOpen, setIsPaymentMethodOpen] = useState(false); const handlePaymentAdded = () => { setPaymentMethodSaving(true); - onPaymentMethodClose(); + setIsPaymentMethodOpen(false); }; const { title, reason, resolution } = getRecurringPaymentFailureResponse({ @@ -58,100 +46,77 @@ export const RecurringPaymentFailureAlert: React.FC< : title; return ( - - + + setIsPaymentMethodOpen(false)} /> -
- - - - - {header} - - - - - - {reason ? `${reason}. ` : ""} - {resolution ? `${resolution}. ` : ""} - {isServiceCutoff - ? "" - : "We will retry several times over the next 10 days after your invoice date, after which you will lose access to your services."} - - {affectedServices.length > 0 && ( -
- Affected services: - - {affectedServices.map((service) => ( -
  • - {service} -
  • - ))} -
    -
    - )} -
    - {account && ( - - )} - - Contact Support - -
    -
    -
    -
    + {header} + + {reason ? `${reason}. ` : ""} + {resolution ? `${resolution}. ` : ""} + {isServiceCutoff + ? "" + : "We will retry several times over the next 10 days after your invoice date, after which you will lose access to your services."} + + +
    + {affectedServices.length > 0 && ( +
    +

    Affected services:

    +
      + {affectedServices.map((service) => ( +
    • + {service} +
    • + ))} +
    +
    + )} + +
    + setIsPaymentMethodOpen(true) + } + /> + + +
    {onDismiss && ( - } - color="bgBlack" + )} ); diff --git a/apps/dashboard/src/components/settings/Account/Billing/index.tsx b/apps/dashboard/src/components/settings/Account/Billing/index.tsx index ef9ebbe6db9..7f3721b3692 100644 --- a/apps/dashboard/src/components/settings/Account/Billing/index.tsx +++ b/apps/dashboard/src/components/settings/Account/Billing/index.tsx @@ -248,10 +248,7 @@ export const Billing: React.FC = ({ account }) => { <> - + = {}, +): Account { + return { + id: id, + name: `Name ${id}`, + email: `email-${id}@example.com`, + status: AccountStatus.NoPayment, + plan: AccountPlan.Free, + advancedEnabled: false, + currentBillingPeriodStartsAt: new Date().toISOString(), + currentBillingPeriodEndsAt: new Date().toISOString(), + emailConfirmedAt: new Date().toISOString(), + creatorWalletAddress: ZERO_ADDRESS, + isStaff: false, + recurringPaymentFailures: [], + ...overrides, + }; +} + +export function createBillableServiceUsageDataStub( + overrides: Partial = {}, +): UsageBillableByService { + return { + usage: { + bundler: [], + storage: { + sumFileSizeBytes: 0, + }, + embeddedWallets: { + countWalletAddresses: 0, + }, + }, + billableUsd: { + bundler: 0, + storage: 0, + embeddedWallets: 0, + }, + limits: { + storage: 0, + embeddedWallets: 0, + }, + rateLimits: { + storage: 0, + rpc: 0, + }, + rateLimitedAt: {}, + ...overrides, + }; +}