Update referral points pricing
This commit is contained in:
@@ -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}
|
||||
|
||||
@@ -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 积分 & 7天Pro
|
||||
+{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",
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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},续费时会自动生效。",
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user