Skip to content

Commit

Permalink
refactor: Initial pass through, unit tests passing
Browse files Browse the repository at this point in the history
  • Loading branch information
DafyddLlyr committed Dec 11, 2024
1 parent 68d8cda commit 2b6ab91
Show file tree
Hide file tree
Showing 3 changed files with 137 additions and 46 deletions.
152 changes: 113 additions & 39 deletions editor.planx.uk/src/@planx/components/Pay/Public/Confirm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ import { FeeBreakdown } from "./FeeBreakdown/FeeBreakdown";
import InviteToPayForm, { InviteToPayFormProps } from "./InviteToPayForm";
import { PAY_API_ERROR_UNSUPPORTED_TEAM } from "./Pay";

type State = "error" | "informationOnly" | "inviteToPay" | "zeroFee" | "pay";

export interface Props extends Omit<Pay, "title" | "fn" | "govPayMetadata"> {
title?: string;
fee: number;
Expand All @@ -48,35 +50,35 @@ const PayText = styled(Box)(({ theme }) => ({
},
}));

const Error: React.FC<Props> = ({ onConfirm, error }) => {
if (error?.startsWith(PAY_API_ERROR_UNSUPPORTED_TEAM)) {
return (
<Card handleSubmit={onConfirm} isValid>
<ErrorSummary
format="error"
heading={error}
message="Click continue to skip payment and proceed with your application for testing."
/>
</Card>
);
}

return (
<Card>
<ErrorSummary
format="error"
heading={error}
message="This error has been logged and our team will see it soon. You can safely close this tab and try resuming again soon by returning to this URL."
/>
</Card>
);
};

const PayBody: React.FC<PayBodyProps> = (props) => {
const path = useStore((state) => state.path);
const isSaveReturn = path === ApplicationPath.SaveAndReturn;
const defaults = getDefaultContent();

if (props.error) {
if (props.error.startsWith(PAY_API_ERROR_UNSUPPORTED_TEAM)) {
return (
<Card handleSubmit={props.onConfirm} isValid>
<ErrorSummary
format="error"
heading={props.error}
message="Click continue to skip payment and proceed with your application for testing."
/>
</Card>
);
} else {
return (
<Card>
<ErrorSummary
format="error"
heading={props.error}
message="This error has been logged and our team will see it soon. You can safely close this tab and try resuming again soon by returning to this URL."
/>
</Card>
);
}
}

return (
<Card>
<PayText>
Expand All @@ -95,11 +97,9 @@ const PayBody: React.FC<PayBodyProps> = (props) => {
size="large"
onClick={props.onConfirm}
>
{props.hidePay
? "Continue"
: props.buttonTitle || "Pay now using GOV.UK Pay"}
{props.buttonTitle || "Pay now using GOV.UK Pay"}
</Button>
{!props.hidePay && props.showInviteToPay && (
{props.showInviteToPay && (
<Button
variant="contained"
color="secondary"
Expand All @@ -117,17 +117,87 @@ const PayBody: React.FC<PayBodyProps> = (props) => {
);
};

const InformationOnly: React.FC<Props> = (props) => {
const path = useStore((state) => state.path);
const isSaveReturn = path === ApplicationPath.SaveAndReturn;
const defaults = getDefaultContent();

return (
<Card>
<PayText>
<Typography variant="h2" component={props.hideFeeBanner ? "h2" : "h3"}>
{props.instructionsTitle || defaults.instructionsTitle}
</Typography>
<ReactMarkdownOrHtml
source={
props.instructionsDescription || defaults.instructionsDescription
}
openLinksOnNewTab
/>
<Button
variant="contained"
color="primary"
size="large"
onClick={props.onConfirm}
>
Continue
</Button>
{isSaveReturn && <SaveResumeButton />}
</PayText>
</Card>
);
};

const ZeroFee: React.FC<Props> = (props) => {
const path = useStore((state) => state.path);
const isSaveReturn = path === ApplicationPath.SaveAndReturn;
const defaults = getDefaultContent();

return (
<Card>
<PayText>
<Typography variant="h2" component="h3">
{props.instructionsTitle || defaults.instructionsTitle}
</Typography>
<ReactMarkdownOrHtml
source={
props.instructionsDescription || defaults.instructionsDescription
}
openLinksOnNewTab
/>
<Button
variant="contained"
color="primary"
size="large"
onClick={props.onConfirm}
>
Continue
</Button>
{isSaveReturn && <SaveResumeButton />}
</PayText>
</Card>
);
};

const getInitialState = (props: Props): State => {
if (props.error) return "error";
if (props.hidePay) return "informationOnly";
if (props.fee === 0) return "zeroFee";

return "pay";
};

export default function Confirm(props: Props) {
const theme = useTheme();
const [page, setPage] = useState<"Pay" | "InviteToPay">("Pay");
const [state, setState] = useState<State>(getInitialState(props));

const defaults = getDefaultContent();

const changePage = () => {
if (page === "Pay" && !props.paymentStatus) {
setPage("InviteToPay");
if (state === "pay" && !props.paymentStatus) {
setState("inviteToPay");
} else {
setPage("Pay");
setState("pay");
}
};

Expand All @@ -146,10 +216,10 @@ export default function Confirm(props: Props) {
<>
<Container maxWidth="contentWrap">
<Typography variant="h2" component="h1" align="left" pb={3}>
{page === "Pay" ? props.title : props.secondaryPageTitle}
{state === "inviteToPay" ? props.secondaryPageTitle : props.title}
</Typography>
</Container>
{page === "Pay" && !props.hideFeeBanner && (
{state !== "inviteToPay" && !props.hideFeeBanner && (
<Banner
color={{
background: theme.palette.info.light,
Expand Down Expand Up @@ -185,11 +255,15 @@ export default function Confirm(props: Props) {
{hasFeatureFlag("FEE_BREAKDOWN") && <FeeBreakdown />}
</Banner>
)}
{page === "Pay" ? (
<PayBody changePage={changePage} {...props} />
) : (
<InviteToPayForm {...inviteToPayFormProps} />
)}
{
{
pay: <PayBody {...props} changePage={changePage} />,
informationOnly: <InformationOnly {...props} />,
inviteToPay: <InviteToPayForm {...inviteToPayFormProps} />,
error: <Error {...props} />,
zeroFee: <ZeroFee {...props} />,
}[state]
}
</>
</Box>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import ErrorWrapper from "ui/shared/ErrorWrapper";
import Input from "ui/shared/Input/Input";
import ReactMarkdownOrHtml from "ui/shared/ReactMarkdownOrHtml/ReactMarkdownOrHtml";
import { object, string } from "yup";

import { getDefaultContent } from "../model";

// Passport keys which will be used to display a preview of the session to the payee as part of their journey
Expand Down Expand Up @@ -229,7 +230,10 @@ const InviteToPayForm: React.FC<InviteToPayFormProps> = ({
/>
</Typography>
)}
<InputLabel label={yourDetailsLabel || defaults.yourDetailsLabel } htmlFor="applicantName">
<InputLabel
label={yourDetailsLabel || defaults.yourDetailsLabel}
htmlFor="applicantName"
>
<Input
bordered
name="applicantName"
Expand Down
25 changes: 19 additions & 6 deletions editor.planx.uk/src/@planx/components/Pay/Public/Pay.test.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { PaymentStatus } from "@opensystemslab/planx-core/types";
import { ComponentType as TYPES } from "@opensystemslab/planx-core/types";
import { screen } from "@testing-library/react";
import { logger } from "airbrake";
import { FullStore, Store, useStore } from "pages/FlowEditor/lib/store";
import React from "react";
import { act } from "react-dom/test-utils";
Expand All @@ -22,7 +23,7 @@ vi.spyOn(ReactNavi, "useCurrentRoute").mockImplementation(
);

vi.mock("lib/featureFlags", () => ({
hasFeatureFlag: vi.fn(),
hasFeatureFlag: vi.fn().mockResolvedValue(true),
}));

const resumeButtonText = "Resume an application you have already started";
Expand Down Expand Up @@ -132,15 +133,15 @@ describe("Pay component when fee is undefined or £0", () => {
expect(screen.queryByText("Continue")).not.toBeInTheDocument();
});

it("Skips pay if fee = 0", () => {
it("Allows the user to view a fee breakdown and continue if the fee is zero", async () => {
const handleSubmit = vi.fn();

setState({ flow: flowWithZeroFee, breadcrumbs: breadcrumbs });
expect(getState().computePassport()).toEqual({
data: { "application.fee.payable": ["0"] },
});

setup(
const { getByTestId, user, getByRole } = setup(
<Pay
title="Pay for your application"
fn="application.fee.payable"
Expand All @@ -149,7 +150,14 @@ describe("Pay component when fee is undefined or £0", () => {
/>,
);

// handleSubmit is called to auto-answer Pay (aka "skip" in card sequence)
// Node is not auto-answered
expect(handleSubmit).not.toHaveBeenCalled();

// Fee breakdown displayed
expect(getByTestId("fee-breakdown-table")).toBeVisible();

// User can continue
await user.click(getByRole("button", { name: "Continue" }));
expect(handleSubmit).toHaveBeenCalled();
});

Expand All @@ -166,7 +174,10 @@ describe("Pay component when fee is undefined or £0", () => {
},
};

setState({ flow: flowWithNegativeFee, breadcrumbs: negativeFeeBreadcrumbs });
setState({
flow: flowWithNegativeFee,
breadcrumbs: negativeFeeBreadcrumbs,
});

expect(getState().computePassport()).toEqual({
data: { "application.fee.payable": ["-12"] },
Expand All @@ -183,7 +194,9 @@ describe("Pay component when fee is undefined or £0", () => {

// handleSubmit is called to auto-answer Pay (aka "skip" in card sequence)
expect(handleSubmit).toHaveBeenCalled();
expect(loggerSpy).toHaveBeenCalledWith(expect.stringMatching(/Negative fee calculated/));
expect(loggerSpy).toHaveBeenCalledWith(
expect.stringMatching(/Negative fee calculated/),
);
});
});

Expand Down

0 comments on commit 2b6ab91

Please sign in to comment.