diff --git a/editor.planx.uk/src/@planx/components/MapAndLabel/Public/index.tsx b/editor.planx.uk/src/@planx/components/MapAndLabel/Public/index.tsx index 4b260271a7..f55921d07f 100644 --- a/editor.planx.uk/src/@planx/components/MapAndLabel/Public/index.tsx +++ b/editor.planx.uk/src/@planx/components/MapAndLabel/Public/index.tsx @@ -8,8 +8,8 @@ import Tab, { tabClasses, TabProps } from "@mui/material/Tab"; import Tabs from "@mui/material/Tabs"; import Typography from "@mui/material/Typography"; import { SiteAddress } from "@planx/components/FindProperty/model"; -import { ErrorSummaryContainer } from "@planx/components/shared/Preview/ErrorSummaryContainer"; import { SchemaFields } from "@planx/components/shared/Schema/SchemaFields"; +import { GraphError } from "components/Error/GraphError"; import { GeoJsonObject } from "geojson"; import sortBy from "lodash/sortBy"; import { useStore } from "pages/FlowEditor/lib/store"; @@ -305,24 +305,6 @@ export const Presentational: React.FC = (props) => ( ); -const GraphError = (props: Props) => ( - - - - - Invalid graph - - - Edit this flow so that "MapAndLabel" is positioned after "FindProperty"; - an initial address is required to correctly display the map. - - - -); - function MapAndLabelComponent(props: Props) { const teamSettings = useStore.getState().teamSettings; const passport = useStore((state) => state.computePassport()); @@ -330,7 +312,7 @@ function MapAndLabelComponent(props: Props) { (passport?.data?._address as SiteAddress) || {}; if (!latitude || !longitude) { - return ; + throw new GraphError("nodeMustFollowFindProperty"); } return ( diff --git a/editor.planx.uk/src/@planx/components/Page/schema/AdvertConsent.ts b/editor.planx.uk/src/@planx/components/Page/schema/AdvertConsent.ts index 78165a04d4..e8b2dd4df4 100644 --- a/editor.planx.uk/src/@planx/components/Page/schema/AdvertConsent.ts +++ b/editor.planx.uk/src/@planx/components/Page/schema/AdvertConsent.ts @@ -3,6 +3,13 @@ import { PageSchema } from "../model"; export const ProposedAdvertisements: PageSchema = { type: "Proposed advertisements", fields: [ + { + type: "map", + data: { + title: "Where is it?", + fn: "location", + }, + }, { type: "number", data: { diff --git a/editor.planx.uk/src/@planx/components/PlanningConstraints/Public.tsx b/editor.planx.uk/src/@planx/components/PlanningConstraints/Public.tsx index f93b0a1edc..513961d632 100644 --- a/editor.planx.uk/src/@planx/components/PlanningConstraints/Public.tsx +++ b/editor.planx.uk/src/@planx/components/PlanningConstraints/Public.tsx @@ -9,6 +9,7 @@ import Card from "@planx/components/shared/Preview/Card"; import CardHeader from "@planx/components/shared/Preview/CardHeader"; import type { PublicProps } from "@planx/components/ui"; import DelayedLoadingIndicator from "components/DelayedLoadingIndicator"; +import { GraphError } from "components/Error/GraphError"; import capitalize from "lodash/capitalize"; import { useStore } from "pages/FlowEditor/lib/store"; import { HandleSubmit } from "pages/Preview/Node"; @@ -63,6 +64,8 @@ function Component(props: Props) { // PlanningConstraints must come after at least a FindProperty in the graph const showGraphError = !x || !y || !longitude || !latitude; + if (showGraphError) + throw new GraphError("mapInputFieldMustFollowFindProperty"); // Even though this component will fetch fresh GIS data when coming "back", // still prepopulate any previously marked inaccurateConstraints @@ -145,8 +148,6 @@ function Component(props: Props) { ...roads?.metadata, }; - if (showGraphError) return ; - const isLoading = isValidating || isValidatingRoads; if (isLoading) return ( @@ -396,28 +397,3 @@ const ConstraintsFetchError = (props: ConstraintsFetchErrorProps) => ( ); - -interface ConstraintsGraphErrorProps { - title: string; - description: string; - handleSubmit?: HandleSubmit; -} - -const ConstraintsGraphError = (props: ConstraintsGraphErrorProps) => ( - - - - - Invalid graph - - - Edit this flow so that "Planning constraints" is positioned after "Find - property"; an address or site boundary drawing is required to fetch - data. - - - -); diff --git a/editor.planx.uk/src/@planx/components/PropertyInformation/Public.tsx b/editor.planx.uk/src/@planx/components/PropertyInformation/Public.tsx index 5502fc42aa..6b6bfc17d9 100644 --- a/editor.planx.uk/src/@planx/components/PropertyInformation/Public.tsx +++ b/editor.planx.uk/src/@planx/components/PropertyInformation/Public.tsx @@ -1,12 +1,12 @@ import { useQuery } from "@apollo/client"; import Box from "@mui/material/Box"; import Link from "@mui/material/Link"; -import Typography from "@mui/material/Typography"; import { visuallyHidden } from "@mui/utils"; import Card from "@planx/components/shared/Preview/Card"; import CardHeader from "@planx/components/shared/Preview/CardHeader"; import { SummaryListTable } from "@planx/components/shared/Preview/SummaryList"; import type { PublicProps } from "@planx/components/ui"; +import { GraphError } from "components/Error/GraphError"; import { Feature } from "geojson"; import { publicClient } from "lib/graphql"; import find from "lodash/find"; @@ -17,7 +17,6 @@ import React from "react"; import type { SiteAddress } from "../FindProperty/model"; import { FETCH_BLPU_CODES } from "../FindProperty/Public"; -import { ErrorSummaryContainer } from "../shared/Preview/ErrorSummaryContainer"; import { MapContainer } from "../shared/Preview/MapContainer"; import type { PropertyInformation } from "./model"; @@ -32,7 +31,10 @@ function Component(props: PublicProps) { client: publicClient, }); - return passport.data?._address ? ( + if (!passport.data?._address) + throw new GraphError("nodeMustFollowFindProperty"); + + return ( ) { }); }} /> - ) : ( - - - - Invalid graph - - - Edit this flow so that "Property information" is positioned after - "Find property"; an address is required to render. - - - ); } diff --git a/editor.planx.uk/src/@planx/components/shared/Schema/InputFields/MapFieldInput.tsx b/editor.planx.uk/src/@planx/components/shared/Schema/InputFields/MapFieldInput.tsx index 7b55fa14b2..7ddc054bc9 100644 --- a/editor.planx.uk/src/@planx/components/shared/Schema/InputFields/MapFieldInput.tsx +++ b/editor.planx.uk/src/@planx/components/shared/Schema/InputFields/MapFieldInput.tsx @@ -2,6 +2,7 @@ import Box from "@mui/material/Box"; import { SiteAddress } from "@opensystemslab/planx-core/types"; import { MapContainer } from "@planx/components/shared/Preview/MapContainer"; import type { MapField } from "@planx/components/shared/Schema/model"; +import { GraphError } from "components/Error/GraphError"; import { Feature } from "geojson"; import { useStore } from "pages/FlowEditor/lib/store"; import React, { useEffect, useState } from "react"; @@ -18,12 +19,8 @@ export const MapFieldInput: React.FC> = (props) => { (state.computePassport()?.data?.["_address"] as SiteAddress) || {}, ); - if (!longitude || !latitude) { - throw Error( - 'Edit this flow so that this component is positioned after "FindProperty"; an address is required for schemas that include a "map" field.', - { cause: "Invalid graph" }, - ); - } + if (!longitude || !latitude) + throw new GraphError("mapInputFieldMustFollowFindProperty"); const { formik, diff --git a/editor.planx.uk/src/components/ErrorFallback.stories.tsx b/editor.planx.uk/src/components/Error/ErrorFallback.stories.tsx similarity index 100% rename from editor.planx.uk/src/components/ErrorFallback.stories.tsx rename to editor.planx.uk/src/components/Error/ErrorFallback.stories.tsx diff --git a/editor.planx.uk/src/components/ErrorFallback.tsx b/editor.planx.uk/src/components/Error/ErrorFallback.tsx similarity index 67% rename from editor.planx.uk/src/components/ErrorFallback.tsx rename to editor.planx.uk/src/components/Error/ErrorFallback.tsx index a2e2e1773d..71aef0f689 100644 --- a/editor.planx.uk/src/components/ErrorFallback.tsx +++ b/editor.planx.uk/src/components/Error/ErrorFallback.tsx @@ -3,21 +3,24 @@ import Card from "@planx/components/shared/Preview/Card"; import { ErrorSummaryContainer } from "@planx/components/shared/Preview/ErrorSummaryContainer"; import React from "react"; -import { logger } from "../airbrake"; +import { logger } from "../../airbrake"; +import { GraphErrorComponent, isGraphError } from "./GraphError"; -function ErrorFallback(props: { error: Error }) { - logger.notify(props.error); +const ErrorFallback: React.FC<{ error: Error }> = ({ error }) => { + if (isGraphError(error)) return ; + + logger.notify(error); return ( - {(props.error.cause as string) || "Something went wrong"} + Something went wrong - {props.error?.message && ( + {error.message && (
-              {props.error.message}
+              {error.message}
             
)}
@@ -27,6 +30,6 @@ function ErrorFallback(props: { error: Error }) {
); -} +}; export default ErrorFallback; diff --git a/editor.planx.uk/src/components/Error/GraphError.tsx b/editor.planx.uk/src/components/Error/GraphError.tsx new file mode 100644 index 0000000000..91afdb93c4 --- /dev/null +++ b/editor.planx.uk/src/components/Error/GraphError.tsx @@ -0,0 +1,45 @@ +import Typography from "@mui/material/Typography"; +import Card from "@planx/components/shared/Preview/Card"; +import CardHeader from "@planx/components/shared/Preview/CardHeader"; +import { ErrorSummaryContainer } from "@planx/components/shared/Preview/ErrorSummaryContainer"; +import React from "react"; + +type GraphErrorType = + | "nodeMustFollowFindProperty" + | "mapInputFieldMustFollowFindProperty"; + +const GRAPH_ERROR_MESSAGES: Record = { + nodeMustFollowFindProperty: + 'Edit this flow so that this node is positioned after "Find property"; an address or site boundary drawing is required to fetch data', + mapInputFieldMustFollowFindProperty: + 'Edit this flow so that this component is positioned after "FindProperty"; an address is required for schemas that include a "map" field.', +}; + +export class GraphError extends Error { + constructor(public type: GraphErrorType) { + super(); + this.type = type; + } +} + +export const isGraphError = (error: unknown): error is GraphError => + error instanceof GraphError; + +export const GraphErrorComponent: React.FC<{ error: GraphError }> = ({ + error, +}) => ( + + + + + Invalid graph + + + {GRAPH_ERROR_MESSAGES[error.type]} + + + +); diff --git a/editor.planx.uk/src/pages/FlowEditor/components/Settings/Submissions/EventsLog.tsx b/editor.planx.uk/src/pages/FlowEditor/components/Settings/Submissions/EventsLog.tsx index 4de7b76aac..29d1994471 100644 --- a/editor.planx.uk/src/pages/FlowEditor/components/Settings/Submissions/EventsLog.tsx +++ b/editor.planx.uk/src/pages/FlowEditor/components/Settings/Submissions/EventsLog.tsx @@ -15,7 +15,7 @@ import TableHead from "@mui/material/TableHead"; import TableRow from "@mui/material/TableRow"; import Typography from "@mui/material/Typography"; import DelayedLoadingIndicator from "components/DelayedLoadingIndicator"; -import ErrorFallback from "components/ErrorFallback"; +import ErrorFallback from "components/Error/ErrorFallback"; import { format } from "date-fns"; import React, { useState } from "react"; import ErrorSummary from "ui/shared/ErrorSummary"; diff --git a/editor.planx.uk/src/pages/FlowEditor/components/forms/FormModal.tsx b/editor.planx.uk/src/pages/FlowEditor/components/forms/FormModal.tsx index 9e400b8267..a937b8d259 100644 --- a/editor.planx.uk/src/pages/FlowEditor/components/forms/FormModal.tsx +++ b/editor.planx.uk/src/pages/FlowEditor/components/forms/FormModal.tsx @@ -9,7 +9,7 @@ import IconButton from "@mui/material/IconButton"; import { styled } from "@mui/material/styles"; import { ComponentType as TYPES } from "@opensystemslab/planx-core/types"; import { parseFormValues } from "@planx/components/shared"; -import ErrorFallback from "components/ErrorFallback"; +import ErrorFallback from "components/Error/ErrorFallback"; import { hasFeatureFlag } from "lib/featureFlags"; import React from "react"; import { ErrorBoundary } from "react-error-boundary"; diff --git a/editor.planx.uk/src/pages/Preview/Questions.tsx b/editor.planx.uk/src/pages/Preview/Questions.tsx index f287939af9..1819922734 100644 --- a/editor.planx.uk/src/pages/Preview/Questions.tsx +++ b/editor.planx.uk/src/pages/Preview/Questions.tsx @@ -12,7 +12,7 @@ import React, { useCallback, useEffect, useMemo, useState } from "react"; import { ErrorBoundary } from "react-error-boundary"; import { ApplicationPath, Session } from "types"; -import ErrorFallback from "../../components/ErrorFallback"; +import ErrorFallback from "../../components/Error/ErrorFallback"; import { useStore } from "../FlowEditor/lib/store"; import Node, { HandleSubmit } from "./Node"; diff --git a/editor.planx.uk/src/pages/layout/FlowEditorLayout.tsx b/editor.planx.uk/src/pages/layout/FlowEditorLayout.tsx index 327d8da975..d1d9e6eada 100644 --- a/editor.planx.uk/src/pages/layout/FlowEditorLayout.tsx +++ b/editor.planx.uk/src/pages/layout/FlowEditorLayout.tsx @@ -1,4 +1,4 @@ -import ErrorFallback from "components/ErrorFallback"; +import ErrorFallback from "components/Error/ErrorFallback"; import FlowEditor from "pages/FlowEditor"; import React, { PropsWithChildren } from "react"; import { ErrorBoundary } from "react-error-boundary"; diff --git a/editor.planx.uk/src/pages/layout/PublicLayout.tsx b/editor.planx.uk/src/pages/layout/PublicLayout.tsx index 23df5c9232..f7b21a23b1 100644 --- a/editor.planx.uk/src/pages/layout/PublicLayout.tsx +++ b/editor.planx.uk/src/pages/layout/PublicLayout.tsx @@ -6,7 +6,7 @@ import { ThemeProvider, } from "@mui/material/styles"; import Typography from "@mui/material/Typography"; -import ErrorFallback from "components/ErrorFallback"; +import ErrorFallback from "components/Error/ErrorFallback"; import Feedback from "components/Feedback"; import { useStore } from "pages/FlowEditor/lib/store"; import React, { PropsWithChildren } from "react";