fix: 共享页面全面适配 light/claude 主题配色
问题:非黑色背景的主题下,共享页面中的灰色/黑色硬编码颜色不协调 修复方案: 1. 扩展 CSS 变量体系:新增 --page-surface, --page-surface-hover, --page-divider, --page-inset, --page-success/warning/danger/info 2. .theme-light 和 .theme-claude 各自定义对应的浅色值 3. Tailwind 新增 text-page-success/warning/danger/info 语义色 4. 新增 CSS 工具类: bg-page-surface, hover:bg-page-surface-hover, border-page-divider, bg-page-inset 修改文件: - index.css: 扩展 CSS 变量 + 工具类 - tailwind.config.js: 新增语义色 token - Dashboard: SpotlightCard → 简单 glass 卡片,hover 色修复 - Pricing: border-white/hover-white → 主题感知类 - Tokens: bg-black/bg-neutral-800 → bg-page-inset/surface - Topup: SpotlightCard → glass,hover/text 色修复 - ConfigExporter: 下拉菜单/代码预览区全面适配 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
1 parent
3b8206284f
commit
b34e81bb58
7 files changed
+123
-61
No files matched your search
@@ -258,7 +258,7 @@ print(message.content[0].text)`;
|
||||
return (
|
||||
<div className="glass rounded-2xl overflow-hidden">
|
||||
{/* Header */}
|
||||
<div className="px-5 py-4 border-b border-white/5">
|
||||
<div className="px-5 py-4 border-b border-page-divider">
|
||||
<h4 className="font-semibold text-sm text-page flex items-center gap-2">
|
||||
<svg className="w-4 h-4 text-brand-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
|
||||
@@ -316,14 +316,14 @@ print(message.content[0].text)`;
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
|
||||
</svg>
|
||||
{modelDropdownOpen && (
|
||||
<div className="absolute z-50 mt-1 w-full max-h-60 overflow-y-auto rounded-lg border border-white/10 bg-neutral-900 shadow-xl">
|
||||
<div className="absolute z-50 mt-1 w-full max-h-60 overflow-y-auto rounded-lg glass shadow-xl">
|
||||
{availableModels
|
||||
.filter(m => m.toLowerCase().includes(modelSearch.toLowerCase()))
|
||||
.map((model) => (
|
||||
<button
|
||||
key={model}
|
||||
onClick={() => { setSelectedModel(model); setModelDropdownOpen(false); setModelSearch(''); }}
|
||||
className={`w-full text-left px-3 py-2 text-sm transition-colors hover:bg-white/10 ${
|
||||
className={`w-full text-left px-3 py-2 text-sm transition-colors hover:bg-page-surface-hover ${
|
||||
model === selectedModel ? 'text-brand-400 bg-brand-500/10' : 'text-page-secondary'
|
||||
}`}
|
||||
>
|
||||
@@ -351,7 +351,7 @@ print(message.content[0].text)`;
|
||||
className={`px-3 py-1.5 rounded-lg text-xs font-medium transition-all ${
|
||||
selectedTool === tool.id
|
||||
? 'bg-brand-500 text-white shadow-sm'
|
||||
: 'bg-white/5 text-page-secondary hover:bg-white/10'
|
||||
: 'bg-page-surface text-page-secondary hover:bg-page-surface-hover'
|
||||
}`}
|
||||
>
|
||||
{tool.name}
|
||||
@@ -362,8 +362,8 @@ print(message.content[0].text)`;
|
||||
</div>
|
||||
|
||||
{/* Config Preview */}
|
||||
<div className="border-t border-white/5">
|
||||
<div className="flex items-center justify-between px-4 py-2.5 bg-black/20">
|
||||
<div className="border-t border-page-divider">
|
||||
<div className="flex items-center justify-between px-4 py-2.5 bg-page-inset">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="flex gap-1.5">
|
||||
<span className="w-2.5 h-2.5 rounded-full bg-red-400" />
|
||||
@@ -377,25 +377,25 @@ print(message.content[0].text)`;
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
onClick={handleCopy}
|
||||
className="p-1.5 rounded-md hover:bg-white/10 transition-colors text-page-muted hover:text-page"
|
||||
className="p-1.5 rounded-md hover:bg-page-surface-hover transition-colors text-page-muted hover:text-page"
|
||||
title={t('config.copy')}
|
||||
>
|
||||
{copied ? (
|
||||
<svg className="w-3.5 h-3.5 text-green-400" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" /></svg>
|
||||
<svg className="w-3.5 h-3.5 text-page-success" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" /></svg>
|
||||
) : (
|
||||
<svg className="w-3.5 h-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z" /></svg>
|
||||
)}
|
||||
</button>
|
||||
<button
|
||||
onClick={handleDownload}
|
||||
className="p-1.5 rounded-md hover:bg-white/10 transition-colors text-page-muted hover:text-page"
|
||||
className="p-1.5 rounded-md hover:bg-page-surface-hover transition-colors text-page-muted hover:text-page"
|
||||
title={t('config.download')}
|
||||
>
|
||||
<svg className="w-3.5 h-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<pre className="p-4 text-xs leading-relaxed overflow-x-auto max-h-64 font-mono text-green-400">
|
||||
<pre className="p-4 text-xs leading-relaxed overflow-x-auto max-h-64 font-mono text-page-success">
|
||||
<code>{config}</code>
|
||||
</pre>
|
||||
</div>
|
||||
|
||||
+66
-3
@@ -5,16 +5,30 @@
|
||||
@layer base {
|
||||
:root {
|
||||
--brand: 99 102 241;
|
||||
/* Light theme text colors (overridden by .theme-light) */
|
||||
/* Text colors */
|
||||
--page-text: 255 255 255;
|
||||
--page-text-secondary: 163 163 163;
|
||||
--page-text-muted: 115 115 115;
|
||||
--page-label: 212 212 212;
|
||||
--page-link: 129 140 248;
|
||||
/* Surface colors */
|
||||
--page-card-bg: rgba(255,255,255,0.03);
|
||||
--page-card-border: rgba(255,255,255,0.06);
|
||||
--page-input-bg: rgba(255,255,255,0.04);
|
||||
--page-input-border: rgba(255,255,255,0.1);
|
||||
--page-link: 129 140 248;
|
||||
--page-surface: rgba(255,255,255,0.04);
|
||||
--page-surface-hover: rgba(255,255,255,0.08);
|
||||
--page-divider: rgba(255,255,255,0.06);
|
||||
--page-inset: rgba(0,0,0,0.4);
|
||||
/* Semantic colors — dark mode defaults */
|
||||
--page-success: 74 222 128;
|
||||
--page-success-text: 74 222 128;
|
||||
--page-warning: 250 204 21;
|
||||
--page-warning-text: 253 224 71;
|
||||
--page-danger: 248 113 113;
|
||||
--page-danger-text: 248 113 113;
|
||||
--page-info: 192 132 252;
|
||||
--page-info-text: 192 132 252;
|
||||
}
|
||||
|
||||
body {
|
||||
@@ -46,15 +60,33 @@
|
||||
--page-text-secondary: 107 114 128;
|
||||
--page-text-muted: 156 163 175;
|
||||
--page-label: 55 65 81;
|
||||
--page-link: 79 70 229;
|
||||
--page-card-bg: rgba(255,255,255,0.85);
|
||||
--page-card-border: rgb(229,231,235);
|
||||
--page-input-bg: #fff;
|
||||
--page-input-border: rgb(209,213,219);
|
||||
--page-link: 79 70 229;
|
||||
--page-surface: rgba(0,0,0,0.03);
|
||||
--page-surface-hover: rgba(0,0,0,0.06);
|
||||
--page-divider: rgb(229,231,235);
|
||||
--page-inset: rgba(0,0,0,0.04);
|
||||
--page-success: 22 163 74;
|
||||
--page-success-text: 22 163 74;
|
||||
--page-warning: 202 138 4;
|
||||
--page-warning-text: 161 98 7;
|
||||
--page-danger: 220 38 38;
|
||||
--page-danger-text: 220 38 38;
|
||||
--page-info: 124 58 237;
|
||||
--page-info-text: 124 58 237;
|
||||
}
|
||||
|
||||
.theme-claude {
|
||||
--page-link: 217 119 87;
|
||||
--page-card-bg: rgba(255,255,255,0.7);
|
||||
--page-card-border: #E8DDD0;
|
||||
--page-surface: rgba(61,48,36,0.04);
|
||||
--page-surface-hover: rgba(61,48,36,0.08);
|
||||
--page-divider: #E8DDD0;
|
||||
--page-inset: rgba(61,48,36,0.06);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -182,3 +214,34 @@
|
||||
.theme-claude .btn-secondary:hover {
|
||||
background-color: rgba(217,119,87,0.05);
|
||||
}
|
||||
|
||||
/* ═══════════════════════════════════════════════
|
||||
Theme-aware utility classes for surfaces/dividers
|
||||
═══════════════════════════════════════════════ */
|
||||
.surface {
|
||||
background: var(--page-surface);
|
||||
}
|
||||
.surface-hover:hover {
|
||||
background: var(--page-surface-hover);
|
||||
}
|
||||
.bg-page-inset {
|
||||
background: var(--page-inset);
|
||||
}
|
||||
.divide-page > * + * {
|
||||
border-color: var(--page-divider);
|
||||
}
|
||||
.border-page-divider {
|
||||
border-color: var(--page-divider);
|
||||
}
|
||||
.bg-page-surface {
|
||||
background: var(--page-surface);
|
||||
}
|
||||
.bg-page-surface-hover {
|
||||
background: var(--page-surface-hover);
|
||||
}
|
||||
.hover\:bg-page-surface:hover {
|
||||
background: var(--page-surface);
|
||||
}
|
||||
.hover\:bg-page-surface-hover:hover {
|
||||
background: var(--page-surface-hover);
|
||||
}
|
||||
+10
-14
@@ -3,9 +3,7 @@ import { Link } from 'react-router-dom';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useAuth } from '../context/AuthContext';
|
||||
import { getUserUsage, redeemCode, Q, quotaToDollar } from '../api';
|
||||
import SpotlightCard from '../components/bits/SpotlightCard';
|
||||
import CountUp from '../components/bits/CountUp';
|
||||
import ShinyText from '../components/bits/ShinyText';
|
||||
import toast from 'react-hot-toast';
|
||||
|
||||
export default function Dashboard() {
|
||||
@@ -33,14 +31,12 @@ export default function Dashboard() {
|
||||
if (res.data.success) {
|
||||
toast.success(t('dashboard.redeemSuccess'));
|
||||
setRedeemInput('');
|
||||
// Refresh both usage AND user data to update balance
|
||||
await Promise.all([loadUsage(), refreshUser()]);
|
||||
}
|
||||
} catch (err) { /* interceptor */ }
|
||||
setRedeeming(false);
|
||||
};
|
||||
|
||||
// Use usage data (fresh from API) for balance/used — NOT stale user object
|
||||
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;
|
||||
@@ -51,35 +47,35 @@ export default function Dashboard() {
|
||||
{/* Welcome */}
|
||||
<div className="mb-8">
|
||||
<h1 className="text-2xl font-heading font-bold text-page mb-1">
|
||||
{t('dashboard.welcome')} <ShinyText text={user?.display_name || user?.username || 'User'} className="!inline" speed={3} color="#a5b4fc" shineColor="#e0e7ff" />
|
||||
{t('dashboard.welcome')} {user?.display_name || user?.username || 'User'}
|
||||
</h1>
|
||||
<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">
|
||||
<SpotlightCard className="!bg-neutral-900/60 !border-neutral-800/60 !p-6" spotlightColor="rgba(34,197,94,0.15)">
|
||||
<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">
|
||||
$<CountUp from={0} to={balanceDollars} duration={1.5} />
|
||||
</div>
|
||||
<p className="text-xs text-page-muted mt-1">{t('dashboard.quotaUnits', { count: quota.toLocaleString() })}</p>
|
||||
</SpotlightCard>
|
||||
</div>
|
||||
|
||||
<SpotlightCard className="!bg-neutral-900/60 !border-neutral-800/60 !p-6" spotlightColor="rgba(129,140,248,0.15)">
|
||||
<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">
|
||||
$<CountUp from={0} to={usedQuota / Q} duration={1.5} />
|
||||
</div>
|
||||
<p className="text-xs text-page-muted mt-1">{t('dashboard.quotaUnits', { count: usedQuota.toLocaleString() })}</p>
|
||||
</SpotlightCard>
|
||||
</div>
|
||||
|
||||
<SpotlightCard className="!bg-neutral-900/60 !border-neutral-800/60 !p-6" spotlightColor="rgba(244,114,182,0.15)">
|
||||
<div className="glass rounded-2xl p-6">
|
||||
<p className="text-sm text-page-secondary mb-2">{t('dashboard.totalRequests')}</p>
|
||||
<div className="text-3xl font-bold text-page">
|
||||
<CountUp from={0} to={requestCount} duration={1.5} />
|
||||
</div>
|
||||
</SpotlightCard>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Quick Actions */}
|
||||
@@ -105,15 +101,15 @@ export default function Dashboard() {
|
||||
<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">
|
||||
<Link to="/tokens" className="glass-sm !rounded-xl px-4 py-3 hover:bg-white/[0.06] transition-colors group">
|
||||
<Link to="/tokens" 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('dashboard.apiKeys')}</p>
|
||||
<p className="text-xs text-page-muted">{t('dashboard.manageKeys')}</p>
|
||||
</Link>
|
||||
<Link to="/packages" className="glass-sm !rounded-xl px-4 py-3 hover:bg-white/[0.06] transition-colors group">
|
||||
<Link to="/packages" 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('dashboard.packages')}</p>
|
||||
<p className="text-xs text-page-muted">{t('dashboard.viewPlans')}</p>
|
||||
</Link>
|
||||
<Link to="/pricing" className="glass-sm !rounded-xl px-4 py-3 hover:bg-white/[0.06] transition-colors group">
|
||||
<Link to="/pricing" 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('dashboard.pricing')}</p>
|
||||
<p className="text-xs text-page-muted">{t('dashboard.modelPrices')}</p>
|
||||
</Link>
|
||||
|
||||
@@ -73,7 +73,7 @@ export default function Pricing() {
|
||||
className={`inline-flex items-center gap-1.5 px-3.5 py-2 rounded-xl text-sm font-medium transition-all ${
|
||||
!vendor
|
||||
? 'bg-brand-500 text-white shadow-lg shadow-brand-500/25'
|
||||
: 'glass-sm text-page-secondary hover:text-page hover:bg-white/[0.08]'
|
||||
: 'glass-sm text-page-secondary hover:text-page hover:bg-page-surface-hover'
|
||||
}`}
|
||||
>
|
||||
{t('pricing.allVendors')}
|
||||
@@ -85,7 +85,7 @@ export default function Pricing() {
|
||||
className={`inline-flex items-center gap-1.5 px-3.5 py-2 rounded-xl text-sm font-medium transition-all ${
|
||||
vendor === v.name
|
||||
? 'bg-brand-500 text-white shadow-lg shadow-brand-500/25'
|
||||
: 'glass-sm text-page-secondary hover:text-page hover:bg-white/[0.08]'
|
||||
: 'glass-sm text-page-secondary hover:text-page hover:bg-page-surface-hover'
|
||||
}`}
|
||||
>
|
||||
{v.name}
|
||||
@@ -118,7 +118,7 @@ export default function Pricing() {
|
||||
<div className="glass-sm rounded-xl overflow-hidden">
|
||||
<table className="w-full text-sm">
|
||||
<thead>
|
||||
<tr className="border-b border-white/[0.06]">
|
||||
<tr className="border-b border-page-divider">
|
||||
<th className="text-left px-5 py-3.5 font-medium text-page-secondary">{t('pricing.model')}</th>
|
||||
<th className="text-right px-5 py-3.5 font-medium text-page-secondary">{t('pricing.inputPrice')}</th>
|
||||
<th className="text-right px-5 py-3.5 font-medium text-page-secondary">{t('pricing.outputPrice')}</th>
|
||||
@@ -127,7 +127,7 @@ export default function Pricing() {
|
||||
</thead>
|
||||
<tbody>
|
||||
{filtered.map((m, i) => (
|
||||
<tr key={m.model_name || i} className="border-b border-white/[0.04] last:border-0 hover:bg-white/[0.02] transition-colors">
|
||||
<tr key={m.model_name || i} className="border-b border-page-divider last:border-0 hover:bg-page-surface transition-colors">
|
||||
<td className="px-5 py-3.5">
|
||||
<span className="font-mono text-page">{m.display_name || m.model_name}</span>
|
||||
</td>
|
||||
@@ -140,8 +140,8 @@ export default function Pricing() {
|
||||
<td className="px-5 py-3.5 text-center">
|
||||
<span className={`inline-flex items-center gap-1.5 px-2 py-0.5 rounded-full text-xs border ${
|
||||
m.status === 'healthy'
|
||||
? 'bg-green-500/10 text-green-400 border-green-500/20'
|
||||
: 'bg-neutral-500/10 text-page-secondary border-neutral-500/20'
|
||||
? 'bg-green-500/10 text-page-success border-green-500/20'
|
||||
: 'bg-page-surface text-page-secondary border-page-divider'
|
||||
}`}>
|
||||
<span className={`w-1.5 h-1.5 rounded-full ${m.status === 'healthy' ? 'bg-green-500' : 'bg-neutral-500'}`} />
|
||||
{m.status === 'healthy' ? t('pricing.online') : t('pricing.unknown')}
|
||||
|
||||
+11
-11
@@ -149,12 +149,12 @@ export default function Tokens() {
|
||||
<div className="glass rounded-2xl p-6 w-full max-w-lg">
|
||||
<h2 className="text-lg font-semibold text-page mb-2">{t('tokens.newApiKey')}</h2>
|
||||
<div className="bg-yellow-500/10 border border-yellow-500/20 rounded-xl p-3 mb-4">
|
||||
<p className="text-sm text-yellow-300">
|
||||
<p className="text-sm text-page-warning">
|
||||
{t('tokens.keyWarning')}
|
||||
</p>
|
||||
</div>
|
||||
<div className="bg-black/40 rounded-xl p-4 flex items-center gap-3">
|
||||
<code className="text-sm font-mono text-green-400 flex-1 break-all select-all">
|
||||
<div className="bg-page-inset rounded-xl p-4 flex items-center gap-3">
|
||||
<code className="text-sm font-mono text-page-success flex-1 break-all select-all">
|
||||
{newKey}
|
||||
</code>
|
||||
<button
|
||||
@@ -194,8 +194,8 @@ export default function Tokens() {
|
||||
{/* Token List */}
|
||||
{tokens.length === 0 ? (
|
||||
<div className="glass rounded-2xl p-12 text-center">
|
||||
<div className="w-16 h-16 mx-auto mb-4 rounded-2xl bg-neutral-800/50 flex items-center justify-center">
|
||||
<svg className="w-8 h-8 text-neutral-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<div className="w-16 h-16 mx-auto mb-4 rounded-2xl bg-page-surface flex items-center justify-center">
|
||||
<svg className="w-8 h-8 text-page-muted" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M15 7a2 2 0 012 2m4 0a6 6 0 01-7.743 5.743L11 17H9v2H7v2H4a1 1 0 01-1-1v-2.586a1 1 0 01.293-.707l5.964-5.964A6 6 0 1121 9z" />
|
||||
</svg>
|
||||
</div>
|
||||
@@ -210,7 +210,7 @@ export default function Tokens() {
|
||||
<div key={token.id} className="glass-sm rounded-xl p-5">
|
||||
<div className="flex items-center gap-4">
|
||||
{/* Status dot */}
|
||||
<div className={`w-2.5 h-2.5 rounded-full flex-shrink-0 ${token.status === 1 ? 'bg-green-500' : 'bg-neutral-600'}`} />
|
||||
<div className={`w-2.5 h-2.5 rounded-full flex-shrink-0 ${token.status === 1 ? 'bg-green-500' : 'bg-page-muted'}`} />
|
||||
|
||||
{/* Info */}
|
||||
<div className="flex-1 min-w-0">
|
||||
@@ -228,15 +228,15 @@ export default function Tokens() {
|
||||
onClick={() => handleToggle(token)}
|
||||
className={`px-3 py-1 text-xs rounded-lg border transition-colors ${
|
||||
token.status === 1
|
||||
? 'border-green-500/30 text-green-400 hover:bg-green-500/10'
|
||||
: 'border-neutral-600 text-page-secondary hover:bg-neutral-800'
|
||||
? 'border-green-500/30 text-page-success hover:bg-green-500/10'
|
||||
: 'border-page-divider text-page-secondary hover:bg-page-surface-hover'
|
||||
}`}
|
||||
>
|
||||
{token.status === 1 ? t('tokens.enabled') : t('tokens.disabled')}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setDeleteConfirm(token)}
|
||||
className="px-3 py-1 text-xs rounded-lg border border-red-500/20 text-red-400 hover:bg-red-500/10 transition-colors"
|
||||
className="px-3 py-1 text-xs rounded-lg border border-red-500/20 text-page-danger hover:bg-red-500/10 transition-colors"
|
||||
>
|
||||
{t('tokens.delete')}
|
||||
</button>
|
||||
@@ -245,13 +245,13 @@ export default function Tokens() {
|
||||
|
||||
{/* Key row — always visible with copy button */}
|
||||
{token.key && (
|
||||
<div className="mt-3 flex items-center gap-2 bg-black/30 rounded-lg px-3 py-2">
|
||||
<div className="mt-3 flex items-center gap-2 bg-page-inset rounded-lg px-3 py-2">
|
||||
<code className="text-xs font-mono text-page-muted flex-1 break-all select-all">
|
||||
sk-{token.key}
|
||||
</code>
|
||||
<button
|
||||
onClick={() => handleCopy('sk-' + token.key)}
|
||||
className="flex-shrink-0 px-2.5 py-1 text-xs rounded-md bg-white/10 text-page-secondary hover:bg-white/20 hover:text-page transition-colors"
|
||||
className="flex-shrink-0 px-2.5 py-1 text-xs rounded-md bg-page-surface text-page-secondary hover:bg-page-surface-hover hover:text-page transition-colors"
|
||||
>
|
||||
{copiedId === 'sk-' + token.key ? t('tokens.copied') : t('tokens.copy')}
|
||||
</button>
|
||||
|
||||
+16
-17
@@ -8,7 +8,6 @@ import {
|
||||
createCryptoOrder, getCryptoOrderStatus, getTopupHistory,
|
||||
Q, quotaToDollar,
|
||||
} from '../api';
|
||||
import SpotlightCard from '../components/bits/SpotlightCard';
|
||||
import CountUp from '../components/bits/CountUp';
|
||||
import toast from 'react-hot-toast';
|
||||
|
||||
@@ -322,27 +321,27 @@ export default function Topup() {
|
||||
|
||||
{/* Balance Stats */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-6 mb-10">
|
||||
<SpotlightCard className="!bg-neutral-900/60 !border-neutral-800/60 !p-6" spotlightColor="rgba(34,197,94,0.15)">
|
||||
<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">
|
||||
$<CountUp from={0} to={balanceDollars} duration={1.5} />
|
||||
</div>
|
||||
<p className="text-xs text-page-muted mt-1">{t('dashboard.quotaUnits', { count: quota.toLocaleString() })}</p>
|
||||
</SpotlightCard>
|
||||
</div>
|
||||
|
||||
<SpotlightCard className="!bg-neutral-900/60 !border-neutral-800/60 !p-6" spotlightColor="rgba(129,140,248,0.15)">
|
||||
<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">
|
||||
$<CountUp from={0} to={usedQuota / Q} duration={1.5} />
|
||||
</div>
|
||||
</SpotlightCard>
|
||||
</div>
|
||||
|
||||
<SpotlightCard className="!bg-neutral-900/60 !border-neutral-800/60 !p-6" spotlightColor="rgba(244,114,182,0.15)">
|
||||
<div className="glass rounded-2xl p-6">
|
||||
<p className="text-sm text-page-secondary mb-2">{t('dashboard.totalRequests')}</p>
|
||||
<div className="text-3xl font-bold text-page">
|
||||
<CountUp from={0} to={requestCount} duration={1.5} />
|
||||
</div>
|
||||
</SpotlightCard>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Online Topup - EPay & Stripe */}
|
||||
@@ -369,7 +368,7 @@ export default function Topup() {
|
||||
className={`py-2.5 px-3 rounded-xl text-sm font-medium transition-all ${
|
||||
selectedPreset === val
|
||||
? 'bg-brand-500 text-white shadow-lg shadow-brand-500/25'
|
||||
: 'glass-sm text-page-label hover:text-page hover:bg-white/[0.08]'
|
||||
: 'glass-sm text-page-label hover:text-page hover:bg-page-surface-hover'
|
||||
}`}
|
||||
>
|
||||
${val}
|
||||
@@ -399,7 +398,7 @@ export default function Topup() {
|
||||
<p className="text-xs text-page-muted mt-2">{t('topup.calculating')}</p>
|
||||
) : payAmount ? (
|
||||
<p className="text-xs text-page-secondary mt-2">
|
||||
{t('topup.payAmountLabel')}: <span className="text-green-400 font-medium">¥{payAmount}</span>
|
||||
{t('topup.payAmountLabel')}: <span className="text-page-success font-medium">¥{payAmount}</span>
|
||||
</p>
|
||||
) : null}
|
||||
</div>
|
||||
@@ -415,7 +414,7 @@ export default function Topup() {
|
||||
key={method.type}
|
||||
onClick={() => handlePay(method.type)}
|
||||
disabled={paymentLoading || !amount}
|
||||
className="px-4 py-2.5 rounded-xl text-sm font-medium glass-sm text-page-label hover:text-page hover:bg-white/[0.08] disabled:opacity-40 disabled:cursor-not-allowed transition-all"
|
||||
className="px-4 py-2.5 rounded-xl text-sm font-medium glass-sm text-page-label hover:text-page hover:bg-page-surface-hover disabled:opacity-40 disabled:cursor-not-allowed transition-all"
|
||||
>
|
||||
{isCurrentLoading ? t('topup.processing') : method.name}
|
||||
</button>
|
||||
@@ -437,12 +436,12 @@ export default function Topup() {
|
||||
className={`glass-sm rounded-xl p-4 cursor-pointer transition-all ${
|
||||
selectedCreemProduct?.productId === product.productId
|
||||
? 'ring-2 ring-brand-500 bg-brand-500/10'
|
||||
: 'hover:bg-white/[0.06]'
|
||||
: 'hover:bg-page-surface'
|
||||
}`}
|
||||
onClick={() => setSelectedCreemProduct(product)}
|
||||
>
|
||||
<h3 className="text-sm font-semibold text-page mb-1">{product.name}</h3>
|
||||
<p className="text-xl font-bold text-green-400 mb-1">
|
||||
<p className="text-xl font-bold text-page-success mb-1">
|
||||
${product.price} <span className="text-xs text-page-muted font-normal">{product.currency || 'USD'}</span>
|
||||
</p>
|
||||
{product.quota && (
|
||||
@@ -499,7 +498,7 @@ export default function Topup() {
|
||||
className={`px-4 py-2 rounded-xl text-sm font-medium transition-all ${
|
||||
selectedChain === chain.key
|
||||
? 'bg-brand-500 text-white shadow-lg shadow-brand-500/25'
|
||||
: 'glass-sm text-page-label hover:text-page hover:bg-white/[0.08]'
|
||||
: 'glass-sm text-page-label hover:text-page hover:bg-page-surface-hover'
|
||||
}`}
|
||||
>
|
||||
{chain.label}
|
||||
@@ -519,7 +518,7 @@ export default function Topup() {
|
||||
className={`px-4 py-2 rounded-xl text-sm font-medium transition-all ${
|
||||
selectedToken === token
|
||||
? 'bg-brand-500 text-white shadow-lg shadow-brand-500/25'
|
||||
: 'glass-sm text-page-label hover:text-page hover:bg-white/[0.08]'
|
||||
: 'glass-sm text-page-label hover:text-page hover:bg-page-surface-hover'
|
||||
}`}
|
||||
>
|
||||
{token.toUpperCase()}
|
||||
@@ -599,10 +598,10 @@ export default function Topup() {
|
||||
</div>
|
||||
<span className={`text-xs px-2 py-1 rounded-full ${
|
||||
item.status === 'success'
|
||||
? 'bg-green-500/10 text-green-400'
|
||||
? 'bg-green-500/10 text-page-success'
|
||||
: item.status === 'pending'
|
||||
? 'bg-yellow-500/10 text-yellow-400'
|
||||
: 'bg-red-500/10 text-red-400'
|
||||
? 'bg-yellow-500/10 text-page-warning'
|
||||
: 'bg-red-500/10 text-page-danger'
|
||||
}`}>
|
||||
{item.status === 'success' ? t('topup.statusSuccess') : item.status === 'pending' ? t('topup.statusPending') : t('topup.statusFailed')}
|
||||
</span>
|
||||
|
||||
@@ -37,6 +37,10 @@ export default {
|
||||
muted: 'rgb(var(--page-text-muted) / <alpha-value>)',
|
||||
label: 'rgb(var(--page-label) / <alpha-value>)',
|
||||
link: 'rgb(var(--page-link) / <alpha-value>)',
|
||||
success: 'rgb(var(--page-success-text) / <alpha-value>)',
|
||||
warning: 'rgb(var(--page-warning-text) / <alpha-value>)',
|
||||
danger: 'rgb(var(--page-danger-text) / <alpha-value>)',
|
||||
info: 'rgb(var(--page-info-text) / <alpha-value>)',
|
||||
},
|
||||
},
|
||||
animation: {
|
||||
|
||||
Reference in new issue
Block a user