Skip to content

Commit

Permalink
feat: functional login
Browse files Browse the repository at this point in the history
  • Loading branch information
corp-0 committed Mar 5, 2024
1 parent bff5d8d commit bc505e9
Show file tree
Hide file tree
Showing 19 changed files with 380 additions and 208 deletions.
131 changes: 94 additions & 37 deletions app/(account)/login/page.tsx
Original file line number Diff line number Diff line change
@@ -1,49 +1,106 @@
import React from "react";
"use client"

import React, {useContext, useEffect, useState} from "react";
import Button from "../../common/uiLibrary/Button";
import AlternativeActions from "../../common/uiLibrary/forms/alternativeActions";
import FormContainer from "../../common/uiLibrary/Layouters/formContainer";
import TextField from "../../common/uiLibrary/forms/textField";
import ContactInformation from "../../(home)/contactInformation";

import {AuthorizerContext} from "../../../context/AuthorizerContextProvider";
import {isFieldError, isGeneralError} from "../../../lib/auth/guards";
import GenericLoading from "../../common/uiLibrary/genericLoading";
import {redirect} from "next/navigation";

const LoginPage = () => {
return (
<div className='flex flex-col 'style={{minHeight: 'calc(100vh - 60px)'}}>
<div className='flex-grow'>
<FormContainer title='Login'>
<TextField
label='Your email'
id='email'
type='email'
placeholder='[email protected]'
required
shadow
/>

<TextField
label='Your password'
id='password'
type='password'
placeholder='********'
required
shadow
/>

<Button type="submit" className="mt-4 w-full" filled>Log in</Button>

<AlternativeActions
links={
[
{link: '/register', linkText: 'Don\'t have an account?'},
{link: '/reset-password', linkText: 'Forgot your password?'}
]
}
/>
</FormContainer>
const {state, credentialsLogin} = useContext(AuthorizerContext);
const [isLoading, setIsLoading] = useState(false);
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');

useEffect(() => {
if (state.isLoggedIn) {
redirect('/');
}
}, [state.isLoggedIn])

const handleSubmit = async () => {
setIsLoading(true);
await credentialsLogin(email, password);
setIsLoading(false);
}

const errorMessage = (message: string) => {
return (
<div className='flex flex-col p-4 gap-4'>
<h3 className="text-lg text-center font-medium text-red-700">Oops!</h3>
<p>{message}</p>
</div>
)
}

const loginForm = () => {
return (
<div className='flex flex-col' style={{minHeight: 'calc(100vh - 60px)'}}>
<div className='flex-grow relative'>
{isLoading &&
<div className='absolute top-0 left-0 w-full h-full flex flex-col justify-center items-center bg-black bg-opacity-70 z-10'>
<GenericLoading/>
Loading...
</div>
}
<FormContainer onSubmit={(e) => e.preventDefault()} title='Login'>
{state.error && isGeneralError(state.error) && errorMessage(state.error.error)}

<TextField
value={email}
label='Your email'
type='email'
placeholder='[email protected]'
required
shadow
onChange={(e) => {
setEmail(e.target.value)
}}
helperText={state.error && isFieldError(state.error) && state.error.error.email &&
<div className='text-red-700'>{state.error.error.email}</div>
}
/>

<TextField
value={password}
label='Your password'
id='password'
type='password'
placeholder='********'
required
shadow
onChange={(e) => {
setPassword(e.target.value)
}}
helperText={state.error && isFieldError(state.error) && state.error.error.password &&
<div className='text-red-700'>{state.error.error.password}</div>
}
/>

<Button onClick={handleSubmit} className="mt-4 w-full" filled>Log in</Button>

<AlternativeActions
links={
[
{link: '/register', linkText: 'Don\'t have an account?'},
{link: '/reset-password', linkText: 'Forgot your password?'}
]
}
/>
</FormContainer>
</div>
<ContactInformation/>
</div>
<ContactInformation/>
</div>
)
}

);
return !state.isLoggedIn && loginForm();
}

export default LoginPage;
14 changes: 5 additions & 9 deletions app/(account)/logout/page.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,16 @@
"use client"

import {useContext, useEffect} from "react";
import {logout} from "../../../lib/auth/authorization";
import {redirect} from "next/navigation";
import {AuthorizerContext} from "../../../context/authorizerContext";
import {AuthorizerContext} from "../../../context/AuthorizerContextProvider";

const LogoutPage = () => {

const {revalidateAuth} = useContext(AuthorizerContext);
const authState = useContext(AuthorizerContext);

useEffect(() => {
logout().then(_ => {
revalidateAuth();
redirect("/");
}
);
authState.logout().then(() => {
redirect("login");
});
}, []);
}

Expand Down
1 change: 0 additions & 1 deletion app/(account)/reset-password/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ export const resendMailConfirmationToken = async (prevState: ResendMailResponse,
body: JSON.stringify({ email }),
});

console.log(prevState)
if (!response.ok) {
return { success: false, email: email, message: 'An unexpected error occurred'};
}
Expand Down
8 changes: 4 additions & 4 deletions app/(home)/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import ContactInformation from "./contactInformation";
import FeaturesList, {FeatureData} from "./featuresList";
import React from "react";
import {RiGamepadLine, RiRefreshLine, RiRocket2Line, RiTeamLine} from "react-icons/ri";
import HeroRandomImageClient from "./HeroRandomImageClient";
import dynamic from 'next/dynamic';

const mainText = "Welcome to Unitystation!";
const secondaryText = "Free and open-source remake of the cult classic Space Station 13, made in Unity Engine.";
Expand Down Expand Up @@ -49,19 +49,19 @@ const fetchLatestBlogPost = async (): Promise<BlogPost[]> => {
return resPage1.results.concat(resPage2.results);
}


const HomePage: () => Promise<JSX.Element> = async () => {

const latestBlogPosts: BlogPost[] = await fetchLatestBlogPost();
const NoSsrHeroImageClient = dynamic(() => import('./HeroRandomImageClient'), {ssr: false});

return (
<>
<PageSection className="gap-16">
<HeroRandomImageClient>
<NoSsrHeroImageClient>
<LandingText mainText={mainText} secondaryText={secondaryText}/>
<DownloadButtonClient/>
<LandingButtonsServer/>
</HeroRandomImageClient>
</NoSsrHeroImageClient>
<FeaturesList features={features}/>
</PageSection>
<PageSection verticalCenter={false}>
Expand Down
49 changes: 34 additions & 15 deletions app/common/defaultNavbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,40 +3,59 @@
import {Dropdown, Navbar} from 'flowbite-react';
import React, {useContext} from "react";
import {GoLinkExternal} from "react-icons/go";
import {AuthorizerContext} from "../../context/authorizerContext";
import {AuthorizerContext} from "../../context/AuthorizerContextProvider";
import Link from "next/link";


const playerWiki = 'https://wiki.unitystation.org'
const devWiki = 'https://unitystation.github.io/unitystation/'

export default function DefaultNavbar() {
const {isLoggedIn, authContext, error} = useContext(AuthorizerContext);
const username = authContext?.account.username;
const {state} = useContext(AuthorizerContext);
const username = state.authContext?.account.username;

const loggedOptions = () => (
<>
<Dropdown.Item>My Account</Dropdown.Item>
<Dropdown.Item>
<Link href='logout'>Logout</Link>
</Dropdown.Item>
</>
)

const notLoggedOptions = () => (
<>
<Dropdown.Item>
<Link href='login'>Login/Register</Link>
</Dropdown.Item>
<Dropdown.Divider/>
<Dropdown.Item>
<Link href='reset-password'>Reset password</Link>
</Dropdown.Item>
</>
)

return (
<Navbar fluid rounded className="dark:bg-gray-900">
<Navbar.Brand/>
<Navbar.Toggle/>

<div className="flex md:order-2">
<Dropdown label={username || 'Account'} color='#1F2937' >
{!isLoggedIn && <Dropdown.Item href="/login">Login/register</Dropdown.Item>}
{isLoggedIn && <Dropdown.Item>My Account</Dropdown.Item>}
{isLoggedIn && <Dropdown.Item href="/logout">Logout</Dropdown.Item>}
<Dropdown.Divider/>
<Dropdown.Item href="/reset-password">Reset password</Dropdown.Item>
<Dropdown label={username || 'Account'} color='#1F2937'>
{state.isLoggedIn ? loggedOptions() : notLoggedOptions()}
</Dropdown>
</div>

<Navbar.Collapse>
<Navbar.Link href="/">
<Link href='/'>
<p>Home</p>
</Navbar.Link>
<Navbar.Link href="/blog">
</Link>
<Link href='blog'>
<p>Blog</p>
</Navbar.Link>
<Navbar.Link href="/changelog">
</Link>
<Link href='changelog'>
<p>Changelog</p>
</Navbar.Link>
</Link>
<Navbar.Link href={playerWiki} target="_blank">
<div className="flex flex-row gap-1">
<p>Player&apos;s wiki</p>
Expand Down
10 changes: 5 additions & 5 deletions app/common/uiLibrary/Button.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
import {IconType} from "react-icons";
import classNames from "classnames";
import LayoutChildren from "../../../types/layoutChildren";
import React from "react";

interface ButtonProps extends LayoutChildren{
interface ButtonProps extends React.HTMLAttributes<HTMLButtonElement>{
filled: boolean,
type: 'button' | 'submit' | 'reset',
type?: 'button' | 'submit' | 'reset',
iconLeft?: IconType,
iconRight?: IconType,
className?: string
}

const Button = (props: ButtonProps) => {
const {filled, iconLeft: IconLeft, iconRight: IconRight, ...rest} = props;
const {type = 'button', filled, iconLeft: IconLeft, iconRight: IconRight} = props;
const filledClass = 'bg-gray-800 border-2 border-transparent text-white text-md mr-4 hover:bg-gray-900';
const outlineClass = 'bg-gray-800 bg-opacity-30 border-2 border-gray-800 text-white text-md hover:bg-gray-800';
const classes = classNames(
Expand All @@ -21,7 +21,7 @@ const Button = (props: ButtonProps) => {
)

return (
<button className={classes}>{props.children}</button>
<button onClick={props.onClick} type={type} className={classes}>{props.children}</button>
)

}
Expand Down
2 changes: 2 additions & 0 deletions app/common/uiLibrary/forms/textField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ const TextField = (props: TextFieldProps) => {
<Label htmlFor={props.id} value={props.label}/>
</div>
<TextInput
value={props.value}
className='w-full'
id={props.id}
name={props.name}
Expand All @@ -25,6 +26,7 @@ const TextField = (props: TextFieldProps) => {
required={props.required}
shadow={shadow}
helperText={props.helperText}
onChange={props.onChange}
/>
</div>
)
Expand Down
12 changes: 12 additions & 0 deletions app/common/uiLibrary/genericLoading.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
const GenericLoading = () => {

return (
<div className="flex justify-center items-center">
<div className='dot'></div>
<div className='dot'></div>
<div className='dot'></div>
</div>
)
}

export default GenericLoading;
34 changes: 33 additions & 1 deletion app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ html, body {
color: white;
}

body{
body {
background-image: url("https://unitystationfile.b-cdn.net/Website-Statics/layer1.png");
}

Expand All @@ -33,3 +33,35 @@ a {
box-shadow: var(--tw-shadow);
pointer-events: none;
}

.dot {
width: 40px;
height: 40px;
background-color: whitesmoke;
border-radius: 50%;
margin: 0 10px;
animation: dotAnimation 1.5s infinite cubic-bezier(0.68, -0.55, 0.27, 1.55);
}

@keyframes dotAnimation {
0%, 100% {
transform: translateY(0);
opacity: 0.2;
}
50% {
transform: translateY(-20px);
opacity: 1;
}
}

.dot:nth-child(1) {
animation-delay: 0s;
}

.dot:nth-child(2) {
animation-delay: 0.5s;
}

.dot:nth-child(3) {
animation-delay: 1s;
}
2 changes: 1 addition & 1 deletion app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {Metadata} from "next";
import DefaultNavbar from "./common/defaultNavbar";
import {Analytics} from '@vercel/analytics/react';
import type {Viewport} from 'next'
import {Providers} from "./providers";
import Providers from "../context/providers";

export const metadata: Metadata = {
title: 'Unitystation - The Space Station 13 Remake Made in Unity',
Expand Down
Loading

0 comments on commit bc505e9

Please sign in to comment.