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 = () => (
+
+);
+
+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 (
+
+ );
+};
+
+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 (
+
+ )
+}
\ 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 (
+
+ );
+};
+
+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 && (
+
+ }>
+ Link to CV
+
+
+ )}
+
+
+);
+
+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