import React, { useState, useEffect } from "react"; import { Modal, Button, Text, Alert, Loader, Stack, Group, NumberInput, } from "@mantine/core"; import { useTranslation } from "react-i18next"; import { Z_INDEX_OVER_CONFIG_MODAL } from "@app/styles/zIndex"; interface UpdateSeatsModalProps { opened: boolean; onClose: () => void; currentSeats: number; minimumSeats: number; _onSuccess?: () => void; onError?: (error: string) => void; onUpdateSeats?: (newSeats: number) => Promise; // Returns billing portal URL } type UpdateState = { status: "idle" | "loading" | "error"; error?: string; }; const UpdateSeatsModal: React.FC = ({ opened, onClose, currentSeats, minimumSeats, _onSuccess, onError, onUpdateSeats, }) => { const { t } = useTranslation(); const [state, setState] = useState({ status: "idle" }); const [newSeatCount, setNewSeatCount] = useState(minimumSeats); // Reset seat count when modal opens useEffect(() => { if (opened) { setNewSeatCount(minimumSeats); setState({ status: "idle" }); } }, [opened, minimumSeats]); const handleUpdateSeats = async () => { if (!onUpdateSeats) { setState({ status: "error", error: "Update function not provided", }); return; } if (newSeatCount < minimumSeats) { setState({ status: "error", error: t( "billing.seatCountTooLow", "Seat count must be at least {{minimum}} (current number of users)", { minimum: minimumSeats, }, ), }); return; } if (newSeatCount === currentSeats) { setState({ status: "error", error: t( "billing.seatCountUnchanged", "Please select a different seat count", ), }); return; } try { setState({ status: "loading" }); // Call the update function (will call manage-billing) const portalUrl = await onUpdateSeats(newSeatCount); // Redirect to Stripe billing portal console.log("Redirecting to Stripe billing portal:", portalUrl); window.location.href = portalUrl; // Note: No need to call onSuccess here since we're redirecting // The return flow will handle success notification } catch (err) { const errorMessage = err instanceof Error ? err.message : "Failed to update seat count"; setState({ status: "error", error: errorMessage, }); onError?.(errorMessage); } }; const handleClose = () => { setState({ status: "idle" }); setNewSeatCount(currentSeats); onClose(); }; const renderContent = () => { if (state.status === "loading") { return ( {t("billing.preparingUpdate", "Preparing seat update...")} ); } return ( {state.status === "error" && ( {state.error} )} {t("billing.currentSeats", "Current Seats")}: {currentSeats} {t("billing.minimumSeats", "Minimum Seats")}: {minimumSeats} {t("billing.basedOnUsers", "(current users)")} setNewSeatCount(typeof value === "number" ? value : minimumSeats) } min={minimumSeats} max={10000} step={1} size="md" styles={{ input: { fontSize: "1.5rem", fontWeight: 500, textAlign: "center", }, }} /> {t( "billing.stripePortalRedirect", "You will be redirected to Stripe's billing portal to review and confirm the seat change. The prorated amount will be calculated automatically.", )} ); }; return ( {t("billing.updateEnterpriseSeats", "Update Enterprise Seats")} } size="md" centered withCloseButton={true} closeOnEscape={true} closeOnClickOutside={false} zIndex={Z_INDEX_OVER_CONFIG_MODAL} > {renderContent()} ); }; export default UpdateSeatsModal;