feat: complete user management workflow
This commit is contained in:
@@ -10,6 +10,7 @@ import { Button } from "@/components/ui/button";
|
||||
import { Card } from "@/components/ui/card";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import type { ApiResult } from "@/modules/core/server/api";
|
||||
import type { Organization } from "@/modules/core/types";
|
||||
|
||||
type AuthMode = "login" | "register" | "setup";
|
||||
|
||||
@@ -30,6 +31,7 @@ function AuthPanelContent({ mode }: AuthPanelProps): React.ReactElement {
|
||||
const [message, setMessage] = useState("");
|
||||
const [isPending, setIsPending] = useState(false);
|
||||
const [hasAccounts, setHasAccounts] = useState<boolean | null>(null);
|
||||
const [organizations, setOrganizations] = useState<Organization[]>([]);
|
||||
|
||||
const copy = modeCopy[mode];
|
||||
const isSetup = mode === "setup";
|
||||
@@ -39,17 +41,23 @@ function AuthPanelContent({ mode }: AuthPanelProps): React.ReactElement {
|
||||
let isMounted = true;
|
||||
|
||||
async function loadState(): Promise<void> {
|
||||
const [bootstrapResponse, sessionResponse] = await Promise.all([
|
||||
const [bootstrapResponse, sessionResponse, organizationsResponse] = await Promise.all([
|
||||
fetch("/api/auth/bootstrap"),
|
||||
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[] }>;
|
||||
|
||||
if (!isMounted || !bootstrap.success || !session.success) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (organizationsResult.success) {
|
||||
setOrganizations(organizationsResult.organizations);
|
||||
}
|
||||
|
||||
const initialized = !bootstrap.setupRequired;
|
||||
setHasAccounts(initialized);
|
||||
|
||||
@@ -97,6 +105,7 @@ function AuthPanelContent({ mode }: AuthPanelProps): React.ReactElement {
|
||||
const email = String(formData.get("email") ?? "");
|
||||
const password = String(formData.get("password") ?? "");
|
||||
const organization = String(formData.get("organization") ?? "");
|
||||
const organizationId = String(formData.get("organizationId") ?? "");
|
||||
|
||||
const endpoint = isLogin ? "/api/auth/login" : isSetup ? "/api/auth/setup" : "/api/auth/register";
|
||||
const response = await fetch(endpoint, {
|
||||
@@ -107,9 +116,10 @@ function AuthPanelContent({ mode }: AuthPanelProps): React.ReactElement {
|
||||
email,
|
||||
password,
|
||||
organization,
|
||||
organizationId,
|
||||
}),
|
||||
});
|
||||
const result = (await response.json()) as ApiResult<{ account: unknown }>;
|
||||
const result = (await response.json()) as ApiResult<{ account: unknown; pendingApproval?: boolean }>;
|
||||
setIsPending(false);
|
||||
|
||||
if (!result.success) {
|
||||
@@ -117,6 +127,11 @@ function AuthPanelContent({ mode }: AuthPanelProps): React.ReactElement {
|
||||
return;
|
||||
}
|
||||
|
||||
if (result.pendingApproval) {
|
||||
setMessage(result.reason);
|
||||
return;
|
||||
}
|
||||
|
||||
router.refresh();
|
||||
router.replace(isLogin ? redirectTo : "/app/dashboard");
|
||||
}
|
||||
@@ -180,6 +195,23 @@ function AuthPanelContent({ mode }: AuthPanelProps): React.ReactElement {
|
||||
</label>
|
||||
) : null}
|
||||
|
||||
{mode === "register" && organizations.length > 0 ? (
|
||||
<label className="block space-y-2">
|
||||
<span className="text-sm font-medium">申请加入机构</span>
|
||||
<select
|
||||
className="h-11 w-full rounded-md border bg-background px-3 text-sm outline-none transition-colors focus-visible:ring-2 focus-visible:ring-ring"
|
||||
name="organizationId"
|
||||
>
|
||||
<option value="">暂不加入机构</option>
|
||||
{organizations.map((organization) => (
|
||||
<option key={organization.id} value={organization.id}>
|
||||
{organization.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</label>
|
||||
) : null}
|
||||
|
||||
{!isLogin ? (
|
||||
<label className="block space-y-2">
|
||||
<span className="text-sm font-medium">姓名</span>
|
||||
|
||||
Reference in New Issue
Block a user