diff --git a/src/App.css b/src/App.css deleted file mode 100644 index 74b5e053450a48a6bdb4d71aad648e7af821975c..0000000000000000000000000000000000000000 --- a/src/App.css +++ /dev/null @@ -1,38 +0,0 @@ -.App { - text-align: center; -} - -.App-logo { - height: 40vmin; - pointer-events: none; -} - -@media (prefers-reduced-motion: no-preference) { - .App-logo { - animation: App-logo-spin infinite 20s linear; - } -} - -.App-header { - background-color: #282c34; - min-height: 100vh; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - font-size: calc(10px + 2vmin); - color: white; -} - -.App-link { - color: #61dafb; -} - -@keyframes App-logo-spin { - from { - transform: rotate(0deg); - } - to { - transform: rotate(360deg); - } -} diff --git a/src/App.md b/src/App.md new file mode 100644 index 0000000000000000000000000000000000000000..2f0c205bd7beeed9e23ceded925f6a131e6efa9c --- /dev/null +++ b/src/App.md @@ -0,0 +1,5 @@ +The folders are divided basing on if the user is authenticated or not authenticated. +For this, the pages are organized as: +- AuthUser: private pages (for authenticated users). + Those pages must be covered by a PrivateRoute except for SignInForm and SignUpForm. +- NonAuthUser: public pages, require a normal Route. \ No newline at end of file diff --git a/src/App.style.ts b/src/App.style.ts new file mode 100644 index 0000000000000000000000000000000000000000..94f03fdec5580d8c6272d090a9ce013ff3473e09 --- /dev/null +++ b/src/App.style.ts @@ -0,0 +1,29 @@ +import { createMuiTheme } from '@material-ui/core/styles'; + +export const muiTheme = createMuiTheme({ + palette: { + primary: { + main: '#5e5ce4', + }, + secondary: { + main: '#e2e45c', + }, + }, + + typography: { + fontSize: 16, + }, + + overrides: { + MuiTabs: { + indicator: { + backgroundColor: 'white', + }, + }, + MuiTab: { + wrapper: { + flexDirection: 'row', + }, + }, + }, +}); diff --git a/src/App.tsx b/src/App.tsx index 82f532d6c28d2a341fa463602be3f7c737d2960f..facca47bdc9ca174ecee01b589b483096b197f1c 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,63 +1,55 @@ import React, { FC } from 'react'; -import { createMuiTheme, ThemeProvider } from '@material-ui/core/styles'; +import { ThemeProvider } from '@material-ui/core/styles'; import { BrowserRouter as Router, Switch, Route } from 'react-router-dom'; -import { HomePage } from 'components/HomePage/HomePage'; -import { AuthUser } from 'components/AuthUser/AuthUser'; -import { LandingPage } from 'components/LandingPage/LandingPage'; -import { PrivateRoute } from 'components/api/PrivateRoute/PrivateRoute'; -import { AuthRoutes, NonAuthRoutes } from 'components/api/routes'; +import { AuthRoutes, NonAuthRoutes } from 'api/routes'; +import { Roles } from 'api/userRoles'; +import { configDjangoCookieName } from 'api/configDjangoCookieName'; +import { muiTheme } from 'App.style'; +import { withAuthorization } from 'components/Authorization/Authorization'; +import { useRole } from 'hooks/useRole'; +import { useAuth } from 'hooks/useAuth'; +import { AuthUser } from 'components/Auth/Auth'; +import { AuthContext } from 'components/Auth/AuthContext'; +import { Dashboard } from 'components/Dashboard/Dashboard'; import { NotFound } from 'components/NotFound/NotFound'; -import { ProfilePage } from 'components/ProfilePage/ProfilePage'; -import { Roles } from 'components/api/userRoles'; import { Unauthorized } from 'components/Unauthorized/Unauthorized'; +import { LandingPage } from 'components/LandingPage/LandingPage'; -const theme = createMuiTheme({ - palette: { - primary: { - main: '#5e5ce4', - }, - secondary: { - main: '#e2e45c', - }, - }, +const All = withAuthorization([ + Roles.admin, + Roles.operator, + Roles.senior, + Roles.driver, +]); - typography: { - fontSize: 16, - }, +/** + * Entry point of the app. + */ +export const App: FC = () => { + configDjangoCookieName(); + const [role, setRole] = useRole(); + const [isAuth, setIsAuth] = useAuth(); - overrides: { - MuiTabs: { - indicator: { - backgroundColor: 'white', - }, - }, - MuiTab: { - wrapper: { - flexDirection: 'row', - }, - }, - }, -}); + const value = { role, setRole, isAuth, setIsAuth }; -export const App: FC = () => ( - <Router> + return ( <div data-testid="App"> - <Switch> - <Route path={NonAuthRoutes.signIn} component={AuthUser} /> - <Route exact path={NonAuthRoutes.home} component={LandingPage} /> - <PrivateRoute - path={AuthRoutes.dashboard} - Component={HomePage} - requiredRoles={[Roles.admin, Roles.operator, Roles.senior]} - /> - <PrivateRoute - path={AuthRoutes.profile} - Component={ProfilePage} - requiredRoles={[Roles.admin, Roles.operator, Roles.senior]} - /> - <Route path={NonAuthRoutes.unauthorized} component={Unauthorized} /> - <Route component={NotFound} /> - </Switch> + <ThemeProvider theme={muiTheme}> + <AuthContext.Provider value={value}> + <Router> + <Switch> + <Route exact path={NonAuthRoutes.home} component={LandingPage} /> + <Route path={NonAuthRoutes.auth} component={AuthUser} /> + <Route path={AuthRoutes.dashboard} component={All(Dashboard)} /> + <Route + path={NonAuthRoutes.unauthorized} + component={Unauthorized} + /> + <Route component={NotFound} /> + </Switch> + </Router> + </AuthContext.Provider> + </ThemeProvider> </div> - </Router> -); + ); +}; diff --git a/src/api/configDjangoCookieName.ts b/src/api/configDjangoCookieName.ts new file mode 100644 index 0000000000000000000000000000000000000000..4213d5ad9490abdc3726d9a94db4c7059485d158 --- /dev/null +++ b/src/api/configDjangoCookieName.ts @@ -0,0 +1,10 @@ +import axios from 'axios'; + +/** + * Config for django compatibility. + */ +export const configDjangoCookieName = (): void => { + axios.defaults.xsrfHeaderName = 'X-CSRFTOKEN'; + axios.defaults.xsrfCookieName = 'csrftoken'; + axios.defaults.withCredentials = true; +}; diff --git a/src/api/fetchCookie.ts b/src/api/fetchCookie.ts new file mode 100644 index 0000000000000000000000000000000000000000..de99b415d2982d9630153ea1306035fd0b7937fc --- /dev/null +++ b/src/api/fetchCookie.ts @@ -0,0 +1,10 @@ +import axios from 'axios'; + +/** + * @async + * Call for csrf cookie. This cookie is the user session identifier and + * must be sent during the login process. + * @returns {string} csrf cookie + */ +export const fetchCookie = async (): Promise<string> => + axios('/api/web/csrf').then((res) => res.data.token); diff --git a/src/api/getReservations.ts b/src/api/getReservations.ts new file mode 100644 index 0000000000000000000000000000000000000000..9f28c7d3455493636f6038ae53fe43de6b6f4f5b --- /dev/null +++ b/src/api/getReservations.ts @@ -0,0 +1,4 @@ +import axios from 'axios'; + +export const getReservations = async (): Promise<any> => + axios('/api/web/reservations/').then((res) => res.data); diff --git a/src/api/getRole.ts b/src/api/getRole.ts new file mode 100644 index 0000000000000000000000000000000000000000..395a0350e5d568b7dc2f300870f9d42ef495fb79 --- /dev/null +++ b/src/api/getRole.ts @@ -0,0 +1,10 @@ +import axios from 'axios'; + +/** + * @async + * Asks for the current set role. + * @returns {string} empty if the role is not set or a name between those defined + * in {@see userRoles} + */ +export const getRole = async (): Promise<string> => + axios('/api/web/login/get_role').then((res) => res.data.role); diff --git a/src/api/getRoles.ts b/src/api/getRoles.ts new file mode 100644 index 0000000000000000000000000000000000000000..2d3983e47e2d27ee3331a8dc9675800bb4f0ca56 --- /dev/null +++ b/src/api/getRoles.ts @@ -0,0 +1,9 @@ +import axios from 'axios'; + +/** + * @async + * Ask the server all the roles of an user if the user has multiple roles. + * @returns {string[]} array of roles. + */ +export const getRoles = async (): Promise<string[]> => + axios('/api/web/login/get_roles').then((res) => res.data.roles); diff --git a/src/api/isAuthenticated.ts b/src/api/isAuthenticated.ts new file mode 100644 index 0000000000000000000000000000000000000000..ab8dc20cff1d753fd14c4596106328ef93983d2f --- /dev/null +++ b/src/api/isAuthenticated.ts @@ -0,0 +1,11 @@ +import axios from 'axios'; + +/** + * @async + * Ask the server if the user is authenticated. + * @returns {boolean} true or false if the user is authenticated. + */ +export const isAuthenticated = async (): Promise<boolean> => + axios('/api/web/login/is_authenticated').then( + (res) => res.data.is_authenticated, + ); diff --git a/src/components/api/routes.ts b/src/api/routes.ts similarity index 76% rename from src/components/api/routes.ts rename to src/api/routes.ts index 70860945c86eb10f079548318fe4523420ab3d21..cd7039900d83be1a34aeb9cd4247f5ac0ad87c53 100644 --- a/src/components/api/routes.ts +++ b/src/api/routes.ts @@ -5,7 +5,10 @@ */ export enum AuthRoutes { dashboard = '/dashboard', + home = '/home', profile = '/profile', + reservation = '/reservation', + choseRole = '/choseRole', } /** @@ -13,6 +16,8 @@ export enum AuthRoutes { */ export enum NonAuthRoutes { home = '/', + auth = '/auth', signIn = '/signIn', + signUp = '/signUp', unauthorized = '/unauthorized', } diff --git a/src/api/setRole.ts b/src/api/setRole.ts new file mode 100644 index 0000000000000000000000000000000000000000..27087fb3697344e4899db8860a77e82168450f9f --- /dev/null +++ b/src/api/setRole.ts @@ -0,0 +1,9 @@ +import axios from 'axios'; + +/** + * @async + * Set the role of the user in the database. + * @return {string} role + */ +export const setRole = async (role: string): Promise<void> => + axios.post('/api/web/login/set_role', { role }); diff --git a/src/api/userRoles.ts b/src/api/userRoles.ts new file mode 100644 index 0000000000000000000000000000000000000000..77e66434cebfbba2299eafd90a537960766934c1 --- /dev/null +++ b/src/api/userRoles.ts @@ -0,0 +1,9 @@ +export enum Roles { + /** Website visitor, is not logged. Can access to NonAuthRoutes */ + visitor = '', + /** Can access to routes wrapped by withAuthorization. */ + senior = 'senior', + admin = 'admin', + operator = 'operator', + driver = 'driver', +} diff --git a/src/components/Auth/Auth.tsx b/src/components/Auth/Auth.tsx new file mode 100644 index 0000000000000000000000000000000000000000..bd5dd6d8f45438dd7cc6eac1628953f7812e4360 --- /dev/null +++ b/src/components/Auth/Auth.tsx @@ -0,0 +1,21 @@ +import React, { FC } from 'react'; +import Container from '@material-ui/core/Container'; +import { Redirect, Route, useRouteMatch } from 'react-router-dom'; +import { AuthRoutes, NonAuthRoutes } from 'api/routes'; +import { ChoseRole } from 'components/Auth/ChoseRole/ChoseRole'; +import { SignInForm } from 'components/Auth/SignInForm/SignInForm'; +import { SignUpForm } from 'components/Auth/SignUpForm/SignUpForm'; + +/** + * Keeps all components related to SignIn/SignOut logic. + */ +export const AuthUser: FC = () => { + const { path } = useRouteMatch(); + return ( + <Container maxWidth="sm"> + <Route path={`${path}${NonAuthRoutes.signIn}`} component={SignInForm} /> + <Route path={`${path}${NonAuthRoutes.signUp}`} component={SignUpForm} /> + <Route path={`${path}${AuthRoutes.choseRole}`} component={ChoseRole} /> + </Container> + ); +}; diff --git a/src/components/Auth/AuthContext.tsx b/src/components/Auth/AuthContext.tsx new file mode 100644 index 0000000000000000000000000000000000000000..a94586988d500b74cf063b2e4727f0355a6cfd0e --- /dev/null +++ b/src/components/Auth/AuthContext.tsx @@ -0,0 +1,20 @@ +import { Roles } from 'api/userRoles'; +import { createContext } from 'react'; + +export type AuthData = { + role: string; + isAuth: boolean | null; + setRole: (role: string) => void; + setIsAuth: (state: boolean | null) => void; +}; + +export const AuthContext = createContext<AuthData>({ + role: Roles.visitor, + isAuth: false, + setRole: () => { + // Do nothing + }, + setIsAuth: () => { + // Do nothing + }, +}); diff --git a/src/components/Auth/ChoseRole/ChoseRole.md b/src/components/Auth/ChoseRole/ChoseRole.md new file mode 100644 index 0000000000000000000000000000000000000000..f62b0c2e7d51d362ed2975ebf07eb9d310f09a93 --- /dev/null +++ b/src/components/Auth/ChoseRole/ChoseRole.md @@ -0,0 +1,7 @@ +Chose role. + +``` +const role = ["operator", "driver",]; + +<ChoseRole role={role} /> +``` \ No newline at end of file diff --git a/src/components/Auth/ChoseRole/ChoseRole.test.tsx b/src/components/Auth/ChoseRole/ChoseRole.test.tsx new file mode 100644 index 0000000000000000000000000000000000000000..0f885e7fb9a7fbc97a7c9b0872ead5b8a3270682 --- /dev/null +++ b/src/components/Auth/ChoseRole/ChoseRole.test.tsx @@ -0,0 +1,10 @@ +import React from 'react'; +import { render } from '@testing-library/react'; +import { ChoseRole } from './ChoseRole'; + +describe('<ChoseRole />', () => { + it('renders without crashing', () => { + const wrapper = render(<ChoseRole />); + expect(wrapper.queryByTestId('ChoseRole')).toBeTruthy(); + }); +}); diff --git a/src/components/Auth/ChoseRole/ChoseRole.tsx b/src/components/Auth/ChoseRole/ChoseRole.tsx new file mode 100644 index 0000000000000000000000000000000000000000..334e2bbdf6582d738c62608511cfc49ff848b5f6 --- /dev/null +++ b/src/components/Auth/ChoseRole/ChoseRole.tsx @@ -0,0 +1,49 @@ +import React, { FC, useContext, useEffect, useState } from 'react'; +import Button from '@material-ui/core/Button'; +import { getRoles } from 'api/getRoles'; +import { setRole } from 'api/setRole'; +import { useHistory } from 'react-router-dom'; +import { AuthRoutes } from 'api/routes'; +import { AuthContext } from 'components/Auth/AuthContext'; + +/** + * Page that let's users decide role between available roles. + * This is intended to use when a user has more than one role. + * @returns ChoseRole component. + */ +export const ChoseRole: FC = () => { + const history = useHistory(); + const [userRoles, setUserRoles] = useState<string[]>(['']); + const { setIsAuth } = useContext(AuthContext); + + const choseAndForward = (role: string): void => { + // Set role in the server. + setRole(role); + setIsAuth(true); + // Push to homepage. + history.push(`${AuthRoutes.dashboard}${AuthRoutes.home}`); + }; + + useEffect(() => { + getRoles().then((fetchedRoles) => setUserRoles(fetchedRoles)); + }, []); + + return ( + <div data-testid="ChoseRole"> + {userRoles ? ( + userRoles.map((role) => ( + <Button + variant="outlined" + color="default" + key={role} + onClick={() => choseAndForward(role)} + > + {role} + </Button> + )) + ) : ( + <h1>No roles were returned.</h1> + )} + </div> + ); +}; diff --git a/src/components/AuthUser/SignInForm/InputField/InputField.md b/src/components/Auth/InputField/InputField.md similarity index 100% rename from src/components/AuthUser/SignInForm/InputField/InputField.md rename to src/components/Auth/InputField/InputField.md diff --git a/src/components/AuthUser/SignInForm/InputField/InputField.tsx b/src/components/Auth/InputField/InputField.tsx similarity index 67% rename from src/components/AuthUser/SignInForm/InputField/InputField.tsx rename to src/components/Auth/InputField/InputField.tsx index ee002a43ac6a34af89632f37fa67c966eda73b16..af97fa3b785fe1d216f1df0f6219a09ebe812e1b 100644 --- a/src/components/AuthUser/SignInForm/InputField/InputField.tsx +++ b/src/components/Auth/InputField/InputField.tsx @@ -15,11 +15,11 @@ type Props = { * Takes error from react-hook-form and is thrown if * the component is not valid. */ - error: boolean; + error?: boolean; /** * Message to display if the component is not valid. */ - errorMessage: string; + errorMessage?: string; /** * React-hook-form control. */ @@ -27,11 +27,29 @@ type Props = { /** * Validation rules. */ - rules: Partial<unknown>; + rules?: Partial<unknown>; + /** + * HTML type of input. + */ + type: string; + + /** + * MUI props override for defining style on fly. + */ + InputLabelProps?: Record<string, boolean>; }; export const InputField: FC<Props> = (props: Props) => { - const { name, label, error, errorMessage, control, rules } = props; + const { + name, + label, + error, + type, + errorMessage, + control, + rules, + InputLabelProps, + } = props; return ( <Controller name={name} @@ -41,7 +59,7 @@ export const InputField: FC<Props> = (props: Props) => { <TextField variant="outlined" margin="normal" - type={name} + type={type} required fullWidth id={name} @@ -53,8 +71,16 @@ export const InputField: FC<Props> = (props: Props) => { autoFocus error={error} helperText={error && errorMessage} + InputLabelProps={{ ...InputLabelProps, shrink: true }} /> )} /> ); }; + +InputField.defaultProps = { + rules: undefined, + error: false, + errorMessage: '', + InputLabelProps: {}, +}; diff --git a/src/components/Auth/SignInForm/CredentialsType.ts b/src/components/Auth/SignInForm/CredentialsType.ts new file mode 100644 index 0000000000000000000000000000000000000000..e93cec7875260e44be6fcda17d5c3f1528027753 --- /dev/null +++ b/src/components/Auth/SignInForm/CredentialsType.ts @@ -0,0 +1,4 @@ +export type CredentialsType = { + username: string; + password: string; +}; diff --git a/src/components/AuthUser/SignInForm/SignInForm.md b/src/components/Auth/SignInForm/SignInForm.md similarity index 100% rename from src/components/AuthUser/SignInForm/SignInForm.md rename to src/components/Auth/SignInForm/SignInForm.md diff --git a/src/components/AuthUser/SignInForm/SignInForm.test.tsx b/src/components/Auth/SignInForm/SignInForm.test.tsx similarity index 100% rename from src/components/AuthUser/SignInForm/SignInForm.test.tsx rename to src/components/Auth/SignInForm/SignInForm.test.tsx diff --git a/src/components/AuthUser/SignInForm/SignInForm.tsx b/src/components/Auth/SignInForm/SignInForm.tsx similarity index 58% rename from src/components/AuthUser/SignInForm/SignInForm.tsx rename to src/components/Auth/SignInForm/SignInForm.tsx index 0e63a94b93b648f823be50ea0af4616e3a5c7a4a..3c44dcb3d38b91c7f8f702c500ec0f64c2866d30 100644 --- a/src/components/AuthUser/SignInForm/SignInForm.tsx +++ b/src/components/Auth/SignInForm/SignInForm.tsx @@ -1,36 +1,40 @@ -import React, { FC } from 'react'; +import React, { FC, useContext } from 'react'; import axios from 'axios'; +import { useCookie } from 'hooks/useCookie'; +import { AuthRoutes, NonAuthRoutes } from 'api/routes'; import { SubmitHandler, useForm } from 'react-hook-form'; import { Button } from '@material-ui/core'; -import { InputField } from 'components/AuthUser/SignInForm/InputField/InputField'; import { useHistory } from 'react-router-dom'; -import { AuthRoutes } from 'components/api/routes'; +import { AuthContext } from 'components/Auth/AuthContext'; +import { InputField } from 'components/Auth/InputField/InputField'; import { useStyles } from './useStyles'; +import { CredentialsType } from './CredentialsType'; + +const defaultValues = { + username: '', + password: '', +}; export const SignInForm: FC = () => { const history = useHistory(); + const { setRole, setIsAuth } = useContext(AuthContext); - interface FormData { - email: string; - password: string; - } - - const defaultValues: FormData = { - email: '', - password: '', - }; + const cookie = useCookie(); - const { control, errors, setError, handleSubmit } = useForm<FormData>({ + const { control, errors, setError, handleSubmit } = useForm<CredentialsType>({ defaultValues, }); - const onSubmit: SubmitHandler<FormData> = (values: FormData) => { + const onSubmit: SubmitHandler<CredentialsType> = ( + values: CredentialsType, + ) => { axios .post( '/api/web/login', { - username: values.email, + username: values.username, password: values.password, + csrfmiddlewaretoken: cookie, }, { headers: { @@ -40,16 +44,20 @@ export const SignInForm: FC = () => { ) .then((response) => { if (response.data.status === 'fail') { - setError('email', { + setError('username', { type: 'server', - message: 'Something went wrong with email', + message: 'Something went wrong with username', }); setError('password', { type: 'server', message: 'Something went wrong with password', }); + } else if (response.data.status === 'role-choice-needed') { + history.replace(`${NonAuthRoutes.auth}${AuthRoutes.choseRole}`); } else if (response.data.status === 'success') { - history.replace(AuthRoutes.dashboard); + setRole(response.data.role); + setIsAuth(true); + history.replace(`${AuthRoutes.dashboard}${AuthRoutes.home}`); } }); }; @@ -63,23 +71,23 @@ export const SignInForm: FC = () => { data-testid="Form" > <InputField - name="email" + name="username" control={control} rules={{ - validate: (value: string) => - /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i.test(value), required: { value: true, - message: 'Email is not valid', + message: 'Username is not valid', }, }} - label="Email" - error={!!errors.email} - errorMessage="Insert email" + label="username" + type="username" + error={!!errors.username} + errorMessage="Insert username" /> <InputField name="password" + type="password" control={control} rules={{ minLength: 8, diff --git a/src/components/Auth/SignInForm/postCredentials.ts b/src/components/Auth/SignInForm/postCredentials.ts new file mode 100644 index 0000000000000000000000000000000000000000..0ab2746444b53c20045f73647237af7c4df40306 --- /dev/null +++ b/src/components/Auth/SignInForm/postCredentials.ts @@ -0,0 +1,23 @@ +import axios from 'axios'; +import { CredentialsType } from './CredentialsType'; + +export const postCredentials = async ( + values: CredentialsType, + cookie: React.Dispatch<React.SetStateAction<string>>, +): Promise<unknown> => { + const response = await axios.post( + '/api/web/login', + { + username: values.username, + password: values.password, + csrfmiddlewaretoken: cookie, + }, + { + headers: { + 'Content-Type': 'application/json', + }, + }, + ); + + return null; +}; diff --git a/src/components/AuthUser/SignInForm/useStyles.ts b/src/components/Auth/SignInForm/useStyles.ts similarity index 100% rename from src/components/AuthUser/SignInForm/useStyles.ts rename to src/components/Auth/SignInForm/useStyles.ts diff --git a/src/components/Auth/SignUpForm/SignUpForm.md b/src/components/Auth/SignUpForm/SignUpForm.md new file mode 100644 index 0000000000000000000000000000000000000000..1f982c636f5eac485bd42a5c38858a9db4b7e5e1 --- /dev/null +++ b/src/components/Auth/SignUpForm/SignUpForm.md @@ -0,0 +1,5 @@ +Form for user registration + +```js +<SignUpForm /> +``` diff --git a/src/components/Auth/SignUpForm/SignUpForm.test.tsx b/src/components/Auth/SignUpForm/SignUpForm.test.tsx new file mode 100644 index 0000000000000000000000000000000000000000..0b7bcb9d2a5138d77d94df2f354f7b956fbc8f80 --- /dev/null +++ b/src/components/Auth/SignUpForm/SignUpForm.test.tsx @@ -0,0 +1,10 @@ +import React from 'react'; +import { render } from '@testing-library/react'; +import { SignUpForm } from './SignUpForm'; + +describe('<SignUpForm />', () => { + it('should render form', () => { + const wrapper = render(<SignUpForm />); + expect(wrapper.queryByTestId('Form')).toBeTruthy(); + }); +}); diff --git a/src/components/Auth/SignUpForm/SignUpForm.tsx b/src/components/Auth/SignUpForm/SignUpForm.tsx new file mode 100644 index 0000000000000000000000000000000000000000..b947b51260790a2c723816f22f4b1f84e0e6061c --- /dev/null +++ b/src/components/Auth/SignUpForm/SignUpForm.tsx @@ -0,0 +1,220 @@ +import React, { FC } from 'react'; +import { SubmitHandler, useForm } from 'react-hook-form'; +import { Button } from '@material-ui/core'; + +import axios from 'axios'; +import { InputField } from 'components/Auth/InputField/InputField'; +import { useStyles } from 'components/Auth/SignUpForm/useStyles'; +// TODO: Data validation +// TODO: Replace notes with big input field +export const SignUpForm: FC = () => { + type FormData = { + username: string; + password: string; + firstName: string; + lastName: string; + email: string; + phoneNumber: number; + address: number; + name: string; + memberCardNumber: number; + notes: string; + }; + + const { control, errors, handleSubmit } = useForm<FormData>(); + + const onSubmit: SubmitHandler<FormData> = (values: FormData) => { + axios.post( + '/api/web/seniors/', + { + user: { + username: values.username, + password: values.password, + firstName: values.firstName, + lastName: values.lastName, + email: values.email, + }, + phone_number: values.phoneNumber, + home_address: { + address: values.address, + }, + member_card_issuer: { + name: values.memberCardNumber, + }, + member_card_number: values.memberCardNumber, + notes: values.notes, + }, + { + headers: { + 'Content-Type': 'application/json', + }, + }, + ); + }; + + const classes = useStyles(); + return ( + <> + <form + className={classes.form} + onSubmit={handleSubmit(onSubmit)} + data-testid="Form" + > + <InputField + name="email" + type="email" + control={control} + rules={{ + validate: (value: string) => + /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i.test(value), + required: { + value: true, + message: 'Email is not valid', + }, + }} + label="Email" + error={!!errors.email} + errorMessage="Insert email" + /> + + <InputField + name="password" + type="password" + control={control} + rules={{ + minLength: 8, + maxLength: 60, + required: { + value: true, + message: 'Insert valid password', + }, + }} + label="Password" + error={!!errors.password} + errorMessage="Insert valid password" + /> + + <InputField + name="username" + type="text" + control={control} + rules={{ + maxLength: 150, + required: { + value: true, + message: 'Insert valid username', + }, + }} + label="Username" + error={!!errors.username} + errorMessage="Insert valid username" + /> + + <InputField + name="firstName" + type="text" + control={control} + rules={{ + maxLength: 150, + required: { + value: true, + message: 'Insert valid first name', + }, + }} + label="First name" + error={!!errors.firstName} + errorMessage="Insert valid first name" + /> + + <InputField + name="lastName" + type="text" + control={control} + rules={{ + maxLength: 150, + required: { + value: true, + message: 'Insert valid last name', + }, + }} + label="Last name" + error={!!errors.lastName} + errorMessage="Insert valid last name" + /> + + <InputField + name="address" + type="text" + control={control} + rules={{ + required: { + value: true, + message: 'Insert valid address', + }, + }} + label="Address" + error={!!errors.address} + errorMessage="Insert valid address" + /> + + <InputField + name="phoneNumber" + type="number" + control={control} + rules={{ + maxLength: 15, + required: { + value: true, + message: 'Insert valid phone number', + }, + }} + label="Phone number" + error={!!errors.phoneNumber} + errorMessage="Insert valid phone number" + /> + + <InputField + name="memberCardNumber" + type="number" + control={control} + rules={{ + maxLength: 20, + required: { + value: true, + message: 'Insert valid card number', + }, + }} + label="Card number" + error={!!errors.memberCardNumber} + errorMessage="Insert valid card number" + /> + + <InputField + name="notes" + type="text" + control={control} + rules={{ + required: { + value: true, + message: 'Add notes', + }, + }} + label="Notes" + error={!!errors.notes} + errorMessage="Insert notes" + /> + + <Button + data-testid="Submit" + type="submit" + fullWidth + variant="contained" + color="primary" + className={classes.submit} + > + Sign Up + </Button> + </form> + </> + ); +}; diff --git a/src/components/Auth/SignUpForm/useStyles.ts b/src/components/Auth/SignUpForm/useStyles.ts new file mode 100644 index 0000000000000000000000000000000000000000..4a510bf92bf7cffee0efce3aebfd00998f44a3be --- /dev/null +++ b/src/components/Auth/SignUpForm/useStyles.ts @@ -0,0 +1,18 @@ +import { createStyles, makeStyles, Theme } from '@material-ui/core/styles'; + +export const useStyles = makeStyles((theme: Theme) => + createStyles({ + root: { + '& > *': { + margin: theme.spacing(0), + }, + }, + form: { + width: '100%', // Fix IE 11 issue. + marginTop: theme.spacing(1), + }, + submit: { + margin: theme.spacing(3, 0, 2), + }, + }), +); diff --git a/src/components/AuthUser/AuthUser.tsx b/src/components/AuthUser/AuthUser.tsx deleted file mode 100644 index 317a5f7677a7bfd7c330f588749f011fb1690bdd..0000000000000000000000000000000000000000 --- a/src/components/AuthUser/AuthUser.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import React, { FC, useEffect } from 'react'; -import axios from 'axios'; -import Container from '@material-ui/core/Container'; -import { SignInForm } from './SignInForm/SignInForm'; - -const configDjangoCookieName = (): void => { - axios.defaults.xsrfHeaderName = 'X-CSRFTOKEN'; - axios.defaults.xsrfCookieName = 'csrftoken'; - axios.defaults.withCredentials = true; -}; - -export const AuthUser: FC = () => { - configDjangoCookieName(); - useEffect(() => { - axios - .get('api/web/csrf') - .then((response) => { - axios.defaults.headers.common['X-CSRFTOKEN'] = response.data.token; - sessionStorage.setItem('X-CSRFTOKEN', response.data.token); - sessionStorage.setItem('ROLE', 'admin'); - }) - .catch((error) => error); - }, []); - return ( - <Container maxWidth="sm"> - <SignInForm /> - </Container> - ); -}; diff --git a/src/components/AuthUser/HomePage2/Reservation.tsx b/src/components/AuthUser/HomePage2/Reservation.tsx deleted file mode 100644 index 7bdcd177cbe2f0bfa38f73c13a692ebf9b8dbc3d..0000000000000000000000000000000000000000 --- a/src/components/AuthUser/HomePage2/Reservation.tsx +++ /dev/null @@ -1,60 +0,0 @@ -import React, { FC } from 'react'; -import { makeStyles } from '@material-ui/core/styles'; -import { - Paper, - Table, - TableBody, - TableCell, - TableContainer, - TableHead, - TableRow, -} from '@material-ui/core'; - -const useStyles = makeStyles(() => ({ - noShadow: { - border: 'none', - boxShadow: 'none', - backgroundColor: 'transparent', - }, -})); - -function createData( - name: string, - value: string, -): { name: string; value: string } { - return { name, value }; -} - -const rows = [ - createData('From:', 'Via Di Quel Bozen, 45, 39037'), - createData('Date:', '29 / 07 / 2021'), - createData('Time:', '12:15'), - createData('To:', 'Via del Krankenhaus, 7, 39037'), -]; - -export const Reservation: FC = () => { - const classes = useStyles(); - - return ( - <TableContainer component={Paper}> - <Table aria-label="simple table"> - <TableHead> - <TableRow> - <TableCell>DriveToHospital </TableCell> - </TableRow> - </TableHead> - - <TableBody> - {rows.map((row) => ( - <TableRow key={row.name}> - <TableCell component="th" scope="row"> - {row.name} - </TableCell> - <TableCell align="right">{row.value}</TableCell> - </TableRow> - ))} - </TableBody> - </Table> - </TableContainer> - ); -}; diff --git a/src/components/AuthUser/Homepage/Header.tsx b/src/components/AuthUser/Homepage/Header.tsx deleted file mode 100644 index 1cd6466bb132cc1cea74ee8d99ca4a82b611f08f..0000000000000000000000000000000000000000 --- a/src/components/AuthUser/Homepage/Header.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import React, { FC } from 'react'; -import { AppBar, Toolbar, Button } from '@material-ui/core'; -import { makeStyles } from '@material-ui/core/styles'; -import { Home, ImportContacts } from '@material-ui/icons'; - -const useStyles = makeStyles((theme) => ({ - button: { - margin: theme.spacing(1), - color: theme.palette.getContrastText('#000000'), - outline: theme.palette.getContrastText('#000000'), - }, -})); - -export const Header: FC = () => { - const classes = useStyles(); - - return ( - <AppBar position="static"> - <Toolbar> - <Button - variant="outlined" - className={classes.button} - startIcon={<Home />} - > - Home - </Button> - <Button - variant="outlined" - className={classes.button} - startIcon={<ImportContacts />} - > - Reservation - </Button> - </Toolbar> - </AppBar> - ); -}; diff --git a/src/components/AuthUser/Homepage/HomePage.tsx b/src/components/AuthUser/Homepage/HomePage.tsx deleted file mode 100644 index cc7caa470a2cc49035ae8f0761b7258435a13588..0000000000000000000000000000000000000000 --- a/src/components/AuthUser/Homepage/HomePage.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import React, { FC } from 'react'; -import { CssBaseline, Grid } from '@material-ui/core'; - -import { Header } from './Header'; - -export const HomePage: FC = () => ( - <Grid container direction="column"> - <Grid item> - <CssBaseline /> - <Header /> - </Grid> - <Grid item container> - <Grid item xs={false} sm={2} /> - <Grid item xs={12} sm={8}> - Content - </Grid> - <Grid item xs={false} sm={2} /> - </Grid> - </Grid> -); diff --git a/src/components/AuthUser/Profile/ProfilePage.tsx b/src/components/AuthUser/Profile/ProfilePage.tsx deleted file mode 100644 index 80195856ca7954a99b71e421fef388f42b18442b..0000000000000000000000000000000000000000 --- a/src/components/AuthUser/Profile/ProfilePage.tsx +++ /dev/null @@ -1,163 +0,0 @@ -import React, { FC } from 'react'; -import { makeStyles } from '@material-ui/core/styles'; -import { - CssBaseline, - Grid, - Typography, - Hidden, - createMuiTheme, - responsiveFontSizes, - MuiThemeProvider, - Container, -} from '@material-ui/core'; -import AddIcon from '@material-ui/icons/Add'; -import Fab from '@material-ui/core/Fab'; - -import { NavBar } from '../HomePage2/NavBar'; -import { Steps } from '../HomePage2/Steps'; -import { Numbers } from '../HomePage2/Numbers'; -import { Reservation } from '../HomePage2/Reservation'; - -let themeResp = createMuiTheme(); -themeResp = responsiveFontSizes(themeResp); - -const useStyles = makeStyles(() => ({ - root: { - minHeight: '36vh', - backgroundImage: `url(${'/assets/bg7.png'})`, - backgroundRepeat: 'no-repeat', - backgroundSize: 'cover', - }, - rightAlign: { - marginLeft: 'auto', - }, - whiteText: { - color: 'white', - }, - imageIcon: { - maxHeight: '50%', - }, - paddingBottom: { - paddingBottom: '65px', - }, - paddingTop: { - paddingTop: '65px', - }, - bodyIcon: { - fontSize: '90px', - paddingBottom: '10px', - paddingTop: '10px', - }, - contIcon: { - width: '100%', - left: '0', - right: '0', - }, - noShadow: { - border: 'none', - boxShadow: 'none', - backgroundColor: 'transparent', - }, - extendedIcon: { - fontSize: '50px', - }, - fab: { - margin: '10px 90px 80px 10px', - bottom: '0', - right: '0', - position: 'fixed', - padding: '45px', - }, -})); - -export const ReservationPage: FC = () => { - const classes = useStyles(); - - return ( - <Grid container direction="column" className={classes.paddingBottom}> - <div className={classes.root}> - <CssBaseline /> - - <Grid item> - <NavBar /> - </Grid> - - <MuiThemeProvider theme={themeResp}> - <Grid item container className={classes.paddingBottom}> - <Grid item xs={1} md={2} lg={2} /> - - <Grid item xs={10} md={6} lg={4}> - <Typography variant="h2" className={classes.whiteText}> - Plan Here Your - </Typography> - <Typography variant="h1" className={classes.whiteText}> - RESERVATIONS - </Typography> - </Grid> - - <Grid item xs={1} /> - </Grid> - </MuiThemeProvider> - </div> - - <Fab - color="primary" - size="large" - aria-label="add" - className={classes.fab} - > - <AddIcon className={classes.extendedIcon} /> - </Fab> - - <Grid item container className={classes.paddingTop}> - <Grid item xs={2} /> - <Grid item xs={8}> - <Typography variant="h3">Your Next Reservations</Typography> - </Grid> - <Grid item xs={2} /> - </Grid> - - <Grid item container style={{ paddingTop: '20px' }}> - <Grid item xs={1} lg={2} /> - <Grid item xs={10} lg={8}> - <Reservation /> - </Grid> - <Grid item xs={1} lg={2} /> - </Grid> - - <Grid item container style={{ paddingTop: '20px' }}> - <Grid item xs={1} lg={2} /> - <Grid item xs={10} lg={8}> - <Reservation /> - </Grid> - <Grid item xs={1} lg={2} /> - </Grid> - - <Grid item container style={{ paddingTop: '20px' }}> - <Grid item xs={1} lg={2} /> - <Grid item xs={10} lg={8}> - <Reservation /> - </Grid> - <Grid item xs={1} lg={2} /> - </Grid> - - <Grid item container className={classes.paddingTop}> - <Grid item xs={2} /> - <Grid item xs={8}> - <Typography variant="h3" align="right"> - Your Past Reservations - </Typography> - </Grid> - <Grid item xs={2} /> - </Grid> - - <Grid item container style={{ paddingTop: '20px' }}> - <Grid item xs={1} lg={2} /> - <Grid item xs={10} lg={8}> - <Reservation /> - </Grid> - <Grid item xs={1} lg={2} /> - </Grid> - </Grid> - ); -}; diff --git a/src/components/AuthUser/Reservation/ReservationPage.tsx b/src/components/AuthUser/Reservation/ReservationPage.tsx deleted file mode 100644 index b21e58171b52d4641ee48bcb6772d6a5dae4e4f1..0000000000000000000000000000000000000000 --- a/src/components/AuthUser/Reservation/ReservationPage.tsx +++ /dev/null @@ -1,255 +0,0 @@ -import React, { FC } from 'react'; -import { makeStyles } from '@material-ui/core/styles'; -import { - CssBaseline, - Grid, - Typography, - Hidden, - createMuiTheme, - responsiveFontSizes, - MuiThemeProvider, - Container, - Dialog, - DialogActions, - DialogContent, - DialogContentText, - DialogTitle , - TextField, - Button -} from '@material-ui/core'; -import AddIcon from '@material-ui/icons/Add'; -import Fab from '@material-ui/core/Fab'; - -import { NavBar } from '../HomePage2/NavBar'; -import { NavBarLogin } from '../HomePage2/NavBarLogin'; -import { Steps } from '../HomePage2/Steps'; -import { Numbers } from '../HomePage2/Numbers'; -import { Reservation } from '../HomePage2/Reservation'; - -let themeResp = createMuiTheme(); -themeResp = responsiveFontSizes(themeResp); - -const useStyles = makeStyles(() => ({ - root: { - minHeight: '36vh', - backgroundImage: `url(${'/assets/bg7.png'})`, - backgroundRepeat: 'no-repeat', - backgroundSize: 'cover', - }, - rightAlign: { - marginLeft: 'auto', - }, - whiteText: { - color: 'white', - }, - imageIcon: { - maxHeight: '50%', - }, - paddingBottom: { - paddingBottom: '65px', - }, - paddingTop: { - paddingTop: '65px', - }, - bodyIcon: { - fontSize: '90px', - paddingBottom: '10px', - paddingTop: '10px', - }, - contIcon: { - width: '100%', - left: '0', - right: '0', - }, - noShadow: { - border: 'none', - boxShadow: 'none', - backgroundColor: 'transparent', - }, - extendedIcon: { - fontSize: '50px', - }, - fab: { - margin: '10px 90px 80px 10px', - bottom: '0', - right: '0', - position: 'fixed', - padding: '45px', - }, - fabSmall: { - margin: '10px 45px 70px 10px', - bottom: '0', - right: '0', - position: 'fixed', - padding: '35px', - }, -})); - -export const ReservationPage: FC = () => { - const classes = useStyles(); - - const [open, setOpen] = React.useState(false); - - const handleClickOpen = (): void => { - setOpen(true); - }; - - const handleClose = (): void => { - setOpen(false); - }; - - return ( - <Grid container direction="column" className={classes.paddingBottom}> - <div className={classes.root}> - <CssBaseline /> - - <Grid item> - <NavBar /> - </Grid> - - - <Dialog open={open} onClose={handleClose} aria-labelledby="form-dialog-title"> - <DialogTitle id="form-dialog-title">Book New Reservation</DialogTitle> - <DialogContent> - <DialogContentText> - Write here below the details of your next reservation - </DialogContentText> - <TextField - autoFocus - margin="dense" - id="name" - label="Name Reservation" - type="text" - fullWidth - /> - <TextField - autoFocus - margin="dense" - id="name" - label="Date" - type="date" - fullWidth - InputLabelProps={{ shrink: true }} - /> - <TextField - autoFocus - margin="dense" - id="name" - label="Department Time" - type="time" - fullWidth - InputLabelProps={{ shrink: true }} - /> - <TextField - autoFocus - margin="dense" - id="name" - label="Destination" - type="text" - fullWidth - /> - </DialogContent> - <DialogActions> - <Button onClick={handleClose} color="primary"> - Cancel - </Button> - <Button onClick={handleClose} color="primary" variant="contained"> - Subscribe - </Button> - </DialogActions> - </Dialog> - - - <MuiThemeProvider theme={themeResp}> - <Grid item container className={classes.paddingBottom}> - <Grid item xs={1} md={2} lg={2} /> - - <Grid item xs={10} md={6} lg={4}> - <Typography variant="h2" className={classes.whiteText}> - Plan Here Your - </Typography> - <Typography variant="h1" className={classes.whiteText}> - RESERVATIONS - </Typography> - </Grid> - - <Grid item xs={1} /> - </Grid> - </MuiThemeProvider> - </div> - - <Hidden mdDown> - <Fab - color="primary" - size="large" - aria-label="add" - className={classes.fab} - onClick={handleClickOpen} - > - <AddIcon className={classes.extendedIcon} /> - </Fab> - </Hidden> - - <Hidden lgUp> - <Fab - color="primary" - size="small" - aria-label="add" - className={classes.fabSmall} - > - <AddIcon /> - </Fab> - </Hidden> - - <Grid item container className={classes.paddingTop}> - <Grid item xs={2} /> - <Grid item xs={8}> - <Typography variant="h3">Your Next Reservations</Typography> - </Grid> - <Grid item xs={2} /> - </Grid> - - <Grid item container style={{ paddingTop: '20px' }}> - <Grid item xs={1} lg={2} /> - <Grid item xs={10} lg={8}> - <Reservation /> - </Grid> - <Grid item xs={1} lg={2} /> - </Grid> - - <Grid item container style={{ paddingTop: '20px' }}> - <Grid item xs={1} lg={2} /> - <Grid item xs={10} lg={8}> - <Reservation /> - </Grid> - <Grid item xs={1} lg={2} /> - </Grid> - - <Grid item container style={{ paddingTop: '20px' }}> - <Grid item xs={1} lg={2} /> - <Grid item xs={10} lg={8}> - <Reservation /> - </Grid> - <Grid item xs={1} lg={2} /> - </Grid> - - <Grid item container className={classes.paddingTop}> - <Grid item xs={2} /> - <Grid item xs={8}> - <Typography variant="h3" align="right"> - Your Past Reservations - </Typography> - </Grid> - <Grid item xs={2} /> - </Grid> - - <Grid item container style={{ paddingTop: '20px' }}> - <Grid item xs={1} lg={2} /> - <Grid item xs={10} lg={8}> - <Reservation /> - </Grid> - <Grid item xs={1} lg={2} /> - </Grid> - </Grid> - ); -}; diff --git a/src/components/Authorization/Authorization.tsx b/src/components/Authorization/Authorization.tsx new file mode 100644 index 0000000000000000000000000000000000000000..08db3cea5572ddc10184b44cece73f59e49e5ce7 --- /dev/null +++ b/src/components/Authorization/Authorization.tsx @@ -0,0 +1,53 @@ +import React, { ComponentType, FC, useContext } from 'react'; +import { BlurCircular } from '@material-ui/icons'; +import { NonAuthRoutes } from 'api/routes'; +import { Redirect } from 'react-router-dom'; +import { Unauthorized } from 'components/Unauthorized/Unauthorized'; +import { AuthContext } from 'components/Auth/AuthContext'; + +const HandleIsAuth: FC<{ isAuth: boolean }> = ({ isAuth }) => + isAuth ? ( + <Unauthorized /> + ) : ( + <Redirect + to={{ pathname: `${NonAuthRoutes.auth}${NonAuthRoutes.signIn}` }} + /> + ); + +interface WithAuthProps { + allowedRoles: string[]; +} + +/* eslint-disable react/jsx-props-no-spreading */ +/** + * + * @param WrappedComponent component to be wrapped by the authentication control. + * This creates a "personal area" in the working implementation. + * @returns {FC<T>} wrapped component. + */ + +export const withAuthorization = (allowedRoles: string[]) => < + T extends WithAuthProps = WithAuthProps +>( + WrappedComponent: React.ComponentType<T>, +): FC<T> => { + // Creating the inner component. The calculated Props type here is the where the magic happens. + const ComponentWithAuthorization: FC<T> = ( + props: Omit<T, keyof WithAuthProps>, + ) => { + const { role, isAuth } = useContext(AuthContext); + console.log(`ROLE ${role} AUTH ${isAuth}`); + + /* eslint-disable no-nested-ternary */ + return typeof isAuth === null && role === null ? ( + <BlurCircular /> + ) : // props comes afterwards so the can override the default ones. + allowedRoles.includes(role) && isAuth ? ( + <WrappedComponent {...(props as T)} /> + ) : ( + <HandleIsAuth isAuth={!!isAuth} /> + ); + }; + + return ComponentWithAuthorization; +}; diff --git a/src/components/Dashboard/Dashboard.tsx b/src/components/Dashboard/Dashboard.tsx new file mode 100644 index 0000000000000000000000000000000000000000..4453cc5ef571c2c7d356fce1bf1579767f11c72a --- /dev/null +++ b/src/components/Dashboard/Dashboard.tsx @@ -0,0 +1,21 @@ +import React, { FC } from 'react'; +import Container from '@material-ui/core/Container'; +import { Route, useRouteMatch } from 'react-router-dom'; +import { AuthRoutes } from 'api/routes'; +import { HomePage } from './HomePage/HomePage'; +import { ProfilePage } from './ProfilePage/ProfilePage'; +import { ReservationPage } from './ReservationPage/ReservationPage'; + +export const Dashboard: FC = () => { + const { path } = useRouteMatch(); + return ( + <> + <Route exact path={`${path}${AuthRoutes.home}`} component={HomePage} /> + <Route path={`${path}${AuthRoutes.profile}`} component={ProfilePage} /> + <Route + path={`${path}${AuthRoutes.reservation}`} + component={ReservationPage} + /> + </> + ); +}; diff --git a/src/components/AuthUser/HomePage2/HomePage2.tsx b/src/components/Dashboard/HomePage/HomePage.tsx similarity index 86% rename from src/components/AuthUser/HomePage2/HomePage2.tsx rename to src/components/Dashboard/HomePage/HomePage.tsx index 96e84546494a8ce4e99695578a67836058e4eeb7..94f1eb7c9ed64465e724d768f8aa600096bc90f4 100644 --- a/src/components/AuthUser/HomePage2/HomePage2.tsx +++ b/src/components/Dashboard/HomePage/HomePage.tsx @@ -10,13 +10,11 @@ import { MuiThemeProvider, Container, } from '@material-ui/core'; - -import { MapElement } from './MapElement'; -import { NavBar } from './NavBar'; -import { NavBarLogin } from './NavBarLogin'; -import { Steps } from './Steps'; -import { Reservation } from './Reservation'; -import { Numbers } from './Numbers'; +import { MapElement } from 'components/Dashboard/HomePage/MapElement'; +import { NavBar } from 'components/Dashboard/HomePage/NavBar'; +import { Numbers } from 'components/Dashboard/HomePage/Numbers'; +import { Steps } from 'components/Dashboard/HomePage/Steps'; +import { Reservation } from '../ReservationPage/Reservation/Reservation'; let themeResp = createMuiTheme(); themeResp = responsiveFontSizes(themeResp); @@ -60,7 +58,7 @@ const useStyles = makeStyles(() => ({ }, })); -export const HomePage2: FC = () => { +export const HomePage: FC = () => { const classes = useStyles(); return ( @@ -111,7 +109,7 @@ export const HomePage2: FC = () => { <Grid item container style={{ paddingTop: '20px' }}> <Grid item xs={1} lg={2} /> <Grid item xs={10} lg={8}> - <Reservation /> + <Reservation departure="" destination="" time="" date="" /> </Grid> <Grid item xs={1} lg={2} /> </Grid> diff --git a/src/components/AuthUser/HomePage2/MapElement.tsx b/src/components/Dashboard/HomePage/MapElement.tsx similarity index 100% rename from src/components/AuthUser/HomePage2/MapElement.tsx rename to src/components/Dashboard/HomePage/MapElement.tsx diff --git a/src/components/AuthUser/HomePage2/NavBar.tsx b/src/components/Dashboard/HomePage/NavBar.tsx similarity index 76% rename from src/components/AuthUser/HomePage2/NavBar.tsx rename to src/components/Dashboard/HomePage/NavBar.tsx index 1bc3cce61bc2b5bf11a849a6d0943de84fe6664e..e05702fe0be796606d802702a5b88f2d9276c5b8 100644 --- a/src/components/AuthUser/HomePage2/NavBar.tsx +++ b/src/components/Dashboard/HomePage/NavBar.tsx @@ -1,9 +1,11 @@ import React, { FC } from 'react'; import { makeStyles } from '@material-ui/core/styles'; -import { Tabs, Tab, AppBar } from '@material-ui/core'; +import { Tabs, Tab } from '@material-ui/core'; import HomeIcon from '@material-ui/icons/Home'; import ImportContactsIcon from '@material-ui/icons/ImportContacts'; import AccountCircleIcon from '@material-ui/icons/AccountCircle'; +import { AuthRoutes } from 'api/routes'; +import { useHistory } from 'react-router-dom'; const useStyles = makeStyles(() => ({ tabs: { @@ -29,15 +31,17 @@ const useStyles = makeStyles(() => ({ })); export const NavBar: FC = () => { + const history = useHistory(); const classes = useStyles(); - const [value, setValue] = React.useState(0); + const [value, setValue] = React.useState<string>(''); const handleChange = ( event: React.ChangeEvent<unknown>, - newValue: number, + newValue: string, ): void => { setValue(newValue); + history.replace(newValue); }; return ( @@ -51,16 +55,19 @@ export const NavBar: FC = () => { > <Tab label="HomePage" + value={`${AuthRoutes.dashboard}${AuthRoutes.home}`} icon={<HomeIcon className={classes.tabIcon} />} className={classes.tabPan} /> <Tab label="Reservation" + value={`${AuthRoutes.dashboard}${AuthRoutes.reservation}`} icon={<ImportContactsIcon className={classes.tabIcon} />} className={`${classes.tabPan} ${classes.rightAlign}`} /> <Tab label="Profile" + value={`${AuthRoutes.dashboard}${AuthRoutes.profile}`} className={classes.tabPan} icon={<AccountCircleIcon className={classes.tabIcon} />} /> diff --git a/src/components/AuthUser/HomePage2/NavBarLogin.tsx b/src/components/Dashboard/HomePage/NavBarLogin.tsx similarity index 78% rename from src/components/AuthUser/HomePage2/NavBarLogin.tsx rename to src/components/Dashboard/HomePage/NavBarLogin.tsx index 92d00454f5ddc811637f6353b520f89db0cbcebe..e37413e0ddd4d1cd44a60f9eb517e09c0560ee5e 100644 --- a/src/components/AuthUser/HomePage2/NavBarLogin.tsx +++ b/src/components/Dashboard/HomePage/NavBarLogin.tsx @@ -3,6 +3,9 @@ import { makeStyles } from '@material-ui/core/styles'; import { Tabs, Tab } from '@material-ui/core'; import LockIcon from '@material-ui/icons/Lock'; +import { useHistory } from 'react-router-dom'; +import { AuthRoutes } from 'api/routes'; + const useStyles = makeStyles(() => ({ tabs: { color: 'white', @@ -26,15 +29,19 @@ const useStyles = makeStyles(() => ({ })); export const NavBarLogin: FC = () => { + const history = useHistory(); + const classes = useStyles(); - const [value, setValue] = React.useState(0); + const [value, setValue] = React.useState(''); const handleChange = ( event: React.ChangeEvent<unknown>, - newValue: number, + newValue: string, ): void => { setValue(newValue); + history.replace(newValue); + console.log(newValue); }; return ( @@ -45,8 +52,8 @@ export const NavBarLogin: FC = () => { className={classes.tabs} TabIndicatorProps={{ style: { display: 'none' } }} > - <Tab label="" className={classes.tabPan} /> <Tab + value={`${AuthRoutes.dashboard}${AuthRoutes.home}`} label="Login" icon={<LockIcon className={classes.tabIcon} />} className={`${classes.tabPan} ${classes.rightAlign}`} diff --git a/src/components/AuthUser/HomePage2/Numbers.tsx b/src/components/Dashboard/HomePage/Numbers.tsx similarity index 100% rename from src/components/AuthUser/HomePage2/Numbers.tsx rename to src/components/Dashboard/HomePage/Numbers.tsx diff --git a/src/components/AuthUser/HomePage2/Steps.tsx b/src/components/Dashboard/HomePage/Steps.tsx similarity index 100% rename from src/components/AuthUser/HomePage2/Steps.tsx rename to src/components/Dashboard/HomePage/Steps.tsx diff --git a/src/components/ProfilePage/ProfilePage.md b/src/components/Dashboard/ProfilePage/ProfilePage.md similarity index 100% rename from src/components/ProfilePage/ProfilePage.md rename to src/components/Dashboard/ProfilePage/ProfilePage.md diff --git a/src/components/ProfilePage/ProfilePage.test.tsx b/src/components/Dashboard/ProfilePage/ProfilePage.test.tsx similarity index 100% rename from src/components/ProfilePage/ProfilePage.test.tsx rename to src/components/Dashboard/ProfilePage/ProfilePage.test.tsx diff --git a/src/components/ProfilePage/ProfilePage.tsx b/src/components/Dashboard/ProfilePage/ProfilePage.tsx similarity index 54% rename from src/components/ProfilePage/ProfilePage.tsx rename to src/components/Dashboard/ProfilePage/ProfilePage.tsx index 2fe6808acc4b7876ad3bc9730c53f78b1d11547d..2856a225f1f4fe806dece906f90bbd143a6f30a3 100644 --- a/src/components/ProfilePage/ProfilePage.tsx +++ b/src/components/Dashboard/ProfilePage/ProfilePage.tsx @@ -1,16 +1,18 @@ -import React, { FC } from 'react'; +import React, { FC, useContext } from 'react'; import Button from '@material-ui/core/Button'; import axios from 'axios'; -import { NonAuthRoutes } from 'components/api/routes'; +import { NonAuthRoutes } from 'api/routes'; import { useHistory } from 'react-router-dom'; +import { AuthContext } from 'components/Auth/AuthContext'; export const ProfilePage: FC = () => { const history = useHistory(); - + const { setIsAuth } = useContext(AuthContext); const logout = (): void => { - axios - .post('/api/web/login/logout') - .then(() => history.replace(NonAuthRoutes.home)); + axios.get('/api/web/login/logout').then(() => { + setIsAuth(false); + history.replace(NonAuthRoutes.home); + }); }; return ( diff --git a/src/components/Dashboard/ReservationPage/Reservation/Reservation.tsx b/src/components/Dashboard/ReservationPage/Reservation/Reservation.tsx new file mode 100644 index 0000000000000000000000000000000000000000..b9cd2bedb8074d158fd74dc4c3653a22333e9ec2 --- /dev/null +++ b/src/components/Dashboard/ReservationPage/Reservation/Reservation.tsx @@ -0,0 +1,57 @@ +import React, { FC } from 'react'; +import { makeStyles } from '@material-ui/core/styles'; +import { + Grid, + Paper, + Table, + TableBody, + TableCell, + TableContainer, + TableHead, + TableRow, +} from '@material-ui/core'; +import { ReservationProps } from './ReservationProps'; + +const useStyles = makeStyles(() => ({ + noShadow: { + border: 'none', + boxShadow: 'none', + backgroundColor: 'transparent', + }, +})); + +export const Reservation: FC<ReservationProps> = ({ + departure, + destination, + time, + date, +}: ReservationProps) => { + const classes = useStyles(); + + return ( + <Grid item container style={{ paddingTop: '20px' }}> + <Grid item xs={1} lg={2} /> + <Grid item xs={10} lg={8}> + <TableContainer component={Paper}> + <Table aria-label="simple table"> + <TableHead> + <TableRow> + <TableCell>DriveToHospital </TableCell> + </TableRow> + </TableHead> + + <TableBody> + <TableRow key="departure"> + <TableCell component="th" scope="row"> + Departure + </TableCell> + <TableCell align="right">{departure}</TableCell> + </TableRow> + </TableBody> + </Table> + </TableContainer> + </Grid> + <Grid item xs={1} lg={2} /> + </Grid> + ); +}; diff --git a/src/components/Dashboard/ReservationPage/Reservation/ReservationProps.ts b/src/components/Dashboard/ReservationPage/Reservation/ReservationProps.ts new file mode 100644 index 0000000000000000000000000000000000000000..512c6c9e38e13a4cefe0c6a4f64f0e6a149e6be6 --- /dev/null +++ b/src/components/Dashboard/ReservationPage/Reservation/ReservationProps.ts @@ -0,0 +1,6 @@ +export type ReservationProps = { + departure: string; + destination: string; + time: string; + date: string; +}; diff --git a/src/components/Dashboard/ReservationPage/ReservationDialog.tsx b/src/components/Dashboard/ReservationPage/ReservationDialog.tsx new file mode 100644 index 0000000000000000000000000000000000000000..499f130e01f6e5ecb2c9cd2bdc2da37d665113d3 --- /dev/null +++ b/src/components/Dashboard/ReservationPage/ReservationDialog.tsx @@ -0,0 +1,108 @@ +import React, { FC } from 'react'; +import { + Grid, + Typography, + Dialog, + DialogActions, + DialogContent, + DialogContentText, + DialogTitle, + Button, +} from '@material-ui/core'; +import AddIcon from '@material-ui/icons/Add'; +import Fab from '@material-ui/core/Fab'; +import { NavBar } from 'components/Dashboard/HomePage/NavBar'; +import { Reservation } from 'components/Dashboard/ReservationPage/Reservation/Reservation'; +import { useReservations } from 'hooks/useReservations'; +import { ReservationProps } from 'components/Dashboard/ReservationPage/Reservation/ReservationProps'; +import { SubmitHandler, useForm } from 'react-hook-form'; +import { InputField } from 'components/Auth/InputField/InputField'; +import { useStyles } from './ReservationPage.style'; + +type ReservationDialogProps = { + handleClose: () => void; + isOpen: boolean; +}; +export const ReservationDialog: FC<ReservationDialogProps> = ({ + handleClose, + isOpen, +}: ReservationDialogProps) => { + const classes = useStyles(); + const { control, handleSubmit } = useForm<ReservationProps>(); + + const reservation = useReservations(); + + const onSubmit: SubmitHandler<ReservationProps> = ( + data: ReservationProps, + ) => { + console.log(data); + handleClose(); + }; + + return ( + <div data-testid="ReservationDialog"> + <Grid container direction="column" className={classes.paddingBottom}> + <div className={classes.root}> + <Grid item> + <NavBar /> + </Grid> + + <Dialog + open={isOpen} + onClose={handleClose} + aria-labelledby="form-dialog-title" + > + <form onSubmit={handleSubmit(onSubmit)} data-testid="Form"> + <DialogTitle id="form-dialog-title"> + Book New Reservation + </DialogTitle> + <DialogContent> + <DialogContentText> + Write here below the details of your next reservation + </DialogContentText> + + <InputField + name="name" + label="Name Reservation" + type="text" + control={control} + /> + <InputField + name="date" + label="Reservation date" + type="date" + control={control} + /> + <InputField + name="time" + label="Department Time" + type="time" + control={control} + /> + <InputField + name="destination" + label="Destination" + type="text" + control={control} + /> + </DialogContent> + <DialogActions> + <Button onClick={handleClose} color="primary"> + Cancel + </Button> + <Button + type="submit" + fullWidth + variant="contained" + color="primary" + > + Insert + </Button> + </DialogActions> + </form> + </Dialog> + </div> + </Grid> + </div> + ); +}; diff --git a/src/components/Dashboard/ReservationPage/ReservationPage.style.ts b/src/components/Dashboard/ReservationPage/ReservationPage.style.ts new file mode 100644 index 0000000000000000000000000000000000000000..c3d2fca6c309c408222292a499d1d07903da935b --- /dev/null +++ b/src/components/Dashboard/ReservationPage/ReservationPage.style.ts @@ -0,0 +1,51 @@ +import { makeStyles } from '@material-ui/core/styles'; + +export const useStyles = makeStyles(() => ({ + root: { + minHeight: '36vh', + backgroundImage: `url(${'/assets/bg7.png'})`, + backgroundRepeat: 'no-repeat', + backgroundSize: 'cover', + }, + rightAlign: { + marginLeft: 'auto', + }, + whiteText: { + color: 'white', + }, + imageIcon: { + maxHeight: '50%', + }, + paddingBottom: { + paddingBottom: '65px', + }, + paddingTop: { + paddingTop: '65px', + }, + bodyIcon: { + fontSize: '90px', + paddingBottom: '10px', + paddingTop: '10px', + }, + contIcon: { + width: '100%', + left: '0', + right: '0', + }, + noShadow: { + border: 'none', + boxShadow: 'none', + backgroundColor: 'transparent', + }, + extendedIcon: { + fontSize: '50px', + }, + fab: { + margin: '1.5em', + bottom: '0', + right: '0', + position: 'fixed', + fontSize: '2em', + padding: '1.5em', + }, +})); diff --git a/src/components/Reservation/Reservation.test.tsx b/src/components/Dashboard/ReservationPage/ReservationPage.test.tsx similarity index 57% rename from src/components/Reservation/Reservation.test.tsx rename to src/components/Dashboard/ReservationPage/ReservationPage.test.tsx index 8a07eec9b8a0a43d865356e09c820ad8ff96f605..628b7b5f432b5dc81f327f0d7a03a2fc5f775bfe 100644 --- a/src/components/Reservation/Reservation.test.tsx +++ b/src/components/Dashboard/ReservationPage/ReservationPage.test.tsx @@ -1,10 +1,10 @@ import React from 'react'; import { render } from '@testing-library/react'; -import { Reservation } from './Reservation'; +import { ReservationPage } from './ReservationPage'; -describe('<Reservation />', () => { +describe('<ReservationPage />', () => { it('renders without crashing', () => { - const wrapper = render(<Reservation />); + const wrapper = render(<ReservationPage />); expect(wrapper.queryByTestId('Reservation')).toBeTruthy(); }); }); diff --git a/src/components/Dashboard/ReservationPage/ReservationPage.tsx b/src/components/Dashboard/ReservationPage/ReservationPage.tsx new file mode 100644 index 0000000000000000000000000000000000000000..c4e5ee210c76474ce3a451ed6a724fc3bae68457 --- /dev/null +++ b/src/components/Dashboard/ReservationPage/ReservationPage.tsx @@ -0,0 +1,104 @@ +import React, { FC } from 'react'; +import { + Grid, + Typography, + Dialog, + DialogActions, + DialogContent, + DialogContentText, + DialogTitle, + Button, +} from '@material-ui/core'; +import AddIcon from '@material-ui/icons/Add'; +import Fab from '@material-ui/core/Fab'; +import { NavBar } from 'components/Dashboard/HomePage/NavBar'; +import { Reservation } from 'components/Dashboard/ReservationPage/Reservation/Reservation'; +import { useReservations } from 'hooks/useReservations'; +import { ReservationProps } from 'components/Dashboard/ReservationPage/Reservation/ReservationProps'; +import { SubmitHandler, useForm } from 'react-hook-form'; +import { InputField } from 'components/Auth/InputField/InputField'; +import { useStyles } from './ReservationPage.style'; +import { ReservationDialog } from './ReservationDialog'; + +export const ReservationPage: FC = () => { + const classes = useStyles(); + const { control, handleSubmit } = useForm<ReservationProps>(); + + const [isOpen, setIsOpen] = React.useState<boolean>(false); + + const reservation = useReservations(); + + const handleOpen = (): void => { + setIsOpen(true); + }; + + const handleClose = (): void => { + setIsOpen(false); + }; + + const onSubmit: SubmitHandler<ReservationProps> = ( + data: ReservationProps, + ) => { + console.log(data); + }; + + return ( + <div data-testid="Reservation"> + <Grid container direction="column" className={classes.paddingBottom}> + <div className={classes.root}> + <Grid item> + <NavBar /> + </Grid> + <ReservationDialog handleClose={handleClose} isOpen={isOpen} /> + + <Grid item container className={classes.paddingBottom}> + <Grid item xs={1} md={2} lg={2} /> + + <Grid item xs={10} md={6} lg={4}> + <Typography variant="h2" className={classes.whiteText}> + Plan Here Your + </Typography> + <Typography variant="h1" className={classes.whiteText}> + RESERVATIONS + </Typography> + </Grid> + + <Grid item xs={1} /> + </Grid> + </div> + + <Fab + color="primary" + size="large" + aria-label="add" + className={classes.fab} + onClick={handleOpen} + > + <AddIcon className={classes.extendedIcon} /> + </Fab> + + <Grid item container className={classes.paddingTop}> + <Grid item xs={2} /> + <Grid item xs={8}> + <Typography variant="h3">Your Next Reservations</Typography> + </Grid> + <Grid item xs={2} /> + </Grid> + + <Reservation departure="" destination="" time="" date="" /> + + <Grid item container className={classes.paddingTop}> + <Grid item xs={2} /> + <Grid item xs={8}> + <Typography variant="h3" align="right"> + Your Past Reservations + </Typography> + </Grid> + <Grid item xs={2} /> + </Grid> + + <Reservation departure="" destination="" time="" date="" /> + </Grid> + </div> + ); +}; diff --git a/src/components/HomePage/HomePage.tsx b/src/components/HomePage/HomePage.tsx deleted file mode 100644 index 99b3448459007f960afbfdacbfefac747d0e5d9a..0000000000000000000000000000000000000000 --- a/src/components/HomePage/HomePage.tsx +++ /dev/null @@ -1,104 +0,0 @@ -import React, { FC } from 'react'; -import { createStyles, Theme, makeStyles } from '@material-ui/core/styles'; -import Drawer from '@material-ui/core/Drawer'; -import AppBar from '@material-ui/core/AppBar'; -import CssBaseline from '@material-ui/core/CssBaseline'; -import Toolbar from '@material-ui/core/Toolbar'; -import List from '@material-ui/core/List'; -import Typography from '@material-ui/core/Typography'; -import ListItem from '@material-ui/core/ListItem'; -import ListItemText from '@material-ui/core/ListItemText'; - -const drawerWidth = 240; - -const useStyles = makeStyles((theme: Theme) => - createStyles({ - root: { - display: 'flex', - }, - appBar: { - zIndex: theme.zIndex.drawer + 1, - }, - drawer: { - width: drawerWidth, - flexShrink: 0, - }, - drawerPaper: { - width: drawerWidth, - }, - drawerContainer: { - overflow: 'auto', - }, - content: { - flexGrow: 1, - padding: theme.spacing(3), - }, - }), -); - -export const HomePage: FC = () => { - const classes = useStyles(); - - return ( - <div className={classes.root}> - <CssBaseline /> - <AppBar position="fixed" className={classes.appBar}> - <Toolbar> - <Typography variant="h6" noWrap> - Moveaid - </Typography> - </Toolbar> - </AppBar> - <Drawer - className={classes.drawer} - variant="permanent" - classes={{ - paper: classes.drawerPaper, - }} - > - <Toolbar /> - <div className={classes.drawerContainer}> - <List> - {['Reservation'].map((text) => ( - <ListItem button key={text}> - <ListItemText primary={text} /> - </ListItem> - ))} - </List> - </div> - </Drawer> - <main className={classes.content}> - <Toolbar /> - <Typography paragraph> - Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do - eiusmod tempor incididunt ut labore et dolore magna aliqua. Rhoncus - dolor purus non enim praesent elementum facilisis leo vel. Risus at - ultrices mi tempus imperdiet. Semper risus in hendrerit gravida rutrum - quisque non tellus. Convallis convallis tellus id interdum velit - laoreet id donec ultrices. Odio morbi quis commodo odio aenean sed - adipiscing. Amet nisl suscipit adipiscing bibendum est ultricies - integer quis. Cursus euismod quis viverra nibh cras. Metus vulputate - eu scelerisque felis imperdiet proin fermentum leo. Mauris commodo - quis imperdiet massa tincidunt. Cras tincidunt lobortis feugiat - vivamus at augue. At augue eget arcu dictum varius duis at consectetur - lorem. Velit sed ullamcorper morbi tincidunt. Lorem donec massa sapien - faucibus et molestie ac. - </Typography> - <Typography paragraph> - Consequat mauris nunc congue nisi vitae suscipit. Fringilla est - ullamcorper eget nulla facilisi etiam dignissim diam. Pulvinar - elementum integer enim neque volutpat ac tincidunt. Ornare suspendisse - sed nisi lacus sed viverra tellus. Purus sit amet volutpat consequat - mauris. Elementum eu facilisis sed odio morbi. Euismod lacinia at quis - risus sed vulputate odio. Morbi tincidunt ornare massa eget egestas - purus viverra accumsan in. In hendrerit gravida rutrum quisque non - tellus orci ac. Pellentesque nec nam aliquam sem et tortor. Habitant - morbi tristique senectus et. Adipiscing elit duis tristique - sollicitudin nibh sit. Ornare aenean euismod elementum nisi quis - eleifend. Commodo viverra maecenas accumsan lacus vel facilisis. Nulla - posuere sollicitudin aliquam ultrices sagittis orci a. - </Typography> - </main> - </div> - ); -}; diff --git a/src/components/LandingPage/LandingPage.tsx b/src/components/LandingPage/LandingPage.tsx index fae4bba76e98ced845155da37e41f40e78cbca66..1a07d866636cbadb0848e971b2cf94d64b090241 100644 --- a/src/components/LandingPage/LandingPage.tsx +++ b/src/components/LandingPage/LandingPage.tsx @@ -1,47 +1,113 @@ import React, { FC } from 'react'; -import Button from '@material-ui/core/Button'; -import { Link } from 'react-router-dom'; -import { NonAuthRoutes } from 'components/api/routes'; - -export const LandingPage: FC = () => ( - <> - <Button variant="contained"> - <Link to={NonAuthRoutes.signIn}>Login</Link> - </Button> - <section> - <h2>What is MoveAid?</h2> - - <p> - MoveAid is a project born in order to satisfy a specific problem. There - are many senior citizens who are not able to drive and move from a place - to another easily, because of this they sometimes need to take private - Taxis or public means of transport which could be expensive and not - simple. MoveAid wants to be the solution to this problem offering free - drives (carried out by voulunteers) for those who need them to reach - medical visit, to go to the supermarket or to satisfy any other need. - </p> - </section> - - <section> - <h2>How will MoveAid work?</h2> - - <p> - MoveAid will be the platform that will allow people to ask for a drive - (either by phone or online) and organizations of voulenteers to organise - and accept drives. - </p> - </section> - - <p> - <br /> - </p> - - <footer> - <p> - Authors: Alberto Defendi, Andrea Esposito, Marco Marinello, Francesco - Mazzini - </p> - <p>© 2021 - All right reserved</p> - </footer> - </> -); +import { makeStyles } from '@material-ui/core/styles'; +import { + CssBaseline, + Grid, + Typography, + Hidden, + createMuiTheme, + responsiveFontSizes, + MuiThemeProvider, + Container, +} from '@material-ui/core'; +import { MapElement } from 'components/Dashboard/HomePage/MapElement'; +import { NavBarLogin } from 'components/Dashboard/HomePage/NavBarLogin'; +import { Numbers } from 'components/Dashboard/HomePage/Numbers'; +import { Steps } from 'components/Dashboard/HomePage/Steps'; + +let themeResp = createMuiTheme(); +themeResp = responsiveFontSizes(themeResp); + +const useStyles = makeStyles(() => ({ + root: { + minHeight: '100vh', + backgroundImage: `url(${'/assets/bg3.png'})`, + backgroundRepeat: 'no-repeat', + backgroundSize: 'cover', + }, + rightAlign: { + marginLeft: 'auto', + }, + whiteText: { + color: 'white', + }, + imageIcon: { + maxHeight: '50%', + }, + paddingBottom: { + paddingBottom: '65px', + }, + paddingTop: { + paddingTop: '65px', + }, + bodyIcon: { + fontSize: '90px', + paddingBottom: '10px', + paddingTop: '10px', + }, + contIcon: { + width: '100%', + left: '0', + right: '0', + }, + noShadow: { + border: 'none', + boxShadow: 'none', + backgroundColor: 'transparent', + }, +})); + +export const LandingPage: FC = () => { + const classes = useStyles(); + + return ( + <Grid container direction="column" className={classes.paddingBottom}> + <div className={classes.root}> + <CssBaseline /> + + <Grid item> + <NavBarLogin /> + </Grid> + + <MuiThemeProvider theme={themeResp}> + <Grid item container className={classes.paddingBottom}> + <Grid item xs={1} md={2} lg={2} /> + + <Grid item xs={10} md={6} lg={4}> + <Typography variant="h2" className={classes.whiteText}> + Book Free Rides Now With + </Typography> + <Typography variant="h1" className={classes.whiteText}> + MOVE AID + </Typography> + </Grid> + + <Grid item xs={false} md={3} lg={5}> + <Hidden mdDown> + <Container> + <MapElement /> + </Container> + </Hidden> + </Grid> + + <Grid item xs={1} /> + </Grid> + + <Steps /> + </MuiThemeProvider> + </div> + + <Grid item container className={classes.paddingTop}> + <Grid item xs={2} /> + <Grid item xs={8}> + <Typography variant="h3" align="right"> + Some Our Numbers + </Typography> + </Grid> + <Grid item xs={2} /> + </Grid> + + <Numbers /> + </Grid> + ); +}; diff --git a/src/components/LandingPage/TeamPage.tsx b/src/components/LandingPage/TeamPage.tsx deleted file mode 100644 index b0a945e8347fdc1b4c1027ea1cf3a03df91eb95d..0000000000000000000000000000000000000000 --- a/src/components/LandingPage/TeamPage.tsx +++ /dev/null @@ -1,86 +0,0 @@ -import React, { FC } from 'react'; - -export const TeamPage: FC = () => ( - <> - <section> - <h2>These are the students of the MoveAid project</h2> - - <div> - <h3>Alberto Defendi</h3> - <table> - <tr> - <td> </td> - <td> - <p> - Age: 19 <br /> - Role: Front End Developer - <br /> - Hobbys: Sports, maths - <br /> - </p> - </td> - </tr> - </table> - </div> - - <div> - <h3>Andrea Esposito</h3> - <table> - <tr> - <td> </td> - <td> - <p> - Age: 19 <br /> - Role: xxxxx - <br /> - Hobbys: xxxxxxxxx - <br /> - </p> - </td> - </tr> - </table> - </div> - - <div> - <h3>Marco Marinello</h3> - <table> - <tr> - <td> </td> - <td> - <p> - Age: 19 <br /> - Role: xxxxx - <br /> - Hobbys: xxxxxxxxx - <br /> - </p> - </td> - </tr> - </table> - </div> - - <div> - <h3>Francesco Mazzini</h3> - <table> - <tr> - <td> </td> - <td> - <p> - <strong>Age:</strong> 19 <br /> - <br /> - <strong>Role:</strong> Front End Developer - <br /> - <br /> - <strong>Hobbys:</strong> I like Graphic and Programing. - <br /> - I also like playing videogames, hanging out <br /> - with friends, watching tv series and <br /> - training in gym! - </p> - </td> - </tr> - </table> - </div> - </section> - </> -); diff --git a/src/components/LandingPage/assets/alberto.png b/src/components/LandingPage/assets/alberto.png deleted file mode 100644 index a4030b2bda405381ab60d5ba68b7ea7c057ebefe..0000000000000000000000000000000000000000 Binary files a/src/components/LandingPage/assets/alberto.png and /dev/null differ diff --git a/src/components/LandingPage/assets/logo04Circle.png b/src/components/LandingPage/assets/logo04Circle.png deleted file mode 100644 index 4e5a76b9f51d9580a7f6b6b78f076f0a4a821f48..0000000000000000000000000000000000000000 Binary files a/src/components/LandingPage/assets/logo04Circle.png and /dev/null differ diff --git a/src/components/LandingPage/assets/pp.jpg b/src/components/LandingPage/assets/pp.jpg deleted file mode 100644 index 593205f4d623a459904abdcfb13c8517e4ccf666..0000000000000000000000000000000000000000 Binary files a/src/components/LandingPage/assets/pp.jpg and /dev/null differ diff --git a/src/components/Reservation/Reservation.tsx b/src/components/Reservation/Reservation.tsx deleted file mode 100644 index 3db3d07e0da7c6eb24888854e948624287dde9d8..0000000000000000000000000000000000000000 --- a/src/components/Reservation/Reservation.tsx +++ /dev/null @@ -1,7 +0,0 @@ -import React, { FC } from 'react'; - -export const Reservation: FC = () => ( - <div data-testid="Reservation"> - <h1>Reservation page!</h1> - </div> -); diff --git a/src/components/Unauthorized/Unauthirized.md b/src/components/Unauthorized/Unauthorized.md similarity index 100% rename from src/components/Unauthorized/Unauthirized.md rename to src/components/Unauthorized/Unauthorized.md diff --git a/src/components/Unauthorized/Unauthirized.test.tsx b/src/components/Unauthorized/Unauthorized.test.tsx similarity index 100% rename from src/components/Unauthorized/Unauthirized.test.tsx rename to src/components/Unauthorized/Unauthorized.test.tsx diff --git a/src/components/api/PrivateRoute/PrivateRoute.tsx b/src/components/api/PrivateRoute/PrivateRoute.tsx deleted file mode 100644 index e9e431cbf88b9a481bc95016c3729a2f30a0c9e9..0000000000000000000000000000000000000000 --- a/src/components/api/PrivateRoute/PrivateRoute.tsx +++ /dev/null @@ -1,66 +0,0 @@ -import React, { useState, useEffect } from 'react'; -import axios from 'axios'; -import { Route, Redirect, RouteProps } from 'react-router-dom'; -import { NonAuthRoutes } from 'components/api/routes'; - -/** - * A wrapper for <Route> that redirects to the login screen if you're not yet authenticated. - * */ - -type Props = { - Component: React.FC<RouteProps>; - path: string; - requiredRoles: string[]; -}; - -/* eslint-disable react/jsx-props-no-spreading */ -export const PrivateRoute = ({ - Component, - path, - requiredRoles, -}: Props): JSX.Element => { - const [auth, setAuth] = useState<boolean>(false); - const [loading, setLoading] = useState<boolean>(false); - - useEffect(() => { - const fetch = async (): Promise<unknown> => { - const result = await axios('/api/web/login/is_authenticated'); - setAuth(result.data.is_authenticated); - setLoading(true); - return null; - }; - fetch(); - }, []); - - const currentRole = String(sessionStorage.getItem('ROLE')); - const userHasRequiredRole = requiredRoles.includes(currentRole); - const message = userHasRequiredRole - ? 'Please log in to view this page' - : 'Your role is not allowed'; - - return !loading ? ( - <p>loading</p> - ) : ( - <Route - exact={false} - path={path} - render={(props: RouteProps) => - auth && userHasRequiredRole ? ( - <Component {...props} /> - ) : ( - <Redirect - to={{ - pathname: userHasRequiredRole - ? NonAuthRoutes.signIn - : NonAuthRoutes.unauthorized, - state: { - message, - requestedPath: path, - }, - }} - /> - ) - } - /> - ); -}; diff --git a/src/components/api/userRoles.ts b/src/components/api/userRoles.ts deleted file mode 100644 index add57691555c87750a834de2c58ad941f834e43c..0000000000000000000000000000000000000000 --- a/src/components/api/userRoles.ts +++ /dev/null @@ -1,5 +0,0 @@ -export enum Roles { - senior = 'senior', - admin = 'admin', - operator = 'operator', -} diff --git a/src/hooks/useAuth.ts b/src/hooks/useAuth.ts new file mode 100644 index 0000000000000000000000000000000000000000..10236cbf18cda927dbb66cf436bcd65361f2b7ae --- /dev/null +++ b/src/hooks/useAuth.ts @@ -0,0 +1,28 @@ +import { isAuthenticated } from 'api/isAuthenticated'; +import { useEffect, useState } from 'react'; + +/** + * Custom hook that uses the call is_authenticated. + * {@link isAuthenticated} + * + * @returns {boolean | null} isAuth: true if user is authenticated, false otherwise or null if the request has not finished. + */ +export const useAuth = (): [ + boolean | null, + React.Dispatch<React.SetStateAction<boolean | null>>, +] => { + let isMounted = true; + const [isAuth, setIsAuth] = useState<boolean | null>(null); + useEffect(() => { + isAuthenticated().then((state) => { + if (isMounted) { + setIsAuth(state); + } + }); + + return () => { + isMounted = false; + }; + }, []); + return [isAuth, setIsAuth]; +}; diff --git a/src/hooks/useCookie.ts b/src/hooks/useCookie.ts new file mode 100644 index 0000000000000000000000000000000000000000..d2fc2eaad555d1a00ab5dfefe43a64103d6f5b22 --- /dev/null +++ b/src/hooks/useCookie.ts @@ -0,0 +1,27 @@ +import { fetchCookie } from 'api/fetchCookie'; +import { useCallback, useEffect, useState } from 'react'; + +/** + * Custom hook that return csrf cookie fetched from server. + * + * @return cookie + */ +export const useCookie = (): string => { + const [cookie, setCookie] = useState(''); + + const askCookie = useCallback(() => { + let isMounted = true; + if (isMounted) + fetchCookie().then((cookieResponse) => setCookie(cookieResponse)); + + return () => { + isMounted = false; + }; + }, []); + + useEffect(() => { + askCookie(); + }, [askCookie]); + + return cookie; +}; diff --git a/src/hooks/useReservations.ts b/src/hooks/useReservations.ts new file mode 100644 index 0000000000000000000000000000000000000000..b671101318ae92d15fa2a581f1437abbad35aeb3 --- /dev/null +++ b/src/hooks/useReservations.ts @@ -0,0 +1,16 @@ +import { getReservations } from 'api/getReservations'; +import { getRole } from 'api/getRole'; +import { useEffect, useState } from 'react'; + +export const useReservations = (): any => { + useEffect(() => { + let isMounted = true; + + getReservations().then((data) => console.log(data)); + + return () => { + isMounted = false; + }; + }, []); + return null; +}; diff --git a/src/hooks/useRole.ts b/src/hooks/useRole.ts new file mode 100644 index 0000000000000000000000000000000000000000..785a9793d68e38bc9aad578169aa195a0d84f9fc --- /dev/null +++ b/src/hooks/useRole.ts @@ -0,0 +1,24 @@ +import { getRole } from 'api/getRole'; +import { useEffect, useState } from 'react'; + +export const useRole = (): [ + string, + React.Dispatch<React.SetStateAction<string>>, +] => { + const [role, setRole] = useState(''); + + useEffect(() => { + let isMounted = true; + // Initialize asking the server if this session is already logged in. + + getRole().then((responseRole) => { + if (isMounted) { + setRole(responseRole); + } + }); + return () => { + isMounted = false; + }; + }, []); + return [role, setRole]; +}; diff --git a/src/index.tsx b/src/index.tsx index 295b12325a3d7f53544b079f13cd9322206f054c..aac593622b068ecd48801dca196fc8367614dbb0 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -6,7 +6,7 @@ import { App } from 'App'; ReactDOM.render( <React.StrictMode> <CssBaseline /> - <App />, + <App /> </React.StrictMode>, document.getElementById('root'), );