Improve trial upgrade conversion
This commit is contained in:
@@ -9,6 +9,7 @@ import {
|
||||
ChevronDown,
|
||||
ChevronLeft,
|
||||
ChevronRight,
|
||||
Crown,
|
||||
GraduationCap,
|
||||
Menu,
|
||||
MessageSquare,
|
||||
@@ -612,6 +613,8 @@ function PolyWeatherTerminal({
|
||||
selectedRow,
|
||||
setSelectedRow,
|
||||
toggleLocale,
|
||||
isTrialTerminalAccess,
|
||||
trialSubscriptionExpiresAt,
|
||||
userLocalTime,
|
||||
searchQuery,
|
||||
setSearchQuery,
|
||||
@@ -632,6 +635,8 @@ function PolyWeatherTerminal({
|
||||
selectedRow: ScanOpportunityRow | null;
|
||||
setSelectedRow: (row: ScanOpportunityRow) => void;
|
||||
toggleLocale: () => void;
|
||||
isTrialTerminalAccess: boolean;
|
||||
trialSubscriptionExpiresAt: string | null;
|
||||
userLocalTime: string;
|
||||
searchQuery: string;
|
||||
setSearchQuery: (val: string) => void;
|
||||
@@ -668,6 +673,17 @@ function PolyWeatherTerminal({
|
||||
const [onlineCount, setOnlineCount] = useState<number | null>(null);
|
||||
const [feedbackDraft, setFeedbackDraft] = useState<FeedbackDraft | null>(null);
|
||||
const [feedbackRefreshKey, setFeedbackRefreshKey] = useState(0);
|
||||
const trialExpiryMs = Date.parse(String(trialSubscriptionExpiresAt || ""));
|
||||
const trialHoursLeft = Number.isFinite(trialExpiryMs)
|
||||
? Math.max(0, Math.ceil((trialExpiryMs - Date.now()) / 3_600_000))
|
||||
: null;
|
||||
const trialUpgradeLabel = isEn
|
||||
? trialHoursLeft != null
|
||||
? `Trial ${trialHoursLeft}h left`
|
||||
: "Trial access"
|
||||
: trialHoursLeft != null
|
||||
? `试用剩 ${trialHoursLeft} 小时`
|
||||
: "试用中";
|
||||
const handleSelectNav = useCallback((key: string) => {
|
||||
setActiveNavKey(key);
|
||||
}, []);
|
||||
@@ -957,6 +973,17 @@ function PolyWeatherTerminal({
|
||||
<div className="hidden lg:block">
|
||||
<UpdateAnnouncementButton isEn={isEn} />
|
||||
</div>
|
||||
{isTrialTerminalAccess && (
|
||||
<Link
|
||||
href="/account?checkout=1"
|
||||
className="inline-flex h-7 items-center gap-2 rounded border border-amber-300 bg-amber-50 px-2.5 text-[11px] font-black text-amber-800 transition hover:bg-amber-100"
|
||||
title={isEn ? "Upgrade to Pro" : "升级 Pro"}
|
||||
>
|
||||
<Crown size={12} />
|
||||
<span className="hidden sm:inline">{trialUpgradeLabel}</span>
|
||||
<span>{isEn ? "Upgrade" : "升级"}</span>
|
||||
</Link>
|
||||
)}
|
||||
{onlineCount != null && (
|
||||
<div className="hidden items-center gap-1 text-[10px] font-medium text-slate-400 lg:flex">
|
||||
<Users size={12} />
|
||||
@@ -1630,6 +1657,10 @@ function ScanTerminalScreen() {
|
||||
},
|
||||
[cityFallbackRows, terminalData?.rows],
|
||||
);
|
||||
const isTrialTerminalAccess = Boolean(
|
||||
isPro &&
|
||||
String(proAccess.subscriptionPlanCode || "").toLowerCase().includes("signup_trial"),
|
||||
);
|
||||
|
||||
const fallbackFetchedRef = useRef(false);
|
||||
useEffect(() => {
|
||||
@@ -1743,6 +1774,10 @@ function ScanTerminalScreen() {
|
||||
selectedRow={selectedRow}
|
||||
setSelectedRow={handleSelectRow}
|
||||
toggleLocale={toggleLocale}
|
||||
isTrialTerminalAccess={isTrialTerminalAccess}
|
||||
trialSubscriptionExpiresAt={
|
||||
proAccess.subscriptionTotalExpiresAt || proAccess.subscriptionExpiresAt
|
||||
}
|
||||
userLocalTime={userLocalTime}
|
||||
searchQuery={searchQuery}
|
||||
setSearchQuery={setSearchQuery}
|
||||
|
||||
@@ -7,7 +7,7 @@ const ACCESS_TERM = {
|
||||
signInToContinue: { en: "Sign in to continue", zh: "请先登录" },
|
||||
proAccessRequired: { en: "Pro subscription required", zh: "需要开通 Pro" },
|
||||
month: { en: "/ 30 days", zh: "/ 30 天" },
|
||||
subscribeNow: { en: "View Pro plans", zh: "查看订阅方案" },
|
||||
subscribeNow: { en: "Subscribe & Activate", zh: "立即订阅并激活" },
|
||||
backToProduct: { en: "Back to product overview", zh: "返回产品介绍页" },
|
||||
} as const;
|
||||
|
||||
@@ -77,7 +77,7 @@ function SubscriptionGate({ isEn }: { isEn: boolean }) {
|
||||
</ul>
|
||||
|
||||
<Link
|
||||
href="/account"
|
||||
href="/account?checkout=1"
|
||||
className="flex w-full items-center justify-center gap-2 rounded-xl bg-blue-600 py-3.5 text-sm font-black text-white shadow-sm transition hover:bg-blue-700"
|
||||
>
|
||||
<CreditCard size={16} />
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
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 dashboardSource = fs.readFileSync(
|
||||
path.join(process.cwd(), "components", "dashboard", "ScanTerminalDashboard.tsx"),
|
||||
"utf8",
|
||||
);
|
||||
|
||||
assert(
|
||||
dashboardSource.includes("isTrialTerminalAccess") &&
|
||||
dashboardSource.includes("trialUpgradeLabel") &&
|
||||
dashboardSource.includes('href="/account?checkout=1"'),
|
||||
"terminal header must show a non-blocking trial countdown with a direct Pro upgrade entry",
|
||||
);
|
||||
|
||||
assert(
|
||||
dashboardSource.includes("subscriptionPlanCode") &&
|
||||
dashboardSource.includes("signup_trial"),
|
||||
"terminal trial upgrade nudge must be driven by the active trial subscription plan, not a generic subscribed state",
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user