diff --git a/.prettierignore b/.prettierignore index 18a79da9d9afe8f54cdf97bc1d83bc5618009c1c..af62006000ff71589d3375f42ebb5dabb8365f74 100644 --- a/.prettierignore +++ b/.prettierignore @@ -62,4 +62,6 @@ config/* # Auto generated by React serviceWorker.* -setupTests.* \ No newline at end of file +setupTests.* + +tools/* \ No newline at end of file diff --git a/README.md b/README.md index 5f6fa9352035a5fdd0be9124202fa33d846be0ef..b3d603f830e39045997f763f3f64ff1aa1d1ec73 100644 --- a/README.md +++ b/README.md @@ -1,37 +1,11 @@ -This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). - -Environment variables needed in development found in `.env.example` ! - -## Available Scripts - -In the project directory, you can run: - -### `npm start` - -Runs the app in the development mode.<br /> -Open [http://localhost:3000](http://localhost:3000) to view it in the browser. - -### `npm test` - -Launches the test runner in the interactive watch mode.<br /> -See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. +## BodySCH Frontend -### `npm run build` - -Builds the app - -### `npm run lint` - -Runs the linter in the console. - -### `npm run format` - -Format the files in the project with prettier. - -### `npm run validate` - -Checks the formatting and linter rules for all the files in the source. +This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). -### `npm run fix` +#### Local proxy -It runs the formatting and the linter with --fix option +`./tools/local_proxy/Caddyfile/` +Create a local proxy with caddy: +`docker run -d -it --name caddyFrontend -v /path/to/Caddyfile:/etc/caddy/Caddyfile --network host caddy` +When you change the config just `docker stop ...` and `docker start ...`. +If you experience any error check it with `docker logs ...`. diff --git a/package-lock.json b/package-lock.json index fa2b8b6f56e084cbc2819d3c6385790ab2b843cc..d5729b90991739e6596e1061de2531510d8086a2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2512,6 +2512,14 @@ } } }, + "@material-ui/icons": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@material-ui/icons/-/icons-4.9.1.tgz", + "integrity": "sha512-GBitL3oBWO0hzBhvA9KxqcowRUsA0qzwKkURyC8nppnC3fw54KPKZ+d4V1Eeg/UnDRSzDaI9nGCdel/eh9AQMg==", + "requires": { + "@babel/runtime": "^7.4.4" + } + }, "@material-ui/styles": { "version": "4.10.0", "resolved": "https://registry.npmjs.org/@material-ui/styles/-/styles-4.10.0.tgz", @@ -2934,7 +2942,6 @@ "version": "3.3.1", "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz", "integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==", - "dev": true, "requires": { "@types/react": "*", "hoist-non-react-statics": "^3.3.0" @@ -3029,7 +3036,6 @@ "version": "0.63.6", "resolved": "https://registry.npmjs.org/@types/react-native/-/react-native-0.63.6.tgz", "integrity": "sha512-qAv/VOyXAk4it9MOsQoyUjUnEJ3kAW1FCRGi0OvfQDKLH1/FFogVFvoB6xAlBNc6lPyBtCg+nvzj/ScYe0PqCQ==", - "dev": true, "requires": { "@types/react": "*" } @@ -3072,7 +3078,6 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/@types/styled-components/-/styled-components-5.1.2.tgz", "integrity": "sha512-HNocYLfrsnNNm8NTS/W53OERSjRA8dx5Bn6wBd2rXXwt4Z3s+oqvY6/PbVt3e6sgtzI63GX//WiWiRhWur08qQ==", - "dev": true, "requires": { "@types/hoist-non-react-statics": "*", "@types/react": "*", @@ -3083,8 +3088,7 @@ "csstype": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.2.tgz", - "integrity": "sha512-ofovWglpqoqbfLNOTBNZLSbMuGrblAf1efvvArGKOZMBrIoJeu5UsAipQolkijtyQx5MtAzT/J9IHj/CEY1mJw==", - "dev": true + "integrity": "sha512-ofovWglpqoqbfLNOTBNZLSbMuGrblAf1efvvArGKOZMBrIoJeu5UsAipQolkijtyQx5MtAzT/J9IHj/CEY1mJw==" } } }, @@ -7889,9 +7893,9 @@ } }, "follow-redirects": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.12.1.tgz", - "integrity": "sha512-tmRv0AVuR7ZyouUHLeNSiO6pqulF7dYa3s19c6t+wz9LD69/uSzdMxJ2S91nTI9U3rt/IldxpzMOFejp6f0hjg==" + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.0.tgz", + "integrity": "sha512-aq6gF1BEKje4a9i9+5jimNFIpq4Q1WiwBToeRK5NvZBd/TRsmW8BsJfOEGkr76TbOyPVD3OVDN910EcUNtRYEA==" }, "for-in": { "version": "1.0.2", @@ -8562,17 +8566,6 @@ "requires-port": "^1.0.0" } }, - "http-proxy-middleware": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-0.19.1.tgz", - "integrity": "sha512-yHYTgWMQO8VvwNS22eLLloAkvungsKdKTLO8AJlftYIKNfJr3GK3zK0ZCfzDDGUBttdGc8xFy1mCitvNKQtC3Q==", - "requires": { - "http-proxy": "^1.17.0", - "is-glob": "^4.0.0", - "lodash": "^4.17.11", - "micromatch": "^3.1.10" - } - }, "http-signature": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", @@ -16914,6 +16907,17 @@ } } }, + "http-proxy-middleware": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-0.19.1.tgz", + "integrity": "sha512-yHYTgWMQO8VvwNS22eLLloAkvungsKdKTLO8AJlftYIKNfJr3GK3zK0ZCfzDDGUBttdGc8xFy1mCitvNKQtC3Q==", + "requires": { + "http-proxy": "^1.17.0", + "is-glob": "^4.0.0", + "lodash": "^4.17.11", + "micromatch": "^3.1.10" + } + }, "is-absolute-url": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/is-absolute-url/-/is-absolute-url-3.0.3.tgz", diff --git a/package.json b/package.json index a6c602e19f26c11f88c4f3a168be097b576d9f4c..26d80d02513dbe3c9fc73497bc8642b4bf268708 100644 --- a/package.json +++ b/package.json @@ -46,6 +46,8 @@ }, "dependencies": { "@material-ui/core": "^4.11.0", + "@material-ui/icons": "^4.9.1", + "@types/styled-components": "^5.1.2", "axios": "^0.20.0", "react": "^16.13.1", "react-dom": "^16.13.1", diff --git a/public/style.css b/public/style.css new file mode 100644 index 0000000000000000000000000000000000000000..5b9993abe8a209887cfbbd3a1959bc6a720bb93e --- /dev/null +++ b/public/style.css @@ -0,0 +1,24 @@ +#modal { + background-color: rgba(0, 0, 0, 0.4); + position: fixed; + left: 0; + right: 0; + bottom: 0; + top: 0; + z-index: 10; + display: flex; + justify-content: center; + align-items: center; +} + +#modal:empty { + display: none; +} + +#modal > div { + background-color: #ffffff; + max-width: 500px; + padding: 10px; + border-radius: 5px; + text-align: center; +} diff --git a/src/core/App.tsx b/src/core/App.tsx index ebed2efd7cd3c9fe7e9dc87e9c9f96982eac1d14..3053fbebcc45ce748e946faa7a9d2b45e4fb79f6 100644 --- a/src/core/App.tsx +++ b/src/core/App.tsx @@ -4,6 +4,7 @@ import styled from 'styled-components'; import Footer from './components/Footer'; import Header from './components/Header'; import MainRouting from './components/MainRouting'; +import { UserStateProvider } from './context/UserContext'; const Container = styled.div` height: 100%; @@ -18,15 +19,17 @@ const MainContent = styled.div` function App() { return ( - <Router> - <Container> - <Header /> - <MainContent> - <MainRouting /> - </MainContent> - <Footer /> - </Container> - </Router> + <UserStateProvider> + <Router> + <Container> + <Header /> + <MainContent> + <MainRouting /> + </MainContent> + <Footer /> + </Container> + </Router> + </UserStateProvider> ); } diff --git a/src/core/components/CustomModal.tsx b/src/core/components/CustomModal.tsx new file mode 100644 index 0000000000000000000000000000000000000000..e0c6af7a42e9cf8c739090064058120c217d9a35 --- /dev/null +++ b/src/core/components/CustomModal.tsx @@ -0,0 +1,75 @@ +import { Modal } from '@material-ui/core'; +import { makeStyles } from '@material-ui/core/styles'; +import React, { Dispatch, SetStateAction } from 'react'; + +type Dispatcher<S> = Dispatch<SetStateAction<S>>; + +interface ICustomModalProps { + isOpen: boolean; + setIsOpen: Dispatcher<boolean>; + children: React.ReactNode; + customStyle: { + backgroundColor?: string; + padding?: string; + width?: string; + border?: string; + borderRadius?: string; + }; +} + +const useStyles = makeStyles((theme) => ({ + modal: (customStyle: ICustomModalProps['customStyle']) => ({ + backgroundColor: 'transparent', + margin: '7% auto', + padding: 'none', + border: 'none', + width: customStyle.width || '85%', + [theme.breakpoints.down(700)]: { + margin: '3% auto', + width: '95%', + }, + }), + modalContent: (customStyle: ICustomModalProps['customStyle']) => ({ + ...{ + backgroundColor: customStyle.backgroundColor || 'theme.palette.background.paper', + border: customStyle.border || 'none', + padding: customStyle.padding || 'none', + borderRadius: customStyle.borderRadius || 'none', + boxShadow: theme.shadows[5], + '&:focus': { + outline: 'none', + }, + }, + }), +})); + +export const CustomModal = ({ + isOpen, + setIsOpen, + customStyle = {}, + children, +}: ICustomModalProps) => { + const classes = useStyles(customStyle); + + const handleClose = () => { + setIsOpen(false); + }; + + const body = <div className={classes.modalContent}>{children}</div>; + + return ( + <div> + <Modal + className={classes.modal} + open={isOpen} + onClose={handleClose} + aria-labelledby="custom-modal" + aria-describedby="custom-modal" + > + {body} + </Modal> + </div> + ); +}; + +export default CustomModal; diff --git a/src/core/components/Header.tsx b/src/core/components/Header.tsx index 11ab0b6937a7686ab6ed43ab54b8bf72f824ca90..fef7cc09770da760fa21d9477d1a207269712d44 100644 --- a/src/core/components/Header.tsx +++ b/src/core/components/Header.tsx @@ -1,5 +1,105 @@ -import React from 'react'; +import { + AppBar, + Button, + Grid, + makeStyles, + Tab, + Tabs, + Toolbar, + Typography, +} from '@material-ui/core'; +import React, { useState } from 'react'; +import { useHistory } from 'react-router'; -const Header = () => <div>Header</div>; +interface AppBarMenuItem { + id: number; + title: string; + redirectTo: string; +} + +const MENU_ITEMS: AppBarMenuItem[] = [ + { + id: 0, + title: 'News', + redirectTo: '/news', + }, + { + id: 1, + title: 'About', + redirectTo: '/about', + }, + { + id: 2, + title: 'FAQ', + redirectTo: '/faq', + }, +]; + +const useStyles = makeStyles((theme) => ({ + appBar: { + backgroundColor: '#E5BD2F', + padding: theme.spacing(1), + }, + tab: { + fontWeight: 600, + padding: '0px', + }, + indicator: { + backgroundColor: 'white', + }, + navItem: { + flex: 1, + alignItems: 'center', + '&:first-child': { + marginRight: 'auto;', + }, + '&:last-child': { + marginRight: 'auto;', + }, + }, +})); + +const Header: React.FC = () => { + const classes = useStyles(); + const history = useHistory(); + const [value, setValue] = useState( + MENU_ITEMS.find((item) => history.location.pathname === item.redirectTo)?.id ?? 0, + ); + + return ( + <AppBar className={classes.appBar} position="static"> + <Toolbar> + <Grid container> + <Grid className={classes.navItem} item container justify="flex-start"> + <Tabs + value={value} + onChange={(event, val) => { + setValue(val); + }} + TabIndicatorProps={{ className: classes.indicator }} + > + {MENU_ITEMS.map((item) => ( + <Tab + key={item.id} + className={classes.tab} + label={item.title} + onClick={() => { + history.push(item.redirectTo); + }} + /> + ))} + </Tabs> + </Grid> + <Grid className={classes.navItem} item container justify="center"> + <Typography variant="h4">SCH-BODY</Typography> + </Grid> + <Grid className={classes.navItem} item container justify="flex-end"> + <Button>Login</Button> + </Grid> + </Grid> + </Toolbar> + </AppBar> + ); +}; export default Header; diff --git a/src/core/components/MainRouting.tsx b/src/core/components/MainRouting.tsx index 882d03dd31c84c881e1648e00a21f0bc8d991c06..10d1ee86a868ff0ed9ef2e06119013b0366cd61e 100644 --- a/src/core/components/MainRouting.tsx +++ b/src/core/components/MainRouting.tsx @@ -1,13 +1,14 @@ import React from 'react'; import { Route, Switch } from 'react-router'; +import { ProfileButton } from './ProfileButton'; -const Main = () => ( +const MainRouting: React.FC = () => ( <Switch> <Route path="/" exact> - Hello + <ProfileButton /> </Route> <Route path="/news">News</Route> </Switch> ); -export default Main; +export default MainRouting; diff --git a/src/core/components/ProfileButton.tsx b/src/core/components/ProfileButton.tsx new file mode 100644 index 0000000000000000000000000000000000000000..34451f47da81afa65fbc46141699763f75aee43a --- /dev/null +++ b/src/core/components/ProfileButton.tsx @@ -0,0 +1,22 @@ +import { Button } from '@material-ui/core'; +import React, { useState } from 'react'; +import { ProfileModal } from './ProfileModal'; + +export const ProfileButton = () => { + const [isProfileModalOpen, setIsProfileModalOpen] = useState<boolean>(true); + + const handleProfileModalOpen = () => { + setIsProfileModalOpen(true); + }; + + return ( + <div> + <Button variant="contained" color="primary" onClick={handleProfileModalOpen}> + Open Profile + </Button> + <ProfileModal isOpen={isProfileModalOpen} setIsOpen={setIsProfileModalOpen} /> + </div> + ); +}; + +export default ProfileButton; diff --git a/src/core/components/ProfileModal.tsx b/src/core/components/ProfileModal.tsx new file mode 100644 index 0000000000000000000000000000000000000000..bbaaaf5c803f4e137277c206e8bc5c71abd8cd1f --- /dev/null +++ b/src/core/components/ProfileModal.tsx @@ -0,0 +1,78 @@ +import { + Avatar, Box, Button, IconButton, TextField, Typography, +} from '@material-ui/core'; +import CloseIcon from '@material-ui/icons/Close'; +import React, { Dispatch, SetStateAction } from 'react'; +import { CustomModal } from './CustomModal'; + +type Dispatcher<S> = Dispatch<SetStateAction<S>>; + +interface IProfileModalProps { + isOpen: boolean; + setIsOpen: Dispatcher<boolean>; +} + +export const ProfileModal = ({ isOpen, setIsOpen }: IProfileModalProps) => { + const onClose = () => { + setIsOpen(false); + }; + return ( + <CustomModal + isOpen={isOpen} + setIsOpen={setIsOpen} + customStyle={{ + backgroundColor: '#EBEBEB', + padding: '15px', + width: '500px', + borderRadius: '8px', + }} + > + <Box + fontWeight="fontWeightBold" + display="flex" + justifyContent="space-between" + alignItems="center" + pl={2} + > + <Typography variant="h5" component="h5"> + Profilom + </Typography> + <IconButton onClick={onClose}> + <CloseIcon /> + </IconButton> + </Box> + <Box display="flex" justifyContent="center" my={3}> + <Avatar alt="Profile image" variant="rounded" style={{ width: '200px', height: '200px' }} /> + </Box> + <Box px={1} mt={2} mb={1}> + <Box my={1}> + <TextField + label="Diákigazolvány szám" + variant="outlined" + color="secondary" + fullWidth + size="small" + style={{ backgroundColor: 'white' }} + /> + </Box> + <Box my={1}> + <TextField + label="Szobaszám" + variant="outlined" + color="secondary" + fullWidth + size="small" + style={{ backgroundColor: 'white' }} + /> + </Box> + <Box display="flex" justifyContent="flex-end" mt={3}> + <Button variant="contained" onClick={onClose}> + Mentés + </Button> + </Box> + </Box> + </CustomModal> + ); +}; + +export default ProfileModal; diff --git a/src/core/context/UserContext.tsx b/src/core/context/UserContext.tsx new file mode 100644 index 0000000000000000000000000000000000000000..d7f84377340ab90295155068e6a502dafc59623e --- /dev/null +++ b/src/core/context/UserContext.tsx @@ -0,0 +1,67 @@ +import React, { createContext, ReactNode, useReducer } from 'react'; + +// Interface definitions +interface IWarnings extends Document { + text: string; + date: Date; + given_by: { + _id: string; + name: string; + }; +} + +enum Role { + Admin, + Staff, + User, +} + +export interface IProfile { + external_id: string; + studentCardNumber?: string; + roomNumber?: string; + picture?: string; + role: Role.Admin | Role.Staff | Role.User; + email: string; + name: string; + warnings: [IWarnings] | []; +} + +type Props = { + children: ReactNode; +}; + +interface IContextProps { + state: IProfile; + dispatch: ({ type }: { type: string }) => void; +} + +// Context +const initialState = { + external_id: 'abcd', + studentCardNumber: '1234', + roomNumber: 104, + picture: 'alma.jpg', + role: Role.User, + email: 'alma@gmail.com', + name: 'Nagy Gizike', + warning: [], +}; +export const userContext = createContext({} as IContextProps); +const { Provider } = userContext; + +export const UserStateProvider: React.FunctionComponent<Props> = (props: Props) => { + const { children } = props; + const [state, dispatch] = useReducer((prevState: any, action: any) => { + switch (action.type) { + case 'update': + return action.payload; + default: + return prevState; // do nothing + } + }, initialState); + + return <Provider value={{ state, dispatch }}>{children}</Provider>; +}; + +export default { userContext, UserStateProvider }; diff --git a/src/index.css b/src/index.css index 06f062d46be0e706e86ea5a8f8db1430a661d57f..65137f8e0fbdd391412f19afdcaf81fe8ac5375b 100644 --- a/src/index.css +++ b/src/index.css @@ -1,5 +1,7 @@ html, body, #root { + margin: 0; height: 100%; + margin: 0; } diff --git a/tools/local_proxy/Caddyfile b/tools/local_proxy/Caddyfile new file mode 100644 index 0000000000000000000000000000000000000000..0a389bc76920c518d7ae24343f3fd2aeeaef395a --- /dev/null +++ b/tools/local_proxy/Caddyfile @@ -0,0 +1,13 @@ +# PORT +:8080 + +# Reverse proxy +# rewrite + +# auth-service +route /api/* { + #uri strip_prefix /api + reverse_proxy * 127.0.0.1:8000 +} + +reverse_proxy * 127.0.0.1:3000