diff --git a/src/components/Checkbox/Checkbox.tsx b/src/components/Checkbox/Checkbox.tsx
index ae423bd97..e4ddbfd1a 100644
--- a/src/components/Checkbox/Checkbox.tsx
+++ b/src/components/Checkbox/Checkbox.tsx
@@ -33,7 +33,7 @@ const Checkbox: React.FC = ({ label, name, onChange, header, checked, val
) : null}
-
+
{helperText ? {helperText}
: null}
diff --git a/src/components/Checkbox/__snapshots__/Checkbox.test.tsx.snap b/src/components/Checkbox/__snapshots__/Checkbox.test.tsx.snap
index 3f632029a..65a4973f6 100644
--- a/src/components/Checkbox/__snapshots__/Checkbox.test.tsx.snap
+++ b/src/components/Checkbox/__snapshots__/Checkbox.test.tsx.snap
@@ -17,6 +17,7 @@ exports[` renders and matches snapshot 1`] = `
diff --git a/src/components/EditPasswordForm/EditPasswordForm.module.scss b/src/components/EditPasswordForm/EditPasswordForm.module.scss
index 265c663f9..f4c647fcf 100644
--- a/src/components/EditPasswordForm/EditPasswordForm.module.scss
+++ b/src/components/EditPasswordForm/EditPasswordForm.module.scss
@@ -15,6 +15,8 @@
.button {
margin-bottom: 8px;
}
-.link {
+
+.link,
+.textField {
margin-bottom: 24px;
}
diff --git a/src/components/EditPasswordForm/EditPasswordForm.test.tsx b/src/components/EditPasswordForm/EditPasswordForm.test.tsx
index fbc7a10b6..e58474859 100644
--- a/src/components/EditPasswordForm/EditPasswordForm.test.tsx
+++ b/src/components/EditPasswordForm/EditPasswordForm.test.tsx
@@ -6,7 +6,7 @@ import EditPasswordForm from './EditPasswordForm';
describe('', () => {
test('renders and matches snapshot', () => {
const { container } = render(
- ,
+ ,
);
expect(container).toMatchSnapshot();
diff --git a/src/components/EditPasswordForm/EditPasswordForm.tsx b/src/components/EditPasswordForm/EditPasswordForm.tsx
index fc0f7394b..efdb43e93 100644
--- a/src/components/EditPasswordForm/EditPasswordForm.tsx
+++ b/src/components/EditPasswordForm/EditPasswordForm.tsx
@@ -18,13 +18,14 @@ import styles from './EditPasswordForm.module.scss';
type Props = {
onSubmit: React.FormEventHandler;
onChange: React.ChangeEventHandler;
+ onBlur: React.FocusEventHandler;
error?: string;
errors: FormErrors;
value: EditPasswordFormData;
submitting: boolean;
};
-const EditPasswordForm: React.FC = ({ onSubmit, onChange, value, errors, submitting }: Props) => {
+const EditPasswordForm: React.FC = ({ onSubmit, onChange, onBlur, value, errors, submitting }: Props) => {
const { t } = useTranslation('account');
const [viewPassword, toggleViewPassword] = useToggle();
@@ -33,12 +34,19 @@ const EditPasswordForm: React.FC = ({ onSubmit, onChange, value, errors,
{t('reset.password_reset')}
{errors.form ? {errors.form} : null}
+
+ {t('reset.password_helper_text')}
+
+ )}
name="password"
type={viewPassword ? 'text' : 'password'}
rightControl={
@@ -48,7 +56,6 @@ const EditPasswordForm: React.FC = ({ onSubmit, onChange, value, errors,
}
required
/>
-
{submitting && }
diff --git a/src/components/EditPasswordForm/__snapshots__/EditPasswordForm.test.tsx.snap b/src/components/EditPasswordForm/__snapshots__/EditPasswordForm.test.tsx.snap
index c397a5202..9080d8035 100644
--- a/src/components/EditPasswordForm/__snapshots__/EditPasswordForm.test.tsx.snap
+++ b/src/components/EditPasswordForm/__snapshots__/EditPasswordForm.test.tsx.snap
@@ -12,13 +12,13 @@ exports[` renders and matches snapshot 1`] = `
reset.password_reset
renders and matches snapshot 1`] = `
-
-
-
+ reset.password_helper_text
-
- registration.password_strength
-
+
+ login.not_registered
+
+
+ login.sign_up
+
+
`;
diff --git a/src/components/PasswordStrength/PasswordStrength.module.scss b/src/components/PasswordStrength/PasswordStrength.module.scss
index 0deec91e9..4de7fa81d 100644
--- a/src/components/PasswordStrength/PasswordStrength.module.scss
+++ b/src/components/PasswordStrength/PasswordStrength.module.scss
@@ -1,9 +1,30 @@
@use '../../styles/variables';
@use '../../styles/theme';
+@mixin strength($strength, $width, $color) {
+ &[data-strength="#{$strength}"] {
+ .passwordStrengthFill {
+ width: $width;
+ background: $color;
+ }
+
+ .label {
+ color: $color;
+ }
+ }
+}
+
.passwordStrength {
+ @include strength(1, 25%, orangered);
+ @include strength(2, 50%, orange);
+ @include strength(3, 75%, yellowgreen);
+ @include strength(4, 100%, green);
+
position: relative;
- margin: variables.$base-spacing 0;
+ display: flex;
+ align-items: center;
+ height: 16px;
+ margin: 8px 0;
font-size: 14px;
}
@@ -11,7 +32,7 @@
position: relative;
width: 170px;
height: 6px;
- margin: variables.$base-spacing 0;
+ margin-right: 8px;
background: #ddd;
border-radius: 5px;
@@ -20,28 +41,13 @@
.passwordStrengthFill {
position: absolute;
width: 0;
- height: inherit;
+ height: 100%;
background: transparent;
border-radius: inherit;
transition: width 0.5s ease-in-out, background 0.25s;
}
-.passwordStrengthFill[data-strength='1'] {
- width: 25%;
- background: orangered;
-}
-
-.passwordStrengthFill[data-strength='2'] {
- width: 50%;
- background: orange;
-}
-
-.passwordStrengthFill[data-strength='3'] {
- width: 75%;
- background: yellowgreen;
-}
-
-.passwordStrengthFill[data-strength='4'] {
- width: 100%;
- background: green;
+.label {
+ font-weight: theme.$body-font-weight-bold;
+ font-size: 14px;
}
diff --git a/src/components/PasswordStrength/PasswordStrength.tsx b/src/components/PasswordStrength/PasswordStrength.tsx
index e6b1411ef..0ec0237b3 100644
--- a/src/components/PasswordStrength/PasswordStrength.tsx
+++ b/src/components/PasswordStrength/PasswordStrength.tsx
@@ -7,32 +7,51 @@ type Props = {
password: string;
};
+const PASSWORD_REGEX = /^(?=.*[a-z])(?=.*[0-9]).{8,}$/;
+
const PasswordStrength: React.FC = ({ password }: Props) => {
const { t } = useTranslation('account');
+
const passwordStrength = (password: string) => {
let strength = 0;
- if (password.match(/[a-z]+/)) {
+ if (!password.match(PASSWORD_REGEX)) return strength;
+
+ if (password.match(/[A-Z]+/)) {
strength += 1;
}
- if (password.match(/[A-Z]+/)) {
+
+ if (password.match(/(\d.*\d)/)) {
strength += 1;
}
- if (password.match(/[0-9|!@#$%^&*()_+-=]+/)) {
+
+ if (password.match(/[!,@#$%^&*?_~]/)) {
strength += 1;
}
- if (password.length >= 6) {
+
+ if (password.match(/([!,@#$%^&*?_~].*[!,@#$%^&*?_~])/)) {
strength += 1;
}
return strength;
};
+ const strength = passwordStrength(password);
+ const labels = [
+ t('registration.password_strength.invalid'),
+ t('registration.password_strength.weak'),
+ t('registration.password_strength.fair'),
+ t('registration.password_strength.strong'),
+ t('registration.password_strength.very_strong'),
+ ];
+
+ if (!strength) return null;
+
return (
-
+
-
{t('registration.password_strength')}
+
+
{' '}
+
{labels[strength]}
);
};
diff --git a/src/components/PasswordStrength/__snapshots__/PasswordStrength.test.tsx.snap b/src/components/PasswordStrength/__snapshots__/PasswordStrength.test.tsx.snap
index 4370f4a3b..2f312e3a7 100644
--- a/src/components/PasswordStrength/__snapshots__/PasswordStrength.test.tsx.snap
+++ b/src/components/PasswordStrength/__snapshots__/PasswordStrength.test.tsx.snap
@@ -4,17 +4,20 @@ exports[` renders and matches snapshot 1`] = `
-
- registration.password_strength
+
+
+ registration.password_strength.fair
diff --git a/src/components/RegistrationForm/RegistrationForm.test.tsx b/src/components/RegistrationForm/RegistrationForm.test.tsx
index 5cf844892..c0b99cd13 100644
--- a/src/components/RegistrationForm/RegistrationForm.test.tsx
+++ b/src/components/RegistrationForm/RegistrationForm.test.tsx
@@ -1,5 +1,6 @@
import React from 'react';
-import { render } from '@testing-library/react';
+
+import { render } from '../../testUtils';
import RegistrationForm from './RegistrationForm';
@@ -9,12 +10,14 @@ describe('', () => {
,
);
diff --git a/src/components/RegistrationForm/RegistrationForm.tsx b/src/components/RegistrationForm/RegistrationForm.tsx
index cc8a2c6b3..45019c9ce 100644
--- a/src/components/RegistrationForm/RegistrationForm.tsx
+++ b/src/components/RegistrationForm/RegistrationForm.tsx
@@ -15,12 +15,14 @@ import PasswordStrength from '../PasswordStrength/PasswordStrength';
import Checkbox from '../Checkbox/Checkbox';
import FormFeedback from '../FormFeedback/FormFeedback';
import LoadingOverlay from '../LoadingOverlay/LoadingOverlay';
+import Link from '../Link/Link';
import styles from './RegistrationForm.module.scss';
type Props = {
onSubmit: React.FormEventHandler;
onChange: React.ChangeEventHandler;
+ onBlur: React.FocusEventHandler;
onConsentChange: React.ChangeEventHandler;
errors: FormErrors;
values: RegistrationFormData;
@@ -28,16 +30,19 @@ type Props = {
consentValues: Record;
consentErrors: string[];
submitting: boolean;
+ canSubmit: boolean;
publisherConsents?: Consent[];
};
const RegistrationForm: React.FC = ({
onSubmit,
onChange,
+ onBlur,
values,
errors,
submitting,
loading,
+ canSubmit,
publisherConsents,
consentValues,
onConsentChange,
@@ -48,10 +53,6 @@ const RegistrationForm: React.FC = ({
const { t } = useTranslation('account');
const history = useHistory();
- const loginButtonClickHandler = () => {
- history.push(addQueryParam(history, 'u', 'login'));
- };
-
const formatConsentLabel = (label: string): string | JSX.Element => {
// @todo sanitize consent label to prevent XSS
const hasHrefOpenTag = //.test(label);
@@ -79,6 +80,7 @@ const RegistrationForm: React.FC = ({
= ({
+
+ {t('registration.password_helper_text')}
+
+ )}
name="password"
type={viewPassword ? 'text' : 'password'}
rightControl={
@@ -106,13 +114,13 @@ const RegistrationForm: React.FC = ({
}
required
/>
-
{publisherConsents?.map((consent, index) => (
= ({
variant="contained"
color="primary"
size="large"
- disabled={submitting}
+ disabled={submitting || !canSubmit}
fullWidth
/>
-
- {t('registration.already_account')}
-
- {t('login.sign_in')}
-
-
+
+ {t('registration.already_account')} {t('login.sign_in')}
+
{submitting && }
);
diff --git a/src/components/RegistrationForm/__snapshots__/RegistrationForm.test.tsx.snap b/src/components/RegistrationForm/__snapshots__/RegistrationForm.test.tsx.snap
index c8b35f4d8..420ea94e9 100644
--- a/src/components/RegistrationForm/__snapshots__/RegistrationForm.test.tsx.snap
+++ b/src/components/RegistrationForm/__snapshots__/RegistrationForm.test.tsx.snap
@@ -80,21 +80,11 @@ exports[` renders and matches snapshot 1`] = `
-
-
-
+ registration.password_helper_text
-
- registration.password_strength
-
renders and matches snapshot 1`] = `
registration.continue
-
-
- registration.already_account
-
-
login.sign_in
-
-
+
+
`;
diff --git a/src/components/TextField/TextField.tsx b/src/components/TextField/TextField.tsx
index d031abddf..991d11c8a 100644
--- a/src/components/TextField/TextField.tsx
+++ b/src/components/TextField/TextField.tsx
@@ -14,7 +14,8 @@ type Props = {
value: string;
type?: 'text' | 'email' | 'password' | 'search' | 'number' | 'date';
onChange?: React.ChangeEventHandler;
- onFocus?: React.ChangeEventHandler;
+ onFocus?: React.FocusEventHandler;
+ onBlur?: React.FocusEventHandler;
helperText?: React.ReactNode;
leftControl?: React.ReactNode;
rightControl?: React.ReactNode;
diff --git a/src/containers/AccountModal/forms/EditPassword.tsx b/src/containers/AccountModal/forms/EditPassword.tsx
index ae2c25b00..8df3d73bf 100644
--- a/src/containers/AccountModal/forms/EditPassword.tsx
+++ b/src/containers/AccountModal/forms/EditPassword.tsx
@@ -17,9 +17,13 @@ const ResetPassword: React.FC = () => {
const emailParam = useQueryParam('email');
const passwordSubmitHandler: UseFormOnSubmitHandler = async (formData, { setErrors, setSubmitting, setValue }) => {
- try {
- if (!resetPasswordTokenParam || !emailParam) throw new Error('invalid reset link');
+ if (!emailParam || !resetPasswordTokenParam) {
+ setErrors({ form: t('reset.invalid_link') });
+
+ return setSubmitting(false);
+ }
+ try {
await changePassword(emailParam, formData.password, resetPasswordTokenParam);
history.push(addQueryParam(history, 'u', 'login'));
} catch (error: unknown) {
@@ -28,8 +32,6 @@ const ResetPassword: React.FC = () => {
setErrors({ password: t('reset.invalid_password') });
} else if (error.message.includes('resetPasswordToken is not valid')) {
setErrors({ password: t('reset.invalid_token') });
- } else if (error.message.includes('invalid reset link')) {
- setErrors({ password: t('reset.invalid_link') });
}
setValue('password', '');
@@ -42,20 +44,22 @@ const ResetPassword: React.FC = () => {
{ password: '' },
passwordSubmitHandler,
object().shape({
- password: string().required(t('login.field_required')),
+ password: string()
+ .matches(/^(?=.*[a-z])(?=.*[0-9]).{8,}$/, t('registration.invalid_password'))
+ .required(t('login.field_required')),
}),
+ true,
);
return (
-
-
-
+
);
};
diff --git a/src/containers/AccountModal/forms/Login.tsx b/src/containers/AccountModal/forms/Login.tsx
index 20862e855..c559823e6 100644
--- a/src/containers/AccountModal/forms/Login.tsx
+++ b/src/containers/AccountModal/forms/Login.tsx
@@ -8,8 +8,10 @@ import LoginForm from '../../../components/LoginForm/LoginForm';
import { login } from '../../../stores/AccountStore';
import useForm, { UseFormOnSubmitHandler } from '../../../hooks/useForm';
import { removeQueryParam } from '../../../utils/history';
+import { ConfigStore } from '../../../stores/ConfigStore';
const Login = () => {
+ const { siteName } = ConfigStore.useState((s) => s.config);
const history = useHistory();
const { t } = useTranslation('account');
const loginSubmitHandler: UseFormOnSubmitHandler = async (formData, { setErrors, setSubmitting, setValue }) => {
@@ -39,7 +41,7 @@ const Login = () => {
const initialValues: LoginFormData = { email: '', password: '' };
const { handleSubmit, handleChange, values, errors, submitting } = useForm(initialValues, loginSubmitHandler, validationSchema);
- return ;
+ return ;
};
export default Login;
diff --git a/src/containers/AccountModal/forms/Registration.tsx b/src/containers/AccountModal/forms/Registration.tsx
index 592567e4f..36dc2cb7e 100644
--- a/src/containers/AccountModal/forms/Registration.tsx
+++ b/src/containers/AccountModal/forms/Registration.tsx
@@ -73,16 +73,17 @@ const Registration = () => {
const validationSchema: SchemaOf = object().shape({
email: string().email(t('registration.field_is_not_valid_email')).required(t('registration.field_required')),
- password: string().required(t('registration.field_required')),
+ password: string().matches(/^(?=.*[a-z])(?=.*[0-9]).{8,}$/, t('registration.invalid_password')).required(t('registration.field_required')),
});
const initialRegistrationValues: RegistrationFormData = { email: '', password: '' };
- const { handleSubmit, handleChange, values, errors, submitting } = useForm(initialRegistrationValues, registrationSubmitHandler, validationSchema);
+ const { handleSubmit, handleChange, handleBlur, values, errors, submitting } = useForm(initialRegistrationValues, registrationSubmitHandler, validationSchema, true);
return (
{
publisherConsents={publisherConsents}
loading={publisherConsentsLoading}
onConsentChange={handleChangeConsent}
+ canSubmit={!!values.email && !!values.password}
/>
);
};
diff --git a/src/containers/AccountModal/forms/ResetPassword.tsx b/src/containers/AccountModal/forms/ResetPassword.tsx
index 536dd3cfd..3246613ba 100644
--- a/src/containers/AccountModal/forms/ResetPassword.tsx
+++ b/src/containers/AccountModal/forms/ResetPassword.tsx
@@ -20,7 +20,7 @@ const ResetPassword: React.FC = ({ type }: Prop) => {
const { t } = useTranslation('account');
const history = useHistory();
const user = AccountStore.useState((state) => state.user);
- const [resetPasswordSubmtting, setResetPasswordSubmitting] = useState(false);
+ const [resetPasswordSubmitting, setResetPasswordSubmitting] = useState(false);
const cancelClickHandler = () => {
history.push(removeQueryParam(history, 'u'));
@@ -31,7 +31,7 @@ const ResetPassword: React.FC = ({ type }: Prop) => {
};
const resetPasswordClickHandler = async () => {
- const resetUrl = `${window.location.origin}/u/my-account?u=edit-password`;
+ const resetUrl = `${window.location.origin}/?u=edit-password`;
try {
if (!user?.email) throw new Error('invalid param email');
@@ -51,7 +51,7 @@ const ResetPassword: React.FC = ({ type }: Prop) => {
};
const emailSubmitHandler: UseFormOnSubmitHandler = async (formData, { setErrors, setSubmitting }) => {
- const resetUrl = `${window.location.origin}/u/my-account?u=edit-password`;
+ const resetUrl = `${window.location.origin}/?u=edit-password`;
try {
await resetPassword(formData.email, resetUrl);
@@ -72,12 +72,13 @@ const ResetPassword: React.FC = ({ type }: Prop) => {
object().shape({
email: string().email(t('login.field_is_not_valid_email')).required(t('login.field_required')),
}),
+ true,
);
return (
{type === 'reset' && (
-
+
)}
{type === 'forgot' && (
= ({ type }: Prop) => {
/>
)}
{type === 'confirmation' && }
- {(emailForm.submitting || resetPasswordSubmtting) && }
+ {(emailForm.submitting || resetPasswordSubmitting) && }
);
};
diff --git a/src/containers/Cinema/Cinema.tsx b/src/containers/Cinema/Cinema.tsx
index 8dc66de7f..59a7ce8a5 100644
--- a/src/containers/Cinema/Cinema.tsx
+++ b/src/containers/Cinema/Cinema.tsx
@@ -11,6 +11,7 @@ import { watchHistoryStore, useWatchHistory } from '../../stores/WatchHistorySto
import { ConfigContext } from '../../providers/ConfigProvider';
import { addScript } from '../../utils/dom';
import useOttAnalytics from '../../hooks/useOttAnalytics';
+import { deepCopy } from '../../utils/collection';
import styles from './Cinema.module.scss';
@@ -64,15 +65,7 @@ const Cinema: React.FC = ({ item, onPlay, onPause, onComplete, onUserActi
}
// load new item
- playerRef.current.load([
- {
- mediaid: item.mediaid,
- image: item.image,
- title: item.title,
- description: item.description,
- sources: item.sources.map((source) => ({ ...source })),
- },
- ]);
+ playerRef.current.load([deepCopy(item)]);
};
const initializePlayer = () => {
@@ -85,7 +78,7 @@ const Cinema: React.FC = ({ item, onPlay, onPause, onComplete, onUserActi
playerRef.current = window.jwplayer(playerElementRef.current);
playerRef.current.setup({
- playlist: [item],
+ playlist: [deepCopy(item)],
aspect: false,
width: '100%',
height: '100%',
diff --git a/src/hooks/useForm.ts b/src/hooks/useForm.ts
index 1713912cd..46cefb862 100644
--- a/src/hooks/useForm.ts
+++ b/src/hooks/useForm.ts
@@ -1,5 +1,5 @@
import { useState } from 'react';
-import type { FormErrors, GenericFormValues, UseFormChangeHandler, UseFormSubmitHandler } from 'types/form';
+import type { FormErrors, GenericFormValues, UseFormChangeHandler, UseFormBlurHandler, UseFormSubmitHandler } from 'types/form';
import { ValidationError, AnySchema } from 'yup';
export type UseFormReturnValue = {
@@ -7,6 +7,7 @@ export type UseFormReturnValue = {
errors: FormErrors;
submitting: boolean;
handleChange: UseFormChangeHandler;
+ handleBlur: UseFormBlurHandler;
handleSubmit: UseFormSubmitHandler;
setValue: (key: keyof T, value: string) => void;
setErrors: (errors: FormErrors) => void;
@@ -26,19 +27,53 @@ export default function useForm(
initialValues: T,
onSubmit: UseFormOnSubmitHandler,
validationSchema?: AnySchema,
+ validateOnBlur: boolean = false,
): UseFormReturnValue {
+ const [touched, setTouched] = useState>(
+ Object.fromEntries((Object.keys(initialValues) as Array).map((key) => [key, false])) as Record,
+ );
const [values, setValues] = useState(initialValues);
const [submitting, setSubmitting] = useState(false);
const [errors, setErrors] = useState>({});
+ const validateField = (name: string, formValues: T) => {
+ if (!validationSchema) return;
+
+ try {
+ validationSchema.validateSyncAt(name, formValues);
+
+ // clear error
+ setErrors((errors) => ({ ...errors, [name]: null }));
+ } catch (error: unknown) {
+ if (error instanceof ValidationError) {
+ const errorMessage = error.errors[0];
+ setErrors((errors) => ({ ...errors, [name]: errorMessage }));
+ }
+ }
+ };
+
const setValue = (name: keyof T, value: string | boolean) => {
setValues((current) => ({ ...current, [name]: value }));
};
const handleChange: UseFormChangeHandler = (event) => {
+ const name = event.target.name;
const value = event.target instanceof HTMLInputElement && event.target.type === 'checkbox' ? event.target.checked : event.target.value;
- setValues((current) => ({ ...current, [event.target.name]: value }));
+ const newValues = { ...values, [name]: value };
+
+ setValues(newValues);
+ setTouched(current => ({ ...current, [name]: value }));
+
+ if (errors[name]) {
+ validateField(name, newValues)
+ }
+ };
+
+ const handleBlur: UseFormBlurHandler = (event) => {
+ if (!validateOnBlur || !touched[event.target.name]) return;
+
+ validateField(event.target.name, values);
};
const validate = (validationSchema: AnySchema) => {
@@ -85,5 +120,5 @@ export default function useForm(
onSubmit(values, { setValue, setErrors, setSubmitting, validate });
};
- return { values, errors, handleChange, handleSubmit, submitting, setValue, setErrors, setSubmitting };
+ return { values, errors, handleChange, handleBlur, handleSubmit, submitting, setValue, setErrors, setSubmitting };
}
diff --git a/src/i18n/locales/en_US/account.json b/src/i18n/locales/en_US/account.json
index d50f67074..05fa538af 100644
--- a/src/i18n/locales/en_US/account.json
+++ b/src/i18n/locales/en_US/account.json
@@ -57,8 +57,10 @@
"field_required": "This field is required",
"forgot_password": "Forgot password?",
"hide_password": "Hide password",
+ "not_registered": "New to {{siteName}}?",
"password": "Password",
"sign_in": "Sign in",
+ "sign_up": "Sign up",
"view_password": "View password",
"wrong_combination": "Incorrect email/password combination",
"wrong_email": "Please check your email and try again."
@@ -97,9 +99,16 @@
"field_is_not_valid_email": "Please re-enter your email details",
"field_required": "This field is required",
"hide_password": "Hide password",
- "invalid_password": "Use a minimum of 6 characters (case sensitive) with at least one number or special character and one capital character",
+ "invalid_password": "Use a minimum of 8 characters (case sensitive) with at least one number",
"password": "Password",
- "password_strength": "Use a minimum of 6 characters (case sensitive) with at least one number or special character and one capital character",
+ "password_helper_text": "Use a minimum of 8 characters (case sensitive) with at least one number",
+ "password_strength": {
+ "fair": "Fair",
+ "invalid": "",
+ "strong": "Strong",
+ "very_strong": "Very strong",
+ "weak": "Weak"
+ },
"sign_up": "Sign up",
"user_exists": "There is already a user with this email address",
"view_password": "View password",
@@ -126,9 +135,11 @@
"invalid_token": "Invalid link",
"link_sent": "Password link sent",
"link_sent_text": "Please check your inbox at {{email}}",
+ "new_password": "New password",
"no": "No, thanks",
"not_sure": "Not sure that was the right email address?",
"password": "Password",
+ "password_helper_text": "Use a minimum of 8 characters (case sensitive) with at least one number",
"password_reset": "Password reset",
"reset_password": "Edit Password",
"text": "If you want to edit your password, click 'YES, Reset' to receive password reset instruction on your mail",
diff --git a/src/i18n/locales/nl_NL/account.json b/src/i18n/locales/nl_NL/account.json
index 84f23c26b..2bf628e80 100644
--- a/src/i18n/locales/nl_NL/account.json
+++ b/src/i18n/locales/nl_NL/account.json
@@ -57,8 +57,10 @@
"field_required": "",
"forgot_password": "",
"hide_password": "",
+ "not_registered": "",
"password": "",
"sign_in": "",
+ "sign_up": "",
"view_password": "",
"wrong_combination": "",
"wrong_email": ""
@@ -95,7 +97,14 @@
"hide_password": "",
"invalid_password": "",
"password": "",
- "password_strength": "",
+ "password_helper_text": "",
+ "password_strength": {
+ "fair": "",
+ "invalid": "",
+ "strong": "",
+ "very_strong": "",
+ "weak": ""
+ },
"sign_up": "",
"user_exists": "",
"view_password": "",
@@ -122,9 +131,11 @@
"invalid_token": "",
"link_sent": "",
"link_sent_text": "",
+ "new_password": "",
"no": "",
"not_sure": "",
"password": "",
+ "password_helper_text": "",
"password_reset": "",
"reset_password": "",
"text": "",
diff --git a/src/utils/collection.ts b/src/utils/collection.ts
index 77eb13ba9..a2410d628 100644
--- a/src/utils/collection.ts
+++ b/src/utils/collection.ts
@@ -126,6 +126,13 @@ const checkConsentsFromValues = (publisherConsents: Consent[], consents: Record<
return { customerConsents, consentsErrors };
};
+const deepCopy = (obj: unknown) => {
+ if (Array.isArray(obj) || (typeof obj === 'object' && obj !== null)) {
+ return JSON.parse(JSON.stringify(obj));
+ }
+ return obj;
+};
+
export {
getFiltersFromConfig,
getFiltersFromSeries,
@@ -138,4 +145,5 @@ export {
formatConsentsFromValues,
extractConsentValues,
checkConsentsFromValues,
+ deepCopy,
};
diff --git a/types/form.d.ts b/types/form.d.ts
index 766b22176..b93f11f13 100644
--- a/types/form.d.ts
+++ b/types/form.d.ts
@@ -1,4 +1,5 @@
export type UseFormChangeHandler = React.ChangeEventHandler;
+export type UseFormBlurHandler = React.FocusEventHandler;
export type UseFormSubmitHandler = React.FormEventHandler;
export type GenericFormErrors = { form: string };