Add nested distributor pricing and profit split
This commit is contained in:
1 parent
1138015cc4
commit
de5d047371
14 files changed
+384
-3
No files matched your search
Vendored
+2
-2
@@ -7,8 +7,8 @@
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&family=Noto+Sans+SC:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet" />
|
||||
<title>AI Platform</title>
|
||||
<script type="module" crossorigin src="/assets/index-C_OxsqDt.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-CWIuyqhC.css">
|
||||
<script type="module" crossorigin src="/assets/index-CpYyIBrK.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-1hnkxZ6J.css">
|
||||
</head>
|
||||
<body class="antialiased">
|
||||
<script>
|
||||
|
||||
@@ -11,6 +11,7 @@ const Packages = lazy(() => import('./pages/Packages'));
|
||||
const Pricing = lazy(() => import('./pages/Pricing'));
|
||||
const Topup = lazy(() => import('./pages/Topup'));
|
||||
const Logs = lazy(() => import('./pages/Logs'));
|
||||
const SubDistributor = lazy(() => import('./pages/SubDistributor'));
|
||||
|
||||
const Loading = () => (
|
||||
<div className="flex items-center justify-center min-h-screen" style={{ background: 'var(--page-bg)' }}>
|
||||
@@ -30,6 +31,7 @@ function ThemedRoutes() {
|
||||
<Route path="/" element={<Home />} />
|
||||
<Route path="/pricing" element={<Pricing />} />
|
||||
<Route path="/packages" element={<Packages />} />
|
||||
<Route path="/sub-site" element={<SubDistributor />} />
|
||||
<Route path="/login" element={<Login />} />
|
||||
<Route path="/register" element={<Register />} />
|
||||
|
||||
|
||||
@@ -48,6 +48,7 @@ export const getSiteModels = () => api.get('/api/dist/site/models');
|
||||
export const getSitePricing = () => api.get('/api/dist/site/pricing');
|
||||
export const getSitePackages = () => api.get('/api/dist/site/packages');
|
||||
export const getSiteKeyGroups = () => api.get('/api/dist/site/key-groups');
|
||||
export const getSubDistributorInfo = () => api.get('/api/dist/site/sub-distributor/info');
|
||||
|
||||
// ===== Auth =====
|
||||
export const register = (data) => api.post('/api/dist/user/register', data);
|
||||
@@ -86,6 +87,7 @@ 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 createSubDistributorOrder = (data) => api.post('/api/dist/site/sub-distributor/pay', data);
|
||||
|
||||
// ===== Helpers =====
|
||||
export const quotaToDollar = (quota) => (quota / Q).toFixed(4);
|
||||
|
||||
@@ -93,6 +93,46 @@
|
||||
"dashboard.apiDocs": "API Docs",
|
||||
"dashboard.comingSoon": "Coming soon",
|
||||
|
||||
"subDist.nav": "Launch Sub-site",
|
||||
"subDist.dashboardEntry": "Pay once and manage your own sub-site",
|
||||
"subDist.badge": "Site Owner Plan",
|
||||
"subDist.title": "Launch Your Own Sub-site",
|
||||
"subDist.subtitle": "Pay through {{name}} to open your own sub-site. After payment, you can manage it from the main distributor console.",
|
||||
"subDist.defaultSiteName": "this site",
|
||||
"subDist.notAvailable": "Sub-site launch is currently unavailable",
|
||||
"subDist.disabledFallback": "Please contact the site owner to enable this feature",
|
||||
"subDist.loginHint": "Please log in or register on this site first. After successful payment, your account will be upgraded to a site owner.",
|
||||
"subDist.goLogin": "Log In",
|
||||
"subDist.goRegister": "Register",
|
||||
"subDist.siteName": "Site Name",
|
||||
"subDist.siteNamePlaceholder": "e.g. My AI Site",
|
||||
"subDist.siteSlug": "Site Slug",
|
||||
"subDist.siteSlugHelp": "This will become your site identifier and cannot be changed after payment.",
|
||||
"subDist.paymentMethod": "Payment Method",
|
||||
"subDist.chain": "Blockchain",
|
||||
"subDist.token": "Token",
|
||||
"subDist.payAndOpen": "Pay ¥{{price}} to Launch",
|
||||
"subDist.currentUserHint": "Current paying account: {{user}}, payment method: {{method}}",
|
||||
"subDist.manageHint": "After payment, go to the main distributor console to manage your new sub-site. If you are not logged in there yet, sign in with the same account.",
|
||||
"subDist.cryptoTitle": "Crypto Order Created",
|
||||
"subDist.cryptoHint": "Please transfer using the details below. Your sub-site will be activated automatically after confirmation.",
|
||||
"subDist.wallet": "Wallet",
|
||||
"subDist.amount": "Amount",
|
||||
"subDist.tradeNo": "Trade No",
|
||||
"subDist.priceCardTitle": "Pricing",
|
||||
"subDist.openPrice": "Launch Price",
|
||||
"subDist.platformBase": "Platform Base Cost",
|
||||
"subDist.parentSpread": "Owner Spread",
|
||||
"subDist.ruleTitle": "Profit Rules",
|
||||
"subDist.rule1": "You pay the launch price set by the current site owner. Any amount above the platform base cost is recorded as the parent site's one-time launch profit.",
|
||||
"subDist.rule2": "If the parent site owner has already purchased a higher discount tier and you have not, the discount spread will continue to accrue to the parent site. Current parent discount: {{discount}}%.",
|
||||
"subDist.rule3": "Once activated, your account is upgraded to a site owner and you can continue setup from the main distributor console.",
|
||||
"subDist.manageUrlLabel": "Main distributor console:",
|
||||
"subDist.loginRequired": "Please log in before launching a sub-site",
|
||||
"subDist.fillRequired": "Please fill in the site name, slug, and payment method",
|
||||
"subDist.selectPayment": "Please select a payment method",
|
||||
"subDist.createFailed": "Failed to create order",
|
||||
|
||||
"tokens.title": "API Keys",
|
||||
"tokens.subtitle": "Manage your API access tokens",
|
||||
"tokens.newKey": "+ New Key",
|
||||
|
||||
@@ -93,6 +93,46 @@
|
||||
"dashboard.apiDocs": "API 文档",
|
||||
"dashboard.comingSoon": "即将推出",
|
||||
|
||||
"subDist.nav": "开通分站",
|
||||
"subDist.dashboardEntry": "付费开通并管理自己的分站",
|
||||
"subDist.badge": "站长计划",
|
||||
"subDist.title": "开通你的下级分站",
|
||||
"subDist.subtitle": "基于 {{name}} 直接付费开通自己的分站。支付完成后,可前往主站分销后台管理和设置。",
|
||||
"subDist.defaultSiteName": "当前分站",
|
||||
"subDist.notAvailable": "当前暂未开放下级分站",
|
||||
"subDist.disabledFallback": "请联系站长开启该功能",
|
||||
"subDist.loginHint": "请先登录或注册当前分站账户,支付成功后会自动获得分站站长权限。",
|
||||
"subDist.goLogin": "去登录",
|
||||
"subDist.goRegister": "去注册",
|
||||
"subDist.siteName": "站点名称",
|
||||
"subDist.siteNamePlaceholder": "例如:我的 AI 分站",
|
||||
"subDist.siteSlug": "站点标识",
|
||||
"subDist.siteSlugHelp": "将作为您的专属分站标识,支付成功后不可修改。",
|
||||
"subDist.paymentMethod": "支付方式",
|
||||
"subDist.chain": "区块链",
|
||||
"subDist.token": "代币",
|
||||
"subDist.payAndOpen": "支付开通 ¥{{price}}",
|
||||
"subDist.currentUserHint": "当前支付账号:{{user}},支付方式:{{method}}",
|
||||
"subDist.manageHint": "支付完成后,请前往主站的分销后台管理您的新分站;如未登录主站,请使用同一账号重新登录即可。",
|
||||
"subDist.cryptoTitle": "加密货币订单已创建",
|
||||
"subDist.cryptoHint": "请按照以下信息转账,到账后系统会自动开通分站。",
|
||||
"subDist.wallet": "收款地址",
|
||||
"subDist.amount": "支付金额",
|
||||
"subDist.tradeNo": "订单号",
|
||||
"subDist.priceCardTitle": "费用说明",
|
||||
"subDist.openPrice": "当前开通价",
|
||||
"subDist.platformBase": "平台基础成本",
|
||||
"subDist.parentSpread": "站长开通差价",
|
||||
"subDist.ruleTitle": "收益规则",
|
||||
"subDist.rule1": "您支付的是当前分站站长设置的开通价;若高于平台基础成本,差价会直接记入上级站长收益。",
|
||||
"subDist.rule2": "后续如果上级站长已经买了更高折扣,而您还没买到同档折扣,这部分折扣差也会持续沉淀为上级收益。当前上级折扣:{{discount}}%。",
|
||||
"subDist.rule3": "开通成功后,您会升级为分站站长,可以进入主站分销后台继续配置域名、主题、价格和模型。",
|
||||
"subDist.manageUrlLabel": "主站分销后台:",
|
||||
"subDist.loginRequired": "请先登录后再开通分站",
|
||||
"subDist.fillRequired": "请填写站点名称、标识和支付方式",
|
||||
"subDist.selectPayment": "请选择支付方式",
|
||||
"subDist.createFailed": "创建订单失败",
|
||||
|
||||
"tokens.title": "API 密钥",
|
||||
"tokens.subtitle": "管理您的 API 访问令牌",
|
||||
"tokens.newKey": "+ 新建密钥",
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Link } from 'react-router-dom';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useAuth } from '../context/AuthContext';
|
||||
import { getUserUsage, redeemCode, getAffCode, transferAffQuota, getAffEarnings, Q, quotaToDollar } from '../api';
|
||||
import { useCurrency } from '../context/SiteContext';
|
||||
import { useCurrency, useSite } from '../context/SiteContext';
|
||||
import CountUp from '../components/bits/CountUp';
|
||||
import toast from 'react-hot-toast';
|
||||
|
||||
@@ -11,6 +11,7 @@ export default function Dashboard() {
|
||||
const { t } = useTranslation();
|
||||
const { user, refreshUser } = useAuth();
|
||||
const { symbol, rate } = useCurrency();
|
||||
const { site } = useSite();
|
||||
const [usage, setUsage] = useState(null);
|
||||
const [redeemInput, setRedeemInput] = useState('');
|
||||
const [redeeming, setRedeeming] = useState(false);
|
||||
@@ -175,6 +176,12 @@ export default function Dashboard() {
|
||||
<p className="text-sm font-medium text-page group-hover:text-page-link transition-colors">{t('dashboard.logs')}</p>
|
||||
<p className="text-xs text-page-muted">{t('dashboard.viewLogs')}</p>
|
||||
</Link>
|
||||
{site?.allow_sub_dist && (
|
||||
<Link to="/sub-site" className="glass-sm !rounded-xl px-4 py-3 hover:bg-page-surface-hover transition-colors group">
|
||||
<p className="text-sm font-medium text-page group-hover:text-page-link transition-colors">{t('subDist.nav')}</p>
|
||||
<p className="text-xs text-page-muted">{t('subDist.dashboardEntry')}</p>
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,283 @@
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import toast from 'react-hot-toast';
|
||||
import { createSubDistributorOrder, getSubDistributorInfo } from '../api';
|
||||
import { useAuth } from '../context/AuthContext';
|
||||
import { useSite } from '../context/SiteContext';
|
||||
|
||||
function submitEpayForm(resData) {
|
||||
const params = resData.data;
|
||||
const url = resData.url;
|
||||
if (!params || !url) return;
|
||||
const form = document.createElement('form');
|
||||
form.action = url;
|
||||
form.method = 'POST';
|
||||
const isSafari = navigator.userAgent.indexOf('Safari') > -1 && navigator.userAgent.indexOf('Chrome') < 1;
|
||||
if (!isSafari) {
|
||||
form.target = '_blank';
|
||||
}
|
||||
Object.keys(params).forEach((key) => {
|
||||
const input = document.createElement('input');
|
||||
input.type = 'hidden';
|
||||
input.name = key;
|
||||
input.value = params[key];
|
||||
form.appendChild(input);
|
||||
});
|
||||
document.body.appendChild(form);
|
||||
form.submit();
|
||||
document.body.removeChild(form);
|
||||
}
|
||||
|
||||
export default function SubDistributor() {
|
||||
const { t } = useTranslation();
|
||||
const { user } = useAuth();
|
||||
const { site } = useSite();
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [subInfo, setSubInfo] = useState(null);
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
const [cryptoOrder, setCryptoOrder] = useState(null);
|
||||
const [form, setForm] = useState({
|
||||
name: '',
|
||||
slug: '',
|
||||
payment_method: '',
|
||||
chain: 'tron',
|
||||
token: 'usdt',
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
getSubDistributorInfo()
|
||||
.then((res) => {
|
||||
if (res.data.success) {
|
||||
const info = res.data.data;
|
||||
setSubInfo(info);
|
||||
if (info.pay_methods?.length > 0) {
|
||||
setForm((prev) => ({ ...prev, payment_method: info.pay_methods[0].type }));
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(() => {})
|
||||
.finally(() => setLoading(false));
|
||||
}, []);
|
||||
|
||||
const paymentMethods = subInfo?.pay_methods || [];
|
||||
const currentPayMethod = useMemo(
|
||||
() => paymentMethods.find((item) => item.type === form.payment_method),
|
||||
[paymentMethods, form.payment_method]
|
||||
);
|
||||
|
||||
const handleSubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
if (!user) {
|
||||
toast.error(t('subDist.loginRequired'));
|
||||
return;
|
||||
}
|
||||
if (!form.name.trim() || !form.slug.trim()) {
|
||||
toast.error(t('subDist.fillRequired'));
|
||||
return;
|
||||
}
|
||||
if (!form.payment_method) {
|
||||
toast.error(t('subDist.selectPayment'));
|
||||
return;
|
||||
}
|
||||
|
||||
setSubmitting(true);
|
||||
setCryptoOrder(null);
|
||||
try {
|
||||
const payload = {
|
||||
name: form.name.trim(),
|
||||
slug: form.slug.trim().toLowerCase(),
|
||||
payment_method: form.payment_method,
|
||||
return_url: subInfo?.manage_url || '',
|
||||
};
|
||||
if (form.payment_method === 'crypto') {
|
||||
payload.chain = form.chain;
|
||||
payload.token = form.token;
|
||||
}
|
||||
const res = await createSubDistributorOrder(payload);
|
||||
if (res.data.message === 'success') {
|
||||
if (res.data.payment_type === 'stripe' && res.data.data?.pay_link) {
|
||||
window.open(res.data.data.pay_link, '_blank');
|
||||
} else if (res.data.payment_type === 'crypto') {
|
||||
setCryptoOrder(res.data.data);
|
||||
} else {
|
||||
submitEpayForm(res.data);
|
||||
}
|
||||
} else if (res.data.data) {
|
||||
toast.error(typeof res.data.data === 'string' ? res.data.data : t('subDist.createFailed'));
|
||||
}
|
||||
} catch (e) {
|
||||
// handled by interceptor
|
||||
}
|
||||
setSubmitting(false);
|
||||
};
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="min-h-[60vh] flex items-center justify-center">
|
||||
<div className="w-8 h-8 rounded-full border-2 border-white/20 border-t-white animate-spin" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="max-w-5xl mx-auto px-6 py-12">
|
||||
<div className="grid lg:grid-cols-[1.1fr_0.9fr] gap-6 items-start">
|
||||
<div className="glass rounded-3xl p-8">
|
||||
<div className="mb-8">
|
||||
<p className="text-sm text-page-link font-medium mb-3">{t('subDist.badge')}</p>
|
||||
<h1 className="text-3xl font-heading font-bold text-page mb-3">{t('subDist.title')}</h1>
|
||||
<p className="text-sm text-page-secondary leading-6">
|
||||
{t('subDist.subtitle', { name: site?.name || t('subDist.defaultSiteName') })}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{!subInfo?.enabled ? (
|
||||
<div className="rounded-2xl border border-red-500/20 bg-red-500/5 p-5">
|
||||
<p className="text-sm font-medium text-page mb-2">{t('subDist.notAvailable')}</p>
|
||||
<p className="text-sm text-page-secondary">{subInfo?.disabled_reason || t('subDist.disabledFallback')}</p>
|
||||
</div>
|
||||
) : !user ? (
|
||||
<div className="rounded-2xl border border-border p-5 space-y-4">
|
||||
<p className="text-sm text-page-secondary">{t('subDist.loginHint')}</p>
|
||||
<div className="flex flex-wrap gap-3">
|
||||
<Link to="/login" className="btn-primary">{t('subDist.goLogin')}</Link>
|
||||
<Link to="/register" className="btn-secondary">{t('subDist.goRegister')}</Link>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<form onSubmit={handleSubmit} className="space-y-5">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-page-label mb-2">{t('subDist.siteName')}</label>
|
||||
<input
|
||||
value={form.name}
|
||||
onChange={(e) => setForm({ ...form, name: e.target.value })}
|
||||
className="input"
|
||||
placeholder={t('subDist.siteNamePlaceholder')}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-page-label mb-2">{t('subDist.siteSlug')}</label>
|
||||
<input
|
||||
value={form.slug}
|
||||
onChange={(e) => setForm({ ...form, slug: e.target.value.toLowerCase() })}
|
||||
className="input"
|
||||
placeholder="my-sub-site"
|
||||
required
|
||||
/>
|
||||
<p className="text-xs text-page-muted mt-2">{t('subDist.siteSlugHelp')}</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-page-label mb-2">{t('subDist.paymentMethod')}</label>
|
||||
<div className="grid sm:grid-cols-2 gap-3">
|
||||
{paymentMethods.map((method) => (
|
||||
<label
|
||||
key={method.type}
|
||||
className={`rounded-2xl border px-4 py-3 cursor-pointer transition-colors ${
|
||||
form.payment_method === method.type
|
||||
? 'border-page-link bg-page-link/10'
|
||||
: 'border-border hover:bg-page-surface-hover'
|
||||
}`}
|
||||
>
|
||||
<input
|
||||
type="radio"
|
||||
className="sr-only"
|
||||
checked={form.payment_method === method.type}
|
||||
onChange={() => setForm({ ...form, payment_method: method.type })}
|
||||
/>
|
||||
<div className="text-sm font-medium text-page">{method.name || method.type}</div>
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{form.payment_method === 'crypto' && (
|
||||
<div className="grid sm:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-page-label mb-2">{t('subDist.chain')}</label>
|
||||
<select value={form.chain} onChange={(e) => setForm({ ...form, chain: e.target.value })} className="input">
|
||||
<option value="tron">TRON (TRC20)</option>
|
||||
<option value="eth">Ethereum (ERC20)</option>
|
||||
<option value="bsc">BSC (BEP20)</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-page-label mb-2">{t('subDist.token')}</label>
|
||||
<select value={form.token} onChange={(e) => setForm({ ...form, token: e.target.value })} className="input">
|
||||
<option value="usdt">USDT</option>
|
||||
<option value="usdc">USDC</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<button type="submit" disabled={submitting} className="btn-primary w-full justify-center flex items-center gap-2">
|
||||
{submitting && <span className="w-4 h-4 rounded-full border-2 border-white/30 border-t-white animate-spin" />}
|
||||
{t('subDist.payAndOpen', { price: Number(subInfo?.price || 0).toFixed(2) })}
|
||||
</button>
|
||||
|
||||
<p className="text-xs text-page-muted">
|
||||
{t('subDist.currentUserHint', { user: user.display_name || user.username || 'User', method: currentPayMethod?.name || form.payment_method })}
|
||||
</p>
|
||||
<p className="text-xs text-page-muted">
|
||||
{t('subDist.manageHint')}
|
||||
</p>
|
||||
</form>
|
||||
)}
|
||||
|
||||
{cryptoOrder && (
|
||||
<div className="mt-6 rounded-2xl border border-emerald-500/20 bg-emerald-500/5 p-5 space-y-2">
|
||||
<p className="text-sm font-medium text-page">{t('subDist.cryptoTitle')}</p>
|
||||
<p className="text-sm text-page-secondary">{t('subDist.cryptoHint')}</p>
|
||||
<div className="text-sm text-page">
|
||||
<div>{t('subDist.wallet')}: <span className="font-mono break-all">{cryptoOrder.wallet}</span></div>
|
||||
<div>{t('subDist.amount')}: <span className="font-mono">{cryptoOrder.amount} {cryptoOrder.token}</span></div>
|
||||
<div>{t('subDist.tradeNo')}: <span className="font-mono break-all">{cryptoOrder.trade_no}</span></div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="space-y-6">
|
||||
<div className="glass rounded-3xl p-6">
|
||||
<h2 className="text-lg font-semibold text-page mb-4">{t('subDist.priceCardTitle')}</h2>
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center justify-between text-sm">
|
||||
<span className="text-page-secondary">{t('subDist.openPrice')}</span>
|
||||
<span className="text-2xl font-bold text-page">¥{Number(subInfo?.price || 0).toFixed(2)}</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-between text-sm">
|
||||
<span className="text-page-secondary">{t('subDist.platformBase')}</span>
|
||||
<span className="text-page">¥{Number(subInfo?.base_cost || 0).toFixed(2)}</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-between text-sm">
|
||||
<span className="text-page-secondary">{t('subDist.parentSpread')}</span>
|
||||
<span className="text-page-link font-semibold">¥{Number(subInfo?.price_spread || 0).toFixed(2)}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="glass rounded-3xl p-6">
|
||||
<h2 className="text-lg font-semibold text-page mb-4">{t('subDist.ruleTitle')}</h2>
|
||||
<div className="space-y-3 text-sm text-page-secondary leading-6">
|
||||
<p>{t('subDist.rule1')}</p>
|
||||
<p>{t('subDist.rule2', { discount: Number(subInfo?.seller_discount || 0).toFixed(0) })}</p>
|
||||
<p>{t('subDist.rule3')}</p>
|
||||
{subInfo?.manage_url && (
|
||||
<p>
|
||||
{t('subDist.manageUrlLabel')}{' '}
|
||||
<a className="text-page-link hover:underline" href={subInfo.manage_url} target="_blank" rel="noreferrer">
|
||||
{subInfo.manage_url}
|
||||
</a>
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -19,6 +19,7 @@ export default function ClaudeLayout() {
|
||||
{ to: '/', label: t('nav.home'), auth: false },
|
||||
{ to: '/pricing', label: t('nav.pricing'), auth: false },
|
||||
{ to: '/packages', label: t('nav.packages'), auth: false },
|
||||
...(site?.allow_sub_dist ? [{ to: '/sub-site', label: t('subDist.nav'), auth: false }] : []),
|
||||
{ to: '/dashboard', label: t('nav.dashboard'), auth: true },
|
||||
{ to: '/tokens', label: t('nav.apiKeys'), auth: true },
|
||||
{ to: '/logs', label: t('nav.logs'), auth: true },
|
||||
|
||||
@@ -19,6 +19,7 @@ export default function CleanLayout() {
|
||||
{ to: '/', label: t('nav.home'), auth: false },
|
||||
{ to: '/pricing', label: t('nav.pricing'), auth: false },
|
||||
{ to: '/packages', label: t('nav.packages'), auth: false },
|
||||
...(site?.allow_sub_dist ? [{ to: '/sub-site', label: t('subDist.nav'), auth: false }] : []),
|
||||
{ to: '/dashboard', label: t('nav.dashboard'), auth: true },
|
||||
{ to: '/tokens', label: t('nav.apiKeys'), auth: true },
|
||||
{ to: '/logs', label: t('nav.logs'), auth: true },
|
||||
|
||||
@@ -19,6 +19,7 @@ export default function CorporateLayout() {
|
||||
{ to: '/', label: t('nav.home'), auth: false },
|
||||
{ to: '/pricing', label: t('nav.pricing'), auth: false },
|
||||
{ to: '/packages', label: t('nav.packages'), auth: false },
|
||||
...(site?.allow_sub_dist ? [{ to: '/sub-site', label: t('subDist.nav'), auth: false }] : []),
|
||||
{ to: '/dashboard', label: t('nav.dashboard'), auth: true },
|
||||
{ to: '/tokens', label: t('nav.apiKeys'), auth: true },
|
||||
{ to: '/logs', label: t('nav.logs'), auth: true },
|
||||
|
||||
@@ -19,6 +19,7 @@ export default function DarkLayout() {
|
||||
{ to: '/', label: t('nav.home'), auth: false },
|
||||
{ to: '/pricing', label: t('nav.pricing'), auth: false },
|
||||
{ to: '/packages', label: t('nav.packages'), auth: false },
|
||||
...(site?.allow_sub_dist ? [{ to: '/sub-site', label: t('subDist.nav'), auth: false }] : []),
|
||||
{ to: '/dashboard', label: t('nav.dashboard'), auth: true },
|
||||
{ to: '/tokens', label: t('nav.apiKeys'), auth: true },
|
||||
{ to: '/logs', label: t('nav.logs'), auth: true },
|
||||
|
||||
@@ -19,6 +19,7 @@ export default function DefaultLayout() {
|
||||
{ to: '/', label: t('nav.home'), auth: false },
|
||||
{ to: '/pricing', label: t('nav.pricing'), auth: false },
|
||||
{ to: '/packages', label: t('nav.packages'), auth: false },
|
||||
...(site?.allow_sub_dist ? [{ to: '/sub-site', label: t('subDist.nav'), auth: false }] : []),
|
||||
{ to: '/dashboard', label: t('nav.dashboard'), auth: true },
|
||||
{ to: '/tokens', label: t('nav.apiKeys'), auth: true },
|
||||
{ to: '/logs', label: t('nav.logs'), auth: true },
|
||||
|
||||
@@ -19,6 +19,7 @@ export default function MinimalLayout() {
|
||||
{ to: '/', label: t('nav.home'), auth: false },
|
||||
{ to: '/pricing', label: t('nav.pricing'), auth: false },
|
||||
{ to: '/packages', label: t('nav.packages'), auth: false },
|
||||
...(site?.allow_sub_dist ? [{ to: '/sub-site', label: t('subDist.nav'), auth: false }] : []),
|
||||
{ to: '/dashboard', label: t('nav.dashboard'), auth: true },
|
||||
{ to: '/tokens', label: t('nav.apiKeys'), auth: true },
|
||||
{ to: '/logs', label: t('nav.logs'), auth: true },
|
||||
|
||||
@@ -19,6 +19,7 @@ export default function StarterLayout() {
|
||||
{ to: '/', label: t('nav.home'), auth: false },
|
||||
{ to: '/pricing', label: t('nav.pricing'), auth: false },
|
||||
{ to: '/packages', label: t('nav.packages'), auth: false },
|
||||
...(site?.allow_sub_dist ? [{ to: '/sub-site', label: t('subDist.nav'), auth: false }] : []),
|
||||
{ to: '/dashboard', label: t('nav.dashboard'), auth: true },
|
||||
{ to: '/tokens', label: t('nav.apiKeys'), auth: true },
|
||||
{ to: '/logs', label: t('nav.logs'), auth: true },
|
||||
|
||||
Reference in new issue
Block a user