feat: Warm Industrial UI redesign + social footer + code deduplication

Design System (Phase A):
- New palette: warm charcoal #111110 / amber gold #c9954a / sage green #7a9a8a
- Fonts: Cabinet Grotesk (heading) + Geist (body) + JetBrains Mono (data)
- SVG grain texture overlay, collapsed sidebar with hover expand
- Bento grid dashboard with WideContinueCard + PosterCard + QuickLink
- Backward-compatible CSS aliases for glass-panel/neon-button/input-base

Social Footer (Phase B):
- AppFooter component with GitHub repo / author homepage / TG group links
- Embedded in LoginPage, Layout (global), and SettingsPage
- TG group badge added to README.md

Code Cleanup:
- Removed deprecated DownloadClientCard / NotifyChannelCard components
- Merged downloadClient/notify API clients into unified modules
- Route deduplication: removed authed-group duplicates for admin endpoints
- Removed redundant AdminPage tabs (sites/downloads/notify/scheduler)
- UI fixes: blank pages (discover/download-clients/notify-channels) nil slice defense
- Email channel support in NotifyChannelsPage
@
This commit is contained in:
ShukeBta
2026-05-17 23:13:26 +08:00
parent 9be81222cc
commit 828ca36d7f
18 changed files with 732 additions and 910 deletions
+77
View File
@@ -125,6 +125,7 @@ const TYPE_LABELS: Record<NotifyChannel['channel_type'], string> = {
wechat: '企业微信',
bark: 'Bark',
webhook: 'Webhook',
email: 'Email',
}
function ChannelCard({
@@ -188,6 +189,8 @@ function channelSummary(ch: NotifyChannel): string {
return `Device ${String(cfg.device_key ?? '').slice(0, 10)}…`
case 'webhook':
return `${cfg.method ?? 'POST'} ${cfg.url ?? ''}`
case 'email':
return `SMTP ${cfg.smtp_host ?? '-'}:${cfg.smtp_port ?? '-'} → ${cfg.to ?? '-'}`
default:
return ''
}
@@ -200,6 +203,7 @@ const EMPTY_CONFIG: Record<NotifyChannel['channel_type'], Record<string, string>
wechat: { sendkey: '' },
bark: { device_key: '', server: '' },
webhook: { url: '', method: 'POST', headers: '', body_template: '' },
email: { smtp_host: '', smtp_port: '465', username: '', password: '', from: '', to: '', tls: 'true' },
}
function ChannelFormModal({
@@ -282,6 +286,7 @@ function ChannelFormModal({
<option value="wechat">企业微信 / Server酱</option>
<option value="bark">Bark (iOS)</option>
<option value="webhook">Webhook</option>
<option value="email">Email (SMTP)</option>
</select>
</Field>
@@ -383,6 +388,78 @@ function ChannelFormModal({
</>
)}
{type === 'email' && (
<>
<Field label="SMTP 地址">
<input
required
className="input-base"
placeholder="smtp.gmail.com"
value={config.smtp_host ?? ''}
onChange={(e) => updateConfig('smtp_host', e.target.value)}
/>
</Field>
<div className="grid grid-cols-2 gap-3">
<Field label="SMTP 端口">
<input
required
className="input-base"
placeholder="465"
value={config.smtp_port ?? ''}
onChange={(e) => updateConfig('smtp_port', e.target.value)}
/>
</Field>
<Field label="TLS">
<select
className="input-base"
value={config.tls ?? 'true'}
onChange={(e) => updateConfig('tls', e.target.value)}
>
<option value="true">启用</option>
<option value="false">关闭</option>
</select>
</Field>
</div>
<div className="grid grid-cols-2 gap-3">
<Field label="用户名">
<input
required
className="input-base"
value={config.username ?? ''}
onChange={(e) => updateConfig('username', e.target.value)}
/>
</Field>
<Field label="密码">
<input
type="password"
required
className="input-base"
value={config.password ?? ''}
onChange={(e) => updateConfig('password', e.target.value)}
/>
</Field>
</div>
<Field label="发件人">
<input
required
className="input-base"
placeholder="noreply@example.com"
value={config.from ?? ''}
onChange={(e) => updateConfig('from', e.target.value)}
/>
</Field>
<Field label="收件人 (多个用逗号分隔)">
<input
required
className="input-base"
placeholder="user@example.com"
value={config.to ?? ''}
onChange={(e) => updateConfig('to', e.target.value)}
/>
</Field>
</>
)}
<label className="flex cursor-pointer items-center gap-2 text-sm text-slate-300">
<input
type="checkbox"