frontend: convert login and register to homepage modals
- Remove register link from login form - Redirect /login and /register to /?login=1 and /?register=1 - Open login/register as dialogs on homepage instead of separate pages
This commit is contained in:
@@ -6,11 +6,18 @@ import { useSearchParams } from "next/navigation";
|
||||
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";
|
||||
|
||||
export function LoginForm({ hasKeycloak }: { hasKeycloak: boolean }) {
|
||||
export function LoginForm({
|
||||
hasKeycloak,
|
||||
callbackUrl: callbackUrlProp,
|
||||
onSuccess,
|
||||
}: {
|
||||
hasKeycloak: boolean;
|
||||
callbackUrl?: string;
|
||||
onSuccess?: () => void;
|
||||
}) {
|
||||
const searchParams = useSearchParams();
|
||||
const callbackUrl = searchParams.get("callbackUrl") || "/dashboard";
|
||||
const callbackUrl = callbackUrlProp ?? searchParams.get("callbackUrl") ?? "/dashboard";
|
||||
const [username, setUsername] = useState("");
|
||||
const [password, setPassword] = useState("");
|
||||
const [error, setError] = useState("");
|
||||
@@ -29,6 +36,7 @@ export function LoginForm({ hasKeycloak }: { hasKeycloak: boolean }) {
|
||||
if (res?.error) {
|
||||
setError("登录失败,请检查用户名和密码");
|
||||
} else {
|
||||
onSuccess?.();
|
||||
const redirectUrl = callbackUrl.startsWith("http")
|
||||
? callbackUrl
|
||||
: window.location.origin + callbackUrl;
|
||||
@@ -37,11 +45,7 @@ export function LoginForm({ hasKeycloak }: { hasKeycloak: boolean }) {
|
||||
}
|
||||
|
||||
return (
|
||||
<Card className="w-full max-w-md">
|
||||
<CardHeader>
|
||||
<CardTitle className="text-center">登录</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<div className="space-y-4">
|
||||
<form onSubmit={handleSubmit} className="space-y-4">
|
||||
<div>
|
||||
<Label htmlFor="username">用户名</Label>
|
||||
@@ -88,14 +92,6 @@ export function LoginForm({ hasKeycloak }: { hasKeycloak: boolean }) {
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
|
||||
<p className="text-center text-sm text-gray-500">
|
||||
还没有账号?{" "}
|
||||
<a href="/register" className="text-blue-600 hover:underline">
|
||||
立即注册
|
||||
</a>
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,14 +1,50 @@
|
||||
import { Suspense } from "react";
|
||||
"use client";
|
||||
|
||||
import { Suspense, useEffect } from "react";
|
||||
import { useRouter, useSearchParams } from "next/navigation";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "@/components/ui/dialog";
|
||||
import { LoginForm } from "./login-form";
|
||||
|
||||
const hasKeycloak = !!process.env.AUTH_KEYCLOAK_ISSUER;
|
||||
|
||||
export default function LoginPage() {
|
||||
function LoginPageContent() {
|
||||
const router = useRouter();
|
||||
const searchParams = useSearchParams();
|
||||
const callbackUrl = searchParams.get("callbackUrl");
|
||||
const error = searchParams.get("error");
|
||||
|
||||
useEffect(() => {
|
||||
// If not an OAuth callback, redirect to home with login modal open
|
||||
if (!callbackUrl && !error) {
|
||||
router.replace("/?login=1");
|
||||
}
|
||||
}, [callbackUrl, error, router]);
|
||||
|
||||
return (
|
||||
<div className="flex min-h-screen items-center justify-center bg-gray-50 p-4">
|
||||
<div className="flex min-h-screen items-center justify-center bg-gradient-to-br from-slate-50 to-slate-100 p-4">
|
||||
<Dialog defaultOpen>
|
||||
<DialogContent className="sm:max-w-md">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="text-center">登录</DialogTitle>
|
||||
</DialogHeader>
|
||||
<Suspense fallback={<div>加载中...</div>}>
|
||||
<LoginForm hasKeycloak={hasKeycloak} />
|
||||
</Suspense>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function LoginPage() {
|
||||
return (
|
||||
<Suspense fallback={<div className="flex min-h-screen items-center justify-center">加载中...</div>}>
|
||||
<LoginPageContent />
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,113 +1,18 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { useEffect } from "react";
|
||||
import { useRouter } from "next/navigation";
|
||||
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";
|
||||
|
||||
export default function RegisterPage() {
|
||||
const router = useRouter();
|
||||
const [form, setForm] = useState({
|
||||
username: "",
|
||||
email: "",
|
||||
password: "",
|
||||
confirmPassword: "",
|
||||
});
|
||||
const [error, setError] = useState("");
|
||||
|
||||
async function handleSubmit(e: React.FormEvent) {
|
||||
e.preventDefault();
|
||||
setError("");
|
||||
|
||||
if (form.password !== form.confirmPassword) {
|
||||
setError("两次输入的密码不一致");
|
||||
return;
|
||||
}
|
||||
|
||||
const res = await fetch("/api/proxy/auth/register", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
username: form.username,
|
||||
email: form.email,
|
||||
password: form.password,
|
||||
}),
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
const data = await res.json();
|
||||
setError(data.error || "注册失败");
|
||||
return;
|
||||
}
|
||||
|
||||
router.push("/login");
|
||||
}
|
||||
useEffect(() => {
|
||||
router.replace("/");
|
||||
}, [router]);
|
||||
|
||||
return (
|
||||
<div className="flex min-h-screen items-center justify-center bg-gray-50 p-4">
|
||||
<Card className="w-full max-w-md">
|
||||
<CardHeader>
|
||||
<CardTitle className="text-center">注册</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<form onSubmit={handleSubmit} className="space-y-4">
|
||||
<div>
|
||||
<Label htmlFor="username">用户名</Label>
|
||||
<Input
|
||||
id="username"
|
||||
value={form.username}
|
||||
onChange={(e) => setForm({ ...form, username: e.target.value })}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Label htmlFor="email">邮箱</Label>
|
||||
<Input
|
||||
id="email"
|
||||
type="email"
|
||||
value={form.email}
|
||||
onChange={(e) => setForm({ ...form, email: e.target.value })}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Label htmlFor="password">密码</Label>
|
||||
<Input
|
||||
id="password"
|
||||
type="password"
|
||||
value={form.password}
|
||||
onChange={(e) => setForm({ ...form, password: e.target.value })}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Label htmlFor="confirmPassword">确认密码</Label>
|
||||
<Input
|
||||
id="confirmPassword"
|
||||
type="password"
|
||||
value={form.confirmPassword}
|
||||
onChange={(e) =>
|
||||
setForm({ ...form, confirmPassword: e.target.value })
|
||||
}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
{error && <p className="text-sm text-red-500">{error}</p>}
|
||||
<Button type="submit" className="w-full">
|
||||
注册
|
||||
</Button>
|
||||
</form>
|
||||
|
||||
<p className="text-center text-sm text-gray-500">
|
||||
已有账号?{" "}
|
||||
<a href="/login" className="text-blue-600 hover:underline">
|
||||
去登录
|
||||
</a>
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<div className="flex min-h-screen items-center justify-center">
|
||||
加载中...
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user