Skip to content

Commit

Permalink
Merge pull request #35 from SailingSteve/steveStaffClientFeb24-1215pm
Browse files Browse the repository at this point in the history
Added reset password, which required a lot of changes and refactors
  • Loading branch information
DaleMcGrew authored Feb 26, 2025
2 parents 3d9688d + e598d5a commit 14fb862
Show file tree
Hide file tree
Showing 18 changed files with 1,888 additions and 1,613 deletions.
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;')};
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

0 comments on commit 14fb862

Please sign in to comment.