Use Pro paywall for scan access
This commit is contained in:
@@ -14,7 +14,7 @@ const TELEGRAM_GROUP_URL = String(
|
|||||||
const SUBSCRIPTION_HELP_HREF = "/subscription-help";
|
const SUBSCRIPTION_HELP_HREF = "/subscription-help";
|
||||||
|
|
||||||
type ProFeaturePaywallProps = {
|
type ProFeaturePaywallProps = {
|
||||||
feature: "today" | "history" | "future" | "assistant";
|
feature: "today" | "history" | "future" | "assistant" | "scan";
|
||||||
onClose?: () => void;
|
onClose?: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import { MapCanvas } from "@/components/dashboard/MapCanvas";
|
|||||||
import { getWindowPhaseMeta } from "@/components/dashboard/OpportunityTable";
|
import { getWindowPhaseMeta } from "@/components/dashboard/OpportunityTable";
|
||||||
import { ScanKPIBar } from "@/components/dashboard/ScanKPIBar";
|
import { ScanKPIBar } from "@/components/dashboard/ScanKPIBar";
|
||||||
import { OpportunityTable } from "@/components/dashboard/OpportunityTable";
|
import { OpportunityTable } from "@/components/dashboard/OpportunityTable";
|
||||||
|
import { ProFeaturePaywall } from "@/components/dashboard/ProFeaturePaywall";
|
||||||
import {
|
import {
|
||||||
DashboardStoreProvider,
|
DashboardStoreProvider,
|
||||||
useDashboardStore,
|
useDashboardStore,
|
||||||
@@ -243,6 +244,7 @@ function ScanTerminalScreen() {
|
|||||||
const [selectedRowId, setSelectedRowId] = useState<string | null>(null);
|
const [selectedRowId, setSelectedRowId] = useState<string | null>(null);
|
||||||
const [activeView, setActiveView] = useState<ContentView>("map");
|
const [activeView, setActiveView] = useState<ContentView>("map");
|
||||||
const [mapSelectedCityName, setMapSelectedCityName] = useState<string | null>(null);
|
const [mapSelectedCityName, setMapSelectedCityName] = useState<string | null>(null);
|
||||||
|
const [showScanPaywall, setShowScanPaywall] = useState(false);
|
||||||
const [userLocalTime, setUserLocalTime] = useState("--");
|
const [userLocalTime, setUserLocalTime] = useState("--");
|
||||||
const [themeMode, setThemeMode] = useState<ThemeMode>("dark");
|
const [themeMode, setThemeMode] = useState<ThemeMode>("dark");
|
||||||
const deferredRows = useDeferredValue(terminalData?.rows || []);
|
const deferredRows = useDeferredValue(terminalData?.rows || []);
|
||||||
@@ -490,6 +492,10 @@ function ScanTerminalScreen() {
|
|||||||
void store.selectCity(row.city);
|
void store.selectCity(row.city);
|
||||||
}, [store]);
|
}, [store]);
|
||||||
|
|
||||||
|
const openScanPaywall = useCallback(() => {
|
||||||
|
setShowScanPaywall(true);
|
||||||
|
}, []);
|
||||||
|
|
||||||
const renderMainView = () => {
|
const renderMainView = () => {
|
||||||
if (resolvedView === "map") {
|
if (resolvedView === "map") {
|
||||||
return (
|
return (
|
||||||
@@ -635,12 +641,16 @@ function ScanTerminalScreen() {
|
|||||||
</button>
|
</button>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<Link href={accountHref} className="scan-primary-button">
|
<button
|
||||||
|
type="button"
|
||||||
|
className="scan-primary-button"
|
||||||
|
onClick={openScanPaywall}
|
||||||
|
>
|
||||||
<UserRound size={14} />
|
<UserRound size={14} />
|
||||||
{store.proAccess.authenticated
|
{store.proAccess.authenticated
|
||||||
? isEn ? "Upgrade Pro" : "升级 Pro"
|
? isEn ? "Upgrade Pro" : "升级 Pro"
|
||||||
: isEn ? "Sign in" : "登录"}
|
: isEn ? "Sign in" : "登录"}
|
||||||
</Link>
|
</button>
|
||||||
)}
|
)}
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@@ -692,10 +702,12 @@ function ScanTerminalScreen() {
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className={resolvedView === "list" ? "active" : ""}
|
className={resolvedView === "list" ? "active" : ""}
|
||||||
disabled={!isPro}
|
|
||||||
title={!isPro ? (isEn ? "Pro scan required" : "扫描需 Pro") : undefined}
|
title={!isPro ? (isEn ? "Pro scan required" : "扫描需 Pro") : undefined}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (!isPro) return;
|
if (!isPro) {
|
||||||
|
openScanPaywall();
|
||||||
|
return;
|
||||||
|
}
|
||||||
setActiveView("list");
|
setActiveView("list");
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -704,10 +716,12 @@ function ScanTerminalScreen() {
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className={resolvedView === "calendar" ? "active" : ""}
|
className={resolvedView === "calendar" ? "active" : ""}
|
||||||
disabled={!isPro}
|
|
||||||
title={!isPro ? (isEn ? "Pro scan required" : "扫描需 Pro") : undefined}
|
title={!isPro ? (isEn ? "Pro scan required" : "扫描需 Pro") : undefined}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (!isPro) return;
|
if (!isPro) {
|
||||||
|
openScanPaywall();
|
||||||
|
return;
|
||||||
|
}
|
||||||
setActiveView("calendar");
|
setActiveView("calendar");
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -749,6 +763,24 @@ function ScanTerminalScreen() {
|
|||||||
<CityDetailPanel variant="rail" />
|
<CityDetailPanel variant="rail" />
|
||||||
</div>
|
</div>
|
||||||
<FutureForecastModal />
|
<FutureForecastModal />
|
||||||
|
{showScanPaywall ? (
|
||||||
|
<div
|
||||||
|
className="modal-overlay"
|
||||||
|
role="dialog"
|
||||||
|
aria-modal="true"
|
||||||
|
aria-label={isEn ? "Unlock market scan" : "解锁市场扫描"}
|
||||||
|
onClick={(event) => {
|
||||||
|
if (event.target === event.currentTarget) {
|
||||||
|
setShowScanPaywall(false);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ProFeaturePaywall
|
||||||
|
feature="scan"
|
||||||
|
onClose={() => setShowScanPaywall(false)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,11 +56,13 @@ type UnlockProOverlayProps = {
|
|||||||
|
|
||||||
const FEATURES = {
|
const FEATURES = {
|
||||||
"zh-CN": [
|
"zh-CN": [
|
||||||
|
"市场扫描台 + V4-Flash 深度复核",
|
||||||
"今日日内机场报文规则分析(含高温时段)",
|
"今日日内机场报文规则分析(含高温时段)",
|
||||||
"历史对账 + 未来日期分析",
|
"历史对账 + 未来日期分析",
|
||||||
"全平台智能气象推送",
|
"全平台智能气象推送",
|
||||||
],
|
],
|
||||||
"en-US": [
|
"en-US": [
|
||||||
|
"Market Scan Terminal + V4-Flash review",
|
||||||
"Intraday METAR rule-based analysis with peak-time window",
|
"Intraday METAR rule-based analysis with peak-time window",
|
||||||
"Historical reconciliation + future-date analysis",
|
"Historical reconciliation + future-date analysis",
|
||||||
"Cross-platform alerts",
|
"Cross-platform alerts",
|
||||||
|
|||||||
Reference in New Issue
Block a user