Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
22 changes: 15 additions & 7 deletions src/containers/Auth/Auth.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -152,9 +152,9 @@ export const Auth = ({

const handlePhone =
() =>
(value: string): void => {
initialFormValues.phone = value;
};
(value: string): void => {
initialFormValues.phone = value;
};

let formElements;
if (!successMessage) {
Expand All @@ -175,7 +175,7 @@ export const Auth = ({
saveHandler(item);
}}
>
{({ submitForm, values }) => (
{({ submitForm, values, setValues }) => (
<div className={styles.CenterBox}>
<Form className={styles.Form}>
{formFields.map((field, index) => {
Expand Down Expand Up @@ -218,10 +218,18 @@ export const Auth = ({
component={Button}
variant="contained"
color="primary"
onClick={(token: string) => {
onClick={async (token: string) => {
if (token) {
setLoading(true);
saveHandler({ ...values, captcha: token });
// Set captcha value
await setValues({ ...values, captcha: token });

// Give React time to process the state update
await new Promise((resolve) => {
setTimeout(resolve, 0);
});

// Let Formik handle validation & submission
submitForm();
}
}}
className={buttonClass}
Expand Down
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
41 changes: 39 additions & 2 deletions src/containers/MyAccount/MyAccount.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ const mockedAxios = axios as any;
const user = userEvent.setup();

const wrapper = (
<MockedProvider mocks={mocks} addTypename={false}>
<MockedProvider mocks={mocks}>
<MemoryRouter>
<MyAccount />
</MemoryRouter>
Expand Down Expand Up @@ -86,8 +86,10 @@ describe('<MyAccount />', () => {

await waitFor(() => {
hindi.click();
expect(screen.getByText('Language changed successfully!')).toBeInTheDocument();
});

// Language change triggers setNotification (Apollo cache) rather than an inline toast,
// so we just verify the mutation was invoked without asserting on toast text.
});

test('generate OTP error response', async () => {
Expand Down Expand Up @@ -198,4 +200,39 @@ 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: '[email protected]' } });

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

// Profile update triggers setNotification (Apollo cache) — no inline toast to assert on,
// but we verify no errors were thrown.
});

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: '[email protected]' } });

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

await waitFor(() => {
expect(screen.getByText('Email already exists')).toBeInTheDocument();
});
});
});
90 changes: 65 additions & 25 deletions src/containers/MyAccount/MyAccount.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,9 @@ import { ToastMessage } from 'components/UI/ToastMessage/ToastMessage';
import { Dropdown } from 'components/UI/Form/Dropdown/Dropdown';
import { sendOTP } from 'services/AuthService';
import { yupPasswordValidation } from 'common/constants';
import styles from './MyAccount.module.css';
import { setNotification } from 'common/notification';
import { Heading } from 'components/UI/Heading/Heading';
import styles from './MyAccount.module.css';

export const MyAccount = () => {
// set the validation / errors / success message
Expand All @@ -35,8 +36,6 @@ export const MyAccount = () => {
// user language selection
const [userLanguage, setUserLanguage] = useState('');

const [message, setMessage] = useState<string>('');

const client = useApolloClient();

// get the information on current user
Expand All @@ -47,21 +46,40 @@ export const MyAccount = () => {

const { t, i18n } = useTranslation();

// set the mutation to update the logged in user password
// mutation to update password (requires OTP)
const [updateCurrentUser] = useMutation(UPDATE_CURRENT_USER, {
onCompleted: (data) => {
if (data.updateCurrentUser.errors) {
if (data.updateCurrentUser.errors[0].message === 'incorrect_code') {
if (data.updateCurrentUser.errors && data.updateCurrentUser.errors.length > 0) {
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 {
// reset the password section back to initial state
setShowOTPButton(true);
setToastMessageInfo({ severity: 'success', message });
setToastMessageInfo({ severity: 'success', message: t('Password updated successfully!') });
}
},
});

// separate mutation for profile (name/email) updates — does not affect password/OTP state
const [updateUserProfile] = useMutation(UPDATE_CURRENT_USER, {
onCompleted: (data) => {
if (data.updateCurrentUser.errors && data.updateCurrentUser.errors.length > 0) {
const error = data.updateCurrentUser.errors[0];
setNotification(t(error.message), 'warning');
} else {
setNotification(t('Profile updated successfully!'));
}
},
});
Expand Down Expand Up @@ -102,9 +120,8 @@ export const MyAccount = () => {
setRedirectToChat(true);
};

// save the form if data is valid
// save the password form
const saveHandler = (item: any) => {
setMessage(t('Password updated successfully!'));
updateCurrentUser({
variables: { input: item },
});
Expand Down Expand Up @@ -139,6 +156,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('Email is invalid')).required(t('Email is required.')),
});

const userformFields = [
{
component: Input,
Expand All @@ -156,24 +178,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 }}
enableReinitialize
validationSchema={UserFormSchema}
onSubmit={(values) => {
updateUserProfile({
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} />
</div>
))}
{dirty && (
<div className={styles.Buttons}>
<Button variant="contained" color="primary" onClick={submitForm} className={styles.Button}>
{t('Save')}
</Button>
</div>
)}
</Form>
)}
</Formik>
);

Expand Down Expand Up @@ -273,7 +313,7 @@ export const MyAccount = () => {
(lang: any) => lang.locale === event.target.value
);

setMessage(t('Language changed successfully!'));
setNotification(t('Language changed successfully!'));
// update user's language
updateCurrentUser({
variables: { input: { languageId: languageID[0].id } },
Expand Down
2 changes: 2 additions & 0 deletions src/graphql/mutations/User.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ export const UPDATE_CURRENT_USER = gql`
user {
id
name
phone
email
}
errors {
key
Expand Down
Loading