From bf9b04e579fa4d06c6eb9a164bfbf187b388a8db Mon Sep 17 00:00:00 2001 From: Adibov Date: Mon, 27 Nov 2023 20:27:32 +0330 Subject: [PATCH] fix(frontend): rename Components to components --- .../src/components/Form/FormTextField.jsx | 26 +++ frontend/src/components/app-bar/AppBar.jsx | 148 ++++++++++++++ frontend/src/components/app-bar/useNavItem.js | 11 + frontend/src/components/footer/PageFooter.jsx | 60 ++++++ .../forgot-pass-modal/forgot-pass-modal.jsx | 68 ++++++ frontend/src/components/image/Image.jsx | 16 ++ .../src/components/item-card/item-card.jsx | 193 ++++++++++++++++++ .../components/item-card/more-info-modal.jsx | 108 ++++++++++ .../components/main-content/MainContent.jsx | 29 +++ .../components/presenters/PresenterCard.jsx | 109 ++++++++++ .../presenters/PresenterProfile.jsx | 60 ++++++ .../src/components/presenters/Presenters.jsx | 27 +++ .../src/components/table/ObjListTable.jsx | 69 +++++++ .../src/components/table/obj-list-table.css | 80 ++++++++ frontend/src/components/toast/Toast.jsx | 38 ++++ 15 files changed, 1042 insertions(+) create mode 100644 frontend/src/components/Form/FormTextField.jsx create mode 100644 frontend/src/components/app-bar/AppBar.jsx create mode 100644 frontend/src/components/app-bar/useNavItem.js create mode 100644 frontend/src/components/footer/PageFooter.jsx create mode 100644 frontend/src/components/forgot-pass-modal/forgot-pass-modal.jsx create mode 100644 frontend/src/components/image/Image.jsx create mode 100644 frontend/src/components/item-card/item-card.jsx create mode 100644 frontend/src/components/item-card/more-info-modal.jsx create mode 100644 frontend/src/components/main-content/MainContent.jsx create mode 100644 frontend/src/components/presenters/PresenterCard.jsx create mode 100644 frontend/src/components/presenters/PresenterProfile.jsx create mode 100644 frontend/src/components/presenters/Presenters.jsx create mode 100644 frontend/src/components/table/ObjListTable.jsx create mode 100644 frontend/src/components/table/obj-list-table.css create mode 100644 frontend/src/components/toast/Toast.jsx diff --git a/frontend/src/components/Form/FormTextField.jsx b/frontend/src/components/Form/FormTextField.jsx new file mode 100644 index 0000000..b7de48a --- /dev/null +++ b/frontend/src/components/Form/FormTextField.jsx @@ -0,0 +1,26 @@ +import React from 'react'; +import { TextField } from '@mui/material'; +import '../../css/FormTextField.css'; + +export default function FormTextField({ type, label, value, onChange, error, required = true }) { + return ( + + ); +} diff --git a/frontend/src/components/app-bar/AppBar.jsx b/frontend/src/components/app-bar/AppBar.jsx new file mode 100644 index 0000000..2606d0d --- /dev/null +++ b/frontend/src/components/app-bar/AppBar.jsx @@ -0,0 +1,148 @@ +import * as React from 'react'; +import { Link } from 'react-router-dom'; +import MenuIcon from '@mui/icons-material/Menu'; +import AppBar from '@mui/material/AppBar'; +import Box from '@mui/material/Box'; +import Button from '@mui/material/Button'; +import Divider from '@mui/material/Divider'; +import Drawer from '@mui/material/Drawer'; +import IconButton from '@mui/material/IconButton'; +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 Toolbar from '@mui/material/Toolbar'; +import AAISS from '../../assets/AAISS.png'; +import { useConfig } from '../../providers/config-provider/ConfigProvider.jsx'; +import Image from '../image/Image.jsx'; +import useNavItem from './useNavItem.js'; + +const drawerWidth = 240; + +const NavBarImage = () => ( + {'aaiss +); + +export default function DrawerAppBar() { + const { ROUTES, currentRoute, setCurrentRoute, accessToken, refreshToken } = useConfig(); + const [mobileOpen, setMobileOpen] = React.useState(false); + + const { getVariant } = useNavItem(); + + const appBarPaths = Object.keys(ROUTES).filter((route) => !ROUTES[route]?.hideFromAppBar); + + const handleDrawerToggle = () => { + setMobileOpen((prevState) => !prevState); + }; + + const shouldShowRoute = (route) => { + if (route.title === 'My Account') { + if (accessToken || refreshToken) { + return true; + } + return false; + } + + if (route.title === 'Signup') { + if (accessToken || refreshToken) { + return false; + } + return true; + } + + return true; + }; + + const drawer = ( + + setCurrentRoute(ROUTES.home)}> + + + + + {appBarPaths.map((name, index) => { + return ( + shouldShowRoute(ROUTES[name]) && ( + + + + + + + + ) + ); + })} + + + ); + + return ( + + + + + + + setCurrentRoute(ROUTES.home)}> + + + + {appBarPaths.map((name, index) => { + return ( + shouldShowRoute(ROUTES[name]) && ( + + ) + ); + })} + + + + + + ); +} diff --git a/frontend/src/components/app-bar/useNavItem.js b/frontend/src/components/app-bar/useNavItem.js new file mode 100644 index 0000000..7bffe4a --- /dev/null +++ b/frontend/src/components/app-bar/useNavItem.js @@ -0,0 +1,11 @@ +import { useCallback } from 'react'; + +export default function useNavItem() { + const getVariant = useCallback((path, sect) => { + return path !== sect ? 'text' : 'contained'; + }, []); + + return { + getVariant, + }; +} diff --git a/frontend/src/components/footer/PageFooter.jsx b/frontend/src/components/footer/PageFooter.jsx new file mode 100644 index 0000000..b9fd70c --- /dev/null +++ b/frontend/src/components/footer/PageFooter.jsx @@ -0,0 +1,60 @@ +import React from 'react'; +import aut from '../../assets/AUT.png'; +import ceit from '../../assets/CEIT.png'; +import ssc from '../../assets/SSC.png'; +import '../../css/Footer.css' +import SvgIcon from "@mui/material/SvgIcon"; +import Link from "@mui/material/Link"; + +export default function PageFooter() { + return ( + + ); +} \ No newline at end of file diff --git a/frontend/src/components/forgot-pass-modal/forgot-pass-modal.jsx b/frontend/src/components/forgot-pass-modal/forgot-pass-modal.jsx new file mode 100644 index 0000000..6bdfd91 --- /dev/null +++ b/frontend/src/components/forgot-pass-modal/forgot-pass-modal.jsx @@ -0,0 +1,68 @@ +import React, { useState } from 'react'; +import { + Dialog, + DialogTitle, + DialogContent, + DialogContentText, + DialogActions, + Button, + FormHelperText, +} from '@mui/material'; +import { hasEmailError } from '../../utils/Email'; +import FormTextField from '../Form/FormTextField'; + +const ForgotPassModal = ({ visibility, onVisibilityChange }) => { + const [email, setEmail] = useState(''); + const [isEmailWrong, setIsEmailWrong] = useState(false); + const [emailHelperText, setEmailHelperText] = useState(''); + + const onSubmit = () => { + if (hasEmailError(email)) { + setIsEmailWrong(true); + setEmailHelperText('Your email is not valid'); + return; + } + // TODO: api call + // TODO: if an error ocurred, set the helper text to an error msg + // close the modal if it was successful + onVisibilityChange(); + }; + + return ( + + Forgot Password + + + Enter your email here in order to get the verification code. + + {emailHelperText} + { + setEmail(event.target.value); + setIsEmailWrong(false); + setEmailHelperText(''); + }} + /> + + + + + + + ); +}; + +export default ForgotPassModal; diff --git a/frontend/src/components/image/Image.jsx b/frontend/src/components/image/Image.jsx new file mode 100644 index 0000000..41ee510 --- /dev/null +++ b/frontend/src/components/image/Image.jsx @@ -0,0 +1,16 @@ +export default function Image({ + src, + alt, + style, + }) { + return ( + {alt} + ) +} \ No newline at end of file diff --git a/frontend/src/components/item-card/item-card.jsx b/frontend/src/components/item-card/item-card.jsx new file mode 100644 index 0000000..57ad69c --- /dev/null +++ b/frontend/src/components/item-card/item-card.jsx @@ -0,0 +1,193 @@ +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 MoreInfoModal from './more-info-modal'; + +const Presenter = ({ presenterName }) => ( + + + + {presenterName} + + +); + +// TODO: format cost with commas +const Cost = ({ cost }) => ( + + + + {cost} T + + +); + +const CapacityChip = ({ capacity, isFull }) => ( + +); + +const Level = ({ name, color }) => ( + + + + {name} + + +); + +const levelComponentMapping = { + 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', + capacity = 50, + isFull = false, + addToCalendarLink = 'https://google.com', + onClickAddToCart = () => {}, + onClickRemoveFromCart = () => {}, +}) => { + const [moreInfoModalVisibility, setMoreInfoModalVisibility] = useState(false); + const hasBought = purchaseState === 2; + + const handleClickOnMoreInfo = () => { + setMoreInfoModalVisibility(true); + }; + + const getActionComponent = () => { + switch (purchaseState) { + case 0: + return ( + + ); + case 1: + return ( + + ); + case 2: + return ( + + ); + default: + return null; + } + }; + + return ( + <> + setMoreInfoModalVisibility(false)} + title={title} + purchaseState={purchaseState} + hasProject={hasProject} + prerequisites={prerequisites} + syllabus={syllabus} + isFull={isFull} + addToCalendarLink={addToCalendarLink} + onClickAddToCart={onClickAddToCart} + onClickRemoveFromCart={onClickRemoveFromCart} + /> + + + + + {description} + + + 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, +}; + +export default ItemCard; diff --git a/frontend/src/components/item-card/more-info-modal.jsx b/frontend/src/components/item-card/more-info-modal.jsx new file mode 100644 index 0000000..f78581a --- /dev/null +++ b/frontend/src/components/item-card/more-info-modal.jsx @@ -0,0 +1,108 @@ +import React from 'react'; +import { Checklist } from '@mui/icons-material'; +import ListIcon from '@mui/icons-material/List'; +import { + Dialog, + DialogTitle, + DialogContent, + DialogContentText, + DialogActions, + Button, + Divider, + Stack, + Chip, +} from '@mui/material'; +import PropTypes from 'prop-types'; + +const Prerequisites = ({ prerequisites }) => ( + <> + + + + Prerequisites + + + {prerequisites} + +); + +const Syllabus = ({ syllabus }) => ( + <> + + + + Syllabus + + + {syllabus} + +); + +const MoreInfoModal = ({ + visibility, + onVisibilityChange, + title, + purchaseState, + hasProject, + prerequisites, + syllabus, + isFull, + onClickAddToCart, + onClickRemoveFromCart, +}) => { + const handleClickAddToCart = () => { + onVisibilityChange(); + onClickAddToCart(); + }; + + return ( + + {title} + + + + + {hasProject && ( + <> + + + + )} + + + + {purchaseState === 0 ? ( + + ) : purchaseState === 1 ? ( + + ) : null} + + + ); +}; + +MoreInfoModal.propTypes = { + title: PropTypes.string, + isBought: PropTypes.bool, + purchaseState: PropTypes.number, + prerequisites: PropTypes.string, + syllabus: PropTypes.string, + isFull: PropTypes.bool, + onClickAddToCart: PropTypes.func, + onClickRemoveFromCart: PropTypes.func, +}; + +export default MoreInfoModal; diff --git a/frontend/src/components/main-content/MainContent.jsx b/frontend/src/components/main-content/MainContent.jsx new file mode 100644 index 0000000..8035ea7 --- /dev/null +++ b/frontend/src/components/main-content/MainContent.jsx @@ -0,0 +1,29 @@ +import { Route, Routes, useLocation } from 'react-router-dom'; +import PageFooter from '../footer/PageFooter'; +import ForgotPassword from '../../pages/ForgotPassword/ForgotPassword.jsx'; +import { useConfig } from '../../providers/config-provider/ConfigProvider.jsx'; +import DrawerAppBar from '../app-bar/AppBar.jsx'; + +export default function MainContent() { + const { ROUTES } = useConfig(); + const { hash, pathname, search } = useLocation(); + + return ( +
+
+ +
+
+ + {Object.keys(ROUTES).map((name) => { + return ; + })} + } key="forgot" /> + +
+ {pathname !== '/' && ( + + )} +
+ ); +} diff --git a/frontend/src/components/presenters/PresenterCard.jsx b/frontend/src/components/presenters/PresenterCard.jsx new file mode 100644 index 0000000..6c480f8 --- /dev/null +++ b/frontend/src/components/presenters/PresenterCard.jsx @@ -0,0 +1,109 @@ +import React from 'react'; +import { Box, Button, Card, CardActions, CardContent, Chip, Divider, Stack, Typography } from '@mui/material'; +import PropTypes from 'prop-types'; +import '../../css/PresenterCard.css'; +import URL from '../../providers/APIProvider/URL.js'; +import Image from '../image/Image.jsx'; + +const PresenterCard = ({ name, photo, desc, logo, onClick, showButton = true, role, containerHeight }) => { + return ( + + + + + + + + {name} + + + {role && } + {logo && ( + + + + {desc} + + + )} + + + {showButton && ( + + + + )} + + + ); +}; + +PresenterCard.propTypes = { + name: PropTypes.string, + photo: PropTypes.string, + desc: PropTypes.string, + onClick: PropTypes.func, + showButton: PropTypes.bool, + role: PropTypes.string, + containerHeight: PropTypes.number, +}; + +export default PresenterCard; diff --git a/frontend/src/components/presenters/PresenterProfile.jsx b/frontend/src/components/presenters/PresenterProfile.jsx new file mode 100644 index 0000000..37cd733 --- /dev/null +++ b/frontend/src/components/presenters/PresenterProfile.jsx @@ -0,0 +1,60 @@ +import React from 'react'; +import { ArrowForward } from '@mui/icons-material'; +import { Box, Button, Divider, Stack, Typography } from '@mui/material'; +import PropTypes from 'prop-types'; +import URL from '../../providers/APIProvider/URL.js'; +import Image from '../image/Image.jsx'; + +const Header = ({ name, workplace, photo }) => ( + + + + + {name} + + + from {workplace} + + + +); + +const PresenterProfile = ({ name, workplace, photo, cvPath, bio }) => ( + + +
+ + + Biography + + + {bio} + {cvPath && ( + + + + )} + + +); + +PresenterProfile.propTypes = { + name: PropTypes.string, + photo: PropTypes.string, + description: PropTypes.string, + cvPath: PropTypes.string, + bio: PropTypes.string, +}; + +export default PresenterProfile; diff --git a/frontend/src/components/presenters/Presenters.jsx b/frontend/src/components/presenters/Presenters.jsx new file mode 100644 index 0000000..84d4249 --- /dev/null +++ b/frontend/src/components/presenters/Presenters.jsx @@ -0,0 +1,27 @@ +import "../../css/Presenters.css"; +import PresenterCard from "./PresenterCard"; + +export default function Presenters({ presenters }) { + return ( +
+ {!presenters ? ( +

loading

+ ) : ( +
+ { + presenters.map(item => { + return ( + + ) + }) + } +
+ )} +
+ ); +} diff --git a/frontend/src/components/table/ObjListTable.jsx b/frontend/src/components/table/ObjListTable.jsx new file mode 100644 index 0000000..db752a3 --- /dev/null +++ b/frontend/src/components/table/ObjListTable.jsx @@ -0,0 +1,69 @@ +import "./obj-list-table.css" +import {Table, TableBody, TableCell, TableContainer, TableHead, TableRow} from "@mui/material"; + +export default function ObjListTable({ + data, + title + }) { + + return ( +
+ {!data || Object.keys(data).length === 0 ?

loading...

: +
+
+

{title}

+ + + + + {Object.keys(data[0]).map(name => { + return ( + + {name} + + ) + })} + + + + {data.map((item, index) => { + return ( + + {Object.keys(item).map((name, secondIndex) => { + return ( + + {item[name]} + + ) + })} + + ) + })} + +
+
+
+
+ } + {data && Object.keys(data).length === 0 && "Nothing to display!"} +
+ ) +} \ No newline at end of file diff --git a/frontend/src/components/table/obj-list-table.css b/frontend/src/components/table/obj-list-table.css new file mode 100644 index 0000000..4f12734 --- /dev/null +++ b/frontend/src/components/table/obj-list-table.css @@ -0,0 +1,80 @@ +#loading { + text-align: center; +} + +.table-container { + background-color: var(--background-color-lighter-40-glassy); + backdrop-filter: blur(2px); + border-radius: 10px; + border: 2px solid var(--background-color-lighter-40-glassy); + padding: 0 1rem 1rem; + box-sizing: border-box; +} + +.table-container h3 { + color: var(--light-text-color); + font-weight: bold; + font-size: x-large; +} + +.MuiTableContainer-root { + border-radius: 10px; +} + +section, table { + width: 100%; +} + +table { + border-radius: 10px; +} + +table td, table th { + text-align: center !important; + vertical-align: middle !important; +} + +table th { + font-size: medium !important; + font-weight: bolder !important; +} + +tr { + transition: background-color .07s; + background-color: var(--background-color-lighter-80); +} + +table tbody tr:nth-child(even), table thead tr { + background-color: var(--background-color-lighter-60); +} + +tbody tr.MuiTableRow-root:hover { + cursor: pointer; + color: white !important; + background-color: var(--dark-text-color-lighter-20); +} + +table { + border-collapse: collapse; +} + +table, th, td { + outline: 1px var(--background-color-lighter-40) solid !important; +} + +td { + padding: 0.3rem; + max-width: 50vw; +} + +h3 { + text-align: center; + margin: 0; + padding: 1rem 0; +} + +@media only screen and (max-width: 850px) { + .table-container { + font-size: 3vw; + } +} diff --git a/frontend/src/components/toast/Toast.jsx b/frontend/src/components/toast/Toast.jsx new file mode 100644 index 0000000..82d8fc0 --- /dev/null +++ b/frontend/src/components/toast/Toast.jsx @@ -0,0 +1,38 @@ +import {useCallback} from "react"; +import {Alert, Snackbar} from "@mui/material"; +import Slide from '@mui/material/Slide'; + +export default function Toast({ + duration = 6000, + vertical = "top", //'top', 'bottom' + horizontal = "right", //'left', 'center', 'right' + message = "fill me", + alertType = "success",//'error', 'warning', 'info', 'success', + open, + setOpen, + }) { + + + const onClose = useCallback(() => { + setOpen(false) + }, []) + + return ( + } + > + + {message} + + + ) +} \ No newline at end of file