Skip to content

Commit

Permalink
feat: Surface password requirements on signup / dynamic validation (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
sumtzehern authored Jul 31, 2024
1 parent 02f7df7 commit 104d66d
Show file tree
Hide file tree
Showing 2 changed files with 61 additions and 1 deletion.
39 changes: 39 additions & 0 deletions frontend/web/components/PasswordRequirements.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import React, { useEffect } from 'react';
import PropTypes from 'prop-types';
import { close, checkmark } from 'ionicons/icons'
import { IonIcon } from '@ionic/react'

const PasswordRequirements = ({ password, onRequirementsMet}) => {
const requirements = [
{ label: 'At least 8 characters', test: password.length >= 8 },
{ label: 'Contains a number', test: /\d/.test(password) },
{ label: 'Contains a special character', test: /[!@#$%^&*(),.?":{}|<>[\]\\\/_+=-]/.test(password) },
{ label: 'Contains an uppercase letter', test: /[A-Z]/.test(password) },
{ label: 'Contains a lowercase letter', test: /[a-z]/.test(password) },
];

const allRequirementsMet = requirements.every(req => req.test);

useEffect(() => {
onRequirementsMet(allRequirementsMet);
}, [allRequirementsMet, onRequirementsMet]);

return (
<div>
<ul className="password-requirements" style={{ listStyleType: 'none', padding: 0 }}>
{requirements.map((req, index) => (
<p key={index} style={{ color: req.test ? 'green' : 'red', fontSize: '12px', margin: '4px 0' }}>
<IonIcon style={{ marginRight: '4px', verticalAlign: 'middle'}} icon={req.test ? checkmark : close} />{req.label}
</p>
))}
</ul>
</div>
);
};

PasswordRequirements.propTypes = {
password: PropTypes.string.isRequired,
onRequirementsMet: PropTypes.func.isRequired,
};

export default PasswordRequirements;
23 changes: 22 additions & 1 deletion frontend/web/components/pages/HomePage.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import ConfigProvider from 'common/providers/ConfigProvider'
import Constants from 'common/constants'
import ErrorMessage from 'components/ErrorMessage'
import Button from 'components/base/forms/Button'
import PasswordRequirements from 'components/PasswordRequirements'
import { informationCircleOutline } from 'ionicons/icons'
import { IonIcon } from '@ionic/react'
import classNames from 'classnames'
Expand All @@ -32,8 +33,16 @@ const HomePage = class extends React.Component {
// can handle always setting the marketing consent.
API.setCookie('marketing_consent_given', 'true')
this.state = {
email: '',
first_name: '',
last_name: '',
password: '',
marketing_consent_given: true,
allRequirementsMet: false,
}

this.handlePasswordChange = this.handlePasswordChange.bind(this);
this.handleRequirementsMet = this.handleRequirementsMet.bind(this);
}

addAlbacross() {
Expand Down Expand Up @@ -131,6 +140,14 @@ const HomePage = class extends React.Component {
}
}

handlePasswordChange(e) {
this.setState({ password: e.target.value });
}

handleRequirementsMet(allRequirementsMet) {
this.setState({ allRequirementsMet });
}

showForgotPassword = (e) => {
e.preventDefault()
openModal(
Expand Down Expand Up @@ -607,11 +624,15 @@ const HomePage = class extends React.Component {
name='password'
id='password'
/>
<PasswordRequirements
password={this.state.password}
onRequirementsMet={this.handleRequirementsMet}
/>
<div className='form-cta'>
<Button
data-test='signup-btn'
name='signup-btn'
disabled={isLoading || isSaving}
disabled={isLoading || isSaving || !this.state.allRequirementsMet}
className='px-4 mt-3 full-width'
type='submit'
>
Expand Down

0 comments on commit 104d66d

Please sign in to comment.