From 62d13729fbeb58b8a22a7c480362e9657d09f852 Mon Sep 17 00:00:00 2001 From: Nate Kelley Date: Mon, 21 Apr 2025 14:27:20 -0600 Subject: [PATCH] password validation check --- .../components/features/auth/LoginForm.tsx | 67 ++++----- .../features/auth/PolicyCheck.stories.tsx | 70 --------- .../components/features/auth/PolicyCheck.tsx | 133 +++++++----------- .../features/auth/ResetPasswordForm.tsx | 27 ++-- 4 files changed, 101 insertions(+), 196 deletions(-) delete mode 100644 web/src/components/features/auth/PolicyCheck.stories.tsx diff --git a/web/src/components/features/auth/LoginForm.tsx b/web/src/components/features/auth/LoginForm.tsx index d01e88e70..72808bffa 100644 --- a/web/src/components/features/auth/LoginForm.tsx +++ b/web/src/components/features/auth/LoginForm.tsx @@ -287,26 +287,32 @@ const LoginOptions: React.FC<{ autoComplete="new-password" tabIndex={5} /> - {signUpFlow && ( -
- -
- )} {signUpFlow && ( - { - setPassword2(v.target.value); - }} - disabled={!!loading} - id="password2" - type="password" - name="password2" - placeholder="Confirm password" - autoComplete="new-password" - tabIndex={6} - /> + <> + { + setPassword2(v.target.value); + }} + disabled={!!loading} + id="password2" + type="password" + name="password2" + placeholder="Confirm password" + autoComplete="new-password" + tabIndex={6} + /> + + {password && ( + + )} + )}
@@ -315,21 +321,16 @@ const LoginOptions: React.FC<{ ))}
- - - +
diff --git a/web/src/components/features/auth/PolicyCheck.stories.tsx b/web/src/components/features/auth/PolicyCheck.stories.tsx deleted file mode 100644 index 4676ee21c..000000000 --- a/web/src/components/features/auth/PolicyCheck.stories.tsx +++ /dev/null @@ -1,70 +0,0 @@ -import type { Meta, StoryObj } from '@storybook/react'; -import { PolicyCheck } from './PolicyCheck'; -import { fn } from '@storybook/test'; - -const meta = { - title: 'Features/Auth/PolicyCheck', - component: PolicyCheck, - parameters: { - layout: 'centered' - }, - tags: ['autodocs'], - argTypes: { - password: { control: 'text' }, - show: { control: 'boolean' }, - placement: { - control: 'select', - options: ['top', 'right', 'bottom', 'left'] - }, - onCheckChange: { action: 'onCheckChange' } - } -} satisfies Meta; - -export default meta; -type Story = StoryObj; - -export const Default: Story = { - args: { - password: '', - show: true, - placement: 'left', - onCheckChange: fn() - } -}; - -export const ValidPassword: Story = { - args: { - password: 'Test123!@#', - show: true, - placement: 'left', - onCheckChange: fn() - } -}; - -export const InvalidPassword: Story = { - args: { - password: 'weak', - show: true, - placement: 'left', - onCheckChange: fn() - } -}; - -export const DifferentPlacement: Story = { - args: { - password: 'Test123!@#', - show: true, - placement: 'right', - onCheckChange: fn() - } -}; - -export const WithCustomChildren: Story = { - args: { - password: 'Test123!@#', - show: true, - placement: 'left', - onCheckChange: fn(), - children: Custom trigger element - } -}; diff --git a/web/src/components/features/auth/PolicyCheck.tsx b/web/src/components/features/auth/PolicyCheck.tsx index 86ebfb26a..4511265b5 100644 --- a/web/src/components/features/auth/PolicyCheck.tsx +++ b/web/src/components/features/auth/PolicyCheck.tsx @@ -3,14 +3,34 @@ import React, { useEffect, useMemo } from 'react'; import { Text } from '@/components/ui/typography'; import { Popover, PopoverProps } from '@/components/ui/popover/Popover'; import { Button } from '@/components/ui/buttons/Button'; +import { validate } from 'email-validator'; + +const PasswordCheckItem: React.FC<{ + passwordGood: boolean; + text: string; +}> = ({ passwordGood, text }) => { + return ( +
+ {passwordGood ? ( +
+ +
+ ) : ( +
+ +
+ )} + {text} +
+ ); +}; export const PolicyCheck: React.FC<{ + email: string; password: string; - show: boolean; - onCheckChange?: (value: boolean) => void; - children?: React.ReactNode; - placement?: 'top' | 'right' | 'bottom' | 'left'; -}> = ({ password, show, onCheckChange, children, placement = 'left' }) => { + password2: string | undefined; + onChangePolicyCheck: (passed: boolean) => void; +}> = ({ email, password, password2, onChangePolicyCheck }) => { const items = useMemo(() => { const containsNumber = /\d/; const containsSpecialChar = /[!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?]+/; @@ -32,6 +52,10 @@ export const PolicyCheck: React.FC<{ }; const items = [ + { + text: 'Email is valid', + check: validate(email) + }, { text: 'Contains a number', check: passwordGood.numberCheck @@ -51,90 +75,39 @@ export const PolicyCheck: React.FC<{ { text: 'Is at least 8 characters long', check: passwordGood.passwordLengthCheck + }, + { + text: 'Passwords match', + check: password === password2 || password2 === undefined } ]; return items; - }, [password]); + }, [password, email, password2]); - const allCompleted = useMemo(() => { - return items.every((item) => item.check); + const percentageCompleted = useMemo(() => { + const numberOfChecks = items.length; + const numberOfChecksCompleted = items.filter((item) => item.check).length; + return (numberOfChecksCompleted / numberOfChecks) * 100; }, [items]); useEffect(() => { - if (show && onCheckChange) { - onCheckChange(allCompleted); - } - }, [show, allCompleted, onCheckChange]); - - const PasswordCheck: React.FC<{ - passwordGood: boolean; - text: string; - }> = ({ passwordGood, text }) => { - return ( -
- {passwordGood ? ( -
- -
- ) : ( -
- -
- )} - {text} -
- ); - }; - - const sideMemo: PopoverProps['side'] = useMemo(() => { - switch (placement) { - case 'top': - return 'top'; - case 'right': - return 'right'; - case 'bottom': - return 'bottom'; - case 'left': - return 'left'; - } - }, [placement]); - - const alignMemo: PopoverProps['align'] = useMemo(() => { - switch (placement) { - case 'top': - return 'start'; - case 'right': - return 'end'; - case 'bottom': - return 'start'; - case 'left': - return 'end'; - } - }, [placement]); - - if (!show) return children; + onChangePolicyCheck(percentageCompleted === 100); + }, [percentageCompleted, onChangePolicyCheck]); return ( - - {items.map((item, index) => ( - - ))} -
- }> - {!children ? ( - - ) : ( - children - )} - +
+
+
+
+
+ {items.map((item, index) => ( + + ))} +
+
); }; diff --git a/web/src/components/features/auth/ResetPasswordForm.tsx b/web/src/components/features/auth/ResetPasswordForm.tsx index 370c8cdb8..bb82dba77 100644 --- a/web/src/components/features/auth/ResetPasswordForm.tsx +++ b/web/src/components/features/auth/ResetPasswordForm.tsx @@ -106,21 +106,22 @@ export const ResetPasswordForm: React.FC<{ /> { + password2={password2} + email={email || ''} + onChangePolicyCheck={(v) => { setGoodPassword(v); - }}> - - + }} + /> + +
)}