From 2f037bd78ebd5888a5700fc0c87fe5a39d6f55c9 Mon Sep 17 00:00:00 2001 From: Frikky Date: Sat, 5 Apr 2025 01:27:40 +0200 Subject: [PATCH] Minor billing page fixes --- frontend/src/components/Billing.jsx | 2 + frontend/src/components/LicencePopup.jsx | 226 ++++++++++++++++++++--- frontend/src/components/Navbar.jsx | 2 + 3 files changed, 209 insertions(+), 21 deletions(-) diff --git a/frontend/src/components/Billing.jsx b/frontend/src/components/Billing.jsx index ef09b6f4..8663fdc2 100644 --- a/frontend/src/components/Billing.jsx +++ b/frontend/src/components/Billing.jsx @@ -2059,6 +2059,7 @@ const Billing = memo((props) => { userdata={userdata} stripeKey={stripeKey} isScale={isScale} + features={selectedOrganization?.sync_features} {...props} /> : !isCloud ? @@ -2093,6 +2094,7 @@ const Billing = memo((props) => { billingInfo={billingInfo} isCloud={isCloud} userdata={userdata} + features={selectedOrganization?.sync_features} stripeKey={stripeKey} isScale={isScale} /> diff --git a/frontend/src/components/LicencePopup.jsx b/frontend/src/components/LicencePopup.jsx index 6bf296d1..0e354966 100644 --- a/frontend/src/components/LicencePopup.jsx +++ b/frontend/src/components/LicencePopup.jsx @@ -48,7 +48,7 @@ import { handlePayasyougo } from "../views/HandlePaymentNew.jsx" import Billing from "./Billing.jsx"; const LicencePopup = (props) => { - const { globalUrl, userdata, serverside, billingInfo, stripeKey, setModalOpen, isScale, isLoggedIn, isMobile, selectedOrganization, isCloud } = props; + const { globalUrl, userdata, serverside, billingInfo, stripeKey, setModalOpen, isScale, isLoggedIn, isMobile, selectedOrganization, isCloud, features, licensePopup = false } = props; //const alert = useAlert(); let navigate = useNavigate(); const [selectedDealModalOpen, setSelectedDealModalOpen] = React.useState(false); @@ -148,21 +148,175 @@ const LicencePopup = (props) => { height: "100%" } + const userInScalePlan = userdata?.app_execution_limit > 10000 + const appRuns = userInScalePlan ? (userdata?.app_execution_limit / 1000) + "K App Runs" : userdata?.app_execution_limit === 10000 ? "10,000 App Runs" : "2,000 App Runs" + + // Add this function to format the limit value + const formatLimit = (limit) => { + if (limit === null || limit === undefined || limit === 0) return "Unlimited"; + if (typeof limit === "string" && limit.toLowerCase() === "unlimited") return "Unlimited"; + if (typeof limit === "number") return limit.toLocaleString(); + return limit.toString(); + }; + + // Add this function to format the feature text with proper unlimited handling + const formatFeatureText = (feature, limit) => { + if (!feature) return ""; + + // Dynamic features that use limits + const featureMapping = { + app_executions: (limit) => { + const formattedLimit = formatLimit(limit); + return `Includes ${formattedLimit} App Executions per month`; + }, + multi_env: (limit) => { + const formattedLimit = formatLimit(limit); + return formattedLimit === "Unlimited" + ? "Unlimited Environments" + : `${formattedLimit} Environment${parseInt(formattedLimit) > 1 ? 's' : ''}`; + }, + multi_tenant: (limit) => { + const formattedLimit = formatLimit(limit); + return formattedLimit === "Unlimited" + ? "Unlimited Tenants" + : `${formattedLimit} Tenant${parseInt(formattedLimit) > 1 ? 's' : ''}`; + }, + multi_region: (limit) => { + const formattedLimit = formatLimit(limit); + return formattedLimit === "Unlimited" + ? "Unlimited Regions" + : `${formattedLimit} Region${parseInt(formattedLimit) > 1 ? 's' : ''}`; + }, + webhook: (limit) => { + const formattedLimit = formatLimit(limit); + return formattedLimit === "Unlimited" + ? "Unlimited Webhooks" + : `${formattedLimit} Webhook${parseInt(formattedLimit) > 1 ? 's' : ''}`; + }, + schedules: (limit) => { + const formattedLimit = formatLimit(limit); + return formattedLimit === "Unlimited" + ? "Unlimited Schedules" + : `${formattedLimit} Schedule${parseInt(formattedLimit) > 1 ? 's' : ''}`; + }, + user_input: (limit) => { + const formattedLimit = formatLimit(limit); + return formattedLimit === "Unlimited" + ? "Unlimited User Inputs" + : `${formattedLimit} User Input${parseInt(formattedLimit) > 1 ? 's' : ''}`; + }, + send_mail: (limit) => { + const formattedLimit = formatLimit(limit); + return formattedLimit === "Unlimited" + ? "Unlimited Emails per month" + : `${formattedLimit} Email${parseInt(formattedLimit) > 1 ? 's' : ''} per month`; + }, + send_sms: (limit) => { + const formattedLimit = formatLimit(limit); + return formattedLimit === "Unlimited" + ? "Unlimited SMS per month" + : `${formattedLimit} SMS${parseInt(formattedLimit) > 1 ? 's' : ''} per month`; + }, + email_trigger: (limit) => { + const formattedLimit = formatLimit(limit); + return formattedLimit === "Unlimited" + ? "Unlimited Email Triggers" + : `${formattedLimit} Email Trigger${parseInt(formattedLimit) > 1 ? 's' : ''}`; + }, + notifications: (limit) => { + const formattedLimit = formatLimit(limit); + return formattedLimit === "Unlimited" + ? "Unlimited Notifications" + : `${formattedLimit} Notification${parseInt(formattedLimit) > 1 ? 's' : ''}`; + }, + workflows: (limit) => { + const formattedLimit = formatLimit(limit); + return formattedLimit === "Unlimited" + ? "Unlimited Workflows" + : `${formattedLimit} Workflow${parseInt(formattedLimit) > 1 ? 's' : ''}`; + }, + autocomplete: (limit) => { + const formattedLimit = formatLimit(limit); + return formattedLimit === "Unlimited" + ? "Unlimited Autocomplete" + : `${formattedLimit} Autocomplete${parseInt(formattedLimit) > 1 ? 's' : ''}`; + }, + workflow_executions: (limit) => { + const formattedLimit = formatLimit(limit); + return formattedLimit === "Unlimited" + ? "Unlimited Workflow Executions" + : `${formattedLimit} Workflow Execution${parseInt(formattedLimit) > 1 ? 's' : ''}`; + }, + authentication: (limit) => { + const formattedLimit = formatLimit(limit); + return formattedLimit === "Unlimited" + ? "Unlimited Authentication" + : `${formattedLimit} Authentication${parseInt(formattedLimit) > 1 ? 's' : ''}`; + }, + shuffle_gpt: (limit) => { + const formattedLimit = formatLimit(limit); + return formattedLimit === "Unlimited" + ? "Unlimited Shuffle GPT" + : `${formattedLimit} Shuffle GPT${parseInt(formattedLimit) > 1 ? 's' : ''}`; + }, + }; + + try { + // Check if we have a mapping for this feature + const formatter = featureMapping[feature]; + if (formatter) { + return formatter(limit); + } + + // Default format for unknown features + const formattedLimit = formatLimit(limit); + return `${feature}: ${formattedLimit}`; + } catch (error) { + console.warn(`Error formatting feature ${feature}:`, error); + return `${feature}: ${formatLimit(limit)}`; + } + }; + + console.log("selectedOrganization: ", selectedOrganization) + // Update the subscription features section billingInfo.subscription = { "active": true, - "name": userdata?.app_execution_limit === 10000 ? "10,000 App Runs" : "2,000 App Runs", - "price": "Free", - "currency": "Free", + "name": appRuns, + "price": userInScalePlan ? "" : "Free", + "currency": userInScalePlan ? "" : "Free", "currency_text": "", "interval": "", - "description": "Pay as you go", - "features": [ - "Community Support", - `Includes ${userdata?.app_execution_limit === 10000 ? "10,000" : "2,000"} app run/month for free. `, - "Get all 2500+ Apps and 10 Workflows", - "Invite up to 5 users" + "description": "", + "features": userInScalePlan ? [ + // Add static features first + ...(userInScalePlan ? ["Standard Email Support"] : []), + + // Then add dynamic features from the database + ...Object.entries(features || {}) + .filter(([_, featureData]) => { + return featureData && + typeof featureData === 'object' && + featureData.active === true; + }) + .map(([featureName, featureData]) => { + try { + return formatFeatureText(featureName, featureData?.limit); + } catch (error) { + console.warn(`Error processing feature ${featureName}:`, error); + return ""; + } + }) + .filter(feature => + feature.length > 0 && + !feature.toLowerCase().includes('unlimited') // Add this filter to remove "unlimited" features + ) + ] : [ + userInScalePlan ? "Standard Email Support" : "Community Support", + userInScalePlan ? `Includes ${appRuns}. ` : `Includes ${appRuns} for free. `, + userInScalePlan ? "Multi-Tenant & Multi-Region" : "Get all 2500+ Apps and 10 Workflows", + userInScalePlan ? "All features included in the Scale plan" : "Invite up to 5 users" ], - "limit": userdata?.app_execution_limit === 10000 ? 10000 : 2000, + "limit": userInScalePlan ? userdata?.app_execution_limit : 10000, } const sendSignatureRequest = (subscription) => { @@ -196,6 +350,15 @@ const LicencePopup = (props) => { }) } + // Create a function to remove duplicates and merge features + const mergeUniqueFeatures = (existingFeatures, newFeatures) => { + // Convert arrays to Sets to remove duplicates + const uniqueFeatures = new Set([ + ...(existingFeatures || []), + ...(newFeatures || []) + ]); + return Array.from(uniqueFeatures); + }; const SubscriptionObject = (props) => { const { globalUrl, index, userdata, serverside, billingInfo, stripeKey, selectedOrganization, handleGetOrg, subscription, highlight, } = props; @@ -204,7 +367,7 @@ const LicencePopup = (props) => { const [tosChecked, setTosChecked] = React.useState(subscription.eula_signed) const [hovered, setHovered] = React.useState(false) const [newBillingEmail, setNewBillingEmail] = useState(''); - var top_text = "Starter Plan" + var top_text = userInScalePlan ? "Scale Plan" : "Starter Plan" // if (subscription.limit === undefined && subscription.level === undefined || subscription.level === null || subscription.level === 0) { // subscription.name = "Enterprise" // subscription.currency_text = "$" @@ -317,12 +480,30 @@ const LicencePopup = (props) => { }); } + console.log("OrgSyncFeatures: ", selectedOrganization?.sync_features) + + const extraFeatures = Object.entries(features || {}) + .filter(([_, featureData]) => { + return featureData && + typeof featureData === 'object' && + featureData.active === true; + }) + .map(([featureName, featureData]) => { + return formatFeatureText(featureName, featureData?.limit); + }) + .filter(feature => + feature.length > 0 && + !feature.toLowerCase().includes('unlimited') // Add this filter to remove "unlimited" features + ) + + subscription.features = mergeUniqueFeatures(subscription.features, extraFeatures); + return ( -
+
setHovered(true)} @@ -681,7 +862,7 @@ const LicencePopup = (props) => { }} onClick={() => { console.log("Subscription: ", subscription.name) - if(!subscription.name.includes("App Run Units")) { + if(!subscription.name.includes("App Run Units") && !userInScalePlan) { window.open("https://discord.gg/B2CBzUm", "_blank") } else { window.open("mailto:support@shuffler.io", "_blank") @@ -702,7 +883,7 @@ const LicencePopup = (props) => { fontSize: 16, position: "relative", // Required for positioning tooltip }} - title="Click to book a call" + title="Get more app runs" onClick={() => { if (isCloud) { ReactGA.event({ @@ -710,7 +891,7 @@ const LicencePopup = (props) => { action: "bookcall_upgread_popup", label: "", })}; - window.open("https://drift.me/frikky/meeting", "_blank"); + navigate("/contact?category=contact") // if (isLoggedIn) { // isLoggedInHandler() // } else { @@ -719,7 +900,7 @@ const LicencePopup = (props) => { }} > - Book a call + Get more app runs { console.log("Selected Organization: ", selectedOrganization.subscriptions) return (
- - + + {selectedOrganization.subscriptions === undefined || selectedOrganization.subscriptions === null || selectedOrganization.subscriptions.length === 0 ? { }) : null} */} - + { + licensePopup && + ( {errorMessage.length > 0 ? Error: {errorMessage} : null} { - + ) + }
) diff --git a/frontend/src/components/Navbar.jsx b/frontend/src/components/Navbar.jsx index fa038a51..fb36859a 100644 --- a/frontend/src/components/Navbar.jsx +++ b/frontend/src/components/Navbar.jsx @@ -834,10 +834,12 @@ const Navbar = (props) => { globalUrl={globalUrl} selectedOrganization={selectedOrganization} billingInfo={billingInfo} + features={selectedOrganization?.sync_features} isCloud={isCloud} userdata={userdata} stripeKey={stripeKey} setModalOpen={setPricingModalOpen} + licensePopup={true} {...props} />