From 58ac7b9cc5145955f57738a40a33536124c51776 Mon Sep 17 00:00:00 2001 From: Usman Baig Date: Thu, 26 Mar 2026 21:30:17 +0100 Subject: [PATCH] feat: pricing and welcome CTAs now redirect to /checkout page --- app/welcome/page.tsx | 98 ++--------------------- components/PricingSection.tsx | 141 ++-------------------------------- 2 files changed, 13 insertions(+), 226 deletions(-) diff --git a/app/welcome/page.tsx b/app/welcome/page.tsx index e30ee61..5739f93 100644 --- a/app/welcome/page.tsx +++ b/app/welcome/page.tsx @@ -16,7 +16,6 @@ import { type Organization, type OrganizationMember, } from '@/lib/api/organization' -import { createCheckoutSession } from '@/lib/api/billing' import { createSite, type Site } from '@/lib/api/sites' import { setSessionAction } from '@/app/actions/auth' import { useAuth } from '@/lib/auth/context' @@ -88,7 +87,6 @@ function WelcomeContent() { const [orgLoading, setOrgLoading] = useState(false) const [orgError, setOrgError] = useState('') - const [planLoading, setPlanLoading] = useState(false) const [planError, setPlanError] = useState('') const [siteName, setSiteName] = useState('') @@ -98,14 +96,9 @@ function WelcomeContent() { const [createdSite, setCreatedSite] = useState(null) const [showVerificationModal, setShowVerificationModal] = useState(false) - const [redirectingCheckout, setRedirectingCheckout] = useState(false) const [hadPendingCheckout, setHadPendingCheckout] = useState(null) const [dismissedPendingCheckout, setDismissedPendingCheckout] = useState(false) - const [welcomeCountry, setWelcomeCountry] = useState('') - const [welcomeVatId, setWelcomeVatId] = useState('') - const [showBillingFields, setShowBillingFields] = useState(false) - const [organizations, setOrganizations] = useState(null) const [orgsLoading, setOrgsLoading] = useState(false) const [switchingOrgId, setSwitchingOrgId] = useState(null) @@ -216,36 +209,14 @@ function WelcomeContent() { return } - // Show billing fields first if country not yet selected - if (!welcomeCountry) { - setShowBillingFields(true) - return - } - - setPlanLoading(true) - setPlanError('') + trackWelcomePlanContinue() try { - trackWelcomePlanContinue() - const intent = JSON.parse(raw) - const { url } = await createCheckoutSession({ - plan_id: intent.planId, - interval: intent.interval || 'month', - limit: intent.limit ?? 100000, - country: welcomeCountry, - vat_id: welcomeVatId || undefined, - }) + const { planId, interval, limit } = JSON.parse(raw) localStorage.removeItem('pulse_pending_checkout') - if (url) { - setRedirectingCheckout(true) - window.location.href = url - return - } - throw new Error('No checkout URL returned') - } catch (err: unknown) { - setPlanError(getAuthErrorMessage(err) || (err as Error)?.message || 'Failed to start checkout') + router.push(`/checkout?plan=${planId}&interval=${interval || 'month'}&limit=${limit ?? 100000}`) + } catch { + setPlanError('Failed to parse checkout data') localStorage.removeItem('pulse_pending_checkout') - } finally { - setPlanLoading(false) } } @@ -333,15 +304,6 @@ function WelcomeContent() { return } - if (redirectingCheckout || (planLoading && step === 3)) { - return ( - - ) - } - const cardClass = 'bg-white dark:bg-neutral-900 border border-neutral-200 dark:border-neutral-800 rounded-2xl shadow-sm p-6 max-w-lg mx-auto' @@ -581,52 +543,6 @@ function WelcomeContent() { {planError && (

{planError}

)} - {showBillingFields && showPendingCheckoutInStep3 && ( -
-
- - -
-
- - ) => setWelcomeVatId(e.target.value)} - placeholder="e.g. BE0123456789" - /> -
-
- )}
{showPendingCheckoutInStep3 ? ( <> @@ -634,15 +550,13 @@ function WelcomeContent() { variant="primary" className="w-full sm:w-auto" onClick={handlePlanContinue} - disabled={planLoading || (showBillingFields && !welcomeCountry)} > - {showBillingFields ? 'Continue to payment' : 'Continue to checkout'} + Continue to checkout diff --git a/components/PricingSection.tsx b/components/PricingSection.tsx index 656b036..e6a9566 100644 --- a/components/PricingSection.tsx +++ b/components/PricingSection.tsx @@ -2,14 +2,12 @@ import { useState, useEffect } from 'react' import { logger } from '@/lib/utils/logger' -import { useSearchParams } from 'next/navigation' +import { useSearchParams, useRouter } from 'next/navigation' import { motion } from 'framer-motion' import { Button, CheckCircleIcon } from '@ciphera-net/ui' import { useAuth } from '@/lib/auth/context' import { initiateOAuthFlow } from '@/lib/api/oauth' import { toast } from '@ciphera-net/ui' -import { COUNTRY_OPTIONS } from '@/lib/countries' -import { createCheckoutSession } from '@/lib/api/billing' // 1. Define Plans with IDs and Site Limits const PLANS = [ @@ -103,20 +101,12 @@ const TRAFFIC_TIERS = [ }, ] -const PRICING_COUNTRY_OPTIONS = [ - ...COUNTRY_OPTIONS.map((c) => ({ code: c.value, label: c.label })), - { code: 'OTHER', label: 'Other' }, -] - export default function PricingSection() { const searchParams = useSearchParams() + const router = useRouter() const [isYearly, setIsYearly] = useState(false) const [sliderIndex, setSliderIndex] = useState(2) // Default to 100k (index 2) const [loadingPlan, setLoadingPlan] = useState(null) - const [checkoutCountry, setCheckoutCountry] = useState('') - const [checkoutVatId, setCheckoutVatId] = useState('') - const [showCheckoutForm, setShowCheckoutForm] = useState(false) - const [pendingCheckout, setPendingCheckout] = useState<{ planId: string; interval: string; limit: number } | null>(null) const { user } = useAuth() // * Show toast when redirected from Mollie Checkout with canceled=true @@ -176,7 +166,7 @@ export default function PricingSection() { } } - const handleSubscribe = async (planId: string, options?: { interval?: string, limit?: number }) => { + const handleSubscribe = (planId: string, options?: { interval?: string, limit?: number }) => { // 1. If not logged in, redirect to login/signup if (!user) { const intent = { @@ -191,49 +181,10 @@ export default function PricingSection() { return } - // 2. Show checkout form to collect country + optional VAT - const interval = options?.interval || (isYearly ? 'year' : 'month') - const limit = options?.limit || currentTraffic.value - setPendingCheckout({ planId, interval, limit }) - setCheckoutCountry('') - setCheckoutVatId('') - setShowCheckoutForm(true) - } - - const handleCheckoutSubmit = async (e: React.FormEvent) => { - e.preventDefault() - if (!pendingCheckout || !checkoutCountry) return - - try { - setLoadingPlan(pendingCheckout.planId) - - const { url } = await createCheckoutSession({ - plan_id: pendingCheckout.planId, - interval: pendingCheckout.interval, - limit: pendingCheckout.limit, - country: checkoutCountry, - vat_id: checkoutVatId || undefined, - }) - - if (url) { - window.location.href = url - } else { - throw new Error('No checkout URL returned') - } - } catch (error: unknown) { - logger.error('Checkout error:', error) - toast.error('Failed to start checkout — please try again') - } finally { - setLoadingPlan(null) - } - } - - const handleCheckoutCancel = () => { - setShowCheckoutForm(false) - setPendingCheckout(null) - setCheckoutCountry('') - setCheckoutVatId('') - setLoadingPlan(null) + // 2. Navigate to embedded checkout page + const selectedInterval = options?.interval || (isYearly ? 'year' : 'month') + const selectedLimit = options?.limit || currentTraffic.value + router.push(`/checkout?plan=${planId}&interval=${selectedInterval}&limit=${selectedLimit}`) } return ( @@ -463,84 +414,6 @@ export default function PricingSection() {
- {/* Checkout Country / VAT Modal */} - {showCheckoutForm && ( -
-
- -

- Billing details -

-

- Select your country to calculate the correct tax rate. -

- -
- {/* Country */} -
- - -
- - {/* VAT ID */} -
- - setCheckoutVatId(e.target.value)} - placeholder="e.g. BE0123456789" - className="w-full rounded-lg border border-neutral-700 bg-neutral-800 px-3 py-2.5 text-sm text-white placeholder-neutral-500 focus:border-brand-orange focus:outline-none focus:ring-1 focus:ring-brand-orange transition-colors" - /> -
- - {/* Actions */} -
- - -
-
-
-
- )} ) }