fix: stabilize kumo ui components
This commit is contained in:
@@ -25,6 +25,26 @@ const modeCopy: Record<AuthMode, { title: string; badge: string; submit: string
|
||||
setup: { title: "初始化机构", badge: "First run", submit: "完成初始化" },
|
||||
};
|
||||
|
||||
async function readApiResult<T extends Record<string, unknown>>(response: Response): Promise<ApiResult<T>> {
|
||||
const text = await response.text();
|
||||
|
||||
if (!text) {
|
||||
return {
|
||||
success: false,
|
||||
reason: response.ok ? "服务器返回了空响应" : `请求失败:${response.status}`,
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
return JSON.parse(text) as ApiResult<T>;
|
||||
} catch {
|
||||
return {
|
||||
success: false,
|
||||
reason: response.ok ? "服务器返回了无法解析的数据" : `请求失败:${response.status}`,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function AuthPanelContent({ mode }: AuthPanelProps): React.ReactElement {
|
||||
const router = useRouter();
|
||||
const searchParams = useSearchParams();
|
||||
@@ -48,11 +68,14 @@ function AuthPanelContent({ mode }: AuthPanelProps): React.ReactElement {
|
||||
fetch("/api/auth/session"),
|
||||
fetch("/api/organizations"),
|
||||
]);
|
||||
const bootstrap = (await bootstrapResponse.json()) as ApiResult<{ setupRequired: boolean }>;
|
||||
const session = (await sessionResponse.json()) as ApiResult<{ account: unknown | null }>;
|
||||
const organizationsResult = (await organizationsResponse.json()) as ApiResult<{ organizations: Organization[] }>;
|
||||
const bootstrap = await readApiResult<{ setupRequired: boolean }>(bootstrapResponse);
|
||||
const session = await readApiResult<{ account: unknown | null }>(sessionResponse);
|
||||
const organizationsResult = await readApiResult<{ organizations: Organization[] }>(organizationsResponse);
|
||||
|
||||
if (!isMounted || !bootstrap.success || !session.success) {
|
||||
if (isMounted) {
|
||||
setMessage(!bootstrap.success ? bootstrap.reason : session.reason);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -122,7 +145,7 @@ function AuthPanelContent({ mode }: AuthPanelProps): React.ReactElement {
|
||||
invitationToken,
|
||||
}),
|
||||
});
|
||||
const result = (await response.json()) as ApiResult<{ account: unknown; pendingApproval?: boolean }>;
|
||||
const result = await readApiResult<{ account: unknown; pendingApproval?: boolean }>(response);
|
||||
setIsPending(false);
|
||||
|
||||
if (!result.success) {
|
||||
|
||||
@@ -5,8 +5,13 @@ import { useRouter } from "next/navigation";
|
||||
import { useState } from "react";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
export function SignOutButton(): React.ReactElement {
|
||||
type SignOutButtonProps = {
|
||||
className?: string;
|
||||
};
|
||||
|
||||
export function SignOutButton({ className }: SignOutButtonProps): React.ReactElement {
|
||||
const router = useRouter();
|
||||
const [isPending, setIsPending] = useState(false);
|
||||
|
||||
@@ -25,6 +30,7 @@ export function SignOutButton(): React.ReactElement {
|
||||
onClick={handleSignOut}
|
||||
disabled={isPending}
|
||||
aria-label="退出登录"
|
||||
className={cn(className)}
|
||||
>
|
||||
<LogOut className="size-4" aria-hidden="true" />
|
||||
<span className="hidden sm:inline">退出</span>
|
||||
|
||||
@@ -192,6 +192,17 @@ export function AppShell({ children, account, permissions }: AppShellProps): Rea
|
||||
))}
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div className="border-t p-3">
|
||||
<div className="flex min-w-0 items-center gap-3 rounded-md bg-secondary/55 p-2">
|
||||
<UserAvatar avatarUrl={account.avatarUrl} name={account.name} size="sm" />
|
||||
<div className="min-w-0 flex-1">
|
||||
<p className="truncate text-sm font-medium leading-tight">{account.name}</p>
|
||||
<p className="truncate text-xs leading-tight text-muted-foreground">{account.email}</p>
|
||||
</div>
|
||||
</div>
|
||||
<SignOutButton className="mt-2 w-full justify-start" />
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
@@ -205,12 +216,12 @@ export function AppShell({ children, account, permissions }: AppShellProps): Rea
|
||||
<p className="text-sm font-medium">工作台</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="hidden items-center gap-2 sm:flex">
|
||||
<div className="text-right">
|
||||
<p className="text-sm font-medium">{account.name}</p>
|
||||
<p className="text-xs text-muted-foreground">{account.email}</p>
|
||||
</div>
|
||||
<div className="flex min-w-0 items-center gap-2 xl:hidden">
|
||||
<div className="hidden min-w-0 items-center gap-2 sm:flex">
|
||||
<p className="min-w-0 max-w-[min(52vw,24rem)] truncate whitespace-nowrap text-right text-sm leading-tight">
|
||||
<span className="font-medium">{account.name}</span>
|
||||
<span className="ml-2 text-muted-foreground">{account.email}</span>
|
||||
</p>
|
||||
<UserAvatar avatarUrl={account.avatarUrl} name={account.name} size="sm" />
|
||||
</div>
|
||||
<SignOutButton />
|
||||
|
||||
Reference in New Issue
Block a user