From acfd26baea1af1e1d59013a1ab4d68795582b6b7 Mon Sep 17 00:00:00 2001 From: KimiaMontazeri Date: Wed, 29 Nov 2023 09:56:54 +0330 Subject: [PATCH 1/8] fix(app-bar): hide logout in drawer appbar --- frontend/src/components/app-bar/AppBar.jsx | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/frontend/src/components/app-bar/AppBar.jsx b/frontend/src/components/app-bar/AppBar.jsx index 464574d..2d50df2 100644 --- a/frontend/src/components/app-bar/AppBar.jsx +++ b/frontend/src/components/app-bar/AppBar.jsx @@ -86,11 +86,13 @@ export default function DrawerAppBar() { ) ); })} - - - - - + {shouldShowLogoutButton && ( + + + + + + )} ); From 72529e7aec5e77d6c101bf8945aee6d5a7ef0506 Mon Sep 17 00:00:00 2001 From: KimiaMontazeri Date: Wed, 29 Nov 2023 10:02:10 +0330 Subject: [PATCH 2/8] fix(item-card): remove default values for some props --- .../src/components/item-card/item-card.jsx | 352 +++++++++--------- 1 file changed, 175 insertions(+), 177 deletions(-) diff --git a/frontend/src/components/item-card/item-card.jsx b/frontend/src/components/item-card/item-card.jsx index da082af..2582c12 100644 --- a/frontend/src/components/item-card/item-card.jsx +++ b/frontend/src/components/item-card/item-card.jsx @@ -1,201 +1,199 @@ -import React, {useState} from 'react'; -import {CreditCard, Person, SignalCellularAlt} from '@mui/icons-material'; -import {Button, Card, CardActions, CardContent, CardHeader, Chip, Divider, Stack, Typography} from '@mui/material'; +import React, { useState } from 'react'; +import { CreditCard, Person, SignalCellularAlt } from '@mui/icons-material'; +import { Button, Card, CardActions, CardContent, CardHeader, Chip, Divider, Stack, Typography } from '@mui/material'; import PropTypes from 'prop-types'; +import { Helper } from '../../utils/Helper.js'; import MoreInfoModal from './more-info-modal'; -import {Helper} from "../../utils/Helper.js"; -export const Presenter = ({presenterName}) => ( - - - - {presenterName} - - +export const Presenter = ({ presenterName }) => ( + + + + {presenterName} + + ); -export const Cost = ({cost}) => ( - - - - {cost} T - - +export const Cost = ({ cost }) => ( + + + + {cost} T + + ); -const CapacityChip = ({capacity, isFull, remainingCapacity}) => ( - +const CapacityChip = ({ capacity, isFull, remainingCapacity }) => ( + ); -const Level = ({name, color}) => ( - - - - {name} - - +const Level = ({ name, color }) => ( + + + + {name} + + ); export const levelComponentMapping = { - Elementary: , - Intermediate: , - Advanced: , + Elementary: , + Intermediate: , + Advanced: , }; const ItemCard = ({ - title = 'Title', - isWorkshop = true, - description = 'Default Description', - startDate = '2021-08-27 06:00', - endDate = '2021-08-31 18:00', - presenterName = 'Presenter Name', - level = 'elementary', - cost = 50000, - purchaseState = 0, // 0 -> not purchased, 1 -> in cart, 2 -> purchased - hasProject = true, - prerequisites = 'List of prerequisites', - syllabus = 'List of syllabus', - remainingCapacity = 50, - capacity = 50, - isFull = false, - addToCalendarLink = 'https://google.com', - onClickAddToCart = () => { - }, - onClickRemoveFromCart = () => { - }, - }) => { - const [moreInfoModalVisibility, setMoreInfoModalVisibility] = useState(false); - const hasBought = purchaseState === 2; + title, + isWorkshop = true, + description, + startDate, + endDate, + presenterName, + level, + cost = 50000, + purchaseState = 0, // 0 -> not purchased, 1 -> in cart, 2 -> purchased + hasProject = true, + prerequisites, + syllabus, + remainingCapacity = 50, + capacity = 50, + isFull = false, + addToCalendarLink, + onClickAddToCart = () => {}, + onClickRemoveFromCart = () => {}, +}) => { + const [moreInfoModalVisibility, setMoreInfoModalVisibility] = useState(false); + const hasBought = purchaseState === 2; - const handleClickOnMoreInfo = () => { - setMoreInfoModalVisibility(true); - }; + const handleClickOnMoreInfo = () => { + setMoreInfoModalVisibility(true); + }; - const getActionComponent = () => { - switch (purchaseState) { - case 0: - return ( - - ); - case 1: - return ( - - ); - case 2: - return ( - - ); - default: - return null; - } - }; + const getActionComponent = () => { + switch (purchaseState) { + case 0: + return ( + + ); + case 1: + return ( + + ); + case 2: + return ( + + ); + default: + return null; + } + }; - return ( - <> - setMoreInfoModalVisibility(false)} - title={title} - presenterName={presenterName} - cost={cost} - purchaseState={purchaseState} - description={description} - level={level} - hasProject={hasProject} - prerequisites={prerequisites} - syllabus={syllabus} - isFull={isFull} - addToCalendarLink={addToCalendarLink} - onClickAddToCart={onClickAddToCart} - onClickRemoveFromCart={onClickRemoveFromCart} - /> - - - - - {Helper.omitLongString(description, 50)} - - - From: {new Date(startDate).toLocaleString('fa-IR-u-nu-latn')} - - - To: {new Date(endDate).toLocaleString('fa-IR-u-nu-latn')} - - - - {levelComponentMapping[level]} - {!hasBought && } - {!hasBought && } - - - - {getActionComponent()} - - - - ); + return ( + <> + setMoreInfoModalVisibility(false)} + title={title} + presenterName={presenterName} + cost={cost} + purchaseState={purchaseState} + description={description} + level={level} + hasProject={hasProject} + prerequisites={prerequisites} + syllabus={syllabus} + isFull={isFull} + addToCalendarLink={addToCalendarLink} + onClickAddToCart={onClickAddToCart} + onClickRemoveFromCart={onClickRemoveFromCart} + /> + + + + + {Helper.omitLongString(description, 50)} + + + From: {new Date(startDate).toLocaleString('fa-IR-u-nu-latn')} + + + To: {new Date(endDate).toLocaleString('fa-IR-u-nu-latn')} + + + + {levelComponentMapping[level]} + {!hasBought && } + {!hasBought && } + + + + {getActionComponent()} + + + + ); }; ItemCard.propTypes = { - title: PropTypes.string, - isWorkshop: PropTypes.bool, - description: PropTypes.string, - startDate: PropTypes.string, - endDate: PropTypes.string, - presenterName: PropTypes.string, - level: PropTypes.string, - cost: PropTypes.number, - purchaseState: PropTypes.number, - hasProject: PropTypes.bool, - prerequisites: PropTypes.string, - syllabus: PropTypes.string, - capacity: PropTypes.number, - isFull: PropTypes.bool, - addToCalendarLink: PropTypes.string, - onClickAddToCart: PropTypes.func, - remainingCapacity: PropTypes.number + title: PropTypes.string, + isWorkshop: PropTypes.bool, + description: PropTypes.string, + startDate: PropTypes.string, + endDate: PropTypes.string, + presenterName: PropTypes.string, + level: PropTypes.string, + cost: PropTypes.number, + purchaseState: PropTypes.number, + hasProject: PropTypes.bool, + prerequisites: PropTypes.string, + syllabus: PropTypes.string, + capacity: PropTypes.number, + isFull: PropTypes.bool, + addToCalendarLink: PropTypes.string, + onClickAddToCart: PropTypes.func, + remainingCapacity: PropTypes.number, }; export default ItemCard; From 6173cb70e686d1a439ca704c2dae937135a3bc63 Mon Sep 17 00:00:00 2001 From: KimiaMontazeri Date: Wed, 29 Nov 2023 10:02:58 +0330 Subject: [PATCH 3/8] fix(more-info-modal): use Typography component to render description --- .../components/item-card/more-info-modal.jsx | 35 ++++++++++++++----- 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/frontend/src/components/item-card/more-info-modal.jsx b/frontend/src/components/item-card/more-info-modal.jsx index c9daac4..eadb23c 100644 --- a/frontend/src/components/item-card/more-info-modal.jsx +++ b/frontend/src/components/item-card/more-info-modal.jsx @@ -1,7 +1,7 @@ import React from 'react'; import { Checklist } from '@mui/icons-material'; -import ListIcon from '@mui/icons-material/List'; import InfoIcon from '@mui/icons-material/Info'; +import ListIcon from '@mui/icons-material/List'; import { Dialog, DialogTitle, @@ -11,7 +11,10 @@ import { Button, Divider, Stack, - Chip, Icon, Box, + Chip, + Icon, + Box, + Typography, } from '@mui/material'; import PropTypes from 'prop-types'; import { Cost, levelComponentMapping, Presenter } from './item-card'; @@ -47,7 +50,7 @@ const MoreInfoModal = ({ presenterName, cost, level, - description, + description, purchaseState, hasProject, prerequisites, @@ -73,13 +76,27 @@ const MoreInfoModal = ({ > {title} -
+
- {description && {description}} + {description && ( + + {description} + + )}
{levelComponentMapping[level]} From 96c0f15bc6e990ae7dd52972cdeb7cfc88250c94 Mon Sep 17 00:00:00 2001 From: KimiaMontazeri Date: Wed, 29 Nov 2023 10:07:19 +0330 Subject: [PATCH 4/8] fix(more-info-modal): render prerequisites and syllabus conditionally --- .../components/item-card/more-info-modal.jsx | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/frontend/src/components/item-card/more-info-modal.jsx b/frontend/src/components/item-card/more-info-modal.jsx index eadb23c..82a54e2 100644 --- a/frontend/src/components/item-card/more-info-modal.jsx +++ b/frontend/src/components/item-card/more-info-modal.jsx @@ -99,12 +99,20 @@ const MoreInfoModal = ({ )}
- {levelComponentMapping[level]} + {level && levelComponentMapping[level]} - - - - + {prerequisites && ( + <> + + + + )} + {syllabus && ( + <> + + + + )} {hasProject && ( <> From 63e4ac1b0276e9b6f02717f7e3a60a43414ccd31 Mon Sep 17 00:00:00 2001 From: KimiaMontazeri Date: Wed, 29 Nov 2023 10:08:08 +0330 Subject: [PATCH 5/8] fix(more-info-modal): use theme colors for border color --- frontend/src/components/item-card/more-info-modal.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/components/item-card/more-info-modal.jsx b/frontend/src/components/item-card/more-info-modal.jsx index 82a54e2..7d8a6bb 100644 --- a/frontend/src/components/item-card/more-info-modal.jsx +++ b/frontend/src/components/item-card/more-info-modal.jsx @@ -91,7 +91,7 @@ const MoreInfoModal = ({ paddingLeft: '5px', marginTop: '0px', marginBottom: '10px', - borderLeft: '1px solid white', + borderLeft: '1px solid var(--light-text-color-lighter)', }} > {description} From 6559279c4bf99545ac092621ca12fc68149308de Mon Sep 17 00:00:00 2001 From: KimiaMontazeri Date: Wed, 29 Nov 2023 10:41:51 +0330 Subject: [PATCH 6/8] fix(signup): call signup and login api if form is validated --- frontend/src/pages/Signup/Signup.jsx | 83 +++++++++++++++------------- 1 file changed, 45 insertions(+), 38 deletions(-) diff --git a/frontend/src/pages/Signup/Signup.jsx b/frontend/src/pages/Signup/Signup.jsx index 35b919c..a1d8340 100644 --- a/frontend/src/pages/Signup/Signup.jsx +++ b/frontend/src/pages/Signup/Signup.jsx @@ -48,42 +48,49 @@ const SignUpForm = ({ onLoginClick }) => { navigate(ROUTES.myAccount); }; - const handleSignUp = useCallback(() => { - const data = {}; - data.name = fullname; - data.phone_number = phoneNumber; - data.account = { - password: password, - email: email, - }; - createUser(data); - }, [createUser, email, fullname, password, phoneNumber]); - - // TODO: add form validation - const handleFormSubmit = (e) => { - e.preventDefault(); + const validateForm = useCallback(() => { if (hasEmailError(email)) { setIsEmailWrong(true); setEmailHelperText('Your email is not valid'); - return; + return false; } const { lengthIsOk, startsWithZeroNine } = validatePhone(phoneNumber); if (!lengthIsOk) { setIsPhoneWrong(true); setPhoneHelperText('Phone number is two short'); - return; + return false; } if (!startsWithZeroNine) { setIsPhoneWrong(true); setPhoneHelperText('Phone number should start with 09'); - return; + return false; } if (password !== secondPass) { setIsSecondPassWrong(true); setSecondPassHelperText('Passwords are not the same'); - return; + return false; } - // TODO: route to my-account page + return true; + }, [email, phoneNumber, password, secondPass]); + + const handleSignUp = useCallback(() => { + const isFormValid = validateForm(); + if (isFormValid) { + const data = { + name: fullname, + phone_number: phoneNumber, + account: { + password, + email, + }, + }; + createUser(data); + } + }, [createUser, email, fullname, password, phoneNumber, validateForm]); + + const handleFormSubmit = (e) => { + e.preventDefault(); + validateForm(); }; useEffect(() => { @@ -211,24 +218,22 @@ const LoginForm = ({ onSignUpClick }) => { const [openToast, setOpenToast] = useState(false); const [toastData, setToastData] = useState(); - const handleFormSubmit = (e) => { + const validateForm = useCallback(() => { if (hasEmailError(email)) { setIsEmailWrong(true); setEmailHelperText('Your email is not valid'); - return; + return false; } + return true; + }, [email]); + + const handleFormSubmit = (e) => { + validateForm(); e.preventDefault(); - // TODO: add API call for login - // TODO: setIsPasswordWrong(true) if pass is wrong - // TODO: setPasswordHelperText('Wrong password') - // TODO: route to my-account page if it's successful }; - const {navigate} = useNavigate() const { issueToken, issueTokenResponse } = useAPI(); - const { - setAccessTokenFromLocalStorage - } = useConfig() + const { setAccessTokenFromLocalStorage } = useConfig(); useEffect(() => { if (issueTokenResponse == null) return; @@ -237,22 +242,24 @@ const LoginForm = ({ onSignUpClick }) => { setOpenToast(true); localStorage['user'] = JSON.stringify(issueTokenResponse.data); - setAccessTokenFromLocalStorage() - - }, [issueTokenResponse]); + setAccessTokenFromLocalStorage(); + }, [issueTokenResponse, setAccessTokenFromLocalStorage]); const handleClickOnForgotPass = () => { setForgotPassModalVisibility(true); }; const handleLogin = useCallback(() => { - const data = { - password: password, - email: email, - }; + const isFormValid = validateForm(); + if (isFormValid) { + const data = { + password: password, + email: email, + }; - issueToken(data); - }, [email, issueToken, password]); + issueToken(data); + } + }, [email, issueToken, password, validateForm]); return ( From 93a9d06d10359185a3c7c8641c42b828014b7d0f Mon Sep 17 00:00:00 2001 From: KimiaMontazeri Date: Wed, 29 Nov 2023 11:08:49 +0330 Subject: [PATCH 7/8] refactor(app-bar): use icon for account and logging out instead of button --- frontend/src/components/app-bar/AppBar.jsx | 64 +++++++++++++------ .../src/providers/config-provider/ROUTES.jsx | 1 + 2 files changed, 46 insertions(+), 19 deletions(-) diff --git a/frontend/src/components/app-bar/AppBar.jsx b/frontend/src/components/app-bar/AppBar.jsx index 2d50df2..b1f4ef2 100644 --- a/frontend/src/components/app-bar/AppBar.jsx +++ b/frontend/src/components/app-bar/AppBar.jsx @@ -1,4 +1,5 @@ import React, { useState } from 'react'; +import AccountCircle from '@mui/icons-material/AccountCircle'; import MenuIcon from '@mui/icons-material/Menu'; import { Link } from '@mui/material'; import AppBar from '@mui/material/AppBar'; @@ -11,6 +12,8 @@ import List from '@mui/material/List'; import ListItem from '@mui/material/ListItem'; import ListItemButton from '@mui/material/ListItemButton'; import ListItemText from '@mui/material/ListItemText'; +import Menu from '@mui/material/Menu'; +import MenuItem from '@mui/material/MenuItem'; import Toolbar from '@mui/material/Toolbar'; import AAISS from '../../assets/AAISS.png'; import { useConfig } from '../../providers/config-provider/ConfigProvider.jsx'; @@ -35,11 +38,21 @@ export default function DrawerAppBar() { const { ROUTES, accessToken, refreshToken } = useConfig(); const [mobileOpen, setMobileOpen] = useState(false); const [logoutModalVisibility, setLogoutModalVisibility] = useState(false); + const [anchorEl, setAnchorEl] = React.useState(null); const handleLogout = () => { + handleClose(); setLogoutModalVisibility(true); }; + const handleMenu = (event) => { + setAnchorEl(event.currentTarget); + }; + + const handleClose = () => { + setAnchorEl(null); + }; + const appBarPaths = Object.keys(ROUTES).filter((route) => !ROUTES[route]?.hideFromAppBar); const handleDrawerToggle = () => { @@ -47,13 +60,6 @@ export default function DrawerAppBar() { }; const shouldShowRoute = (route) => { - if (route.title === 'My Account') { - if (accessToken || refreshToken) { - return true; - } - return false; - } - if (route.title === 'Signup') { if (accessToken || refreshToken) { return false; @@ -64,7 +70,7 @@ export default function DrawerAppBar() { return true; }; - const shouldShowLogoutButton = Boolean(accessToken); + const isLoggedIn = Boolean(accessToken); const drawer = ( @@ -86,13 +92,6 @@ export default function DrawerAppBar() { ) ); })} - {shouldShowLogoutButton && ( - - - - - - )} ); @@ -135,10 +134,37 @@ export default function DrawerAppBar() { ); })} - {shouldShowLogoutButton && ( - + {isLoggedIn && ( + + + + + + window.location.replace(ROUTES.myAccount.path)}>My account + Logout + + )} diff --git a/frontend/src/providers/config-provider/ROUTES.jsx b/frontend/src/providers/config-provider/ROUTES.jsx index 60e0e93..d6a0324 100644 --- a/frontend/src/providers/config-provider/ROUTES.jsx +++ b/frontend/src/providers/config-provider/ROUTES.jsx @@ -48,6 +48,7 @@ const ROUTES = { path: '/my-account', title: 'My Account', component: , + hideFromAppBar: true, }, }; From 06a55ab13899800efe5ba0cc4d41d6d96a3838a0 Mon Sep 17 00:00:00 2001 From: KimiaMontazeri Date: Wed, 29 Nov 2023 11:51:04 +0330 Subject: [PATCH 8/8] fix(signup): trim email --- frontend/src/pages/Signup/Signup.jsx | 4 ++-- frontend/src/utils/Email.js | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/frontend/src/pages/Signup/Signup.jsx b/frontend/src/pages/Signup/Signup.jsx index a1d8340..ac990ae 100644 --- a/frontend/src/pages/Signup/Signup.jsx +++ b/frontend/src/pages/Signup/Signup.jsx @@ -81,7 +81,7 @@ const SignUpForm = ({ onLoginClick }) => { phone_number: phoneNumber, account: { password, - email, + email: email.trim(), }, }; createUser(data); @@ -254,7 +254,7 @@ const LoginForm = ({ onSignUpClick }) => { if (isFormValid) { const data = { password: password, - email: email, + email: email.trim(), }; issueToken(data); diff --git a/frontend/src/utils/Email.js b/frontend/src/utils/Email.js index 757f664..7cdfeba 100644 --- a/frontend/src/utils/Email.js +++ b/frontend/src/utils/Email.js @@ -3,7 +3,9 @@ export const hasEmailError = (email) => { return false; } - const result = String(email) + const trimmedEmail = String(email).trim(); + + const result = trimmedEmail .toLowerCase() .match( /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|.(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/,