client/components/mma/dataPrivacy/DataPrivacyPage.tsx (121 lines of code) (raw):
import type { CMP } from '@guardian/libs';
import { from } from '@guardian/source/foundations';
import { useEffect, useState } from 'react';
import { gridItemPlacement } from '../../../styles/grid';
import { fetchWithDefaultParameters } from '../../../utilities/fetch';
import {
LoadingState,
useAsyncLoader,
} from '../../../utilities/hooks/useAsyncLoader';
import { GenericErrorScreen } from '../../shared/GenericErrorScreen';
import { WithStandardTopMargin } from '../../shared/WithStandardTopMargin';
import * as UserAPI from '../identity/idapi/user';
import { ConsentOptions, mapSubscriptions } from '../identity/identity';
import { Lines } from '../identity/Lines';
import type { ConsentOption } from '../identity/models';
import { Actions, useConsentOptions } from '../identity/useConsentOptions';
import { JsonResponseHandler } from '../shared/asyncComponents/DefaultApiResponseHandler';
import { DefaultLoadingView } from '../shared/asyncComponents/DefaultLoadingView';
import { CookiesOnThisBrowserSection } from './CookiesOnTheBrowserSection';
import { dataPrivacyWrapper } from './DataPrivacy.styles';
import { LearnMoreSection } from './LearnMoreSection';
import { YourDataSection } from './YourDataSection';
type DataPrivacyResponse = [ConsentOption[], UserAPI.UserAPIResponse];
const dataPrivacyFetcher = () =>
Promise.all([
fetchWithDefaultParameters('/idapi/consents'),
fetchWithDefaultParameters('/idapi/user'),
]);
export const DataPrivacyPage = () => {
const { options, error, subscribe, unsubscribe } = Actions;
const [state, dispatch] = useConsentOptions();
const [importedCmp, setImportedCmp] = useState<CMP | null>(null);
const {
data: dataPrivacyResponse,
loadingState,
}: {
data: DataPrivacyResponse | null;
loadingState: LoadingState;
} = useAsyncLoader(dataPrivacyFetcher, JsonResponseHandler);
/**
* This function imports and loads the cmp app to the state value, importedCmp
*
*/
const loadCMP = () => {
import('@guardian/libs').then(({ cmp }) => {
setImportedCmp(cmp);
});
};
const consents = ConsentOptions.consents(state.options);
useEffect(() => {
if (dataPrivacyResponse) {
/**
* Use the response from the dataPrivacyFetcher api call
* to get the users subscriptions/consents and dispatch the options to the
* store.
*/
const [consentOptions, userResponse] = dataPrivacyResponse;
const user = UserAPI.toUser(userResponse);
const consentOpt = mapSubscriptions(user.consents, consentOptions);
dispatch(options(consentOpt));
}
loadCMP();
}, [dataPrivacyResponse, dispatch, options]);
if (loadingState == LoadingState.HasError) {
return <GenericErrorScreen />;
}
if (loadingState == LoadingState.IsLoading) {
return (
<DefaultLoadingView loadingMessage="Loading your privacy details." />
);
}
if (dataPrivacyResponse === null) {
return <GenericErrorScreen />;
}
/**
* This function makes an API request to /users/me/consents to subscribe or unsubscribe.
* It then dispatches a redux action
*
* @param {string} id
*/
const toggleConsentSubscription = async (id: string) => {
const option = ConsentOptions.findById(state.options, id);
try {
if (option === undefined) {
throw Error('Id not found');
}
if (option.subscribed) {
await ConsentOptions.unsubscribe(option);
dispatch(unsubscribe(id));
} else {
await ConsentOptions.subscribe(option);
dispatch(subscribe(id));
}
} catch (e) {
dispatch(error(e));
}
};
/**
* This function triggers importedCmp?.showPrivacyManage
* if it has loaded.
*
* The importedCmp is lazy loaded.
*
*/
const openManageCookies = () => {
importedCmp?.showPrivacyManager();
};
const content = () => (
<div css={dataPrivacyWrapper}>
<div
css={{
...gridItemPlacement(1, 12),
[from.tablet]: {
...gridItemPlacement(1, 12),
},
[from.desktop]: {
...gridItemPlacement(1, 10),
},
[from.wide]: {
...gridItemPlacement(1, 14),
},
}}
>
<WithStandardTopMargin>
<YourDataSection
consents={consents}
toggleConsent={toggleConsentSubscription}
/>
<Lines n={1} />
<CookiesOnThisBrowserSection onClick={openManageCookies} />
<Lines n={1} />
<LearnMoreSection />
</WithStandardTopMargin>
</div>
</div>
);
return <>{content()}</>;
};