From 051d8ef40110ff80cb5450b71dfb59eb05856489 Mon Sep 17 00:00:00 2001 From: Ilango Date: Fri, 18 Jul 2025 15:56:50 +0530 Subject: [PATCH] Add UI for credits --- packages/ui/src/api/user.js | 10 + packages/ui/src/views/account/index.jsx | 308 +++++++++++++++++++++++- 2 files changed, 315 insertions(+), 3 deletions(-) diff --git a/packages/ui/src/api/user.js b/packages/ui/src/api/user.js index cc5ff2992..6726558e2 100644 --- a/packages/ui/src/api/user.js +++ b/packages/ui/src/api/user.js @@ -25,6 +25,11 @@ const getPlanProration = (subscriptionId, newPlanId) => const updateSubscriptionPlan = (subscriptionId, newPlanId, prorationDate) => client.post(`/organization/update-subscription-plan`, { subscriptionId, newPlanId, prorationDate }) const getCurrentUsage = () => client.get(`/organization/get-current-usage`) +const getPredictionEligibility = () => client.get(`/organization/prediction-eligibility`) +const purchaseCredits = (packageType) => client.post(`/organization/purchase-credits`, { packageType }) +const getCreditsBalance = () => client.get(`/organization/credits-balance`) +const getUsageWithCredits = () => client.get(`/organization/usage-with-credits`) +const getCreditsPackages = () => client.get(`/organization/credits-packages`) // workspace users const getAllUsersByWorkspaceId = (workspaceId) => client.get(`/workspaceuser?workspaceId=${workspaceId}`) @@ -55,5 +60,10 @@ export default { getPlanProration, updateSubscriptionPlan, getCurrentUsage, + getPredictionEligibility, + purchaseCredits, + getCreditsBalance, + getUsageWithCredits, + getCreditsPackages, deleteOrganizationUser } diff --git a/packages/ui/src/views/account/index.jsx b/packages/ui/src/views/account/index.jsx index cd4a57679..50950dcd7 100644 --- a/packages/ui/src/views/account/index.jsx +++ b/packages/ui/src/views/account/index.jsx @@ -33,7 +33,7 @@ import SettingsSection from '@/ui-component/form/settings' import PricingDialog from '@/ui-component/subscription/PricingDialog' // Icons -import { IconAlertCircle, IconCreditCard, IconExternalLink, IconSparkles, IconX } from '@tabler/icons-react' +import { IconAlertCircle, IconCoins, IconCreditCard, IconExternalLink, IconSparkles, IconX } from '@tabler/icons-react' // API import accountApi from '@/api/account.api' @@ -88,6 +88,12 @@ const AccountSettings = () => { const [purchasedSeats, setPurchasedSeats] = useState(0) const [occupiedSeats, setOccupiedSeats] = useState(0) const [totalSeats, setTotalSeats] = useState(0) + const [creditsBalance, setCreditsBalance] = useState(null) + const [creditsPackages, setCreditsPackages] = useState([]) + const [usageWithCredits, setUsageWithCredits] = useState(null) + const [openCreditsDialog, setOpenCreditsDialog] = useState(false) + const [selectedPackage, setSelectedPackage] = useState(null) + const [isPurchasingCredits, setIsPurchasingCredits] = useState(false) const predictionsUsageInPercent = useMemo(() => { return usage ? calculatePercentage(usage.predictions?.usage, usage.predictions?.limit) : 0 @@ -106,6 +112,11 @@ const AccountSettings = () => { const getCustomerDefaultSourceApi = useApi(userApi.getCustomerDefaultSource) const updateAdditionalSeatsApi = useApi(userApi.updateAdditionalSeats) const getCurrentUsageApi = useApi(userApi.getCurrentUsage) + const getCreditsBalanceApi = useApi(userApi.getCreditsBalance) + const getCreditsPackagesApi = useApi(userApi.getCreditsPackages) + const getUsageWithCreditsApi = useApi(userApi.getUsageWithCredits) + const purchaseCreditsApi = useApi(userApi.purchaseCredits) + const getPredictionEligibilityApi = useApi(userApi.getPredictionEligibility) useEffect(() => { if (isCloud) { @@ -113,6 +124,10 @@ const AccountSettings = () => { getPricingPlansApi.request() getAdditionalSeatsQuantityApi.request(currentUser?.activeOrganizationSubscriptionId) getCurrentUsageApi.request() + getCreditsBalanceApi.request() + getCreditsPackagesApi.request() + getUsageWithCreditsApi.request() + getPredictionEligibilityApi.request() } // eslint-disable-next-line react-hooks/exhaustive-deps }, [isCloud]) @@ -140,13 +155,31 @@ const AccountSettings = () => { }, [getCurrentUsageApi.data]) useEffect(() => { - if (openRemoveSeatsDialog || openAddSeatsDialog) { + if (getCreditsBalanceApi.data) { + setCreditsBalance(getCreditsBalanceApi.data) + } + }, [getCreditsBalanceApi.data]) + + useEffect(() => { + if (getCreditsPackagesApi.data) { + setCreditsPackages(getCreditsPackagesApi.data) + } + }, [getCreditsPackagesApi.data]) + + useEffect(() => { + if (getUsageWithCreditsApi.data) { + setUsageWithCredits(getUsageWithCreditsApi.data) + } + }, [getUsageWithCreditsApi.data]) + + useEffect(() => { + if (openRemoveSeatsDialog || openAddSeatsDialog || openCreditsDialog) { setSeatsQuantity(0) getCustomerDefaultSourceApi.request(currentUser?.activeOrganizationCustomerId) } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [openRemoveSeatsDialog, openAddSeatsDialog]) + }, [openRemoveSeatsDialog, openAddSeatsDialog, openCreditsDialog]) useEffect(() => { if (getAdditionalSeatsProrationApi.data) { @@ -447,6 +480,61 @@ const AccountSettings = () => { } } + const handlePurchaseCredits = async (packageType) => { + try { + setIsPurchasingCredits(true) + + const response = await purchaseCreditsApi.request(packageType) + + if (response.data?.success) { + enqueueSnackbar({ + message: 'Credits purchased successfully!', + options: { + key: new Date().getTime() + Math.random(), + variant: 'success', + action: (key) => ( + + ) + } + }) + + // Refresh credits data + getCreditsBalanceApi.request() + getUsageWithCreditsApi.request() + setOpenCreditsDialog(false) + setSelectedPackage(null) + } + } catch (error) { + console.error('Error purchasing credits:', error) + enqueueSnackbar({ + message: `Failed to purchase credits: ${ + typeof error.response?.data === 'object' ? error.response.data.message : error.response?.data || error.message + }`, + options: { + key: new Date().getTime() + Math.random(), + variant: 'error', + persist: true, + action: (key) => ( + + ) + } + }) + } finally { + setIsPurchasingCredits(false) + } + } + + const handleCreditsDialogClose = () => { + if (!isPurchasingCredits) { + setOpenCreditsDialog(false) + setSelectedPackage(null) + } + } + // Calculate empty seats const emptySeats = Math.min(purchasedSeats, totalSeats - occupiedSeats) @@ -726,6 +814,78 @@ const AccountSettings = () => { + + + + + Available Credits: + + {getCreditsBalanceApi.loading ? ( + + ) : ( + creditsBalance?.balance || 0 + )} + + + {usageWithCredits && ( + + Credits Used This Month: + + {getUsageWithCreditsApi.loading ? ( + + ) : ( + usageWithCredits?.creditsUsed || 0 + )} + + + )} + + Purchase credits for predictions beyond your plan limits + + + + setOpenCreditsDialog(true)} + startIcon={} + sx={{ borderRadius: 2, height: 40 }} + > + Buy Credits + + + + @@ -1457,6 +1617,148 @@ const AccountSettings = () => { )} + + {/* Credits Purchase Dialog */} + + Purchase Credits + + + + Credits are used for predictions beyond your plan limits. Each prediction costs 1 credit. + + + {/* Current Credits Balance */} + + + Current Balance + + + {creditsBalance?.balance || 0} Credits + + + + {/* Payment Method Check */} + {getCustomerDefaultSourceApi.loading ? ( + + ) : getCustomerDefaultSourceApi.data?.invoice_settings?.default_payment_method ? ( + + Payment Method + + {getCustomerDefaultSourceApi.data.invoice_settings.default_payment_method.card && ( + <> + + + + {getCustomerDefaultSourceApi.data.invoice_settings.default_payment_method.card.brand} + + + ••••{' '} + {getCustomerDefaultSourceApi.data.invoice_settings.default_payment_method.card.last4} + + + (expires{' '} + { + getCustomerDefaultSourceApi.data.invoice_settings.default_payment_method.card + .exp_month + } + / + {getCustomerDefaultSourceApi.data.invoice_settings.default_payment_method.card.exp_year} + ) + + + + )} + + + ) : ( + + + + No payment method found + + + + )} + + {/* Credit Packages */} + {getCustomerDefaultSourceApi.data?.invoice_settings?.default_payment_method && ( + + Select Credit Package + {getCreditsPackagesApi.loading ? ( + + ) : ( + creditsPackages.map((pkg) => ( + setSelectedPackage(pkg)} + > + + + {pkg.credits} Credits + + ${(pkg.price / 100).toFixed(2)} USD + + + + ${(pkg.price / 100 / pkg.credits).toFixed(3)} per credit + + + + )) + )} + + )} + + + {getCustomerDefaultSourceApi.data?.invoice_settings?.default_payment_method && ( + + + + + )} + ) }