diff --git a/src/App.js b/src/App.js index 7a13708..c08e629 100644 --- a/src/App.js +++ b/src/App.js @@ -1,24 +1,33 @@ -import logo from './logo.svg'; -import './App.css'; -import Dashboard from './pages/Dashboard' -import Categories from './pages/Categories' -import Products from './pages/Products' -import Login from './pages/Login' -import {Navigate, Route, Routes} from 'react-router-dom' -import {useEffect,useState} from 'react' -import Drawer from './components/Drawer' +import "./App.css"; +import routes, { renderRoutes } from "./routes"; +import { Router } from "react-router-dom"; +import { AuthProvider } from "./contexts/AuthContext"; +import { createBrowserHistory } from "history"; +import { createTheme } from "./theme"; +import { create } from "jss"; +import rtl from "jss-rtl"; +import { useContext } from "react"; +import { jssPreset, StylesProvider, ThemeProvider } from "@material-ui/core"; +import SettingsContext from './contexts/SettingsContext' +const history = createBrowserHistory(); +const jss = create({ plugins: [...jssPreset().plugins, rtl()] }); - -function App(props) { - - - return - }/> - }/> - }/> - }/> - - +function App() { + const { settings } = useContext(SettingsContext); + const theme = createTheme({ + direction: settings.direction, + responsiveFontSizes: settings.responsiveFontSizes, + theme: settings.theme, + }); + return ( + + + + {renderRoutes(routes)} + + + + ); } export default App; diff --git a/src/assets/images/1.jpg b/src/assets/images/1.jpg deleted file mode 100644 index 00e8755..0000000 Binary files a/src/assets/images/1.jpg and /dev/null differ diff --git a/src/common/Constants.js b/src/common/Constants.js new file mode 100644 index 0000000..0c33a95 --- /dev/null +++ b/src/common/Constants.js @@ -0,0 +1,8 @@ +export const SESSION_KEY = "CompUserData"; +export const BASE_URL = "https://website-backend.computiq.tech"; + +export const THEMES = { + LIGHT: 'LIGHT', + ONE_DARK: 'ONE_DARK' + }; + \ No newline at end of file diff --git a/src/components/Avatar.jsx b/src/components/Avatar.jsx deleted file mode 100644 index 5b87d0b..0000000 --- a/src/components/Avatar.jsx +++ /dev/null @@ -1,11 +0,0 @@ -import * as React from 'react'; -import Avatar from '@mui/material/Avatar'; -import Stack from '@mui/material/Stack'; - -export default function ImageAvatars() { - return ( - - - - ); -} diff --git a/src/components/BoardItems.jsx b/src/components/BoardItems.jsx new file mode 100644 index 0000000..2571708 --- /dev/null +++ b/src/components/BoardItems.jsx @@ -0,0 +1,47 @@ +import React from 'react' +import { Box,makeStyles, Typography } from '@material-ui/core'; + + +const useStyles = makeStyles(()=>({ + flx:{ + color: '#575756', + display: 'flex', + justifyContent: 'space-between', + alignItems:'center' + }, + + box: { + display: 'flex', + alignItems:'center' + }, + + img: { + fontSize: 35, + marginLeft: 5 + } + +})) + + +export default function BoardItems(props) { + const classes = useStyles() + return ( + <> + SCORE BOARD + { + props.dataP.map((item,i)=>{ + return ( + + + #{props.data.findIndex((data)=> (data.user__first_name+' '+data.user__last_name)===(item.user__first_name+' '+item.user__last_name) )} + {item.user__score_profile__photo} + + {item.user__first_name+' '+item.user__last_name} + {item.total_score} points + + ) + }) + } + + ) +} diff --git a/src/components/Drawer.jsx b/src/components/Drawer.jsx deleted file mode 100644 index 9e615f1..0000000 --- a/src/components/Drawer.jsx +++ /dev/null @@ -1,240 +0,0 @@ -import * as React from 'react'; -import { useEffect } from 'react' -import { styled, useTheme } from '@mui/material/styles'; -import Box from '@mui/material/Box'; -import Drawer from '@mui/material/Drawer'; -import CssBaseline from '@mui/material/CssBaseline'; -import MuiAppBar from '@mui/material/AppBar'; -import Toolbar from '@mui/material/Toolbar'; -import List from '@mui/material/List'; -import Typography from '@mui/material/Typography'; -import Divider from '@mui/material/Divider'; -import IconButton from '@mui/material/IconButton'; -import MenuIcon from '@mui/icons-material/Menu'; -import ChevronLeftIcon from '@mui/icons-material/ChevronLeft'; -import ChevronRightIcon from '@mui/icons-material/ChevronRight'; -import ListItem from '@mui/material/ListItem'; -import ListItemIcon from '@mui/material/ListItemIcon'; -import ListItemText from '@mui/material/ListItemText'; -import InboxIcon from '@mui/icons-material/MoveToInbox'; -import MailIcon from '@mui/icons-material/Mail'; -import { Routes, Link, Route, useLocation, useNavigate } from 'react-router-dom' -import Dashboard from '../pages/Dashboard' -import Categories from '../pages/Categories' -import Products from '../pages/Products' -import LogoutIcon from '@mui/icons-material/Logout'; -import GridViewIcon from '@mui/icons-material/GridView'; -import CategoryIcon from '@mui/icons-material/Category'; -import ProductionQuantityLimitsIcon from '@mui/icons-material/ProductionQuantityLimits'; -import Login from '../pages/Login' -import Avatar from './Avatar' -import MenuItem from '@mui/material/MenuItem'; -import Menu from '@mui/material/Menu'; -import Tooltip from '@mui/material/Tooltip'; -import {navigate} from 'react-router-dom' -import {TOKEN_KEY} from '../utils/Constants' - -const drawerWidth = 240; - - - -const Main = styled('main', { shouldForwardProp: (prop) => prop !== 'open' })( - ({ theme, open }) => ({ - flexGrow: 1, - padding: theme.spacing(3), - transition: theme.transitions.create('margin', { - easing: theme.transitions.easing.sharp, - duration: theme.transitions.duration.leavingScreen, - }), - marginLeft: `-${drawerWidth}px`, - ...(open && { - transition: theme.transitions.create('margin', { - easing: theme.transitions.easing.easeOut, - duration: theme.transitions.duration.enteringScreen, - }), - marginLeft: 0, - }), - }), -); - -const AppBar = styled(MuiAppBar, { - shouldForwardProp: (prop) => prop !== 'open', -})(({ theme, open }) => ({ - transition: theme.transitions.create(['margin', 'width'], { - easing: theme.transitions.easing.sharp, - duration: theme.transitions.duration.leavingScreen, - }), - ...(open && { - width: `calc(100% - ${drawerWidth}px)`, - marginLeft: `${drawerWidth}px`, - transition: theme.transitions.create(['margin', 'width'], { - easing: theme.transitions.easing.easeOut, - duration: theme.transitions.duration.enteringScreen, - }), - }), -})); - -const DrawerHeader = styled('div')(({ theme }) => ({ - display: 'flex', - alignItems: 'center', - padding: theme.spacing(0, 1), - // necessary for content to be below app bar - ...theme.mixins.toolbar, - justifyContent: 'flex-end', -})); - -function PersistentDrawerLeft(props) { - const location = useLocation() - const navigate = useNavigate() - const theme = useTheme(); - const [open, setOpen] = React.useState(false); - - const [anchorElUser, setAnchorElUser] = React.useState(null); - - const handleDrawerOpen = () => { - setOpen(true); - }; - - const handleDrawerClose = () => { - setOpen(false); - }; - - const logout = () => { - localStorage.removeItem(TOKEN_KEY) - navigate('/login') - handleCloseUserMenu() - } - useEffect(() => { - - }, []) - const renderContent = (routeName) => { - console.log(routeName) - switch (routeName) { - case '/login': - return - case '/products': - return - case '/dashboard': - return - case '/categories': - return - } - } - const handleOpenUserMenu = (event) => { - setAnchorElUser(event.currentTarget); - }; - const handleCloseUserMenu = () => { - setAnchorElUser(null); - }; - return ( - - - - - - - - - - - - - - - - - - - - - Logout - - - - - - - - - - {theme.direction === 'ltr' ? : } - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - {renderContent(location.pathname)} -
-
- ); -} - -export default PersistentDrawerLeft \ No newline at end of file diff --git a/src/components/LoadingScreen.jsx b/src/components/LoadingScreen.jsx new file mode 100644 index 0000000..f72b4c8 --- /dev/null +++ b/src/components/LoadingScreen.jsx @@ -0,0 +1,11 @@ +import React from 'react'; + +function LoadingScreen(props) { + return ( +
+ LoadingScreen +
+ ); +} + +export default LoadingScreen; \ No newline at end of file diff --git a/src/components/TaskItem.jsx b/src/components/TaskItem.jsx new file mode 100644 index 0000000..9ba62dd --- /dev/null +++ b/src/components/TaskItem.jsx @@ -0,0 +1,43 @@ +import React from 'react' +import parse from 'html-react-parser'; + +import { Box , makeStyles} from '@material-ui/core'; +import Collapse from '@mui/material/Collapse'; +import ExpandLess from '@mui/icons-material/ExpandLess'; +import ExpandMore from '@mui/icons-material/ExpandMore'; + +const useStyles = makeStyles(()=>({ + flx:{ + color: '#575756', + display: 'flex', + justifyContent: 'space-between', + alignItems:'center' + }, + + box: { + backgroundColor: 'white', + padding: 20, + borderRadius: 20, + border: '1px solid #c6c6c6', + color: '#575756', + marginBottom: 20, + cursor: 'pointer' + }, + +})) + +export default function TaskItem(props) { + const classes = useStyles() + const [open, setOpen] = React.useState(false); + + return ( + setOpen(!open)} className={classes.box}> + + {props.title} {open ? : } + + + { parse(props.desc) } + + + ) +} diff --git a/src/components/TaskItems.jsx b/src/components/TaskItems.jsx new file mode 100644 index 0000000..89a5e97 --- /dev/null +++ b/src/components/TaskItems.jsx @@ -0,0 +1,13 @@ +import React from 'react' +import TaskItem from './TaskItem' + + +export default function TaskItems(props) { + return ( + <> + { + props.tasks.map((task,i)=> ) + } + + ) +} diff --git a/src/components/Users.jsx b/src/components/Users.jsx deleted file mode 100644 index 5b3b0d7..0000000 --- a/src/components/Users.jsx +++ /dev/null @@ -1,11 +0,0 @@ -import React from 'react'; - -function Users(props) { - return ( -
-

Users component

-
- ); -} - -export default Users; \ No newline at end of file diff --git a/src/components/guards/AuthGuard.jsx b/src/components/guards/AuthGuard.jsx new file mode 100644 index 0000000..0f9fa2c --- /dev/null +++ b/src/components/guards/AuthGuard.jsx @@ -0,0 +1,24 @@ +import React from 'react'; +import { Redirect } from 'react-router-dom'; +import PropTypes from 'prop-types'; +import useAuth from '../../hooks/useAuth'; + +const AuthGuard = ({ children }) => { + const { isAuthenticated } = useAuth(); + + if (!isAuthenticated) { + return ; + } + + return ( + <> + {children} + + ); +}; + +AuthGuard.propTypes = { + children: PropTypes.node +}; + +export default AuthGuard; diff --git a/src/components/guards/GuestGuard.jsx b/src/components/guards/GuestGuard.jsx new file mode 100644 index 0000000..1a5917d --- /dev/null +++ b/src/components/guards/GuestGuard.jsx @@ -0,0 +1,24 @@ +import React from 'react'; +import { Redirect } from 'react-router-dom'; +import PropTypes from 'prop-types'; +import useAuth from '../../hooks/useAuth'; + +const GuestGuard = ({ children }) => { + const { isAuthenticated } = useAuth(); + + if (isAuthenticated) { + return ; + } + + return ( + <> + {children} + + ); +}; + +GuestGuard.propTypes = { + children: PropTypes.node +}; + +export default GuestGuard; diff --git a/src/contexts/AuthContext.js b/src/contexts/AuthContext.js new file mode 100644 index 0000000..2e94f6c --- /dev/null +++ b/src/contexts/AuthContext.js @@ -0,0 +1,255 @@ +import React, { + createContext, + useEffect, + useReducer + } from 'react'; + import jwtDecode from 'jwt-decode'; + import LoadingScreen from '../components/LoadingScreen'; + import axios from '../utils/axios'; + import { LoginService } from '../services/Http/LoginService'; + import { handleResponse } from '../utils/responseHandler'; + import {SESSION_KEY} from '../common/Constants' + const initialAuthState = { + isAuthenticated: false, + isInitialised: false, + user: null, + error:null + }; + + const isValidToken = (tokenObj) => { + console.log(tokenObj) + if (!tokenObj) { + return false; + } + const decoded = jwtDecode(tokenObj.access_token); + const currentTime = Date.now() / 1000; + return decoded.exp > currentTime; + }; + + + const setSessionData = (data) => { + if (data) { + localStorage.setItem(SESSION_KEY, JSON.stringify(data)); + axios.defaults.headers.common.Authorization = `Bearer ${data.token.access_token}`; + } else { + localStorage.removeItem(SESSION_KEY); + delete axios.defaults.headers.common.Authorization; + } + }; + const reducer = (state, action) => { + + switch (action.type) { + case 'INITIALISE': { + const { isAuthenticated, user } = action.payload; + + return { + ...state, + isAuthenticated, + isInitialised: true, + user, + error:null + }; + } + case 'LOGIN': { + const { data } = action.payload; + const user = data; + console.log('in login reducer, user fetched is: ' ) + console.log(data,user) + return { + ...state, + isAuthenticated: true, + user, + error:null + }; + } + case 'LOGOUT': { + return { + ...state, + isAuthenticated: false, + user: null, + error:null + }; + } + + case 'ERROR': { + const errorMSG = action.payload; + + console.log('error dispatched') + console.log(errorMSG) + return { + ...state, + isAuthenticated: false, + error:errorMSG.error.message + }; + } + default: { + return { ...state }; + } + } + }; + + const AuthContext = createContext({ + ...initialAuthState, + method: 'JWT', + login: () => Promise.resolve(), + logout: () => { } + }); + + export const AuthProvider = ({ children }) => { + const [state, dispatch] = useReducer(reducer, initialAuthState); + + const login = (email, password) =>{ + (new LoginService).login({ + email:email, + password:password + }) + .then((res)=>{ + console.log('response is: ',res) + if(res.status == '200') + { + const data = res.data; + // // origin + setSessionData(data) + // setSession(data); + console.log('after set session data,',data) + dispatch({ + type: 'LOGIN', + payload: { + data + } + }); + } + else{ // 404, 400, + let resp = handleResponse(res) + console.log('error of resp handler is : ') + console.log(resp) + dispatch({ + type: 'ERROR', + payload: { + error:resp + } + }); + } + + }) + .catch((err)=>{ + let resp = handleResponse(err) + console.log('error caught: ',err) + dispatch({ + type:'ERROR', + payload:{ + error:resp + } + }) + }) + } + // const login = async (email, password) => { + + // try{ + // const response = await (new LoginService).login({email,password}); + // console.log('response is',response) + // if(!response.data) + // return response + // const data = response.data; + // // origin + // setSessionData(data) + // // setSession(data); + // console.log('after set session data,',data) + // dispatch({ + // type: 'LOGIN', + // payload: { + // data + // } + // }); + + + // return response; + // } + // catch(err) + // { + // console.log('error caught',err) + // dispatch({ + // type: 'ERROR', + // payload: { + // err + // } + // }); + // } + + + + + + // }; + + const logout = () => { + setSessionData(null); + dispatch({ type: 'LOGOUT' }); + }; + + + useEffect(() => { + const initialise = async () => { + try { + console.log('try to load userData from localstaorage') + // const accessToken = window.localStorage.getItem('accessToken'); + let user = window.localStorage.getItem(SESSION_KEY); + if(user) + { + user = JSON.parse(user); + } + console.log(user) + if (user && user.token && isValidToken(user.token)) { + setSessionData(user); + dispatch({ + type: 'INITIALISE', + payload: { + isAuthenticated: true, + user + } + }); + } else { + console.log('user data not found or expired') + dispatch({ + type: 'INITIALISE', + payload: { + isAuthenticated: false, + user: null + } + }); + } + } catch (err) { + let resp = handleResponse(err) + dispatch({ + type: 'INITIALISE', + payload: { + isAuthenticated: false, + user: null + } + }); + } + }; + + initialise(); + }, []); + if (!state.isInitialised) { + return ; + } + + return ( + + {children} + + ); + }; + + export default AuthContext; \ No newline at end of file diff --git a/src/contexts/SettingsContext.js b/src/contexts/SettingsContext.js new file mode 100644 index 0000000..67cf25c --- /dev/null +++ b/src/contexts/SettingsContext.js @@ -0,0 +1,78 @@ +import React, { + createContext, + useEffect, + useState +} from 'react'; +import _ from 'lodash'; +import { THEMES } from '../common/Constants'; +// 1 +const defaultSettings = { + direction: 'ltr', + responsiveFontSizes: true, + theme: THEMES.LIGHT +}; + +export const restoreSettings = () => { + let settings = null; + + try { + const storedData = window.localStorage.getItem('settings'); + + if (storedData) { + settings = JSON.parse(storedData); + } + } catch (err) { + console.error(err); + // If stored data is not a strigified JSON this will fail, + // that's why we catch the error + } + + return settings; +}; + +export const storeSettings = (settings) => { + window.localStorage.setItem('settings', JSON.stringify(settings)); +}; +// 2 +const SettingsContext = createContext({ + settings: defaultSettings, + saveSettings: () => { } +}); + +export const SettingsProvider = ({ settings, children }) => { + const [currentSettings, setCurrentSettings] = useState(settings || defaultSettings); + + const handleSaveSettings = (update = {}) => { + const mergedSettings = _.merge({}, currentSettings, update); + + setCurrentSettings(mergedSettings); + storeSettings(mergedSettings); + }; + + useEffect(() => { + const restoredSettings = restoreSettings(); + + if (restoredSettings) { + setCurrentSettings(restoredSettings); + } + }, []); + + useEffect(() => { + document.dir = currentSettings.direction; + }, [currentSettings]); + + return ( + + {children} + + ); +}; + +export const SettingsConsumer = SettingsContext.Consumer; + +export default SettingsContext; diff --git a/src/hooks/useAuth.js b/src/hooks/useAuth.js new file mode 100644 index 0000000..a5b69fd --- /dev/null +++ b/src/hooks/useAuth.js @@ -0,0 +1,6 @@ +import { useContext } from 'react'; +import AuthContext from '../contexts/AuthContext'; + +const useAuth = () => useContext(AuthContext); + +export default useAuth; diff --git a/src/hooks/useSettings.js b/src/hooks/useSettings.js new file mode 100644 index 0000000..fe0fb11 --- /dev/null +++ b/src/hooks/useSettings.js @@ -0,0 +1,6 @@ +import { useContext } from 'react'; +import SettingsContext from '../contexts/SettingsContext'; + +const useSettings = () => useContext(SettingsContext); + +export default useSettings; diff --git a/src/index.css b/src/index.css index ec2585e..17c3491 100644 --- a/src/index.css +++ b/src/index.css @@ -11,3 +11,7 @@ code { font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace; } + +.MuiPagination-ul { + justify-content: space-around; +} \ No newline at end of file diff --git a/src/index.js b/src/index.js index bb29a24..9a5b6c0 100644 --- a/src/index.js +++ b/src/index.js @@ -3,30 +3,16 @@ import ReactDOM from 'react-dom'; import './index.css'; import App from './App'; import reportWebVitals from './reportWebVitals'; -import {BrowserRouter} from 'react-router-dom' -import Drawer from './components/Drawer' -import { createTheme, ThemeProvider, styled } from '@mui/material/styles'; - -const theme = createTheme({ - - palette: { - type: 'light', - primary: { - main: '#000000', - }, - secondary: { - main: '#f50057', - }, - }, -}) - +import { SnackbarProvider } from 'notistack'; +import {SettingsProvider} from './contexts/SettingsContext' ReactDOM.render( - - - - - + + + + + + , document.getElementById('root') ); diff --git a/src/layouts/DashboardLayout/NavBar/NavItem.js b/src/layouts/DashboardLayout/NavBar/NavItem.js new file mode 100644 index 0000000..1300812 --- /dev/null +++ b/src/layouts/DashboardLayout/NavBar/NavItem.js @@ -0,0 +1,168 @@ +import React, { useState } from 'react'; +import { NavLink as RouterLink } from 'react-router-dom'; +import clsx from 'clsx'; +import PropTypes from 'prop-types'; +import { + Button, + Collapse, + ListItem, + makeStyles +} from '@material-ui/core'; +import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; +import ExpandLessIcon from '@mui/icons-material/ExpandLess'; + +const useStyles = makeStyles((theme) => ({ + item: { + display: 'block', + paddingTop: 0, + paddingBottom: 0 + }, + itemLeaf: { + display: 'flex', + paddingTop: 0, + paddingBottom: 0 + }, + button: { + color: theme.palette.text.secondary, + padding: '10px 8px', + justifyContent: 'flex-start', + textTransform: 'none', + letterSpacing: 0, + width: '100%' + }, + buttonLeaf: { + color: theme.palette.text.secondary, + padding: '10px 8px', + justifyContent: 'flex-start', + textTransform: 'none', + letterSpacing: 0, + width: '100%', + fontWeight: theme.typography.fontWeightRegular, + '&.depth-0': { + '& $title': { + fontWeight: theme.typography.fontWeightMedium + } + } + }, + icon: { + display: 'flex', + alignItems: 'center', + marginRight: theme.spacing(1) + }, + title: { + marginRight: 'auto' + }, + active: { + color: theme.palette.secondary.main, + '& $title': { + fontWeight: theme.typography.fontWeightMedium + }, + '& $icon': { + color: theme.palette.secondary.main + } + } +})); + +const NavItem = ({ + children, + className, + depth, + href, + icon: Icon, + info: Info, + open: openProp, + title, + ...rest +}) => { + const classes = useStyles(); + const [open, setOpen] = useState(openProp); + + const handleToggle = () => { + setOpen((prevOpen) => !prevOpen); + }; + + let paddingLeft = 8; + + if (depth > 0) { + paddingLeft = 32 + 8 * depth; + } + + const style = { paddingLeft }; + + if (children) { + return ( + + + + {children} + + + ); + } + + return ( + + + + ); +}; + +NavItem.propTypes = { + children: PropTypes.node, + className: PropTypes.string, + depth: PropTypes.number.isRequired, + href: PropTypes.string, + icon: PropTypes.elementType, + info: PropTypes.elementType, + open: PropTypes.bool, + title: PropTypes.string.isRequired +}; + +NavItem.defaultProps = { + open: false +}; + +export default NavItem; diff --git a/src/layouts/DashboardLayout/NavBar/index.js b/src/layouts/DashboardLayout/NavBar/index.js new file mode 100644 index 0000000..3e6e530 --- /dev/null +++ b/src/layouts/DashboardLayout/NavBar/index.js @@ -0,0 +1,96 @@ +/* eslint-disable no-use-before-define */ +import React, { useEffect } from 'react'; +import { useLocation, matchPath } from 'react-router-dom'; +import PropTypes from 'prop-types'; +import { + Box, + Drawer, + Hidden, + makeStyles +} from '@material-ui/core'; +import useAuth from '../../../hooks/useAuth'; + + + +function renderNavItems({ + items, + pathname, + depth = 0 +}) { + return ( +

+ ); +} + + +const useStyles = makeStyles(() => ({ + mobileDrawer: { + width: 256 + }, + desktopDrawer: { + width: 256, + top: 64, + height: 'calc(100% - 64px)' + }, + avatar: { + cursor: 'pointer', + width: 64, + height: 64 + } +})); + +const NavBar = ({ onMobileClose, openMobile }) => { + const classes = useStyles(); + const location = useLocation(); + const { user } = useAuth(); + + useEffect(() => { + if (openMobile && onMobileClose) { + onMobileClose(); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [location.pathname]); + + const content = ( + + + + ); + + return ( + <> + + + {content} + + + + + {content} + + + + ); +}; + +NavBar.propTypes = { + onMobileClose: PropTypes.func, + openMobile: PropTypes.bool +}; + +export default NavBar; diff --git a/src/layouts/DashboardLayout/TopBar/Account.js b/src/layouts/DashboardLayout/TopBar/Account.js new file mode 100644 index 0000000..dd0d1db --- /dev/null +++ b/src/layouts/DashboardLayout/TopBar/Account.js @@ -0,0 +1,114 @@ +import React, { + useRef, + useState +} from 'react'; +import { Link as RouterLink } from 'react-router-dom'; +import { useHistory } from 'react-router-dom'; +import { useSnackbar } from 'notistack'; +import { + Avatar, + Box, + ButtonBase, + Hidden, + Menu, + MenuItem, + Typography, + makeStyles +} from '@material-ui/core'; +import useAuth from '../../../hooks/useAuth'; + +const useStyles = makeStyles((theme) => ({ + avatar: { + height: 32, + width: 32, + marginRight: theme.spacing(1) + }, + popover: { + width: 200 + } +})); + +const Account = () => { + const classes = useStyles(); + const history = useHistory(); + const ref = useRef(null); + const { user, logout} = useAuth(); + + const { enqueueSnackbar } = useSnackbar(); + const [isOpen, setOpen] = useState(false); + + const handleOpen = () => { + setOpen(true); + }; + + const handleClose = () => { + setOpen(false); + }; + + const handleLogout = async () => { + try { + handleClose(); + await logout(); + history.push('/login'); + } catch (err) { + enqueueSnackbar('Unable to logout', { + variant: 'error' + }); + } + }; + return ( + <> + + + + + {user.profile.user.first_name} + + + + + + Profile + + + Account + + + Logout + + + + ); +} + +export default Account; diff --git a/src/layouts/DashboardLayout/TopBar/Settings.js b/src/layouts/DashboardLayout/TopBar/Settings.js new file mode 100644 index 0000000..c0cd85e --- /dev/null +++ b/src/layouts/DashboardLayout/TopBar/Settings.js @@ -0,0 +1,172 @@ +import React, { + useState, + useRef +} from 'react'; +import { capitalCase } from 'change-case'; +import { + Badge, + Box, + Button, + FormControlLabel, + IconButton, + Popover, + SvgIcon, + Switch, + TextField, + Tooltip, + Typography, + makeStyles +} from '@material-ui/core'; +import { Settings as SettingsIcon } from 'react-feather'; +import useSettings from '../../../hooks/useSettings'; +import { THEMES } from '../../../common/Constants'; + +const useStyles = makeStyles((theme) => ({ + badge: { + height: 10, + width: 10, + borderRadius: 5, + marginTop: 10, + marginRight: 5 + }, + popover: { + width: 320, + padding: theme.spacing(2) + } +})); + +const Settings = () => { + const classes = useStyles(); + const ref = useRef(null); + const { settings, saveSettings } = useSettings(); + const [isOpen, setOpen] = useState(false); + const [values, setValues] = useState({ + direction: settings.direction, + responsiveFontSizes: settings.responsiveFontSizes, + theme: settings.theme + }); + + const handleOpen = () => { + setOpen(true); + }; + + const handleClose = () => { + setOpen(false); + }; + + const handleChange = (field, value) => { + setValues({ + ...values, + [field]: value + }); + }; + + const handleSave = () => { + saveSettings(values); + setOpen(false); + }; + + return ( + <> + + + + + + + + + + + + Settings + + + handleChange('direction', event.target.checked ? 'rtl' : 'ltr')} + /> + )} + label="RTL" + /> + + + handleChange('responsiveFontSizes', event.target.checked)} + /> + )} + label="Responsive font sizes" + /> + + + handleChange('theme', event.target.value)} + select + SelectProps={{ native: true }} + value={values.theme} + variant="outlined" + > + {Object.keys(THEMES).map((theme) => ( + + ))} + + + + + + + + ); +} + +export default Settings; diff --git a/src/layouts/DashboardLayout/TopBar/index.js b/src/layouts/DashboardLayout/TopBar/index.js new file mode 100644 index 0000000..5f467fb --- /dev/null +++ b/src/layouts/DashboardLayout/TopBar/index.js @@ -0,0 +1,85 @@ +import React from 'react'; +import { Link as RouterLink } from 'react-router-dom'; +import PropTypes from 'prop-types'; +import clsx from 'clsx'; +import { + AppBar, + Box, + Hidden, + IconButton, + Toolbar, + makeStyles, + SvgIcon +} from '@material-ui/core'; +import { Menu as MenuIcon } from 'react-feather'; +import { THEMES } from '../../../common/Constants'; +import Account from './Account'; +import Settings from './Settings'; + +const useStyles = makeStyles((theme) => ({ + root: { + ...theme.name === THEMES.LIGHT ? { + boxShadow: 'none', + backgroundColor: theme.palette.primary.main + } : {}, + ...theme.name === THEMES.ONE_DARK ? { + backgroundColor: theme.palette.background.default + } : {} + }, + toolbar: { + minHeight: 64 + }, + logo:{ + maxHeight:'40px', + maxWidth:'40px' + } +})); + +const TopBar = ({ + className, + onMobileNavOpen, + ...rest +}) => { + const classes = useStyles(); + + return ( + + + + + + + + + + + + + + + + + + + ); +}; + +TopBar.propTypes = { + className: PropTypes.string, + onMobileNavOpen: PropTypes.func +}; + +TopBar.defaultProps = { + onMobileNavOpen: () => {} +}; + +export default TopBar; diff --git a/src/layouts/DashboardLayout/index.js b/src/layouts/DashboardLayout/index.js new file mode 100644 index 0000000..cd647c7 --- /dev/null +++ b/src/layouts/DashboardLayout/index.js @@ -0,0 +1,62 @@ +import React, { useState } from 'react'; +import PropTypes from 'prop-types'; +import { makeStyles } from '@material-ui/core'; +import NavBar from './NavBar'; +import TopBar from './TopBar'; + +const useStyles = makeStyles((theme) => ({ + root: { + backgroundColor: theme.palette.background.dark, + display: 'flex', + height: '100%', + overflow: 'hidden', + width: '100%' + }, + wrapper: { + display: 'flex', + flex: '1 1 auto', + overflow: 'hidden', + paddingTop: 64, + // [theme.breakpoints.up('lg')]: { + // paddingLeft: 256 + // } + }, + contentContainer: { + display: 'flex', + flex: '1 1 auto', + overflow: 'hidden' + }, + content: { + flex: '1 1 auto', + height: '100%', + overflow: 'auto' + } +})); + +const DashboardLayout = ({ children }) => { + const classes = useStyles(); + const [isMobileNavOpen, setMobileNavOpen] = useState(false); + + return ( +
+ setMobileNavOpen(true)} /> + {/* setMobileNavOpen(false)} + openMobile={isMobileNavOpen} + /> */} +
+
+
+ {children} +
+
+
+
+ ); +}; + +DashboardLayout.propTypes = { + children: PropTypes.node +}; + +export default DashboardLayout; diff --git a/src/pages/Categories.jsx b/src/pages/Categories.jsx deleted file mode 100644 index 0e9ce54..0000000 --- a/src/pages/Categories.jsx +++ /dev/null @@ -1,17 +0,0 @@ -import React, { useEffect } from 'react'; - -function Categories(props) { - console.log(props) - useEffect(()=>{ - console.log('use effect') - let userData = JSON.parse(localStorage.getItem('myData')) - console.log(userData) - },[]) - return ( -
- Categories -
- ); -} - -export default Categories; \ No newline at end of file diff --git a/src/pages/Dashboard.jsx b/src/pages/Dashboard.jsx deleted file mode 100644 index 3c13707..0000000 --- a/src/pages/Dashboard.jsx +++ /dev/null @@ -1,32 +0,0 @@ -import React, { useEffect, useState } from 'react'; -import {Navigate} from 'react-router-dom' -function Dashboard(props) { - const [isLogged, setIsLogged] = React.useState(true) - useEffect(()=>{ - console.log('1') - let token; - try { - token = JSON.parse(localStorage.getItem('token')) - console.log('2') - if(!token) - setIsLogged(false) - - } catch (error) { - console.log(error) - setIsLogged(false) - } - - },[]) - console.log('3') - - if(!isLogged) - return - - return ( -
- Dashboard -
- ); -} - -export default Dashboard; \ No newline at end of file diff --git a/src/pages/Login.jsx b/src/pages/Login.jsx deleted file mode 100644 index cf8b851..0000000 --- a/src/pages/Login.jsx +++ /dev/null @@ -1,40 +0,0 @@ -import React from 'react'; -import { useNavigate } from 'react-router-dom'; -import axios from '../utils/axios' -import {TOKEN_KEY} from '../utils/Constants' -function Login(props) { - const navigate = useNavigate() - const [email, setEmail] = React.useState('') - const [password, setPassword] = React.useState('') - - const login = (e)=>{ - e.preventDefault() - axios.post('/api/academy/auth/login', - { - email:email, - password:password - } - ) - .then((response)=>{ - console.log(response) - let token = response.data.token.access_token; - let data = response.data; - localStorage.setItem(TOKEN_KEY, JSON.stringify(data)) - navigate('/dashboard') - }) - .catch((err)=>{ - console.log(err) - }) - } - return ( -
-
- setEmail(e.target.value)} type="email" /> - setPassword(e.target.value)} type="password" /> - -
-
- ); -} - -export default Login; \ No newline at end of file diff --git a/src/pages/Products.jsx b/src/pages/Products.jsx deleted file mode 100644 index e3fcba4..0000000 --- a/src/pages/Products.jsx +++ /dev/null @@ -1,12 +0,0 @@ -import React, { useEffect } from 'react'; - -function Products(props) { - - return ( -
- Products -
- ); -} - -export default Products; \ No newline at end of file diff --git a/src/routes.js b/src/routes.js new file mode 100644 index 0000000..72a91e2 --- /dev/null +++ b/src/routes.js @@ -0,0 +1,66 @@ +import React, { + Suspense, + Fragment, + lazy +} from 'react'; +import { + Switch, + Redirect, + Route +} from 'react-router-dom'; +import DashboardLayout from './layouts/DashboardLayout'; +import LoadingScreen from './components/LoadingScreen'; +import AuthGuard from './components/guards/AuthGuard'; +import GuestGuard from './components/guards/GuestGuard'; + +export const renderRoutes = (routes = []) => ( + }> + + {routes.map((route, i) => { + const Guard = route.guard || Fragment; + const Layout = route.layout || Fragment; + const Component = route.component; + + return ( + ( + + + + + + + + )} + /> + ); + })} + + +); + +export const routes = [ + { + exact: true, + path: '/404', + component: lazy(() => import('./views/NotFoundView')) + }, + { + exact: true, + guard: GuestGuard, + path: '/login', + component: lazy(() => import('./views/LoginView')) + }, + { + exact: true, + guard: AuthGuard, + path: '/board', + layout:DashboardLayout, + component: lazy(() => import('./views/BoardView')) + } +]; + +export default routes; diff --git a/src/services/Http/DashoardService.js b/src/services/Http/DashoardService.js new file mode 100644 index 0000000..333f479 --- /dev/null +++ b/src/services/Http/DashoardService.js @@ -0,0 +1,10 @@ +import { BaseService } from "../../utils/classes/BaseHttp" + +export class DashboradService extends BaseService { + constructor() { + super('') + } + async loadPoints(){ + return await this.http.get("api/score/data/5") + } +} \ No newline at end of file diff --git a/src/services/Http/LoginService.js b/src/services/Http/LoginService.js new file mode 100644 index 0000000..c86883f --- /dev/null +++ b/src/services/Http/LoginService.js @@ -0,0 +1,10 @@ +import { BaseService } from "../../utils/classes/BaseHttp" + +export class LoginService extends BaseService { + constructor() { + super('') + } + async login(body){ + return await this.http.post("/api/score/auth/login",body) + } +} \ No newline at end of file diff --git a/src/theme/index.js b/src/theme/index.js new file mode 100644 index 0000000..c00e818 --- /dev/null +++ b/src/theme/index.js @@ -0,0 +1,128 @@ + + +import _ from 'lodash' +import {THEMES} from '../common/Constants' +import { + colors, + createMuiTheme, + responsiveFontSizes +} from '@material-ui/core'; +const themesOptions = [ + { + name: THEMES.LIGHT, + overrides: { + MuiInputBase: { + input: { + '&::placeholder': { + opacity: 1, + color: colors.blueGrey[600] + } + } + } + }, + palette: { + type: 'light', + action: { + active: colors.blueGrey[600] + }, + background: { + default: colors.common.white, + dark: colors.common.white, + paper: colors.common.white + }, + primary: { + main: '#00bbf0'//ex: top bar + }, + secondary: { + main: '#fdb44b' // + }, + text: { + primary: colors.blueGrey[900], + secondary: colors.blueGrey[600] + } + } + }, + { + name: THEMES.ONE_DARK, + palette: { + type: 'dark', + action: { + active: 'rgba(255, 255, 255, 0.54)', + hover: 'rgba(255, 255, 255, 0.04)', + selected: 'rgba(255, 255, 255, 0.08)', + disabled: 'rgba(255, 255, 255, 0.26)', + disabledBackground: 'rgba(255, 255, 255, 0.12)', + focus: 'rgba(255, 255, 255, 0.12)' + }, + background: { + default: '#005792', + dark:'#f0f0f0', + paper: colors.common.white + }, + primary: { + main: '#00204a' + }, + secondary: { + main: '#fdb44b' + }, + text: { + primary: '#2d2d2e', + secondary: '#8a8a8a' + } + } + }, + { + name: THEMES.UNICORN, + palette: { + type: 'dark', + action: { + active: 'rgba(255, 255, 255, 0.54)', + hover: 'rgba(255, 255, 255, 0.04)', + selected: 'rgba(255, 255, 255, 0.08)', + disabled: 'rgba(255, 255, 255, 0.26)', + disabledBackground: 'rgba(255, 255, 255, 0.12)', + focus: 'rgba(255, 255, 255, 0.12)' + }, + background: { + default: '#2a2d3d', + dark: '#00204a', + paper: '#005792' + }, + primary: { + main: '#00bbf0' + }, + secondary: { + main: '#fdb44b' + }, + text: { + primary: '#f6f5f8', + secondary: '#9699a4' + } + } + } +]; + +const baseOptions = { + direction:'ltr' +} +export const createTheme = (config = {}) => { + let themeOptions = themesOptions.find((theme) => theme.name === config.theme); + console.log('selected theme is ', themeOptions) + if (!themeOptions) { + console.warn(new Error(`The theme ${config.theme} is not valid`)); + [themeOptions] = themesOptions; + } + console.log('theme before update,' , themeOptions) + let theme = createMuiTheme( + _.merge( + {}, + baseOptions, + themeOptions, + { direction: config.direction } + ) + ); + + console.log('last theme object: ',theme) + return theme; + } + \ No newline at end of file diff --git a/src/utils/Constants.js b/src/utils/Constants.js deleted file mode 100644 index 1574da1..0000000 --- a/src/utils/Constants.js +++ /dev/null @@ -1,2 +0,0 @@ -export const TOKEN_KEY = 'token' -export const BASE_URL = 'https://website-backend.computiq.tech' \ No newline at end of file diff --git a/src/utils/axios.js b/src/utils/axios.js new file mode 100644 index 0000000..8ae7635 --- /dev/null +++ b/src/utils/axios.js @@ -0,0 +1,29 @@ +import axios from 'axios'; +import { SESSION_KEY, BASE_URL} from "../common/Constants"; + +const axiosInstance = axios.create(); +axiosInstance.defaults.baseURL=BASE_URL; +axiosInstance.interceptors.response.use( + (response) => response + // ,(error) => Promise.reject((error.response && error.response.data) || 'Something went wrong') +); + +function getSession() { + let session = localStorage.getItem(SESSION_KEY) + if (session) { + return JSON.parse(session) + } + return session +} +function checkSession() { + return localStorage.getItem(SESSION_KEY) !== null +} + +if (checkSession()) { + let sessionData = getSession(); + console.log('local storage: ',sessionData) + let apiToken = sessionData.token.access_token + console.log(apiToken) + axiosInstance.defaults.headers.common['Authorization'] = `Bearer ${apiToken}`; +} +export default axiosInstance; diff --git a/src/utils/axios.jsx b/src/utils/axios.jsx deleted file mode 100644 index 5610218..0000000 --- a/src/utils/axios.jsx +++ /dev/null @@ -1,4 +0,0 @@ -import axios from 'axios' -import {BASE_URL} from './Constants' -axios.defaults.baseURL = BASE_URL; -export default axios \ No newline at end of file diff --git a/src/utils/classes/BaseHttp.js b/src/utils/classes/BaseHttp.js new file mode 100644 index 0000000..ffda785 --- /dev/null +++ b/src/utils/classes/BaseHttp.js @@ -0,0 +1,30 @@ +import { HttpService } from "./HttpHelper"; + +export class BaseService { + + http + + constructor(url_prefix = "") { + this.http = (new HttpService(url_prefix)) + } + + async getAll() { + return await this.http.get(``) + } + + async get(id) { + return await this.http.get(`/${id}`) + } + + async create(body) { + return await this.http.post(``, body) + } + + async update(id, body) { + return await this.http.put(`/${id}`, body) + } + + async delete(id) { + return await this.http.remove(`/${id}`) + } +} \ No newline at end of file diff --git a/src/utils/classes/HttpHelper.js b/src/utils/classes/HttpHelper.js new file mode 100644 index 0000000..c730d97 --- /dev/null +++ b/src/utils/classes/HttpHelper.js @@ -0,0 +1,92 @@ +import axios from '../axios' + + +export class HttpService { + + headers = { + + } + + constructor(url_prefix = "") { + this.url_prefix = url_prefix + this.getHeaders() + } + + async get(url, queryParams) { + try { + let response = await axios.get(this.getUrl(url) + this.mapQueryParams(queryParams), { + headers: this.headers + }) + + return response + } catch (error) { + console.log(error) + return error + } + } + + async post(url, body, queryParams = null) { + try { + let response = await axios.post(this.getUrl(url) + this.mapQueryParams(queryParams),body ,{ + method: "POST", + headers: this.headers + }) + + return response + } catch (error) { + // console.log('error in response of axios is',error) + // let res = handleResponse(error) + return error; // null or object of msg + } + + } + + async put(url, body, queryParams = null) { + try { + let response = await fetch(this.getUrl(url) + this.mapQueryParams(queryParams), { + method: "PUT", + headers: this.headers, + body: JSON.stringify(body) + }) + + return response + } catch (error) { + console.log(error); + return null + } + } + + async remove(url, queryParams = null) { + try { + let response = await fetch(this.getUrl(url) + this.mapQueryParams(queryParams), { + method: "DELETE", + headers: this.headers + }) + + return response + } catch (error) { + console.log(error) + return null + } + } + + getUrl(url) { + return this.url_prefix + url + } + + getHeaders() { + this.headers = { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + 'Access-Control-Allow-Origin': '*' + } + } + + mapQueryParams(queryParams) { + return queryParams + ? Object.keys(queryParams).map(function (key) { + return key + '=' + queryParams[key] + }).join('&') + : "" + } +} \ No newline at end of file diff --git a/src/utils/responseHandler.js b/src/utils/responseHandler.js new file mode 100644 index 0000000..ed803d7 --- /dev/null +++ b/src/utils/responseHandler.js @@ -0,0 +1,60 @@ +export const handleResponse = (err) => { + let resp = null + if(err.response) + { + resp = {} + + switch(err.response.status) + { + case 401: + resp = { + message:err.response.data.message, + statusCode:err.response.status + } + console.log(resp) + // window.location.href='/login' + break; + case 422: + let msg = ''; + console.log('err object: ',err.response) + let details = err.response.data.detail + if(details) + { + details.map((m)=>{ + msg = msg + ", "+m.msg + }) + } + resp = { + message:msg, + statusCode:err.response.status + } + console.log('object created: ',resp) + // window.location.href='/login' + break; + + case 404: + resp ={ + message:err.response.data.message, + statusCode:err.response.status + } + break; + + } + + } + else if(err.request) + { + console.log(err.status) + console.log("Error in the client side at request") + + } + else{ + resp = { + message:'Unknown error!', + statusCode:-1 + } + } + console.log('respo in handler is: ',resp) + return resp; + +} \ No newline at end of file diff --git a/src/views/BoardView.jsx b/src/views/BoardView.jsx new file mode 100644 index 0000000..2ea6f22 --- /dev/null +++ b/src/views/BoardView.jsx @@ -0,0 +1,76 @@ +import { makeStyles, Typography, Box} from '@material-ui/core'; +import React, { useEffect, useState} from 'react'; +import { DashboradService } from '../services/Http/DashoardService'; +import Pagination from '@mui/material/Pagination'; +import BoardItems from '../components/BoardItems'; +import Container from '@mui/material/Container'; +import Grid from '@mui/material/Grid'; +import TaskItems from '../components/TaskItems' +import CircularProgress from '@mui/material/CircularProgress'; + +const useStyles = makeStyles((theme)=>({ + bg:{ + backgroundColor: 'white', + borderRadius: 20, + boxSizing: 'border-box', + padding: 20, + border: '1px #1a8cf7 solid', + boxShadow: '5px 5px 0 #1a8cf7', + }, + + +})) +function BoardView(props) { + const classes = useStyles() + let perPage = 10 + const [data, setData] = useState([]) + const [page, setPage] = useState(1) + const [pages, setPages] = useState(1) + const [dataPaginate, setDataPaginate] = useState([]) + const [tasks, setTasks] = useState([]) + + const handleClick = (event, value)=> { + let fromRecord = perPage * (value - 1) + let toRecord = ((fromRecord + perPage) <= data.length) ? (fromRecord + perPage) : (data.length % perPage) + fromRecord + setPage(value) + setDataPaginate(data.slice(fromRecord,toRecord)) + } + + + useEffect(()=>{ + + (new DashboradService).loadPoints().then((res)=> { + let dataRes = res.data + let boardData = dataRes.data + let tasksData = dataRes.program.tasks + + setTasks(tasksData) + setData([...boardData]) + setPages(Math.ceil(boardData.length/perPage)) + setDataPaginate(boardData.slice(0,10)) + }).catch((err)=> console.log(err)) + + },[]) + + return ( + + {dataPaginate.length==0? : + + + Computiq Score Board + Full-Stack Development Bootcamp + + + + + + + + + + } + + ); +} + +export default BoardView; \ No newline at end of file diff --git a/src/views/LoginView.jsx b/src/views/LoginView.jsx new file mode 100644 index 0000000..2b32ed5 --- /dev/null +++ b/src/views/LoginView.jsx @@ -0,0 +1,82 @@ +import React, { useState } from 'react'; +// import { makeStyles } from '@mui/styles'; +import { Box,makeStyles } from '@material-ui/core'; +import TextField from '@mui/material/TextField'; +import Button from '@mui/material/Button'; +import useAuth from '../hooks/useAuth'; +import Snackbar from '@mui/material/Snackbar'; +import MuiAlert from '@mui/material/Alert'; + + +const Alert = React.forwardRef(function Alert(props, ref) { + return ; + }); + +const useStyles = makeStyles(()=>({ + root:{ + display:'flex', + flexGrow:1, + justifyContent:'center', + alignItems:'center', + backgroundColor:'#e0e0e0', + height:'100vh' + }, + loginBox:{ + width:'400px', + height:'250px', + backgroundColor:'white', + borderRadius:'5px' + }, + form:{ + width:'75%', + margin:'auto', + paddingTop:'15px', + paddingBottom:'15px', + boxSizing:'border-box', + display:'flex', + flexDirection:'column', + justifyContent:'space-between', + height:'100%' + }, + + field:{ + width:'100%' + } + +})) +function LoginView(props) { + const classes = useStyles() + const {login,error} = useAuth() + console.log(' error is ',error) + const [email,setEmail] = useState('') + const [password,setPassword] = useState('') + const auth = (e)=>{ + e.preventDefault(); + login(email, password); + + + } + return ( + + +
+ + + setEmail(e.target.value)} className={classes.field} type="email" id="email" label="Email" variant="standard" /> + setPassword(e.target.value)} className={classes.field} type="password" id="password" label="Password" variant="standard" /> + + + + +
+
+ { + error && {error} + } +
+ ); +} + +export default LoginView; \ No newline at end of file diff --git a/src/views/NotFoundView.jsx b/src/views/NotFoundView.jsx new file mode 100644 index 0000000..1e19bd9 --- /dev/null +++ b/src/views/NotFoundView.jsx @@ -0,0 +1,11 @@ +import React from 'react'; + +function ViewNotFound(props) { + return ( +
+ 404 Not Found +
+ ); +} + +export default ViewNotFound; \ No newline at end of file