Custom sign in/out page
This commit is contained in:
parent
0a6fd88733
commit
dbb9bd8476
6 changed files with 171 additions and 147 deletions
|
@ -48,6 +48,13 @@ const options: NextAuthOptions = {
|
||||||
return session
|
return session
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
pages: {
|
||||||
|
signIn: "/login",
|
||||||
|
signOut: "/logout",
|
||||||
|
// error: '/auth/error', // Error code passed in query string as ?error=
|
||||||
|
// verifyRequest: '/auth/verify-request', // (used for check email message)
|
||||||
|
// newUser: '/auth/new-user' // New users will be directed here on first sign in (leave the property out if not of interest)
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
export { options as authOptions }
|
export { options as authOptions }
|
||||||
|
|
|
@ -1,130 +1,64 @@
|
||||||
import { faWifiExclamation } from "@fortawesome/pro-duotone-svg-icons"
|
import { faLeftLong } from "@fortawesome/pro-solid-svg-icons"
|
||||||
import {
|
|
||||||
faArrowLeft,
|
|
||||||
faCheck,
|
|
||||||
faSpinnerThird,
|
|
||||||
faXmark,
|
|
||||||
} from "@fortawesome/pro-solid-svg-icons"
|
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
|
||||||
import classNames from "classnames"
|
import { signIn, useSession } from "next-auth/react"
|
||||||
import { FormEvent, useState } from "react"
|
import { useRouter } from "next/router"
|
||||||
|
import { useEffect, useState } from "react"
|
||||||
|
import { toast } from "react-toastify"
|
||||||
|
|
||||||
enum ProcessStates {
|
type SignInErrorTypes =
|
||||||
"waiting",
|
| "Signin"
|
||||||
"loading",
|
| "OAuthSignin"
|
||||||
"success",
|
| "OAuthCallback"
|
||||||
"wrong",
|
| "OAuthCreateAccount"
|
||||||
"error",
|
| "EmailCreateAccount"
|
||||||
}
|
| "Callback"
|
||||||
const messages: { [key in ProcessStates]: string } = {
|
| "OAuthAccountNotLinked"
|
||||||
[ProcessStates.waiting]: "Enter Login Details",
|
| "EmailSignin"
|
||||||
[ProcessStates.loading]: "Logging in...",
|
| "CredentialsSignin"
|
||||||
[ProcessStates.success]: "Done!",
|
| "SessionRequired"
|
||||||
[ProcessStates.wrong]: "Wrong username or password",
|
| "default"
|
||||||
[ProcessStates.error]: "An error occurred!",
|
|
||||||
}
|
const errors: Record<SignInErrorTypes, string> = {
|
||||||
const icons = {
|
Signin: "Try signing in with a different account.",
|
||||||
[ProcessStates.loading]: faSpinnerThird,
|
OAuthSignin: "Try signing in with a different account.",
|
||||||
[ProcessStates.success]: faCheck,
|
OAuthCallback: "Try signing in with a different account.",
|
||||||
[ProcessStates.wrong]: faXmark,
|
OAuthCreateAccount: "Try signing in with a different account.",
|
||||||
[ProcessStates.error]: faWifiExclamation,
|
EmailCreateAccount: "Try signing in with a different account.",
|
||||||
}
|
Callback: "Try signing in with a different account.",
|
||||||
const iconClasses = {
|
OAuthAccountNotLinked:
|
||||||
[ProcessStates.loading]: "animate-spin",
|
"To confirm your identity, sign in with the same account you used originally.",
|
||||||
[ProcessStates.success]: "text-green-500",
|
EmailSignin: "The e-mail could not be sent.",
|
||||||
[ProcessStates.wrong]: "text-red-500",
|
CredentialsSignin:
|
||||||
[ProcessStates.error]: "animate-pulse text-amber-500 !text-8xl",
|
"Sign in failed. Check the details you provided are correct.",
|
||||||
|
SessionRequired: "Please sign in to access this page.",
|
||||||
|
default: "Unable to sign in.",
|
||||||
}
|
}
|
||||||
|
|
||||||
function Login() {
|
function Login() {
|
||||||
const [username, setUsername] = useState("")
|
const [email, setEmail] = useState("")
|
||||||
const [password, setPassword] = useState("")
|
const { status } = useSession()
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
const [state, setState] = useState<ProcessStates>(ProcessStates.waiting)
|
const errorType = router.query.error as SignInErrorTypes
|
||||||
const [error, setError] = useState("")
|
|
||||||
|
|
||||||
const elem = () => {
|
useEffect(() => {
|
||||||
if (state === ProcessStates.waiting)
|
if (!errorType) return
|
||||||
return (
|
toast.error(errors[errorType] ?? errors.default, { theme: "colored" })
|
||||||
<form onSubmit={login}>
|
}, [errorType])
|
||||||
<div className="mb-4 text-lg">
|
|
||||||
<input
|
|
||||||
className="rounded-3xl border-none bg-blue-400 bg-opacity-50 px-6 py-2 text-center text-inherit placeholder-slate-200 shadow-lg outline-none backdrop-blur-md"
|
|
||||||
type="text"
|
|
||||||
name="name"
|
|
||||||
placeholder="Username or email"
|
|
||||||
value={username}
|
|
||||||
onChange={(e) => setUsername(e.target.value)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="mb-4 text-lg">
|
useEffect(() => {
|
||||||
<input
|
if (status === "authenticated") router.push("/")
|
||||||
className="rounded-3xl border-none bg-blue-400 bg-opacity-50 px-6 py-2 text-center text-inherit placeholder-slate-200 shadow-lg outline-none backdrop-blur-md"
|
}, [router, status])
|
||||||
type="Password"
|
|
||||||
name="name"
|
|
||||||
placeholder="Password"
|
|
||||||
value={password}
|
|
||||||
onChange={(e) => setPassword(e.target.value)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="mt-8 flex justify-center text-lg text-black">
|
|
||||||
<button
|
|
||||||
type="submit"
|
|
||||||
className="rounded-3xl bg-blue-400 bg-opacity-50 px-10 py-2 text-white shadow-xl backdrop-blur-md transition-colors duration-300 hover:bg-blue-600"
|
|
||||||
>
|
|
||||||
Login
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
)
|
|
||||||
|
|
||||||
return (
|
function login(provider: "email" | "azure-ad") {
|
||||||
<>
|
return () => {
|
||||||
<div className="flex flex-col items-center rounded-3xl bg-slate-800/50 px-16 py-8 shadow-lg drop-shadow-md">
|
signIn(provider, { email, callbackUrl: "/" })
|
||||||
<FontAwesomeIcon
|
|
||||||
className={classNames("text-6xl", iconClasses[state])}
|
|
||||||
icon={icons[state]}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="mt-8 flex justify-center text-lg text-black">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className="rounded-3xl bg-blue-400 bg-opacity-50 px-10 py-2 text-white shadow-xl backdrop-blur-md transition-colors duration-300 hover:bg-blue-600"
|
|
||||||
onClick={() => {
|
|
||||||
setState(ProcessStates.waiting)
|
|
||||||
setError("")
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<FontAwesomeIcon className="-ml-4 mr-4" icon={faArrowLeft} />
|
|
||||||
Return
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function login(e: FormEvent<HTMLFormElement>) {
|
|
||||||
e.preventDefault()
|
|
||||||
setState(ProcessStates.loading)
|
|
||||||
await fetch("/api/login", {
|
|
||||||
method: "POST",
|
|
||||||
body: JSON.stringify({ username, password }),
|
|
||||||
})
|
|
||||||
.then((res) => {
|
|
||||||
if (res.status === 200) setState(ProcessStates.success)
|
|
||||||
if (res.status === 401) setState(ProcessStates.wrong)
|
|
||||||
})
|
|
||||||
.catch((err: Error) => {
|
|
||||||
setState(ProcessStates.error)
|
|
||||||
setError(err.message)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex h-screen w-full items-center justify-center bg-gray-900 bg-[url('/images/wallpaper.jpg')] bg-cover bg-center bg-no-repeat">
|
<div className="flex h-screen w-full items-center justify-center bg-gray-900 bg-[url('/images/wallpaper.jpg')] bg-cover bg-center bg-no-repeat">
|
||||||
<div className="rounded-xl bg-gray-800 bg-opacity-50 px-16 py-10 shadow-lg backdrop-blur-md max-sm:px-8">
|
<div className="rounded-xl bg-gray-800 bg-opacity-60 px-16 py-10 text-white shadow-lg backdrop-blur-md max-sm:px-8">
|
||||||
<div className="text-white">
|
|
||||||
<div className="mb-8 flex flex-col items-center">
|
<div className="mb-8 flex flex-col items-center">
|
||||||
<img
|
<img
|
||||||
className="rounded-full shadow-lg"
|
className="rounded-full shadow-lg"
|
||||||
|
@ -133,12 +67,76 @@ function Login() {
|
||||||
alt="Avatar"
|
alt="Avatar"
|
||||||
/>
|
/>
|
||||||
<h1 className="mb-2 text-2xl">Leaky Ships</h1>
|
<h1 className="mb-2 text-2xl">Leaky Ships</h1>
|
||||||
<span className="text-gray-300">
|
<span className="text-gray-300">Choose Login Method</span>
|
||||||
{error ? error : messages[state]}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
{elem()}
|
{errorType && <hr className="mb-8 border-gray-400" />}
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<label htmlFor="email" className="mx-2 text-lg">
|
||||||
|
Email
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
className="my-1 rounded-lg border-2 border-gray-500 bg-slate-800 bg-opacity-60 px-6 py-2 text-center text-inherit placeholder-slate-400 shadow-lg outline-none backdrop-blur-md focus-within:border-blue-500"
|
||||||
|
type="text"
|
||||||
|
name="email"
|
||||||
|
placeholder="user@example.com"
|
||||||
|
value={email}
|
||||||
|
onChange={(e) => setEmail(e.target.value)}
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
onClick={login("email")}
|
||||||
|
className="my-1 rounded-lg bg-blue-500 bg-opacity-75 px-10 py-3 text-white shadow-inner drop-shadow-md backdrop-blur-md transition-colors duration-300 hover:bg-blue-600"
|
||||||
|
>
|
||||||
|
Sign in with Email
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-row items-center">
|
||||||
|
<hr className="w-full" />
|
||||||
|
<span className="mx-4 my-2">or</span>
|
||||||
|
<hr className="w-full" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="my-2 flex flex-col rounded-lg bg-gradient-to-tr from-[#fff8] via-[#fffd] to-[#fff8] p-4 shadow-lg drop-shadow-md">
|
||||||
|
<a
|
||||||
|
href="https://gbs-grafschaft.de/"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
src="/images/logo-gbs.png"
|
||||||
|
loading="lazy"
|
||||||
|
alt="Gewerbliche Berufsbildende Schulen"
|
||||||
|
className="m-4 mt-2 w-60 justify-center"
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
|
<button
|
||||||
|
onClick={login("azure-ad")}
|
||||||
|
className="flex w-full justify-evenly rounded-lg border border-gray-400 bg-slate-100 px-5 py-3 text-black drop-shadow-md duration-300 hover:bg-slate-200"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
src="/images/Microsoft_icon.svg"
|
||||||
|
loading="lazy"
|
||||||
|
height="24"
|
||||||
|
width="24"
|
||||||
|
alt="Microsoft_icon"
|
||||||
|
/>
|
||||||
|
<span>Sign in with Microsoft</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{errorType && <hr className="mt-8 border-gray-400" />}
|
||||||
|
{errorType && (
|
||||||
|
<div className="flex flex-col items-center">
|
||||||
|
<button
|
||||||
|
onClick={() => router.push("/")}
|
||||||
|
className="mt-10 rounded-lg border-2 border-gray-400 bg-gray-500 bg-opacity-75 px-16 py-2 text-white shadow-inner drop-shadow-md backdrop-blur-md transition-colors duration-300 hover:border-blue-600"
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon icon={faLeftLong} />
|
||||||
|
<span className="mx-4 font-bold">Return</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,15 +1,14 @@
|
||||||
import { faSpinnerThird } from "@fortawesome/pro-solid-svg-icons"
|
import { signOut, useSession } from "next-auth/react"
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
|
|
||||||
import { useRouter } from "next/router"
|
import { useRouter } from "next/router"
|
||||||
import React, { useEffect } from "react"
|
import React, { useEffect } from "react"
|
||||||
|
|
||||||
function Logout() {
|
function Logout() {
|
||||||
|
const { status } = useSession()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const timeout = setTimeout(() => router.push("/dev"), 2000)
|
if (status === "unauthenticated") router.push("/signin")
|
||||||
return () => clearTimeout(timeout)
|
}, [router, status])
|
||||||
}, [router])
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex h-screen w-full items-center justify-center bg-gray-900 bg-[url('/images/wallpaper.jpg')] bg-cover bg-center bg-no-repeat">
|
<div className="flex h-screen w-full items-center justify-center bg-gray-900 bg-[url('/images/wallpaper.jpg')] bg-cover bg-center bg-no-repeat">
|
||||||
|
@ -23,12 +22,17 @@ function Logout() {
|
||||||
alt="Avatar"
|
alt="Avatar"
|
||||||
/>
|
/>
|
||||||
<h1 className="mb-2 text-2xl">Leaky Ships</h1>
|
<h1 className="mb-2 text-2xl">Leaky Ships</h1>
|
||||||
<span className="text-gray-300">Logging out...</span>
|
<span className="text-gray-300">Signout</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col justify-start gap-4">
|
||||||
|
<span>Are you sure you want to sign out?</span>
|
||||||
|
<button
|
||||||
|
onClick={() => signOut({ callbackUrl: "/" })}
|
||||||
|
className="rounded-lg bg-blue-500 bg-opacity-75 px-10 py-3 text-white shadow-inner drop-shadow-md backdrop-blur-md transition-colors duration-300 hover:bg-blue-600"
|
||||||
|
>
|
||||||
|
Sign out
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<FontAwesomeIcon
|
|
||||||
className="mx-24 my-16 animate-spin text-6xl"
|
|
||||||
icon={faSpinnerThird}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -138,8 +138,9 @@ export default function Start() {
|
||||||
<Logo />
|
<Logo />
|
||||||
<BurgerMenu />
|
<BurgerMenu />
|
||||||
<div className="flex flex-col items-center rounded-xl border-4 border-black bg-grayish px-4 py-6 shadow-lg sm:mx-8 sm:p-12 md:w-full">
|
<div className="flex flex-col items-center rounded-xl border-4 border-black bg-grayish px-4 py-6 shadow-lg sm:mx-8 sm:p-12 md:w-full">
|
||||||
|
<div className="flex w-full justify-between">
|
||||||
<button
|
<button
|
||||||
className="-mt-2 w-20 self-start rounded-xl border-b-4 border-shield-gray bg-voidDark text-2xl text-grayish duration-100 active:border-b-0 active:border-t-4 sm:-mt-6 sm:w-40 sm:px-2 sm:text-5xl"
|
className="-mt-2 h-14 w-20 self-start rounded-xl border-b-4 border-shield-gray bg-voidDark text-2xl text-grayish duration-100 active:border-b-0 active:border-t-4 sm:-mt-6 sm:w-40 sm:px-2 sm:text-5xl"
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
router.push("/")
|
router.push("/")
|
||||||
|
@ -148,6 +149,19 @@ export default function Start() {
|
||||||
>
|
>
|
||||||
<FontAwesomeIcon icon={faLeftLong} />
|
<FontAwesomeIcon icon={faLeftLong} />
|
||||||
</button>
|
</button>
|
||||||
|
{!session?.user.id && (
|
||||||
|
<button
|
||||||
|
className="-mt-2 h-14 w-20 self-start rounded-xl border-b-4 border-orange-500 bg-yellow-500 text-2xl active:border-b-0 active:border-t-4 sm:-mt-6 sm:w-40 sm:px-2 sm:text-4xl"
|
||||||
|
onClick={() =>
|
||||||
|
setTimeout(() => {
|
||||||
|
router.push("/signin")
|
||||||
|
}, 200)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Login
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
<div className="flex flex-col items-center gap-6 sm:gap-12">
|
<div className="flex flex-col items-center gap-6 sm:gap-12">
|
||||||
<OptionButton
|
<OptionButton
|
||||||
callback={() => gameFetch()}
|
callback={() => gameFetch()}
|
||||||
|
|
1
leaky-ships/public/images/Microsoft_icon.svg
Normal file
1
leaky-ships/public/images/Microsoft_icon.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 21 21"><path fill="#f35325" d="M0 0h10v10H0z"/><path fill="#81bc06" d="M11 0h10v10H11z"/><path fill="#05a6f0" d="M0 11h10v10H0z"/><path fill="#ffba08" d="M11 11h10v10H11z"/></svg>
|
After Width: | Height: | Size: 232 B |
BIN
leaky-ships/public/images/logo-gbs.png
Normal file
BIN
leaky-ships/public/images/logo-gbs.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 109 KiB |
Loading…
Add table
Add a link
Reference in a new issue