Skip to content

Commit c28c669

Browse files
authored
feat(web): add Go campaign entries and route upgrades to Pricing (#7122)
* feat(web): add Go campaign entries and route upgrades to pricing * fix(web): localize Go campaign copy Generated-By: looper 0.11.8 (runner=fixer, agent=codex) * feat(web): match Go campaign demo UI * test(web): align Go campaign source contract * fix(web): align Go modal model icons * fix(web): preserve Z.ai mark in Go modal list * fix(web): localize and fit Go campaign modal Generated-By: looper 0.11.8 (runner=fixer, agent=codex) * fix(web): clamp Go campaign modal height
1 parent 228113f commit c28c669

27 files changed

Lines changed: 1217 additions & 408 deletions
223 KB
Loading
11.5 KB
Loading

apps/web/src/campaigns/go-plan-content.ts

Lines changed: 311 additions & 0 deletions
Large diffs are not rendered by default.

apps/web/src/campaigns/go-plan.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import type { DeepSeekV4FlashCampaignAudience } from './deepseek-v4-flash';
2+
3+
export const GO_PLAN_CAMPAIGN = {
4+
id: 'go-plan-launch-2026',
5+
window: {
6+
startAt: '2026-08-20T20:00:00+08:00',
7+
endAtExclusive: '2026-09-03T20:00:00+08:00',
8+
},
9+
} as const;
10+
11+
export const GO_PLAN_PRICING_URL = 'https://open-design.ai/pricing/';
12+
13+
export function isGoPlanCampaignWindowOpen(now: number): boolean {
14+
const startAt = Date.parse(GO_PLAN_CAMPAIGN.window.startAt);
15+
const endAtExclusive = Date.parse(GO_PLAN_CAMPAIGN.window.endAtExclusive);
16+
return now >= startAt && now < endAtExclusive;
17+
}
18+
19+
export function goPlanCampaignNextBoundary(now: number): number | null {
20+
const startAt = Date.parse(GO_PLAN_CAMPAIGN.window.startAt);
21+
const endAtExclusive = Date.parse(GO_PLAN_CAMPAIGN.window.endAtExclusive);
22+
if (now < startAt) return startAt;
23+
if (now < endAtExclusive) return endAtExclusive;
24+
return null;
25+
}
26+
27+
/** Resolve subscription state without coupling the permanent Go entry to a campaign window. */
28+
export function resolveSubscriptionAudience(input: {
29+
plan: string | null | undefined;
30+
loggedIn: boolean | null | undefined;
31+
}): DeepSeekV4FlashCampaignAudience {
32+
const plan = input.plan?.trim().toLowerCase() ?? '';
33+
if (plan === 'free' || input.loggedIn === false) return 'unpaid';
34+
if (plan) return 'paid';
35+
return 'unknown';
36+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { useEffect, useState } from 'react';
2+
import {
3+
goPlanCampaignNextBoundary,
4+
isGoPlanCampaignWindowOpen,
5+
} from './go-plan';
6+
7+
export function useGoPlanCampaignVisibility(): { now: number; visible: boolean } {
8+
const [now, setNow] = useState(() => Date.now());
9+
10+
useEffect(() => {
11+
const boundary = goPlanCampaignNextBoundary(Date.now());
12+
if (boundary === null) return;
13+
const timer = window.setTimeout(
14+
() => setNow(Date.now()),
15+
Math.max(0, boundary - Date.now()) + 50,
16+
);
17+
return () => window.clearTimeout(timer);
18+
}, [now]);
19+
20+
return { now, visible: isGoPlanCampaignWindowOpen(now) };
21+
}

apps/web/src/components/AmrBalanceDialog.tsx

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -95,18 +95,8 @@ export function AmrBalanceDialog({
9595
// resume the parked task via onResolved. Bounded so an abandoned recharge
9696
// doesn't poll forever; guarded against double-fires.
9797
const [watchingWallet, setWatchingWallet] = useState(false);
98-
// Where 「升级套餐」 goes. `workspaceUpgradeUrl` is the one decision point for
99-
// every upgrade affordance: personal workspace → B's personal plan modal
100-
// (`billing=plan`); team → `billing=checkout` vs `billing=plan` by whether
101-
// the team ever completed a first checkout. Getting the personal branch wrong
102-
// opened an error-state dialog: routing a personal workspace onto the team
103-
// `billing=checkout` deep link opened the Upgrade-to-Team dialog with "Team
104-
// plan unavailable" / a 3-seat minimum (recvpYEiH019cD, failed acceptance
105-
// round). B now resolves `billing=plan` against the workspace's own state, so
106-
// the team branch's guess is a hint rather than a requirement. The profile
107-
// fallback keeps the CTA alive after a signed-out/no-context read (but not
108-
// while that read is pending) — same `billing=plan` deep link every other
109-
// Upgrade affordance uses (ChatPane, AvatarMenu, InlineModelSwitcher).
98+
// `workspaceUpgradeUrl` keeps every generic upgrade affordance on the public
99+
// Pricing comparison surface. A concrete card there owns the Cloud handoff.
110100
const {
111101
context: workspaceContext,
112102
loading: workspaceContextLoading,

0 commit comments

Comments
 (0)