Files
sundynix-agentix/sundynix-web/src/pages/Overview.tsx
T
Blizzard 7c67256f87
CI / Go · build + vet + test (pull_request) Failing after 30m3s
CI / Frontend · tsc (sundynix-admin) (pull_request) Failing after 9m42s
CI / Frontend · tsc (sundynix-desktop/frontend) (pull_request) Failing after 16m53s
CI / Frontend · tsc (sundynix-web) (pull_request) Has been cancelled
CI / mcp-py · sandbox guard (pull_request) Has been cancelled
feat(web): 薄 Web 面 sundynix-web —— 注册/组织/团队/账单自助入口
SaaS P3 收口第二刀(设计见 SAAS_DESIGN.md §8)。第三个产品面:desktop=用户
工作产品、admin=平台超管控制塔、web=租户客户的自助柜台——做「装桌面端之前
就要能用」的那些事,三者不合并。

- 骨架抄 admin(HashRouter+路由注册表+me()/sdx:logout 鉴权门+vite/vitest
  单文件配置);UI 整目录搬 desktop 的 ui/ 组件+ink/brand 主题 token(亮暗
  双主题),品牌观感与桌面端一致;api.ts 抄 desktop 的 auth/tenant/usage 段
  (纯 fetch 零 Wails 依赖),成员管理/建组织打新的租户自助接口。
- 页面:登录注册(一页两态)/概览(组织+角色+余额+桌面端下载指引)/团队(名册+
  邀请+改角色+移除,写控件按角色显隐、真闸在后端)/组织(列表+自助新建+切换)/
  用量与账单(余额 hero+趋势+合计+最近消耗,数据全来自现成 /me/usage;充值
  只放说明占位,支付 P5 再接,不做假入口)。
- 工程:vite :5175、launch.json 配置、make webface、ci.yml web 矩阵纳入。
- 测试:tsc 干净;vitest 3 例(fmtCredits 去尾零——测试先行抓出 1.50 毛刺;
  ASSIGNABLE_ROLES 不含 owner)。浏览器 live 全流程:甲(owner)登录→概览→
  团队邀请乙→乙(member)登录写控件全隐藏、名册只读,组织/用量页真数据渲染。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-16 17:47:23 +08:00

63 lines
2.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { Link } from "react-router-dom";
import { Users, Building2, Coins, ArrowUpRight, Download } from "lucide-react";
import { useTenant } from "../shell/AppShell";
import { fmtCredits, ROLE_LABEL } from "../api";
import { Badge } from "../ui";
// 概览:当前组织 + 我的角色 + 余额,和去往各功能的入口。桌面端下载指引放这
// (薄 Web 面的定位就是「装桌面端之前能用的那些事」)。
export function Overview() {
const { ctx, tenants } = useTenant();
const cards = [
{ to: "/team", icon: Users, title: "团队", desc: "邀请成员、分配角色" },
{ to: "/orgs", icon: Building2, title: "组织", desc: `我所属的 ${tenants.length} 个组织` },
{ to: "/usage", icon: Coins, title: "用量与账单", desc: "余额、消耗趋势与明细" },
];
return (
<div className="mx-auto max-w-3xl p-8">
<h1 className="text-xl font-semibold tracking-tight text-slate-100">
{ctx?.tenant?.name ?? "…"}
</h1>
<div className="mt-2 flex items-center gap-2 text-xs text-slate-500">
{ctx?.role && <Badge tone="accent">{ROLE_LABEL[ctx.role] ?? ctx.role}</Badge>}
<span> {ctx?.tenant?.plan ?? "-"}</span>
<span>·</span>
<span>
<span className="tabular-nums text-slate-300">{fmtCredits(ctx?.credit_balance_micro ?? 0)}</span>
</span>
</div>
<div className="mt-8 grid grid-cols-1 gap-3 md:grid-cols-3">
{cards.map((c) => {
const Icon = c.icon;
return (
<Link key={c.to} to={c.to}
className="group flex flex-col rounded-xl border border-line bg-ink-900 p-4 shadow-card transition-colors hover:border-brand/40">
<div className="flex items-center justify-between">
<Icon className="h-5 w-5 text-slate-300 transition-colors group-hover:text-brand-400" strokeWidth={1.8} />
<ArrowUpRight className="h-4 w-4 text-slate-700 transition-colors group-hover:text-slate-400" />
</div>
<div className="mt-3 text-sm font-medium text-slate-100">{c.title}</div>
<div className="mt-1 text-xs text-slate-500">{c.desc}</div>
</Link>
);
})}
</div>
<div className="mt-8 flex items-start gap-3 rounded-xl border border-line bg-ink-900 p-4">
<Download className="mt-0.5 h-4 w-4 shrink-0 text-slate-500" />
<div className="text-xs leading-relaxed text-slate-500">
<span className="text-slate-300"></span>
sundynix
<a className="text-brand-400 hover:underline" href="https://github.com/blizzardzhang/sundynix-agentix/releases/latest" target="_blank" rel="noreferrer">
</a>
</div>
</div>
</div>
);
}