Files
evanpage/frontend/app/bind-account/page.tsx
evan b0b85f4d3a Initial fullstack project setup with Next.js 15, Gin, PostgreSQL and Docker Compose
- Frontend: Next.js 15 (App Router), Auth.js v5, shadcn/ui, MagicUI
- Backend: Go + Gin + GORM with layered architecture
- Auth: Local credentials login with optional Keycloak OAuth binding
- Admin: RBAC user management for admin role
- Dev: Docker Compose with hot reload for both frontend and backend
- Docker: 3-service orchestration (frontend, backend, postgres)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-16 15:11:20 +00:00

98 lines
2.8 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.
"use client";
import { Suspense } from "react";
import { useState } from "react";
import { useSearchParams, useRouter } from "next/navigation";
import { signIn } from "next-auth/react";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
function BindForm() {
const searchParams = useSearchParams();
const router = useRouter();
const keycloakId = searchParams.get("keycloakId") || "";
const keycloakEmail = searchParams.get("email") || "";
const [username, setUsername] = useState("");
const [password, setPassword] = useState("");
const [error, setError] = useState("");
async function handleSubmit(e: React.FormEvent) {
e.preventDefault();
setError("");
const res = await fetch("/api/proxy/auth/bind-keycloak", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
username,
password,
keycloakId,
keycloakEmail,
}),
});
if (!res.ok) {
const data = await res.json();
setError(data.error || "绑定失败");
return;
}
const data = await res.json();
if (data.bound) {
await signIn("keycloak", { callbackUrl: "/dashboard" });
}
}
return (
<Card className="w-full max-w-md">
<CardHeader>
<CardTitle className="text-center"></CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<p className="text-sm text-gray-500">
Keycloak {keycloakEmail || keycloakId}
</p>
<form onSubmit={handleSubmit} className="space-y-4">
<div>
<Label htmlFor="username"></Label>
<Input
id="username"
value={username}
onChange={(e) => setUsername(e.target.value)}
required
/>
</div>
<div>
<Label htmlFor="password"></Label>
<Input
id="password"
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
required
/>
</div>
{error && <p className="text-sm text-red-500">{error}</p>}
<Button type="submit" className="w-full">
</Button>
</form>
</CardContent>
</Card>
);
}
export default function BindAccountPage() {
return (
<div className="flex min-h-screen items-center justify-center bg-gray-50 p-4">
<Suspense fallback={<div>...</div>}>
<BindForm />
</Suspense>
</div>
);
}