Skip to content

Commit

Permalink
feat: add basic in-page feedback component (#3841)
Browse files Browse the repository at this point in the history
  • Loading branch information
jamdelion authored Oct 28, 2024
1 parent 23d05a0 commit 18ac5a9
Show file tree
Hide file tree
Showing 17 changed files with 339 additions and 1 deletion.
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { Meta, StoryObj } from "@storybook/react";

import Public from "./Public";

const meta = {
title: "PlanX Components/Feedback",
component: Public,
} satisfies Meta<typeof Public>;

type Story = StoryObj<typeof meta>;

export default meta;

export const Basic = {
args: {
title: "Tell us what you think",
privacyPolicyLink: "https://www.planx.uk/",
},
} satisfies Story;
32 changes: 32 additions & 0 deletions editor.planx.uk/src/@planx/components/Feedback/Public.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { screen } from "@testing-library/react";
import React from "react";
import { setup } from "testUtils";
import { vi } from "vitest";
import { axe } from "vitest-axe";

import FeedbackComponent from "./Public";

const handleSubmit = vi.fn();

describe("when the Feedback component is rendered", async () => {
it("should not have any accessibility violations", async () => {
const { container } = setup(
<FeedbackComponent title="Tell us what you think" />,
);
const results = await axe(container);
expect(results).toHaveNoViolations();
});
it("should call handleSubmit when the continue button is pressed", async () => {
const { user } = setup(
<FeedbackComponent
title="Tell us what you think"
handleSubmit={handleSubmit}
/>,
);

await user.click(screen.getByTestId("feedback-button-terrible"));
await user.click(screen.getByTestId("continue-button"));

expect(handleSubmit).toHaveBeenCalled();
});
});
119 changes: 119 additions & 0 deletions editor.planx.uk/src/@planx/components/Feedback/Public.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import Box from "@mui/material/Box";
import Grid from "@mui/material/Grid";
import Link from "@mui/material/Link";
import Typography from "@mui/material/Typography";
import Card from "@planx/components/shared/Preview/Card";
import { CardHeader } from "@planx/components/shared/Preview/CardHeader/CardHeader";
import type { PublicProps } from "@planx/components/ui";
import { useFormik } from "formik";
import React from "react";
import RichTextInput from "ui/editor/RichTextInput/RichTextInput";
import TerribleFace from "ui/images/feedback_filled-01.svg";
import PoorFace from "ui/images/feedback_filled-02.svg";
import NeutralFace from "ui/images/feedback_filled-03.svg";
import GoodFace from "ui/images/feedback_filled-04.svg";
import ExcellentFace from "ui/images/feedback_filled-05.svg";
import InputLabel from "ui/public/InputLabel";

import { getPreviouslySubmittedData, makeData } from "../shared/utils";
import { FaceBox } from "./components/FaceBox";
import { StyledToggleButtonGroup } from "./styled";
import { FeedbackComponentProps, FormProps } from "./types";

const FeedbackComponent = (
props: PublicProps<FeedbackComponentProps>,
): FCReturn => {
const formik = useFormik<FormProps>({
initialValues: getPreviouslySubmittedData(props) ?? {
feedbackScore: "",
feedback: "",
},
onSubmit: (values) => {
props.handleSubmit?.(makeData(props, values));
},
});

const handleFeedbackChange = (
event: React.MouseEvent<HTMLElement>,
newValue: string | null,
) => {
if (newValue !== null) {
formik.setFieldValue("feedbackScore", newValue);
}
};

return (
<Card handleSubmit={formik.handleSubmit}>
<CardHeader title={props.title} />
<Typography pt={4}>
This service is a work in progress, any feedback you share about your
experience will help us to improve it.
</Typography>
<Typography pt={2}>
Don't share any personal or financial information in your feedback. If
you do we will act according to our{" "}
<Link href={props.privacyPolicyLink ?? ""}>privacy policy</Link>.
</Typography>

<Box pt={2} mb={3}>
<InputLabel label="How would you rate your experience with this service?" />
<StyledToggleButtonGroup
value={formik.values.feedbackScore}
exclusive
id="feedbackButtonGroup"
onChange={handleFeedbackChange}
aria-label="feedback score"
>
<Grid container columnSpacing={15} component="fieldset">
<FaceBox
value="1"
testId="feedback-button-terrible"
icon={TerribleFace}
label="Terrible"
altText="very unhappy face"
/>
<FaceBox
value="2"
icon={PoorFace}
label="Poor"
altText="slightly unhappy face"
/>
<FaceBox
value="3"
icon={NeutralFace}
label="Neutral"
altText="neutral face"
/>
<FaceBox
value="4"
icon={GoodFace}
label="Good"
altText="smiling face"
/>
<FaceBox
value="5"
icon={ExcellentFace}
label="Excellent"
altText="very happy face"
/>
</Grid>
</StyledToggleButtonGroup>
{/* </InputLabel> */}
<InputLabel label="Please tell us more about your experience">
<RichTextInput
name="feedback"
value={formik.values.feedback}
onChange={formik.handleChange}
/>
</InputLabel>
</Box>
<Typography variant="caption">
The information collected here isn't monitored by planning officers.
Don't use it to give extra information about your project or submission.
If you do, it cannot be used to assess your project.
</Typography>
</Card>
);
};

export default FeedbackComponent;
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import Box from "@mui/material/Box";
import Grid from "@mui/material/Grid";
import ToggleButton from "@mui/material/ToggleButton";
import Typography from "@mui/material/Typography";
import React, { ReactElement } from "react";

interface FaceBoxProps {
icon: string;
label: string;
altText: string;
testId?: string;
value: string;
}

export const FaceBox = ({
icon,
label,
altText,
testId,
value,
}: FaceBoxProps): ReactElement => {
return (
<Grid item xs={2} key={label}>
<ToggleButton
value={value}
data-testid={testId}
sx={{
px: 0,
}}
disableRipple
>
<Box
sx={(theme) => ({
p: theme.spacing(2),
border: `2px solid ${theme.palette.border.main} `,
width: "120px",
maxHeight: "120px",
display: "flex",
flexDirection: "column",
justifyContent: "center",
alignItems: "center",
})}
>
<img src={icon} width={50} alt={altText} />
<Typography variant="body2" pt={0.5}>
{label}
</Typography>
</Box>
</ToggleButton>
</Grid>
);
};
15 changes: 15 additions & 0 deletions editor.planx.uk/src/@planx/components/Feedback/styled.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { styled } from "@mui/material/styles";
import ToggleButtonGroup, {
toggleButtonGroupClasses,
} from "@mui/material/ToggleButtonGroup";

export const StyledToggleButtonGroup = styled(ToggleButtonGroup)(
({ theme }) => ({
[`& .${toggleButtonGroupClasses.grouped}`]: {
border: 0,
padding: 0,
marginTop: theme.spacing(1),
},
paddingBottom: theme.spacing(3.5),
}),
);
11 changes: 11 additions & 0 deletions editor.planx.uk/src/@planx/components/Feedback/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { BaseNodeData } from "../shared";

export interface FeedbackComponentProps extends BaseNodeData {
title: string;
privacyPolicyLink?: string;
fn?: string;
}
export interface FormProps {
feedbackScore: string;
feedback: string;
}
9 changes: 9 additions & 0 deletions editor.planx.uk/src/ui/images/feedback_filled-01.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 9 additions & 0 deletions editor.planx.uk/src/ui/images/feedback_filled-02.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 9 additions & 0 deletions editor.planx.uk/src/ui/images/feedback_filled-03.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 9 additions & 0 deletions editor.planx.uk/src/ui/images/feedback_filled-04.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 9 additions & 0 deletions editor.planx.uk/src/ui/images/feedback_filled-05.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 9 additions & 0 deletions editor.planx.uk/src/ui/images/feedback_stroke-01.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 9 additions & 0 deletions editor.planx.uk/src/ui/images/feedback_stroke-02.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 9 additions & 0 deletions editor.planx.uk/src/ui/images/feedback_stroke-03.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 9 additions & 0 deletions editor.planx.uk/src/ui/images/feedback_stroke-04.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 9 additions & 0 deletions editor.planx.uk/src/ui/images/feedback_stroke-05.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion editor.planx.uk/src/ui/public/InputLabel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ const Root = styled("label")(() => ({

export default function InputLabel(props: {
label: string;
children: ReactNode;
children?: ReactNode;
hidden?: boolean;
htmlFor?: string;
id?: string;
Expand Down

0 comments on commit 18ac5a9

Please sign in to comment.