From 65064c0e7e5d157d0cd4413c8cd80bb72c480446 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dafydd=20Ll=C5=B7r=20Pearson?= Date: Wed, 20 Nov 2024 13:29:34 +0000 Subject: [PATCH 01/10] feat: Update model, add optional fee breakdown section in Editor modal --- .../@planx/components/Pay/Editor/Editor.tsx | 2 ++ .../Pay/Editor/FeeBreakdownSection.tsx | 32 +++++++++++++++++++ .../src/@planx/components/Pay/model.ts | 1 + 3 files changed, 35 insertions(+) create mode 100644 editor.planx.uk/src/@planx/components/Pay/Editor/FeeBreakdownSection.tsx diff --git a/editor.planx.uk/src/@planx/components/Pay/Editor/Editor.tsx b/editor.planx.uk/src/@planx/components/Pay/Editor/Editor.tsx index 8f0a9fbb6c..cbb7372bf4 100644 --- a/editor.planx.uk/src/@planx/components/Pay/Editor/Editor.tsx +++ b/editor.planx.uk/src/@planx/components/Pay/Editor/Editor.tsx @@ -22,6 +22,7 @@ import { ICONS } from "../../shared/icons"; import { EditorProps } from "../../shared/types"; import { GovPayMetadataSection } from "./GovPayMetadataSection"; import { InviteToPaySection } from "./InviteToPaySection"; +import { FeeBreakdownSection } from "./FeeBreakdownSection"; export type Props = EditorProps; @@ -114,6 +115,7 @@ const Component: React.FC = (props: Props) => { + { + const { values, setFieldValue } = useFormikContext(); + + return ( + + + + + setFieldValue("showFeeBreakdown", !values.showFeeBreakdown) + } + label="Display a breakdown of the fee to the applicant" + /> + + <> + { Boolean(values.showFeeBreakdown) &&

Fee breakdown fields here

} + +
+
+ ) +} \ No newline at end of file diff --git a/editor.planx.uk/src/@planx/components/Pay/model.ts b/editor.planx.uk/src/@planx/components/Pay/model.ts index 0a905ce14f..b26f10c38c 100644 --- a/editor.planx.uk/src/@planx/components/Pay/model.ts +++ b/editor.planx.uk/src/@planx/components/Pay/model.ts @@ -27,6 +27,7 @@ export interface Pay extends BaseNodeData { yourDetailsDescription?: string; yourDetailsLabel?: string; govPayMetadata: GovPayMetadata[]; + showFeeBreakdown?: boolean; } export const toPence = (decimal: number) => Math.trunc(decimal * 100); From 1ebd54a82864a643eda3f60cae2be33bee7afffa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dafydd=20Ll=C5=B7r=20Pearson?= Date: Mon, 25 Nov 2024 13:37:04 +0000 Subject: [PATCH 02/10] chore: Use existing type to get new `showFeeBreakdown` prop --- .../@planx/components/Pay/Public/Confirm.tsx | 21 +++++++------------ .../src/@planx/components/Pay/Public/Pay.tsx | 11 ++++++---- 2 files changed, 14 insertions(+), 18 deletions(-) diff --git a/editor.planx.uk/src/@planx/components/Pay/Public/Confirm.tsx b/editor.planx.uk/src/@planx/components/Pay/Public/Confirm.tsx index 0cf82f91d8..e302bfca2d 100644 --- a/editor.planx.uk/src/@planx/components/Pay/Public/Confirm.tsx +++ b/editor.planx.uk/src/@planx/components/Pay/Public/Confirm.tsx @@ -14,30 +14,23 @@ import FormWrapper from "ui/public/FormWrapper"; import ErrorSummary from "ui/shared/ErrorSummary/ErrorSummary"; import ReactMarkdownOrHtml from "ui/shared/ReactMarkdownOrHtml/ReactMarkdownOrHtml"; -import { formattedPriceWithCurrencySymbol, getDefaultContent } from "../model"; +import { + formattedPriceWithCurrencySymbol, + getDefaultContent, + Pay, +} from "../model"; import InviteToPayForm, { InviteToPayFormProps } from "./InviteToPayForm"; import { PAY_API_ERROR_UNSUPPORTED_TEAM } from "./Pay"; -export interface Props { +export interface Props extends Omit { title?: string; - bannerTitle?: string; - description?: string; fee: number; - instructionsTitle?: string; - instructionsDescription?: string; showInviteToPay?: boolean; - secondaryPageTitle?: string; - nomineeTitle?: string; - nomineeDescription?: string; - yourDetailsTitle?: string; - yourDetailsDescription?: string; - yourDetailsLabel?: string; paymentStatus?: PaymentStatus; buttonTitle?: string; onConfirm: () => void; error?: string; hideFeeBanner?: boolean; - hidePay?: boolean; } interface PayBodyProps extends Props { @@ -86,7 +79,7 @@ const PayBody: React.FC = (props) => { - {props.instructionsTitle || defaults.instructionsTitle } + {props.instructionsTitle || defaults.instructionsTitle} [ state.id, state.sessionId, @@ -63,7 +68,6 @@ function Component(props: Props) { state.computePassport(), state.previewEnvironment, state.teamSlug, - state.flowSlug, ]); const fee = props.fn ? Number(passport.data?.[props.fn]) : 0; @@ -303,7 +307,6 @@ function Component(props: Props) { } showInviteToPay={showPayOptions && isTeamSupported} paymentStatus={govUkPayment?.state?.status} - hidePay={props.hidePay} /> ) : ( From d3b24cc6764565aff39e7cdd173cfa73818d9351 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dafydd=20Ll=C5=B7r=20Pearson?= Date: Mon, 25 Nov 2024 13:46:04 +0000 Subject: [PATCH 03/10] feat: Dummy initial UI --- .../Pay/Editor/FeeBreakdownSection.tsx | 10 +- .../@planx/components/Pay/Public/Confirm.tsx | 2 + .../components/Pay/Public/FeeBreakdown.tsx | 100 ++++++++++++++++++ 3 files changed, 107 insertions(+), 5 deletions(-) create mode 100644 editor.planx.uk/src/@planx/components/Pay/Public/FeeBreakdown.tsx diff --git a/editor.planx.uk/src/@planx/components/Pay/Editor/FeeBreakdownSection.tsx b/editor.planx.uk/src/@planx/components/Pay/Editor/FeeBreakdownSection.tsx index 9b8d022bb9..4e42b5bf85 100644 --- a/editor.planx.uk/src/@planx/components/Pay/Editor/FeeBreakdownSection.tsx +++ b/editor.planx.uk/src/@planx/components/Pay/Editor/FeeBreakdownSection.tsx @@ -1,11 +1,11 @@ +import ReceiptLongIcon from "@mui/icons-material/ReceiptLong"; +import { useFormikContext } from "formik"; import React from "react"; import ModalSection from "ui/editor/ModalSection"; import ModalSectionContent from "ui/editor/ModalSectionContent"; import InputRow from "ui/shared/InputRow"; import { Switch } from "ui/shared/Switch"; -import ReceiptLongIcon from '@mui/icons-material/ReceiptLong'; -import { useFormikContext } from "formik"; import { Pay } from "../model"; export const FeeBreakdownSection: React.FC = () => { @@ -24,9 +24,9 @@ export const FeeBreakdownSection: React.FC = () => { /> <> - { Boolean(values.showFeeBreakdown) &&

Fee breakdown fields here

} + {Boolean(values.showFeeBreakdown) &&

Fee breakdown fields here

} - ) -} \ No newline at end of file + ); +}; diff --git a/editor.planx.uk/src/@planx/components/Pay/Public/Confirm.tsx b/editor.planx.uk/src/@planx/components/Pay/Public/Confirm.tsx index e302bfca2d..31913894d9 100644 --- a/editor.planx.uk/src/@planx/components/Pay/Public/Confirm.tsx +++ b/editor.planx.uk/src/@planx/components/Pay/Public/Confirm.tsx @@ -19,6 +19,7 @@ import { getDefaultContent, Pay, } from "../model"; +import { FeeBreakdown } from "./FeeBreakdown"; import InviteToPayForm, { InviteToPayFormProps } from "./InviteToPayForm"; import { PAY_API_ERROR_UNSUPPORTED_TEAM } from "./Pay"; @@ -180,6 +181,7 @@ export default function Confirm(props: Props) { /> + {props.showFeeBreakdown && } )} {page === "Pay" ? ( diff --git a/editor.planx.uk/src/@planx/components/Pay/Public/FeeBreakdown.tsx b/editor.planx.uk/src/@planx/components/Pay/Public/FeeBreakdown.tsx new file mode 100644 index 0000000000..75d9328a05 --- /dev/null +++ b/editor.planx.uk/src/@planx/components/Pay/Public/FeeBreakdown.tsx @@ -0,0 +1,100 @@ +import Box from "@mui/material/Box"; +import Table from "@mui/material/Table"; +import TableBody from "@mui/material/TableBody"; +import TableCell from "@mui/material/TableCell"; +import TableContainer from "@mui/material/TableContainer"; +import TableHead from "@mui/material/TableHead"; +import TableRow from "@mui/material/TableRow"; +import Typography from "@mui/material/Typography"; +import React from "react"; +import { FONT_WEIGHT_SEMI_BOLD } from "theme"; + +import { formattedPriceWithCurrencySymbol } from "../model"; + +const VAT_RATE = 20; + +const DESCRIPTION = + "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat."; + +const Header = () => ( + + + + Description + + + Amount + + + +); + +const PlanningFee = () => ( + + Planning fee + {formattedPriceWithCurrencySymbol(100)} + +); + +const Exemptions = () => ( + + Exemptions + {formattedPriceWithCurrencySymbol(-20)} + +); + +const Reductions = () => ( + + Reductions + {formattedPriceWithCurrencySymbol(-30)} + +); + +const ServiceCharge = () => ( + + Service charge + {formattedPriceWithCurrencySymbol(30)} + +); + +const VAT = () => ( + + {`VAT (${VAT_RATE}%)`} + - + +); + +const Total = () => ( + + Total + + {formattedPriceWithCurrencySymbol(50)} + + +); + +export const FeeBreakdown: React.FC = () => { + return ( + + + Fee breakdown + + + {DESCRIPTION} + + + +
+ + + + + + + + +
+
+
+ ); +}; From 6c693dc2f677ca7c07837bbcaf8b6e49cc85342d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dafydd=20Ll=C5=B7r=20Pearson?= Date: Mon, 25 Nov 2024 13:46:58 +0000 Subject: [PATCH 04/10] feat(storybook): Initial setup --- .../components/Pay/Public/Pay.stories.tsx | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/editor.planx.uk/src/@planx/components/Pay/Public/Pay.stories.tsx b/editor.planx.uk/src/@planx/components/Pay/Public/Pay.stories.tsx index d554de6d28..efa6a44b1f 100644 --- a/editor.planx.uk/src/@planx/components/Pay/Public/Pay.stories.tsx +++ b/editor.planx.uk/src/@planx/components/Pay/Public/Pay.stories.tsx @@ -71,3 +71,20 @@ export const ForInformationOnly = { onConfirm: () => {}, }, } satisfies Story; + +// TODO: Setup fee breakdown amounts +export const WithFeeBreakdown = { + args: { + title: "Pay for your application", + bannerTitle: "The fee is", + description: "The fee covers the cost of processing your application", + fee: 103, + instructionsTitle: "How to pay", + instructionsDescription: "Pay via GOV.UK Pay", + buttonTitle: "Pay", + onConfirm: () => {}, + error: undefined, + showInviteToPay: false, + showFeeBreakdown: true, + }, +} satisfies Story; From 023d2882285a7b1110fdc8d6975d47e2dca15fe4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dafydd=20Ll=C5=B7r=20Pearson?= Date: Mon, 25 Nov 2024 14:05:21 +0000 Subject: [PATCH 05/10] chore: Add feature flag --- .../src/@planx/components/Pay/Editor/FeeBreakdownSection.tsx | 3 +++ .../src/@planx/components/Pay/Public/FeeBreakdown.tsx | 3 +++ .../src/@planx/components/Pay/Public/Pay.stories.tsx | 3 +++ editor.planx.uk/src/lib/featureFlags.ts | 2 +- 4 files changed, 10 insertions(+), 1 deletion(-) diff --git a/editor.planx.uk/src/@planx/components/Pay/Editor/FeeBreakdownSection.tsx b/editor.planx.uk/src/@planx/components/Pay/Editor/FeeBreakdownSection.tsx index 4e42b5bf85..787e48b235 100644 --- a/editor.planx.uk/src/@planx/components/Pay/Editor/FeeBreakdownSection.tsx +++ b/editor.planx.uk/src/@planx/components/Pay/Editor/FeeBreakdownSection.tsx @@ -1,5 +1,6 @@ import ReceiptLongIcon from "@mui/icons-material/ReceiptLong"; import { useFormikContext } from "formik"; +import { hasFeatureFlag } from "lib/featureFlags"; import React from "react"; import ModalSection from "ui/editor/ModalSection"; import ModalSectionContent from "ui/editor/ModalSectionContent"; @@ -11,6 +12,8 @@ import { Pay } from "../model"; export const FeeBreakdownSection: React.FC = () => { const { values, setFieldValue } = useFormikContext(); + if (!hasFeatureFlag("FEE_BREAKDOWN")) return null; + return ( diff --git a/editor.planx.uk/src/@planx/components/Pay/Public/FeeBreakdown.tsx b/editor.planx.uk/src/@planx/components/Pay/Public/FeeBreakdown.tsx index 75d9328a05..2f82c4b536 100644 --- a/editor.planx.uk/src/@planx/components/Pay/Public/FeeBreakdown.tsx +++ b/editor.planx.uk/src/@planx/components/Pay/Public/FeeBreakdown.tsx @@ -6,6 +6,7 @@ import TableContainer from "@mui/material/TableContainer"; import TableHead from "@mui/material/TableHead"; import TableRow from "@mui/material/TableRow"; import Typography from "@mui/material/Typography"; +import { hasFeatureFlag } from "lib/featureFlags"; import React from "react"; import { FONT_WEIGHT_SEMI_BOLD } from "theme"; @@ -74,6 +75,8 @@ const Total = () => ( ); export const FeeBreakdown: React.FC = () => { + if (!hasFeatureFlag("FEE_BREAKDOWN")) return null; + return ( diff --git a/editor.planx.uk/src/@planx/components/Pay/Public/Pay.stories.tsx b/editor.planx.uk/src/@planx/components/Pay/Public/Pay.stories.tsx index efa6a44b1f..619503a550 100644 --- a/editor.planx.uk/src/@planx/components/Pay/Public/Pay.stories.tsx +++ b/editor.planx.uk/src/@planx/components/Pay/Public/Pay.stories.tsx @@ -17,6 +17,9 @@ const meta = { }), }, }, + loaders: [ + () => window.localStorage.setItem("FEATURE_FLAGS", '["FEE_BREAKDOWN"]'), + ], } satisfies Meta; type Story = StoryObj; diff --git a/editor.planx.uk/src/lib/featureFlags.ts b/editor.planx.uk/src/lib/featureFlags.ts index 5fd7349747..1e34728156 100644 --- a/editor.planx.uk/src/lib/featureFlags.ts +++ b/editor.planx.uk/src/lib/featureFlags.ts @@ -1,5 +1,5 @@ // add/edit/remove feature flags in array below -const AVAILABLE_FEATURE_FLAGS = [] as const; +const AVAILABLE_FEATURE_FLAGS = ["FEE_BREAKDOWN"] as const; type FeatureFlag = (typeof AVAILABLE_FEATURE_FLAGS)[number]; From 6ab0d49b962fdc1eb7aed5d54ef2455af8267ecc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dafydd=20Ll=C5=B7r=20Pearson?= Date: Mon, 25 Nov 2024 14:14:36 +0000 Subject: [PATCH 06/10] feat: Simplify editor interface (for now!) --- .../src/@planx/components/Pay/Editor/FeeBreakdownSection.tsx | 3 --- 1 file changed, 3 deletions(-) diff --git a/editor.planx.uk/src/@planx/components/Pay/Editor/FeeBreakdownSection.tsx b/editor.planx.uk/src/@planx/components/Pay/Editor/FeeBreakdownSection.tsx index 787e48b235..5db61ca8ae 100644 --- a/editor.planx.uk/src/@planx/components/Pay/Editor/FeeBreakdownSection.tsx +++ b/editor.planx.uk/src/@planx/components/Pay/Editor/FeeBreakdownSection.tsx @@ -26,9 +26,6 @@ export const FeeBreakdownSection: React.FC = () => { label="Display a breakdown of the fee to the applicant" /> - <> - {Boolean(values.showFeeBreakdown) &&

Fee breakdown fields here

} -
); From 370f4ddc255b79feb9008a30bfdeb04e59b16e9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dafydd=20Ll=C5=B7r=20Pearson?= Date: Mon, 25 Nov 2024 14:18:32 +0000 Subject: [PATCH 07/10] fix: Import order, default values --- .../@planx/components/Pay/Editor/Editor.tsx | 25 ++++++------------- .../Pay/Editor/InviteToPaySection.tsx | 12 ++++----- .../src/@planx/components/Pay/model.ts | 3 ++- 3 files changed, 14 insertions(+), 26 deletions(-) diff --git a/editor.planx.uk/src/@planx/components/Pay/Editor/Editor.tsx b/editor.planx.uk/src/@planx/components/Pay/Editor/Editor.tsx index cbb7372bf4..450c13a4d5 100644 --- a/editor.planx.uk/src/@planx/components/Pay/Editor/Editor.tsx +++ b/editor.planx.uk/src/@planx/components/Pay/Editor/Editor.tsx @@ -1,11 +1,5 @@ -import { - ComponentType as TYPES, -} from "@opensystemslab/planx-core/types"; -import { - parsePay, - Pay, - validationSchema, -} from "@planx/components/Pay/model"; +import { ComponentType as TYPES } from "@opensystemslab/planx-core/types"; +import { parsePay, Pay, validationSchema } from "@planx/components/Pay/model"; import { Form, Formik } from "formik"; import React from "react"; import { ComponentTagSelect } from "ui/editor/ComponentTagSelect"; @@ -20,14 +14,13 @@ import { Switch } from "ui/shared/Switch"; import { ICONS } from "../../shared/icons"; import { EditorProps } from "../../shared/types"; +import { FeeBreakdownSection } from "./FeeBreakdownSection"; import { GovPayMetadataSection } from "./GovPayMetadataSection"; import { InviteToPaySection } from "./InviteToPaySection"; -import { FeeBreakdownSection } from "./FeeBreakdownSection"; export type Props = EditorProps; const Component: React.FC = (props: Props) => { - const onSubmit = (newValues: Pay) => { if (props.handleSubmit) { props.handleSubmit({ type: TYPES.Pay, data: newValues }); @@ -42,11 +35,7 @@ const Component: React.FC = (props: Props) => { validateOnChange={true} validateOnBlur={true} > - {({ - values, - handleChange, - setFieldValue, - }) => ( + {({ values, handleChange, setFieldValue }) => (