Commit 3311ef45 authored by Djordje's avatar Djordje

Imlement layouting and side menu

parent fbd917d3
import React from 'react'; import React from 'react';
import logo from './logo.svg';
import './App.css'; import './App.css';
import { BrowserRouter as Router, Navigate, Route, Routes } from 'react-router-dom' import { BrowserRouter as Router, Navigate, Route, Routes } from 'react-router-dom'
import { EntryPage } from './components/entry-page/EntryPage'; import { EntryPage } from './components/entry-page/EntryPage';
import { RegisterForm } from './components/register/RegisterForm'; import { RegisterForm } from './components/register/RegisterForm';
import { LoginForm } from './components/LoginForm'; import { LoginForm } from './components/LoginForm';
import { Form } from './components/Form'; import { Form } from './components/Form';
import { Application } from './components/Application';
function App() { function App() {
return ( return (
<Router> <Router>
<Routes> <Routes>
<Route path='register/:buildingId' element={<EntryPage children={<Form children={<RegisterForm />} title="Create Account"/>}/>} /> <Route path='register/:buildingId' element={<EntryPage children={<Form children={<RegisterForm />} title="Create Account" />} />} />
<Route path='register/error' element={<EntryPage children={<div>Invalid invitational link - screen to be implemented</div>} />} /> <Route path='register/error' element={<EntryPage children={<div>Invalid invitational link - screen to be implemented</div>} />} />
<Route path='login' element={<EntryPage children={<Form children={<LoginForm/>} title="Login" />}/>}/> <Route path='login' element={<EntryPage children={<Form children={<LoginForm />} title="Login" />} />} />
<Route path='*' element={<Navigate replace to="login"/>} /> <Route path='tenant' element={<Application />} />
<Route path='*' element={<Navigate replace to="login" />} />
</Routes> </Routes>
</Router> </Router>
); );
} }
export default App; export default App;
...@@ -210,7 +210,7 @@ export function postLogin(username, password, isManager, callback) { ...@@ -210,7 +210,7 @@ export function postLogin(username, password, isManager, callback) {
{ {
username, username,
password password
}, callback, "POST", false) }, callback, "POST", false)(null)
} }
export function getBuilding(buildingId) { export function getBuilding(buildingId) {
...@@ -253,7 +253,7 @@ export function getTenentVoting(buildingId, callback) { ...@@ -253,7 +253,7 @@ export function getTenentVoting(buildingId, callback) {
} }
export function getTenantInfo(callback) { export function getTenantInfo(callback) {
return thunkRequestTenant('/info', null, callback, "GET") return thunkRequestTenant('/info', null, callback, "GET")(null)
} }
export function getAllUsers(callback) { export function getAllUsers(callback) {
......
import { Box } from "@mui/material";
import { useEffect } from "react"
import { useNavigate } from "react-router-dom"
import { JWT_TENANT } from "../actions/network-manager"
import { ApplicationTab } from "./ApplicationTab";
import { NavBar } from "./NavBar";
import { SideMenu } from "./SideMenu";
const classes = {
mainWrapper: {
display: 'flex',
flexDirection: 'row'
},
rightSideWrapper: {
display: 'flex',
flexDirection: 'column',
width: '85%'
}
} as const;
export const Application = () => {
const navigate = useNavigate();
useEffect(() => {
if (!localStorage.getItem(JWT_TENANT)) {
navigate('../login');
}
}, [navigate])
return (
<Box sx={classes.mainWrapper}>
<SideMenu />
<Box sx={classes.rightSideWrapper}>
<NavBar />
<ApplicationTab />
</Box>
</Box>
)
}
\ No newline at end of file
import { Box } from "@mui/material"
const classes = {
wrapper: {
backgroundColor: 'rgb(241, 245, 249)',
minHeight: '95vh', width: '100%'
}
} as const;
export const ApplicationTab = () => {
return (
<Box sx={classes.wrapper}></Box>
)
}
\ No newline at end of file
import { Button, TextField } from "@mui/material" import { Button, TextField } from "@mui/material"
import { useState } from "react"; import { useEffect, useState } from "react";
import { LoginInfo } from "../internal-types"; import { LoginInfo } from "../internal-types";
import { postLogin } from "../actions/data-manager";
import { JWT_TENANT } from "../actions/network-manager";
import { useNavigate } from "react-router-dom";
const classes = { const classes = {
textField: { textField: {
...@@ -56,7 +59,27 @@ const classes = { ...@@ -56,7 +59,27 @@ const classes = {
export const LoginForm = () => { export const LoginForm = () => {
const [loginInfo, setLoginInfo] = useState<LoginInfo>({username: '', password: ''}); const [loginInfo, setLoginInfo] = useState<LoginInfo>({ username: '', password: '' });
const navigate = useNavigate();
useEffect(() => {
if (!!localStorage.getItem(JWT_TENANT)) {
navigate('../tenant');
}
}, [navigate]);
const loginCallback = (err: any, res: any, dsp: any) => {
if (err) {
console.log("Login error");
console.log(err);
localStorage.removeItem(JWT_TENANT)
return;
}
localStorage.setItem(JWT_TENANT, res);
navigate('../tenant');
}
return ( return (
<> <>
...@@ -67,7 +90,7 @@ export const LoginForm = () => { ...@@ -67,7 +90,7 @@ export const LoginForm = () => {
sx={classes.textField} sx={classes.textField}
value={loginInfo.username} value={loginInfo.username}
onChange={(e) => { onChange={(e) => {
setLoginInfo({...loginInfo, username: e.target.value}) setLoginInfo({ ...loginInfo, username: e.target.value })
}} }}
/> />
<TextField <TextField
...@@ -78,9 +101,20 @@ export const LoginForm = () => { ...@@ -78,9 +101,20 @@ export const LoginForm = () => {
sx={classes.textField} sx={classes.textField}
value={loginInfo.password} value={loginInfo.password}
onChange={(e) => { onChange={(e) => {
setLoginInfo({...loginInfo, password: e.target.value}) setLoginInfo({ ...loginInfo, password: e.target.value })
}} }}
/> />
<Button sx={classes.loginButton} variant='contained'>Login</Button> <Button
sx={classes.loginButton}
variant='contained'
onClick={(event) => {
event.preventDefault();
postLogin(
loginInfo.username,
loginInfo.password,
false,
loginCallback)
}}
>Login</Button>
</>) </>)
} }
\ No newline at end of file
import { Box } from "@mui/material";
const classes = {
wrapper: {
height: '5vh',
backgroundColor: 'white',
boxSizing: 'border-box',
border: '2px solid rgb(213, 225, 239)',
position: 'sticky',
top: '0'
},
} as const;
export const NavBar = () => {
return (
<Box sx={classes.wrapper}></Box>
);
}
\ No newline at end of file
import { Box, Typography } from "@mui/material";
import { useEffect, useState } from "react";
import { getTenantInfo } from "../actions/data-manager";
import { SubMenu } from "../internal-types";
import { SideMenuItem } from "./SideMenuItem";
import { ActivityIcon } from "./icons/ActivityIcon";
import { HomeIcon } from "./icons/HomeIcon";
import { NotificationIcon } from "./icons/NotificationIcon";
import { VotingIcon } from "./icons/VotingIcon";
import { InvoicesIcon } from "./icons/InvoicesIcon";
import { BuidlingInvoicesIcon } from "./icons/BuildingInvoicesIcon";
import { DocumentIcon } from "./icons/DocumentIcon";
import { SettingsIcon } from "./icons/SettingsIcon";
import { LanguageIcon } from "./icons/LanguageIcon";
import { LogoutIcon } from "./icons/LogoutIcon";
import { UserIcon } from "./icons/UserIcon";
const subMenu: SubMenu[] = [
{
title: 'Glavni meni',
items: [
{ title: 'Pregled', icon: <HomeIcon /> },
{ title: 'Obaveštenja', icon: <NotificationIcon /> },
{ title: 'Aktivnosti', icon: <ActivityIcon /> },
{ title: 'Glasanje', icon: <VotingIcon /> }
]
},
{
title: 'Računi/Finansije',
items: [
{ title: 'Moj Račun', icon: <InvoicesIcon /> },
{ title: 'Finansije zgrade', icon: <BuidlingInvoicesIcon /> },
{ title: 'Dokumenta', icon: <DocumentIcon /> }]
},
{
title: 'Aplikacija',
items: [
{ title: 'Podešavanja', icon: <SettingsIcon /> },
{ title: 'Language', icon: <LanguageIcon /> }]
}
];
const classes = {
wrapper: {
width: '15%',
backgroundColor: 'rgba(19, 34, 50, 1)',
height: '100vh',
position: 'sticky',
top: '0px'
},
content: {
display: 'flex',
flexDirection: 'column',
paddingInline: '3%',
paddingBlock: '7.5%',
gap: '30px'
},
logo: {
width: '75%',
alignSelf: 'center'
},
accountInfo: {
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
gap: '5px'
},
menuText: {
color: 'rgb(204, 217, 232)',
margin: '0px',
fontFamily: 'Inter',
},
boldText: {
fontWeight: 'bolder',
},
signOut: {
display: 'flex',
flexDirection: 'row',
width: '100%',
justifyContent: 'center',
gap: '10px',
alignItems: 'center'
}
} as const;
export const SideMenu = () => {
const [tenantInfo, setTenantInfo] = useState<any>();
useEffect(() => {
getTenantInfo((err: any, data: any) => {
setTenantInfo(data);
})
}, []);
return (
<>
{
tenantInfo &&
<Box sx={classes.wrapper}>
<Box sx={classes.content}>
{/* TODO Create component for logo as well. */}
<img src={process.env.PUBLIC_URL + '/assets/logo-svg.svg'} alt='logo' style={classes.logo} />
<Box sx={classes.accountInfo}>
<UserIcon />
<Typography variant="body1" sx={{ ...classes.menuText, ...classes.boldText }}>{tenantInfo.firstName}</Typography>
<Typography style={classes.menuText}>{tenantInfo.email}</Typography>
<Box sx={classes.signOut}>
<Typography style={classes.menuText}>Sign out</Typography>
<LogoutIcon />
</Box>
</Box>
{subMenu.map((menu, index) => (
<SideMenuItem key={index} content={menu} />
))}
</Box>
</Box>
}
</>
);
}
\ No newline at end of file
import { Box, Typography } from "@mui/material";
import { SubMenu } from "../internal-types";
const classes = {
wrapper : {
display: 'flex',
flexDirection: 'column',
color: 'white',
paddingInline: '10%',
gap: '10px'
},
itemTitle: {
color: 'rgb(24, 95, 167)'
},
itemsWrapper: {
display: 'flex',
flexDirection: 'row',
gap: '10%',
alignItems: 'center'
}
} as const;
export const SideMenuItem = ({ content }: { content: SubMenu }) => {
return (
<Box sx={classes.wrapper}>
<Typography sx={classes.itemTitle}> {content.title.toUpperCase()}</Typography>
{content.items.map((item, ind) => (
<Box sx={classes.itemsWrapper} key={ind}>
{!!item.icon && item.icon}
<Typography>{item.title}</Typography>
</Box>
))}
</Box>
);
}
\ No newline at end of file
import './Icons.css';
export const ActivityIcon = () => {
return (
<svg xmlns="http://www.w3.org/2000/svg" className="icons-stroke" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
<path strokeLinecap="round" strokeLinejoin="round" d="M13 10V3L4 14h7v7l9-11h-7z" />
</svg>
);
}
\ No newline at end of file
import './Icons.css';
export const BuidlingInvoicesIcon = () => {
return (
<svg xmlns="http://www.w3.org/2000/svg" className="icons-stroke" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
<path strokeLinecap="round" strokeLinejoin="round" d="M3 10h18M7 15h1m4 0h1m-7 4h12a3 3 0 003-3V8a3 3 0 00-3-3H6a3 3 0 00-3 3v8a3 3 0 003 3z" />
</svg>
);
}
\ No newline at end of file
import './Icons.css';
export const DocumentIcon = () => {
return (
<svg xmlns="http://www.w3.org/2000/svg" className="icons-stroke" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
<path strokeLinecap="round" strokeLinejoin="round" d="M7 21h10a2 2 0 002-2V9.414a1 1 0 00-.293-.707l-5.414-5.414A1 1 0 0012.586 3H7a2 2 0 00-2 2v14a2 2 0 002 2z" />
</svg>
);
}
\ No newline at end of file
import './Icons.css';
export const HomeIcon = () => {
return (
<svg xmlns="http://www.w3.org/2000/svg" className="icons-stroke" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
<path strokeLinecap="round" strokeLinejoin="round" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6" />
</svg>
);
}
\ No newline at end of file
.icons-stroke {
width: 12%;
stroke: rgb(86, 107, 130);
}
\ No newline at end of file
import './Icons.css';
export const InvoicesIcon = () => {
return (
<svg xmlns="http://www.w3.org/2000/svg" className="icons-stroke" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
<path strokeLinecap="round" strokeLinejoin="round" d="M9 17v-2m3 2v-4m3 4v-6m2 10H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
</svg>
);
}
\ No newline at end of file
import './Icons.css';
export const LanguageIcon = () => {
return (
<svg xmlns="http://www.w3.org/2000/svg" className="icons-stroke" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
<path strokeLinecap="round" strokeLinejoin="round" d="M3 5h12M9 3v2m1.048 9.5A18.022 18.022 0 016.412 9m6.088 9h7M11 21l5-10 5 10M12.751 5C11.783 10.77 8.07 15.61 3 18.129" />
</svg>
);
}
\ No newline at end of file
import './Icons.css';
export const LogoutIcon = () => {
return (
<svg xmlns="http://www.w3.org/2000/svg" className="icons-stroke" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
<path strokeLinecap="round" strokeLinejoin="round" d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1" />
</svg>
);
}
\ No newline at end of file
import './Icons.css';
export const NotificationIcon = () => {
return (
<svg xmlns="http://www.w3.org/2000/svg" className="icons-stroke" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
<path strokeLinecap="round" strokeLinejoin="round" d="M7 8h10M7 12h4m1 8l-4-4H5a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v8a2 2 0 01-2 2h-3l-4 4z" />
</svg>
);
}
\ No newline at end of file
import './Icons.css';
export const SettingsIcon = () => {
return (
<svg xmlns="http://www.w3.org/2000/svg" className="icons-stroke" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
<path strokeLinecap="round" strokeLinejoin="round" d="M12 6V4m0 2a2 2 0 100 4m0-4a2 2 0 110 4m-6 8a2 2 0 100-4m0 4a2 2 0 110-4m0 4v2m0-6V4m6 6v10m6-2a2 2 0 100-4m0 4a2 2 0 110-4m0 4v2m0-6V4" />
</svg>
);
}
\ No newline at end of file
import './Icons.css';
export const UserIcon = () => {
return (
<svg xmlns="http://www.w3.org/2000/svg" className="icons-stroke" viewBox="0 0 20 20" fill="rgb(86, 107, 130)" style={{width: '30%'}}>
<path fillRule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-6-3a2 2 0 11-4 0 2 2 0 014 0zm-2 4a5 5 0 00-4.546 2.916A5.986 5.986 0 0010 16a5.986 5.986 0 004.546-2.084A5 5 0 0010 11z" clipRule="evenodd" />
</svg>
);
}
import './Icons.css';
export const VotingIcon = () => {
return (
<svg xmlns="http://www.w3.org/2000/svg" className="icons-stroke" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
<path strokeLinecap="round" strokeLinejoin="round" d="M3 21v-4m0 0V5a2 2 0 012-2h6.5l1 1H21l-3 6 3 6h-8.5l-1-1H5a2 2 0 00-2 2zm9-13.5V9" />
</svg>
);
}
\ No newline at end of file
import { ReactElement } from "react"
export type RegistrationInfo = { export type RegistrationInfo = {
firstName: string, firstName: string,
lastName: string, lastName: string,
...@@ -14,4 +16,14 @@ export type RegistrationInfo = { ...@@ -14,4 +16,14 @@ export type RegistrationInfo = {
export type LoginInfo = { export type LoginInfo = {
username: string, username: string,
password: string password: string
}
export type SubMenu = {
title: string,
items: SubMenuItem[]
}
export type SubMenuItem = {
icon?: ReactElement,
title: string
} }
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment