Skip to content

Commit

Permalink
feat: export account data initial implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
naumovski-filip committed May 3, 2023
1 parent 759ed17 commit 84628b2
Show file tree
Hide file tree
Showing 9 changed files with 212 additions and 109 deletions.
262 changes: 157 additions & 105 deletions src/components/Account/Account.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import React, { useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import React, { useEffect, useMemo, useState } from 'react';
import { Trans, useTranslation } from 'react-i18next';
import { useLocation, useNavigate } from 'react-router-dom';
import shallow from 'zustand/shallow';
import DOMPurify from 'dompurify';
import { useMutation } from 'react-query';

import Alert from '../Alert/Alert';

import type { FormSectionContentArgs, FormSectionProps } from '#components/Form/FormSection';
import Visibility from '#src/icons/Visibility';
Expand All @@ -18,7 +21,7 @@ import { formatConsentsFromValues, formatConsentValues } from '#src/utils/collec
import { addQueryParam } from '#src/utils/location';
import { useAccountStore } from '#src/stores/AccountStore';
import { logDev } from '#src/utils/common';
import { updateConsents, updateUser } from '#src/stores/AccountController';
import { exportAccountData, updateConsents, updateUser } from '#src/stores/AccountController';

type Props = {
panelClassName?: string;
Expand All @@ -39,13 +42,23 @@ const Account = ({ panelClassName, panelHeaderClassName, canUpdateEmail = true }
const navigate = useNavigate();
const location = useLocation();
const [viewPassword, toggleViewPassword] = useToggle();
const exportData = useMutation(exportAccountData);
const [isAlertVisible, setIsAlertVisible] = useState(false);
const exportDataMessage = exportData.isSuccess ? t('account.export_data_success') : t('account.export_data_error');

useEffect(() => {
if (exportData.isSuccess || exportData.isError) {
setIsAlertVisible(true);
}
}, [exportData.isSuccess, exportData.isError]);

const { customer, customerConsents, publisherConsents, canChangePasswordWithOldPassword } = useAccountStore(
({ user, customerConsents, publisherConsents, canChangePasswordWithOldPassword }) => ({
const { customer, customerConsents, publisherConsents, canChangePasswordWithOldPassword, canExportAccountData } = useAccountStore(
({ user, customerConsents, publisherConsents, canChangePasswordWithOldPassword, canExportAccountData }) => ({
customer: user,
customerConsents,
publisherConsents,
canChangePasswordWithOldPassword,
canExportAccountData,
}),
shallow,
);
Expand Down Expand Up @@ -144,113 +157,152 @@ const Account = ({ panelClassName, panelHeaderClassName, canUpdateEmail = true }
};

return (
<Form initialValues={initialValues}>
{[
formSection({
label: t('account.email'),
onSubmit: (values) =>
updateUser({
email: values.email || '',
confirmationPassword: values.confirmationPassword,
}),
canSave: (values) => !!(values.email && values.confirmationPassword),
editButton: t('account.edit_account'),
readOnly: !canUpdateEmail,
content: (section) => (
<>
<TextField
name="email"
label={t('account.email')}
value={section.values.email || ''}
onChange={section.onChange}
error={!!section.errors?.email}
helperText={section.errors?.email}
disabled={section.isBusy}
editing={section.isEditing}
required
/>
{section.isEditing && (
<>
<Form initialValues={initialValues}>
{[
formSection({
label: t('account.email'),
onSubmit: (values) =>
updateUser({
email: values.email || '',
confirmationPassword: values.confirmationPassword,
}),
canSave: (values) => !!(values.email && values.confirmationPassword),
editButton: t('account.edit_account'),
readOnly: !canUpdateEmail,
content: (section) => (
<>
<TextField
name="confirmationPassword"
label={t('account.confirm_password')}
value={section.values.confirmationPassword}
name="email"
label={t('account.email')}
value={section.values.email || ''}
onChange={section.onChange}
error={!!section.errors?.confirmationPassword}
helperText={section.errors?.confirmationPassword}
type={viewPassword ? 'text' : 'password'}
error={!!section.errors?.email}
helperText={section.errors?.email}
disabled={section.isBusy}
rightControl={
<IconButton aria-label={viewPassword ? t('account.hide_password') : t('account.view_password')} onClick={() => toggleViewPassword()}>
{viewPassword ? <Visibility /> : <VisibilityOff />}
</IconButton>
}
editing={section.isEditing}
required
/>
)}
</>
),
}),
formSection({
label: t('account.security'),
editButton: <Button label={t('account.edit_password')} type="button" onClick={() => (customer ? editPasswordClickHandler() : null)} />,
content: () => (
<>
<strong>{t('account.password')}</strong>
<p>****************</p>
</>
),
}),
formSection({
label: t('account.about_you'),
editButton: t('account.edit_information'),
onSubmit: (values) => updateUser({ firstName: values.firstName || '', lastName: values.lastName || '' }),
content: (section) => (
<>
<TextField
name="firstName"
label={t('account.firstname')}
value={section.values.firstName || ''}
onChange={section.onChange}
error={!!section.errors?.firstName}
helperText={section.errors?.firstName}
disabled={section.isBusy}
editing={section.isEditing}
/>
<TextField
name="lastName"
label={t('account.lastname')}
value={section.values.lastName || ''}
onChange={section.onChange}
error={!!section.errors?.lastName}
helperText={section.errors?.lastName}
disabled={section.isBusy}
editing={section.isEditing}
/>
</>
),
}),
formSection({
label: t('account.terms_and_tracking'),
saveButton: t('account.update_consents'),
onSubmit: (values) => updateConsents(formatConsentsFromValues(publisherConsents, values)),
content: (section) => (
<>
{publisherConsents?.map((consent, index) => (
<Checkbox
key={index}
name={`consents.${consent.name}`}
value={consent.value || ''}
checked={(section.values.consents?.[consent.name] as boolean) || false}
{section.isEditing && (
<TextField
name="confirmationPassword"
label={t('account.confirm_password')}
value={section.values.confirmationPassword}
onChange={section.onChange}
error={!!section.errors?.confirmationPassword}
helperText={section.errors?.confirmationPassword}
type={viewPassword ? 'text' : 'password'}
disabled={section.isBusy}
rightControl={
<IconButton aria-label={viewPassword ? t('account.hide_password') : t('account.view_password')} onClick={() => toggleViewPassword()}>
{viewPassword ? <Visibility /> : <VisibilityOff />}
</IconButton>
}
required
/>
)}
</>
),
}),
formSection({
label: t('account.security'),
editButton: <Button label={t('account.edit_password')} type="button" onClick={() => (customer ? editPasswordClickHandler() : null)} />,
content: () => (
<>
<strong>{t('account.password')}</strong>
<p>****************</p>
</>
),
}),
formSection({
label: t('account.about_you'),
editButton: t('account.edit_information'),
onSubmit: (values) => updateUser({ firstName: values.firstName || '', lastName: values.lastName || '' }),
content: (section) => (
<>
<TextField
name="firstName"
label={t('account.firstname')}
value={section.values.firstName || ''}
onChange={section.onChange}
label={formatConsentLabel(consent.label)}
disabled={consent.required || section.isBusy}
error={!!section.errors?.firstName}
helperText={section.errors?.firstName}
disabled={section.isBusy}
editing={section.isEditing}
/>
<TextField
name="lastName"
label={t('account.lastname')}
value={section.values.lastName || ''}
onChange={section.onChange}
error={!!section.errors?.lastName}
helperText={section.errors?.lastName}
disabled={section.isBusy}
editing={section.isEditing}
/>
))}
</>
),
}),
]}
</Form>
</>
),
}),
formSection({
label: t('account.terms_and_tracking'),
saveButton: t('account.update_consents'),
onSubmit: (values) => updateConsents(formatConsentsFromValues(publisherConsents, values)),
content: (section) => (
<>
{publisherConsents?.map((consent, index) => (
<Checkbox
key={index}
name={`consents.${consent.name}`}
value={consent.value || ''}
checked={(section.values.consents?.[consent.name] as boolean) || false}
onChange={section.onChange}
label={formatConsentLabel(consent.label)}
disabled={consent.required || section.isBusy}
/>
))}
</>
),
}),
...(canExportAccountData
? [
formSection({
label: t('account.export_data_title'),
content: (section) => (
// TODO: pull css out into a module
<div
style={{
display: 'flex',
flexDirection: 'column',
gap: '1rem',
}}
>
<div>
<Trans t={t} i18nKey="account.export_data_body" values={{ email: section.values.email }} />
</div>
<div
style={{
display: 'flex',
flexGrow: 1,
}}
>
<Button
label={t('account.export_data_title')}
type="button"
disabled={exportData.isLoading}
onClick={async () => {
exportData.mutate(''); // TODO: remove password once backend is updated
}}
/>
</div>
</div>
),
}),
]
: []),
]}
</Form>
<Alert open={isAlertVisible} message={exportDataMessage} onClose={() => setIsAlertVisible(false)} isSuccess={exportData.isSuccess} />
</>
);
};

Expand Down
5 changes: 3 additions & 2 deletions src/components/Alert/Alert.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,15 @@ type Props = {
open: boolean;
message: string | null;
onClose: () => void;
isSuccess?: boolean;
};

const Alert: React.FC<Props> = ({ open, message, onClose }: Props) => {
const Alert: React.FC<Props> = ({ open, message, onClose, isSuccess }: Props) => {
const { t } = useTranslation('common');

return (
<Dialog open={open} onClose={onClose}>
<h2 className={styles.title}>{t('alert.title')}</h2>
<h2 className={styles.title}>{t(`${isSuccess ? 'alert.success' : 'alert.title'}`)}</h2>
<p className={styles.body}>{message}</p>
<Button label={t('alert.close')} variant="outlined" onClick={onClose} fullWidth />
</Dialog>
Expand Down
3 changes: 2 additions & 1 deletion src/i18n/locales/en_US/common.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
{
"alert": {
"close": "Close",
"title": "An error occurred"
"title": "An error occurred",
"success": "Success"
},
"back": "Back",
"card_lock": "Item locked",
Expand Down
6 changes: 5 additions & 1 deletion src/i18n/locales/en_US/user.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,11 @@
"security": "Security",
"terms_and_tracking": "Terms & tracking",
"update_consents": "Update consents",
"view_password": "View password"
"view_password": "View password",
"export_data_title": "Export account data",
"export_data_body": "Export all of your personal account data to <strong>{{email}}</strong> as an attachment.",
"export_data_success": "Please check your email.",
"export_data_error": "An error occurred while exporting your account data."
},
"favorites": {
"clear": "Clear favorites",
Expand Down
2 changes: 2 additions & 0 deletions src/services/cleeng.account.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,7 @@ const handleErrors = (errors: ApiResponse['errors']) => {
export const updatePersonalShelves: UpdatePersonalShelves = async (payload, sandbox, jwt) => {
return await updateCustomer(payload, sandbox, jwt);
};
export const exportAccountData = () => null;

export const canUpdateEmail = true;

Expand All @@ -228,3 +229,4 @@ export const canSupportEmptyFullName = true;
export const canChangePasswordWithOldPassword = false;
export const subscribeToNotifications = async () => true;
export const canRenewSubscription = true;
export const canExportAccountData = false;
19 changes: 19 additions & 0 deletions src/services/inplayer.account.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import type {
Consent,
Customer,
CustomerConsent,
ExportAccountData,
ExternalData,
GetCaptureStatus,
GetCustomerConsents,
Expand Down Expand Up @@ -314,6 +315,22 @@ export const updatePersonalShelves: UpdatePersonalShelves = async (payload) => {
}
};

export const exportAccountData: ExportAccountData = async ({ password }) => {
// TODO: remove password once backend is updated
const response = await InPlayer.Account.exportData({ password, brandingId: 0 });
const { code, message } = response.data;
if (code !== 200) {
throw new Error(message);
}
return {
errors: [],
responseData: {
message,
code,
},
};
};

const getCustomerExternalData = async (): Promise<ExternalData> => {
const [favoritesData, historyData] = await Promise.all([InPlayer.Account.getFavorites(), await InPlayer.Account.getWatchHistory({})]);

Expand Down Expand Up @@ -425,3 +442,5 @@ export const canSupportEmptyFullName = false;
export const canChangePasswordWithOldPassword = true;

export const canRenewSubscription = false;

export const canExportAccountData = true;
Loading

0 comments on commit 84628b2

Please sign in to comment.