Update referral points pricing

This commit is contained in:
2569718930@qq.com
2026-05-30 18:04:36 +08:00
parent c0b20ed1bf
commit 90bc895000
32 changed files with 940 additions and 224 deletions
+1 -1
View File
@@ -49,7 +49,7 @@ export default async function HomePage({
price: "29.90",
priceCurrency: "USD",
description:
"Pro subscription for 30 days. Referral users pay 26.90 USD-equivalent USDC for the first month.",
"Pro subscription for 30 days. Referral users pay 20.00 USD-equivalent USDC for the first month.",
availability: "https://schema.org/InStock",
},
{
@@ -12,11 +12,6 @@ import {
} from "lucide-react";
import { useI18n } from "@/hooks/useI18n";
const TELEGRAM_GROUP_URL = String(
process.env.NEXT_PUBLIC_TELEGRAM_GROUP_URL ||
"https://t.me/+Io5H9oVHFmVjOTQ5",
).trim();
const FAQ_ITEMS = [
{
q_zh: "Pro 包含哪些功能?",
@@ -27,14 +22,14 @@ const FAQ_ITEMS = [
{
q_zh: "当前订阅价格是多少?",
q_en: "What is the current subscription price?",
a_zh: "目前仅提供月付:10 USDC / 30 天。",
a_en: "Monthly plan only: 10 USDC / 30 days.",
a_zh: "Pro 月付 29.9 USDC / 30 天,Pro 季度 79.9 USDC / 90 天。",
a_en: "Pro monthly is 29.9 USDC / 30 days. Pro quarterly is 79.9 USDC / 90 days.",
},
{
q_zh: "积分如何抵扣?",
q_en: "How do points work for discounts?",
a_zh: "满 500 积分起兑,每 500 积分抵 1U,单次最多抵 3U。",
a_en: "500 points minimum every 500 points = 1 USDC off, up to 3 USDC discount per payment.",
a_zh: "满 500 积分起兑,每 500 积分抵 1U。月付最多抵 3U,季度最多抵 8U。",
a_en: "500 points minimum: every 500 points = 1 USDC off. Monthly orders can use up to 3 USDC off; quarterly orders can use up to 8 USDC off.",
},
{
q_zh: "支持哪些钱包和支付方式?",
@@ -55,11 +50,11 @@ export function SubscriptionHelpClient() {
? "Complete subscription rules and payment guide."
: "这里是完整的订阅规则和支付说明。你可以先在页面内绑定钱包,再直接开通 Pro。",
priceLabel: isEn ? "Price" : "订阅价格",
priceText: "10 USDC / 30 " + (isEn ? "Days" : "天"),
priceText: isEn ? "29.9 / 30d · 79.9 / 90d" : "29.9 / 30天 · 79.9 / 90天",
discountLabel: isEn ? "Points Discount" : "积分抵扣",
discountText: isEn ? "Up to 3 USDC off" : "最多抵 3U",
communityLabel: isEn ? "Community Points" : "社群积分",
communityLink: isEn ? "Join community to earn" : "加入社群即可赚取积分",
discountText: isEn ? "Monthly 3U · Quarterly 8U" : "月付 3U · 季度 8U",
communityLabel: isEn ? "Telegram Group" : "Telegram 群",
communityLink: isEn ? "Open Account Center" : "前往账户中心",
faqTitle: isEn ? "FAQ" : "常见问题",
}), [isEn]);
@@ -102,8 +97,7 @@ export function SubscriptionHelpClient() {
<span className="text-sm font-semibold">{copy.communityLabel}</span>
</div>
<Link
href={TELEGRAM_GROUP_URL}
target="_blank"
href="/account"
className="inline-flex min-h-9 items-center text-sm font-semibold text-blue-700 underline decoration-blue-500/50 underline-offset-4"
>
{copy.communityLink}
+37 -33
View File
@@ -431,20 +431,31 @@ export function AccountCenter() {
showOverlay,
]);
// ── Weekly points display (component-only derived) ──────
const backendWeeklyPointsRaw = Number(backend?.weekly_points);
const metadataWeeklyPointsRaw = Number(
user?.user_metadata?.weekly_points ?? 0,
);
const weeklyPointsRaw = Number.isFinite(backendWeeklyPointsRaw)
? backendWeeklyPointsRaw
: metadataWeeklyPointsRaw;
const weeklyRankRaw =
backend?.weekly_rank ?? user?.user_metadata?.weekly_rank;
const weeklyPoints = Number.isFinite(weeklyPointsRaw)
? Math.max(0, weeklyPointsRaw)
// ── Referral points display ────────────────────────────
const referralRewardPointsRaw = Number(referral?.reward_points ?? 3500);
const referralRewardPoints = Number.isFinite(referralRewardPointsRaw)
? Math.max(0, referralRewardPointsRaw)
: 3500;
const monthlyReferralCountRaw = Number(referral?.monthly_reward_count ?? 0);
const monthlyReferralCount = Number.isFinite(monthlyReferralCountRaw)
? Math.max(0, monthlyReferralCountRaw)
: 0;
const weeklyRank = weeklyRankRaw == null ? "--" : String(weeklyRankRaw);
const monthlyReferralLimitRaw = Number(referral?.monthly_reward_limit ?? 10);
const monthlyReferralLimit = Number.isFinite(monthlyReferralLimitRaw)
? Math.max(0, monthlyReferralLimitRaw)
: 10;
const monthlyReferralPointsRaw = Number(
referral?.monthly_reward_points ?? monthlyReferralCount * referralRewardPoints,
);
const monthlyReferralPoints = Number.isFinite(monthlyReferralPointsRaw)
? Math.max(0, monthlyReferralPointsRaw)
: 0;
const monthlyReferralPointsLimitRaw = Number(
referral?.monthly_reward_points_limit ?? monthlyReferralLimit * referralRewardPoints,
);
const monthlyReferralPointsLimit = Number.isFinite(monthlyReferralPointsLimitRaw)
? Math.max(0, monthlyReferralPointsLimitRaw)
: monthlyReferralLimit * referralRewardPoints;
// ── Telegram bind command ──────────────────────────────
const bindCommand = userId
@@ -691,7 +702,7 @@ export function AccountCenter() {
</p>
<p className="flex items-center justify-center gap-2 text-xl font-bold text-slate-950">
<TrendingUp size={16} className="text-emerald-400" />{" "}
{weeklyPoints.toLocaleString()}
{monthlyReferralCount.toLocaleString()}
</p>
</div>
<div className="min-w-[140px] rounded-xl border border-blue-200 bg-blue-50 px-6 py-4 text-center">
@@ -700,13 +711,13 @@ export function AccountCenter() {
</p>
<p className="flex items-center justify-center gap-2 text-xl font-bold text-slate-950">
<Trophy size={16} className="text-amber-400" />{" "}
{weeklyRank === "--" ? weeklyRank : `#${weeklyRank}`}
{monthlyReferralCount}/{monthlyReferralLimit}
</p>
</div>
</div>
</div>
{/* Weekly Ranking Motivation */}
{/* Referral rewards */}
{showSecondarySections ? (
<div className="flex flex-col justify-between rounded-2xl border border-slate-200 bg-white p-6 shadow-sm lg:col-span-4">
<div>
@@ -717,35 +728,29 @@ export function AccountCenter() {
<div className="space-y-3">
<div className="flex items-center justify-between rounded-xl border border-slate-200 bg-slate-50 p-3">
<span className="text-sm flex items-center gap-2">
<div className="w-5 h-5 bg-yellow-500 rounded text-black font-bold text-[10px] flex items-center justify-center">
1
</div>{" "}
Top 1
<Coins size={16} className="text-yellow-500" />{" "}
{copy.referralRewardHint}
</span>
<span className="text-xs font-bold text-amber-600">
+200 & 7Pro
+{referralRewardPoints.toLocaleString()}
</span>
</div>
<div className="flex items-center justify-between rounded-xl border border-slate-200 bg-slate-50 p-3">
<span className="text-sm flex items-center gap-2">
<div className="w-5 h-5 bg-slate-300 rounded text-black font-bold text-[10px] flex items-center justify-center">
2
</div>{" "}
Top 2-3
<TrendingUp size={16} className="text-emerald-500" />{" "}
{copy.weeklyPoints}
</span>
<span className="text-xs font-bold text-slate-600">
+100
{monthlyReferralCount}/{monthlyReferralLimit}
</span>
</div>
<div className="flex items-center justify-between rounded-xl border border-slate-200 bg-slate-50 p-3">
<span className="text-sm flex items-center gap-2">
<div className="w-5 h-5 bg-orange-800 rounded text-white font-bold text-[10px] flex items-center justify-center">
4
</div>{" "}
Top 4-10
<Trophy size={16} className="text-blue-500" />{" "}
{copy.totalPoints}
</span>
<span className="text-xs font-bold text-orange-400">
+50
{monthlyReferralPoints.toLocaleString()}/{monthlyReferralPointsLimit.toLocaleString()}
</span>
</div>
</div>
@@ -753,8 +758,7 @@ export function AccountCenter() {
<div className="mt-6 flex items-start gap-2 rounded-xl border border-slate-200 bg-slate-50 p-3">
<Info size={14} className="text-slate-500 mt-0.5 shrink-0" />
<p className="text-[10px] text-slate-500 leading-normal italic">
+
{copy.pointsRule}
</p>
</div>
</div>
@@ -46,6 +46,14 @@ export function runTests() {
accountCenter.includes("29.9"),
"account center must show monthly and quarterly Pro prices",
);
assert(
accountCopy.includes("20 USDC") &&
accountCopy.includes("+3500 积分") &&
accountCopy.includes("月付订单最多抵扣 3 USDC") &&
accountCopy.includes("季度订单最多抵扣 8 USDC") &&
!accountCopy.includes("群内有效发言"),
"account copy must describe balanced referral points and remove group-message points",
);
assert(
!useAccountPayment.includes("monthlyPlanList") &&
!usePaymentFlow.includes("monthlyPlanList"),
@@ -54,7 +62,8 @@ export function runTests() {
assert(
types.includes("ReferralSummary") &&
types.includes("referral?: ReferralSummary | null") &&
types.includes("duration_days: number"),
types.includes("duration_days: number") &&
types.includes("max_discount_usdc_by_plan"),
"account auth and payment types must include referral summary and plan durations",
);
}
+11 -11
View File
@@ -10,9 +10,9 @@ export function createAccountCopy(isEn: boolean): Record<string, string> {
guestUser: isEn ? "Signed-out account" : "未登录账户",
joinedAt: isEn ? "Joined" : "加入时间",
totalPoints: isEn ? "Total Points" : "总积分 (荣誉)",
weeklyPoints: isEn ? "Weekly Points" : "本周积分 (竞技)",
weeklyRank: isEn ? "Weekly Rank" : "周排行 (竞技)",
weeklyRewards: isEn ? "Weekly Rewards" : "周榜奖励",
weeklyPoints: isEn ? "This Month Invites" : "本月有效邀请",
weeklyRank: isEn ? "Monthly Cap" : "月度上限",
weeklyRewards: isEn ? "Referral Rewards" : "邀请奖励",
membershipDetails: isEn ? "Membership Details" : "会员权限详情",
identityStatus: isEn ? "Identity Status" : "身份状态",
authMode: isEn ? "Auth Mode" : "鉴权模式",
@@ -74,14 +74,14 @@ export function createAccountCopy(isEn: boolean): Record<string, string> {
: "邀请码已应用,首月 Pro 将按邀请价结算。",
referralApplyFailed: isEn ? "Failed to apply referral code" : "邀请码应用失败",
referralDiscountHint: isEn
? "Invite discount: first monthly Pro is 26.9 USDC."
: "邀请首月价:Pro 月付 26.9 USDC。",
? "Invite discount: first monthly Pro is 20 USDC."
: "邀请首月价:Pro 月付 20 USDC。",
referralRewardHint: isEn
? "When an invited user pays for Pro, you receive +3 days Pro."
: "被邀请人成功付费后,邀请人获得 +3 天 Pro。",
? "When an invited user pays for Pro, you receive +3500 points."
: "被邀请人成功付费后,邀请人获得 +3500 积分。",
referralInviteLimit: isEn
? "Monthly referral reward cap: 10 paid invites, up to +30 days Pro."
: "每月最多 10 个有效付费邀请奖励,最高 +30 天 Pro。",
? "Monthly referral reward cap: 10 paid invites, up to +35000 points."
: "每月最多 10 个有效付费邀请奖励,最高 +35000 积分。",
paymentToken: isEn ? "Payment Token" : "支付币种",
paymentAccount: isEn ? "Subscription Account" : "订阅归属账号",
paymentWallet: isEn ? "Paying Wallet" : "付款钱包",
@@ -334,8 +334,8 @@ export function createAccountCopy(isEn: boolean): Record<string, string> {
: "加载支付配置失败: {raw}",
// ── Points / Footer ────────────────────────────────────────────────
pointsRule: isEn
? "Points rule: active messages in group (anti-spam) + daily first message bonus. Weekly leaderboard settles every Monday midnight. All active members receive participation awards."
: "积分规则:群内有效发言(自动防刷检测)+ 每日首条发言额外奖励。每周一零点结算周榜,所有活跃用户均享参与奖。",
? "Points rule: points are earned through successful paid referrals only. 500 points = 1 USDC. Monthly orders can use up to 3 USDC off; quarterly orders can use up to 8 USDC off."
: "积分规则:积分仅通过有效付费邀请获得。500 积分 = 1 USDC。月付订单最多抵扣 3 USDC,季度订单最多抵扣 8 USDC。",
pointsDiscount: isEn
? "Available: {points} points discounting ${amount}. Applied automatically on renewal."
: "当前可用 {points} 积分抵扣 ${amount},续费时会自动生效。",
+4
View File
@@ -26,10 +26,13 @@ export type ReferralSummary = {
discount_usdc?: string;
discounted_monthly_amount_usdc?: string;
reward_days?: number;
reward_points?: number;
monthly_reward_limit?: number;
monthly_reward_days_limit?: number;
monthly_reward_points_limit?: number;
monthly_reward_count?: number;
monthly_reward_days?: number;
monthly_reward_points?: number;
applied_code?: string;
attribution_status?: string;
};
@@ -81,6 +84,7 @@ export type PointsRedemptionConfig = {
enabled?: boolean;
points_per_usdc?: number;
max_discount_usdc?: number;
max_discount_usdc_by_plan?: Record<string, number>;
};
export type PaymentConfig = {
+7 -3
View File
@@ -150,7 +150,10 @@ export function useBilling(params: UseBillingParams) {
const pointsPerUsdc =
Number.isFinite(pointsPerUsdcRaw) && pointsPerUsdcRaw > 0 ? Math.floor(pointsPerUsdcRaw) : 500;
const maxDiscountRaw = Number(pointsCfg.max_discount_usdc ?? 3);
const maxDiscountByPlan = pointsCfg.max_discount_usdc_by_plan || {};
const planMaxDiscountRaw =
maxDiscountByPlan[selectedPlanCode] ?? pointsCfg.max_discount_usdc ?? 3;
const maxDiscountRaw = Number(planMaxDiscountRaw);
const maxDiscountUsdc = Math.max(
0,
Math.min(
@@ -160,10 +163,11 @@ export function useBilling(params: UseBillingParams) {
);
const maxRedeemablePoints = pointsPerUsdc * maxDiscountUsdc;
const actualRedeem = pointsEnabled ? Math.min(totalPoints, maxRedeemablePoints) : 0;
const pointsCanApply = pointsEnabled && !referralApplies;
const actualRedeem = pointsCanApply ? Math.min(totalPoints, maxRedeemablePoints) : 0;
const discountUnits = Math.floor(actualRedeem / pointsPerUsdc);
const pointsUsed = discountUnits * pointsPerUsdc;
const canRedeem = pointsEnabled && maxDiscountUsdc > 0 && totalPoints >= pointsPerUsdc;
const canRedeem = pointsCanApply && maxDiscountUsdc > 0 && totalPoints >= pointsPerUsdc;
const applyDiscount = usePoints && canRedeem && pointsUsed > 0;
return {
@@ -131,13 +131,13 @@ function InstitutionalLandingScreen() {
{ label: "Trial", value: "3 days" },
{ label: "Monthly", value: "29.9 USDC" },
{ label: "Quarterly", value: "79.9 USDC" },
{ label: "Referral", value: "26.9 USDC" },
{ label: "Referral", value: "20 USDC" },
]
: [
{ label: "试用", value: "3 天" },
{ label: "月付", value: "29.9 USDC" },
{ label: "季度", value: "79.9 USDC" },
{ label: "邀请首月", value: "26.9 USDC" },
{ label: "邀请首月", value: "20 USDC" },
];
return (
@@ -387,7 +387,7 @@ function InstitutionalLandingScreen() {
<span className="text-sm font-semibold text-slate-500">USDC / 30 </span>
</div>
<p className="mt-2 text-xs font-semibold text-slate-500">
{isEn ? "Referral first month: 26.9 USDC" : "使用邀请码首月 26.9 USDC"}
{isEn ? "Referral first month: 20 USDC" : "使用邀请码首月 20 USDC"}
</p>
<ul className="mt-7 space-y-3 border-t border-slate-200 pt-6">
{(isEn ? PRO_FEATURES_EN : PRO_FEATURES_ZH).map((feature) => (
@@ -425,8 +425,8 @@ function InstitutionalLandingScreen() {
</div>
<div className="mt-5 rounded-md border border-slate-200 bg-white px-4 py-3 text-xs font-semibold text-slate-600">
{isEn
? "Invite reward: referrer gets +3 days Pro when invitee subscribes."
: "邀请奖励:被邀请人付费后,邀请人 +3 天 Pro。"}
? "Invite reward: referrer gets +3500 points when invitee subscribes."
: "邀请奖励:被邀请人付费后,邀请人 +3500 积分。"}
</div>
<Link
href="/account"
@@ -20,7 +20,7 @@ export function runTests() {
assert(source.includes("/static/web.png"), "landing page must show the product preview image");
assert(source.includes("29.9") && source.includes("30 天"), "landing page must show monthly Pro pricing");
assert(source.includes("79.9") && source.includes("90 天"), "landing page must show quarterly Pro pricing");
assert(source.includes("26.9") && source.includes("+3 天 Pro"), "landing page must describe referral discount and reward");
assert(source.includes("20 USDC") && source.includes("+3500 积分"), "landing page must describe referral discount and reward");
assert(!source.includes("AI 气象证据链解读"), "legacy AI evidence-chain wording must be removed");
assert(!source.includes("AI weather evidence"), "legacy AI evidence wording must be removed");
assert(!source.includes("$10"), "legacy $10/month pricing must be removed from landing page");
@@ -22,7 +22,7 @@ import {
import s from "./UnlockProOverlay.module.css";
const DEFAULT_FAQ_HREF = "/subscription-help";
const DEFAULT_TELEGRAM_GROUP_URL = "https://t.me/+Io5H9oVHFmVjOTQ5";
const DEFAULT_TELEGRAM_GROUP_URL = "";
export type UnlockProBilling = {
pointsEnabled: boolean;
@@ -310,16 +310,16 @@ export function UnlockProOverlay({
>
<MessageSquare size={12} />
{isEn
? "Join community to earn points"
: "加入社群即可赚取积分"}
? "Invite paid users to earn points"
: "邀请付费用户即可获得积分"}
<ArrowRight size={11} />
</Link>
) : (
<span className={s.unavailCta} style={{ cursor: "default" }}>
<MessageSquare size={12} />
{isEn
? "Join community to earn points"
: "加入社群即可赚取积分"}
? "Invite paid users to earn points"
: "邀请付费用户即可获得积分"}
</span>
)}
</div>