Skip to content
Snippets Groups Projects
Verified Commit cb492285 authored by Rafael László's avatar Rafael László :speech_balloon:
Browse files

Merge branch 'dev' into about

parents 67116b98 c304518c
Branches
No related tags found
2 merge requests!19fix type imports,!16About page
Showing with 19880 additions and 2269 deletions
Source diff could not be displayed: it is too large. Options to address this: view the blob.
import React from 'react'; import React from 'react';
import { QueryClient, QueryClientProvider } from 'react-query'; import { QueryClient, QueryClientProvider } from 'react-query';
import { BrowserRouter as Router } from 'react-router-dom'; import { BrowserRouter as Router } from 'react-router-dom';
import RememberMe from './components/RememberMe';
import { ClientContextProvider } from './context'; import { ClientContextProvider } from './context';
import ThemeProvider from './context/ThemeProvider'; import ThemeProvider from './context/ThemeProvider';
import { UserStateProvider } from './context/UserContext'; import { UserStateProvider } from './context/UserContext';
...@@ -16,6 +17,7 @@ function App(): React.ReactElement { ...@@ -16,6 +17,7 @@ function App(): React.ReactElement {
<UserStateProvider> <UserStateProvider>
<ThemeProvider theme={darkTheme}> <ThemeProvider theme={darkTheme}>
<Router> <Router>
<RememberMe />
<Routes /> <Routes />
</Router> </Router>
</ThemeProvider> </ThemeProvider>
......
...@@ -2,6 +2,7 @@ import React from 'react'; ...@@ -2,6 +2,7 @@ import React from 'react';
import { Redirect, Route, Switch } from 'react-router'; import { Redirect, Route, Switch } from 'react-router';
import AboutPage from './pages/AboutPage'; import AboutPage from './pages/AboutPage';
import NewsPage from './pages/NewsPage'; import NewsPage from './pages/NewsPage';
import RegisterPage from './pages/RegisterPage';
import RulesPage from './pages/RulesPage'; import RulesPage from './pages/RulesPage';
const Routes: React.FC = () => ( const Routes: React.FC = () => (
...@@ -18,6 +19,9 @@ const Routes: React.FC = () => ( ...@@ -18,6 +19,9 @@ const Routes: React.FC = () => (
<Route path="/rules"> <Route path="/rules">
<RulesPage /> <RulesPage />
</Route> </Route>
<Route path="/register">
<RegisterPage />
</Route>
</Switch> </Switch>
); );
......
import Axios, { AxiosRequestConfig, AxiosResponse } from 'axios'; import Axios, { AxiosError, AxiosRequestConfig } from 'axios';
import { EmptyReq, LoginReq, RegisterReq, UserResponse } from './types'; import type { ProfileWithStatus } from '../context';
import { IProfile } from '../types/Profile';
type LoginQuery = (data?: LoginReq) => Promise<AxiosResponse<UserResponse>>; type MeQuery = () => Promise<ProfileWithStatus>;
type RegisterQuery = (data?: RegisterReq) => Promise<AxiosResponse<UserResponse>>; type RegisterQuery = (data: ProfileData) => Promise<any>;
type MeQuery = (data?: EmptyReq, authToken?: string) => Promise<AxiosResponse<UserResponse>>;
export type ProfileData = {
studentCardNumber: string;
roomNumber: number;
};
export type Id = {
_id: string;
};
export type AuthClient = { export type AuthClient = {
login: LoginQuery;
me: MeQuery; me: MeQuery;
register: RegisterQuery; register: RegisterQuery;
}; };
...@@ -14,29 +22,28 @@ export type AuthClient = { ...@@ -14,29 +22,28 @@ export type AuthClient = {
export function auth(config: AxiosRequestConfig): AuthClient { export function auth(config: AxiosRequestConfig): AuthClient {
const axios = Axios.create({ const axios = Axios.create({
...config, ...config,
baseURL: `${config.baseURL}/api/auth`, baseURL: `${config.baseURL}/api/v1/`,
}); });
const login: LoginQuery = (data?: LoginReq): Promise<AxiosResponse<UserResponse>> => { const me: MeQuery = async (): Promise<ProfileWithStatus> => {
return axios.post<UserResponse>('/login', data); try {
}; const profileResp = await axios.get<IProfile>('/users/me');
const isLoggedIn =
const register: RegisterQuery = (data?: RegisterReq): Promise<AxiosResponse<UserResponse>> => { profileResp.status === 404 || profileResp.status === 200 || profileResp.status === 403;
return axios.post<UserResponse>('/register', data); return { profile: profileResp.data, isLoggedIn };
} catch (e) {
const err = e as AxiosError;
const isLoggedIn = (err.response?.status === 404 || err.response?.status === 403) ?? false;
return { profile: undefined, isLoggedIn };
}
}; };
const me: MeQuery = ( const register: RegisterQuery = async (data: ProfileData): Promise<Id> => {
data?: undefined, return (await axios.post('/users/me', data)).data;
authToken?: string,
): Promise<AxiosResponse<UserResponse>> => {
return axios.get<UserResponse>('/me', {
headers: { Authorization: `Bearer ${authToken}` },
});
}; };
return { return {
login,
register,
me, me,
register,
}; };
} }
...@@ -89,10 +89,9 @@ const useStyles = makeStyles((theme) => ({ ...@@ -89,10 +89,9 @@ const useStyles = makeStyles((theme) => ({
export type HeaderProps = { export type HeaderProps = {
customToolbar?: React.ReactElement; customToolbar?: React.ReactElement;
menuItems?: AppBarMenuItem[]; menuItems?: AppBarMenuItem[];
handleLogout?: () => void;
}; };
const Header: React.FC<HeaderProps> = ({ customToolbar, handleLogout, menuItems = MENU_ITEMS }) => { const Header: React.FC<HeaderProps> = ({ customToolbar, menuItems = MENU_ITEMS }) => {
const classes = useStyles(); const classes = useStyles();
const history = useHistory(); const history = useHistory();
const { profile } = useUserContext(); const { profile } = useUserContext();
...@@ -143,7 +142,7 @@ const Header: React.FC<HeaderProps> = ({ customToolbar, handleLogout, menuItems ...@@ -143,7 +142,7 @@ const Header: React.FC<HeaderProps> = ({ customToolbar, handleLogout, menuItems
/> />
))} ))}
{profile.role === 'ADMIN' && ( {profile.profile && profile.profile.role === 'ADMIN' && (
<> <>
{ADMIN_MENU_ITEMS.map((item) => ( {ADMIN_MENU_ITEMS.map((item) => (
<Tab <Tab
...@@ -162,35 +161,35 @@ const Header: React.FC<HeaderProps> = ({ customToolbar, handleLogout, menuItems ...@@ -162,35 +161,35 @@ const Header: React.FC<HeaderProps> = ({ customToolbar, handleLogout, menuItems
</Tabs> </Tabs>
</Grid> </Grid>
{!isNil(profile) ? ( {!isNil(profile) && profile.profile ? (
<> <>
<IconButton size="medium"> <IconButton size="medium">
<Badge <Badge color="error">
badgeContent={profile.notices.filter((item) => !item.isSeen).length}
color="error"
>
<MailOutline style={{ color: 'white' }} /> <MailOutline style={{ color: 'white' }} />
</Badge> </Badge>
</IconButton> </IconButton>
{profile.warnings.length > 0 && (
<IconButton size="medium" disableRipple> <IconButton size="medium" disableRipple>
<Report color="error" /> <Report color="error" />
</IconButton> </IconButton>
)}
<Typography variant="h6" align="center" className={classes.texts}> <Typography variant="h6" align="center" className={classes.texts}>
{profile.name} {profile.profile && profile.profile.name}
</Typography> </Typography>
<a href="/api/v1/logout">
<Box color="secondary.main"> <Box color="secondary.main">
<IconButton color="inherit" size="medium" onClick={handleLogout}> <IconButton color="inherit" size="medium">
<ExitToApp /> <ExitToApp />
</IconButton> </IconButton>
</Box> </Box>
</a>
</> </>
) : ( ) : (
<a href="/api/v1/login">
<Button color="secondary" variant="contained"> <Button color="secondary" variant="contained">
Belépés Belépés
</Button> </Button>
</a>
)} )}
</Toolbar> </Toolbar>
)} )}
......
import { Button } from '@material-ui/core';
import React, { useState } from 'react';
import { ProfileModal } from './ProfileModal';
const ProfileButton: React.FC = () => {
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;
// eslint-disable-next-line object-curly-newline
import { Avatar, Box, Button, Container, Typography } from '@material-ui/core';
import { Field, Form, Formik } from 'formik';
import { isNil } from 'lodash';
import React, { useEffect, useState } from 'react';
import { useQueryClient } from 'react-query';
import { Redirect } from 'react-router-dom';
import useRegister from '../hooks/useRegister';
import StyledTextField from './StyledTextField';
type FormValues = {
roomNumber: string | undefined;
studentCardNumber: string;
};
const ProfileForm: React.FC = () => {
const { mutate: register, data, isError } = useRegister();
const [redirect, setRedirect] = useState(false);
const client = useQueryClient();
useEffect(() => {
if (data && !isError) {
client.invalidateQueries('me');
setRedirect(true);
}
}, [data, isError]);
return (
<Container
style={{
padding: '15px',
width: '500px',
borderRadius: '8px',
}}
>
<Formik
initialValues={{ roomNumber: '', studentCardNumber: '' } as FormValues}
onSubmit={async ({ roomNumber, studentCardNumber }): Promise<void> => {
if (!isNil(roomNumber) && !isNil(studentCardNumber)) {
await register({ roomNumber: Number(roomNumber), studentCardNumber });
}
}}
>
<Form>
<Box
fontWeight="fontWeightBold"
display="flex"
justifyContent="space-between"
alignItems="center"
pl={2}
>
<Typography variant="h5" component="h5" color="textPrimary">
Profilom
</Typography>
</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}>
<Field
as={StyledTextField}
id="studentCardNumber"
name="studentCardNumber"
label="Diákigazolvány szám"
variant="outlined"
color="primary"
fullWidth
size="small"
/>
</Box>
<Box my={1}>
<Field
as={StyledTextField}
id="roomNumber"
name="roomNumber"
type="number"
label="Szobaszám"
variant="outlined"
color="secondary"
fullWidth
size="small"
/>
</Box>
<Box display="flex" justifyContent="flex-end" mt={3}>
<Button type="submit" variant="contained">
Mentés
</Button>
</Box>
</Box>
</Form>
</Formik>
{redirect && <Redirect to="/" />}
</Container>
);
};
export default ProfileForm;
// eslint-disable-next-line object-curly-newline
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: React.FC<IProfileModalProps> = ({ isOpen, setIsOpen }) => {
const onClose = (): void => {
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;
import { isNil } from 'lodash';
import React, { useEffect } from 'react';
import { Redirect } from 'react-router-dom';
import useMe from '../hooks/useMe';
import { useUserContext } from '../hooks/useUserContext';
interface Props {}
const RememberMe: React.FC = ({ children }) => {
const { setProfile } = useUserContext();
const { data: me } = useMe();
useEffect(() => {
if (me) {
setProfile(me);
} else {
setProfile(undefined);
}
}, [me, setProfile]);
if (isNil(me?.profile) && me?.isLoggedIn) {
return <Redirect to="/register" />;
}
return <>{children}</>;
};
export default RememberMe;
import { TextField, withStyles } from '@material-ui/core';
const StyledTextField = withStyles(({ palette: { primary: { contrastText } } }) => ({
root: {
'& label.Mui-focused': {
color: contrastText,
},
'& label': {
color: contrastText,
},
'& .MuiOutlinedInput-root': {
color: contrastText,
'& fieldset': {
borderColor: contrastText,
},
'&:hover fieldset': {
borderColor: contrastText,
},
'&.Mui-focused fieldset': {
borderColor: contrastText,
},
},
color: contrastText,
},
}))(TextField);
export default StyledTextField;
import React, { createContext, useState } from 'react'; import React, { createContext, useState } from 'react';
import { IProfile, Role } from '../types/Profile'; import { IProfile } from '../types/Profile';
export type ProfileWithStatus = {
profile?: IProfile;
isLoggedIn: boolean;
};
export interface UserContextType { export interface UserContextType {
profile: IProfile | undefined; profile?: ProfileWithStatus;
setProfile: (profile: IProfile) => void; setProfile: (profile: ProfileWithStatus | undefined) => void;
} }
// Context /* // Context
// eslint-disable-next-line @typescript-eslint/no-unused-vars // eslint-disable-next-line @typescript-eslint/no-unused-vars
const initialState: IProfile = { const initialState: IProfile = {
externalId: 'abcd', externalId: 'abcd',
...@@ -17,14 +22,14 @@ const initialState: IProfile = { ...@@ -17,14 +22,14 @@ const initialState: IProfile = {
name: 'Nagy Gizike', name: 'Nagy Gizike',
warnings: [], warnings: [],
notices: [{ _id: '123', text: 'Asd', isSeen: false }], notices: [{ _id: '123', text: 'Asd', isSeen: false }],
}; }; */
export const UserContext = createContext({} as UserContextType); export const UserContext = createContext({} as UserContextType);
const { Provider } = UserContext; const { Provider } = UserContext;
export const UserStateProvider: React.FC = ({ children }) => { export const UserStateProvider: React.FC = ({ children }) => {
const [profile, setProfile] = useState<IProfile | undefined>(initialState); const [profile, setProfile] = useState<ProfileWithStatus>();
return <Provider value={{ profile, setProfile }}>{children}</Provider>; return <Provider value={{ profile, setProfile }}>{children}</Provider>;
}; };
......
import { useMutation } from 'react-query';
import useClientContext from './useClientContext';
export default function useLogin() {
const client = useClientContext();
return useMutation('login', client.client.auth.login);
}
import { useQuery, UseQueryResult } from 'react-query';
import { ProfileWithStatus } from '../context';
import useClientContext from './useClientContext';
export default function useMe(): UseQueryResult<ProfileWithStatus, Error> {
const client = useClientContext();
return useQuery<ProfileWithStatus, Error>('me', async () => client.client.auth.me());
}
import { useMutation, UseMutationResult } from 'react-query';
import { Id, ProfileData } from '../client/auth';
import useClientContext from './useClientContext';
export default function useRegister(): UseMutationResult<Id, Error, ProfileData> {
const client = useClientContext();
return useMutation<any, Error, ProfileData>('register', async (data: ProfileData) =>
client.client.auth.register(data),
);
}
...@@ -20,13 +20,15 @@ const Page: React.FC = ({ children }) => { ...@@ -20,13 +20,15 @@ const Page: React.FC = ({ children }) => {
const isMobile = useMediaQuery((theme: Theme) => theme.breakpoints.down('xs')); const isMobile = useMediaQuery((theme: Theme) => theme.breakpoints.down('xs'));
const classes = useStyles(); const classes = useStyles();
/* const { mutate } = useRegister();
useEffect(() => {
mutate({ roomNumber: 1300, studentCardNumber: '122223455' });
}, [mutate]); */
return ( return (
<Box className={classes.root}> <Box className={classes.root}>
{isMobile ? ( {isMobile ? <MobileHeader onMenuClick={toggleOn} /> : <Header />}
<MobileHeader onMenuClick={toggleOn} />
) : (
<Header handleLogout={(): void => {}} />
)}
{isMobile && ( {isMobile && (
<DrawerMenu open={openDrawer} toggleOpen={toggleOff} handleLogout={(): void => {}} /> <DrawerMenu open={openDrawer} toggleOpen={toggleOff} handleLogout={(): void => {}} />
)} )}
......
import React from 'react';
import ProfileForm from '../components/ProfileForm';
import Page from './Page';
const NewsPage: React.FC = () => (
<Page>
<ProfileForm />
</Page>
);
export default NewsPage;
...@@ -18,18 +18,12 @@ export interface IWarning { ...@@ -18,18 +18,12 @@ export interface IWarning {
} }
export interface IProfile { export interface IProfile {
externalId: string; _id: string;
studentCardNumber?: string; studentCardNumber?: string;
roomNumber?: Number; roomNumber?: Number;
newPicture?: string;
acceptedPicture?: string;
role: Role; role: Role;
email: string; email: string;
name: string; name: string;
isStaffMember?: boolean;
staffMemberText?: string;
warnings: IWarning[];
notices: INotice[];
} }
export interface IStaff { export interface IStaff {
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment