Update referral points pricing
This commit is contained in:
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user