Add subsite commission applications and default rebate rates
Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
1 parent
1651d1ad66
commit
58a18a2991
35 files changed
+310
-400
No files matched your search
@@ -87,6 +87,8 @@ export const transferAffQuota = (data) => api.post('/api/dist/aff_transfer', dat
|
||||
export const getAffEarnings = (params) => api.get('/api/dist/aff_earnings', { params });
|
||||
export const getAffPayouts = (params) => api.get('/api/dist/aff_payouts', { params });
|
||||
export const requestAffWithdraw = (data) => api.post('/api/dist/aff_withdraw', data);
|
||||
export const submitDistKolApply = (data) => api.post('/api/dist/kol_apply', data);
|
||||
export const getDistKolStatus = () => api.get('/api/dist/kol_status');
|
||||
export const createSubDistributorOrder = (data) => api.post('/api/dist/site/sub-distributor/pay', data);
|
||||
|
||||
// ===== Helpers =====
|
||||
|
||||
@@ -312,6 +312,31 @@
|
||||
"topup.withdrawExceedsBalance": "Withdrawal amount cannot exceed your withdrawable balance",
|
||||
"topup.enterWithdrawMethod": "Please enter a payment method",
|
||||
"topup.currentCommissionRateLabel": "Commission Rate",
|
||||
"topup.currentCommissionRateDesc": "All regular users get a default {{rate}}% commission. It is paid only from the sub-site owner's profit and never touches the main site's base cost.",
|
||||
"topup.kolApplyTitle": "Apply for a Higher Commission Rate",
|
||||
"topup.kolApplyDesc": "Your default commission is {{rate}}%. If you have promotion resources, you can apply for a higher custom rate from the sub-site owner.",
|
||||
"topup.kolApplyAction": "Apply Now",
|
||||
"topup.kolPendingTitle": "Commission Upgrade Under Review",
|
||||
"topup.kolPendingDesc": "Your request has been submitted. The sub-site owner will review it and set a new rate if approved.",
|
||||
"topup.kolPendingBadge": "Pending",
|
||||
"topup.kolApprovedTitle": "Custom Commission Enabled",
|
||||
"topup.kolApprovedDesc": "You are currently using a custom commission rate approved by the sub-site owner.",
|
||||
"topup.kolRemarkLabel": "Owner note: ",
|
||||
"topup.kolRejectedLabel": "Request not approved: ",
|
||||
"topup.kolRejectedFallback": "The request was not approved yet. You can improve your materials and apply again.",
|
||||
"topup.kolApplyModalTitle": "Apply for a Higher Commission Rate",
|
||||
"topup.kolApplyModalDesc": "Submit your promotion profile and the sub-site owner can approve a higher rate for you.",
|
||||
"topup.kolSocialLabel": "Social Link",
|
||||
"topup.kolSocialPlaceholder": "Enter your profile, channel, or account link",
|
||||
"topup.kolFollowersLabel": "Followers / Community Size",
|
||||
"topup.kolFollowersPlaceholder": "For example: 5,000 followers / 3 groups",
|
||||
"topup.kolPlanLabel": "Promotion Plan",
|
||||
"topup.kolPlanPlaceholder": "Tell us how you plan to promote this sub-site",
|
||||
"topup.kolContactLabel": "Contact Info",
|
||||
"topup.kolContactPlaceholder": "Enter WeChat, Telegram, email, etc.",
|
||||
"topup.kolApplySubmit": "Submit Request",
|
||||
"topup.kolApplySuccess": "Application submitted. Please wait for the owner review.",
|
||||
"topup.kolSocialRequired": "Please enter a social link",
|
||||
|
||||
"dashboard.topupDesc": "Recharge account balance",
|
||||
|
||||
|
||||
@@ -312,6 +312,31 @@
|
||||
"topup.withdrawExceedsBalance": "提现金额不能超过可提现余额",
|
||||
"topup.enterWithdrawMethod": "请输入收款方式",
|
||||
"topup.currentCommissionRateLabel": "当前返佣比例",
|
||||
"topup.currentCommissionRateDesc": "普通用户默认可获得 {{rate}}% 返佣,返佣只从分站站长利润中结算,不会扣到主站进货成本。",
|
||||
"topup.kolApplyTitle": "申请更高返佣比例",
|
||||
"topup.kolApplyDesc": "当前默认返佣为 {{rate}}%,如果您有推广资源,可以向分站站长申请更高返佣。",
|
||||
"topup.kolApplyAction": "申请提额",
|
||||
"topup.kolPendingTitle": "返佣申请审核中",
|
||||
"topup.kolPendingDesc": "您的提额申请已提交,分站站长审核后会为您设置新的返佣比例。",
|
||||
"topup.kolPendingBadge": "审核中",
|
||||
"topup.kolApprovedTitle": "已获得自定义返佣",
|
||||
"topup.kolApprovedDesc": "您当前享受的是站长审批后的自定义返佣比例。",
|
||||
"topup.kolRemarkLabel": "站长备注:",
|
||||
"topup.kolRejectedLabel": "申请未通过:",
|
||||
"topup.kolRejectedFallback": "暂未通过审核,您可以补充资料后重新申请。",
|
||||
"topup.kolApplyModalTitle": "申请更高返佣比例",
|
||||
"topup.kolApplyModalDesc": "请填写您的推广资料,站长审核通过后会为您调整返佣比例。",
|
||||
"topup.kolSocialLabel": "社交媒体链接",
|
||||
"topup.kolSocialPlaceholder": "请输入主页、频道或账号链接",
|
||||
"topup.kolFollowersLabel": "粉丝数 / 社群规模",
|
||||
"topup.kolFollowersPlaceholder": "例如:5000 粉丝 / 3 个社群",
|
||||
"topup.kolPlanLabel": "推广计划",
|
||||
"topup.kolPlanPlaceholder": "介绍您打算如何推广本分站",
|
||||
"topup.kolContactLabel": "联系方式",
|
||||
"topup.kolContactPlaceholder": "请输入微信、Telegram、邮箱等",
|
||||
"topup.kolApplySubmit": "提交申请",
|
||||
"topup.kolApplySuccess": "申请已提交,请等待站长审核",
|
||||
"topup.kolSocialRequired": "请填写社交媒体链接",
|
||||
|
||||
"dashboard.topupDesc": "充值账户余额",
|
||||
|
||||
|
||||
+256
-67
@@ -2,7 +2,17 @@ import React, { useState, useEffect, useCallback } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useAuth } from '../context/AuthContext';
|
||||
import { getUserUsage, redeemCode, getAffCode, transferAffQuota, getAffEarnings, requestAffWithdraw, Q } from '../api';
|
||||
import {
|
||||
getUserUsage,
|
||||
redeemCode,
|
||||
getAffCode,
|
||||
transferAffQuota,
|
||||
getAffEarnings,
|
||||
requestAffWithdraw,
|
||||
getDistKolStatus,
|
||||
submitDistKolApply,
|
||||
Q,
|
||||
} from '../api';
|
||||
import { useCurrency, useSite } from '../context/SiteContext';
|
||||
import CountUp from '../components/bits/CountUp';
|
||||
import toast from 'react-hot-toast';
|
||||
@@ -28,21 +38,36 @@ export default function Dashboard() {
|
||||
const [withdrawMethod, setWithdrawMethod] = useState('');
|
||||
const [withdrawRemark, setWithdrawRemark] = useState('');
|
||||
const [withdrawing, setWithdrawing] = useState(false);
|
||||
const [distKolStatus, setDistKolStatus] = useState(null);
|
||||
const [showKolApplyModal, setShowKolApplyModal] = useState(false);
|
||||
const [kolApplyLoading, setKolApplyLoading] = useState(false);
|
||||
const [socialLink, setSocialLink] = useState('');
|
||||
const [followers, setFollowers] = useState('');
|
||||
const [promotionPlan, setPromotionPlan] = useState('');
|
||||
const [contactInfo, setContactInfo] = useState('');
|
||||
|
||||
const loadData = useCallback(async () => {
|
||||
try {
|
||||
const [usageRes, affRes] = await Promise.all([
|
||||
const [usageRes, affRes, kolRes] = await Promise.all([
|
||||
getUserUsage(),
|
||||
getAffCode().catch(() => null),
|
||||
getDistKolStatus().catch(() => null),
|
||||
]);
|
||||
if (usageRes.data.success) setUsage(usageRes.data.data);
|
||||
if (affRes?.data?.success && affRes.data.data) {
|
||||
setAffLink(`${window.location.origin}/register?aff=${affRes.data.data}`);
|
||||
}
|
||||
} catch (e) { /* interceptor */ }
|
||||
if (kolRes?.data?.success) {
|
||||
setDistKolStatus(kolRes.data.data || null);
|
||||
}
|
||||
} catch (e) {
|
||||
/* interceptor */
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => { loadData(); }, [loadData]);
|
||||
useEffect(() => {
|
||||
loadData();
|
||||
}, [loadData]);
|
||||
|
||||
const handleRedeem = async (e) => {
|
||||
e.preventDefault();
|
||||
@@ -55,7 +80,9 @@ export default function Dashboard() {
|
||||
setRedeemInput('');
|
||||
await Promise.all([loadData(), refreshUser()]);
|
||||
}
|
||||
} catch (err) { /* interceptor */ }
|
||||
} catch (err) {
|
||||
/* interceptor */
|
||||
}
|
||||
setRedeeming(false);
|
||||
};
|
||||
|
||||
@@ -66,7 +93,9 @@ export default function Dashboard() {
|
||||
if (res.data.success && res.data.data) {
|
||||
setAffEarnings(res.data.data);
|
||||
}
|
||||
} catch (e) { /* interceptor */ }
|
||||
} catch (e) {
|
||||
/* interceptor */
|
||||
}
|
||||
setAffEarningsLoading(false);
|
||||
};
|
||||
|
||||
@@ -93,7 +122,9 @@ export default function Dashboard() {
|
||||
setTransferAmount('');
|
||||
await Promise.all([loadData(), refreshUser()]);
|
||||
}
|
||||
} catch (e) { /* interceptor */ }
|
||||
} catch (e) {
|
||||
/* interceptor */
|
||||
}
|
||||
setTransferring(false);
|
||||
};
|
||||
|
||||
@@ -114,12 +145,35 @@ export default function Dashboard() {
|
||||
setShowWithdrawModal(false);
|
||||
};
|
||||
|
||||
const resetKolApplyForm = () => {
|
||||
setSocialLink('');
|
||||
setFollowers('');
|
||||
setPromotionPlan('');
|
||||
setContactInfo('');
|
||||
};
|
||||
|
||||
const handleOpenKolApply = () => {
|
||||
resetKolApplyForm();
|
||||
setShowKolApplyModal(true);
|
||||
};
|
||||
|
||||
const handleCloseKolApply = () => {
|
||||
if (kolApplyLoading) return;
|
||||
resetKolApplyForm();
|
||||
setShowKolApplyModal(false);
|
||||
};
|
||||
|
||||
const quota = usage?.quota ?? user?.quota ?? 0;
|
||||
const usedQuota = usage?.used_quota ?? user?.used_quota ?? 0;
|
||||
const requestCount = usage?.request_count ?? user?.request_count ?? 0;
|
||||
const balanceDollars = quota / Q * rate;
|
||||
const balanceDollars = (quota / Q) * rate;
|
||||
const availableAffAmount = ((user?.aff_quota || 0) / Q) * rate;
|
||||
const currentCommissionRate = Number(user?.commission_rate || 0);
|
||||
const defaultCommissionRate = Number(user?.default_commission_rate ?? 0.05);
|
||||
const currentCommissionRate = Number(
|
||||
user?.commission_rate ?? defaultCommissionRate,
|
||||
);
|
||||
const hasCustomCommissionRate =
|
||||
currentCommissionRate > defaultCommissionRate + 1e-8;
|
||||
|
||||
const handleWithdraw = async () => {
|
||||
const amount = Number.parseFloat(withdrawAmount);
|
||||
@@ -149,13 +203,107 @@ export default function Dashboard() {
|
||||
resetWithdrawForm();
|
||||
await Promise.all([loadData(), refreshUser()]);
|
||||
}
|
||||
} catch (err) { /* interceptor */ }
|
||||
} catch (err) {
|
||||
/* interceptor */
|
||||
}
|
||||
setWithdrawing(false);
|
||||
};
|
||||
|
||||
const handleKolApply = async () => {
|
||||
if (!socialLink.trim()) {
|
||||
toast.error(t('topup.kolSocialRequired'));
|
||||
return;
|
||||
}
|
||||
setKolApplyLoading(true);
|
||||
try {
|
||||
const res = await submitDistKolApply({
|
||||
social_link: socialLink.trim(),
|
||||
followers: followers.trim(),
|
||||
promotion_plan: promotionPlan.trim(),
|
||||
contact_info: contactInfo.trim(),
|
||||
});
|
||||
if (res.data.success) {
|
||||
toast.success(res.data.message || t('topup.kolApplySuccess'));
|
||||
setShowKolApplyModal(false);
|
||||
resetKolApplyForm();
|
||||
await Promise.all([loadData(), refreshUser()]);
|
||||
}
|
||||
} catch (err) {
|
||||
/* interceptor */
|
||||
}
|
||||
setKolApplyLoading(false);
|
||||
};
|
||||
|
||||
const renderCommissionApplicationPanel = () => {
|
||||
if (distKolStatus?.status === 0) {
|
||||
return (
|
||||
<div className="glass-sm rounded-xl border border-amber-500/20 bg-amber-500/5 p-4">
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<div>
|
||||
<p className="text-sm font-medium text-page">{t('topup.kolPendingTitle')}</p>
|
||||
<p className="mt-1 text-xs text-page-secondary">{t('topup.kolPendingDesc')}</p>
|
||||
</div>
|
||||
<span className="rounded-full border border-amber-500/20 bg-amber-500/10 px-3 py-1 text-xs font-medium text-amber-600">
|
||||
{t('topup.kolPendingBadge')}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (distKolStatus?.status === 1 || hasCustomCommissionRate) {
|
||||
return (
|
||||
<div className="glass-sm rounded-xl border border-emerald-500/20 bg-emerald-500/5 p-4">
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<div>
|
||||
<p className="text-sm font-medium text-page">{t('topup.kolApprovedTitle')}</p>
|
||||
<p className="mt-1 text-xs text-page-secondary">{t('topup.kolApprovedDesc')}</p>
|
||||
</div>
|
||||
<span className="rounded-full border border-emerald-500/20 bg-emerald-500/10 px-3 py-1 text-xs font-medium text-emerald-600">
|
||||
{(currentCommissionRate * 100).toFixed(1)}%
|
||||
</span>
|
||||
</div>
|
||||
{distKolStatus?.admin_remark && (
|
||||
<p className="mt-3 text-xs text-page-muted">
|
||||
{t('topup.kolRemarkLabel')}
|
||||
{distKolStatus.admin_remark}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="glass-sm rounded-xl border border-brand-500/20 bg-brand-500/5 p-4">
|
||||
<div className="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
|
||||
<div>
|
||||
<p className="text-sm font-medium text-page">{t('topup.kolApplyTitle')}</p>
|
||||
<p className="mt-1 text-xs text-page-secondary">
|
||||
{t('topup.kolApplyDesc', {
|
||||
rate: (defaultCommissionRate * 100).toFixed(1),
|
||||
})}
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleOpenKolApply}
|
||||
className="btn-primary whitespace-nowrap px-4 py-2 text-sm"
|
||||
>
|
||||
{t('topup.kolApplyAction')}
|
||||
</button>
|
||||
</div>
|
||||
{distKolStatus?.status === 2 && (
|
||||
<p className="mt-3 text-xs text-red-500">
|
||||
{t('topup.kolRejectedLabel')}
|
||||
{distKolStatus.admin_remark || t('topup.kolRejectedFallback')}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="max-w-7xl mx-auto px-6 py-10">
|
||||
{/* Welcome */}
|
||||
<div className="mb-8">
|
||||
<h1 className="text-2xl font-heading font-bold text-page mb-1">
|
||||
{t('dashboard.welcome')} {user?.display_name || user?.username || 'User'}
|
||||
@@ -163,12 +311,12 @@ export default function Dashboard() {
|
||||
<p className="text-sm text-page-secondary">{t('dashboard.manageDesc')}</p>
|
||||
</div>
|
||||
|
||||
{/* Stats Cards */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-6 mb-10">
|
||||
<div className="glass rounded-2xl p-6">
|
||||
<p className="text-sm text-page-secondary mb-2">{t('dashboard.balance')}</p>
|
||||
<div className="text-3xl font-bold text-page">
|
||||
{symbol}<CountUp from={0} to={Math.round(balanceDollars * 100) / 100} duration={1.5} />
|
||||
{symbol}
|
||||
<CountUp from={0} to={Math.round(balanceDollars * 100) / 100} duration={1.5} />
|
||||
</div>
|
||||
<p className="text-xs text-page-muted mt-1">{t('dashboard.quotaUnits', { count: quota.toLocaleString() })}</p>
|
||||
</div>
|
||||
@@ -176,7 +324,8 @@ export default function Dashboard() {
|
||||
<div className="glass rounded-2xl p-6">
|
||||
<p className="text-sm text-page-secondary mb-2">{t('dashboard.used')}</p>
|
||||
<div className="text-3xl font-bold text-page">
|
||||
{symbol}<CountUp from={0} to={Math.round(usedQuota / Q * rate * 100) / 100} duration={1.5} />
|
||||
{symbol}
|
||||
<CountUp from={0} to={Math.round((usedQuota / Q) * rate * 100) / 100} duration={1.5} />
|
||||
</div>
|
||||
<p className="text-xs text-page-muted mt-1">{t('dashboard.quotaUnits', { count: usedQuota.toLocaleString() })}</p>
|
||||
</div>
|
||||
@@ -189,9 +338,7 @@ export default function Dashboard() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Quick Actions */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
{/* Redeem Code */}
|
||||
<div className="glass rounded-2xl p-6">
|
||||
<h2 className="text-lg font-semibold text-page mb-4">{t('dashboard.redeemCode')}</h2>
|
||||
<form onSubmit={handleRedeem} className="flex gap-3">
|
||||
@@ -208,7 +355,6 @@ export default function Dashboard() {
|
||||
</form>
|
||||
</div>
|
||||
|
||||
{/* Quick Links */}
|
||||
<div className="glass rounded-2xl p-6">
|
||||
<h2 className="text-lg font-semibold text-page mb-4">{t('dashboard.quickLinks')}</h2>
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
@@ -242,7 +388,6 @@ export default function Dashboard() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Invitation / Affiliate */}
|
||||
{affLink && (
|
||||
<div className="glass rounded-2xl p-6 mt-6">
|
||||
<div className="mb-5 flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between">
|
||||
@@ -256,18 +401,27 @@ export default function Dashboard() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Stats */}
|
||||
<p className="mb-5 text-xs text-page-muted">
|
||||
{t('topup.currentCommissionRateDesc', {
|
||||
rate: (defaultCommissionRate * 100).toFixed(1),
|
||||
})}
|
||||
</p>
|
||||
|
||||
<div className="mb-5">
|
||||
{renderCommissionApplicationPanel()}
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-3 gap-4 mb-6">
|
||||
<div className="glass-sm rounded-xl p-4 text-center">
|
||||
<p className="text-xs text-page-secondary mb-1">{t('topup.affAvailable')}</p>
|
||||
<p className="text-xl font-bold text-page">
|
||||
{symbol}{((user?.aff_quota || 0) / Q * rate).toFixed(2)}
|
||||
{symbol}{(((user?.aff_quota || 0) / Q) * rate).toFixed(2)}
|
||||
</p>
|
||||
</div>
|
||||
<div className="glass-sm rounded-xl p-4 text-center">
|
||||
<p className="text-xs text-page-secondary mb-1">{t('topup.affTotal')}</p>
|
||||
<p className="text-xl font-bold text-page">
|
||||
{symbol}{((user?.aff_history_quota || 0) / Q * rate).toFixed(2)}
|
||||
{symbol}{(((user?.aff_history_quota || 0) / Q) * rate).toFixed(2)}
|
||||
</p>
|
||||
</div>
|
||||
<div className="glass-sm rounded-xl p-4 text-center">
|
||||
@@ -276,35 +430,21 @@ export default function Dashboard() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Invitation Link */}
|
||||
<div className="mb-5">
|
||||
<label className="block text-sm font-medium text-page-label mb-2">{t('topup.inviteLink')}</label>
|
||||
<div className="flex gap-2">
|
||||
<input
|
||||
type="text"
|
||||
readOnly
|
||||
value={affLink}
|
||||
className="input flex-1 text-sm"
|
||||
/>
|
||||
<button
|
||||
onClick={handleCopyAffLink}
|
||||
className="btn-primary whitespace-nowrap text-sm px-4"
|
||||
>
|
||||
<input type="text" readOnly value={affLink} className="input flex-1 text-sm" />
|
||||
<button onClick={handleCopyAffLink} className="btn-primary whitespace-nowrap text-sm px-4">
|
||||
{t('topup.copy')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Transfer to balance */}
|
||||
{(user?.aff_quota || 0) > 0 && (
|
||||
<div className="mb-5">
|
||||
<div className="mb-2 flex items-center justify-between gap-3">
|
||||
<label className="block text-sm font-medium text-page-label">{t('topup.transferToBalance')}</label>
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleOpenWithdraw}
|
||||
className="btn-secondary whitespace-nowrap px-4 py-2 text-sm"
|
||||
>
|
||||
<button type="button" onClick={handleOpenWithdraw} className="btn-secondary whitespace-nowrap px-4 py-2 text-sm">
|
||||
{t('topup.withdraw')}
|
||||
</button>
|
||||
</div>
|
||||
@@ -317,21 +457,19 @@ export default function Dashboard() {
|
||||
className="input flex-1 text-sm"
|
||||
min={1}
|
||||
/>
|
||||
<button
|
||||
onClick={handleTransfer}
|
||||
disabled={transferring}
|
||||
className="btn-primary whitespace-nowrap text-sm px-4"
|
||||
>
|
||||
<button onClick={handleTransfer} disabled={transferring} className="btn-primary whitespace-nowrap text-sm px-4">
|
||||
{transferring ? t('topup.processing') : t('topup.transfer')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Earnings Details */}
|
||||
<div>
|
||||
<button
|
||||
onClick={() => { setShowAffEarnings(!showAffEarnings); if (!showAffEarnings) loadAffEarnings(); }}
|
||||
onClick={() => {
|
||||
setShowAffEarnings(!showAffEarnings);
|
||||
if (!showAffEarnings) loadAffEarnings();
|
||||
}}
|
||||
className="text-sm text-page-secondary hover:text-page transition-colors"
|
||||
>
|
||||
{showAffEarnings ? t('topup.hideEarnings') : t('topup.viewEarnings')}
|
||||
@@ -355,7 +493,7 @@ export default function Dashboard() {
|
||||
</p>
|
||||
</div>
|
||||
<span className="text-sm font-medium text-page-success">
|
||||
+{symbol}{(item.commission_quota / Q * rate).toFixed(4)}
|
||||
+{symbol}{((item.commission_quota / Q) * rate).toFixed(4)}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
@@ -372,10 +510,7 @@ export default function Dashboard() {
|
||||
className="modal-overlay fixed inset-0 z-50 flex items-center justify-center bg-black/60 p-4 backdrop-blur-sm"
|
||||
onClick={handleCloseWithdraw}
|
||||
>
|
||||
<div
|
||||
className="glass w-full max-w-md rounded-2xl p-6"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<div className="glass w-full max-w-md rounded-2xl p-6" onClick={(e) => e.stopPropagation()}>
|
||||
<div className="mb-5">
|
||||
<h3 className="text-lg font-semibold text-page mb-1">{t('topup.withdrawTitle')}</h3>
|
||||
<p className="text-sm text-page-secondary">{t('topup.withdrawSubtitle')}</p>
|
||||
@@ -409,11 +544,7 @@ export default function Dashboard() {
|
||||
className="input pl-8"
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setWithdrawAmount(availableAffAmount.toFixed(2))}
|
||||
className="btn-secondary whitespace-nowrap px-4"
|
||||
>
|
||||
<button type="button" onClick={() => setWithdrawAmount(availableAffAmount.toFixed(2))} className="btn-secondary whitespace-nowrap px-4">
|
||||
{t('topup.withdrawAll')}
|
||||
</button>
|
||||
</div>
|
||||
@@ -442,26 +573,84 @@ export default function Dashboard() {
|
||||
</div>
|
||||
|
||||
<div className="mt-6 flex justify-end gap-3">
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleCloseWithdraw}
|
||||
disabled={withdrawing}
|
||||
className="btn-secondary px-4 py-2"
|
||||
>
|
||||
<button type="button" onClick={handleCloseWithdraw} disabled={withdrawing} className="btn-secondary px-4 py-2">
|
||||
{t('tokens.cancel')}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleWithdraw}
|
||||
disabled={withdrawing}
|
||||
className="btn-primary px-4 py-2"
|
||||
>
|
||||
<button type="button" onClick={handleWithdraw} disabled={withdrawing} className="btn-primary px-4 py-2">
|
||||
{withdrawing ? t('topup.processing') : t('topup.submitWithdraw')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{showKolApplyModal && (
|
||||
<div
|
||||
className="modal-overlay fixed inset-0 z-50 flex items-center justify-center bg-black/60 p-4 backdrop-blur-sm"
|
||||
onClick={handleCloseKolApply}
|
||||
>
|
||||
<div className="glass w-full max-w-md rounded-2xl p-6" onClick={(e) => e.stopPropagation()}>
|
||||
<div className="mb-5">
|
||||
<h3 className="text-lg font-semibold text-page mb-1">{t('topup.kolApplyModalTitle')}</h3>
|
||||
<p className="text-sm text-page-secondary">{t('topup.kolApplyModalDesc')}</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<label className="mb-2 block text-sm font-medium text-page-label">{t('topup.kolSocialLabel')}</label>
|
||||
<input
|
||||
type="text"
|
||||
value={socialLink}
|
||||
onChange={(e) => setSocialLink(e.target.value)}
|
||||
placeholder={t('topup.kolSocialPlaceholder')}
|
||||
className="input"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="mb-2 block text-sm font-medium text-page-label">{t('topup.kolFollowersLabel')}</label>
|
||||
<input
|
||||
type="text"
|
||||
value={followers}
|
||||
onChange={(e) => setFollowers(e.target.value)}
|
||||
placeholder={t('topup.kolFollowersPlaceholder')}
|
||||
className="input"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="mb-2 block text-sm font-medium text-page-label">{t('topup.kolPlanLabel')}</label>
|
||||
<textarea
|
||||
value={promotionPlan}
|
||||
onChange={(e) => setPromotionPlan(e.target.value)}
|
||||
placeholder={t('topup.kolPlanPlaceholder')}
|
||||
className="input min-h-[96px] resize-y"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="mb-2 block text-sm font-medium text-page-label">{t('topup.kolContactLabel')}</label>
|
||||
<input
|
||||
type="text"
|
||||
value={contactInfo}
|
||||
onChange={(e) => setContactInfo(e.target.value)}
|
||||
placeholder={t('topup.kolContactPlaceholder')}
|
||||
className="input"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-6 flex justify-end gap-3">
|
||||
<button type="button" onClick={handleCloseKolApply} disabled={kolApplyLoading} className="btn-secondary px-4 py-2">
|
||||
{t('tokens.cancel')}
|
||||
</button>
|
||||
<button type="button" onClick={handleKolApply} disabled={kolApplyLoading} className="btn-primary px-4 py-2">
|
||||
{kolApplyLoading ? t('topup.processing') : t('topup.kolApplySubmit')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user