diff --git a/editor.planx.uk/src/pages/FlowEditor/components/Settings/DesignSettings.tsx b/editor.planx.uk/src/pages/FlowEditor/components/Settings/DesignSettings.tsx deleted file mode 100644 index 3d759f8b87..0000000000 --- a/editor.planx.uk/src/pages/FlowEditor/components/Settings/DesignSettings.tsx +++ /dev/null @@ -1,285 +0,0 @@ -import Box from "@mui/material/Box"; -import Button from "@mui/material/Button"; -import Link from "@mui/material/Link"; -import { styled } from "@mui/material/styles"; -import Typography from "@mui/material/Typography"; -import { useFormik } from "formik"; -import { hasFeatureFlag } from "lib/featureFlags"; -import React from "react"; -import ColorPicker from "ui/editor/ColorPicker"; -import EditorRow from "ui/editor/EditorRow"; -import { FeaturePlaceholder } from "ui/editor/FeaturePlaceholder"; -import InputDescription from "ui/editor/InputDescription"; -import InputGroup from "ui/editor/InputGroup"; -import InputLegend from "ui/editor/InputLegend"; -import InputRow from "ui/shared/InputRow"; -import InputRowItem from "ui/shared/InputRowItem"; -import InputRowLabel from "ui/shared/InputRowLabel"; -import PublicFileUploadButton from "ui/shared/PublicFileUploadButton"; - -const DesignPreview = styled(Box)(({ theme }) => ({ - border: `2px solid ${theme.palette.border.input}`, - padding: theme.spacing(2), - boxShadow: "4px 4px 0px rgba(150, 150, 150, 0.5)", -})); - -const exampleColor = "#007078"; - -const DesignSettings: React.FC = () => { - const formik = useFormik<{ - themeColor: string; - buttonColor: string; - linkColor: string; - }>({ - initialValues: { - themeColor: exampleColor, - buttonColor: exampleColor, - linkColor: exampleColor, - }, - onSubmit: () => {}, - validate: () => {}, - }); - - const isUsingFeatureFlag = () => hasFeatureFlag("SHOW_TEAM_SETTINGS"); - - return ( - <> - - - Design - - - How your service appears to public users - - - {!isUsingFeatureFlag() ? ( - - {" "} - - ) : ( - <> - -
- - Theme colour & logo - - The theme colour and logo, are used in the header of the - service. The theme colour should be a dark colour that - contrasts with white ("#ffffff"). The logo should contrast - with a dark background colour (your theme colour) and have a - transparent background. - - - - See our guide for setting theme colours and logos - - - - - - formik.setFieldValue("themeColor", color) - } - label="Theme colour" - /> - - - - Logo: - - - - - .png or .svg - - - - - - Preview: - - - council logo - - - - - - -
-
- -
- - Button colour - - The button background colour should be either a dark or light - colour. The text will be programatically selected to contrast - with the selected colour (being either black or white). - - - - See our guide for setting button colours - - - - - - formik.setFieldValue("buttonColor", color) - } - label="Button colour" - /> - - - - - - Preview: - - - - - - - - - -
-
- -
- - Text link colour - - The text link colour should be a dark colour that contrasts - with white ("#ffffff"). - - - - See our guide for setting text link colours - - - - - - formik.setFieldValue("linkColor", color) - } - label="Text link colour" - /> - - - - - - Preview: - - - Example text link - - - - - - -
-
- -
- - Favicon - - Set the favicon to be used in the browser tab. The favicon - should be 32x32px and in .ico or .png format. - - - - See our guide for favicons - - - - Favicon: - - - - - .ico or .png - - - - - - - -
-
- - )} - - ); -}; - -export default DesignSettings; diff --git a/editor.planx.uk/src/pages/FlowEditor/components/Settings/DesignSettings/ButtonForm.tsx b/editor.planx.uk/src/pages/FlowEditor/components/Settings/DesignSettings/ButtonForm.tsx new file mode 100644 index 0000000000..ddeb8d32c9 --- /dev/null +++ b/editor.planx.uk/src/pages/FlowEditor/components/Settings/DesignSettings/ButtonForm.tsx @@ -0,0 +1,61 @@ +import Button from "@mui/material/Button"; +import Link from "@mui/material/Link"; +import { useFormik } from "formik"; +import React from "react"; +import ColorPicker from "ui/editor/ColorPicker"; +import InputDescription from "ui/editor/InputDescription"; +import InputRow from "ui/shared/InputRow"; +import InputRowItem from "ui/shared/InputRowItem"; + +import { DesignPreview, EXAMPLE_COLOUR, SettingsForm } from "."; + +export const ButtonForm: React.FC = () => { + const formik = useFormik<{ + buttonColor: string; + }>({ + initialValues: { + buttonColor: EXAMPLE_COLOUR, + }, + onSubmit: () => {}, + validate: () => {}, + }); + + return ( + + + The button background colour should be either a dark or light + colour. The text will be programmatically selected to contrast with + the selected colour (being either black or white). + + + + See our guide for setting button colours + + + + } + input={ + + + formik.setFieldValue("buttonColor", color)} + label="Button colour" + /> + + + } + preview={ + + + + } + /> + ); +}; diff --git a/editor.planx.uk/src/pages/FlowEditor/components/Settings/DesignSettings/FaviconForm.tsx b/editor.planx.uk/src/pages/FlowEditor/components/Settings/DesignSettings/FaviconForm.tsx new file mode 100644 index 0000000000..729f011d21 --- /dev/null +++ b/editor.planx.uk/src/pages/FlowEditor/components/Settings/DesignSettings/FaviconForm.tsx @@ -0,0 +1,57 @@ +import Link from "@mui/material/Link"; +import Typography from "@mui/material/Typography"; +import { useFormik } from "formik"; +import React from "react"; +import InputDescription from "ui/editor/InputDescription"; +import InputRow from "ui/shared/InputRow"; +import InputRowItem from "ui/shared/InputRowItem"; +import InputRowLabel from "ui/shared/InputRowLabel"; +import PublicFileUploadButton from "ui/shared/PublicFileUploadButton"; + +import { EXAMPLE_COLOUR, SettingsForm } from "."; + +export const FaviconForm: React.FC = () => { + const formik = useFormik<{ + textLinkColor: string; + }>({ + initialValues: { + textLinkColor: EXAMPLE_COLOUR, + }, + onSubmit: () => {}, + validate: () => {}, + }); + + return ( + + + Set the favicon to be used in the browser tab. The favicon should be + 32x32px and in .ico or .png format. + + + See our guide for favicons + + + } + input={ + + Favicon: + + + + + .ico or .png + + + } + /> + ); +}; diff --git a/editor.planx.uk/src/pages/FlowEditor/components/Settings/DesignSettings/TextLinkForm.tsx b/editor.planx.uk/src/pages/FlowEditor/components/Settings/DesignSettings/TextLinkForm.tsx new file mode 100644 index 0000000000..f89d7c0223 --- /dev/null +++ b/editor.planx.uk/src/pages/FlowEditor/components/Settings/DesignSettings/TextLinkForm.tsx @@ -0,0 +1,57 @@ +import Link from "@mui/material/Link"; +import { useFormik } from "formik"; +import React from "react"; +import ColorPicker from "ui/editor/ColorPicker"; +import InputDescription from "ui/editor/InputDescription"; +import InputRow from "ui/shared/InputRow"; +import InputRowItem from "ui/shared/InputRowItem"; + +import { DesignPreview, EXAMPLE_COLOUR, SettingsForm } from "."; + +export const TextLinkForm: React.FC = () => { + const formik = useFormik<{ + linkColor: string; + }>({ + initialValues: { + linkColor: EXAMPLE_COLOUR, + }, + onSubmit: () => {}, + validate: () => {}, + }); + + return ( + + + The text link colour should be a dark colour that contrasts with + white ("#ffffff"). + + + + See our guide for setting text link colours + + + + } + input={ + + + formik.setFieldValue("linkColor", color)} + label="Text link colour" + /> + + + } + preview={ + + Example text link + + } + /> + ); +}; diff --git a/editor.planx.uk/src/pages/FlowEditor/components/Settings/DesignSettings/ThemeAndLogoForm.tsx b/editor.planx.uk/src/pages/FlowEditor/components/Settings/DesignSettings/ThemeAndLogoForm.tsx new file mode 100644 index 0000000000..8e1f2e8286 --- /dev/null +++ b/editor.planx.uk/src/pages/FlowEditor/components/Settings/DesignSettings/ThemeAndLogoForm.tsx @@ -0,0 +1,127 @@ +import Link from "@mui/material/Link"; +import { getContrastRatio, useTheme } from "@mui/material/styles"; +import Typography from "@mui/material/Typography"; +import { Team, TeamTheme } from "@opensystemslab/planx-core/types"; +import { useFormik } from "formik"; +import { useStore } from "pages/FlowEditor/lib/store"; +import React, { useEffect, useState } from "react"; +import ColorPicker from "ui/editor/ColorPicker"; +import InputDescription from "ui/editor/InputDescription"; +import InputRow from "ui/shared/InputRow"; +import InputRowItem from "ui/shared/InputRowItem"; +import InputRowLabel from "ui/shared/InputRowLabel"; +import PublicFileUploadButton from "ui/shared/PublicFileUploadButton"; + +import { DesignPreview, SettingsForm } from "."; + +type FormValues = Pick; + +export const ThemeAndLogoForm: React.FC<{ + team: Team; + onSuccess: () => void; +}> = ({ team, onSuccess }) => { + const theme = useTheme(); + + useEffect(() => { + setInitialValues({ + primaryColour: team.theme?.primaryColour, + logo: team.theme?.logo, + }); + }, [team]); + + const [initialValues, setInitialValues] = useState({ + primaryColour: "", + logo: "", + }); + + const formik = useFormik({ + initialValues, + onSubmit: async (values, { resetForm }) => { + const isSuccess = await useStore.getState().updateTeamTheme(values); + if (isSuccess) { + onSuccess(); + // Reset "dirty" status to disable Save & Reset buttons + resetForm({ values }); + } + }, + validateOnBlur: false, + validateOnChange: false, + enableReinitialize: true, + validate: ({ primaryColour }) => { + const isContrastThresholdMet = + getContrastRatio("#FFF", primaryColour) > + theme.palette.contrastThreshold; + + if (!isContrastThresholdMet) { + return { + primaryColour: + "Theme colour does not meet accessibility contrast requirements (3:1)", + }; + } + }, + }); + + return ( + + + The theme colour and logo, are used in the header of the service. + The theme colour should be a dark colour that contrasts with white + ("#ffffff"). The logo should contrast with a dark background colour + (your theme colour) and have a transparent background. + + + + See our guide for setting theme colours and logos + + + + } + input={ + <> + + + + formik.setFieldValue("primaryColour", color) + } + label="Theme colour" + /> + + + + Logo: + + formik.setFieldValue("logo", newUrl)} + /> + + + .png or .svg + + + + } + preview={ + + {formik.values.logo ? ( + council logo + ) : ( + + {team?.name} + + )} + + } + /> + ); +}; diff --git a/editor.planx.uk/src/pages/FlowEditor/components/Settings/DesignSettings/index.tsx b/editor.planx.uk/src/pages/FlowEditor/components/Settings/DesignSettings/index.tsx new file mode 100644 index 0000000000..4bc13a3951 --- /dev/null +++ b/editor.planx.uk/src/pages/FlowEditor/components/Settings/DesignSettings/index.tsx @@ -0,0 +1,158 @@ +import Alert from "@mui/material/Alert"; +import Box from "@mui/material/Box"; +import Button from "@mui/material/Button"; +import Snackbar from "@mui/material/Snackbar"; +import { styled } from "@mui/material/styles"; +import Typography from "@mui/material/Typography"; +import { Team } from "@opensystemslab/planx-core/types"; +import { FormikProps, getIn } from "formik"; +import { hasFeatureFlag } from "lib/featureFlags"; +import { useStore } from "pages/FlowEditor/lib/store"; +import React, { useEffect, useState } from "react"; +import EditorRow from "ui/editor/EditorRow"; +import { FeaturePlaceholder } from "ui/editor/FeaturePlaceholder"; +import InputGroup from "ui/editor/InputGroup"; +import InputLegend from "ui/editor/InputLegend"; +import ErrorWrapper from "ui/shared/ErrorWrapper"; + +import { ButtonForm } from "./ButtonForm"; +import { FaviconForm } from "./FaviconForm"; +import { TextLinkForm } from "./TextLinkForm"; +import { ThemeAndLogoForm } from "./ThemeAndLogoForm"; + +export const DesignPreview = styled(Box)(({ theme }) => ({ + border: `2px solid ${theme.palette.border.input}`, + padding: theme.spacing(2), + boxShadow: "4px 4px 0px rgba(150, 150, 150, 0.5)", +})); + +export const EXAMPLE_COLOUR = "#007078"; + +type SettingsFormProps = { + legend: string; + description: React.ReactElement; + input: React.ReactElement; + formik: FormikProps; + preview?: React.ReactElement; +}; + +const useTeam = () => { + const [team, setTeam] = useState({} as Team); + + useEffect(() => { + const fetchTeam = async () => { + try { + const fetchedTeam = await useStore.getState().fetchCurrentTeam(); + if (!fetchedTeam) throw Error("Unable to find team"); + setTeam(fetchedTeam); + } catch (error) { + console.error("Error fetching team:", error); + } + }; + + fetchTeam(); + }, []); + + return team; +}; + +export const SettingsForm: React.FC = ({ + formik, + legend, + description, + input, + preview, +}) => { + return ( + +
+ + {legend} + {description} + {input} + + {preview && ( + + + Preview: + + {preview} + + )} + + + + + + +
+
+ ); +}; + +const DesignSettings: React.FC = () => { + const isUsingFeatureFlag = hasFeatureFlag("SHOW_TEAM_SETTINGS"); + const team = useTeam(); + const [open, setOpen] = useState(false); + + const handleClose = ( + _event?: React.SyntheticEvent | Event, + reason?: string, + ) => { + if (reason === "clickaway") { + return; + } + + setOpen(false); + }; + + return ( + <> + + + Design + + + How your service appears to public users + + + {!isUsingFeatureFlag ? ( + + {" "} + + ) : ( + <> + setOpen(true)} /> + + + + + + Theme updated successfully + + + + )} + + ); +}; + +export default DesignSettings; diff --git a/editor.planx.uk/src/pages/FlowEditor/lib/store/team.ts b/editor.planx.uk/src/pages/FlowEditor/lib/store/team.ts index 7506f3c70b..c7b1e11c2d 100644 --- a/editor.planx.uk/src/pages/FlowEditor/lib/store/team.ts +++ b/editor.planx.uk/src/pages/FlowEditor/lib/store/team.ts @@ -24,6 +24,7 @@ export interface TeamStore { initTeamStore: (slug: string) => Promise; clearTeamStore: () => void; fetchCurrentTeam: () => Promise; + updateTeamTheme: (theme: Partial) => Promise; } export const teamStore: StateCreator< @@ -115,4 +116,10 @@ export const teamStore: StateCreator< const team = await $client.team.getBySlug(teamSlug); return team; }, + + updateTeamTheme: async (theme: Partial) => { + const { teamId, $client } = get(); + const isSuccess = await $client.team.updateTheme(teamId, theme); + return isSuccess; + }, });