client/components/mma/delivery/records/DeliveryRecordsProblemReview.tsx (651 lines of code) (raw):
import { css } from '@emotion/react';
import {
from,
headlineBold28,
palette,
space,
textSans17,
textSansBold17,
until,
} from '@guardian/source/foundations';
import { Button, Stack } from '@guardian/source/react-components';
import { capitalize } from 'lodash';
import { useContext, useState } from 'react';
import { Navigate, useLocation, useNavigate } from 'react-router-dom';
import { parseDate } from '@/shared/dates';
import type {
DeliveryRecordApiItem,
PaidSubscriptionPlan,
} from '@/shared/productResponse';
import { getMainPlan, isPaidSubscriptionPlan } from '@/shared/productResponse';
import { CallCentreEmailAndNumbers } from '../../../shared/CallCenterEmailAndNumbers';
import type {
PotentialHolidayStopsResponse,
RawPotentialHolidayStopDetail,
} from '../../holiday/HolidayStopApi';
import {
getPotentialHolidayStopsFetcher,
PotentialHolidayStopsAsyncLoader,
} from '../../holiday/HolidayStopApi';
import { InfoIconDark } from '../../shared/assets/InfoIconDark';
import { ProgressIndicator } from '../../shared/ProgressIndicator';
import { DeliveryRecordCard } from './DeliveryRecordCard';
import { PageStatus } from './DeliveryRecords';
import type {
ContactPhoneNumbers,
DeliveryRecordsPostPayload,
} from './deliveryRecordsApi';
import type {
DeliveryRecordsContextInterface,
DeliveryRecordsRouterState,
} from './DeliveryRecordsContainer';
import {
checkForExistingDeliveryProblem,
DeliveryRecordsContext,
} from './DeliveryRecordsContainer';
import { DeliveryRecordCreditContext } from './DeliveryRecordsProblemContext';
import { UserPhoneNumber } from './UserPhoneNumber';
export const DeliveryRecordsProblemReview = () => {
const location = useLocation();
const routerState = location.state as DeliveryRecordsRouterState;
const { productDetail } = useContext(
DeliveryRecordsContext,
) as DeliveryRecordsContextInterface;
if (!routerState) {
return <Navigate to=".." />;
}
const subscription = productDetail.subscription;
const isTestUser = productDetail.isTestUser;
const problemStartDate =
routerState.affectedRecords[routerState.affectedRecords.length - 1]
.deliveryDate;
const problemEndDate = routerState.affectedRecords[0].deliveryDate;
const renderReviewDetails = (
potentialHolidayStopsResponseWithCredits: PotentialHolidayStopsResponse,
) => {
const totalCreditAmount: number =
potentialHolidayStopsResponseWithCredits.potentials.length &&
potentialHolidayStopsResponseWithCredits.potentials
.flatMap((x) => [Math.abs(x.credit || 0)])
.reduce(
(accumulator, currentValue) => accumulator + currentValue,
);
return (
<DeliveryRecordsProblemReviewFC
showCredit
creditDate={
potentialHolidayStopsResponseWithCredits.nextInvoiceDateAfterToday
}
relatedPublications={
potentialHolidayStopsResponseWithCredits.potentials
}
totalCreditAmount={totalCreditAmount}
/>
);
};
return routerState.showProblemCredit ? (
<PotentialHolidayStopsAsyncLoader
fetch={getPotentialHolidayStopsFetcher(
subscription.subscriptionId,
parseDate(problemStartDate).date,
parseDate(problemEndDate).date,
isTestUser,
)}
render={renderReviewDetails}
loadingMessage="Generating your report"
/>
) : (
<DeliveryRecordsProblemReviewFC />
);
};
interface DeliveryRecordsProblemReviewFCProps {
showCredit?: true;
creditDate?: string;
totalCreditAmount?: number;
relatedPublications?: RawPotentialHolidayStopDetail[];
}
const DeliveryRecordsProblemReviewFC = (
props: DeliveryRecordsProblemReviewFCProps,
) => {
const location = useLocation();
const routerState = location.state as DeliveryRecordsRouterState;
const navigate = useNavigate();
const { productType, productDetail, data } = useContext(
DeliveryRecordsContext,
) as DeliveryRecordsContextInterface;
const mainPlan = getMainPlan(
productDetail.subscription,
) as PaidSubscriptionPlan;
if (!isPaidSubscriptionPlan(mainPlan)) {
throw new Error(
'mainPlan is not a PaidSubscriptionPlan in deliveryRecordsProblemReview',
);
}
const contactPhoneNumbers = data.contactPhoneNumbers;
const apiProductName =
productType.delivery.records.productNameForProblemReport;
const repeatDeliveryProblem = checkForExistingDeliveryProblem(data.results);
const subscription = productDetail.subscription;
const productName = capitalize(
productType.shortFriendlyName || productType.friendlyName,
);
const subscriptionCurrency = mainPlan.currency;
const deliveryProblemMap = data.deliveryProblemMap;
const [newPhoneNumbers, setPhoneNumbers] = useState<
ContactPhoneNumbers | undefined
>(contactPhoneNumbers);
const [showCallCenterNumbers, setShowCallCenterNumbers] =
useState<boolean>(false);
const dtCss: string = `
font-weight: bold;
display: inline-block;
vertical-align: top;
min-width: 12ch;
${from.tablet} {
min-width: 16ch;
}
`;
const ddCss: string = `
margin: 0;
display: inline-block;
vertical-align: top;
`;
const deliveryIssuePostPayload: DeliveryRecordsPostPayload = {
productName: apiProductName,
description: routerState.problemType?.message,
problemType: routerState.problemType?.category,
repeatDeliveryProblem: repeatDeliveryProblem,
deliveryRecords:
props.showCredit && props.relatedPublications
? routerState.affectedRecords.map((record) => {
const matchingPublication =
props.relatedPublications?.find(
(x) =>
x.publicationDate === record.deliveryDate,
);
return {
id: record.id,
creditAmount: matchingPublication?.credit,
invoiceDate: matchingPublication
? props.creditDate
: undefined,
};
})
: routerState.affectedRecords.map((record) => {
return { id: record.id };
}),
...((newPhoneNumbers?.Phone ||
newPhoneNumbers?.HomePhone ||
newPhoneNumbers?.MobilePhone ||
newPhoneNumbers?.OtherPhone) && {
newContactPhoneNumbers: newPhoneNumbers,
}),
};
return (
<DeliveryRecordCreditContext.Provider
value={{
showCredit: routerState.showProblemCredit,
...(props.totalCreditAmount && {
creditAmount: `${subscriptionCurrency}${props.totalCreditAmount.toFixed(
2,
)}`,
}),
...(props.creditDate && {
creditDate:
props.creditDate &&
parseDate(props.creditDate).dateStr(),
}),
}}
>
<ProgressIndicator
steps={[
{ title: 'Update' },
{ title: 'Review', isCurrentStep: true },
{ title: 'Confirmation' },
]}
additionalCSS={css`
margin: ${space[5]}px 0 ${space[12]}px;
`}
/>
<h2
css={css`
border-top: 1px solid ${palette.neutral['86']};
${headlineBold28};
${until.tablet} {
font-size: 1.25rem;
line-height: 1.6;
}
`}
>
Delivery report review
</h2>
<section
css={css`
border: 1px solid ${palette.neutral['86']};
`}
>
<h2
css={css`
margin: 0;
padding: ${space[3]}px;
background-color: ${palette.neutral['97']};
border-bottom: 1px solid ${palette.neutral['86']};
${textSansBold17};
${from.tablet} {
padding: ${space[3]}px ${space[5]}px;
}
`}
>
Step 4. Please review your report details
</h2>
<dl
css={css`
padding: 0 ${space[3]}px;
${textSans17};
display: flex;
flex-wrap: wrap;
flex-direction: column;
justify-content: space-between;
${from.tablet} {
flex-direction: initial;
padding: 0 ${space[5]}px;
}
`}
>
<div
css={css`
flex-grow: 1;
`}
>
<dt
css={css`
${dtCss}
`}
>
Subscription ID:
</dt>
<dd
css={css`
${ddCss}
`}
data-qm-masking="blocklist"
>
{subscription.subscriptionId}
</dd>
</div>
<div
css={css`
flex-grow: 1;
margin-top: 16px;
${from.tablet} {
margin-top: 0;
}
`}
>
<dt
css={css`
${dtCss}
${from.tablet} {
min-width: 10ch;
}
`}
>
Product:
</dt>
<dd
css={css`
${ddCss}
`}
>
{productName}
</dd>
</div>
<div
css={css`
margin-top: 16px;
width: 100%;
${from.tablet} {
margin-top: ${space[5]}px;
}
`}
>
<dt
css={css`
${dtCss}
`}
>
Type of problem:
</dt>
<dd
css={css`
${ddCss}
max-width: calc(100% - 12ch);
${from.tablet} {
max-width: calc(100% - 16ch);
}
`}
>
<h4
css={css`
${textSansBold17};
margin: 0;
`}
>
{routerState.problemType &&
routerState.problemType.category}
</h4>
{routerState.problemType?.message && (
<p
css={css`
margin: 0;
`}
>
{routerState.problemType?.message}
</p>
)}
</dd>
</div>
<div
css={css`
margin-top: 16px;
width: 100%;
${from.tablet} {
margin-top: ${space[5]}px;
}
`}
>
<dt
css={css`
${dtCss}
`}
>
Selected Issue(s):
</dt>
<dd
css={css`
${ddCss}
max-width: calc(100% - 12ch);
${from.tablet} {
max-width: calc(100% - 16ch);
}
`}
>
<h4
css={css`
${textSans17};
margin: 0;
`}
>
{routerState.affectedRecords?.length}
</h4>
</dd>
</div>
</dl>
{routerState && routerState.affectedRecords?.length ? (
<div
css={css`
padding: 0 ${space[3]}px;
margin-bottom: ${space[3]}px;
${from.tablet} {
padding: 0 ${space[5]}px;
margin-bottom: ${space[5]}px;
}
`}
>
{routerState.affectedRecords.map(
(
deliveryRecord: DeliveryRecordApiItem,
listIndex,
) => (
<DeliveryRecordCard
key={deliveryRecord.id}
deliveryRecord={deliveryRecord}
listIndex={listIndex}
pageStatus={PageStatus.ReadOnly}
showDeliveryInstructions={
productType.delivery.records
.showDeliveryInstructions
}
deliveryProblemMap={deliveryProblemMap}
/>
),
)}
</div>
) : (
<p>There aren't any delivery records to show you yet</p>
)}
{routerState.showProblemCredit && props.totalCreditAmount ? (
<>
<span
css={css`
position: relative;
display: block;
margin: ${space[3]}px;
padding: 0 ${space[3]}px 0
${space[5] + space[2]}px;
${textSans17};
${from.tablet} {
margin: ${space[5]}px;
padding: 0 ${space[5]}px 0
${space[5] + space[2]}px;
}
`}
>
<i
css={css`
position: absolute;
top: 4px;
left: 0;
`}
>
<InfoIconDark fillColor={palette.brand[500]} />
</i>
We apologise for any inconvenience caused and will
credit you the amount shown below once you submit
your report. We continually review these reports and
use them to improve our service. If you’re not
satisfied with this outcome please{' '}
<span
css={css`
color: ${palette.brand[500]};
text-decoration: underline;
cursor: pointer;
`}
onClick={() =>
setShowCallCenterNumbers(
!showCallCenterNumbers,
)
}
>
contact us
</span>{' '}
instead of submitting your report.
</span>
<dl
css={css`
${textSans17};
padding: ${space[3]}px;
margin: ${space[3]}px;
background-color: ${palette.neutral['97']};
${from.tablet} {
padding: ${space[5]}px;
margin: ${space[5]}px;
}
`}
>
<div
css={css`
display: inline-block;
`}
>
<dt
css={css`
display: inline-block;
font-weight: bold;
min-width: 12ch;
${from.tablet} {
min-width: 0;
}
`}
>
Credit amount:
</dt>
<dd
css={css`
display: inline-block;
margin-left: 0;
font-weight: bold;
${from.tablet} {
margin-left: ${space[9]}px;
min-width: 9ch;
}
`}
>
{subscriptionCurrency}
{props.totalCreditAmount.toFixed(2)}
</dd>
</div>
<div
css={css`
display: inline-block;
`}
>
<dt
css={css`
display: inline-block;
font-weight: bold;
min-width: 12ch;
${from.tablet} {
min-width: 0;
}
`}
>
Credit date:
</dt>
<dd
css={css`
display: inline-block;
margin-left: 0;
${from.tablet} {
margin-left: ${space[9]}px;
}
`}
>
{props.creditDate &&
parseDate(props.creditDate).dateStr()}
</dd>
</div>
</dl>
</>
) : (
<>
<span
css={css`
position: relative;
display: block;
margin: ${space[3]}px;
padding: 0 ${space[3]}px 0
${space[5] + space[2]}px;
${textSans17};
${from.tablet} {
margin: ${space[5]}px;
padding: 0 ${space[5]}px 0
${space[5] + space[2]}px;
}
`}
>
<i
css={css`
position: absolute;
top: 4px;
left: 0;
`}
>
<InfoIconDark fillColor={palette.brand[500]} />
</i>
Once you submit your report, your case will be
marked as high priority. Our customer service team
will try their best to contact you as soon as
possible to resolve the issue.
</span>
{newPhoneNumbers && (
<UserPhoneNumber
existingPhoneNumbers={contactPhoneNumbers}
callback={(newNumbers: ContactPhoneNumbers) => {
setPhoneNumbers(newNumbers);
}}
/>
)}
</>
)}
</section>
<div
css={css`
margin-top: ${space[9]}px;
`}
>
<Button
onClick={() => {
navigate('../confirmed', {
state: {
productDetail,
affectedRecords: routerState.affectedRecords,
deliveryIssuePostPayload:
deliveryIssuePostPayload,
},
});
}}
>
Submit your report
</Button>
<Button
cssOverrides={css`
${textSans17};
background-color: transparent;
font-weight: bold;
margin-left: 22px;
padding: 0;
color: ${palette.brand[400]};
:hover {
background-color: transparent;
}
`}
onClick={() => {
navigate('..', {
state: {
productDetail,
},
});
}}
>
Cancel
</Button>
</div>
<Stack space={5}>
<p
css={css`
${textSans17};
margin: ${space[6]}px 0 0;
color: ${palette.neutral[46]};
`}
>
If your delivery is not shown above, or you’d like to talk
to someone,{' '}
<span
css={css`
color: ${palette.brand[500]};
text-decoration: underline;
cursor: pointer;
`}
onClick={() =>
setShowCallCenterNumbers(!showCallCenterNumbers)
}
>
contact us
</span>
</p>
{showCallCenterNumbers && <CallCentreEmailAndNumbers />}
</Stack>
</DeliveryRecordCreditContext.Provider>
);
};