Skip to content

Commit

Permalink
fix: remove unfinished mutations for consents and customer data and m…
Browse files Browse the repository at this point in the history
…ove logic to account store (#17)

* fix: remove unfinished mutations for consents and customer data and move logic to account store
* fix: add translations for account update errors
  • Loading branch information
dbudzins authored Mar 7, 2022
1 parent 1a38bd9 commit 4a75628
Show file tree
Hide file tree
Showing 16 changed files with 249 additions and 253 deletions.
19 changes: 8 additions & 11 deletions src/components/Account/Account.test.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,20 @@
import React from 'react';
import { render } from '@testing-library/react';
import type { Customer } from 'types/account';
import type { Consent } from 'types/account';

import customer from '../../fixtures/customer.json';
import { AccountStore } from '../../stores/AccountStore';

import Account from './Account';

describe('<Account>', () => {
test('renders and matches snapshot', () => {
const { container } = render(
<Account
customer={customer as Customer}
isLoading={false}
consentsLoading={false}
onUpdateEmailSubmit={() => null}
onUpdateInfoSubmit={() => null}
onUpdateConsentsSubmit={() => null}
/>,
);
AccountStore.update((s) => {
s.user = customer;
s.publisherConsents = Array.of({ name: 'marketing', label: 'Receive Marketing Emails' } as Consent);
});

const { container } = render(<Account panelClassName={'panel-class'} panelHeaderClassName={'header-class'} />);

// todo
expect(container).toMatchSnapshot();
Expand Down
124 changes: 73 additions & 51 deletions src/components/Account/Account.tsx
Original file line number Diff line number Diff line change
@@ -1,59 +1,39 @@
import React, { useState, useEffect, useMemo } from 'react';
import React, { useState, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import type { Consent, Customer, CustomerConsent, UpdateCustomerPayload } from 'types/account';
import type { CustomerFormValues, FormErrors, GenericFormValues } from 'types/form';
import type { GenericFormValues, CustomerFormErrors } from 'types/form';
import { useHistory } from 'react-router-dom';

import { formatConsentsFromValues, formatConsentValues } from '../../utils/collection';
import Visibility from '../../icons/Visibility';
import VisibilityOff from '../../icons/VisibilityOff';
import useToggle from '../../hooks/useToggle';
import Button from '../../components/Button/Button';
import Spinner from '../../components/Spinner/Spinner';
import Form from '../Form/Form';
import IconButton from '../IconButton/IconButton';
import LoadingOverlay from '../LoadingOverlay/LoadingOverlay';
import TextField from '../TextField/TextField';
import Checkbox from '../Checkbox/Checkbox';
import { addQueryParam } from '../../utils/history';
import { AccountStore, updateConsents, updateUser } from '../../stores/AccountStore';

import styles from './Account.module.scss';

type Props = {
customer: Customer;
errors?: FormErrors<UpdateCustomerPayload>;
isLoading: boolean;
consentsLoading: boolean;
publisherConsents?: Consent[];
customerConsents?: CustomerConsent[];
onUpdateEmailSubmit: (data: CustomerFormValues) => void;
onUpdateInfoSubmit: (data: CustomerFormValues) => void;
onUpdateConsentsSubmit: (consents: CustomerConsent[]) => void;
onReset?: () => void;
panelClassName?: string;
panelHeaderClassName?: string;
};

type Editing = 'none' | 'account' | 'password' | 'info' | 'consents';

const Account = ({
customer,
errors,
isLoading,
consentsLoading,
publisherConsents,
customerConsents,
panelClassName,
panelHeaderClassName,
onUpdateEmailSubmit,
onUpdateInfoSubmit,
onUpdateConsentsSubmit,
onReset,
}: Props): JSX.Element => {
const Account = ({ panelClassName, panelHeaderClassName }: Props): JSX.Element => {
const { t } = useTranslation('user');
const history = useHistory();
const [editing, setEditing] = useState<Editing>('none');
const [errors, setErrors] = useState<CustomerFormErrors | undefined>();
const [isLoading, setIsLoading] = useState(false);
const [viewPassword, toggleViewPassword] = useToggle();

const { user: customer, customerConsents, publisherConsents } = AccountStore.useState((state) => state);
const consentValues = useMemo(() => formatConsentValues(publisherConsents, customerConsents), [publisherConsents, customerConsents]);
const initialValues = useMemo(() => ({ ...customer, consents: consentValues }), [customer, consentValues]);

Expand All @@ -69,32 +49,79 @@ const Account = ({
return label;
};

const handleSubmit = (values: GenericFormValues) => {
const translateErrors = (errors?: string[]) => {
const formErrors: CustomerFormErrors = {};

// Some errors are combined in a single CSV string instead of one string per error
errors
?.flatMap((e) => e.split(','))
.map((error) => {
switch (error.trim()) {
case 'Invalid param email':
formErrors.email = t('account.errors.invalid_param_email');
break;
case 'Customer email already exists':
formErrors.email = t('account.errors.email_exists');
break;
case 'Please enter a valid e-mail address.':
formErrors.email = t('account.errors.please_enter_valid_email');
break;
case 'Invalid confirmationPassword': {
formErrors.confirmationPassword = t('account.errors.invalid_password');
break;
}
case 'firstName can have max 50 characters.': {
formErrors.firstName = t('account.errors.first_name_too_long');
break;
}
case 'lastName can have max 50 characters.': {
formErrors.lastName = t('account.errors.last_name_too_long');
break;
}
default:
console.info('Unknown error', error);
return;
}
});
return formErrors;
};

async function handleSubmit(values: GenericFormValues) {
let response: ApiResponse | undefined = undefined;
setIsLoading(true);
switch (editing) {
case 'account':
return onUpdateEmailSubmit(values as CustomerFormValues);
response = await updateUser({ email: values.email, confirmationPassword: values.confirmationPassword });
break;
case 'info':
return onUpdateInfoSubmit(values as CustomerFormValues);
response = await updateUser({ firstName: values.firstName, lastName: values.lastName });
break;
case 'consents':
return onUpdateConsentsSubmit(formatConsentsFromValues(publisherConsents, values));
response = await updateConsents(formatConsentsFromValues(publisherConsents, values));
break;
default:
return;
}
};

setErrors(translateErrors(response?.errors));

if (response && !response?.errors?.length) {
setEditing('none');
}

setIsLoading(false);
}

const onCancelClick = (formResetHandler?: () => void): void => {
formResetHandler && formResetHandler();
setErrors(undefined);
setEditing('none');
onReset && onReset();
};

const editPasswordClickHandler = () => {
history.push(addQueryParam(history, 'u', 'reset-password'));
};

useEffect(() => {
!isLoading && setEditing('none');
}, [isLoading]);

return (
<Form initialValues={initialValues} onSubmit={handleSubmit}>
{({ values, handleChange, handleReset, handleSubmit, hasChanged }) => (
Expand Down Expand Up @@ -127,10 +154,7 @@ const Account = ({
type={viewPassword ? 'text' : 'password'}
disabled={isLoading}
rightControl={
<IconButton
aria-label={viewPassword ? t('account.hide_password') : t('account.view_password')}
onClick={() => toggleViewPassword()}
>
<IconButton aria-label={viewPassword ? t('account.hide_password') : t('account.view_password')} onClick={() => toggleViewPassword()}>
{viewPassword ? <Visibility /> : <VisibilityOff />}
</IconButton>
}
Expand Down Expand Up @@ -203,13 +227,11 @@ const Account = ({
</div>
</div>
</div>
<div className={panelClassName}>
<div className={panelHeaderClassName}>
<h3>{t('account.terms_and_tracking')}</h3>
</div>
{consentsLoading ? (
<Spinner size="small" />
) : publisherConsents ? (
{publisherConsents && (
<div className={panelClassName}>
<div className={panelHeaderClassName}>
<h3>{t('account.terms_and_tracking')}</h3>
</div>
<div className={styles.flexBox} onClick={() => setEditing('consents')}>
{publisherConsents.map((consent, index) => (
<Checkbox
Expand All @@ -233,8 +255,8 @@ const Account = ({
/>
</div>
</div>
) : null}
</div>
</div>
)}
</>
)}
</Form>
Expand Down
71 changes: 63 additions & 8 deletions src/components/Account/__snapshots__/Account.test.tsx.snap
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,12 @@ exports[`<Account> renders and matches snapshot 1`] = `
<form
novalidate=""
>
<div>
<div>
<div
class="panel-class"
>
<div
class="header-class"
>
<h3>
account.email
</h3>
Expand Down Expand Up @@ -41,8 +45,12 @@ exports[`<Account> renders and matches snapshot 1`] = `
</div>
</div>
</div>
<div>
<div>
<div
class="panel-class"
>
<div
class="header-class"
>
<h3>
account.security
</h3>
Expand All @@ -64,8 +72,12 @@ exports[`<Account> renders and matches snapshot 1`] = `
</button>
</div>
</div>
<div>
<div>
<div
class="panel-class"
>
<div
class="header-class"
>
<h3>
account.about_you
</h3>
Expand Down Expand Up @@ -115,12 +127,55 @@ exports[`<Account> renders and matches snapshot 1`] = `
</div>
</div>
</div>
<div>
<div>
<div
class="panel-class"
>
<div
class="header-class"
>
<h3>
account.terms_and_tracking
</h3>
</div>
<div
class="flexBox"
>
<div
class="checkbox"
>
<div
class="row"
>
<input
id="check-box_1235_marketing"
name="marketing"
type="checkbox"
value=""
/>
<label
for="check-box_1235_marketing"
>
Receive Marketing Emails
</label>
</div>
</div>
<div
class="controls"
>
<button
aria-disabled="true"
class="button submitConsents default outlined disabled"
disabled=""
id="submit_consents"
type="button"
>
<span>
account.update_consents
</span>
</button>
</div>
</div>
</div>
</form>
</div>
Expand Down
11 changes: 0 additions & 11 deletions src/containers/Account/AccountContainer.test.ts

This file was deleted.

Loading

0 comments on commit 4a75628

Please sign in to comment.