Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added reset password, which required a lot of changes and refactors #35

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions src/js/common/utils/logging.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,18 @@ export function authLog (text, res) {
}
}

// Log reactQuery fetches and responses
export function reactQueryLog (text, res) {
if (webAppConfig.LOG_REACT_QUERY_EVENTS) {
if (res) {
console.log('>> reactQuery >> ', text, res);
} else {
console.log('>> reactQuery >> ', text);
}
}
}


// Cordova offsets
export function cordovaOffsetLog (text, res) {
if (webAppConfig.LOG_CORDOVA_OFFSETS) {
Expand Down
179 changes: 179 additions & 0 deletions src/js/components/Login/ResetYourPassword.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
import { Modal } from '@mui/material';
import Button from '@mui/material/Button';
import Dialog from '@mui/material/Dialog';
import DialogContent from '@mui/material/DialogContent';
import DialogContentText from '@mui/material/DialogContentText';
import DialogTitle from '@mui/material/DialogTitle';
import TextField from '@mui/material/TextField';
import PropTypes from 'prop-types';
import * as React from 'react';
import { useEffect, useRef, useState } from 'react';
import validator from 'validator';
import { renderLog } from '../../common/utils/logging';
import { useConnectAppContext } from '../../contexts/ConnectAppContext';
import makeRequestParams from '../../react-query/makeRequestParams';
import { useLogoutMutation, usePersonRetrieveByEmailMutation, usePersonSaveForAuthMutation } from '../../react-query/mutations';
import weConnectQueryFn, { METHOD } from '../../react-query/WeConnectQuery';
import { ErrorMessage } from '../Style/sharedStyles';
import VerifySecretCodeModal from '../VerifySecretCodeModal';

const ResetYourPassword = ({ openDialog, closeDialog }) => {
renderLog('ResetYourPassword');
const { mutate: mutateRetrievePersonByEmail } = usePersonRetrieveByEmailMutation();
const { mutate: mutatePersonSaveForAuth } = usePersonSaveForAuthMutation();
const { mutate: mutateLogout } = useLogoutMutation();
const { getAppContextValue, setAppContextValue } = useConnectAppContext();

const [open, setOpen] = React.useState(openDialog);
const [displayEmailAddress, setDisplayEmailAddress] = useState(true);
const [warningLine, setWarningLine] = useState('');
const [errorMessage, setErrorMessage] = useState('');

const emailRef = useRef('');
const password1Ref = useRef('');
const password2Ref = useRef('');
const authPerson = useRef(undefined);

useEffect(() => {
setOpen(openDialog);
}, [openDialog]);

const secretCodeVerified = getAppContextValue('secretCodeVerifiedForReset') || false;
useEffect(() => {
if (secretCodeVerified === true) {
console.log('received new secretCodeVerifiedForReset', secretCodeVerified);
setDisplayEmailAddress(false);
emailRef.current = '';
password1Ref.current = '';
password2Ref.current = '';
}
}, [secretCodeVerified]);

const auth = getAppContextValue('authenticatedPerson');
useEffect(() => {
const authP = getAppContextValue('authenticatedPerson');
if (authP && open) {
console.log('received new authP', authP);
authPerson.current = authP;
console.log('authPerson.personId in Login useEffect [auth] id: ', authP.personId);
console.log('authPerson.personId in Login useEffect [auth] open: ', open);
weConnectQueryFn('send-email-code', { personId: authP.personId }, METHOD.POST)
.then(setAppContextValue('openVerifySecretCodeModalDialog', true));
}
// eslint would have us add getAppContextValue and setAppContextValue, which causes and endless loop
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [auth]);

const sendEmail = async () => {
const email = emailRef.current.value;
setAppContextValue('resetEmail', email);
console.log('email in sendEmail: ', emailRef.current.value);
if (!validator.isEmail(email)) {
setWarningLine('Please enter a valid email address.');
return;
}
setWarningLine('');
setAppContextValue('openVerifySecretCodeModalDialog', true);
// Logout so that the current sessionID will not be reused when resetting password for a potentially differnt staff member
await mutateLogout();
// This retrieve will set the 'authenticatedPerson' app context value, and bring back a new sessionID (without touching the cookie)
await mutateRetrievePersonByEmail({ emailPersonal: email });
};

const handleClose = () => {
setOpen(false);
closeDialog(false);
};

const changePassword = async () => {
const pass1 = password1Ref.current.value;
const pass2 = password2Ref.current.value;
const person = authPerson.current;

console.log('password in changePassword: ', pass1, pass2);
if (pass1 !== pass2) {
setErrorMessage('Your password entries do not match.');
} else {
setErrorMessage('');
// const person = getAppContextValue('authenticatedPerson');
await mutatePersonSaveForAuth(makeRequestParams({ id: person.id }, { password: pass1 }));
setAppContextValue('isAuthenticated', true);
console.log('ResetYourPassword changePassword pass1, pass2: ', pass1, pass2);
setAppContextValue('resetPassword', pass1);
handleClose();
}
};

console.log('ResetYourPassword incoming authPerson: ', authPerson);
return (
<>
<Modal
open={open}
// onClose={handleClose}
aria-labelledby="parent-modal-title"
aria-describedby="parent-modal-description"
>
<Dialog open={open} onClose={handleClose}>
<DialogTitle sx={{ width: '400px' }}>Reset your Password</DialogTitle>
<div id="warningLine" style={{ color: 'red', padding: '10px 0 0 30px', paddingBottom: '20px' }}>{warningLine}</div>
<DialogContent>
<DialogContentText sx={{ marginBottom: '15px' }}>
{displayEmailAddress ? 'Please enter your email address.' : 'Please enter your new password.'}
</DialogContentText>
<ErrorMessage>{errorMessage}</ErrorMessage>
{ displayEmailAddress ? (
<TextField
autoFocus
fullWidth
id="email"
inputRef={emailRef}
label="Email Address"
margin="dense"
name="email"
required
type="email"
variant="standard"
/>
) : (
<form>
<TextField
autoFocus
fullWidth
id="field1"
inputRef={password1Ref}
label="Password"
margin="dense"
name="password1"
required
type="password"
variant="standard"
/>
<TextField
fullWidth
id="field2"
inputRef={password2Ref}
label="Verify Password"
margin="dense"
name="password2"
required
type="password"
variant="standard"
/>
</form>
)}
<Button sx={{ float: 'right' }} onClick={displayEmailAddress ? sendEmail : changePassword}>
{displayEmailAddress ? 'Send reset email' : 'Save your new password'}
</Button>
</DialogContent>
</Dialog>
</Modal>
<VerifySecretCodeModal person={authPerson.current} />
</>
);
};
ResetYourPassword.propTypes = {
openDialog: PropTypes.func,
closeDialog: PropTypes.func,
};

export default ResetYourPassword;
8 changes: 4 additions & 4 deletions src/js/components/Navigation/HeaderBar.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import styled from 'styled-components';
import standardBoxShadow from '../../common/components/Style/standardBoxShadow';
import { hasIPhoneNotch } from '../../common/utils/cordovaUtils';
import { normalizedHrefPage } from '../../common/utils/hrefUtils';
import { renderLog } from '../../common/utils/logging';
import { authLog, renderLog } from '../../common/utils/logging';
import { useConnectAppContext } from '../../contexts/ConnectAppContext';
import { clearSignedInGlobals } from '../../contexts/contextFunctions';
import { viewerCanSeeOrDo } from '../../models/AuthModel';
Expand All @@ -22,7 +22,7 @@ import HeaderBarLogo from './HeaderBarLogo';
const HeaderBar = ({ hideTabs }) => {
renderLog('HeaderBar');
const navigate = useNavigate();
const { apiDataCache, getAppContextValue, setAppContextValue } = useConnectAppContext();
const { apiDataCache, getAppContextValue, setAppContextValue, getAppContextData } = useConnectAppContext();
const { viewerAccessRights } = apiDataCache;
const { mutate: mutateLogout } = useLogoutMutation();

Expand All @@ -34,7 +34,7 @@ const HeaderBar = ({ hideTabs }) => {
const isAuth = getAppContextValue('isAuthenticated');
useEffect(() => {
if (isAuth !== null) {
console.log('----------- isAuthenticated changed =', isAuth);
authLog('HeaderBar isAuthenticated changed =', isAuth);
setIsAuthenticated(isAuth);
}
}, [isAuth]);
Expand All @@ -43,7 +43,7 @@ const HeaderBar = ({ hideTabs }) => {
// I don't think we want to make the weConnectQueryFn call here since we are about to call mutateLogout
const data = await weConnectQueryFn('logout', {}, METHOD.POST);
console.log(`/logout response in HeaderBar -- status: '${'status'}', data: ${JSON.stringify(data)}`);
clearSignedInGlobals(setAppContextValue);
clearSignedInGlobals(setAppContextValue, getAppContextData);
navigate('/login');
mutateLogout();
};
Expand Down
6 changes: 1 addition & 5 deletions src/js/components/Person/PersonSummaryRow.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,7 @@ import styled from 'styled-components';
import DesignTokenColors from '../../common/components/Style/DesignTokenColors';
import { renderLog } from '../../common/utils/logging';
import { useConnectAppContext } from '../../contexts/ConnectAppContext';
import {
getFullNamePreferredPerson,
// useGetFullNamePreferred,
} from '../../models/PersonModel';
import { getFullNamePreferredPerson } from '../../models/PersonModel';
import { useRemoveTeamMemberMutation } from '../../react-query/mutations';
import { DeleteStyled, EditStyled } from '../Style/iconStyles';
import { viewerCanSeeOrDo, viewerCanSeeOrDoForThisTeam } from '../../models/AuthModel';
Expand Down Expand Up @@ -117,7 +114,6 @@ const PersonSummaryRow = ({ person, rowNumberForDisplay, teamId }) => {
id={`removeMember-personId-${person.personId}`}
onClick={() => removeTeamMemberClick(person)}
style={{ cursor: 'pointer' }}
// cellwidth="20"
cellwidth={20}
>
<DeleteStyled />
Expand Down
9 changes: 4 additions & 5 deletions src/js/components/PrivateRoute.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,25 +7,24 @@ import { captureAccessRightsData } from '../models/AuthModel';

const PrivateRoute = () => {
const location = useLocation();
const { apiDataCache, getAppContextValue } = useConnectAppContext();
const { apiDataCache, getAppContextValue, setAppContextValue } = useConnectAppContext();
const dispatch = useConnectDispatch();

const [isAuthenticated, setIsAuthenticated] = useState(null);

const { data: dataAuth, isSuccess: isSuccessAuth } = useFetchData(['get-auth'], {}, METHOD.POST);
useEffect(() => {
if (isSuccessAuth) {
// console.log('useFetchData in PrivateRoute useEffect dataAuth good:', dataAuth, isSuccessAuth);
authLog('useFetchData in PrivateRoute useEffect dataAuth good:', dataAuth, isSuccessAuth);
setIsAuthenticated(dataAuth.isAuthenticated);
// setAppContextValue('loggedInPersonIsAdmin', dataAuth.loggedInPersonIsAdmin);
setAppContextValue('loggedInPersonIsAdmin', dataAuth.loggedInPersonIsAdmin);
captureAccessRightsData(dataAuth, isSuccessAuth, apiDataCache, dispatch);
authLog('========= PrivateRoute =========== INNER isAuthenticated: ', dataAuth.isAuthenticated);
}
}, [dataAuth, isSuccessAuth]);

const isAuth = getAppContextValue('isAuthenticated');

authLog('========= PrivateRoute =========== OUTER isAuthenticated: ', isAuthenticated, ', isAuth: ', isAuth);
authLog('========= PrivateRoute =========== isAuthenticated: ', isAuthenticated);

if (isAuthenticated || isAuth || isAuthenticated !== false) {
return <Outlet />;
Expand Down
7 changes: 7 additions & 0 deletions src/js/components/Style/sharedStyles.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,10 @@ export const MatchingPerson = styled('div')`
margin: 10px 0 0 10px;
font-style: italic;
`;

export const ErrorMessage = styled('div')`
color: red;
margin-top: 50px;
text-align: center;
font-size: 18px;
`;
20 changes: 9 additions & 11 deletions src/js/components/Team/TeamHeader.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ const TeamHeader = ({ showHeaderLabels, showIcons, team }) => {
return (
<OneTeamHeader>
{/* Width (below) of this TeamHeaderCell comes from the combined widths of the first x columns in TeamMemberList */}
<TeamHeaderCell cellwidth={215} largefont="true" titlecell="true">
<TeamHeaderCell cellwidth={215} $largefont $titlecell>
{teamLocal && (
<Link to={`/team-home/${teamLocal.id}`}>
{teamLocal.teamName}
Expand Down Expand Up @@ -85,19 +85,17 @@ const OneTeamHeader = styled('div')`
margin-top: 10px;
`;

const TeamHeaderCell = styled('div', {
shouldForwardProp: (prop) => !['largefont', 'titlecell', 'cellwidth'].includes(prop),
})(({ largefont, titlecell, cellwidth }) => (`
const TeamHeaderCell = styled.div`
align-content: center;
${(titlecell) ? '' : 'border-bottom: 1px solid #ccc;'}
${(largefont) ? 'font-size: 1.1em;' : 'font-size: .8em;'}
${(titlecell) ? '' : 'font-weight: 550;'}
border-bottom: ${(props) => (props?.$titleCell ? ';' : '1px solid #ccc;')}
font-size: ${(props) => (props?.$largefont ? '1.1em;' : '.8em;')};
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would prefer to only pass in the property names if they have a value, like this:
${(props) => (props?.$titleCell ? '' : 'border-bottom: 1px solid #ccc;')}
${(props) => (props?.$titleCell ? '' : 'font-weight: 550;')}

One can never be sure that Firefox or Opera will treat properties defined like this as "don't do anything", so I'd rather not send this css to the DOM:
border-bottom: ;
font-weight: ;

font-weight: ${(props) => (props?.$titleCell ? ';' : '550;')}
height: 22px;
${cellwidth ? `max-width: ${cellwidth}px;` : ''}
${cellwidth ? `min-width: ${cellwidth}px;` : ''}
max-width: ${(props) => (props.cellwidth ? `${props.cellwidth}px;` : ';')};
min-width: ${(props) => (props.cellwidth ? `${props.cellwidth}px;` : ';')};
width: ${(props) => (props.cellwidth ? `${props.cellwidth}px;` : ';')};
overflow: hidden;
white-space: nowrap;
${cellwidth ? `width: ${cellwidth}px;` : ''}
`));
`;

export default withStyles(styles)(TeamHeader);
Loading
Loading