fix tooltip null title
This commit is contained in:
commit
b350a419c7
15 changed files with 393 additions and 165 deletions
21
LICENSE
Normal file
21
LICENSE
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2022 C4 Patino, Andy Teh, Tejus Krishan
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
157
client/src/components/MobileNavbar.jsx
Normal file
157
client/src/components/MobileNavbar.jsx
Normal file
|
|
@ -0,0 +1,157 @@
|
|||
import {
|
||||
AppBar,
|
||||
Button,
|
||||
IconButton,
|
||||
Toolbar,
|
||||
Typography,
|
||||
Box,
|
||||
Drawer,
|
||||
List,
|
||||
ListItem,
|
||||
ListItemButton,
|
||||
ListItemIcon,
|
||||
ListItemText,
|
||||
} from '@mui/material';
|
||||
import MailIcon from '@mui/icons-material/Mail';
|
||||
import LoginIcon from '@mui/icons-material/Login';
|
||||
import LogoutIcon from '@mui/icons-material/Logout';
|
||||
import AppRegistrationIcon from '@mui/icons-material/AppRegistration';
|
||||
import DarkModeIcon from '@mui/icons-material/DarkMode';
|
||||
import LightModeIcon from '@mui/icons-material/LightMode';
|
||||
import SearchIcon from '@mui/icons-material/Search';
|
||||
import { Link } from 'react-router-dom';
|
||||
import Logo from 'assets/bdpa-logo.svg';
|
||||
import { useState } from 'react';
|
||||
import Gravatar from 'react-gravatar';
|
||||
import { useMode } from 'contexts';
|
||||
|
||||
export default function MobileNavbar({ logout, userData }) {
|
||||
const [open, setOpen] = useState(false);
|
||||
const { mode, setMode } = useMode();
|
||||
|
||||
const handleClick = () => setOpen((initial) => !initial);
|
||||
|
||||
const onClick = () => {
|
||||
setMode(() => (mode === 'light' ? 'dark' : 'light'));
|
||||
};
|
||||
|
||||
function NavbarControls() {
|
||||
return userData.username ? (
|
||||
<>
|
||||
<ListItem>
|
||||
<ListItemButton
|
||||
color='inherit'
|
||||
component={Link}
|
||||
to='/dashboard'
|
||||
>
|
||||
<ListItemIcon>
|
||||
<Gravatar
|
||||
email={userData.email}
|
||||
size={20}
|
||||
style={{ borderRadius: '100%' }}
|
||||
/>
|
||||
</ListItemIcon>
|
||||
<ListItemText>Dashboard</ListItemText>
|
||||
</ListItemButton>
|
||||
</ListItem>
|
||||
<ListItem>
|
||||
<ListItemButton color='inherit' component={Link} to='/mail'>
|
||||
<ListItemIcon>
|
||||
<MailIcon />
|
||||
</ListItemIcon>
|
||||
<ListItemText>Mail</ListItemText>
|
||||
</ListItemButton>
|
||||
</ListItem>
|
||||
<ListItem>
|
||||
<ListItemButton
|
||||
color='inherit'
|
||||
component={Link}
|
||||
to='/users/login'
|
||||
onClick={logout}
|
||||
>
|
||||
<ListItemIcon>
|
||||
<LogoutIcon />
|
||||
</ListItemIcon>
|
||||
<ListItemText>Logout</ListItemText>
|
||||
</ListItemButton>
|
||||
</ListItem>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<ListItem>
|
||||
<ListItemButton
|
||||
color='inherit'
|
||||
component={Link}
|
||||
to='/users/login'
|
||||
>
|
||||
<ListItemIcon>
|
||||
<LoginIcon />
|
||||
</ListItemIcon>
|
||||
<ListItemText>Login</ListItemText>
|
||||
</ListItemButton>
|
||||
</ListItem>
|
||||
<ListItem>
|
||||
<ListItemButton
|
||||
color='inherit'
|
||||
component={Link}
|
||||
to='/users/register'
|
||||
>
|
||||
<ListItemIcon>
|
||||
<AppRegistrationIcon />
|
||||
</ListItemIcon>
|
||||
<ListItemText>Register</ListItemText>
|
||||
</ListItemButton>
|
||||
</ListItem>
|
||||
</>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<AppBar position='static'>
|
||||
<Toolbar>
|
||||
<IconButton
|
||||
component={Link}
|
||||
to='/'
|
||||
sx={{ margin: '0 1vw 0 0 ' }}
|
||||
>
|
||||
<img src={Logo} alt='bdpa logo' width='40' height='40' />
|
||||
</IconButton>
|
||||
<Typography variant='h6' component='div'>
|
||||
qOverflow
|
||||
</Typography>
|
||||
<Box sx={{ flexGrow: 1 }} />
|
||||
|
||||
<Button color='inherit' onClick={handleClick}>
|
||||
Menu
|
||||
</Button>
|
||||
</Toolbar>
|
||||
<Drawer anchor='right' open={open} onClose={handleClick}>
|
||||
<List>
|
||||
<ListItem>
|
||||
<ListItemButton component={Link} to='/questions/search'>
|
||||
<ListItemIcon>
|
||||
<SearchIcon />
|
||||
</ListItemIcon>
|
||||
<ListItemText>Search</ListItemText>
|
||||
</ListItemButton>
|
||||
</ListItem>
|
||||
<NavbarControls />
|
||||
<ListItem>
|
||||
<ListItemButton onClick={onClick}>
|
||||
<ListItemIcon>
|
||||
{mode === 'light' ? (
|
||||
<DarkModeIcon />
|
||||
) : (
|
||||
<LightModeIcon />
|
||||
)}
|
||||
</ListItemIcon>
|
||||
<ListItemText>
|
||||
Switch to {mode === 'light' ? 'dark' : 'light'}{' '}
|
||||
mode
|
||||
</ListItemText>
|
||||
</ListItemButton>
|
||||
</ListItem>
|
||||
</List>
|
||||
</Drawer>
|
||||
</AppBar>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import { List, Pagination } from '@mui/material';
|
||||
import { List, Pagination, Box } from '@mui/material';
|
||||
|
||||
import { NoData } from 'components';
|
||||
|
||||
|
|
@ -8,22 +8,29 @@ export default function PaginatedList({
|
|||
data,
|
||||
handleChangePage,
|
||||
noData = true,
|
||||
page
|
||||
page,
|
||||
}) {
|
||||
return (
|
||||
<List sx={{ pl: 2, pr: 2, width: '100%' }}>
|
||||
{data.length
|
||||
? data.map((item) => Component(item))
|
||||
: noData && <NoData />}
|
||||
{count > 1 && (
|
||||
<Pagination
|
||||
count={count}
|
||||
onChange={handleChangePage}
|
||||
page={page}
|
||||
/>
|
||||
<Box
|
||||
fullWidth
|
||||
display='flex'
|
||||
justifyContent='center'
|
||||
sx={{ margin: '1vh 0' }}
|
||||
>
|
||||
<Pagination
|
||||
count={count}
|
||||
onChange={handleChangePage}
|
||||
page={page}
|
||||
siblingCount={1}
|
||||
size='small'
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
{
|
||||
data.length
|
||||
? data.map(item => Component(item))
|
||||
: noData && <NoData />
|
||||
}
|
||||
</List>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,11 @@ import {
|
|||
CommentControl,
|
||||
VoteControl,
|
||||
} from 'controllers/QAControllers';
|
||||
import { getAnswerVote, updateAnswerVote } from 'services/questionsServices';
|
||||
import {
|
||||
getAnswerVote,
|
||||
updateAnswerVote,
|
||||
postAnswerComment,
|
||||
} from 'services/questionsServices';
|
||||
|
||||
export default function Answer({
|
||||
accepted,
|
||||
|
|
@ -21,11 +25,16 @@ export default function Answer({
|
|||
text,
|
||||
upvotes,
|
||||
}) {
|
||||
const { userData: { level, username } } = useUser();
|
||||
const {
|
||||
userData: { level, username },
|
||||
} = useUser();
|
||||
const getVote = () => getAnswerVote(question_id, answer_id);
|
||||
const updateVote = (data) => updateAnswerVote(question_id, answer_id, data);
|
||||
const canComment = level >= 3 || creator === username;
|
||||
|
||||
const postComment = (data) =>
|
||||
postAnswerComment(question_id, answer_id, data);
|
||||
|
||||
return (
|
||||
<span key={answer_id}>
|
||||
<ListItem disablePadding>
|
||||
|
|
@ -41,10 +50,7 @@ export default function Answer({
|
|||
/>
|
||||
{accepted && (
|
||||
<div style={{ textAlign: 'center' }}>
|
||||
<Tooltip
|
||||
title='Accepted Answer'
|
||||
placement='right'
|
||||
>
|
||||
<Tooltip title='Accepted Answer' placement='right'>
|
||||
<CheckIcon color='success' />
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
|
@ -55,7 +61,7 @@ export default function Answer({
|
|||
{...{ createdAt, creator, text: 'answered' }}
|
||||
/>
|
||||
<ReactMarkdown>{text}</ReactMarkdown>
|
||||
<CommentControl {...{ canComment }} />
|
||||
<CommentControl {...{ canComment, postComment }} />
|
||||
</ListItemText>
|
||||
</ListItem>
|
||||
<ListItem sx={{ pl: 8 }}>
|
||||
|
|
|
|||
|
|
@ -6,19 +6,24 @@ import { AnswerForm } from 'controllers/FormControllers'
|
|||
import MdPreview from 'components/MdPreview'
|
||||
|
||||
export default function CreateAnswer({ canAnswer, question_id }) {
|
||||
const [show, setShow] = useState(false)
|
||||
|
||||
const [show, setShow] = useState(false);
|
||||
|
||||
function toggleShow() {
|
||||
setShow(!show)
|
||||
setShow(!show);
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
<Tooltip title={canAnswer ? '' : 'You need to be authenticated to answer or level 5 if the question is protected'}>
|
||||
<span>
|
||||
<Button disabled={!canAnswer} variant="contained" onClick={toggleShow}>Add Answer</Button>
|
||||
</span>
|
||||
</Tooltip>
|
||||
<Tooltip title={canAnswer ? '' : 'You need to be authenticated to answer or level 5 if the question is protected'}>
|
||||
<span>
|
||||
<Button
|
||||
disabled={!canAnswer}
|
||||
variant='contained'
|
||||
onClick={toggleShow}
|
||||
>
|
||||
Add Answer
|
||||
</Button>
|
||||
</span>
|
||||
</Tooltip>
|
||||
{show && (
|
||||
<Card>
|
||||
<CardContent>
|
||||
|
|
@ -27,9 +32,6 @@ export default function CreateAnswer({ canAnswer, question_id }) {
|
|||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
|
||||
</div>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -109,24 +109,17 @@ export default function Question({
|
|||
<>
|
||||
<Box m={2}>
|
||||
<Typography variant='h4'>{title}</Typography>
|
||||
<Typography display='inline' m={1}>
|
||||
Views: {views}
|
||||
</Typography>
|
||||
<Typography display='inline' m={1}>
|
||||
Answers: {answers}{' '}
|
||||
</Typography>
|
||||
<Typography display='inline' m={1}>
|
||||
Comments: {comments}{' '}
|
||||
</Typography>
|
||||
<Typography display='inline' m={1}>
|
||||
{' '}
|
||||
Status:
|
||||
</Typography>
|
||||
<Typography display='inline' m={1}>Views: {views}</Typography>
|
||||
<Typography display='inline' m={1}>Answers: {answers}</Typography>
|
||||
<Typography display='inline' m={1}>Comments: {comments}</Typography>
|
||||
<Typography display='inline' m={1}>Status:</Typography>
|
||||
<Chip
|
||||
color={statusColor(status)}
|
||||
label={status}
|
||||
size='small'
|
||||
/>
|
||||
|
||||
<Chip color={statusColor(status)} label={status} size='small' />
|
||||
<Typography display='inline' m={1}>
|
||||
Accepted Answer:
|
||||
</Typography>
|
||||
<Typography display='inline' m={1}>Accepted Answer:</Typography>
|
||||
<Chip
|
||||
color={hasAcceptedAnswer ? 'success' : 'error'}
|
||||
label={hasAcceptedAnswer ? 'yes' : 'no'}
|
||||
|
|
@ -134,15 +127,14 @@ export default function Question({
|
|||
/>
|
||||
<Button
|
||||
component={Link}
|
||||
to='../ask'
|
||||
style={{ marginLeft: '10px' }}
|
||||
display='inline'
|
||||
m={1}
|
||||
style={{ marginLeft: '10px' }}
|
||||
to='../ask'
|
||||
variant='contained'
|
||||
>
|
||||
Ask question
|
||||
</Button>
|
||||
|
||||
<Tooltip title={close ? '' : 'You must be level 7'}>
|
||||
<span>
|
||||
<Button
|
||||
|
|
@ -150,20 +142,14 @@ export default function Question({
|
|||
style={{ marginLeft: '10px' }}
|
||||
display='inline'
|
||||
m={1}
|
||||
variant='contained'
|
||||
onClick={changeProtect}
|
||||
variant='contained'
|
||||
>
|
||||
Close/Open
|
||||
</Button>
|
||||
</span>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip
|
||||
title={
|
||||
!protect &&
|
||||
'You must be level 6 and this question must be open'
|
||||
}
|
||||
>
|
||||
<Tooltip title={protect ? '' : 'You must be level 6 and this question must be open'}>
|
||||
<span>
|
||||
<Button
|
||||
disabled={!protect}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import PaginatedList from './PaginatedList';
|
|||
import SearchBar from './SearchBar';
|
||||
import MdPreview from './MdPreview';
|
||||
import Profile from './Profile';
|
||||
import MobileNavbar from './MobileNavbar';
|
||||
|
||||
export {
|
||||
CreationInfoTag,
|
||||
|
|
@ -23,5 +24,6 @@ export {
|
|||
PaginatedList,
|
||||
SearchBar,
|
||||
MdPreview,
|
||||
Profile
|
||||
Profile,
|
||||
MobileNavbar,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -21,13 +21,9 @@ export default function Dashboard() {
|
|||
const navigate = useNavigate();
|
||||
|
||||
const [current, setCurrent] = useState('questions');
|
||||
const { userData: {
|
||||
email,
|
||||
level,
|
||||
loading,
|
||||
points,
|
||||
username,
|
||||
} } = useUser();
|
||||
const {
|
||||
userData: { email, level, loading, points, username },
|
||||
} = useUser();
|
||||
|
||||
const onChange = (_, value) => {
|
||||
setCurrent(value);
|
||||
|
|
@ -35,14 +31,14 @@ export default function Dashboard() {
|
|||
|
||||
const getAnswers = async ({ answer_id }) => {
|
||||
const { error, answers } = await getUserAnswers({ after: answer_id });
|
||||
if (!error)
|
||||
return answers;
|
||||
if (!error) return answers;
|
||||
};
|
||||
|
||||
const getQuestions = async ({ question_id }) => {
|
||||
const { error, questions } = await getUserQuestions({ after: question_id });
|
||||
if (!error)
|
||||
return questions;
|
||||
const { error, questions } = await getUserQuestions({
|
||||
after: question_id,
|
||||
});
|
||||
if (!error) return questions;
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
|
|
@ -57,62 +53,102 @@ export default function Dashboard() {
|
|||
}
|
||||
}, [loading, navigate]);
|
||||
|
||||
return username && (
|
||||
<>
|
||||
<Helmet>
|
||||
<title>Dashboard</title>
|
||||
</Helmet>
|
||||
<Paper sx={{ margin: '1vh', padding: '5vh' }}>
|
||||
<Grid container spacing={2}>
|
||||
<Grid item md={2}>
|
||||
{email && <Gravatar email={email} size={200} style={{ borderRadius: '100%' }} />}
|
||||
</Grid>
|
||||
<Grid item md={10}>
|
||||
<Typography>Username: {username}</Typography>
|
||||
<Typography>Email: {email}</Typography>
|
||||
<Typography>Level: {level}</Typography>
|
||||
<Typography>Points: {points}</Typography>
|
||||
</Grid>
|
||||
<Grid item md={12}>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
flexDirection: 'column',
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
color='primary'
|
||||
component={Link}
|
||||
to='/dashboard/update'
|
||||
sx={{ margin: '0 1vh' }}
|
||||
variant='outlined'
|
||||
fullWidth
|
||||
return (
|
||||
username && (
|
||||
<>
|
||||
<Helmet>
|
||||
<title>Dashboard</title>
|
||||
</Helmet>
|
||||
<Paper
|
||||
sx={{
|
||||
margin: { xs: '0.3vh', sm: '1vh', md: '1vh' },
|
||||
padding: { xs: '1vh', sm: '3vh', md: '5vh' },
|
||||
}}
|
||||
>
|
||||
<Grid
|
||||
container
|
||||
spacing={2}
|
||||
sx={{
|
||||
display: { xs: 'flex', md: 'block' },
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
>
|
||||
<Grid item md={2}>
|
||||
{email && (
|
||||
<Gravatar
|
||||
email={email}
|
||||
size={200}
|
||||
style={{ borderRadius: '100%' }}
|
||||
/>
|
||||
)}
|
||||
</Grid>
|
||||
<Grid item md={10}>
|
||||
<Typography>Username: {username}</Typography>
|
||||
<Typography>Email: {email}</Typography>
|
||||
<Typography>Level: {level}</Typography>
|
||||
<Typography>Points: {points}</Typography>
|
||||
</Grid>
|
||||
<Grid item md={12}>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
flexDirection: 'column',
|
||||
}}
|
||||
>
|
||||
Update User Information
|
||||
</Button>
|
||||
<ToggleButtonGroup
|
||||
value={current}
|
||||
exclusive
|
||||
onChange={onChange}
|
||||
fullWidth
|
||||
>
|
||||
<ToggleButton value='questions' variant='outlined'>
|
||||
Questions
|
||||
</ToggleButton>
|
||||
<ToggleButton value='answers' variant='outlined'>
|
||||
Answers
|
||||
</ToggleButton>
|
||||
</ToggleButtonGroup>
|
||||
</Box>
|
||||
</Grid>
|
||||
<Button
|
||||
color='primary'
|
||||
component={Link}
|
||||
to='/dashboard/update'
|
||||
sx={{ margin: '0 1vh' }}
|
||||
variant='outlined'
|
||||
fullWidth
|
||||
>
|
||||
Update User Information
|
||||
</Button>
|
||||
<ToggleButtonGroup
|
||||
value={current}
|
||||
exclusive
|
||||
onChange={onChange}
|
||||
fullWidth
|
||||
>
|
||||
<ToggleButton
|
||||
value='questions'
|
||||
variant='outlined'
|
||||
>
|
||||
Questions
|
||||
</ToggleButton>
|
||||
<ToggleButton
|
||||
value='answers'
|
||||
variant='outlined'
|
||||
>
|
||||
Answers
|
||||
</ToggleButton>
|
||||
</ToggleButtonGroup>
|
||||
</Box>
|
||||
</Grid>
|
||||
|
||||
<Grid item md={12}>
|
||||
{current === 'questions' && <PaginatedList {...{ Component: ListQuestion, getData: getQuestions }} />}
|
||||
{current === 'answers' && <PaginatedList {...{ Component: ListAnswer, getData: getAnswers }} />}
|
||||
<Grid item md={12}>
|
||||
{current === 'questions' && (
|
||||
<PaginatedList
|
||||
{...{
|
||||
Component: ListQuestion,
|
||||
getData: getQuestions,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{current === 'answers' && (
|
||||
<PaginatedList
|
||||
{...{
|
||||
Component: ListAnswer,
|
||||
getData: getAnswers,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Paper>
|
||||
</>
|
||||
</Paper>
|
||||
</>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,15 +11,15 @@ export default function ForgotPassword() {
|
|||
justifyContent='center'
|
||||
style={{ height: '95vh' }}
|
||||
>
|
||||
<Grid item xs={3.5} />
|
||||
<Grid item xs={5}>
|
||||
<Grid item xs />
|
||||
<Grid item xs={11} sm={7} md={5}>
|
||||
<Card sx={{ padding: '1vh' }}>
|
||||
<CardContent>
|
||||
<ForgotPasswordForm />
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Grid>
|
||||
<Grid item xs={3.5} />
|
||||
<Grid item xs />
|
||||
</Grid>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,14 +23,16 @@ export default function Login() {
|
|||
justifyContent='center'
|
||||
style={{ height: '92vh' }}
|
||||
>
|
||||
<Grid item xs={3.5} />
|
||||
<Grid item xs={5}>
|
||||
<Grid item xs />
|
||||
<Grid item xs={11} sm={7} md={5}>
|
||||
<Card sx={{ padding: '1vh' }}>
|
||||
<CardContent>
|
||||
<LoginForm />
|
||||
<Typography variant='body1'>
|
||||
Forgot your password?{' '}
|
||||
<Link to='/users/recover'>Recover Password</Link>
|
||||
<Link to='/users/recover'>
|
||||
Recover Password
|
||||
</Link>
|
||||
</Typography>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
|
@ -38,7 +40,7 @@ export default function Login() {
|
|||
<Alert severity='warning'> {returnMsg()} </Alert>
|
||||
)}
|
||||
</Grid>
|
||||
<Grid item xs={3.5} />
|
||||
<Grid item xs />
|
||||
</Grid>
|
||||
</>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -7,29 +7,29 @@ import { RegisterForm } from 'controllers/FormControllers';
|
|||
export default function Register() {
|
||||
return (
|
||||
<>
|
||||
<Helmet>
|
||||
<Helmet>
|
||||
<title>Register - qOverflow</title>
|
||||
</Helmet>
|
||||
<Grid
|
||||
container
|
||||
spacing={2}
|
||||
alignItems='center'
|
||||
justifyContent='center'
|
||||
style={{ height: '92vh' }}
|
||||
>
|
||||
<Grid item xs={3.5} />
|
||||
<Grid item xs={5}>
|
||||
<Card sx={{ padding: '1vh' }}>
|
||||
<CardContent>
|
||||
<RegisterForm />
|
||||
<div>
|
||||
<img src={CAPTCHA} alt='CAPTCHA' />
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Grid
|
||||
container
|
||||
spacing={2}
|
||||
alignItems='center'
|
||||
justifyContent='center'
|
||||
style={{ height: '92vh' }}
|
||||
>
|
||||
<Grid item xs />
|
||||
<Grid item xs={11} sm={7} md={5}>
|
||||
<Card sx={{ padding: '1vh' }}>
|
||||
<CardContent>
|
||||
<RegisterForm />
|
||||
<div>
|
||||
<img src={CAPTCHA} alt='CAPTCHA' />
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Grid>
|
||||
<Grid item xs />
|
||||
</Grid>
|
||||
<Grid item xs={3.5} />
|
||||
</Grid>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,15 +28,15 @@ export default function Register() {
|
|||
justifyContent='center'
|
||||
style={{ height: '92vh' }}
|
||||
>
|
||||
<Grid item xs={3.5} />
|
||||
<Grid item xs={5}>
|
||||
<Grid item xs />
|
||||
<Grid item xs={11} sm={7} md={5}>
|
||||
<Card sx={{ padding: '1vh' }}>
|
||||
<CardContent>
|
||||
<UpdateForm />
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Grid>
|
||||
<Grid item xs={3.5} />
|
||||
<Grid item xs />
|
||||
</Grid>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
import { useUser, useError } from 'contexts';
|
||||
import { Navbar } from 'components';
|
||||
import { Navbar, MobileNavbar } from 'components';
|
||||
import { logout } from 'services/userServices';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useMediaQuery } from '@mui/material';
|
||||
import Cookies from 'js-cookie';
|
||||
|
||||
export default function NavbarController() {
|
||||
|
|
@ -9,6 +10,10 @@ export default function NavbarController() {
|
|||
const { setError } = useError();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const xs = useMediaQuery((theme) => theme.breakpoints.only('xs'));
|
||||
const sm = useMediaQuery((theme) => theme.breakpoints.only('sm'));
|
||||
const md = useMediaQuery((theme) => theme.breakpoints.only('md'));
|
||||
|
||||
const logoutUser = async () => {
|
||||
const { error } = await logout();
|
||||
if (error) {
|
||||
|
|
@ -19,5 +24,7 @@ export default function NavbarController() {
|
|||
navigate('/users/login');
|
||||
};
|
||||
|
||||
return Navbar({ logout: logoutUser, userData });
|
||||
return md
|
||||
? Navbar({ logout: logoutUser, userData })
|
||||
: MobileNavbar({ logout: logoutUser, userData });
|
||||
}
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ const updateAnswerVote = async (question_id, answer_id, data) =>
|
|||
const getAnswerComments = async (question_id, answer_id) =>
|
||||
callQuestionsAPI('get', `/${question_id}/answers/${answer_id}/comments`);
|
||||
|
||||
const postAnswerComments = async (question_id, answer_id, data) =>
|
||||
const postAnswerComment = async (question_id, answer_id, data) =>
|
||||
callQuestionsAPI(
|
||||
'post',
|
||||
`/${question_id}/answers/${answer_id}/comments`,
|
||||
|
|
@ -109,7 +109,7 @@ export {
|
|||
getQuestion,
|
||||
getQuestionComments,
|
||||
postAnswer,
|
||||
postAnswerComments,
|
||||
postAnswerComment,
|
||||
postQuestion,
|
||||
postQuestionComment,
|
||||
searchQuestions,
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ const createRequest = require('server/utils/api');
|
|||
const getUserLevel = require('server/utils/getUserLevel');
|
||||
|
||||
async function CreateAnswerComment(req, res) {
|
||||
const { username } = req.user;
|
||||
const user = req.user;
|
||||
const { question_id, answer_id } = req.params;
|
||||
const { text } = req.body;
|
||||
|
||||
|
|
@ -20,20 +20,22 @@ async function CreateAnswerComment(req, res) {
|
|||
return res.status(403).send(config.errorForbidden);
|
||||
|
||||
// Verify user has permissions to comment
|
||||
if (getUserLevel(user.points) < 3 && cachedAnswer?.creator !== username) {
|
||||
if (
|
||||
getUserLevel(user.points) < 3 &&
|
||||
cachedAnswer?.creator !== user.username
|
||||
) {
|
||||
return res.status(403).send(config.errorForbidden);
|
||||
}
|
||||
|
||||
// Verify text is included in request body
|
||||
if (!text)
|
||||
return res.status(400).send({ success: false });
|
||||
if (!text) return res.status(400).send({ success: false });
|
||||
|
||||
// Post question with BDPA server
|
||||
const { success, comment } = await createRequest(
|
||||
'post',
|
||||
`/questions/${question_id}/answers/${answer_id}/comments`,
|
||||
{
|
||||
creator: username,
|
||||
creator: user.username,
|
||||
text,
|
||||
}
|
||||
);
|
||||
|
|
|
|||
Reference in a new issue