Improve trial upgrade conversion
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useRouter, useSearchParams } from "next/navigation";
|
||||
import type { User } from "@supabase/supabase-js";
|
||||
import {
|
||||
User as UserIcon,
|
||||
@@ -68,6 +68,7 @@ import { useAccountPayment } from "./useAccountPayment";
|
||||
|
||||
export function AccountCenter() {
|
||||
const router = useRouter();
|
||||
const searchParams = useSearchParams();
|
||||
const { locale } = useI18n();
|
||||
const isEn = locale === "en-US";
|
||||
const copy = useMemo(() => createAccountCopy(isEn), [isEn]);
|
||||
@@ -401,10 +402,11 @@ export function AccountCenter() {
|
||||
!isSubscribed && expiryInfo && expiryInfo.expired,
|
||||
);
|
||||
const paymentFeatureReady = paymentReadyForRecovery;
|
||||
const canTrialUpgrade = Boolean(isSubscribed && isTrialSubscription);
|
||||
const canOpenCheckoutOverlay = Boolean(
|
||||
paymentFeatureReady &&
|
||||
!isSubscriptionUnknown &&
|
||||
(!isSubscribed || showExpiringSoon || showExpiredReminder),
|
||||
(canTrialUpgrade || !isSubscribed || showExpiringSoon || showExpiredReminder),
|
||||
);
|
||||
const subscriptionStatusTitle = showExpiredReminder
|
||||
? copy.proExpiredTitle
|
||||
@@ -472,6 +474,13 @@ export function AccountCenter() {
|
||||
showOverlay,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
if (showOverlay || !canOpenCheckoutOverlay) return;
|
||||
if (searchParams.get("checkout") === "1") {
|
||||
setShowOverlay(true);
|
||||
}
|
||||
}, [canOpenCheckoutOverlay, searchParams, showOverlay]);
|
||||
|
||||
// ── Referral points display ────────────────────────────
|
||||
const referralRewardPointsRaw = Number(referral?.reward_points ?? 3500);
|
||||
const referralRewardPoints = Number.isFinite(referralRewardPointsRaw)
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
|
||||
function assert(condition: unknown, message: string) {
|
||||
if (!condition) throw new Error(message);
|
||||
}
|
||||
|
||||
export function runTests() {
|
||||
const projectRoot = process.cwd();
|
||||
const accountDir = path.join(projectRoot, "components", "account");
|
||||
const accountCenter = fs.readFileSync(path.join(accountDir, "AccountCenter.tsx"), "utf8");
|
||||
const paymentFlow = fs.readFileSync(path.join(accountDir, "usePaymentFlow.ts"), "utf8");
|
||||
const accountPayment = fs.readFileSync(path.join(accountDir, "useAccountPayment.ts"), "utf8");
|
||||
const productAccess = fs.readFileSync(
|
||||
path.join(projectRoot, "components", "dashboard", "scan-terminal", "ProductAccessRequired.tsx"),
|
||||
"utf8",
|
||||
);
|
||||
|
||||
assert(
|
||||
accountCenter.includes("canTrialUpgrade") &&
|
||||
accountCenter.includes("isTrialSubscription") &&
|
||||
accountCenter.includes("canTrialUpgrade || !isSubscribed"),
|
||||
"trial subscribers must be allowed to open the Pro checkout before expiry",
|
||||
);
|
||||
|
||||
assert(
|
||||
accountCenter.includes("useSearchParams") &&
|
||||
accountCenter.includes('searchParams.get("checkout") === "1"') &&
|
||||
accountCenter.includes("setShowOverlay(true)"),
|
||||
"account page must support /account?checkout=1 for direct upgrade entry",
|
||||
);
|
||||
|
||||
assert(
|
||||
productAccess.includes('href="/account?checkout=1"') &&
|
||||
productAccess.includes("Subscribe & Activate") &&
|
||||
productAccess.includes("立即订阅并激活"),
|
||||
"expired terminal gate must send users directly to the checkout entry, not a generic account page",
|
||||
);
|
||||
|
||||
assert(
|
||||
accountPayment.includes("authUserId,") &&
|
||||
paymentFlow.includes("authUserId: string") &&
|
||||
paymentFlow.includes("user_id: authUserId || null"),
|
||||
"payment_start and payment_success analytics must carry the authenticated user id for trial-to-paid attribution",
|
||||
);
|
||||
}
|
||||
@@ -487,6 +487,7 @@ export function useAccountPayment(params: UseAccountPaymentParams) {
|
||||
isEn,
|
||||
copy,
|
||||
backend,
|
||||
authUserId,
|
||||
paymentConfig,
|
||||
setPaymentConfig,
|
||||
selectedPlanCode,
|
||||
|
||||
@@ -40,6 +40,7 @@ export interface UsePaymentFlowParams {
|
||||
isEn: boolean;
|
||||
copy: Record<string, string>;
|
||||
backend: AuthMeResponse | null;
|
||||
authUserId: string;
|
||||
|
||||
// Shared payment config state
|
||||
paymentConfig: PaymentConfig | null;
|
||||
@@ -122,6 +123,7 @@ export function usePaymentFlow(params: UsePaymentFlowParams) {
|
||||
isEn,
|
||||
copy,
|
||||
backend,
|
||||
authUserId,
|
||||
paymentConfig,
|
||||
setPaymentConfig,
|
||||
selectedPlanCode,
|
||||
@@ -528,6 +530,7 @@ export function usePaymentFlow(params: UsePaymentFlowParams) {
|
||||
if (!intentId || !txPayload?.to || !txPayload?.data) throw new Error(copy.intentPayloadInvalid);
|
||||
trackPaymentStart({
|
||||
entry: "account_center",
|
||||
user_id: authUserId || null,
|
||||
plan_code: selectedPlan?.plan_code || "pro_monthly",
|
||||
intent_id: intentId,
|
||||
use_points: billing.canRedeem && usePoints,
|
||||
@@ -634,6 +637,7 @@ export function usePaymentFlow(params: UsePaymentFlowParams) {
|
||||
setPaymentInfo(copy.paymentConfirmed.replace("{txHash}", shortAddress(txHashNorm)));
|
||||
trackPaymentSuccess({
|
||||
entry: "account_center",
|
||||
user_id: authUserId || null,
|
||||
plan_code: selectedPlan?.plan_code || "pro_monthly",
|
||||
intent_id: intentId,
|
||||
tx_hash: txHashNorm,
|
||||
@@ -732,6 +736,7 @@ export function usePaymentFlow(params: UsePaymentFlowParams) {
|
||||
);
|
||||
trackPaymentStart({
|
||||
entry: "account_center_manual_transfer",
|
||||
user_id: authUserId || null,
|
||||
plan_code: selectedPlan?.plan_code || "pro_monthly",
|
||||
intent_id: intentId,
|
||||
payment_mode: "direct",
|
||||
@@ -803,6 +808,7 @@ export function usePaymentFlow(params: UsePaymentFlowParams) {
|
||||
setTxValidation({ loading: false, checked: false });
|
||||
trackPaymentSuccess({
|
||||
entry: "account_center_manual_transfer",
|
||||
user_id: authUserId || null,
|
||||
plan_code: selectedPlan?.plan_code || "pro_monthly",
|
||||
intent_id: intentIdVal,
|
||||
tx_hash: txHashNorm,
|
||||
|
||||
Reference in New Issue
Block a user