feat(web): 薄 Web 面 sundynix-web —— 注册/组织/团队/账单自助入口
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
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
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>
This commit is contained in:
@@ -0,0 +1,45 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { HashRouter } from "react-router-dom";
|
||||
import { AppShell } from "./shell/AppShell";
|
||||
import { AuthPage } from "./pages/AuthPage";
|
||||
import { authMe, logout, type AuthUser } from "./api";
|
||||
import { ToastProvider } from "./ui";
|
||||
|
||||
// 薄 Web 面:租户客户的自助柜台(注册/组织/团队/账单)。
|
||||
// 与 desktop(用户工作产品)、admin(平台超管控制塔)是三个独立产品面。
|
||||
// HashRouter:纯静态托管即可深链,无需服务端路由配置(与 admin 同款理由)。
|
||||
export default function App() {
|
||||
const [user, setUser] = useState<AuthUser | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
authMe()
|
||||
.then(setUser)
|
||||
.catch(() => setUser(null))
|
||||
.finally(() => setLoading(false));
|
||||
const onLogout = () => setUser(null);
|
||||
window.addEventListener("sdx:logout", onLogout);
|
||||
return () => window.removeEventListener("sdx:logout", onLogout);
|
||||
}, []);
|
||||
|
||||
if (loading) {
|
||||
return <div className="flex h-screen w-screen items-center justify-center bg-ink-950 text-sm text-slate-500">加载中…</div>;
|
||||
}
|
||||
return (
|
||||
<ToastProvider>
|
||||
{user ? (
|
||||
<HashRouter>
|
||||
<AppShell
|
||||
user={user}
|
||||
onLogout={() => {
|
||||
logout();
|
||||
setUser(null);
|
||||
}}
|
||||
/>
|
||||
</HashRouter>
|
||||
) : (
|
||||
<AuthPage onAuthed={setUser} />
|
||||
)}
|
||||
</ToastProvider>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user