Skip to content

Commit

Permalink
Merge pull request #10 from LinumLabs/feature/dm/FAIR-85-login-page
Browse files Browse the repository at this point in the history
Add Login Page & Main Navbar
  • Loading branch information
MostertCoder authored Feb 7, 2022
2 parents bfe02d1 + 5a7acee commit a167f46
Show file tree
Hide file tree
Showing 28 changed files with 733 additions and 184 deletions.
24 changes: 24 additions & 0 deletions src/api/authentication.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import axios from '@api/customAxios';

interface LoginData {
user_name: string;
password: string;
}

interface LoginResponse {
code: number;
message: string;
}

export async function login(data: LoginData): Promise<LoginResponse> {
return axios.post('user/login', data);
}

export async function logout(): Promise<LoginResponse> {
return axios.post('user/logout');
}

export const userStats = async () => {
const response = await axios.get('user/stat');
return response;
};
10 changes: 10 additions & 0 deletions src/api/customAxios.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import axios from 'axios';

export default axios.create({
withCredentials: true,
baseURL: process.env.NEXT_PUBLIC_FAIROSHOST,
headers: {
'Content-Type': 'application/json',
Accept: 'application/json',
},
});
136 changes: 136 additions & 0 deletions src/components/Buttons/Button/Button.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
import { FC, ReactNode, ReactChild } from 'react';

interface ButtonProps {
type?: 'button' | 'submit';
variant:
| 'primary'
| 'primary-outlined'
| 'secondary'
| 'tertiary'
| 'tertiary-outlined';
label?: string;
icon?: ReactNode;
onClick?: any;
className?: string;
padding?: string;
children?: ReactChild | ReactChild[];
disabled?: boolean;
}

const Button: FC<ButtonProps> = ({
type = 'button',
variant,
label,
icon,
onClick,
className,
padding,
children,
disabled = false,
}) => {
const getVariantStyling = () => {
switch (variant) {
case 'primary':
return (
'bg-color-shade-dark-4-day text-main-purple text-base effect-style-small-button-drop-shadow' +
' ' +
(padding ? '' : 'py-3 px-8')
);
case 'primary-outlined':
return (
'bg-none border border-color-accents-purple-heavy text-color-accents-purple-heavy text-base' +
' ' +
(padding ? '' : 'py-3 px-8')
);
case 'secondary':
return (
'bg-color-shade-white-night text-color-accents-purple-black text-base' +
' ' +
(padding ? '' : 'py-3 px-8')
);
case 'tertiary':
return (
'text-color-accents-purple-black text-xs' +
' ' +
(padding ? '' : 'py-2 px-3')
);
case 'tertiary-outlined':
return (
'bg-none border border-color-accents-purple-heavy text-color-accents-purple-heavy text-xs' +
' ' +
(padding ? '' : 'py-2 px-3')
);
}
};

const getVariantDisabledStyle = () => {
if (disabled) {
switch (variant) {
case 'primary':
return 'text-color-shade-light-3-night disabled:bg-color-shade-dark-4-day';
case 'primary-outlined':
return 'text-color-shade-light-3-night disabled:border-color-shade-light-3-night';
case 'secondary':
return 'bg-none text-color-shade-light-3-night';
case 'tertiary':
return '';
case 'tertiary-outlined':
return '';
}
} else return '';
};

const getVariantSelectedStyle = () => {
if (!disabled) {
switch (variant) {
case 'primary':
return 'focus:shadow-dark-purple focus:bg-color-shade-dark-4 effect-style-small-button-drop-shadow';
case 'primary-outlined':
return 'focus:shadow-dark-purple focus:bg-color-shade-dark-3-day';
case 'secondary':
return 'focus:shadow-dark-purple focus:bg-color-shade-white-night';
case 'tertiary':
return 'focus:text-base';
case 'tertiary-outlined':
return 'focus:shadow-dark-purple focus:bg-color-shade-dark-3-day';
}
} else return '';
};

const getVariantHoverStyle = () => {
if (!disabled) {
switch (variant) {
case 'primary':
return 'hover:shadow-soft-purple hover:bg-color-shade-dark-4 effect-style-small-button-drop-shadow';
case 'primary-outlined':
return 'hover:shadow-soft-purple hover:bg-color-shade-dark-3-day';
case 'secondary':
return 'hover:shadow-soft-purple hover:bg-color-shade-white-night';
case 'tertiary':
return 'hover:text-base';
case 'tertiary-outlined':
return 'hover:shadow-soft-purple hover:bg-color-shade-dark-3-day';
}
} else return '';
};

return (
<button
type={type}
onClick={onClick}
className={`${getVariantStyling()} ${getVariantHoverStyle()} ${getVariantDisabledStyle()} ${getVariantSelectedStyle()} ${className} ${padding} text-center rounded`}
disabled={disabled}
>
{children ? (
children
) : (
<div>
{label}
{icon}
</div>
)}
</button>
);
};

export default Button;
23 changes: 23 additions & 0 deletions src/components/Buttons/UserDropdownToggle/UserDropdownToggle.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { FC, useContext } from 'react';

import UserContext from '@context/UserContext';
import Blockies from 'react-blockies';

interface UserDropdownToggleProps {
onClickHandler: any;
}

const UserDropdownToggle: FC<UserDropdownToggleProps> = ({
onClickHandler,
}) => {
const { address } = useContext(UserContext);

return (
<button className="cursor-pointer" onClick={() => onClickHandler()}>
<Blockies className="inline-block rounded" seed={address} />
</button>
);
};

export default UserDropdownToggle;
4 changes: 4 additions & 0 deletions src/components/Buttons/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import Button from '@components/Buttons/Button/Button';
import UserDropdownToggle from '@components/Buttons/UserDropdownToggle/UserDropdownToggle';

export { Button, UserDropdownToggle };
22 changes: 22 additions & 0 deletions src/components/FeedbackMessage/FeedbackMessage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { FC } from 'react';

interface FeedbackMessageProps {
type: 'success' | 'error';
message: string;
}

const FeedbackMessage: FC<FeedbackMessageProps> = ({ message, type }) => {
return (
<div
className={`${
type === 'success'
? 'text-color-status-positive-day'
: 'text-color-status-negative-day'
} text-sm`}
>
{message}
</div>
);
};

export default FeedbackMessage;
110 changes: 110 additions & 0 deletions src/components/Forms/LoginForm/LoginForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import { FC, useContext, useState } from 'react';
import router from 'next/router';
import { useForm } from 'react-hook-form';

import UserContext from '@context/UserContext';
// import PodContext from '@context/PodContext';

import { login, userStats } from '@api/authentication';

import { AuthenticationHeader } from '@components/Headers';
import { AuthenticationInput } from '@components/Inputs';
import { Button } from '@components/Buttons';
import FeedbackMessage from '@components/FeedbackMessage/FeedbackMessage';

const LoginForm: FC = () => {
const { register, handleSubmit, formState } = useForm();
const { errors } = formState;

const { setUser, setPassword, setAddress } = useContext(UserContext);
// const { clearPodContext } = useContext(PodContext);

const [errorMessage, setErrorMessage] = useState('');

const onSubmit = (data: { user_name: string; password: string }) => {
login(data)
.then(() => {
setUser(data.user_name);
setPassword(data.password);

userStats()
.then((res) => {
setAddress(res.data.reference);
// clearPodContext();
router.push('/gallery');
})
.catch(() => {
setErrorMessage(
'Login failed. Incorrect user credentials, please try again.'
);
});
})
.catch(() => {
setErrorMessage(
'Login failed. Incorrect user credentials, please try again.'
);
});
};

return (
<div className="flex flex-col justify-center items-center">
<AuthenticationHeader
title="Welcome back"
content="Please log in to get access to your photos."
/>

<div className="w-98 mt-8">
<div className="mb-5 text-center">
<FeedbackMessage type="error" message={errorMessage} />
</div>

<form onSubmit={handleSubmit(onSubmit)} className="w-full">
<AuthenticationInput
label="username"
id="user_name"
type="text"
name="user_name"
placeholder="Type here"
useFormRegister={register}
validationRules={{
required: true,
}}
error={errors.user_name}
errorMessage="Username or e-mail is required"
/>

<AuthenticationInput
label="password"
id="password"
type="password"
name="password"
placeholder="Type here"
useFormRegister={register}
validationRules={{
required: true,
}}
error={errors.password}
errorMessage="Password is required"
/>

<div className="mt-14 text-center">
<Button type="submit" variant="secondary" label="Login" />
</div>

<div className="my-6 text-center">
<a
href="https://fairdrive.vercel.app/register"
target="_blank"
rel="noreferrer"
className="font-normal text-xs text-color-accents-purple-black"
>
Don&apos;t have an account?
</a>
</div>
</form>
</div>
</div>
);
};

export default LoginForm;
3 changes: 3 additions & 0 deletions src/components/Forms/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import LoginForm from '@components/Forms/LoginForm/LoginForm';

export { LoginForm };
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { FC } from 'react';

interface AuthenticationHeaderProps {
title: string;
content: string;
}

const AuthenticationHeader: FC<AuthenticationHeaderProps> = ({
title,
content,
}) => {
return (
<div className="w-108 text-center">
<h1 className="mb-4 font-semibold text-3xl text-main-purple leading-10">
{title}
</h1>
<p className="font-normal text-base text-color-accents-plum-black">
{content}
</p>
</div>
);
};

export default AuthenticationHeader;
3 changes: 3 additions & 0 deletions src/components/Headers/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import AuthenticationHeader from '@components/Headers/AuthenticationHeader/AuthenticationHeader';

export { AuthenticationHeader };
Loading

0 comments on commit a167f46

Please sign in to comment.