From ff0946dc15759b595c34b6b7fcf93bcbe7dc241d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dafydd=20Ll=C5=B7r=20Pearson?= Date: Wed, 4 Dec 2024 16:09:10 +0000 Subject: [PATCH 1/6] feat: Remove toggle for FeeBreakdown (#4039) --- .../Pay/Editor/FeeBreakdownSection.tsx | 18 +-------- .../@planx/components/Pay/Public/Confirm.tsx | 3 +- .../components/Pay/Public/Pay.stories.tsx | 1 - .../@planx/components/Pay/Public/Pay.test.tsx | 37 ------------------- .../src/@planx/components/Pay/model.ts | 2 - 5 files changed, 4 insertions(+), 57 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 5db61ca8ae..61b495a398 100644 --- a/editor.planx.uk/src/@planx/components/Pay/Editor/FeeBreakdownSection.tsx +++ b/editor.planx.uk/src/@planx/components/Pay/Editor/FeeBreakdownSection.tsx @@ -1,31 +1,17 @@ import ReceiptLongIcon from "@mui/icons-material/ReceiptLong"; -import { useFormikContext } from "formik"; import { hasFeatureFlag } from "lib/featureFlags"; import React from "react"; +import { FeaturePlaceholder } from "ui/editor/FeaturePlaceholder"; 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 { Pay } from "../model"; export const FeeBreakdownSection: React.FC = () => { - const { values, setFieldValue } = useFormikContext(); - if (!hasFeatureFlag("FEE_BREAKDOWN")) return null; return ( - - - setFieldValue("showFeeBreakdown", !values.showFeeBreakdown) - } - label="Display a breakdown of the fee to the applicant" - /> - + ); 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 31913894d9..db484d2d19 100644 --- a/editor.planx.uk/src/@planx/components/Pay/Public/Confirm.tsx +++ b/editor.planx.uk/src/@planx/components/Pay/Public/Confirm.tsx @@ -6,6 +6,7 @@ import Typography from "@mui/material/Typography"; import { PaymentStatus } from "@opensystemslab/planx-core/types"; import Card from "@planx/components/shared/Preview/Card"; import SaveResumeButton from "@planx/components/shared/Preview/SaveResumeButton"; +import { hasFeatureFlag } from "lib/featureFlags"; import { useStore } from "pages/FlowEditor/lib/store"; import React, { useState } from "react"; import { ApplicationPath } from "types"; @@ -181,7 +182,7 @@ export default function Confirm(props: Props) { /> - {props.showFeeBreakdown && } + {hasFeatureFlag("FEE_BREAKDOWN") && } )} {page === "Pay" ? ( 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 619503a550..5de59d2ec8 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 @@ -88,6 +88,5 @@ export const WithFeeBreakdown = { onConfirm: () => {}, error: undefined, showInviteToPay: false, - showFeeBreakdown: true, }, } satisfies Story; diff --git a/editor.planx.uk/src/@planx/components/Pay/Public/Pay.test.tsx b/editor.planx.uk/src/@planx/components/Pay/Public/Pay.test.tsx index be94bc59f8..71e43ed2d5 100644 --- a/editor.planx.uk/src/@planx/components/Pay/Public/Pay.test.tsx +++ b/editor.planx.uk/src/@planx/components/Pay/Public/Pay.test.tsx @@ -487,40 +487,3 @@ describe("the demo user view", () => { expect(errorHeader).toBeInTheDocument(); }); }); - -describe("Displaying the fee breakdown", () => { - beforeAll(() => (initialState = getState())); - afterEach(() => act(() => setState(initialState))); - - test("if the showFeeBreakdown prop is set, the breakdown is displayed to the user", () => { - mockHasFeatureFlag.mockReturnValue(true); - - const { getByRole, getByTestId } = setup( - , - ); - - expect(mockHasFeatureFlag).toHaveBeenCalledWith("FEE_BREAKDOWN"); - expect(mockHasFeatureFlag).toHaveBeenCalledTimes(1); - - expect( - getByRole("heading", { level: 3, name: "Fee breakdown" }), - ).toBeVisible(); - expect(getByTestId("fee-breakdown-table")).toBeVisible(); - }); - - test("if the showFeeBreakdown prop is not set, the breakdown is not displayed to the user", () => { - mockHasFeatureFlag.mockReturnValue(true); - - const { queryByRole, queryByTestId } = setup( - , - ); - - expect(mockHasFeatureFlag).toHaveBeenCalledWith("FEE_BREAKDOWN"); - expect(mockHasFeatureFlag).toHaveBeenCalledTimes(1); - - expect( - queryByRole("heading", { level: 3, name: "Fee breakdown" }), - ).not.toBeInTheDocument(); - expect(queryByTestId("fee-breakdown-table")).not.toBeInTheDocument(); - }); -}); diff --git a/editor.planx.uk/src/@planx/components/Pay/model.ts b/editor.planx.uk/src/@planx/components/Pay/model.ts index 5b390e42c6..eff610376f 100644 --- a/editor.planx.uk/src/@planx/components/Pay/model.ts +++ b/editor.planx.uk/src/@planx/components/Pay/model.ts @@ -27,7 +27,6 @@ export interface Pay extends BaseNodeData { yourDetailsDescription?: string; yourDetailsLabel?: string; govPayMetadata: GovPayMetadata[]; - showFeeBreakdown?: boolean; } export const toPence = (decimal: number) => Math.trunc(decimal * 100); @@ -182,7 +181,6 @@ export const getDefaultContent = (): Pay => ({ nomineeTitle: "Details of the person paying", yourDetailsTitle: "Your details", yourDetailsLabel: "Your name or organisation name", - showFeeBreakdown: false, govPayMetadata: [ { key: "flow", From b1c063032dd08a62e23551effd172652f65d3510 Mon Sep 17 00:00:00 2001 From: Jo Humphrey <31373245+jamdelion@users.noreply.github.com> Date: Wed, 4 Dec 2024 17:14:05 +0000 Subject: [PATCH 2/6] refactor: checklist component subfolders (#4038) --- .../Checklist/Checklist.stories.tsx | 2 +- .../@planx/components/Checklist/Editor.tsx | 429 ------------------ .../components/Checklist/Editor/Editor.tsx | 189 ++++++++ .../components/Checklist/Editor/Options.tsx | 151 ++++++ .../Checklist/Editor/OptionsEditor.tsx | 104 +++++ .../Public/AutoAnsweredChecklist.tsx | 17 + .../Checklist/{ => Public}/Public.tsx | 119 ++--- .../components/Checklist/Public/helpers.ts | 30 ++ .../{ => Public/tests}/Public.test.tsx | 122 +---- .../Checklist/Public/tests/mockOptions.ts | 118 +++++ .../src/@planx/components/Checklist/model.ts | 2 +- .../src/@planx/components/Checklist/types.ts | 3 + .../FlowEditor/components/forms/index.ts | 2 +- editor.planx.uk/src/pages/Preview/Node.tsx | 2 +- .../src/ui/editor/ComponentTagSelect.test.tsx | 2 +- 15 files changed, 659 insertions(+), 633 deletions(-) delete mode 100644 editor.planx.uk/src/@planx/components/Checklist/Editor.tsx create mode 100644 editor.planx.uk/src/@planx/components/Checklist/Editor/Editor.tsx create mode 100644 editor.planx.uk/src/@planx/components/Checklist/Editor/Options.tsx create mode 100644 editor.planx.uk/src/@planx/components/Checklist/Editor/OptionsEditor.tsx create mode 100644 editor.planx.uk/src/@planx/components/Checklist/Public/AutoAnsweredChecklist.tsx rename editor.planx.uk/src/@planx/components/Checklist/{ => Public}/Public.tsx (68%) create mode 100644 editor.planx.uk/src/@planx/components/Checklist/Public/helpers.ts rename editor.planx.uk/src/@planx/components/Checklist/{ => Public/tests}/Public.test.tsx (78%) create mode 100644 editor.planx.uk/src/@planx/components/Checklist/Public/tests/mockOptions.ts diff --git a/editor.planx.uk/src/@planx/components/Checklist/Checklist.stories.tsx b/editor.planx.uk/src/@planx/components/Checklist/Checklist.stories.tsx index daafdc024c..bc4d495353 100644 --- a/editor.planx.uk/src/@planx/components/Checklist/Checklist.stories.tsx +++ b/editor.planx.uk/src/@planx/components/Checklist/Checklist.stories.tsx @@ -1,6 +1,6 @@ import { Meta, StoryObj } from "@storybook/react"; -import Checklist from "./Public"; +import Checklist from "./Public/Public"; const meta = { title: "PlanX Components/Checklist", diff --git a/editor.planx.uk/src/@planx/components/Checklist/Editor.tsx b/editor.planx.uk/src/@planx/components/Checklist/Editor.tsx deleted file mode 100644 index 0afc79ade7..0000000000 --- a/editor.planx.uk/src/@planx/components/Checklist/Editor.tsx +++ /dev/null @@ -1,429 +0,0 @@ -import Delete from "@mui/icons-material/Delete"; -import Box from "@mui/material/Box"; -import Button from "@mui/material/Button"; -import IconButton from "@mui/material/IconButton"; -import { ComponentType as TYPES } from "@opensystemslab/planx-core/types"; -import { FormikErrors, FormikValues, useFormik } from "formik"; -import adjust from "ramda/src/adjust"; -import compose from "ramda/src/compose"; -import remove from "ramda/src/remove"; -import React, { useEffect, useRef } from "react"; -import { FormikHookReturn } from "types"; -import ImgInput from "ui/editor/ImgInput/ImgInput"; -import InputGroup from "ui/editor/InputGroup"; -import ListManager from "ui/editor/ListManager/ListManager"; -import { ModalFooter } from "ui/editor/ModalFooter"; -import ModalSection from "ui/editor/ModalSection"; -import ModalSectionContent from "ui/editor/ModalSectionContent"; -import RichTextInput from "ui/editor/RichTextInput/RichTextInput"; -import SimpleMenu from "ui/editor/SimpleMenu"; -import Input from "ui/shared/Input/Input"; -import InputRow from "ui/shared/InputRow"; -import InputRowItem from "ui/shared/InputRowItem"; -import { Switch } from "ui/shared/Switch"; - -import { Option, parseBaseNodeData } from "../shared"; -import { FlagsSelect } from "../shared/FlagsSelect"; -import { ICONS } from "../shared/icons"; -import type { Checklist, Group } from "./model"; -import { toggleExpandableChecklist } from "./model"; -import { ChecklistProps, OptionEditorProps } from "./types"; - -const OptionEditor: React.FC = (props) => { - return ( -
- - {props.value.id ? ( - - ) : null} - - { - props.onChange({ - ...props.value, - data: { - ...props.value.data, - text: ev.target.value, - }, - }); - }} - placeholder="Option" - /> - - - { - props.onChange({ - ...props.value, - data: { - ...props.value.data, - img, - }, - }); - }} - /> - - {typeof props.index !== "undefined" && - props.groups && - props.onMoveToGroup && ( - ({ - label: `Move to ${group || `group ${groupIndex}`}`, - onClick: () => { - props.onMoveToGroup && - typeof props.index === "number" && - props.onMoveToGroup(props.index, groupIndex); - }, - disabled: groupIndex === props.groupIndex, - }))} - /> - )} - - - {props.showValueField && ( - - { - props.onChange({ - ...props.value, - data: { - ...props.value.data, - val: ev.target.value, - }, - }); - }} - /> - - )} - - { - props.onChange({ - ...props.value, - data: { - ...props.value.data, - flag: ev, - }, - }); - }} - /> -
- ); -}; - -const Options: React.FC<{ formik: FormikHookReturn }> = ({ formik }) => { - return ( - - {formik.values.groupedOptions ? ( - - {formik.values.groupedOptions.map( - (groupedOption: Group - ) : ( - { - formik.setFieldValue("options", newOptions); - }} - newValueLabel="add new option" - newValue={() => - ({ - data: { - text: "", - description: "", - val: "", - }, - }) as Option - } - Editor={OptionEditor} - editorExtraProps={{ showValueField: !!formik.values.fn }} - /> - )} - - ); -}; - -export const ChecklistComponent: React.FC = (props) => { - const type = TYPES.Checklist; - - const formik = useFormik({ - initialValues: { - allRequired: props.node?.data?.allRequired || false, - neverAutoAnswer: props.node?.data?.neverAutoAnswer || false, - description: props.node?.data?.description || "", - fn: props.node?.data?.fn || "", - groupedOptions: props.groupedOptions, - img: props.node?.data?.img || "", - options: props.options, - text: props.node?.data?.text || "", - ...parseBaseNodeData(props.node?.data), - }, - onSubmit: ({ options, groupedOptions, ...values }) => { - const sourceOptions = options?.length - ? options - : groupedOptions?.flatMap((group) => group.children); - - const filteredOptions = (sourceOptions || []).filter( - (option) => option.data.text, - ); - - const processedOptions = filteredOptions.map((option) => ({ - ...option, - id: option.id || undefined, - type: TYPES.Answer, - })); - - if (props.handleSubmit) { - props.handleSubmit( - { - type, - data: { - ...values, - ...(groupedOptions - ? { - categories: groupedOptions.map((group) => ({ - title: group.title, - count: group.children.length, - })), - } - : { - categories: undefined, - }), - }, - }, - processedOptions, - ); - } else { - alert(JSON.stringify({ type, ...values, options }, null, 2)); - } - }, - validate: ({ options, ...values }) => { - const errors: FormikErrors = {}; - if (values.fn && !options?.some((option) => option.data.val)) { - errors.fn = - "At least one option must set a data value when the checklist has a data field"; - } - return errors; - }, - }); - - const focusRef = useRef(null); - - // horrible hack to remove focus from Rich Text Editor - useEffect(() => { - setTimeout(() => { - (document.activeElement as any).blur(); - focusRef.current?.focus(); - }, 50); - }, []); - - return ( -