mirror of
https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web.git
synced 2025-11-19 23:43:45 +08:00
init
This commit is contained in:
32
app/app/(auth)/layout.tsx
Normal file
32
app/app/(auth)/layout.tsx
Normal file
@@ -0,0 +1,32 @@
|
||||
import "@/app/styles/login.scss";
|
||||
import { Metadata } from "next";
|
||||
import { ReactNode } from "react";
|
||||
// import { useEffect } from "react";
|
||||
// import {useSession} from "next-auth/react";
|
||||
import { getSession, isName } from "@/lib/auth";
|
||||
import { redirect } from "next/navigation";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Login | 实人认证",
|
||||
};
|
||||
|
||||
export default async function AuthLayout({
|
||||
children,
|
||||
}: {
|
||||
children: ReactNode;
|
||||
}) {
|
||||
const session = await getSession();
|
||||
// If the user is already authenticated, redirect them to home
|
||||
if (session?.user?.name && isName(session.user.name)) {
|
||||
// Replace '/dashboard' with the desired redirect path
|
||||
redirect("/");
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="container mx-auto signin">
|
||||
<div className="flex min-h-screen flex-col justify-center py-12 sm:px-6 lg:px-8">
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
54
app/app/(auth)/login/login-button.tsx
Normal file
54
app/app/(auth)/login/login-button.tsx
Normal file
@@ -0,0 +1,54 @@
|
||||
"use client";
|
||||
|
||||
import LoadingDots from "@/app/components/icons/loading-dots";
|
||||
import { signIn } from "next-auth/react";
|
||||
import { useSearchParams } from "next/navigation";
|
||||
import { useState, useEffect } from "react";
|
||||
import { toast } from "sonner";
|
||||
|
||||
export default function LoginButton() {
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
// Get error message added by next/auth in URL.
|
||||
const searchParams = useSearchParams();
|
||||
const error = searchParams?.get("error");
|
||||
|
||||
useEffect(() => {
|
||||
const errorMessage = Array.isArray(error) ? error.pop() : error;
|
||||
errorMessage && toast.error(errorMessage);
|
||||
}, [error]);
|
||||
|
||||
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>
|
||||
);
|
||||
}
|
||||
41
app/app/(auth)/login/page.tsx
Normal file
41
app/app/(auth)/login/page.tsx
Normal file
@@ -0,0 +1,41 @@
|
||||
import Image from "next/image";
|
||||
import LoginButton from "./login-button";
|
||||
import UserLoginButton from "./user-login-button";
|
||||
import { Suspense } from "react";
|
||||
|
||||
export default function LoginPage() {
|
||||
return (
|
||||
<div className="mx-5 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="/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">
|
||||
<Suspense
|
||||
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" />
|
||||
}
|
||||
>
|
||||
<LoginButton />
|
||||
</Suspense>
|
||||
</div>
|
||||
<hr></hr>
|
||||
<div className="mx-auto mt-4 w-11/12 max-w-xs sm:w-full">
|
||||
<Suspense
|
||||
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" />
|
||||
}
|
||||
>
|
||||
<UserLoginButton />
|
||||
</Suspense>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
121
app/app/(auth)/login/user-login-button.tsx
Normal file
121
app/app/(auth)/login/user-login-button.tsx
Normal file
@@ -0,0 +1,121 @@
|
||||
"use client";
|
||||
|
||||
import { signIn } from "next-auth/react";
|
||||
import React, { useState, useEffect, useRef } from "react";
|
||||
import { isName } from "@/lib/auth";
|
||||
|
||||
export default function UserLoginButton() {
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const nameInput = useRef<HTMLInputElement>(null);
|
||||
const [username, setUsername] = useState("");
|
||||
const [error, setError] = useState(false);
|
||||
|
||||
const handleComposition = (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 onSubmitHandler = async (e: React.FormEvent<HTMLFormElement>) => {
|
||||
// handle yow submition
|
||||
setLoading(true);
|
||||
e.preventDefault();
|
||||
|
||||
console.log("current,username2", username);
|
||||
const result = await signIn("credentials", {
|
||||
username: username,
|
||||
redirect: false,
|
||||
});
|
||||
setLoading(false);
|
||||
if (!result?.error) {
|
||||
window.location.href = "/";
|
||||
} else setError(true);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (nameInput.current) {
|
||||
if (!isName(username)) {
|
||||
setError(true);
|
||||
nameInput.current.setCustomValidity("用户名校验失败");
|
||||
} else {
|
||||
setError(false);
|
||||
nameInput.current.setCustomValidity("");
|
||||
}
|
||||
}
|
||||
// console.log("username:", username);
|
||||
}, [username]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{/*
|
||||
This example requires updating your template:
|
||||
|
||||
```
|
||||
<html class="h-full bg-white">
|
||||
<body class="h-full">
|
||||
```
|
||||
*/}
|
||||
|
||||
<div className="mt-10 sm:mx-auto sm:w-full sm:max-w-sm">
|
||||
<form
|
||||
className="space-y-6"
|
||||
action="#"
|
||||
method="POST"
|
||||
autoComplete="off"
|
||||
onSubmit={onSubmitHandler}
|
||||
>
|
||||
<div>
|
||||
<div className="mt-2">
|
||||
<input
|
||||
id="username"
|
||||
name="username"
|
||||
type="username"
|
||||
ref={nameInput}
|
||||
// value={username}
|
||||
onCompositionStart={(e) => e.preventDefault()}
|
||||
onCompositionEnd={handleComposition}
|
||||
onChange={onNameChange}
|
||||
required
|
||||
placeholder="输入姓名、拼音或邮箱"
|
||||
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"
|
||||
: ""
|
||||
}
|
||||
`}
|
||||
/>
|
||||
{/*{error && <p className="mt-2 text-pink-600 text-sm">{error}</p>}*/}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<button
|
||||
disabled={loading}
|
||||
// onClick={(e) => handleSubmit(e)}
|
||||
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>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
{/*</div>*/}
|
||||
</>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user