Skip to content

Commit

Permalink
Implement user registration and logout (#58)
Browse files Browse the repository at this point in the history
  • Loading branch information
Madhawa97 authored Nov 18, 2023
1 parent 9a3eeeb commit 6f6f8b1
Show file tree
Hide file tree
Showing 4 changed files with 312 additions and 13 deletions.
66 changes: 57 additions & 9 deletions src/components/Layout/Navbar/Navbar.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useState } from 'react';
import React, { useContext, useState } from 'react';

import {
AlignRightOutlined,
Expand All @@ -11,12 +11,23 @@ import { Button, Col, Row, Space, Typography } from 'antd';
import styles from './Navbar.module.css';
import MenuDrawer from '../MenuDrawer/MenuDrawer';
import LoginModal from '../../LoginModal';
import RegisterModal from '../../RegisterModal';

import {
UserContext,
type UserContextType,
} from './../../../contexts/UserContext';
import LogoutModal from '../../LogoutModal';

const { Text } = Typography;

const Navbar: React.FC = () => {
const [openMenu, setOpenMenu] = useState(false);
const [isLoginModalVisible, setIsLoginModalVisible] = useState(false);
const [isRegisterModalVisible, setIsRegisterModalVisible] = useState(false);
const [isLogoutModalVisible, setIsLogoutModalVisible] = useState(false);

const { user } = useContext(UserContext) as UserContextType;

const handleLoginModalClose = (): void => {
setIsLoginModalVisible(false);
Expand All @@ -26,6 +37,22 @@ const Navbar: React.FC = () => {
setIsLoginModalVisible(true);
};

const handleRegisterModalClose = (): void => {
setIsRegisterModalVisible(false);
};

const handleRegisterModalOpen = (): void => {
setIsRegisterModalVisible(true);
};

const handleLogoutModalClose = (): void => {
setIsLogoutModalVisible(false);
};

const handleLogoutModalOpen = (): void => {
setIsLogoutModalVisible(true);
};

return (
<>
<Row align={'middle'} justify={'start'}>
Expand All @@ -41,7 +68,7 @@ const Navbar: React.FC = () => {
setOpenMenu(true);
}}
/>
<Col md={16} lg={14} xl={15} xxl={16}>
<Col md={16} lg={12} xl={14} xxl={16}>
<Space direction="horizontal">
<div className={styles.navbarItemContainer}>
<a href="https://sefglobal.org/">
Expand Down Expand Up @@ -99,19 +126,40 @@ const Navbar: React.FC = () => {
<InstagramOutlined className={styles.antIcon} />
</Button>
</a>
<Button
className={styles.loginButton}
onClick={handleLoginModalOpen}
>
Login
</Button>
{user === null ? (
<>
<Button
className={styles.loginButton}
onClick={handleLoginModalOpen}
>
Login
</Button>
<Button
className={styles.loginButton}
onClick={handleRegisterModalOpen}
>
Register
</Button>
</>
) : (
<Button
className={styles.loginButton}
onClick={handleLogoutModalOpen}
>
Logout
</Button>
)}
</Space>
</Col>
</Row>
<MenuDrawer openMenu={openMenu} setOpenMenu={setOpenMenu} />
{isLoginModalVisible ? (
<LoginModal onClose={handleLoginModalClose} />
<LoginModal handleClose={handleLoginModalClose} />
) : null}
{isRegisterModalVisible ? (
<RegisterModal handleClose={handleRegisterModalClose} />
) : null}
{isLogoutModalVisible && <LogoutModal onClose={handleLogoutModalClose} />}
</>
);
};
Expand Down
8 changes: 4 additions & 4 deletions src/components/LoginModal/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@ import closeIcon from '../../assets/svg/closeIcon.svg';
import styles from './LoginModal.module.css';

interface LoginModalProps {
onClose: () => void;
handleClose: () => void;
}

const LoginModal: React.FC<LoginModalProps> = ({ onClose }) => {
const LoginModal: React.FC<LoginModalProps> = ({ handleClose }) => {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const { setUserContext } = useContext(UserContext) as UserContextType;
Expand All @@ -31,7 +31,7 @@ const LoginModal: React.FC<LoginModalProps> = ({ onClose }) => {
)
.then((response: AxiosResponse<Profile>) => {
setUserContext(response.data);
onClose();
handleClose();
})
.catch((error) => {
if (error.response.status !== 401) {
Expand Down Expand Up @@ -64,7 +64,7 @@ const LoginModal: React.FC<LoginModalProps> = ({ onClose }) => {
>
<button
className="absolute top-2 right-2 p-2 text-gray-700 hover:text-gray-900 focus:outline-none"
onClick={onClose}
onClick={handleClose}
>
<img className="w-6 h-6" src={closeIcon} alt="Modal Close Icon" />
</button>
Expand Down
73 changes: 73 additions & 0 deletions src/components/LogoutModal/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import React, { useContext } from 'react';
import { UserContext, type UserContextType } from '../../contexts/UserContext';
import axios from 'axios';
import { API_URL } from '../../constants';

interface LogoutModalProps {
onClose: () => void;
}

const LogoutModal: React.FC<LogoutModalProps> = ({ onClose }) => {
const { setUserContext } = useContext(UserContext) as UserContextType;

const handleLogoutConfirm = (): void => {
axios
.get(`${API_URL}/auth/logout`, {
withCredentials: true,
})
.then(() => {
setUserContext(null);
onClose();
})
.catch((error) => {
if (error.response.status !== 401) {
console.error({
message: 'Something went wrong during logout',
description: error.toString(),
});
} else {
setUserContext(null);
onClose();
}
});
};

return (
<div className="fixed z-10 inset-0 overflow-y-auto">
<div className="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
<div className="fixed inset-0 transition-opacity">
<div className="absolute inset-0 bg-gray-500 opacity-75"></div>
</div>
<span className="hidden sm:inline-block sm:align-middle sm:h-screen"></span>
<div
className="inline-block align-bottom bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full"
role="dialog"
aria-modal="true"
aria-labelledby="modal-headline"
>
<div className="p-4 md:p-5 text-center">
<h3 className="mb-5 text-lg font-normal text-gray-500 dark:text-gray-400">
Are you sure you want to logout from ScholarX?
</h3>
<button
onClick={handleLogoutConfirm}
type="button"
className="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 dark:focus:ring-blue-800 font-medium rounded-lg text-sm inline-flex items-center px-5 py-2.5 text-center me-2"
>
Confirm
</button>
<button
onClick={onClose}
type="button"
className="text-gray-500 bg-white hover:bg-gray-100 focus:ring-4 focus:outline-none focus:ring-gray-200 rounded-lg border border-gray-200 text-sm font-medium px-5 py-2.5 hover:text-gray-900 focus:z-10 dark:bg-gray-700 dark:text-gray-300 dark:border-gray-500 dark:hover:text-white dark:hover:bg-gray-600 dark:focus:ring-gray-600"
>
Cancel
</button>
</div>
</div>
</div>
</div>
);
};

export default LogoutModal;
178 changes: 178 additions & 0 deletions src/components/RegisterModal/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
import React, { useState } from 'react';
import axios from 'axios';
import { API_URL } from '../../constants';
import closeIcon from '../../assets/svg/closeIcon.svg';

interface RegisterModalProps {
handleClose: () => void;
}

const RegisterModal: React.FC<RegisterModalProps> = ({ handleClose }) => {
const [firstName, setFirstName] = useState('');
const [lastName, setLastName] = useState('');
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [error, setError] = useState<string | null>(null);

const handleRegister = (e: React.FormEvent): void => {
e.preventDefault();
setError(null);

axios
.post(
`${API_URL}/auth/register`,
{
email,
password,
first_name: firstName,
last_name: lastName,
},
{
withCredentials: true,
}
)
.then(() => {
handleClose();
})
.catch((error) => {
if (error.response.status !== 401) {
setError(error.response.data.message);
console.error({
message: 'Something went wrong when registering the user',
description: error,
});
} else {
console.error('Registration error:', error);
}
});
};

return (
<div className="fixed z-10 inset-0 overflow-y-auto">
<div className="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
<div className="fixed inset-0 transition-opacity">
<div className="absolute inset-0 bg-gray-500 opacity-75"></div>
</div>
<span className="hidden sm:inline-block sm:align-middle sm:h-screen"></span>
&#8203;
<div
className="inline-block align-bottom bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full"
role="dialog"
aria-modal="true"
aria-labelledby="modal-headline"
>
<button
className="absolute top-2 right-2 p-2 text-gray-700 hover:text-gray-900 focus:outline-none"
onClick={handleClose}
>
<img className="w-6 h-6" src={closeIcon} alt="Modal Close Icon" />
</button>

<div className="bg-white p-6 space-y-8 rounded-lg shadow-xl">
<h2 className="text-2xl font-bold text-gray-900 text-center">
Register to ScholarX
</h2>
<form className="mt-8 space-y-6" onSubmit={handleRegister}>
<div>
<label
htmlFor="firstname lastname"
className="block mb-2 text-sm font-medium text-gray-900"
>
Name
</label>
<div className="flex">
<input
type="text"
name="firstname"
id="firstname"
className="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-1/2 p-2.5 mr-2"
placeholder="first name"
value={firstName}
onChange={(e) => {
setFirstName(e.target.value);
}}
required
/>
<input
type="text"
name="lastname"
id="lastname"
className="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-1/2 p-2.5"
placeholder="last name"
value={lastName}
onChange={(e) => {
setLastName(e.target.value);
}}
required
/>
</div>
</div>
<div>
<label
htmlFor="email"
className="block mb-2 text-sm font-medium text-gray-900"
>
Your email
</label>
<input
type="email"
name="email"
id="email"
className="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5"
placeholder="[email protected]"
value={email}
onChange={(e) => {
setEmail(e.target.value);
}}
required
/>
</div>
<div>
<label
htmlFor="password"
className="block mb-2 text-sm font-medium text-gray-900"
>
Password
</label>
<input
type="password"
name="password"
id="password"
placeholder="••••••••"
className="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5"
value={password}
onChange={(e) => {
setPassword(e.target.value);
}}
required
/>
</div>

{error !== null ? (
<div
className="p-4 mb-4 text-sm text-red-800 rounded-lg bg-red-50 dark:text-red-400"
role="alert"
>
{error}
</div>
) : null}

<button
type="submit"
className="w-full px-5 py-3 text-base font-medium text-center text-white bg-blue-700 rounded-lg hover:bg-blue-800 focus:ring-4 focus:ring-blue-300"
>
Register to ScholarX
</button>
<div className="text-sm font-medium text-gray-900">
Already have an account?{' '}
<a className="text-blue-600 hover:underline">Login</a>
</div>
</form>
</div>
</div>
</div>
</div>
);
};

export default RegisterModal;

0 comments on commit 6f6f8b1

Please sign in to comment.