Skip to content

Commit

Permalink
Add password requirements to login page (#103)
Browse files Browse the repository at this point in the history
closes #90 

Would appreciate some feedback on placement and styling - wanted to fit it into the `Label` element but thought it might be better after the form field
  • Loading branch information
jaggederest authored Nov 18, 2023
1 parent 36999ce commit 7eebda6
Show file tree
Hide file tree
Showing 2 changed files with 87 additions and 2 deletions.
19 changes: 17 additions & 2 deletions packages/app/src/AuthPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { useEffect } from 'react';
import Link from 'next/link';
import cx from 'classnames';

import { PasswordCheck, CheckOrX } from './PasswordCheck';
import LandingHeader from './LandingHeader';
import * as config from './config';
import api from './api';
Expand All @@ -24,6 +25,7 @@ export default function AuthPage({ action }: { action: 'register' | 'login' }) {
handleSubmit,
formState: { errors, isSubmitting },
setError,
watch,
} = useForm<FormData>({
reValidateMode: 'onSubmit',
});
Expand All @@ -45,6 +47,13 @@ export default function AuthPage({ action }: { action: 'register' | 'login' }) {
}
}, [installation, isRegister, router]);

const currentPassword = watch('password', '');
const confirmPassword = watch('confirmPassword', '');

const confirmPass = () => {
return currentPassword === confirmPassword;
};

const onSubmit: SubmitHandler<FormData> = data =>
registerPassword.mutate(
{
Expand Down Expand Up @@ -153,15 +162,21 @@ export default function AuthPage({ action }: { action: 'register' | 'login' }) {
htmlFor="confirmPassword"
className="text-start text-muted fs-7.5 mb-1"
>
Confirm Password
<CheckOrX
handler={confirmPass}
password={currentPassword}
>
Confirm Password
</CheckOrX>
</Form.Label>
<Form.Control
data-test-id="form-confirm-password"
id="confirmPassword"
type="password"
className="border-0"
className="border-0 mb-2"
{...form.confirmPassword}
/>
<PasswordCheck password={currentPassword} />
</>
)}
{isRegister && Object.keys(errors).length > 0 && (
Expand Down
70 changes: 70 additions & 0 deletions packages/app/src/PasswordCheck.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { useMemo } from 'react';

const checkLength = (password: string) => password.length >= 12;
const checkOneUpper = (password: string) => /[A-Z]+/.test(password);
const checkOneLower = (password: string) => /[a-z]+/.test(password);
const checkOneNumber = (password: string) => /\d+/.test(password);
const checkOneSpecial = (password: string) => /\W+/.test(password);

export const PasswordCheck = (opts: { password: string }) => {
const password = opts.password;
return (
<div>
<div>
<CheckOrX handler={checkLength} password={password}>
minimum 12 characters
</CheckOrX>
</div>
<div>
<CheckOrX handler={checkOneUpper} password={password}>
at least 1 uppercase
</CheckOrX>
</div>
<div>
<CheckOrX handler={checkOneLower} password={password}>
at least 1 lowercase
</CheckOrX>
</div>
<div>
<CheckOrX handler={checkOneNumber} password={password}>
at least 1 number
</CheckOrX>
</div>
<div>
<CheckOrX handler={checkOneSpecial} password={password}>
at least 1 special character
</CheckOrX>
</div>
</div>
);
};

export const CheckOrX = ({
handler,
password,
children,
}: {
handler: (password: string) => boolean;
password: string | { password: string | null };
children: React.ReactNode;
}) => {
let actualPassword = '';
if (typeof password === 'string') {
actualPassword = password;
} else {
actualPassword = password.password ?? '';
}
const isValid = useMemo(
() => handler(actualPassword),
[handler, actualPassword],
);
return (
<span className={isValid ? 'text-success' : 'text-danger'}>
{isValid ? <Check /> : <XShape />} {children}
</span>
);
};

const Check = () => <i className={'bi bi-check2'}></i>;

const XShape = () => <i className={'bi bi-x'}></i>;

0 comments on commit 7eebda6

Please sign in to comment.