diff --git a/apps/dashboard/src/components/billing/CloudDeployPlanModal.tsx b/apps/dashboard/src/components/billing/CloudDeployPlanModal.tsx index 7e2411e4..1c5c32f8 100644 --- a/apps/dashboard/src/components/billing/CloudDeployPlanModal.tsx +++ b/apps/dashboard/src/components/billing/CloudDeployPlanModal.tsx @@ -5,12 +5,16 @@ import { Icon as UiIcon } from "@repo/ui/icons"; import { useCallback, useEffect, useId, useRef, useState } from "react"; import { useDialogFocus } from "@/hooks/useDialogFocus"; import { useI18n } from "@/components/i18n-provider"; +import { Button } from "@/components/ui/button"; import { billingApi, type BillingState } from "@/lib/api/billing"; import { ApiError } from "@/lib/api/client"; import { cloudDeployRecovery, type CloudDeployRestriction } from "@/lib/cloud-deploy-pricing"; import { CloudPlanPicker } from "./CloudPlanPicker"; -export function CloudDeployPlanModal({ restriction, onClose }: { +export function CloudDeployPlanModal({ + restriction, + onClose, +}: { restriction: CloudDeployRestriction; onClose: () => void; }) { @@ -58,89 +62,167 @@ export function CloudDeployPlanModal({ restriction, onClose }: { }, [refresh]); const recovery = state ? cloudDeployRecovery(state, restriction) : "subscribe"; - const planChanged = recovery === "upgrade" && state && !state.overQuota && state.tier !== initialTier.current; + const planChanged = + recovery === "upgrade" && state && !state.overQuota && state.tier !== initialTier.current; const ready = recovery === "ready" || planChanged; const showPlans = !ready && (recovery === "subscribe" || recovery === "upgrade"); - const title = recovery === "credits" ? copy.creditsTitle - : recovery === "upgrade" ? copy.upgradeTitle - : recovery === "payment" || recovery === "paused" ? copy.blockedTitle : copy.title; - const reason = restriction.reason && Object.hasOwn(copy.reasons, restriction.reason) - ? copy.reasons[restriction.reason as keyof typeof copy.reasons] : undefined; - const description = ready ? copy.ready - : recovery === "credits" ? copy.creditsDescription - : recovery === "payment" ? copy.paymentDescription - : recovery === "paused" ? copy.pausedDescription - : recovery === "upgrade" ? (reason ?? copy.upgradeDescription) : copy.description; + const title = + recovery === "credits" + ? copy.creditsTitle + : recovery === "upgrade" + ? copy.upgradeTitle + : recovery === "payment" || recovery === "paused" + ? copy.blockedTitle + : copy.title; + const reason = + restriction.reason && Object.hasOwn(copy.reasons, restriction.reason) + ? copy.reasons[restriction.reason as keyof typeof copy.reasons] + : undefined; + const description = ready + ? copy.ready + : recovery === "credits" + ? copy.creditsDescription + : recovery === "payment" + ? copy.paymentDescription + : recovery === "paused" + ? copy.pausedDescription + : recovery === "upgrade" + ? (reason ?? copy.upgradeDescription) + : copy.description; return ( -
-
-
-
-
-

{ready ? copy.readyTitle : title}

-

{description}

-
-
- -
+
+
+

+ {ready ? copy.readyTitle : title} +

+ +
-
- {loading && !state ? ( -
-
- ) : error ? ( -

- {error === "owner" ? copy.ownerRequired : copy.loadError} +

+

+ {description}

- ) : state && ( -
- {showPlans && { setCheckoutStarted(true); setChecked(false); }} - />} - {(recovery === "credits" || recovery === "payment") && ( -
- {recovery === "credits" && state.billing?.enabled && state.topups?.available && ( - - {copy.topups} - )} - {recovery === "paused" && {t.billing.portal.supportButton}} - {checkoutStarted && checked && !loading && !ready &&

{copy.pending}

} -
- )} + ) + )} +

{copy.preserved}

-
-

{copy.preserved}

-
- {!ready && error !== "owner" && } - -
-
+ + )} + +
); } diff --git a/apps/dashboard/src/hooks/useCloudDeployPricing.tsx b/apps/dashboard/src/hooks/useCloudDeployPricing.tsx index 1734cd09..12bd5741 100644 --- a/apps/dashboard/src/hooks/useCloudDeployPricing.tsx +++ b/apps/dashboard/src/hooks/useCloudDeployPricing.tsx @@ -25,6 +25,8 @@ export function useCloudDeployPricing() { customContent: hideModal(id)} />, width: "100%", maxWidth: "1440px", + maxHeight: "calc(100dvh - 2rem)", + overflow: "hidden", showCloseButton: false, onClose: () => { openModal.current = null; }, }); diff --git a/docs/dashboard-design.md b/docs/dashboard-design.md index 6c857dea..8be98a97 100644 --- a/docs/dashboard-design.md +++ b/docs/dashboard-design.md @@ -50,6 +50,9 @@ Reuse [Input](../apps/dashboard/src/components/ui/input.tsx), - Plan cards respond to their container: one column on phones, two at intermediate widths, and four when readable. Offer links to each plan above a stacked comparison; never rely on hidden horizontal overflow to reveal additional plans. +- Deployment plan dialogs use compact title and action rows. Keep explanations in the + scrollable content so the plans receive most of the available viewport height, including + on short screens. Keep actions side by side on phones, allowing long labels to wrap. - The plans comparison (`/billing/plans`) and Scale canvas open with the desktop sidebar collapsed. Keep the toggle available, restore the normal preference on leaving, and keep manual expansion independent between these sections. Mobile navigation opens fully.