mirror of
https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web.git
synced 2025-10-01 23:56:39 +08:00
优化登录逻辑,待添加设置密码页面
This commit is contained in:
parent
402d6b9664
commit
db8be98230
@ -1,6 +1,7 @@
|
|||||||
import "@/app/app/login.scss";
|
import "@/app/app/login.scss";
|
||||||
import { Metadata } from "next";
|
import { Metadata } from "next";
|
||||||
import { ReactNode } from "react";
|
import { ReactNode } from "react";
|
||||||
|
import Image from "next/image";
|
||||||
// import { VerifiedUser } from "@/lib/auth";
|
// import { VerifiedUser } from "@/lib/auth";
|
||||||
// import { redirect } from "next/navigation";
|
// import { redirect } from "next/navigation";
|
||||||
|
|
||||||
@ -22,7 +23,19 @@ export default async function AuthLayout({
|
|||||||
return (
|
return (
|
||||||
<div className="container1 w-full signin">
|
<div className="container1 w-full signin">
|
||||||
<div className="flex min-h-screen flex-col justify-center py-12 sm:px-6 lg:px-8">
|
<div className="flex min-h-screen flex-col justify-center py-12 sm:px-6 lg:px-8">
|
||||||
{children}
|
<div className="login-form border border-stone-200 py-10 dark:border-stone-700 sm:mx-auto sm:w-full sm:max-w-md sm:rounded-lg sm:shadow-md ">
|
||||||
|
<Image
|
||||||
|
alt="Platforms Starter Kit"
|
||||||
|
width={100}
|
||||||
|
height={100}
|
||||||
|
className="relative mx-auto h-12 w-auto dark:scale-110 dark:rounded-full dark:border dark:border-stone-400"
|
||||||
|
src="https://oss.xiaosi.cc/chat/public/android-chrome-512x512.png"
|
||||||
|
/>
|
||||||
|
<h2 className="mt-5 text-center text-2xl font-bold leading-9 tracking-tight text-gray-500">
|
||||||
|
Sign in to your account
|
||||||
|
</h2>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -1,47 +0,0 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import LoadingDots from "@/app/components/icons/loading-dots";
|
|
||||||
import { signIn } from "next-auth/react";
|
|
||||||
import { useSearchParams } from "next/navigation";
|
|
||||||
import { useState } from "react";
|
|
||||||
|
|
||||||
export default function LoginButton() {
|
|
||||||
const [loading, setLoading] = useState(false);
|
|
||||||
|
|
||||||
// Get error message added by next/auth in URL.
|
|
||||||
const searchParams = useSearchParams();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<button
|
|
||||||
disabled={loading}
|
|
||||||
onClick={(e) => {
|
|
||||||
setLoading(true);
|
|
||||||
e.preventDefault();
|
|
||||||
signIn("github");
|
|
||||||
}}
|
|
||||||
className={`${
|
|
||||||
loading
|
|
||||||
? "cursor-not-allowed bg-stone-50 dark:bg-stone-800"
|
|
||||||
: "bg-white hover:bg-stone-50 active:bg-stone-100 dark:bg-black dark:hover:border-white dark:hover:bg-black"
|
|
||||||
} group my-2 flex h-10 w-full items-center justify-center space-x-2 rounded-md border border-stone-200 transition-colors duration-75 focus:outline-none dark:border-stone-700`}
|
|
||||||
>
|
|
||||||
{loading ? (
|
|
||||||
<LoadingDots color="#A8A29E" />
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<svg
|
|
||||||
className="h-4 w-4 text-black dark:text-white"
|
|
||||||
aria-hidden="true"
|
|
||||||
fill="currentColor"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
>
|
|
||||||
<path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z" />
|
|
||||||
</svg>
|
|
||||||
<p className="text-sm font-medium text-stone-600 dark:text-stone-400">
|
|
||||||
Login with GitHub
|
|
||||||
</p>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</button>
|
|
||||||
);
|
|
||||||
}
|
|
44
app/app/(auth)/login/loginByGithub.tsx
Normal file
44
app/app/(auth)/login/loginByGithub.tsx
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import LoadingDots from "@/app/components/icons/loading-dots";
|
||||||
|
import { signIn } from "next-auth/react";
|
||||||
|
import { useState } from "react";
|
||||||
|
|
||||||
|
export default function LoginByGithub() {
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={`group my-2 flex h-10 w-7/12 items-center justify-center space-x-2 rounded-md border border-stone-200 transition-colors duration-75 focus:outline-none dark:border-stone-700 `} //
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
disabled={loading}
|
||||||
|
onClick={(e) => {
|
||||||
|
setLoading(true);
|
||||||
|
e.preventDefault();
|
||||||
|
signIn("github");
|
||||||
|
}}
|
||||||
|
className={`bg-transparent hover:bg-transparent mr-40`}
|
||||||
|
>
|
||||||
|
{loading ? (
|
||||||
|
<LoadingDots color="#A8A29E" />
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<svg
|
||||||
|
className="h-6 w-6 text-gray-500 dark:text-white"
|
||||||
|
aria-hidden="true"
|
||||||
|
fill="currentColor"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z" />
|
||||||
|
</svg>
|
||||||
|
{/*<p className="text-sm font-medium text-stone-600 dark:text-stone-400">*/}
|
||||||
|
{/* Login with GitHub*/}
|
||||||
|
{/*</p>*/}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
<div className={`w-1`}></div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@ -1,41 +1,36 @@
|
|||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import LoginButton from "./login-button";
|
import LoginByGithub from "./loginByGithub";
|
||||||
import UserLoginButton from "./user-login-button";
|
import UserLoginCore from "./user-login-core";
|
||||||
import { Suspense } from "react";
|
import { Suspense } from "react";
|
||||||
|
|
||||||
export default function LoginPage() {
|
export default function LoginPage() {
|
||||||
return (
|
return (
|
||||||
<div className="login-form border border-stone-200 py-10 dark:border-stone-700 sm:mx-auto sm:w-full sm:max-w-md sm:rounded-lg sm:shadow-md ">
|
<>
|
||||||
<Image
|
{/*<hr></hr>*/}
|
||||||
alt="Platforms Starter Kit"
|
|
||||||
width={100}
|
|
||||||
height={100}
|
|
||||||
className="relative mx-auto h-12 w-auto dark:scale-110 dark:rounded-full dark:border dark:border-stone-400"
|
|
||||||
src="/logo.png"
|
|
||||||
/>
|
|
||||||
<h2 className="mt-10 text-center text-2xl font-bold leading-9 tracking-tight text-gray-900">
|
|
||||||
Sign in to your account
|
|
||||||
</h2>
|
|
||||||
|
|
||||||
<div className="mx-auto mt-4 w-11/12 max-w-xs sm:w-full">
|
<div className="mx-auto mt-4 w-11/12 max-w-xs sm:w-full">
|
||||||
<Suspense
|
{/*<Suspense*/}
|
||||||
fallback={
|
{/* fallback={*/}
|
||||||
<div className="my-2 h-10 w-full rounded-md border border-stone-200 bg-stone-100 dark:border-stone-700 dark:bg-stone-800" />
|
{/* <div className="my-2 h-10 w-full rounded-md border border-stone-200 bg-stone-100 dark:border-stone-700 dark:bg-stone-800" />*/}
|
||||||
}
|
{/* }*/}
|
||||||
>
|
{/*>*/}
|
||||||
<LoginButton />
|
{/* */}
|
||||||
</Suspense>
|
{/*</Suspense>*/}
|
||||||
|
<UserLoginCore />
|
||||||
</div>
|
</div>
|
||||||
<hr></hr>
|
<div className="mx-auto mt-4 w-11/12 max-w-xs sm:w-full inline-flex items-center justify-center">
|
||||||
<div className="mx-auto mt-4 w-11/12 max-w-xs sm:w-full">
|
{/*<Suspense*/}
|
||||||
<Suspense
|
{/* fallback={*/}
|
||||||
fallback={
|
{/* <div className="my-2 h-10 w-full rounded-md border border-stone-200 bg-stone-100 dark:border-stone-700 dark:bg-stone-800" />*/}
|
||||||
<div className="my-2 h-10 w-full rounded-md border border-stone-200 bg-stone-100 dark:border-stone-700 dark:bg-stone-800" />
|
{/* }*/}
|
||||||
}
|
{/*>*/}
|
||||||
>
|
{/* */}
|
||||||
<UserLoginButton />
|
{/*</Suspense>*/}
|
||||||
</Suspense>
|
<span className="inline-block align-middle text-left w-5/12">
|
||||||
|
{" "}
|
||||||
|
其它登录方式{" "}
|
||||||
|
</span>
|
||||||
|
<LoginByGithub />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
35
app/app/(auth)/login/set-password/page.tsx
Normal file
35
app/app/(auth)/login/set-password/page.tsx
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
"use client";
|
||||||
|
import { redirect } from "next/navigation";
|
||||||
|
// import { getSession } from "@/lib/auth";
|
||||||
|
import { useSession } from "next-auth/react";
|
||||||
|
import { Button, Checkbox, Form, Input } from "antd";
|
||||||
|
|
||||||
|
type LoginType = "phone" | "account";
|
||||||
|
|
||||||
|
export default function SetPasswordPage() {
|
||||||
|
const { data: session, status } = useSession();
|
||||||
|
// if (typeof window !== "undefined" && loading) return null;
|
||||||
|
console.log("2222222", session);
|
||||||
|
// @ts-expect-error
|
||||||
|
if (!session?.user?.hasPassword) {
|
||||||
|
}
|
||||||
|
// else {
|
||||||
|
// redirect("/")
|
||||||
|
// }
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<p>Signed in as {}</p>
|
||||||
|
<div>需要设置一个密码</div>
|
||||||
|
<Form></Form>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
onClick={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
window.location.href = "/";
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
跳过
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
@ -1,370 +0,0 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import { signIn } from "next-auth/react";
|
|
||||||
import React, { useState, useEffect, useRef, use } from "react";
|
|
||||||
import { isName } from "@/lib/auth_list";
|
|
||||||
import {
|
|
||||||
Form,
|
|
||||||
Input,
|
|
||||||
InputRef,
|
|
||||||
notification as notificationModule,
|
|
||||||
NotificationArgsProps,
|
|
||||||
} from "antd";
|
|
||||||
import { UserOutlined, MailOutlined } from "@ant-design/icons";
|
|
||||||
import type { FormProps } from "antd";
|
|
||||||
import { SignInOptions } from "next-auth/react";
|
|
||||||
|
|
||||||
export default function UserLoginButton() {
|
|
||||||
const [loading, setLoading] = useState(false);
|
|
||||||
const [loginForm] = Form.useForm();
|
|
||||||
const nameInput = useRef<InputRef>(null);
|
|
||||||
// const passwordInput = useRef<InputRef>(null);
|
|
||||||
// const emailInput = useRef<HTMLInputElement>(null);
|
|
||||||
const [username, setUsername] = useState("");
|
|
||||||
// const [password, setPassword] = useState("");
|
|
||||||
const [notification, notificationContextHolder] =
|
|
||||||
notificationModule.useNotification();
|
|
||||||
|
|
||||||
const openNotification = (level: string, arms: NotificationArgsProps) => {
|
|
||||||
if (level === "error") {
|
|
||||||
notification.error({
|
|
||||||
...arms,
|
|
||||||
placement: "topRight",
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
notification.info({
|
|
||||||
...arms,
|
|
||||||
placement: "topRight",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// const [error, setError] = useState(false);
|
|
||||||
type FieldType = {
|
|
||||||
username?: string;
|
|
||||||
password?: string;
|
|
||||||
email?: string;
|
|
||||||
};
|
|
||||||
const onFinish: FormProps<FieldType>["onFinish"] = (values) => {
|
|
||||||
setLoading(true);
|
|
||||||
let signInOptions: SignInOptions = {
|
|
||||||
redirect: false,
|
|
||||||
};
|
|
||||||
let loginProvider = "";
|
|
||||||
|
|
||||||
if (values.email) {
|
|
||||||
loginProvider = "email";
|
|
||||||
signInOptions = { ...signInOptions, email: values.email };
|
|
||||||
} else {
|
|
||||||
loginProvider = "credentials";
|
|
||||||
signInOptions = {
|
|
||||||
...signInOptions,
|
|
||||||
username: values.username,
|
|
||||||
password: values.password ?? "",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
signIn(loginProvider, signInOptions).then((result) => {
|
|
||||||
setLoading(false);
|
|
||||||
if (!result?.error) {
|
|
||||||
window.location.href =
|
|
||||||
result?.url && result.url.includes("verify") ? result.url : "/";
|
|
||||||
} else {
|
|
||||||
switch (result.error) {
|
|
||||||
case "AccessDenied":
|
|
||||||
openNotification("error", {
|
|
||||||
message: "登录失败",
|
|
||||||
description: (
|
|
||||||
<span>
|
|
||||||
无权限,请确认用户名正确并等待审批
|
|
||||||
<br />
|
|
||||||
<span style={{ color: "red" }}>或联系管理员</span>
|
|
||||||
</span>
|
|
||||||
),
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (loginProvider === "credentials") {
|
|
||||||
loginForm.setFields([
|
|
||||||
{
|
|
||||||
name: "username",
|
|
||||||
errors: [result.error],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "password",
|
|
||||||
errors: [result.error],
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
if (loginProvider === "email") {
|
|
||||||
loginForm.setFields([
|
|
||||||
{
|
|
||||||
name: "email",
|
|
||||||
errors: [result.error],
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
console.log("response,", result);
|
|
||||||
});
|
|
||||||
|
|
||||||
setLoading(false);
|
|
||||||
console.log("Success:", values);
|
|
||||||
};
|
|
||||||
|
|
||||||
const onFinishFailed: FormProps<FieldType>["onFinishFailed"] = (
|
|
||||||
errorInfo,
|
|
||||||
) => {
|
|
||||||
console.log("Failed:", errorInfo);
|
|
||||||
};
|
|
||||||
|
|
||||||
// const handleNameComposition = (
|
|
||||||
// e: React.CompositionEvent<HTMLInputElement>,
|
|
||||||
// ) => {
|
|
||||||
// if (e.type === "compositionend") {
|
|
||||||
// setUsername(e.currentTarget.value);
|
|
||||||
// }
|
|
||||||
// };
|
|
||||||
// const onNameChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
||||||
// if ((e.nativeEvent as InputEvent).isComposing) {
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
// setUsername(e.target.value);
|
|
||||||
// };
|
|
||||||
// const onPasswordChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
||||||
// if ((e.nativeEvent as InputEvent).isComposing) {
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
// setPassword(e.target.value);
|
|
||||||
// };
|
|
||||||
// const onSubmitHandler = async (e: React.FormEvent<HTMLFormElement>) => {
|
|
||||||
// // handle yow submition
|
|
||||||
// setLoading(true);
|
|
||||||
// e.preventDefault();
|
|
||||||
//
|
|
||||||
// let result: { error: any; url: string | null } | undefined = {
|
|
||||||
// error: null,
|
|
||||||
// url: null,
|
|
||||||
// };
|
|
||||||
// if (emailInput.current && emailInput.current.value) {
|
|
||||||
// result = await signIn("email", {
|
|
||||||
// email: emailInput.current.value,
|
|
||||||
// redirect: false,
|
|
||||||
// });
|
|
||||||
// } else {
|
|
||||||
// result = await signIn("credentials", {
|
|
||||||
// username: username,
|
|
||||||
// password: password,
|
|
||||||
// redirect: false,
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
// console.log("0000000000000", result);
|
|
||||||
// setLoading(false);
|
|
||||||
// if (!result?.error) {
|
|
||||||
// window.location.href =
|
|
||||||
// result?.url && result.url.includes("verify") ? result.url : "/";
|
|
||||||
// } else setError(true);
|
|
||||||
// };
|
|
||||||
|
|
||||||
// useEffect(() => {
|
|
||||||
// if (!username) return;
|
|
||||||
// if (nameInput.current) {
|
|
||||||
// if (!isName(username)) {
|
|
||||||
// setError(true);
|
|
||||||
// } else {
|
|
||||||
// setError(false);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }, [username]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{notificationContextHolder}
|
|
||||||
<div className="mt-6 sm:mx-auto sm:w-full sm:max-w-sm">
|
|
||||||
<Form
|
|
||||||
className="space-y-6"
|
|
||||||
// action="#"
|
|
||||||
// method="POST"
|
|
||||||
autoComplete="off"
|
|
||||||
onFinish={onFinish}
|
|
||||||
onFinishFailed={onFinishFailed}
|
|
||||||
size="large"
|
|
||||||
form={loginForm}
|
|
||||||
id="login-form"
|
|
||||||
// onSubmit={onSubmitHandler}
|
|
||||||
>
|
|
||||||
<div>
|
|
||||||
<Form.Item
|
|
||||||
name="username"
|
|
||||||
rules={[
|
|
||||||
{
|
|
||||||
validator: async (_, value) => {
|
|
||||||
if (value && !isName(value)) {
|
|
||||||
return Promise.reject(
|
|
||||||
new Error("Invalid username format!"),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
const email_value = loginForm.getFieldValue("email");
|
|
||||||
if (!value && !email_value) {
|
|
||||||
return Promise.reject(
|
|
||||||
new Error("Please input your username!"),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (value && email_value) {
|
|
||||||
return Promise.reject(new Error("Field must be unique!"));
|
|
||||||
}
|
|
||||||
const password_value = loginForm.getFieldValue("password");
|
|
||||||
if (!value && password_value) {
|
|
||||||
return Promise.reject(
|
|
||||||
new Error("Please input your username!"),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<Input
|
|
||||||
// id="basic_username"
|
|
||||||
// name="username"
|
|
||||||
// type="username"
|
|
||||||
// ref={nameInput}
|
|
||||||
// // value={username}
|
|
||||||
// onCompositionStart={(e) => e.preventDefault()}
|
|
||||||
// onCompositionEnd={handleNameComposition}
|
|
||||||
// onChange={onNameChange}
|
|
||||||
// required
|
|
||||||
autoComplete="off"
|
|
||||||
prefix={<UserOutlined style={{ color: "rgba(0,0,0,.25)" }} />}
|
|
||||||
placeholder="输入姓名、拼音或邮箱"
|
|
||||||
className={
|
|
||||||
"text-sm font-medium text-stone-600 dark:text-stone-400"
|
|
||||||
}
|
|
||||||
// className={`${
|
|
||||||
// loading
|
|
||||||
// ? "cursor-not-allowed bg-stone-50 dark:bg-stone-800"
|
|
||||||
// : "bg-white hover:bg-stone-50 active:bg-stone-100 dark:bg-black dark:hover:border-white dark:hover:bg-black"
|
|
||||||
// } group my-2 flex h-10 w-full items-center justify-center space-x-2 rounded-md border border-stone-200 transition-colors duration-75 focus:outline-none dark:border-stone-700
|
|
||||||
// ${
|
|
||||||
// error
|
|
||||||
// ? "focus:invalid:border-red-500 focus:invalid:ring-red-500"
|
|
||||||
// : ""
|
|
||||||
// }
|
|
||||||
// `}
|
|
||||||
/>
|
|
||||||
</Form.Item>
|
|
||||||
|
|
||||||
<Form.Item<FieldType>
|
|
||||||
// label="Password"
|
|
||||||
name="password"
|
|
||||||
rules={[
|
|
||||||
{
|
|
||||||
validator: async (_, value) => {
|
|
||||||
if (value) {
|
|
||||||
if (value.length < 6) {
|
|
||||||
return Promise.reject(
|
|
||||||
new Error("Password must be at least 6 characters!"),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<Input.Password
|
|
||||||
// id="basic_password"
|
|
||||||
// name="password"
|
|
||||||
// type="password"
|
|
||||||
// status={error ? "error" : ""}
|
|
||||||
// ref={passwordInput}
|
|
||||||
// value={password}
|
|
||||||
// // onCompositionStart={(e) => e.preventDefault()}
|
|
||||||
// // onCompositionEnd={handleComposition}
|
|
||||||
// onChange={onPasswordChange}
|
|
||||||
autoComplete="off"
|
|
||||||
// // required
|
|
||||||
placeholder="密码验证,测试阶段"
|
|
||||||
className={
|
|
||||||
"text-sm font-medium text-stone-600 dark:text-stone-400"
|
|
||||||
}
|
|
||||||
// className={`${
|
|
||||||
// loading
|
|
||||||
// ? "cursor-not-allowed bg-stone-50 dark:bg-stone-800"
|
|
||||||
// : "bg-white hover:bg-stone-50 active:bg-stone-100 dark:bg-black dark:hover:border-white dark:hover:bg-black"
|
|
||||||
// } group my-2 flex h-10 w-full items-center justify-center space-x-2 rounded-md border border-stone-200 transition-colors duration-75 focus:outline-none dark:border-stone-700
|
|
||||||
// ${
|
|
||||||
// error
|
|
||||||
// ? "focus:invalid:border-red-500 focus:invalid:ring-red-500"
|
|
||||||
// : ""
|
|
||||||
// }
|
|
||||||
// `}
|
|
||||||
/>
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item
|
|
||||||
name="email"
|
|
||||||
rules={[
|
|
||||||
{
|
|
||||||
type: "email",
|
|
||||||
message: "The input is not valid E-mail!",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
validator: async (_, value) => {
|
|
||||||
const username_value = loginForm.getFieldValue("username");
|
|
||||||
if (value && username_value) {
|
|
||||||
return Promise.reject(new Error("Field must be unique!"));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<Input
|
|
||||||
// id="email"
|
|
||||||
// name="email"
|
|
||||||
// type="email"
|
|
||||||
// ref={emailInput}
|
|
||||||
// value={username}
|
|
||||||
// onCompositionStart={(e) => e.preventDefault()}
|
|
||||||
// onCompositionEnd={handleComposition}
|
|
||||||
// onChange={onNameChange}
|
|
||||||
// required
|
|
||||||
prefix={<MailOutlined style={{ color: "rgba(0,0,0,.25)" }} />}
|
|
||||||
placeholder="邮箱验证,测试阶段"
|
|
||||||
className={
|
|
||||||
"text-sm font-medium text-stone-600 dark:text-stone-400"
|
|
||||||
}
|
|
||||||
// className={`${
|
|
||||||
// loading
|
|
||||||
// ? "cursor-not-allowed bg-stone-50 dark:bg-stone-800"
|
|
||||||
// : "bg-white hover:bg-stone-50 active:bg-stone-100 dark:bg-black dark:hover:border-white dark:hover:bg-black"
|
|
||||||
// } group my-2 flex h-10 w-full items-center justify-center space-x-2 rounded-md border border-stone-200 transition-colors duration-75 focus:outline-none dark:border-stone-700
|
|
||||||
// ${
|
|
||||||
// error
|
|
||||||
// ? "focus:invalid:border-red-500 focus:invalid:ring-red-500"
|
|
||||||
// : ""
|
|
||||||
// }
|
|
||||||
// `}
|
|
||||||
/>
|
|
||||||
</Form.Item>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Form.Item>
|
|
||||||
<button
|
|
||||||
disabled={loading}
|
|
||||||
// onClick={() => loginForm.submit()}
|
|
||||||
type="submit"
|
|
||||||
className={`${
|
|
||||||
loading
|
|
||||||
? "cursor-not-allowed bg-stone-50 dark:bg-stone-800"
|
|
||||||
: "flex w-full justify-center rounded-md bg-indigo-600 px-3 py-1.5 text-sm font-semibold leading-6 text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
Sign in
|
|
||||||
</button>
|
|
||||||
</Form.Item>
|
|
||||||
</Form>
|
|
||||||
</div>
|
|
||||||
{/*</div>*/}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
292
app/app/(auth)/login/user-login-core.tsx
Normal file
292
app/app/(auth)/login/user-login-core.tsx
Normal file
@ -0,0 +1,292 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { signIn } from "next-auth/react";
|
||||||
|
import React, { useState, useEffect, useRef, use } from "react";
|
||||||
|
import { isName } from "@/lib/auth_list";
|
||||||
|
import {
|
||||||
|
Form,
|
||||||
|
Tabs,
|
||||||
|
Input,
|
||||||
|
InputRef,
|
||||||
|
notification as notificationModule,
|
||||||
|
NotificationArgsProps,
|
||||||
|
} from "antd";
|
||||||
|
import { UserOutlined, MailOutlined } from "@ant-design/icons";
|
||||||
|
import type { FormProps, TabsProps } from "antd";
|
||||||
|
import { SignInOptions } from "next-auth/react";
|
||||||
|
|
||||||
|
export default function UserLoginCore() {
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [loginForm] = Form.useForm();
|
||||||
|
const [loginMethod, setLoginMethod] = useState<"common" | "cap">("common");
|
||||||
|
const [notification, notificationContextHolder] =
|
||||||
|
notificationModule.useNotification();
|
||||||
|
|
||||||
|
const openNotification = (level: string, arms: NotificationArgsProps) => {
|
||||||
|
if (level === "error") {
|
||||||
|
notification.error({
|
||||||
|
...arms,
|
||||||
|
placement: "topRight",
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
notification.info({
|
||||||
|
...arms,
|
||||||
|
placement: "topRight",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// const [error, setError] = useState(false);
|
||||||
|
type FieldType = {
|
||||||
|
username?: string;
|
||||||
|
password?: string;
|
||||||
|
email?: string;
|
||||||
|
};
|
||||||
|
const onFinish: FormProps<FieldType>["onFinish"] = (values) => {
|
||||||
|
setLoading(true);
|
||||||
|
let signInOptions: SignInOptions = {
|
||||||
|
redirect: false,
|
||||||
|
};
|
||||||
|
let loginProvider = "";
|
||||||
|
|
||||||
|
if (loginMethod === "cap") {
|
||||||
|
loginProvider = "email";
|
||||||
|
signInOptions = { ...signInOptions, email: values.email };
|
||||||
|
} else {
|
||||||
|
loginProvider = "credentials";
|
||||||
|
signInOptions = {
|
||||||
|
...signInOptions,
|
||||||
|
username: values.username,
|
||||||
|
password: values.password ?? "",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
signIn(loginProvider, signInOptions).then((result) => {
|
||||||
|
setLoading(false);
|
||||||
|
if (!result?.error) {
|
||||||
|
// 如果没有密码,且登录成功了,说明需要设置密码
|
||||||
|
let result_url =
|
||||||
|
result?.url && result.url.includes("verify") ? result.url : "/";
|
||||||
|
if (result_url === "/") {
|
||||||
|
result_url = "/login/set-password";
|
||||||
|
}
|
||||||
|
window.location.href = result_url;
|
||||||
|
} else {
|
||||||
|
switch (result.error) {
|
||||||
|
case "AccessDenied":
|
||||||
|
openNotification("error", {
|
||||||
|
message: "登录失败",
|
||||||
|
description: (
|
||||||
|
<span>
|
||||||
|
无权限,请确认用户名正确并等待审批
|
||||||
|
<br />
|
||||||
|
<span style={{ color: "red" }}>或联系管理员</span>
|
||||||
|
</span>
|
||||||
|
),
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (loginProvider === "credentials") {
|
||||||
|
loginForm.setFields([
|
||||||
|
{
|
||||||
|
name: "username",
|
||||||
|
errors: [result.error],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "password",
|
||||||
|
errors: [result.error],
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
if (loginProvider === "email") {
|
||||||
|
loginForm.setFields([
|
||||||
|
{
|
||||||
|
name: "email",
|
||||||
|
errors: [result.error],
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.log("response,", result);
|
||||||
|
});
|
||||||
|
|
||||||
|
setLoading(false);
|
||||||
|
console.log("Success:", values);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onFinishFailed: FormProps<FieldType>["onFinishFailed"] = (
|
||||||
|
errorInfo,
|
||||||
|
) => {
|
||||||
|
console.log("Failed:", errorInfo);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onTabsChange = (key: "common" | "cap") => {
|
||||||
|
console.log(key);
|
||||||
|
setLoginMethod(key);
|
||||||
|
};
|
||||||
|
|
||||||
|
const tabItems = [
|
||||||
|
{
|
||||||
|
key: "common",
|
||||||
|
label: "账号密码登录",
|
||||||
|
children: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "cap",
|
||||||
|
label: "验证码登录",
|
||||||
|
children: "",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{notificationContextHolder}
|
||||||
|
<div className="mt-6 sm:mx-auto sm:w-full sm:max-w-sm">
|
||||||
|
<Tabs
|
||||||
|
centered
|
||||||
|
defaultActiveKey={loginMethod}
|
||||||
|
items={tabItems}
|
||||||
|
onChange={(key) => onTabsChange(key as "common" | "cap")}
|
||||||
|
></Tabs>
|
||||||
|
<Form
|
||||||
|
className="space-y-6"
|
||||||
|
// action="#"
|
||||||
|
// method="POST"
|
||||||
|
autoComplete="off"
|
||||||
|
onFinish={onFinish}
|
||||||
|
onFinishFailed={onFinishFailed}
|
||||||
|
size="large"
|
||||||
|
form={loginForm}
|
||||||
|
id="login-form"
|
||||||
|
// onSubmit={onSubmitHandler}
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
{loginMethod === "common" && (
|
||||||
|
<>
|
||||||
|
<Form.Item
|
||||||
|
name="username"
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
validator: async (_, value) => {
|
||||||
|
if (value && !isName(value)) {
|
||||||
|
return Promise.reject(
|
||||||
|
new Error("Invalid username format!"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// const email_value = loginForm.getFieldValue("email");
|
||||||
|
// if (!value && !email_value) {
|
||||||
|
// return Promise.reject(
|
||||||
|
// new Error("Please input your username!"),
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
// if (value && email_value) {
|
||||||
|
// return Promise.reject(new Error("Field must be unique!"));
|
||||||
|
// }
|
||||||
|
const password_value =
|
||||||
|
loginForm.getFieldValue("password");
|
||||||
|
if (!value && password_value) {
|
||||||
|
return Promise.reject(
|
||||||
|
new Error("Please input your username!"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Input
|
||||||
|
autoComplete="off"
|
||||||
|
prefix={
|
||||||
|
<UserOutlined style={{ color: "rgba(0,0,0,.25)" }} />
|
||||||
|
}
|
||||||
|
placeholder="输入姓名、拼音或邮箱"
|
||||||
|
className={
|
||||||
|
"text-sm font-medium text-stone-600 dark:text-stone-400"
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item<FieldType>
|
||||||
|
// label="Password"
|
||||||
|
name="password"
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
validator: async (_, value) => {
|
||||||
|
if (value) {
|
||||||
|
if (value.length < 6) {
|
||||||
|
return Promise.reject(
|
||||||
|
new Error(
|
||||||
|
"Password must be at least 6 characters!",
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Input.Password
|
||||||
|
autoComplete="off"
|
||||||
|
// // required
|
||||||
|
placeholder="密码验证,测试阶段"
|
||||||
|
className={
|
||||||
|
"text-sm font-medium text-stone-600 dark:text-stone-400"
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{loginMethod === "cap" && (
|
||||||
|
<>
|
||||||
|
<Form.Item
|
||||||
|
name="email"
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
type: "email",
|
||||||
|
message: "The input is not valid E-mail!",
|
||||||
|
},
|
||||||
|
// {
|
||||||
|
// validator: async (_, value) => {
|
||||||
|
// const username_value = loginForm.getFieldValue("username");
|
||||||
|
// if (value && username_value) {
|
||||||
|
// return Promise.reject(new Error("Field must be unique!"));
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Input
|
||||||
|
prefix={
|
||||||
|
<MailOutlined style={{ color: "rgba(0,0,0,.25)" }} />
|
||||||
|
}
|
||||||
|
placeholder="邮箱验证,测试阶段"
|
||||||
|
className={
|
||||||
|
"text-sm font-medium text-stone-600 dark:text-stone-400"
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Form.Item>
|
||||||
|
<button
|
||||||
|
disabled={loading}
|
||||||
|
// onClick={() => loginForm.submit()}
|
||||||
|
type="submit"
|
||||||
|
className={`${
|
||||||
|
loading
|
||||||
|
? "cursor-not-allowed bg-stone-50 dark:bg-stone-800"
|
||||||
|
: "flex w-full justify-center rounded-md bg-indigo-600 px-3 py-1.5 text-sm font-semibold leading-6 text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
Sign in
|
||||||
|
</button>
|
||||||
|
</Form.Item>
|
||||||
|
</Form>
|
||||||
|
</div>
|
||||||
|
{/*</div>*/}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
@ -73,7 +73,7 @@ html {
|
|||||||
.login-form {
|
.login-form {
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
width: 350px;
|
width: 350px;
|
||||||
height: 550px;
|
height: 500px;
|
||||||
border-radius: 15px;
|
border-radius: 15px;
|
||||||
|
|
||||||
/* 定位到中心 */
|
/* 定位到中心 */
|
||||||
|
@ -8,7 +8,6 @@ import { User } from "@prisma/client";
|
|||||||
import { isEmail, isName } from "@/lib/auth_list";
|
import { isEmail, isName } from "@/lib/auth_list";
|
||||||
import {createTransport} from "nodemailer";
|
import {createTransport} from "nodemailer";
|
||||||
import { comparePassword, hashPassword } from "@/lib/utils";
|
import { comparePassword, hashPassword } from "@/lib/utils";
|
||||||
import {getCurStartEnd} from "@/app/utils/custom";
|
|
||||||
const SECURE_COOKIES:boolean = !!process.env.SECURE_COOKIES;
|
const SECURE_COOKIES:boolean = !!process.env.SECURE_COOKIES;
|
||||||
|
|
||||||
|
|
||||||
@ -158,6 +157,8 @@ export const authOptions: NextAuthOptions = {
|
|||||||
id: token?.sub,
|
id: token?.sub,
|
||||||
// @ts-expect-error
|
// @ts-expect-error
|
||||||
username: token?.user?.username || token?.user?.gh_username,
|
username: token?.user?.username || token?.user?.gh_username,
|
||||||
|
// @ts-expect-error
|
||||||
|
hasPassword: !!token?.user?.password,
|
||||||
};
|
};
|
||||||
// console.log('555555555,', session, token)
|
// console.log('555555555,', session, token)
|
||||||
return session;
|
return session;
|
||||||
|
@ -1,16 +1,21 @@
|
|||||||
import {isName} from "@/lib/auth_list";
|
import { isName } from "@/lib/auth_list";
|
||||||
import { CUS_JWT } from "@/lib/auth_type";
|
import { CUS_JWT } from "@/lib/auth_type";
|
||||||
|
|
||||||
|
|
||||||
export async function VerifiedUser(session: CUS_JWT |null) {
|
export async function VerifiedUser(session: CUS_JWT | null) {
|
||||||
const userId = session?.sub
|
const userId = session?.sub
|
||||||
const name = session?.email || session?.name
|
const name = session?.email || session?.name
|
||||||
return !!(name && isName(name) && userId);
|
return !!(name && isName(name) && userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function VerifiedAdminUser(session: CUS_JWT |null) {
|
export async function VerifiedAdminUser(session: CUS_JWT | null) {
|
||||||
// console.log('-------', session, session?.user?.isAdmin)
|
// console.log('-------', session, session?.user?.isAdmin)
|
||||||
return !!session?.user?.isAdmin;
|
return !!session?.user?.isAdmin;
|
||||||
// const name = session?.email || session?.name
|
// const name = session?.email || session?.name
|
||||||
// return !!(name && ADMIN_LIST.includes(name));
|
// return !!(name && ADMIN_LIST.includes(name));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function VerifiedNeedSetPassword(path: string, session: CUS_JWT | null,) {
|
||||||
|
const need_set_pwd = !session?.user?.password
|
||||||
|
return path === "/login/set-password" && need_set_pwd;
|
||||||
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { NextResponse } from "next/server";
|
import { NextResponse } from "next/server";
|
||||||
import type { NextRequest } from "next/server";
|
import type { NextRequest } from "next/server";
|
||||||
import { getToken } from "next-auth/jwt";
|
import { getToken } from "next-auth/jwt";
|
||||||
import { VerifiedUser, VerifiedAdminUser } from "@/lib/auth_client";
|
import { VerifiedUser, VerifiedAdminUser, VerifiedNeedSetPassword } from "@/lib/auth_client";
|
||||||
import { CUS_JWT } from "@/lib/auth_type";
|
import { CUS_JWT } from "@/lib/auth_type";
|
||||||
|
|
||||||
|
|
||||||
@ -16,7 +16,6 @@ export default async function middleware(req: NextRequest) {
|
|||||||
return NextResponse.redirect(new URL(path.replace('/app', ''), req.url), 301);
|
return NextResponse.redirect(new URL(path.replace('/app', ''), req.url), 301);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const session = await getToken({ req });
|
const session = await getToken({ req });
|
||||||
const isUser = await VerifiedUser(session as CUS_JWT);
|
const isUser = await VerifiedUser(session as CUS_JWT);
|
||||||
const isAdminUser = await VerifiedAdminUser(session as CUS_JWT);
|
const isAdminUser = await VerifiedAdminUser(session as CUS_JWT);
|
||||||
@ -35,7 +34,7 @@ export default async function middleware(req: NextRequest) {
|
|||||||
return NextResponse.redirect(new URL("/", req.url))
|
return NextResponse.redirect(new URL("/", req.url))
|
||||||
}
|
}
|
||||||
|
|
||||||
if (path == '/login') {
|
if (path.startsWith('/login')) {
|
||||||
return NextResponse.rewrite(
|
return NextResponse.rewrite(
|
||||||
new URL(`/app${path}`, req.url),
|
new URL(`/app${path}`, req.url),
|
||||||
);
|
);
|
||||||
@ -46,6 +45,10 @@ export default async function middleware(req: NextRequest) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (VerifiedNeedSetPassword(path, session as CUS_JWT)) {
|
||||||
|
console.log('-0-0-- 需要修改密码', )
|
||||||
|
// return NextResponse.redirect(new URL("/login/set-password", req.url))
|
||||||
|
}
|
||||||
|
|
||||||
return NextResponse.next()
|
return NextResponse.next()
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user