fix(web): fire checkout_started when the payment step is reached (all plans)

checkout_started only fired for the "full" plan (plus a mostly-unreachable
sticky-CTA branch), so non-full-plan users reached payment without it ever
firing — PostHog showed fewer checkout_started (1 person) than the downstream
payment_initiated (3), which is impossible in a clean funnel.

Move it to a step-driven effect that fires once when the payment step is
reached, via any path (plan card, sticky CTA, or a prefilled pending plan),
re-arming if the user returns to plan selection. Fixes the Monetization and
Ödeme funnels at the checkout step and keeps plan -> checkout meaningful
(brand-selection drop-off now shows up instead of a trivial 100% step).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-24 23:34:43 +03:00
parent 195b3204c3
commit 70ee3d637f

View File

@@ -193,6 +193,7 @@ export function SubscriptionPage() {
const [onboardingPhase, setOnboardingPhase] = useState<"provisioning" | "completed" | null>(null);
const [animationEnded, setAnimationEnded] = useState(false);
const hasFiredRef = useRef(false);
const checkoutStartedRef = useRef(false);
const [isDark] = useState(() => {
const theme = getUserSettings().theme ?? "dark";
return theme === "system"
@@ -332,6 +333,22 @@ export function SubscriptionPage() {
return () => clearTimeout(timer);
}, [onboardingPhase]);
// checkout_started fires once when the user reaches the payment step, via any
// path (plan card, sticky CTA, or a prefilled pending plan). It previously
// fired only for the "full" plan, under-counting vs the downstream
// payment_initiated; gating on the payment step fixes that and keeps the
// plan -> checkout funnel step meaningful (brand-selection drop-off shows up).
useEffect(() => {
if (step === "plan") {
checkoutStartedRef.current = false; // re-arm if the user restarts checkout
return;
}
if (step === "payment" && selectedPlanKey && !checkoutStartedRef.current) {
checkoutStartedRef.current = true;
capture("checkout_started", { plan: selectedPlanKey, period: billingPeriod });
}
}, [step, selectedPlanKey, billingPeriod]);
useEffect(() => {
if (search.stripe === "success" && !stripeResultRef.current) {
stripeResultRef.current = true;
@@ -423,9 +440,8 @@ export function SubscriptionPage() {
capture("plan_selected", { plan: planKey });
setSelectedPlanKey(planKey);
setSelectedBrandIds([]);
if (planKey === "full") {
capture("checkout_started", { plan: planKey, period: billingPeriod });
}
// checkout_started now fires from the step-driven effect when the payment
// step is reached (covers every plan + advance path), so we don't fire here.
setStep(planKey === "full" ? "payment" : "brands");
}
@@ -764,7 +780,6 @@ export function SubscriptionPage() {
onAdvance={() => {
if (step === "plan") {
if (!selectedPlanKey) return;
capture("checkout_started", { plan: selectedPlanKey, period: billingPeriod });
setStep(isFullPlan ? "payment" : "brands");
} else if (step === "brands") {
handleAdvanceFromBrands();