Skip to content
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 21 additions & 42 deletions src/containers/Auth/Auth.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
import { checkOrgStatus } from 'services/AuthService';
import { TERMS_OF_USE_LINK } from 'common/constants';

import { Promotion } from './Promotion/Promotion';
// import { Promotion } from './Promotion/Promotion';

export interface AuthProps {
pageTitle: string;
Expand All @@ -36,8 +36,6 @@
linkURL?: string;
errorMessage?: string;
successMessage?: string;
loading?: boolean;
inlineSuccessMessage?: string;
}

export const Auth = ({
Expand All @@ -55,32 +53,26 @@
linkURL,
errorMessage,
successMessage,
loading: externalLoading,
inlineSuccessMessage,
}: AuthProps) => {
// handle visibility for the password field
const [showPassword, setShowPassword] = useState(false);
const [loading, setLoading] = useState(false);
const { t } = useTranslation();
const [orgName, setOrgName] = useState('Glific');
const [status, setStatus] = useState('');

const isLoading = externalLoading !== undefined ? externalLoading : loading;

useEffect(() => {
if (mode === 'trialregistration') {
return;
}

axios
.post(ORGANIZATION_NAME)

Check failure on line 66 in src/containers/Auth/Auth.tsx

View workflow job for this annotation

GitHub Actions / CI

src/containers/Organization/TrialRegistration/TrialRegistration.test.tsx > TrialRegistration > should show validation error for invalid username

TypeError: Cannot read properties of undefined (reading 'then') ❯ src/containers/Auth/Auth.tsx:66:30 ❯ Object.react_stack_bottom_frame node_modules/react-dom/cjs/react-dom-client.development.js:25989:20 ❯ runWithFiberInDEV node_modules/react-dom/cjs/react-dom-client.development.js:874:13 ❯ commitHookEffectListMount node_modules/react-dom/cjs/react-dom-client.development.js:13249:29 ❯ commitHookPassiveMountEffects node_modules/react-dom/cjs/react-dom-client.development.js:13336:11 ❯ commitPassiveMountOnFiber node_modules/react-dom/cjs/react-dom-client.development.js:15484:13 ❯ recursivelyTraversePassiveMountEffects node_modules/react-dom/cjs/react-dom-client.development.js:15439:11 ❯ commitPassiveMountOnFiber node_modules/react-dom/cjs/react-dom-client.development.js:15476:11 ❯ recursivelyTraversePassiveMountEffects node_modules/react-dom/cjs/react-dom-client.development.js:15439:11 ❯ commitPassiveMountOnFiber node_modules/react-dom/cjs/react-dom-client.development.js:15718:11

Check failure on line 66 in src/containers/Auth/Auth.tsx

View workflow job for this annotation

GitHub Actions / CI

src/containers/Organization/TrialRegistration/TrialRegistration.test.tsx > TrialRegistration > should show validation error for invalid organization name

TypeError: Cannot read properties of undefined (reading 'then') ❯ src/containers/Auth/Auth.tsx:66:30 ❯ Object.react_stack_bottom_frame node_modules/react-dom/cjs/react-dom-client.development.js:25989:20 ❯ runWithFiberInDEV node_modules/react-dom/cjs/react-dom-client.development.js:874:13 ❯ commitHookEffectListMount node_modules/react-dom/cjs/react-dom-client.development.js:13249:29 ❯ commitHookPassiveMountEffects node_modules/react-dom/cjs/react-dom-client.development.js:13336:11 ❯ commitPassiveMountOnFiber node_modules/react-dom/cjs/react-dom-client.development.js:15484:13 ❯ recursivelyTraversePassiveMountEffects node_modules/react-dom/cjs/react-dom-client.development.js:15439:11 ❯ commitPassiveMountOnFiber node_modules/react-dom/cjs/react-dom-client.development.js:15476:11 ❯ recursivelyTraversePassiveMountEffects node_modules/react-dom/cjs/react-dom-client.development.js:15439:11 ❯ commitPassiveMountOnFiber node_modules/react-dom/cjs/react-dom-client.development.js:15718:11

Check failure on line 66 in src/containers/Auth/Auth.tsx

View workflow job for this annotation

GitHub Actions / CI

src/containers/Organization/TrialRegistration/TrialRegistration.test.tsx > TrialRegistration > should show validation errors for empty fields

TypeError: Cannot read properties of undefined (reading 'then') ❯ src/containers/Auth/Auth.tsx:66:30 ❯ Object.react_stack_bottom_frame node_modules/react-dom/cjs/react-dom-client.development.js:25989:20 ❯ runWithFiberInDEV node_modules/react-dom/cjs/react-dom-client.development.js:874:13 ❯ commitHookEffectListMount node_modules/react-dom/cjs/react-dom-client.development.js:13249:29 ❯ commitHookPassiveMountEffects node_modules/react-dom/cjs/react-dom-client.development.js:13336:11 ❯ commitPassiveMountOnFiber node_modules/react-dom/cjs/react-dom-client.development.js:15484:13 ❯ recursivelyTraversePassiveMountEffects node_modules/react-dom/cjs/react-dom-client.development.js:15439:11 ❯ commitPassiveMountOnFiber node_modules/react-dom/cjs/react-dom-client.development.js:15476:11 ❯ recursivelyTraversePassiveMountEffects node_modules/react-dom/cjs/react-dom-client.development.js:15439:11 ❯ commitPassiveMountOnFiber node_modules/react-dom/cjs/react-dom-client.development.js:15718:11

Check failure on line 66 in src/containers/Auth/Auth.tsx

View workflow job for this annotation

GitHub Actions / CI

src/containers/Organization/TrialRegistration/TrialRegistration.test.tsx > TrialRegistration > should render TrialRegistration component

TypeError: Cannot read properties of undefined (reading 'then') ❯ src/containers/Auth/Auth.tsx:66:30 ❯ Object.react_stack_bottom_frame node_modules/react-dom/cjs/react-dom-client.development.js:25989:20 ❯ runWithFiberInDEV node_modules/react-dom/cjs/react-dom-client.development.js:874:13 ❯ commitHookEffectListMount node_modules/react-dom/cjs/react-dom-client.development.js:13249:29 ❯ commitHookPassiveMountEffects node_modules/react-dom/cjs/react-dom-client.development.js:13336:11 ❯ commitPassiveMountOnFiber node_modules/react-dom/cjs/react-dom-client.development.js:15484:13 ❯ recursivelyTraversePassiveMountEffects node_modules/react-dom/cjs/react-dom-client.development.js:15439:11 ❯ commitPassiveMountOnFiber node_modules/react-dom/cjs/react-dom-client.development.js:15476:11 ❯ recursivelyTraversePassiveMountEffects node_modules/react-dom/cjs/react-dom-client.development.js:15439:11 ❯ commitPassiveMountOnFiber node_modules/react-dom/cjs/react-dom-client.development.js:15718:11
.then(({ data }) => {
setOrgName(data?.data?.name);
setStatus(data?.data?.status);
})
.catch((error) => setLogs(`orgName error ${JSON.stringify(error)}`, error));
}, [mode]);
}, []);

useEffect(() => {
// Stop loading if any error
if (loading && errorMessage) {
setLoading(false);
}
Expand All @@ -94,7 +86,6 @@
const boxTitleClass = [styles.BoxTitle];
let buttonClass = styles.AuthButton;
let buttonContainedVariant = true;

switch (mode) {
case 'login':
boxClass.push(styles.LoginBox);
Expand All @@ -106,13 +97,6 @@
boxTitleClass.push(styles.RegistrationBoxTitle);
buttonClass = styles.AuthRegistrationButton;
break;
case 'trialregistration':
boxClass.push(styles.RegistrationBox);
boxClass.push(styles.TrialRegistrationBox);
boxTitleClass.push(styles.RegistrationBoxTitle);
buttonClass = styles.AuthButton;
buttonContainedVariant = true;
break;
case 'confirmotp':
boxClass.push(styles.OTPBox);
boxTitleClass.push(styles.RegistrationBoxTitle);
Expand All @@ -132,10 +116,8 @@
}

const isRegistration = mode === 'registration';
const isTrialRegistration = mode === 'trialregistration';
const whatsAppIcon = <WhatsAppIcon className={styles.WhatsAppIcon} />;
const otpMessage = 'You will receive an OTP on your WhatsApp number';

let displayErrorMessage: any = null;
if (errorMessage) {
displayErrorMessage = <div className={styles.ErrorMessage}>{errorMessage}</div>;
Expand All @@ -145,18 +127,22 @@
setShowPassword(!showPassword);
};

// let's add the additonal password field info to the password field to handle
// visibility of the field
const passwordFieldAdditionalInfo = {
endAdornmentCallback: handlePasswordVisibility,
togglePassword: showPassword,
};

const handlePhone =
() =>
(value: string): void => {
initialFormValues.phone = value;
};
(value: string): void => {
// eslint-disable-next-line
initialFormValues.phone = value;
};

let formElements;
// we should not render form elements when displaying success message
if (!successMessage) {
formElements = (
<>
Expand All @@ -169,6 +155,7 @@

<Formik
initialValues={initialFormValues}
enableReinitialize={true}
validationSchema={validationSchema}
onSubmit={(item) => {
setLoading(true);
Expand All @@ -187,7 +174,6 @@
fieldInfo = { ...field, handlePhone };
}
const key = index;

return (
<div className={field.styles} key={key}>
{field.label ? (
Expand All @@ -201,17 +187,11 @@
</div>
);
})}

{isTrialRegistration && inlineSuccessMessage && (
<div className={styles.SuccessMessageInline}>{inlineSuccessMessage}</div>
)}

{linkURL && (
<div className={styles.Link}>
<Link to={`/${linkURL}`}>{linkText}</Link>
</div>
)}

<div className={styles.CenterButton}>
{isRegistration ? (
<Captcha
Expand All @@ -226,7 +206,7 @@
}}
className={buttonClass}
data-testid="SubmitButton"
loading={isLoading}
loading={loading}
action="register"
>
{buttonText}
Expand All @@ -239,14 +219,16 @@
onClick={submitForm}
className={buttonClass}
data-testid="SubmitButton"
loading={isLoading}
type="button"
loading={loading}
>
{buttonText}
{!loading && buttonText}
</Button>
)}
</div>
{isRegistration && <div className={styles.InformationText}>{otpMessage}</div>}
{/* We neeed to add this submit button to enable form sumbitting when user hits enter
key. This is an workaround solution till the bug in formik or react is fixed. For
more info: https://github.com/formium/formik/issues/1418 */}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Typos in comment: "neeed" → "need", "sumbitting" → "submitting".

✏️ Fix
-                {/* We neeed to add this submit button to enable form sumbitting when user hits enter
-                key. This is an workaround solution till the bug in formik or react is fixed. For
+                {/* We need to add this submit button to enable form submitting when user hits enter
+                key. This is a workaround solution till the bug in formik or react is fixed. For
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/containers/Auth/Auth.tsx` around lines 241 - 243, Fix the typos in the
JSX comment inside the Auth component: change "neeed" to "need" and "sumbitting"
to "submitting" in the comment that explains why a hidden submit button is added
(the comment block near the form submit button inside the Auth component).
Ensure the wording now reads that the submit button is required to enable form
submitting when the user hits Enter and keep the reference URL unchanged.

<input className={styles.SubmitAction} type="submit" />
</Form>
{displayErrorMessage}
Expand All @@ -260,17 +242,14 @@
}

return (
<div
className={`${styles.Container} ${isTrialRegistration ? styles.TrialContainer : ''}`}
data-testid="AuthContainer"
>
<div className={styles.Container} data-testid="AuthContainer">
<div className={styles.Auth}>
<div>
<img src={GlificLogo} className={styles.GlificLogo} alt="Glific" />
</div>
<hr className={styles.Break} />

{!isTrialRegistration && <div className={styles.OrganizationName}>{orgName}</div>}
<div className={styles.OrganizationName}>{orgName}</div>

<div className={boxClass.join(' ')}>
{formElements}
Expand All @@ -297,7 +276,7 @@
) : null}
</div>

{mode === 'login' && <Promotion />}
{/* {mode === 'login' && <Promotion />} */}
</div>
);
};
18 changes: 18 additions & 0 deletions src/containers/Auth/ResetPassword/ResetPasswordConfirmOTP.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -76,4 +76,22 @@ describe('<ResetPasswordConfirmOTP />', () => {
expect(sendOptMock).toHaveBeenCalledWith('919967665667');
});
});

test('it should show validation error if phone number is empty in state', async () => {
const WrapperEmpty = (
<MemoryRouter initialEntries={[{ state: { phoneNumber: '' } }]}>
<Routes>
<Route path="/" element={<ResetPasswordConfirmOTP />} />
</Routes>
</MemoryRouter>
);

render(WrapperEmpty);
const saveButton = screen.getByText('Save');
await user.click(saveButton);

await waitFor(() => {
expect(screen.getAllByText('Input required').length).toBeGreaterThan(0);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ export const ResetPasswordConfirmOTP = () => {
];

const FormSchema = Yup.object().shape({
phoneNumber: Yup.string().required(t('Input required')),
OTP: Yup.string().required(t('Input required')),
password: yupPasswordValidation(t),
});
Expand Down
51 changes: 51 additions & 0 deletions src/containers/MyAccount/MyAccount.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { MemoryRouter } from 'react-router';
import { vi } from 'vitest';

import { getCurrentUserQuery, updateUserQuery } from 'mocks/User';
import { UPDATE_CURRENT_USER } from 'graphql/mutations/User';
import { getOrganizationLanguagesQuery } from 'mocks/Organization';
import { MyAccount } from './MyAccount';

Expand All @@ -15,6 +16,20 @@ const mocks = [
getCurrentUserQuery,
getOrganizationLanguagesQuery,
getOrganizationLanguagesQuery,
{
request: {
query: UPDATE_CURRENT_USER,
variables: { input: { name: 'John Doe', email: 'error@domain.com' } },
},
result: {
data: {
updateCurrentUser: {
errors: [{ key: 'email', message: 'Email already exists' }],
user: null,
},
},
},
},
];

vi.mock('axios');
Expand Down Expand Up @@ -198,4 +213,40 @@ describe('<MyAccount />', () => {
const saveButton = screen.getByText('Save');
await user.click(saveButton);
});

test('update profile (name and email) flow', async () => {
const { container } = render(wrapper);

await screen.findByTestId('MyAccount');

// change email
const emailInput = container.querySelector('input[name="email"]') as HTMLInputElement;
fireEvent.change(emailInput, { target: { value: 'newemail@domain.com' } });

// save button should appear
const saveButton = await screen.findByText('Save');
await user.click(saveButton);

await waitFor(() => {
expect(screen.getByText('Profile updated successfully!')).toBeInTheDocument();
});
});

test('update profile error flow', async () => {
const { container } = render(wrapper);

await screen.findByTestId('MyAccount');

// change email to one that triggers an error
const emailInput = container.querySelector('input[name="email"]') as HTMLInputElement;
fireEvent.change(emailInput, { target: { value: 'error@domain.com' } });

// save button should appear
const saveButton = await screen.findByText('Save');
await user.click(saveButton);

await waitFor(() => {
expect(screen.getByText('Email already exists')).toBeInTheDocument();
});
});
});
61 changes: 45 additions & 16 deletions src/containers/MyAccount/MyAccount.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,13 +51,19 @@ export const MyAccount = () => {
const [updateCurrentUser] = useMutation(UPDATE_CURRENT_USER, {
onCompleted: (data) => {
if (data.updateCurrentUser.errors) {
if (data.updateCurrentUser.errors[0].message === 'incorrect_code') {
const error = data.updateCurrentUser.errors[0];
if (error.message === 'incorrect_code') {
setToastMessageInfo({ severity: 'error', message: t('Please enter a valid OTP') });
} else {
} else if (error.message === 'Too many attempts') {
setToastMessageInfo({
severity: 'error',
message: t('Too many attempts, please retry after sometime.'),
});
} else {
setToastMessageInfo({
severity: 'error',
message: t(error.message),
});
}
} else {
setShowOTPButton(true);
Expand Down Expand Up @@ -139,6 +145,11 @@ export const MyAccount = () => {
password: yupPasswordValidation(t),
});

const UserFormSchema = Yup.object().shape({
name: Yup.string().required(t('Name is required')),
email: Yup.string().email(t('Invalid email address')).required(t('Email is required')),
});

const userformFields = [
{
component: Input,
Expand All @@ -156,24 +167,42 @@ export const MyAccount = () => {
component: Input,
name: 'email',
label: t('Email'),
disabled: true,
disabled: false,
},
];

const userForm = (
<Formik initialValues={{ name: userName, phone: userPhone, email: userEmail }} onSubmit={() => { }}>
<Form>
{userformFields.map((field) => (
<div className={styles.UserField} key={field.name}>
{field.label && (
<Typography data-testid="formLabel" variant="h5" className={styles.FieldLabel}>
{field.label}
</Typography>
)}
<Field key={field.name} {...field}></Field>
</div>
))}
</Form>
<Formik
initialValues={{ name: userName, phone: userPhone, email: userEmail }}
validationSchema={UserFormSchema}
onSubmit={(values) => {
setMessage(t('Profile updated successfully!'));
updateCurrentUser({
variables: { input: { name: values.name, email: values.email } },
});
}}
>
{({ dirty, submitForm }) => (
<Form>
{userformFields.map((field) => (
<div className={styles.UserField} key={field.name}>
{field.label && (
<Typography data-testid="formLabel" variant="h5" className={styles.FieldLabel}>
{field.label}
</Typography>
)}
<Field key={field.name} {...field}></Field>
</div>
))}
{dirty && (
<div className={styles.Buttons}>
<Button variant="contained" color="primary" onClick={submitForm} className={styles.Button}>
{t('Save')}
</Button>
</div>
)}
</Form>
)}
</Formik>
);

Expand Down
Loading
Loading