Improve trial upgrade conversion

This commit is contained in:
2569718930@qq.com
2026-06-09 18:17:19 +08:00
parent c98ff34c1e
commit 7b965ac60d
11 changed files with 221 additions and 8 deletions
@@ -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",
);
}