@ploykit/ui 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +201 -41
- package/dist/AppProviders.d.ts +15 -0
- package/dist/AppProviders.js +15 -0
- package/dist/components/ConfirmDialog.js +8 -2
- package/dist/components/DataTable.d.ts +12 -3
- package/dist/components/DataTable.js +6 -2
- package/dist/components/ErrorBoundary.js +1 -1
- package/dist/components/NotificationBell.js +4 -4
- package/dist/components/ReauthDialog.d.ts +15 -0
- package/dist/components/ReauthDialog.js +42 -0
- package/dist/components/SecretModal.d.ts +8 -1
- package/dist/components/SecretModal.js +5 -2
- package/dist/components/ui/{Badge.d.ts → badge.d.ts} +1 -1
- package/dist/components/ui/{Button.d.ts → button.d.ts} +1 -1
- package/dist/components/ui/dialog.d.ts +7 -1
- package/dist/components/ui/dialog.js +7 -2
- package/dist/components/ui/select.d.ts +20 -1
- package/dist/components/ui/select.js +22 -1
- package/dist/components/ui/sheet.d.ts +9 -1
- package/dist/components/ui/sheet.js +9 -2
- package/dist/components/ui/textarea.d.ts +9 -0
- package/dist/components/ui/textarea.js +12 -0
- package/dist/index.d.ts +5 -1
- package/dist/index.js +5 -1
- package/dist/pages/LandingPage.js +1 -1
- package/dist/pages/LoginPage.js +2 -2
- package/dist/pages/account/ProfileSettingsPage.js +1 -1
- package/dist/pages/account/SecuritySettingsPage.js +1 -1
- package/dist/pages/auth/InviteAcceptPage.js +1 -1
- package/dist/pages/auth/ResetPasswordPage.js +1 -1
- package/dist/pages/auth/VerifyEmailPage.js +1 -1
- package/dist/pages/workspace/RolePermissionsPage.d.ts +3 -0
- package/dist/pages/workspace/RolePermissionsPage.js +118 -0
- package/package.json +3 -3
- package/dist/components/ui/menu.d.ts +0 -29
- package/dist/components/ui/menu.js +0 -50
- package/dist/hooks/useApi.d.ts +0 -10
- package/dist/hooks/useApi.js +0 -11
- package/dist/hooks/useAudit.d.ts +0 -39
- package/dist/hooks/useAudit.js +0 -55
- package/dist/hooks/useAuth.d.ts +0 -1
- package/dist/hooks/useAuth.js +0 -4
- package/dist/hooks/useBilling.d.ts +0 -44
- package/dist/hooks/useBilling.js +0 -64
- package/dist/hooks/useLogin.d.ts +0 -10
- package/dist/hooks/useLogin.js +0 -39
- package/dist/hooks/useMembers.d.ts +0 -125
- package/dist/hooks/useMembers.js +0 -127
- package/dist/hooks/useNotificationPrefs.d.ts +0 -20
- package/dist/hooks/useNotificationPrefs.js +0 -42
- package/dist/hooks/usePasswordReset.d.ts +0 -5
- package/dist/hooks/usePasswordReset.js +0 -12
- package/dist/hooks/useSEO.d.ts +0 -6
- package/dist/hooks/useSEO.js +0 -14
- package/dist/hooks/useSessions.d.ts +0 -20
- package/dist/hooks/useSessions.js +0 -38
- package/dist/hooks/useSiteConfig.d.ts +0 -12
- package/dist/hooks/useSiteConfig.js +0 -11
- package/dist/hooks/useTokens.d.ts +0 -36
- package/dist/hooks/useTokens.js +0 -35
- package/dist/hooks/useUsage.d.ts +0 -20
- package/dist/hooks/useUsage.js +0 -21
- package/dist/hooks/useWorkspace.d.ts +0 -26
- package/dist/hooks/useWorkspace.js +0 -32
- package/dist/hooks/useZodForm.d.ts +0 -6
- package/dist/hooks/useZodForm.js +0 -6
- package/dist/hooks/ws.d.ts +0 -4
- package/dist/hooks/ws.js +0 -21
- package/dist/lib/perm.d.ts +0 -2
- package/dist/lib/perm.js +0 -7
- package/dist/lib/ws-query-bridge.d.ts +0 -7
- package/dist/lib/ws-query-bridge.js +0 -35
- package/dist/provider/PloykitProvider.d.ts +0 -50
- package/dist/provider/PloykitProvider.js +0 -240
- package/dist/realtime.d.ts +0 -3
- package/dist/realtime.js +0 -11
- /package/dist/components/ui/{Badge.js → badge.js} +0 -0
- /package/dist/components/ui/{Button.js → button.js} +0 -0
- /package/dist/components/ui/{Input.d.ts → input.d.ts} +0 -0
- /package/dist/components/ui/{Input.js → input.js} +0 -0
|
@@ -2,13 +2,34 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import { Select as SelectPrimitive } from "@base-ui/react/select";
|
|
3
3
|
import { cn } from "../../lib/utils";
|
|
4
4
|
import { ChevronDownIcon, CheckIcon, ChevronUpIcon } from "lucide-react";
|
|
5
|
-
|
|
5
|
+
/**
|
|
6
|
+
* Select 根组件:base-ui Select Root 的 string 契约包装。
|
|
7
|
+
*
|
|
8
|
+
* 契约:onValueChange 永远收到 string——base-ui 原生回调的 value 可为 null
|
|
9
|
+
* (清空/取消选择时),包装层统一归一为空串(''),产品侧无需再写
|
|
10
|
+
* `v ?? ''` 卫语句。受控 value 仍允许 null、类型不收窄(与 base-ui 一致);
|
|
11
|
+
* 其余 props(items/defaultValue/children 等)原样透传。
|
|
12
|
+
* 泛型保留(默认 string):个别历史调用点用数值 item(如 days),此时
|
|
13
|
+
* 回调运行时收到的是原始数值——签名仍标为 string,属已知偏差。
|
|
14
|
+
* 多选(multiple)不在本契约内;如需多选请直接使用
|
|
15
|
+
* `@base-ui/react/select` 的 Root 原语。
|
|
16
|
+
*/
|
|
17
|
+
function Select({ onValueChange, ...props }) {
|
|
18
|
+
return (_jsx(SelectPrimitive.Root, { ...props, onValueChange: onValueChange
|
|
19
|
+
? (value) => onValueChange(value == null ? "" : value)
|
|
20
|
+
: undefined }));
|
|
21
|
+
}
|
|
6
22
|
function SelectGroup({ className, ...props }) {
|
|
7
23
|
return (_jsx(SelectPrimitive.Group, { "data-slot": "select-group", className: cn("scroll-my-1 p-1", className), ...props }));
|
|
8
24
|
}
|
|
9
25
|
function SelectValue({ className, ...props }) {
|
|
10
26
|
return (_jsx(SelectPrimitive.Value, { "data-slot": "select-value", className: cn("flex flex-1 text-left", className), ...props }));
|
|
11
27
|
}
|
|
28
|
+
/**
|
|
29
|
+
* SelectTrigger 触发按钮。size 控制高度档位(经 data-size 落到 Tailwind
|
|
30
|
+
* data-[size=*] 变体):'default' = h-8(表单默认高度);'sm' = h-7 +
|
|
31
|
+
* 更小圆角(工具栏/紧凑筛选条)。默认 'default'。
|
|
32
|
+
*/
|
|
12
33
|
function SelectTrigger({ className, size = "default", children, ...props }) {
|
|
13
34
|
return (_jsxs(SelectPrimitive.Trigger, { "data-slot": "select-trigger", "data-size": size, className: cn("flex w-fit items-center justify-between gap-1.5 rounded-lg border border-input bg-transparent py-2 pr-2 pl-2.5 text-sm whitespace-nowrap transition-colors outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 data-placeholder:text-muted-foreground data-[size=default]:h-8 data-[size=sm]:h-7 data-[size=sm]:rounded-[min(var(--radius-md),10px)] *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-1.5 dark:bg-input/30 dark:hover:bg-input/50 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", className), ...props, children: [children, _jsx(SelectPrimitive.Icon, { render: _jsx(ChevronDownIcon, { className: "pointer-events-none size-4 text-muted-foreground" }) })] }));
|
|
14
35
|
}
|
|
@@ -3,8 +3,16 @@ import { Dialog as SheetPrimitive } from "@base-ui/react/dialog";
|
|
|
3
3
|
declare function Sheet({ ...props }: SheetPrimitive.Root.Props): React.JSX.Element;
|
|
4
4
|
declare function SheetTrigger({ ...props }: SheetPrimitive.Trigger.Props): React.JSX.Element;
|
|
5
5
|
declare function SheetClose({ ...props }: SheetPrimitive.Close.Props): React.JSX.Element;
|
|
6
|
-
|
|
6
|
+
/**
|
|
7
|
+
* SheetContent 内容面板。size 控制尺寸档位(经 data-size 落到 Tailwind
|
|
8
|
+
* data-[size=*] 变体):left/right 侧约束最大宽度(sm=sm:max-w-sm /
|
|
9
|
+
* md=sm:max-w-lg / lg=sm:max-w-2xl),top/bottom 侧约束最大高度
|
|
10
|
+
* (sm=40vh / md=60vh / lg=80vh)。默认 'sm',与历史视觉一致。
|
|
11
|
+
* 基础类自带 overflow-y-auto,长表单可滚动不被裁剪。
|
|
12
|
+
*/
|
|
13
|
+
declare function SheetContent({ className, children, side, size, showCloseButton, ...props }: SheetPrimitive.Popup.Props & {
|
|
7
14
|
side?: "top" | "right" | "bottom" | "left";
|
|
15
|
+
size?: "sm" | "md" | "lg";
|
|
8
16
|
showCloseButton?: boolean;
|
|
9
17
|
}): React.JSX.Element;
|
|
10
18
|
declare function SheetHeader({ className, ...props }: React.ComponentProps<"div">): React.JSX.Element;
|
|
@@ -18,8 +18,15 @@ function SheetPortal({ ...props }) {
|
|
|
18
18
|
function SheetOverlay({ className, ...props }) {
|
|
19
19
|
return (_jsx(SheetPrimitive.Backdrop, { "data-slot": "sheet-overlay", className: cn("fixed inset-0 z-50 bg-black/10 transition-opacity duration-150 data-ending-style:opacity-0 data-starting-style:opacity-0 supports-backdrop-filter:backdrop-blur-xs", className), ...props }));
|
|
20
20
|
}
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
/**
|
|
22
|
+
* SheetContent 内容面板。size 控制尺寸档位(经 data-size 落到 Tailwind
|
|
23
|
+
* data-[size=*] 变体):left/right 侧约束最大宽度(sm=sm:max-w-sm /
|
|
24
|
+
* md=sm:max-w-lg / lg=sm:max-w-2xl),top/bottom 侧约束最大高度
|
|
25
|
+
* (sm=40vh / md=60vh / lg=80vh)。默认 'sm',与历史视觉一致。
|
|
26
|
+
* 基础类自带 overflow-y-auto,长表单可滚动不被裁剪。
|
|
27
|
+
*/
|
|
28
|
+
function SheetContent({ className, children, side = "right", size = "sm", showCloseButton = true, ...props }) {
|
|
29
|
+
return (_jsxs(SheetPortal, { children: [_jsx(SheetOverlay, {}), _jsxs(SheetPrimitive.Popup, { "data-slot": "sheet-content", "data-side": side, "data-size": size, className: cn("fixed z-50 flex flex-col gap-4 overflow-y-auto bg-popover bg-clip-padding text-sm text-popover-foreground shadow-lg transition duration-200 ease-in-out data-ending-style:opacity-0 data-starting-style:opacity-0 data-[side=bottom]:inset-x-0 data-[side=bottom]:bottom-0 data-[side=bottom]:h-auto data-[side=bottom]:border-t data-[side=bottom]:data-ending-style:translate-y-[2.5rem] data-[side=bottom]:data-starting-style:translate-y-[2.5rem] data-[side=left]:inset-y-0 data-[side=left]:left-0 data-[side=left]:h-full data-[side=left]:w-3/4 data-[side=left]:border-r data-[side=left]:data-ending-style:translate-x-[-2.5rem] data-[side=left]:data-starting-style:translate-x-[-2.5rem] data-[side=right]:inset-y-0 data-[side=right]:right-0 data-[side=right]:h-full data-[side=right]:w-3/4 data-[side=right]:border-l data-[side=right]:data-ending-style:translate-x-[2.5rem] data-[side=right]:data-starting-style:translate-x-[2.5rem] data-[side=top]:inset-x-0 data-[side=top]:top-0 data-[side=top]:h-auto data-[side=top]:border-b data-[side=top]:data-ending-style:translate-y-[-2.5rem] data-[side=top]:data-starting-style:translate-y-[-2.5rem] data-[side=left]:sm:max-w-sm data-[side=right]:sm:max-w-sm data-[side=left]:data-[size=md]:sm:max-w-lg data-[side=right]:data-[size=md]:sm:max-w-lg data-[side=left]:data-[size=lg]:sm:max-w-2xl data-[side=right]:data-[size=lg]:sm:max-w-2xl data-[side=top]:data-[size=sm]:max-h-[40vh] data-[side=bottom]:data-[size=sm]:max-h-[40vh] data-[side=top]:data-[size=md]:max-h-[60vh] data-[side=bottom]:data-[size=md]:max-h-[60vh] data-[side=top]:data-[size=lg]:max-h-[80vh] data-[side=bottom]:data-[size=lg]:max-h-[80vh]", className), ...props, children: [children, showCloseButton && (_jsxs(SheetPrimitive.Close, { "data-slot": "sheet-close", render: _jsx(Button, { variant: "ghost", className: "absolute top-3 right-3", size: "icon-sm" }), children: [_jsx(XIcon, {}), _jsx("span", { className: "sr-only", children: "Close" })] }))] })] }));
|
|
23
30
|
}
|
|
24
31
|
function SheetHeader({ className, ...props }) {
|
|
25
32
|
return (_jsx("div", { "data-slot": "sheet-header", className: cn("flex flex-col gap-0.5 p-4", className), ...props }));
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
/**
|
|
3
|
+
* Textarea 多行文本域:与 Input 完全同族的 token 体系
|
|
4
|
+
* (border-input / bg-transparent / focus-visible ring / aria-invalid
|
|
5
|
+
* destructive / disabled / dark:bg-input/30),min-h 起步、支持
|
|
6
|
+
* ref 与 className 合并。
|
|
7
|
+
*/
|
|
8
|
+
declare function Textarea({ className, ...props }: React.ComponentProps<"textarea">): React.JSX.Element;
|
|
9
|
+
export { Textarea };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { cn } from "../../lib/utils";
|
|
3
|
+
/**
|
|
4
|
+
* Textarea 多行文本域:与 Input 完全同族的 token 体系
|
|
5
|
+
* (border-input / bg-transparent / focus-visible ring / aria-invalid
|
|
6
|
+
* destructive / disabled / dark:bg-input/30),min-h 起步、支持
|
|
7
|
+
* ref 与 className 合并。
|
|
8
|
+
*/
|
|
9
|
+
function Textarea({ className, ...props }) {
|
|
10
|
+
return (_jsx("textarea", { "data-slot": "textarea", className: cn("min-h-16 w-full rounded-lg border border-input bg-transparent px-2.5 py-1 text-base transition-colors outline-none placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:pointer-events-none disabled:cursor-not-allowed disabled:bg-input/50 disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:disabled:bg-input/80 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40", className), ...props }));
|
|
11
|
+
}
|
|
12
|
+
export { Textarea };
|
package/dist/index.d.ts
CHANGED
|
@@ -4,13 +4,15 @@ export { Sidebar, SidebarContent, SidebarFooter, SidebarGroup, SidebarGroupConte
|
|
|
4
4
|
export { Separator } from './components/ui/separator';
|
|
5
5
|
export { Skeleton } from './components/ui/skeleton';
|
|
6
6
|
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider } from './components/ui/tooltip';
|
|
7
|
-
export { Sheet, SheetContent, SheetHeader, SheetTitle, SheetDescription } from './components/ui/sheet';
|
|
7
|
+
export { Sheet, SheetContent, SheetHeader, SheetTitle, SheetDescription, SheetFooter, SheetTrigger, SheetClose } from './components/ui/sheet';
|
|
8
|
+
export { AppProviders } from './AppProviders';
|
|
8
9
|
export { LoginPage } from './pages/LoginPage';
|
|
9
10
|
export type { OAuthProviderOption, LoginPageProps } from './pages/LoginPage';
|
|
10
11
|
export { LandingPage } from './pages/LandingPage';
|
|
11
12
|
export type { Feature as LandingFeature, Plan as LandingPlan } from './pages/LandingPage';
|
|
12
13
|
export { Button, buttonVariants } from './components/ui/button';
|
|
13
14
|
export { Input } from './components/ui/input';
|
|
15
|
+
export { Textarea } from './components/ui/textarea';
|
|
14
16
|
export { StatusBadge } from './components/StatusBadge';
|
|
15
17
|
export { Dialog, DialogTrigger, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter, DialogClose, } from './components/ui/dialog';
|
|
16
18
|
export { PageHeader, PageLoading, PageEmpty, PageError } from './components/Page';
|
|
@@ -64,6 +66,8 @@ export { TokensPage } from './pages/account/TokensPage';
|
|
|
64
66
|
export { NotificationPreferencesPage } from './pages/account/NotificationPreferencesPage';
|
|
65
67
|
export type { NotificationPreferencesPageProps, NotificationTypeMeta } from './pages/account/NotificationPreferencesPage';
|
|
66
68
|
export { MembersPage } from './pages/workspace/MembersPage';
|
|
69
|
+
export { RolePermissionsPage } from './pages/workspace/RolePermissionsPage';
|
|
70
|
+
export { ReauthDialog } from './components/ReauthDialog';
|
|
67
71
|
export { WorkspaceGeneralPage } from './pages/workspace/WorkspaceGeneralPage';
|
|
68
72
|
export { WorkspaceAuditPage } from './pages/workspace/WorkspaceAuditPage';
|
|
69
73
|
export { UsagePage } from './pages/workspace/UsagePage';
|
package/dist/index.js
CHANGED
|
@@ -3,11 +3,13 @@ export { Sidebar, SidebarContent, SidebarFooter, SidebarGroup, SidebarGroupConte
|
|
|
3
3
|
export { Separator } from './components/ui/separator';
|
|
4
4
|
export { Skeleton } from './components/ui/skeleton';
|
|
5
5
|
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider } from './components/ui/tooltip';
|
|
6
|
-
export { Sheet, SheetContent, SheetHeader, SheetTitle, SheetDescription } from './components/ui/sheet';
|
|
6
|
+
export { Sheet, SheetContent, SheetHeader, SheetTitle, SheetDescription, SheetFooter, SheetTrigger, SheetClose } from './components/ui/sheet';
|
|
7
|
+
export { AppProviders } from './AppProviders';
|
|
7
8
|
export { LoginPage } from './pages/LoginPage';
|
|
8
9
|
export { LandingPage } from './pages/LandingPage';
|
|
9
10
|
export { Button, buttonVariants } from './components/ui/button';
|
|
10
11
|
export { Input } from './components/ui/input';
|
|
12
|
+
export { Textarea } from './components/ui/textarea';
|
|
11
13
|
export { StatusBadge } from './components/StatusBadge';
|
|
12
14
|
export { Dialog, DialogTrigger, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter, DialogClose, } from './components/ui/dialog';
|
|
13
15
|
export { PageHeader, PageLoading, PageEmpty, PageError } from './components/Page';
|
|
@@ -49,6 +51,8 @@ export { SecuritySettingsPage } from './pages/account/SecuritySettingsPage';
|
|
|
49
51
|
export { TokensPage } from './pages/account/TokensPage';
|
|
50
52
|
export { NotificationPreferencesPage } from './pages/account/NotificationPreferencesPage';
|
|
51
53
|
export { MembersPage } from './pages/workspace/MembersPage';
|
|
54
|
+
export { RolePermissionsPage } from './pages/workspace/RolePermissionsPage';
|
|
55
|
+
export { ReauthDialog } from './components/ReauthDialog';
|
|
52
56
|
export { WorkspaceGeneralPage } from './pages/workspace/WorkspaceGeneralPage';
|
|
53
57
|
export { WorkspaceAuditPage } from './pages/workspace/WorkspaceAuditPage';
|
|
54
58
|
export { UsagePage } from './pages/workspace/UsagePage';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { Link } from 'react-router';
|
|
3
3
|
export function LandingPage({ brand, tagline, description, features, plans, registerPath = '/register', loginPath = '/login' }) {
|
|
4
|
-
return (_jsxs("div", { className: "min-h-screen bg-
|
|
4
|
+
return (_jsxs("div", { className: "min-h-screen bg-card", children: [_jsx("header", { className: "border-b border-border", children: _jsxs("div", { className: "max-w-6xl mx-auto px-4 h-16 flex items-center justify-between", children: [_jsx("span", { className: "text-xl font-bold text-foreground", children: brand }), _jsxs("div", { className: "flex items-center gap-3", children: [_jsx(Link, { to: loginPath, className: "px-4 py-2 text-sm font-medium text-foreground rounded-lg hover:bg-muted", children: "\u767B\u5F55" }), _jsx(Link, { to: registerPath, className: "px-4 py-2 bg-primary text-primary-foreground text-sm font-medium rounded-lg hover:bg-primary/80", children: "\u514D\u8D39\u6CE8\u518C" })] })] }) }), _jsxs("section", { className: "max-w-6xl mx-auto px-4 pt-20 pb-16 text-center", children: [_jsx("h1", { className: "text-5xl font-bold text-foreground tracking-tight", children: tagline }), _jsx("p", { className: "mt-4 text-xl text-muted-foreground max-w-2xl mx-auto", children: description }), _jsx(Link, { to: registerPath, className: "mt-8 inline-block px-6 py-3 bg-primary text-primary-foreground font-medium rounded-lg hover:bg-primary/80 text-lg", children: "\u514D\u8D39\u5F00\u59CB" })] }), _jsx("section", { className: "bg-muted py-16", children: _jsxs("div", { className: "max-w-6xl mx-auto px-4", children: [_jsx("h2", { className: "text-3xl font-bold text-center text-foreground mb-12", children: "\u6838\u5FC3\u529F\u80FD" }), _jsx("div", { className: "grid md:grid-cols-3 gap-8", children: features.map((f, i) => (_jsxs("div", { className: "p-6 bg-card rounded-xl border border-border", children: [_jsx("div", { className: "text-3xl mb-3", children: f.icon }), _jsx("h3", { className: "text-lg font-semibold text-foreground", children: f.title }), _jsx("p", { className: "mt-2 text-sm text-muted-foreground leading-relaxed", children: f.desc })] }, `${i}-${f.title}`))) })] }) }), _jsx("section", { className: "py-16", children: _jsxs("div", { className: "max-w-6xl mx-auto px-4", children: [_jsx("h2", { className: "text-3xl font-bold text-center text-foreground mb-12", children: "\u5B9A\u4EF7" }), _jsx("div", { className: "grid md:grid-cols-2 gap-8 max-w-3xl mx-auto", children: plans.map((p, i) => (_jsxs("div", { className: `p-8 bg-card rounded-xl ${p.recommended ? 'border-2 border-primary relative' : 'border border-border'}`, children: [p.recommended && (_jsx("span", { className: "absolute -top-3 left-1/2 -translate-x-1/2 px-3 py-1 bg-primary text-primary-foreground text-xs font-medium rounded-full", children: "\u63A8\u8350" })), _jsx("h3", { className: "text-lg font-semibold text-foreground", children: p.name }), _jsxs("p", { className: "mt-1 text-3xl font-bold text-foreground", children: [p.price, _jsx("span", { className: "text-sm text-muted-foreground", children: p.period ?? '/月' })] }), _jsx("ul", { className: "mt-6 space-y-3 text-sm text-muted-foreground", children: p.features.map((f, j) => _jsxs("li", { children: ["\u2713 ", f] }, `${j}-${f}`)) }), _jsx(Link, { to: registerPath, className: `mt-6 block text-center py-2.5 rounded-lg font-medium ${p.recommended ? 'bg-primary text-primary-foreground hover:bg-primary/80' : 'border border-input text-foreground hover:bg-muted'}`, children: p.cta })] }, `${i}-${p.name}`))) })] }) }), _jsxs("footer", { className: "border-t border-border py-8 text-center text-sm text-muted-foreground", children: ["\u00A9 2026 ", brand, ". All rights reserved."] })] }));
|
|
5
5
|
}
|
package/dist/pages/LoginPage.js
CHANGED
|
@@ -77,11 +77,11 @@ export function LoginPage({ resetPath = '/forgot-password', registerPath = '/reg
|
|
|
77
77
|
setPwLoading(false);
|
|
78
78
|
}
|
|
79
79
|
};
|
|
80
|
-
return (_jsx("div", { className: "min-h-screen flex items-center justify-center bg-
|
|
80
|
+
return (_jsx("div", { className: "min-h-screen flex items-center justify-center bg-background", children: _jsxs("div", { className: "w-full max-w-sm space-y-4 p-8 bg-white rounded-lg shadow", children: [_jsxs("div", { className: "text-center", children: [_jsx("h1", { className: "text-2xl font-bold", children: title }), _jsx("p", { className: "mt-1 text-sm text-muted-foreground", children: description })] }), _jsxs(Tabs, { defaultValue: "code", children: [_jsxs(TabsList, { className: "w-full", children: [_jsx(TabsTrigger, { value: "code", children: "\u9A8C\u8BC1\u7801\u767B\u5F55" }), _jsx(TabsTrigger, { value: "password", children: "\u5BC6\u7801\u767B\u5F55" })] }), _jsx(TabsContent, { value: "code", children: _jsxs("form", { onSubmit: handleCodeSubmit, className: "space-y-4 pt-2", children: [_jsxs("div", { className: "space-y-1", children: [_jsx("label", { htmlFor: "login-email", className: "text-sm text-foreground", children: "\u90AE\u7BB1" }), _jsx("input", { id: "login-email", type: "email", value: email, onChange: (e) => setEmail(e.target.value), placeholder: "you@example.com", required: true, autoComplete: "email", className: "w-full px-3 py-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-ring" })] }), _jsxs("div", { className: "space-y-1", children: [_jsx("label", { htmlFor: "login-code", className: "text-sm text-foreground", children: "\u9A8C\u8BC1\u7801" }), _jsxs("div", { className: "flex gap-2", children: [_jsx("input", { id: "login-code", type: "text", value: code, onChange: (e) => setCode(e.target.value), placeholder: "6\u4F4D\u6570\u5B57", maxLength: 6, required: true, autoComplete: "one-time-code", className: "flex-1 px-3 py-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-ring" }), _jsx("button", { type: "button", onClick: handleSendCode, disabled: sending || cooldown > 0 || !email, className: "px-4 py-2 text-sm text-primary border border-primary/30 rounded-md hover:bg-primary/10 disabled:opacity-50", children: cooldown > 0
|
|
81
81
|
? `重发(${cooldown}s)`
|
|
82
82
|
: codeSent
|
|
83
83
|
? '重新发送'
|
|
84
84
|
: sending
|
|
85
85
|
? '发送中…'
|
|
86
|
-
: '发送' })] })] }), sendError && _jsx("p", { role: "alert", className: "text-
|
|
86
|
+
: '发送' })] })] }), sendError && _jsx("p", { role: "alert", className: "text-destructive text-sm", children: sendError }), codeError && _jsx("p", { role: "alert", className: "text-destructive text-sm", children: codeError }), _jsx("button", { type: "submit", disabled: loading || !email || !code, className: "w-full py-2 px-4 bg-primary text-primary-foreground rounded-md hover:bg-primary/80 disabled:opacity-50", children: loading ? '登录中…' : '登录' })] }) }), _jsx(TabsContent, { value: "password", children: _jsxs("form", { onSubmit: handlePasswordSubmit, className: "space-y-4 pt-2", children: [_jsx(FormField, { label: "\u90AE\u7BB1", htmlFor: "login-pw-email", children: _jsx(Input, { id: "login-pw-email", type: "email", value: email, required: true, onChange: (e) => setEmail(e.target.value), placeholder: "you@example.com", autoComplete: "email" }) }), _jsx(FormField, { label: "\u5BC6\u7801", htmlFor: "login-pw-password", children: _jsx(Input, { id: "login-pw-password", type: "password", value: password, required: true, onChange: (e) => setPassword(e.target.value), placeholder: "\u8F93\u5165\u5BC6\u7801", autoComplete: "current-password" }) }), pwError && _jsx("p", { role: "alert", className: "text-destructive text-sm", children: pwError }), _jsx(Button, { type: "submit", className: "w-full", disabled: pwLoading || !email || !password, children: pwLoading ? '登录中…' : '登录' })] }) })] }), oauthProviders.length > 0 && (_jsxs("div", { className: "space-y-2 pt-1", "data-testid": "oauth-providers", children: [_jsxs("div", { className: "relative py-2 text-center", children: [_jsx("span", { className: "absolute inset-x-0 top-1/2 border-t border-border" }), _jsx("span", { className: "relative z-10 bg-card px-2 text-xs text-muted-foreground", children: "\u6216\u4F7F\u7528\u4EE5\u4E0B\u65B9\u5F0F\u767B\u5F55" })] }), oauthProviders.map((p) => (_jsxs("a", { href: '/auth/oauth/' + p.id + '/start', className: "block w-full py-2 px-4 text-center text-sm border border-input rounded-md hover:bg-background", children: ["\u4F7F\u7528 ", p.label, " \u767B\u5F55"] }, p.id)))] })), enableFedLogin && (_jsxs("details", { className: "pt-1", "data-testid": "fed-login", children: [_jsx("summary", { className: "text-sm text-muted-foreground cursor-pointer select-none", children: "\u4F01\u4E1A\u767B\u5F55" }), _jsxs("form", { method: "get", action: "/auth/fed/start", className: "mt-2 space-y-2", children: [_jsx("input", { type: "text", name: "workspace", placeholder: "\u5DE5\u4F5C\u533A\u6807\u8BC6\uFF08slug\uFF09", required: true, className: "w-full px-3 py-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-ring" }), _jsx("button", { type: "submit", className: "w-full py-2 px-4 bg-primary text-primary-foreground text-sm rounded-md hover:bg-primary/80", children: "\u901A\u8FC7\u4F01\u4E1A IdP \u767B\u5F55" })] })] })), _jsxs("div", { className: "flex items-center justify-between text-sm", children: [_jsx("a", { href: resetPath, className: "text-primary hover:underline", children: "\u5FD8\u8BB0\u5BC6\u7801\uFF1F" }), registerPath && (_jsx("a", { href: registerPath, "data-testid": "register-link", className: "text-primary hover:underline", children: "\u521B\u5EFA\u8D26\u53F7" }))] })] }) }));
|
|
87
87
|
}
|
|
@@ -88,7 +88,7 @@ export function ProfileSettingsPage({ onDeleted }) {
|
|
|
88
88
|
}
|
|
89
89
|
};
|
|
90
90
|
const emailReadyToDelete = confirmEmail.trim().toLowerCase() === user.email.toLowerCase();
|
|
91
|
-
return (_jsxs("div", { children: [_jsx(PageHeader, { title: "\u4E2A\u4EBA\u8D44\u6599", description: "\u7BA1\u7406\u6635\u79F0\u3001\u5934\u50CF\u7B49\u8D26\u53F7\u8D44\u6599" }), _jsxs(SettingsCard, { title: "\u8D44\u6599", description: "\u5BF9\u5916\u5C55\u793A\u7684\u6635\u79F0\u4E0E\u5934\u50CF", cta: _jsx(Button, { type: "submit", form: "profile-form", disabled: saving, children: saving ? '保存中…' : '保存' }), children: [_jsxs("form", { id: "profile-form", onSubmit: handleSave, className: "max-w-xl space-y-4", children: [_jsx(FormField, { label: "\u6635\u79F0", required: true, error: form.formState.errors.display_name?.message, htmlFor: "profile-name", children: _jsx(Input, { id: "profile-name", maxLength: 100, placeholder: "\u600E\u4E48\u79F0\u547C\u4F60", ...form.register('display_name') }) }), _jsx(FormField, { label: "\u5934\u50CF\u94FE\u63A5", error: form.formState.errors.avatar_url?.message, htmlFor: "profile-avatar", description: "\u53EF\u9009\uFF1B\u9700 http(s):// \u5F00\u5934\u3001\u6700\u591A 2048 \u5B57\u7B26\u3002\u5916\u90E8\u56FE\u7247\u53EF\u80FD\u88AB\u5185\u5BB9\u5B89\u5168\u7B56\u7565\u62E6\u622A\uFF0C\u65E0\u6CD5\u52A0\u8F7D\u65F6\u663E\u793A\u9ED8\u8BA4\u5934\u50CF", children: _jsx(Input, { id: "profile-avatar", maxLength: 2048, placeholder: "https://\u2026\uFF08\u53EF\u9009\uFF09", ...form.register('avatar_url') }) })] }), _jsxs("div", { className: "mt-6 flex flex-row items-center justify-between gap-4 rounded-lg border p-4", children: [_jsxs("div", { className: "min-w-0", children: [_jsx("p", { className: "text-sm font-medium", children: "\u767B\u5F55\u90AE\u7BB1" }), _jsx("p", { className: "mt-0.5 truncate text-sm text-muted-foreground", "data-testid": "profile-email", children: user.email }), _jsx("p", { className: "mt-1 text-xs text-muted-foreground", children: "\u7528\u4E8E\u767B\u5F55\u4E0E\u63A5\u6536\u901A\u77E5\uFF0C\u6682\u4E0D\u652F\u6301\u81EA\u52A9\u4FEE\u6539" })] }), _jsx("div", { className: "flex shrink-0 items-center gap-2", children: user.email_verified ? (_jsx(Badge, { variant: "secondary", className: "bg-
|
|
91
|
+
return (_jsxs("div", { children: [_jsx(PageHeader, { title: "\u4E2A\u4EBA\u8D44\u6599", description: "\u7BA1\u7406\u6635\u79F0\u3001\u5934\u50CF\u7B49\u8D26\u53F7\u8D44\u6599" }), _jsxs(SettingsCard, { title: "\u8D44\u6599", description: "\u5BF9\u5916\u5C55\u793A\u7684\u6635\u79F0\u4E0E\u5934\u50CF", cta: _jsx(Button, { type: "submit", form: "profile-form", disabled: saving, children: saving ? '保存中…' : '保存' }), children: [_jsxs("form", { id: "profile-form", onSubmit: handleSave, className: "max-w-xl space-y-4", children: [_jsx(FormField, { label: "\u6635\u79F0", required: true, error: form.formState.errors.display_name?.message, htmlFor: "profile-name", children: _jsx(Input, { id: "profile-name", maxLength: 100, placeholder: "\u600E\u4E48\u79F0\u547C\u4F60", ...form.register('display_name') }) }), _jsx(FormField, { label: "\u5934\u50CF\u94FE\u63A5", error: form.formState.errors.avatar_url?.message, htmlFor: "profile-avatar", description: "\u53EF\u9009\uFF1B\u9700 http(s):// \u5F00\u5934\u3001\u6700\u591A 2048 \u5B57\u7B26\u3002\u5916\u90E8\u56FE\u7247\u53EF\u80FD\u88AB\u5185\u5BB9\u5B89\u5168\u7B56\u7565\u62E6\u622A\uFF0C\u65E0\u6CD5\u52A0\u8F7D\u65F6\u663E\u793A\u9ED8\u8BA4\u5934\u50CF", children: _jsx(Input, { id: "profile-avatar", maxLength: 2048, placeholder: "https://\u2026\uFF08\u53EF\u9009\uFF09", ...form.register('avatar_url') }) })] }), _jsxs("div", { className: "mt-6 flex flex-row items-center justify-between gap-4 rounded-lg border p-4", children: [_jsxs("div", { className: "min-w-0", children: [_jsx("p", { className: "text-sm font-medium", children: "\u767B\u5F55\u90AE\u7BB1" }), _jsx("p", { className: "mt-0.5 truncate text-sm text-muted-foreground", "data-testid": "profile-email", children: user.email }), _jsx("p", { className: "mt-1 text-xs text-muted-foreground", children: "\u7528\u4E8E\u767B\u5F55\u4E0E\u63A5\u6536\u901A\u77E5\uFF0C\u6682\u4E0D\u652F\u6301\u81EA\u52A9\u4FEE\u6539" })] }), _jsx("div", { className: "flex shrink-0 items-center gap-2", children: user.email_verified ? (_jsx(Badge, { variant: "secondary", className: "bg-(--success)/10 text-(--success) border-(--success)/30", children: "\u5DF2\u9A8C\u8BC1" })) : (_jsxs(_Fragment, { children: [_jsx(Badge, { variant: "outline", className: "text-(--warning) border-(--warning)/40 bg-(--warning)/15", children: "\u672A\u9A8C\u8BC1" }), _jsx(Button, { variant: "outline", size: "sm", onClick: handleResend, disabled: sending, children: sending ? '发送中…' : '重新发送验证邮件' })] })) })] })] }), _jsx(SettingsCard, { danger: true, title: "\u5220\u9664\u8D26\u53F7", description: "\u6CE8\u9500\u540E\u8D26\u53F7\u8FDB\u5165\u5220\u9664\u72B6\u6001\uFF0C\u65E0\u6CD5\u6062\u590D", cta: _jsx(Button, { variant: "destructive", disabled: !emailReadyToDelete || deleting, onClick: () => setConfirmOpen(true), children: "\u5220\u9664\u8D26\u53F7" }), children: _jsx("div", { className: "max-w-xl space-y-4", children: _jsx(FormField, { label: "\u8F93\u5165\u767B\u5F55\u90AE\u7BB1\u4EE5\u786E\u8BA4", htmlFor: "profile-delete-email", children: _jsx(Input, { id: "profile-delete-email", placeholder: user.email, value: confirmEmail, onChange: (e) => setConfirmEmail(e.target.value) }) }) }) }), _jsx(ImpactConfirmation, { open: confirmOpen, onOpenChange: setConfirmOpen, onConfirm: handleDelete, title: "\u786E\u8BA4\u5220\u9664\u8D26\u53F7\uFF1F", confirmText: "\u6C38\u4E45\u5220\u9664", danger: true, impacts: [
|
|
92
92
|
'账号进入 deleted 状态,邮箱被释放(可用同邮箱重新注册)',
|
|
93
93
|
'所有会话立即失效并吊销全部个人访问令牌(PAT)',
|
|
94
94
|
'昵称、头像等资料被清空,操作不可恢复',
|
|
@@ -119,7 +119,7 @@ export function SecuritySettingsPage() {
|
|
|
119
119
|
{ key: 'expires_at', header: '过期时间', render: (s) => formatDateTime(s.expires_at) },
|
|
120
120
|
{
|
|
121
121
|
key: 'current', header: '当前',
|
|
122
|
-
render: (s) => s.current === undefined ? (_jsx("span", { className: "text-muted-foreground", children: "\u2014" })) : s.current ? (_jsx(Badge, { variant: "secondary", className: "bg-
|
|
122
|
+
render: (s) => s.current === undefined ? (_jsx("span", { className: "text-muted-foreground", children: "\u2014" })) : s.current ? (_jsx(Badge, { variant: "secondary", className: "bg-(--success)/10 text-(--success) border-(--success)/30", children: "\u5F53\u524D\u8BBE\u5907" })) : (_jsx("span", { className: "text-muted-foreground", children: "\u2014" })),
|
|
123
123
|
},
|
|
124
124
|
{
|
|
125
125
|
key: 'actions', header: '', className: 'text-right',
|
|
@@ -36,5 +36,5 @@ export function InviteAcceptPage({ onAccepted, onDeclined } = {}) {
|
|
|
36
36
|
toast.error(apiErrorMessage(e, '操作失败'));
|
|
37
37
|
}
|
|
38
38
|
};
|
|
39
|
-
return (_jsx("div", { className: "flex min-h-svh w-full items-center justify-center bg-muted/40 p-6 md:p-10", children: _jsxs("div", { className: "w-full max-w-md rounded-xl border bg-card p-6 shadow-sm", children: [_jsx("h1", { className: "text-xl font-bold", children: "\u6211\u7684\u9080\u8BF7" }), _jsx("p", { className: "mt-1 text-sm text-muted-foreground", children: "\u63A5\u53D7\u540E\u4F60\u5C06\u52A0\u5165\u5BF9\u5E94\u5DE5\u4F5C\u533A\u3002" }), _jsx("div", { className: "mt-4", children: loading ? (_jsx(PageLoading, { label: "\u52A0\u8F7D\u9080\u8BF7\u2026" })) : error ? (_jsxs("div", { className: "py-6 text-center text-sm text-
|
|
39
|
+
return (_jsx("div", { className: "flex min-h-svh w-full items-center justify-center bg-muted/40 p-6 md:p-10", children: _jsxs("div", { className: "w-full max-w-md rounded-xl border bg-card p-6 shadow-sm", children: [_jsx("h1", { className: "text-xl font-bold", children: "\u6211\u7684\u9080\u8BF7" }), _jsx("p", { className: "mt-1 text-sm text-muted-foreground", children: "\u63A5\u53D7\u540E\u4F60\u5C06\u52A0\u5165\u5BF9\u5E94\u5DE5\u4F5C\u533A\u3002" }), _jsx("div", { className: "mt-4", children: loading ? (_jsx(PageLoading, { label: "\u52A0\u8F7D\u9080\u8BF7\u2026" })) : error ? (_jsxs("div", { className: "py-6 text-center text-sm text-destructive", children: [apiErrorMessage(error, '邀请加载失败'), _jsx("div", { className: "mt-3", children: _jsx(Button, { size: "sm", variant: "outline", onClick: () => refetch(), children: "\u91CD\u8BD5" }) })] })) : pending.length === 0 ? (_jsx(PageEmpty, { label: "\u6682\u65E0\u5F85\u5904\u7406\u7684\u9080\u8BF7", hint: "\u6536\u5230\u65B0\u9080\u8BF7\u65F6\u4F1A\u51FA\u73B0\u5728\u8FD9\u91CC" })) : (_jsx("ul", { className: "divide-y", children: pending.map((inv) => (_jsxs("li", { className: "flex items-center justify-between gap-3 py-3", children: [_jsxs("div", { className: "min-w-0", children: [_jsx("p", { className: "truncate text-sm font-medium", children: inv.workspace_name || inv.workspace_slug || '工作区' }), _jsxs("p", { className: "mt-0.5 flex items-center gap-2 text-xs text-muted-foreground", children: [_jsx(Badge, { variant: "secondary", children: ROLE_LABEL[inv.role] ?? inv.role }), _jsxs("span", { children: [formatDateTime(inv.expires_at), " \u8FC7\u671F"] })] })] }), _jsxs("div", { className: "flex shrink-0 gap-1.5", children: [_jsxs(Button, { size: "sm", variant: "outline", disabled: decline.isPending, onClick: () => onDecline(inv.id, inv.workspace_name), children: [_jsx(X, {}), " \u62D2\u7EDD"] }), _jsxs(Button, { size: "sm", disabled: accept.isPending, onClick: () => onAccept(inv.id, inv.workspace_name), children: [_jsx(Check, {}), " \u63A5\u53D7"] })] })] }, inv.id))) })) })] }) }));
|
|
40
40
|
}
|
|
@@ -67,5 +67,5 @@ export function ResetPasswordPage({ onDone }) {
|
|
|
67
67
|
setLoading(false);
|
|
68
68
|
}
|
|
69
69
|
};
|
|
70
|
-
return (_jsx(AuthShell, { children: _jsxs("form", { onSubmit: handleSubmit, className: "space-y-4", children: [_jsx("div", { className: "space-y-1 text-center", children: _jsx("h1", { className: "text-2xl font-bold tracking-tight", children: "\u8BBE\u7F6E\u65B0\u5BC6\u7801" }) }), done ? (_jsxs("div", { className: "space-y-2 text-center", "data-testid": "reset-done", children: [_jsx("p", { className: "text-sm text-
|
|
70
|
+
return (_jsx(AuthShell, { children: _jsxs("form", { onSubmit: handleSubmit, className: "space-y-4", children: [_jsx("div", { className: "space-y-1 text-center", children: _jsx("h1", { className: "text-2xl font-bold tracking-tight", children: "\u8BBE\u7F6E\u65B0\u5BC6\u7801" }) }), done ? (_jsxs("div", { className: "space-y-2 text-center", "data-testid": "reset-done", children: [_jsx("p", { className: "text-sm text-(--success)", children: "\u5BC6\u7801\u5DF2\u91CD\u7F6E\uFF0C\u5168\u90E8\u65E7\u4F1A\u8BDD\u5DF2\u5931\u6548\u3002" }), _jsx("p", { className: "text-sm text-muted-foreground", children: countdown > 0 ? `${countdown} 秒后跳转登录页…` : '正在跳转…' })] })) : params === null ? (_jsx("p", { className: "text-center text-sm text-muted-foreground", children: "\u6B63\u5728\u6253\u5F00\u94FE\u63A5\u2026" })) : !email || !token ? (_jsx("p", { className: "text-center text-sm text-destructive", "data-testid": "reset-invalid", children: "\u94FE\u63A5\u65E0\u6548\uFF1A\u7F3A\u5C11\u90AE\u7BB1\u6216\u4EE4\u724C\u53C2\u6570\uFF0C\u8BF7\u4ECE\u90AE\u4EF6\u4E2D\u7684\u94FE\u63A5\u8FDB\u5165\u3002" })) : (_jsxs(_Fragment, { children: [_jsxs("p", { className: "text-sm text-muted-foreground", children: ["\u6B63\u5728\u4E3A ", email, " \u8BBE\u7F6E\u65B0\u5BC6\u7801"] }), _jsx(FormField, { label: "\u65B0\u5BC6\u7801", htmlFor: "reset-new", children: _jsx(Input, { id: "reset-new", type: "password", value: password, onChange: (e) => setPassword(e.target.value), placeholder: "\u81F3\u5C11 10 \u4F4D\uFF0C\u542B\u4E09\u7C7B\u5B57\u7B26", autoComplete: "new-password", required: true }) }), _jsx(FormField, { label: "\u786E\u8BA4\u65B0\u5BC6\u7801", error: confirmError ?? undefined, htmlFor: "reset-confirm", children: _jsx(Input, { id: "reset-confirm", type: "password", value: confirm, onChange: (e) => setConfirm(e.target.value), placeholder: "\u518D\u8F93\u5165\u4E00\u6B21", autoComplete: "new-password", "aria-invalid": confirmError ? true : undefined, required: true }) }), serverError && (_jsx("p", { role: "alert", className: "text-sm text-destructive", children: serverError })), _jsx(Button, { type: "submit", className: "w-full", disabled: loading || !password || !confirm, children: loading ? '提交中…' : '重置密码' })] }))] }) }));
|
|
71
71
|
}
|
|
@@ -37,7 +37,7 @@ export function VerifyEmailPage() {
|
|
|
37
37
|
setMessage(err instanceof Error ? err.message : '验证失败,链接可能已失效');
|
|
38
38
|
});
|
|
39
39
|
}, []);
|
|
40
|
-
return (_jsx(AuthShell, { children: _jsxs(Empty, { className: "border-none p-0", children: [_jsxs(EmptyHeader, { children: [_jsxs(EmptyMedia, { variant: "icon", "data-testid": "verify-state", children: [state === 'ok' && _jsx(MailCheck, { className: "text-
|
|
40
|
+
return (_jsx(AuthShell, { children: _jsxs(Empty, { className: "border-none p-0", children: [_jsxs(EmptyHeader, { children: [_jsxs(EmptyMedia, { variant: "icon", "data-testid": "verify-state", children: [state === 'ok' && _jsx(MailCheck, { className: "text-(--success)", "aria-hidden": "true" }), state === 'verifying' && _jsx(LoaderCircle, { className: "animate-spin", "aria-hidden": "true" }), state === 'failed' && _jsx(CircleX, { className: "text-destructive", "aria-hidden": "true" })] }), _jsx(EmptyTitle, { children: state === 'ok' ? '邮箱验证成功' : state === 'failed' ? '验证失败' : '正在验证…' }), _jsx(EmptyDescription, { children: state === 'ok'
|
|
41
41
|
? '感谢确认,现在可以正常登录了。'
|
|
42
42
|
: state === 'failed'
|
|
43
43
|
? (message ?? '验证失败')
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useMemo, useState } from 'react';
|
|
3
|
+
import { RotateCcw, Save } from 'lucide-react';
|
|
4
|
+
import { PageHeader, PageLoading, PageError, } from '../../components/Page';
|
|
5
|
+
import { SettingsCard } from '../../components/SettingsCard';
|
|
6
|
+
import { ReauthDialog } from '../../components/ReauthDialog';
|
|
7
|
+
import { useConfirm } from '../../components/ConfirmDialog';
|
|
8
|
+
import { toast } from '../../components/toast';
|
|
9
|
+
import { Button } from '../../components/ui/button';
|
|
10
|
+
import { Badge } from '../../components/ui/badge';
|
|
11
|
+
import { Checkbox } from '../../components/ui/checkbox';
|
|
12
|
+
import { useRoleConfig } from '@ploykit/hooks';
|
|
13
|
+
import { isApiError } from '@ploykit/client';
|
|
14
|
+
import { apiErrorMessage } from '../../lib/api-error';
|
|
15
|
+
const ROLE_LABEL = { owner: '所有者', admin: '管理员', member: '成员' };
|
|
16
|
+
const DOMAIN_LABEL = {
|
|
17
|
+
workspace: '工作区',
|
|
18
|
+
members: '成员',
|
|
19
|
+
invites: '邀请',
|
|
20
|
+
roles: '角色权限',
|
|
21
|
+
billing: '计费',
|
|
22
|
+
webhooks: 'Webhooks',
|
|
23
|
+
notifications: '通知',
|
|
24
|
+
audit: '审计',
|
|
25
|
+
usage: '用量',
|
|
26
|
+
};
|
|
27
|
+
function domainOf(perm) {
|
|
28
|
+
const i = perm.indexOf(':');
|
|
29
|
+
return i > 0 ? perm.slice(0, i) : perm;
|
|
30
|
+
}
|
|
31
|
+
/** catalog 权限按域分组,保持目录的字典序。 */
|
|
32
|
+
function groupByDomain(catalog) {
|
|
33
|
+
const groups = new Map();
|
|
34
|
+
for (const p of catalog) {
|
|
35
|
+
const d = domainOf(p);
|
|
36
|
+
const arr = groups.get(d) ?? [];
|
|
37
|
+
arr.push(p);
|
|
38
|
+
groups.set(d, arr);
|
|
39
|
+
}
|
|
40
|
+
return [...groups.entries()].map(([domain, perms]) => ({ domain, perms }));
|
|
41
|
+
}
|
|
42
|
+
function isReauth(err) {
|
|
43
|
+
return isApiError(err, 'E_REAUTH_REQUIRED');
|
|
44
|
+
}
|
|
45
|
+
export function RolePermissionsPage(_props = {}) {
|
|
46
|
+
const { roles, catalog, loading, error, setPerms, resetPerms } = useRoleConfig();
|
|
47
|
+
const confirm = useConfirm();
|
|
48
|
+
const [drafts, setDrafts] = useState({});
|
|
49
|
+
const [reauthOpen, setReauthOpen] = useState(false);
|
|
50
|
+
const [pendingRetry, setPendingRetry] = useState(null);
|
|
51
|
+
// 数据到达(或写后失效重取)时以服务端有效集重建草稿
|
|
52
|
+
useEffect(() => {
|
|
53
|
+
const next = {};
|
|
54
|
+
for (const r of roles)
|
|
55
|
+
next[r.role] = { perms: [...r.perms], dirty: false };
|
|
56
|
+
setDrafts(next);
|
|
57
|
+
}, [roles]);
|
|
58
|
+
const groups = useMemo(() => groupByDomain(catalog), [catalog]);
|
|
59
|
+
const toggle = (role, perm, on) => {
|
|
60
|
+
setDrafts((d) => {
|
|
61
|
+
const cur = d[role];
|
|
62
|
+
if (!cur)
|
|
63
|
+
return d;
|
|
64
|
+
const perms = on ? [...cur.perms, perm].sort() : cur.perms.filter((p) => p !== perm);
|
|
65
|
+
const server = roles.find((r) => r.role === role)?.perms ?? [];
|
|
66
|
+
const dirty = perms.join('\u0000') !== server.join('\u0000');
|
|
67
|
+
return { ...d, [role]: { perms, dirty } };
|
|
68
|
+
});
|
|
69
|
+
};
|
|
70
|
+
const save = async (role) => {
|
|
71
|
+
const draft = drafts[role];
|
|
72
|
+
if (!draft)
|
|
73
|
+
return;
|
|
74
|
+
try {
|
|
75
|
+
await setPerms.mutateAsync({ role, perms: draft.perms });
|
|
76
|
+
toast.success(`已更新${ROLE_LABEL[role] ?? role}权限`);
|
|
77
|
+
}
|
|
78
|
+
catch (err) {
|
|
79
|
+
if (isReauth(err)) {
|
|
80
|
+
setPendingRetry(() => () => setPerms.mutateAsync({ role, perms: draft.perms }));
|
|
81
|
+
setReauthOpen(true);
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
toast.error(apiErrorMessage(err));
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
const reset = async (role) => {
|
|
88
|
+
const ok = await confirm({
|
|
89
|
+
title: `恢复${ROLE_LABEL[role] ?? role}默认权限?`,
|
|
90
|
+
description: '自定义权限集将被清除,该角色回到内置默认权限。',
|
|
91
|
+
confirmText: '恢复默认',
|
|
92
|
+
});
|
|
93
|
+
if (!ok)
|
|
94
|
+
return;
|
|
95
|
+
try {
|
|
96
|
+
await resetPerms.mutateAsync(role);
|
|
97
|
+
toast.success(`已恢复${ROLE_LABEL[role] ?? role}默认权限`);
|
|
98
|
+
}
|
|
99
|
+
catch (err) {
|
|
100
|
+
if (isReauth(err)) {
|
|
101
|
+
setPendingRetry(() => () => resetPerms.mutateAsync(role));
|
|
102
|
+
setReauthOpen(true);
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
toast.error(apiErrorMessage(err));
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
if (loading)
|
|
109
|
+
return _jsx(PageLoading, { label: "\u52A0\u8F7D\u89D2\u8272\u6743\u9650\u2026" });
|
|
110
|
+
if (error)
|
|
111
|
+
return _jsx(PageError, { message: apiErrorMessage(error) });
|
|
112
|
+
return (_jsxs("div", { className: "mx-auto w-full max-w-4xl p-6", children: [_jsx(PageHeader, { title: "\u89D2\u8272\u6743\u9650", description: "\u81EA\u5B9A\u4E49\u5404\u89D2\u8272\u80FD\u505A\u4EC0\u4E48\u3002\u6240\u6709\u8005\u59CB\u7EC8\u62E5\u6709\u5168\u90E8\u6743\u9650\uFF1B\u81EA\u5B9A\u4E49\u96C6\u4E3A\u6574\u4F53\u66FF\u6362\uFF08\u975E\u7D2F\u52A0\uFF09\uFF0C\u672A\u77E5\u6743\u9650\u4F1A\u88AB\u670D\u52A1\u7AEF\u62D2\u7EDD\u3002" }), roles.map((r) => {
|
|
113
|
+
const draft = drafts[r.role];
|
|
114
|
+
return (_jsx(SettingsCard, { title: _jsxs(_Fragment, { children: [ROLE_LABEL[r.role] ?? r.role, r.overridden && _jsx(Badge, { variant: "secondary", children: "\u5DF2\u81EA\u5B9A\u4E49" })] }), description: r.overridden
|
|
115
|
+
? '当前为自定义权限集(覆盖默认)'
|
|
116
|
+
: '当前为内置默认权限集', cta: _jsxs("div", { className: "flex gap-2", children: [r.overridden && (_jsxs(Button, { variant: "outline", size: "sm", onClick: () => void reset(r.role), children: [_jsx(RotateCcw, { className: "size-4" }), "\u6062\u590D\u9ED8\u8BA4"] })), _jsxs(Button, { size: "sm", disabled: !draft?.dirty, onClick: () => void save(r.role), children: [_jsx(Save, { className: "size-4" }), "\u4FDD\u5B58\u4FEE\u6539"] })] }), children: _jsx("div", { className: "grid gap-4 sm:grid-cols-2", children: groups.map(({ domain, perms }) => (_jsxs("div", { className: "space-y-2", children: [_jsx("p", { className: "text-sm font-medium text-muted-foreground", children: DOMAIN_LABEL[domain] ?? domain }), _jsx("div", { className: "space-y-1.5", children: perms.map((perm) => (_jsxs("label", { className: "flex items-center gap-2 text-sm", children: [_jsx(Checkbox, { checked: draft?.perms.includes(perm) ?? false, onCheckedChange: (v) => toggle(r.role, perm, v === true) }), _jsx("span", { className: "font-mono text-xs", children: perm })] }, perm))) })] }, domain))) }) }, r.role));
|
|
117
|
+
}), _jsx(ReauthDialog, { open: reauthOpen, retry: pendingRetry, onClose: () => { setReauthOpen(false); setPendingRetry(null); } })] }));
|
|
118
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ploykit/ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -29,8 +29,8 @@
|
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
31
|
"@base-ui/react": "^1.8.0",
|
|
32
|
-
"@ploykit/client": "^0.
|
|
33
|
-
"@ploykit/hooks": "^0.
|
|
32
|
+
"@ploykit/client": "^0.4.0",
|
|
33
|
+
"@ploykit/hooks": "^0.4.0",
|
|
34
34
|
"class-variance-authority": "^0.7.1",
|
|
35
35
|
"clsx": "^2.1.1",
|
|
36
36
|
"cn": "^0.4.0",
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import * as React from "react";
|
|
2
|
-
import { Menu as MenuPrimitive } from "@base-ui/react/menu";
|
|
3
|
-
declare function DropdownMenu({ ...props }: MenuPrimitive.Root.Props): React.JSX.Element;
|
|
4
|
-
declare function DropdownMenuPortal({ ...props }: MenuPrimitive.Portal.Props): React.JSX.Element;
|
|
5
|
-
declare function DropdownMenuTrigger({ ...props }: MenuPrimitive.Trigger.Props): React.JSX.Element;
|
|
6
|
-
declare function DropdownMenuContent({ align, alignOffset, side, sideOffset, className, ...props }: MenuPrimitive.Popup.Props & Pick<MenuPrimitive.Positioner.Props, "align" | "alignOffset" | "side" | "sideOffset">): React.JSX.Element;
|
|
7
|
-
declare function DropdownMenuGroup({ ...props }: MenuPrimitive.Group.Props): React.JSX.Element;
|
|
8
|
-
declare function DropdownMenuLabel({ className, inset, ...props }: MenuPrimitive.GroupLabel.Props & {
|
|
9
|
-
inset?: boolean;
|
|
10
|
-
}): React.JSX.Element;
|
|
11
|
-
declare function DropdownMenuItem({ className, inset, variant, ...props }: MenuPrimitive.Item.Props & {
|
|
12
|
-
inset?: boolean;
|
|
13
|
-
variant?: "default" | "destructive";
|
|
14
|
-
}): React.JSX.Element;
|
|
15
|
-
declare function DropdownMenuSub({ ...props }: MenuPrimitive.SubmenuRoot.Props): React.JSX.Element;
|
|
16
|
-
declare function DropdownMenuSubTrigger({ className, inset, children, ...props }: MenuPrimitive.SubmenuTrigger.Props & {
|
|
17
|
-
inset?: boolean;
|
|
18
|
-
}): React.JSX.Element;
|
|
19
|
-
declare function DropdownMenuSubContent({ align, alignOffset, side, sideOffset, className, ...props }: React.ComponentProps<typeof DropdownMenuContent>): React.JSX.Element;
|
|
20
|
-
declare function DropdownMenuCheckboxItem({ className, children, checked, inset, ...props }: MenuPrimitive.CheckboxItem.Props & {
|
|
21
|
-
inset?: boolean;
|
|
22
|
-
}): React.JSX.Element;
|
|
23
|
-
declare function DropdownMenuRadioGroup({ ...props }: MenuPrimitive.RadioGroup.Props): React.JSX.Element;
|
|
24
|
-
declare function DropdownMenuRadioItem({ className, children, inset, ...props }: MenuPrimitive.RadioItem.Props & {
|
|
25
|
-
inset?: boolean;
|
|
26
|
-
}): React.JSX.Element;
|
|
27
|
-
declare function DropdownMenuSeparator({ className, ...props }: MenuPrimitive.Separator.Props): React.JSX.Element;
|
|
28
|
-
declare function DropdownMenuShortcut({ className, ...props }: React.ComponentProps<"span">): React.JSX.Element;
|
|
29
|
-
export { DropdownMenu, DropdownMenuPortal, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuGroup, DropdownMenuLabel, DropdownMenuItem, DropdownMenuCheckboxItem, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubTrigger, DropdownMenuSubContent, };
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { Menu as MenuPrimitive } from "@base-ui/react/menu";
|
|
3
|
-
import { CheckIcon, ChevronRightIcon } from "lucide-react";
|
|
4
|
-
import { cn } from "../../lib/utils";
|
|
5
|
-
function DropdownMenu({ ...props }) {
|
|
6
|
-
return _jsx(MenuPrimitive.Root, { "data-slot": "dropdown-menu", ...props });
|
|
7
|
-
}
|
|
8
|
-
function DropdownMenuPortal({ ...props }) {
|
|
9
|
-
return _jsx(MenuPrimitive.Portal, { "data-slot": "dropdown-menu-portal", ...props });
|
|
10
|
-
}
|
|
11
|
-
function DropdownMenuTrigger({ ...props }) {
|
|
12
|
-
return _jsx(MenuPrimitive.Trigger, { "data-slot": "dropdown-menu-trigger", ...props });
|
|
13
|
-
}
|
|
14
|
-
function DropdownMenuContent({ align = "start", alignOffset = 0, side = "bottom", sideOffset = 4, className, ...props }) {
|
|
15
|
-
return (_jsx(MenuPrimitive.Portal, { children: _jsx(MenuPrimitive.Positioner, { className: "isolate z-50 outline-none", align: align, alignOffset: alignOffset, side: side, sideOffset: sideOffset, children: _jsx(MenuPrimitive.Popup, { "data-slot": "dropdown-menu-content", className: cn("relative z-50 max-h-(--available-height) w-(--anchor-width) origin-(--transform-origin) overflow-x-hidden overflow-y-auto rounded-md bg-popover/70 p-1 text-popover-foreground text-sm shadow-md ring-1 ring-foreground/10 outline-none duration-100 animate-none! before:pointer-events-none before:absolute before:inset-0 before:-z-1 before:rounded-[inherit] before:backdrop-blur-2xl before:backdrop-saturate-150 data-closed:overflow-hidden data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 **:data-[slot$=-item]:data-highlighted:bg-foreground/10 **:data-[slot$=-separator]:bg-foreground/5 **:data-[slot$=-trigger]:aria-expanded:bg-foreground/10! **:data-[variant=destructive]:data-highlighted:bg-foreground/10! **:data-[variant=destructive]:text-accent-foreground! **:data-[variant=destructive]:**:text-accent-foreground!", className), ...props }) }) }));
|
|
16
|
-
}
|
|
17
|
-
function DropdownMenuGroup({ ...props }) {
|
|
18
|
-
return _jsx(MenuPrimitive.Group, { "data-slot": "dropdown-menu-group", ...props });
|
|
19
|
-
}
|
|
20
|
-
function DropdownMenuLabel({ className, inset, ...props }) {
|
|
21
|
-
return (_jsx(MenuPrimitive.GroupLabel, { "data-slot": "dropdown-menu-label", "data-inset": inset, className: cn("px-2 py-1.5 text-xs font-medium text-muted-foreground data-inset:pl-8", className), ...props }));
|
|
22
|
-
}
|
|
23
|
-
function DropdownMenuItem({ className, inset, variant = "default", ...props }) {
|
|
24
|
-
return (_jsx(MenuPrimitive.Item, { "data-slot": "dropdown-menu-item", "data-inset": inset, "data-variant": variant, className: cn("group/dropdown-menu-item relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-50 data-highlighted:bg-accent data-highlighted:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:data-highlighted:bg-destructive/10 data-[variant=destructive]:data-highlighted:text-destructive dark:data-[variant=destructive]:data-highlighted:bg-destructive/20 data-[variant=destructive]:*:[svg]:text-destructive not-data-[variant=destructive]:data-highlighted:**:text-accent-foreground data-inset:pl-8 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 [&_svg]:shrink-0", className), ...props }));
|
|
25
|
-
}
|
|
26
|
-
function DropdownMenuSub({ ...props }) {
|
|
27
|
-
return _jsx(MenuPrimitive.SubmenuRoot, { "data-slot": "dropdown-menu-sub", ...props });
|
|
28
|
-
}
|
|
29
|
-
function DropdownMenuSubTrigger({ className, inset, children, ...props }) {
|
|
30
|
-
return (_jsxs(MenuPrimitive.SubmenuTrigger, { "data-slot": "dropdown-menu-sub-trigger", "data-inset": inset, className: cn("flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-popup-open:bg-accent data-popup-open:text-accent-foreground data-highlighted:bg-accent data-highlighted:text-accent-foreground data-open:bg-accent data-open:text-accent-foreground not-data-[variant=destructive]:data-highlighted:**:text-accent-foreground data-inset:pl-8 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 [&_svg]:shrink-0", className), ...props, children: [children, _jsx(ChevronRightIcon, { className: "ml-auto rtl:-scale-x-100" })] }));
|
|
31
|
-
}
|
|
32
|
-
function DropdownMenuSubContent({ align = "start", alignOffset = -3, side = "right", sideOffset = 0, className, ...props }) {
|
|
33
|
-
return (_jsx(DropdownMenuContent, { "data-slot": "dropdown-menu-sub-content", className: cn("w-auto min-w-[96px] shadow-lg", className), align: align, alignOffset: alignOffset, side: side, sideOffset: sideOffset, ...props }));
|
|
34
|
-
}
|
|
35
|
-
function DropdownMenuCheckboxItem({ className, children, checked, inset, ...props }) {
|
|
36
|
-
return (_jsxs(MenuPrimitive.CheckboxItem, { "data-slot": "dropdown-menu-checkbox-item", "data-inset": inset, className: cn("relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-50 data-highlighted:bg-accent data-highlighted:text-accent-foreground data-highlighted:**:text-accent-foreground data-inset:pl-8 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 [&_svg]:shrink-0", className), checked: checked, ...props, children: [_jsx("span", { className: "pointer-events-none absolute right-2 flex items-center justify-center", "data-slot": "dropdown-menu-checkbox-item-indicator", children: _jsx(MenuPrimitive.CheckboxItemIndicator, { children: _jsx(CheckIcon, {}) }) }), children] }));
|
|
37
|
-
}
|
|
38
|
-
function DropdownMenuRadioGroup({ ...props }) {
|
|
39
|
-
return (_jsx(MenuPrimitive.RadioGroup, { "data-slot": "dropdown-menu-radio-group", ...props }));
|
|
40
|
-
}
|
|
41
|
-
function DropdownMenuRadioItem({ className, children, inset, ...props }) {
|
|
42
|
-
return (_jsxs(MenuPrimitive.RadioItem, { "data-slot": "dropdown-menu-radio-item", "data-inset": inset, className: cn("relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-50 data-highlighted:bg-accent data-highlighted:text-accent-foreground data-highlighted:**:text-accent-foreground data-inset:pl-8 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 [&_svg]:shrink-0", className), ...props, children: [_jsx("span", { className: "pointer-events-none absolute right-2 flex items-center justify-center", "data-slot": "dropdown-menu-radio-item-indicator", children: _jsx(MenuPrimitive.RadioItemIndicator, { children: _jsx(CheckIcon, {}) }) }), children] }));
|
|
43
|
-
}
|
|
44
|
-
function DropdownMenuSeparator({ className, ...props }) {
|
|
45
|
-
return (_jsx(MenuPrimitive.Separator, { "data-slot": "dropdown-menu-separator", className: cn("-mx-1 my-1 h-px bg-border", className), ...props }));
|
|
46
|
-
}
|
|
47
|
-
function DropdownMenuShortcut({ className, ...props }) {
|
|
48
|
-
return (_jsx("span", { "data-slot": "dropdown-menu-shortcut", className: cn("ml-auto text-xs tracking-widest text-muted-foreground group-data-highlighted/dropdown-menu-item:text-accent-foreground", className), ...props }));
|
|
49
|
-
}
|
|
50
|
-
export { DropdownMenu, DropdownMenuPortal, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuGroup, DropdownMenuLabel, DropdownMenuItem, DropdownMenuCheckboxItem, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubTrigger, DropdownMenuSubContent, };
|
package/dist/hooks/useApi.d.ts
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import { ApiError } from '@ploykit/client';
|
|
2
|
-
export { ApiError };
|
|
3
|
-
export type { ApiFetchOptions } from '@ploykit/client';
|
|
4
|
-
export declare function useApi(): {
|
|
5
|
-
get: <T>(path: string) => Promise<T>;
|
|
6
|
-
post: <T>(path: string, body?: unknown) => Promise<T>;
|
|
7
|
-
put: <T>(path: string, body?: unknown) => Promise<T>;
|
|
8
|
-
patch: <T>(path: string, body?: unknown) => Promise<T>;
|
|
9
|
-
delete: <T>(path: string, body?: unknown) => Promise<T>;
|
|
10
|
-
};
|
package/dist/hooks/useApi.js
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import { useCallback, useMemo } from 'react';
|
|
2
|
-
import { api, ApiError } from '@ploykit/client';
|
|
3
|
-
export { ApiError };
|
|
4
|
-
export function useApi() {
|
|
5
|
-
const get = useCallback((path) => api.get(path), []);
|
|
6
|
-
const post = useCallback((path, body) => api.post(path, body), []);
|
|
7
|
-
const put = useCallback((path, body) => api.put(path, body), []);
|
|
8
|
-
const patch = useCallback((path, body) => api.patch(path, body), []);
|
|
9
|
-
const del = useCallback((path, body) => api.delete(path, body), []);
|
|
10
|
-
return useMemo(() => ({ get, post, put, patch, delete: del }), [get, post, put, patch, del]);
|
|
11
|
-
}
|