({
- initialValues: {
- title: props.node?.data?.title || "Pay for your application",
- bannerTitle:
- props.node?.data?.bannerTitle ||
- "The planning fee for this application is",
- description:
- props.node?.data?.description ||
- `The planning fee covers the cost of processing your application.\
+const GOVPAY_DOCS_URL =
+ "https://docs.payments.service.gov.uk/reporting/#add-more-information-to-a-payment-39-custom-metadata-39-or-39-reporting-columns-39";
+
+/**
+ * Helper method to handle Formik errors in arrays
+ * Required as errors can be at array-level or field-level and the useFormikContext hook cannot correctly type infer this from the validation schema
+ * Docs: https://formik.org/docs/api/fieldarray#fieldarray-validation-gotchas
+ */
+const parseError = (
+ errors: string | undefined | GovPayMetadata[],
+ index: number,
+): string | undefined => {
+ // No errors
+ if (!errors) return;
+
+ // Array-level error - handled at a higher level
+ if (typeof errors === "string") return;
+
+ // No error for this field
+ if (!errors[index]) return;
+
+ // Specific field-level error
+ return errors[index].key || errors[index].value;
+};
+
+/**
+ * Helper method to handle Formik "touched" in arrays
+ * Please see parseError() for additional context
+ */
+const parseTouched = (
+ touched: string | undefined | GovPayMetadata[],
+ index: number,
+): string | undefined => {
+ // No errors
+ if (!touched) return;
+
+ // Array-level error - handled at a higher level
+ if (typeof touched === "string") return;
+
+ // No error for this field
+ if (!touched[index]) return;
+
+ // Specific field-level error
+ return touched[index].key && touched[index].value;
+};
+
+/**
+ * Disable required fields so they cannot be edited
+ * Only disable first instance, otherwise any field beginning with a required field will be disabled, and user will not be able to fix their mistake as the delete icon is also disabled
+ */
+const isFieldDisabled = (key: string, index: number) =>
+ REQUIRED_GOVPAY_METADATA.includes(key) &&
+ index === REQUIRED_GOVPAY_METADATA.indexOf(key);
+
+function GovPayMetadataEditor(props: ListManagerEditorProps) {
+ const { key: currKey, value: currVal } = props.value;
+ const isDisabled = isFieldDisabled(currKey, props.index);
+ const { errors, touched } = useFormikContext();
+ const error = parseError(
+ errors.govPayMetadata as string | undefined | GovPayMetadata[],
+ props.index,
+ );
+ const isTouched = parseTouched(
+ touched.govPayMetadata as string | undefined | GovPayMetadata[],
+ props.index,
+ );
+
+ return (
+
+
+
+
+ props.onChange({ key: newKey, value: currVal })
+ }
+ placeholder="key"
+ />
+
+ props.onChange({ key: currKey, value: newVal })
+ }
+ placeholder="value"
+ />
+
+
+
+ );
+}
+
+export type Props = EditorProps;
+
+const Component: React.FC = (props: Props) => {
+ const [flowName] = useStore((store) => [store.flowName]);
+ const displayGovPayMetadataSection = hasFeatureFlag("GOVPAY_METADATA");
+
+ const initialValues: Pay = {
+ title: props.node?.data?.title || "Pay for your application",
+ bannerTitle:
+ props.node?.data?.bannerTitle ||
+ "The planning fee for this application is",
+ description:
+ props.node?.data?.description ||
+ `The planning fee covers the cost of processing your application.\
Find out more about how planning fees are calculated (opens in new tab).
`,
- fn: props.node?.data?.fn,
- instructionsTitle: props.node?.data?.instructionsTitle || "How to pay",
- instructionsDescription:
- props.node?.data?.instructionsDescription ||
- `You can pay for your application by using GOV.UK Pay.
\
+ fn: props.node?.data?.fn,
+ instructionsTitle: props.node?.data?.instructionsTitle || "How to pay",
+ instructionsDescription:
+ props.node?.data?.instructionsDescription ||
+ `You can pay for your application by using GOV.UK Pay.
\
Your application will be sent after you have paid the fee. \
Wait until you see an application sent message before closing your browser.
`,
- hidePay: props.node?.data?.hidePay || false,
- allowInviteToPay: props.node?.data?.allowInviteToPay ?? true,
- secondaryPageTitle:
- props.node?.data?.secondaryPageTitle ||
- "Invite someone else to pay for this application",
- nomineeTitle:
- props.node?.data?.nomineeTitle || "Details of the person paying",
- nomineeDescription: props.node?.data?.nomineeDescription,
- yourDetailsTitle: props.node?.data?.yourDetailsTitle || "Your details",
- yourDetailsDescription: props.node?.data?.yourDetailsDescription,
- yourDetailsLabel:
- props.node?.data?.yourDetailsLabel || "Your name or organisation name",
- ...parseMoreInformation(props.node?.data),
- },
- onSubmit: (newValues) => {
- if (props.handleSubmit) {
- props.handleSubmit({ type: TYPES.Pay, data: newValues });
- }
- },
- validationSchema,
- validateOnChange: false,
- validateOnBlur: false,
- });
+ hidePay: props.node?.data?.hidePay || false,
+ allowInviteToPay: props.node?.data?.allowInviteToPay ?? true,
+ secondaryPageTitle:
+ props.node?.data?.secondaryPageTitle ||
+ "Invite someone else to pay for this application",
+ nomineeTitle:
+ props.node?.data?.nomineeTitle || "Details of the person paying",
+ nomineeDescription: props.node?.data?.nomineeDescription,
+ yourDetailsTitle: props.node?.data?.yourDetailsTitle || "Your details",
+ yourDetailsDescription: props.node?.data?.yourDetailsDescription,
+ yourDetailsLabel:
+ props.node?.data?.yourDetailsLabel || "Your name or organisation name",
+ govPayMetadata: props.node?.data?.govPayMetadata || [
+ {
+ key: "flow",
+ value: flowName,
+ },
+ {
+ key: "source",
+ value: "PlanX",
+ },
+ {
+ key: "isInviteToPay",
+ value: props.node?.data?.allowInviteToPay ?? true,
+ },
+ ],
+ ...parseMoreInformation(props.node?.data),
+ };
+
+ const onSubmit = (newValues: Pay) => {
+ if (props.handleSubmit) {
+ props.handleSubmit({ type: TYPES.Pay, data: newValues });
+ }
+ };
return (
-
+
+
+ {
+ setFieldValue("allowInviteToPay", !values.allowInviteToPay);
+ // Update GovUKMetadata
+ const inviteToPayIndex = values.govPayMetadata?.findIndex(
+ ({ key }) => key === "isInviteToPay",
+ );
+ setFieldValue(
+ `govPayMetadata[${inviteToPayIndex}].value`,
+ !values.allowInviteToPay,
+ );
+ }}
+ style={{ width: "100%" }}
+ >
+ Allow applicants to invite someone else to pay
+
+ {values.allowInviteToPay ? (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+ ) : (
+ <>>
+ )}
+
+
+
+
+
+ )}
+
);
-}
+};
export default Component;
diff --git a/editor.planx.uk/src/@planx/components/Pay/model.test.ts b/editor.planx.uk/src/@planx/components/Pay/model.test.ts
new file mode 100644
index 0000000000..9a2e0370c5
--- /dev/null
+++ b/editor.planx.uk/src/@planx/components/Pay/model.test.ts
@@ -0,0 +1,110 @@
+import { govPayMetadataSchema } from "./model";
+
+describe("GovPayMetadata Schema", () => {
+ const validate = async (payload: unknown) =>
+ await govPayMetadataSchema.validate(payload).catch((err) => err.errors);
+
+ const defaults = [
+ { key: "flow", value: "flowName" },
+ { key: "source", value: "PlanX" },
+ { key: "isInviteToPay", value: "true" },
+ ];
+
+ test("it requires all default values", async () => {
+ const errors = await validate([]);
+ expect(errors).toHaveLength(1);
+ expect(errors[0]).toMatch(
+ /Keys flow, source and isInviteToPay must be present/,
+ );
+ });
+
+ test("it allows valid data", async () => {
+ const input = [
+ ...defaults,
+ { key: "someKey", value: "someValue" },
+ { key: "someOtherKey", value: "someOtherValue" },
+ ];
+ const result = await validate(input);
+
+ // No errors, input returned from validation
+ expect(result).toEqual(input);
+ });
+
+ test("key is required", async () => {
+ const input = [...defaults, { value: "abc123" }];
+ const errors = await validate(input);
+ expect(errors).toHaveLength(1);
+ expect(errors[0]).toMatch(/Key is a required field/);
+ });
+
+ test("key cannot be greater than 30 characters", async () => {
+ const input = [
+ ...defaults,
+ {
+ key: "this is a very long key which exceeds the amount allowed by GovPay",
+ value: "def456",
+ },
+ ];
+ const errors = await validate(input);
+ expect(errors).toHaveLength(1);
+ expect(errors[0]).toMatch(/Key length cannot exceed 30 characters/);
+ });
+
+ test("value is required", async () => {
+ const input = [...defaults, { key: "abc123" }];
+ const errors = await validate(input);
+ expect(errors).toHaveLength(1);
+ expect(errors[0]).toMatch(/Value is a required field/);
+ });
+
+ test("value cannot be greater than 100 characters", async () => {
+ const input = [
+ ...defaults,
+ {
+ key: "abc123",
+ value:
+ "this is a very long value which exceeds the one-hundred character limit currently allowed by GovUKPay",
+ },
+ ];
+ const errors = await validate(input);
+ expect(errors).toHaveLength(1);
+ expect(errors[0]).toMatch(/Value length cannot exceed 100 characters/);
+ });
+
+ test("keys must be unique", async () => {
+ const input = [
+ ...defaults,
+ { key: "duplicatedKey", value: "someValue" },
+ { key: "duplicatedKey", value: "someOtherValue" },
+ ];
+ const errors = await validate(input);
+ expect(errors).toHaveLength(1);
+ expect(errors[0]).toMatch(/Keys must be unique/);
+ });
+
+ test("max 10 entries can be added", async () => {
+ const input = [
+ ...defaults,
+ { key: "four", value: "someValue" },
+ { key: "five", value: "someValue" },
+ { key: "six", value: "someValue" },
+ { key: "seven", value: "someValue" },
+ { key: "eight", value: "someValue" },
+ { key: "nine", value: "someValue" },
+ { key: "ten", value: "someValue" },
+ ];
+
+ const result = await validate(input);
+
+ // No errors, input returned from validation
+ expect(result).toEqual(input);
+
+ // Try 11 total values
+ const errors = await validate([
+ ...input,
+ { key: "eleven", value: "someValue" },
+ ]);
+ expect(errors).toHaveLength(1);
+ expect(errors[0]).toMatch(/A maximum of 10 fields can be set as metadata/);
+ });
+});
diff --git a/editor.planx.uk/src/@planx/components/Pay/model.ts b/editor.planx.uk/src/@planx/components/Pay/model.ts
index bd99028326..9229073e2c 100644
--- a/editor.planx.uk/src/@planx/components/Pay/model.ts
+++ b/editor.planx.uk/src/@planx/components/Pay/model.ts
@@ -1,9 +1,14 @@
import { useStore } from "pages/FlowEditor/lib/store";
import { ApplicationPath } from "types";
-import { boolean, object, string } from "yup";
+import { array, boolean, object, string } from "yup";
import type { MoreInformation } from "../shared";
+export interface GovPayMetadata {
+ key: string;
+ value: string;
+}
+
export interface Pay extends MoreInformation {
title: string;
bannerTitle?: string;
@@ -20,6 +25,7 @@ export interface Pay extends MoreInformation {
yourDetailsTitle?: string;
yourDetailsDescription?: string;
yourDetailsLabel?: string;
+ govPayMetadata?: GovPayMetadata[];
}
// https://docs.payments.service.gov.uk/making_payments/#creating-a-payment
@@ -90,6 +96,51 @@ const getReturnURL = (sessionId: string): string => {
export const GOV_UK_PAY_URL = `${process.env.REACT_APP_API_URL}/pay`;
+export const REQUIRED_GOVPAY_METADATA = ["flow", "source", "isInviteToPay"];
+
+// Validation must match requirements set out here -
+// https://docs.payments.service.gov.uk/reporting/#add-more-information-to-a-payment-39-custom-metadata-39-or-39-reporting-columns-39
+export const govPayMetadataSchema = array(
+ object({
+ key: string()
+ .required("Key is a required field")
+ .max(30, "Key length cannot exceed 30 characters"),
+ value: string()
+ .required("Value is a required field")
+ .max(100, "Value length cannot exceed 100 characters"),
+ }),
+)
+ .max(10, "A maximum of 10 fields can be set as metadata")
+ .test({
+ name: "unique-keys",
+ message: "Keys must be unique",
+ test: (metadata) => {
+ if (!metadata) return false;
+
+ const keys = metadata.map((item) => item.key);
+ const numKeys = keys?.length;
+ const numUniqueKeys = new Set(keys).size;
+
+ return numKeys === numUniqueKeys;
+ },
+ })
+ .test({
+ name: "required-keys",
+ message: `Keys ${new Intl.ListFormat("en-GB", {
+ style: "long",
+ type: "conjunction",
+ }).format(REQUIRED_GOVPAY_METADATA)} must be present`,
+ test: (metadata) => {
+ if (!metadata) return false;
+
+ const keys = metadata.map((item) => item.key);
+ const allRequiredKeysPresent = REQUIRED_GOVPAY_METADATA.every(
+ (requiredKey) => keys.includes(requiredKey),
+ );
+ return allRequiredKeysPresent;
+ },
+ });
+
export const validationSchema = object({
title: string().trim().required(),
bannerTitle: string().trim().required(),
@@ -119,4 +170,5 @@ export const validationSchema = object({
is: true,
then: string().required(),
}),
+ govPayMetadata: govPayMetadataSchema,
});
diff --git a/editor.planx.uk/src/@planx/components/PlanningConstraints/Editor.tsx b/editor.planx.uk/src/@planx/components/PlanningConstraints/Editor.tsx
index 59f2584d05..98e35f827d 100644
--- a/editor.planx.uk/src/@planx/components/PlanningConstraints/Editor.tsx
+++ b/editor.planx.uk/src/@planx/components/PlanningConstraints/Editor.tsx
@@ -61,6 +61,16 @@ function PlanningConstraintsComponent(props: Props) {
/>
+
+
+
+
+
{
title="Planning constraints"
description="Things that might affect your project"
fn="property.constraints.planning"
+ disclaimer="This page does not include information about historic planning conditions that may apply to this property."
handleSubmit={handleSubmit}
/>,
);
@@ -44,6 +45,7 @@ it("should not have any accessibility violations", async () => {
title="Planning constraints"
description="Things that might affect your project"
fn="property.constraints.planning"
+ disclaimer="This page does not include information about historic planning conditions that may apply to this property."
/>,
);
const results = await axe(container);
diff --git a/editor.planx.uk/src/@planx/components/PlanningConstraints/Public.tsx b/editor.planx.uk/src/@planx/components/PlanningConstraints/Public.tsx
index cd78e4a803..5042e782c9 100644
--- a/editor.planx.uk/src/@planx/components/PlanningConstraints/Public.tsx
+++ b/editor.planx.uk/src/@planx/components/PlanningConstraints/Public.tsx
@@ -23,7 +23,11 @@ import { ErrorSummaryContainer } from "../shared/Preview/ErrorSummaryContainer";
import SimpleExpand from "../shared/Preview/SimpleExpand";
import { WarningContainer } from "../shared/Preview/WarningContainer";
import ConstraintsList from "./List";
-import type { IntersectingConstraints, PlanningConstraints } from "./model";
+import {
+ DEFAULT_PLANNING_CONDITIONS_DISCLAIMER,
+ type IntersectingConstraints,
+ type PlanningConstraints,
+} from "./model";
type Props = PublicProps;
@@ -129,6 +133,7 @@ function Component(props: Props) {
title={props.title}
description={props.description || ""}
fn={props.fn}
+ disclaimer={props.disclaimer}
constraints={constraints}
metadata={metadata}
previousFeedback={props.previouslySubmittedData?.feedback}
@@ -193,6 +198,7 @@ export type PlanningConstraintsContentProps = {
title: string;
description: string;
fn: string;
+ disclaimer: string;
constraints: GISResponse["constraints"];
metadata: GISResponse["metadata"];
handleSubmit: (values: { feedback: string }) => void;
@@ -206,6 +212,7 @@ export function PlanningConstraintsContent(
const {
title,
description,
+ disclaimer,
constraints,
metadata,
handleSubmit,
@@ -257,7 +264,7 @@ export function PlanningConstraintsContent(
)}
-
+
>
)}
{!showError &&
@@ -284,19 +291,18 @@ export function PlanningConstraintsContent(
>
-
+
>
)}
);
}
-const PlanningConditionsInfo = () => (
+const Disclaimer = (props: { text: string }) => (
- This page does not include information about historic planning conditions
- that may apply to this property.
+ {props.text || DEFAULT_PLANNING_CONDITIONS_DISCLAIMER}
);
diff --git a/editor.planx.uk/src/@planx/components/PlanningConstraints/model.ts b/editor.planx.uk/src/@planx/components/PlanningConstraints/model.ts
index 45006d3961..5465b91851 100644
--- a/editor.planx.uk/src/@planx/components/PlanningConstraints/model.ts
+++ b/editor.planx.uk/src/@planx/components/PlanningConstraints/model.ts
@@ -4,6 +4,7 @@ export interface PlanningConstraints extends MoreInformation {
title: string;
description: string;
fn: string;
+ disclaimer: string;
}
export const parseContent = (
@@ -14,7 +15,11 @@ export const parseContent = (
data?.description ||
"Planning constraints might limit how you can develop or use the property",
fn: data?.fn || "property.constraints.planning",
+ disclaimer: data?.disclaimer || DEFAULT_PLANNING_CONDITIONS_DISCLAIMER,
...parseMoreInformation(data),
});
export type IntersectingConstraints = Record;
+
+export const DEFAULT_PLANNING_CONDITIONS_DISCLAIMER =
+ "This page does not include information about historic planning conditions that may apply to this property.";
diff --git a/editor.planx.uk/src/@planx/components/shared/Preview/MapContainer.tsx b/editor.planx.uk/src/@planx/components/shared/Preview/MapContainer.tsx
index 03bdd05f52..5fff936bc9 100644
--- a/editor.planx.uk/src/@planx/components/shared/Preview/MapContainer.tsx
+++ b/editor.planx.uk/src/@planx/components/shared/Preview/MapContainer.tsx
@@ -15,7 +15,7 @@ export const MapContainer = styled(Box)(
maxWidth: "none",
"& my-map": {
width: "100%",
- // Only increase map size in Preview & Unpublished routes
+ // Only increase map size in Published, Preview, and Draft routes
height:
size === "large" && environment === "standalone" ? "70vh" : "50vh",
},
diff --git a/editor.planx.uk/src/@planx/components/shared/Preview/SummaryList.tsx b/editor.planx.uk/src/@planx/components/shared/Preview/SummaryList.tsx
index 2f5338895f..630192dad7 100644
--- a/editor.planx.uk/src/@planx/components/shared/Preview/SummaryList.tsx
+++ b/editor.planx.uk/src/@planx/components/shared/Preview/SummaryList.tsx
@@ -272,17 +272,22 @@ function SummaryList(props: SummaryListProps) {
- If you change this answer, you’ll need to confirm all the other
- answers after it. This is because a changed answer might mean we have
- new or different questions to ask.
-
-
- Are you sure you want to change your answer?
+ Changing this answer means you will need to confirm any other answers
+ after it. This is because:
+
+ -
+ a different answer might mean the service asks new questions
+
+ -
+ your planning officer needs the right information to assess your
+ application
+
+
>
diff --git a/editor.planx.uk/src/components/ConfirmationDialog.tsx b/editor.planx.uk/src/components/ConfirmationDialog.tsx
index 9cc0daa9d1..955baf2c07 100644
--- a/editor.planx.uk/src/components/ConfirmationDialog.tsx
+++ b/editor.planx.uk/src/components/ConfirmationDialog.tsx
@@ -43,11 +43,15 @@ export const ConfirmationDialog: React.FC<
maxWidth="xl"
open={open}
>
- {title}
+ {title}
{children}
-
-
+
+
);
diff --git a/editor.planx.uk/src/components/Header.test.tsx b/editor.planx.uk/src/components/Header.test.tsx
index 8650eae1a9..6bb571dc9f 100644
--- a/editor.planx.uk/src/components/Header.test.tsx
+++ b/editor.planx.uk/src/components/Header.test.tsx
@@ -106,7 +106,7 @@ describe("Header Component - Editor Route", () => {
});
});
-for (const route of ["/preview", "/unpublished", "/pay", "/invite"]) {
+for (const route of ["/published", "/amber", "/draft", "/pay", "/invite"]) {
describe(`Header Component - ${route} Routes`, () => {
beforeAll(() => {
jest.spyOn(ReactNavi, "useCurrentRoute").mockImplementation(
@@ -165,7 +165,7 @@ describe("Section navigation bar", () => {
({
url: {
href: "test",
- pathname: "/team-name/flow-name/preview",
+ pathname: "/team-name/flow-name/published",
},
data: {
flow: "test-flow",
diff --git a/editor.planx.uk/src/components/Header.tsx b/editor.planx.uk/src/components/Header.tsx
index b95ad0c774..19b8481aa5 100644
--- a/editor.planx.uk/src/components/Header.tsx
+++ b/editor.planx.uk/src/components/Header.tsx
@@ -6,7 +6,7 @@ import Visibility from "@mui/icons-material/Visibility";
import AppBar from "@mui/material/AppBar";
import Avatar from "@mui/material/Avatar";
import Box from "@mui/material/Box";
-import { grey } from '@mui/material/colors';
+import { grey } from "@mui/material/colors";
import Container from "@mui/material/Container";
import IconButton from "@mui/material/IconButton";
import Link from "@mui/material/Link";
@@ -272,8 +272,7 @@ const NavBar: React.FC = () => {
],
);
const isSaveAndReturnLandingPage =
- path !== ApplicationPath.SingleSession &&
- !saveToEmail;
+ path !== ApplicationPath.SingleSession && !saveToEmail;
const isContentPage = useCurrentRoute()?.data?.isContentPage;
const { node } = useAnalyticsTracking();
const isSectionCard = node?.type == TYPES.Section;
@@ -454,15 +453,16 @@ const EditorToolbar: React.FC<{
)}
-
- {user.firstName[0]}{user.lastName[0]}
+ {user.firstName[0]}
+ {user.lastName[0]}
= ({ headerRef }) => {
]);
// Editor and custom domains share a path, so we need to rely on previewEnvironment
- if (previewEnvironment === "editor" && path !== "unpublished") {
+ if (previewEnvironment === "editor" && path !== "draft" && path !== "amber") {
return ;
}
switch (path) {
case flowSlug: // Custom domains
case "preview":
- case "unpublished":
+ case "amber":
+ case "draft":
case "pay":
return ;
default:
diff --git a/editor.planx.uk/src/lib/dataMergedHotfix.ts b/editor.planx.uk/src/lib/dataMergedHotfix.ts
deleted file mode 100644
index 7d553138d5..0000000000
--- a/editor.planx.uk/src/lib/dataMergedHotfix.ts
+++ /dev/null
@@ -1,70 +0,0 @@
-import { ComponentType as TYPES } from "@opensystemslab/planx-core/types";
-import gql from "graphql-tag";
-import { Store } from "pages/FlowEditor/lib/store";
-
-import { publicClient } from "../lib/graphql";
-
-const getFlowData = async (id: string) => {
- const { data } = await publicClient.query({
- query: gql`
- query GetFlowData($id: uuid!) {
- flows_by_pk(id: $id) {
- slug
- data
- }
- }
- `,
- variables: {
- id,
- },
- });
- return data.flows_by_pk;
-};
-
-// Flatten a flow's data to include main content & portals in a single JSON representation
-// XXX: getFlowData & dataMerged are currently repeated in api.planx.uk/helpers.ts
-// in order to load frontend /preview routes for flows that are not published
-export const dataMerged = async (
- id: string,
- ob: Store.flow = {},
-): Promise => {
- // get the primary flow data
- const { slug, data }: { slug: string; data: Store.flow } = await getFlowData(
- id,
- );
-
- // recursively get and flatten internal portals & external portals
- for (const [nodeId, node] of Object.entries(data)) {
- const isExternalPortalRoot =
- nodeId === "_root" && Object.keys(ob).length > 0;
- const isExternalPortal = node.type === TYPES.ExternalPortal;
- const isMerged = ob[node.data?.flowId];
-
- // Merge portal root as a new node in the graph
- if (isExternalPortalRoot) {
- ob[id] = {
- ...node,
- type: TYPES.InternalPortal,
- data: { text: slug },
- };
- }
-
- // Merge as internal portal, with reference to flowId
- else if (isExternalPortal) {
- ob[nodeId] = {
- type: TYPES.InternalPortal,
- edges: [node.data?.flowId],
- };
-
- // Recursively merge flow
- if (!isMerged) {
- await dataMerged(node.data?.flowId, ob);
- }
- }
-
- // Merge all other nodes
- else ob[nodeId] = node;
- }
-
- return ob;
-};
diff --git a/editor.planx.uk/src/lib/featureFlags.ts b/editor.planx.uk/src/lib/featureFlags.ts
index 1e270e08ee..52592ea1ad 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 = ["SUBMISSION_VIEW"] as const;
+const AVAILABLE_FEATURE_FLAGS = ["SUBMISSION_VIEW", "GOVPAY_METADATA"] as const;
type FeatureFlag = (typeof AVAILABLE_FEATURE_FLAGS)[number];
diff --git a/editor.planx.uk/src/lib/graphql.ts b/editor.planx.uk/src/lib/graphql.ts
index 7c0a2aa4d6..b5ab8c15ea 100644
--- a/editor.planx.uk/src/lib/graphql.ts
+++ b/editor.planx.uk/src/lib/graphql.ts
@@ -174,7 +174,7 @@ export const client = new ApolloClient({
/**
* Client used to make requests in all public interface
- * e.g. /preview, /unpublished, /pay
+ * e.g. /published, /amber, /draft, /pay
*/
export const publicClient = new ApolloClient({
link: from([retryLink, errorLink, publicHttpLink]),
diff --git a/editor.planx.uk/src/pages/FlowEditor/components/Flow/components/Portal.tsx b/editor.planx.uk/src/pages/FlowEditor/components/Flow/components/Portal.tsx
index eddd97650d..73a69d9fed 100644
--- a/editor.planx.uk/src/pages/FlowEditor/components/Flow/components/Portal.tsx
+++ b/editor.planx.uk/src/pages/FlowEditor/components/Flow/components/Portal.tsx
@@ -51,7 +51,7 @@ const ExternalPortal: React.FC = (props) => {
// If the flow referenced by an external portal has been deleted (eg !data),
// still show a "Corrupted" node so that editors have a visual cue to "delete".
- // Until deleted, the flow schema will still contain a node reference to this portal causing dataMerged to fail
+ // Until deleted, the flow schema will still contain a node reference to this portal causing publishing to fail
if (!loading && !data?.flows_by_pk) {
return (
({
flexDirection: "column",
}));
+const formatLastPublish = (date: string, user: string) =>
+ `Last published ${formatDistanceToNow(new Date(date))} ago by ${user}`;
+
const DebugConsole = () => {
const [passport, breadcrumbs, flowId, cachedBreadcrumbs] = useStore(
(state) => [
@@ -66,7 +78,13 @@ const DebugConsole = () => {
);
};
-function PublishChangeItem(props: any) {
+interface AlteredNode {
+ id: string;
+ type: TYPES;
+ data?: any;
+}
+
+const AlteredNodeListItem = (props: { node: AlteredNode }) => {
const { node } = props;
let text, data;
@@ -84,13 +102,188 @@ function PublishChangeItem(props: any) {
}
return (
- <>
+
{text}
{data && {data}
}
- >
+
);
+};
+
+interface Portal {
+ text: string;
+ flowId: string;
+ publishedFlowId: number;
+ summary: string;
+ publishedBy: number;
+ publishedAt: string;
}
+const AlteredNestedFlowListItem = (props: Portal) => {
+ const { text, flowId, publishedFlowId, summary, publishedAt } = props;
+
+ const [nestedFlowLastPublishedTitle, setNestedFlowLastPublishedTitle] =
+ useState();
+ const lastPublisher = useStore((state) => state.lastPublisher);
+
+ const _nestedFlowLastPublishedRequest = useAsync(async () => {
+ const user = await lastPublisher(flowId);
+ setNestedFlowLastPublishedTitle(formatLastPublish(publishedAt, user));
+ });
+
+ return (
+
+
+ {text}
+
+ ) : (
+ {text}
+ )
+ }
+ secondary={
+ <>
+
+ {nestedFlowLastPublishedTitle}
+
+ {summary && (
+
+ {summary}
+
+ )}
+ >
+ }
+ />
+
+ );
+};
+
+interface AlteredNodesSummary {
+ title: string;
+ portals: Portal[];
+ updated: number;
+ deleted: number;
+}
+
+const AlteredNodesSummaryContent = (props: {
+ alteredNodes: AlteredNode[];
+ url: string;
+}) => {
+ const { alteredNodes, url } = props;
+ const [expandNodes, setExpandNodes] = useState(false);
+
+ const changeSummary: AlteredNodesSummary = {
+ title: "",
+ portals: [],
+ updated: 0,
+ deleted: 0,
+ };
+
+ alteredNodes.map((node) => {
+ if (node.id === "0") {
+ changeSummary["title"] =
+ "You are publishing the main service for the first time.";
+ } else if (node.id && Object.keys(node).length === 1) {
+ changeSummary["deleted"] += 1;
+ } else if (node.type === TYPES.InternalPortal) {
+ if (node.data?.text?.includes("/")) {
+ changeSummary["portals"].push({ ...node.data, flowId: node.id });
+ }
+ } else if (node.type) {
+ changeSummary["updated"] += 1;
+ }
+
+ return changeSummary;
+ });
+
+ return (
+
+ {changeSummary["title"] && (
+
+ {changeSummary["title"]}
+
+ )}
+ {(changeSummary["updated"] > 0 || changeSummary["deleted"] > 0) && (
+
+
+ -
+ {`${changeSummary["updated"]} nodes have been updated or added`}
+
+ -
+ {`${changeSummary["deleted"]} nodes have been deleted`}
+
+
+
+
+ {`See detailed changelog `}
+
+
+
+
+
+ {alteredNodes.map((node) => (
+
+ ))}
+
+
+
+
+
+ )}
+
+ {changeSummary["portals"].length > 0 && (
+
+ {`This includes recently published changes in the following nested services:`}
+
+ {changeSummary["portals"].map((portal) => (
+
+ ))}
+
+
+ )}
+
+
+
+ {`Preview these content changes in-service before publishing `}
+
+ {`here`}
+
+ {` (opens in a new tab).`}
+
+
+
+ );
+};
+
const PreviewBrowser: React.FC<{
url: string;
}> = React.memo((props) => {
@@ -104,6 +297,7 @@ const PreviewBrowser: React.FC<{
lastPublisher,
validateAndDiffFlow,
isFlowPublished,
+ isPlatformAdmin,
] = useStore((state) => [
state.id,
state.flowAnalyticsLink,
@@ -113,19 +307,17 @@ const PreviewBrowser: React.FC<{
state.lastPublisher,
state.validateAndDiffFlow,
state.isFlowPublished,
+ state.user?.isPlatformAdmin,
]);
const [key, setKey] = useState(false);
const [lastPublishedTitle, setLastPublishedTitle] = useState(
"This flow is not published yet",
);
const [validationMessage, setValidationMessage] = useState();
- const [alteredNodes, setAlteredNodes] = useState
@@ -174,7 +185,7 @@ export default function ListManager(
onClick={() => {
props.onChange([...props.values, props.newValue()]);
}}
- disabled={isViewOnly}
+ disabled={isViewOnly || isMaxLength}
>
{props.newValueLabel || "add new"}
diff --git a/editor.planx.uk/src/ui/shared/Input.tsx b/editor.planx.uk/src/ui/shared/Input.tsx
index cd11330f26..49bd131f0c 100644
--- a/editor.planx.uk/src/ui/shared/Input.tsx
+++ b/editor.planx.uk/src/ui/shared/Input.tsx
@@ -101,6 +101,7 @@ export default forwardRef((props: Props, ref): FCReturn => {
errorMessage,
"aria-label": ariaLabel,
"aria-describedby": ariaDescribedBy,
+ "aria-labelledby": ariaLabelledBy,
id,
...restProps
} = props;
@@ -127,6 +128,7 @@ export default forwardRef((props: Props, ref): FCReturn => {
inputProps={{
"aria-label": ariaLabel,
"aria-describedby": ariaDescribedBy,
+ "aria-labelledby": ariaLabelledBy,
}}
id={id}
ref={container}
diff --git a/hasura.planx.uk/migrations/1710431558909_alter_view_public_submission_services_summary_reinstate_allow_list_answers/down.sql b/hasura.planx.uk/migrations/1710431558909_alter_view_public_submission_services_summary_reinstate_allow_list_answers/down.sql
new file mode 100644
index 0000000000..9b137b1565
--- /dev/null
+++ b/hasura.planx.uk/migrations/1710431558909_alter_view_public_submission_services_summary_reinstate_allow_list_answers/down.sql
@@ -0,0 +1,119 @@
+drop view "public"."submission_services_summary";
+
+-- Previous instance of view from hasura.planx.uk/migrations/1710409167587_alter_view_submission_services_summary_json_keys_to_camel_case/up.sql
+create or replace view "public"."submission_services_summary" as
+ with resumes_per_session as (
+ select
+ session_id,
+ count(id) as number_times_resumed
+ from reconciliation_requests
+ group by session_id
+), bops_agg as (
+ select
+ session_id,
+ json_agg(
+ json_build_object(
+ 'id', bops_id,
+ 'submittedAt', created_at,
+ 'destinationUrl', destination_url
+ ) order by created_at desc
+ ) as bops_applications
+ from bops_applications
+ group by session_id
+), email_agg as (
+ select
+ session_id,
+ json_agg(
+ json_build_object(
+ 'id', id,
+ 'recipient', recipient,
+ 'submittedAt', created_at
+ ) order by created_at desc
+ ) as email_applications
+ from email_applications
+ group by session_id
+), uniform_agg as (
+ select
+ submission_reference,
+ json_agg(
+ json_build_object(
+ 'id', idox_submission_id,
+ 'submittedAt', created_at
+ ) order by created_at desc
+ ) as uniform_applications
+ from uniform_applications
+ group by submission_reference
+), payment_requests_agg as (
+ select
+ session_id,
+ json_agg(
+ json_build_object(
+ 'id', id,
+ 'createdAt', created_at,
+ 'paidAt', paid_at,
+ 'govpayPaymentId', govpay_payment_id
+ ) order by created_at desc
+ ) as payment_requests
+ from payment_requests
+ group by session_id
+), payment_status_agg as (
+ select
+ session_id,
+ json_agg(
+ json_build_object(
+ 'govpayPaymentId', payment_id,
+ 'createdAt', created_at,
+ 'status', status
+ ) order by created_at desc
+ ) as payment_status
+ from payment_status
+ group by session_id
+)
+select
+ ls.id::text as session_id,
+ t.slug as team_slug,
+ f.slug as service_slug,
+ ls.created_at,
+ ls.submitted_at,
+ (ls.submitted_at::date - ls.created_at::date) as session_length_days,
+ ls.has_user_saved as user_clicked_save,
+ rps.number_times_resumed,
+ case
+ when pr.payment_requests::jsonb is not null and jsonb_array_length(pr.payment_requests::jsonb) > 0
+ then true
+ else false
+ end as user_invited_to_pay,
+ pr.payment_requests,
+ ps.payment_status,
+ case
+ when ba.bops_applications::jsonb is not null and jsonb_array_length(ba.bops_applications::jsonb) > 0
+ then true
+ else false
+ end as sent_to_bops,
+ ba.bops_applications,
+ case
+ when ua.uniform_applications::jsonb is not null and jsonb_array_length(ua.uniform_applications::jsonb) > 0
+ then true
+ else false
+ end as sent_to_uniform,
+ ua.uniform_applications,
+ case
+ when ea.email_applications::jsonb is not null and jsonb_array_length(ea.email_applications::jsonb) > 0
+ then true
+ else false
+ end as sent_to_email,
+ ea.email_applications
+from lowcal_sessions ls
+ left join flows f on f.id = ls.flow_id
+ left join teams t on t.id = f.team_id
+ left join resumes_per_session rps on rps.session_id = ls.id::text
+ left join payment_requests_agg pr on pr.session_id = ls.id
+ left join payment_status_agg ps on ps.session_id = ls.id
+ left join bops_agg ba on ba.session_id = ls.id::text
+ left join uniform_agg ua on ua.submission_reference = ls.id::text
+ left join email_agg ea on ea.session_id = ls.id
+where f.slug is not null
+ and t.slug is not null;
+
+-- After recreating the view grant Metabase access to it
+GRANT SELECT ON public.submission_services_summary TO metabase_read_only;
\ No newline at end of file
diff --git a/hasura.planx.uk/migrations/1710431558909_alter_view_public_submission_services_summary_reinstate_allow_list_answers/up.sql b/hasura.planx.uk/migrations/1710431558909_alter_view_public_submission_services_summary_reinstate_allow_list_answers/up.sql
new file mode 100644
index 0000000000..66f80f2902
--- /dev/null
+++ b/hasura.planx.uk/migrations/1710431558909_alter_view_public_submission_services_summary_reinstate_allow_list_answers/up.sql
@@ -0,0 +1,119 @@
+drop view "public"."submission_services_summary";
+
+create or replace view "public"."submission_services_summary" as
+ with resumes_per_session as (
+ select
+ session_id,
+ count(id) as number_times_resumed
+ from reconciliation_requests
+ group by session_id
+), bops_agg as (
+ select
+ session_id,
+ json_agg(
+ json_build_object(
+ 'id', bops_id,
+ 'submittedAt', created_at,
+ 'destinationUrl', destination_url
+ ) order by created_at desc
+ ) as bops_applications
+ from bops_applications
+ group by session_id
+), email_agg as (
+ select
+ session_id,
+ json_agg(
+ json_build_object(
+ 'id', id,
+ 'recipient', recipient,
+ 'submittedAt', created_at
+ ) order by created_at desc
+ ) as email_applications
+ from email_applications
+ group by session_id
+), uniform_agg as (
+ select
+ submission_reference,
+ json_agg(
+ json_build_object(
+ 'id', idox_submission_id,
+ 'submittedAt', created_at
+ ) order by created_at desc
+ ) as uniform_applications
+ from uniform_applications
+ group by submission_reference
+), payment_requests_agg as (
+ select
+ session_id,
+ json_agg(
+ json_build_object(
+ 'id', id,
+ 'createdAt', created_at,
+ 'paidAt', paid_at,
+ 'govpayPaymentId', govpay_payment_id
+ ) order by created_at desc
+ ) as payment_requests
+ from payment_requests
+ group by session_id
+), payment_status_agg as (
+ select
+ session_id,
+ json_agg(
+ json_build_object(
+ 'govpayPaymentId', payment_id,
+ 'createdAt', created_at,
+ 'status', status
+ ) order by created_at desc
+ ) as payment_status
+ from payment_status
+ group by session_id
+)
+select
+ ls.id::text as session_id,
+ t.slug as team_slug,
+ f.slug as service_slug,
+ ls.created_at,
+ ls.submitted_at,
+ (ls.submitted_at::date - ls.created_at::date) as session_length_days,
+ ls.has_user_saved as user_clicked_save,
+ rps.number_times_resumed,
+ ls.allow_list_answers,
+ case
+ when pr.payment_requests::jsonb is not null and jsonb_array_length(pr.payment_requests::jsonb) > 0
+ then true
+ else false
+ end as user_invited_to_pay,
+ pr.payment_requests,
+ ps.payment_status,
+ case
+ when ba.bops_applications::jsonb is not null and jsonb_array_length(ba.bops_applications::jsonb) > 0
+ then true
+ else false
+ end as sent_to_bops,
+ ba.bops_applications,
+ case
+ when ua.uniform_applications::jsonb is not null and jsonb_array_length(ua.uniform_applications::jsonb) > 0
+ then true
+ else false
+ end as sent_to_uniform,
+ ua.uniform_applications,
+ case
+ when ea.email_applications::jsonb is not null and jsonb_array_length(ea.email_applications::jsonb) > 0
+ then true
+ else false
+ end as sent_to_email,
+ ea.email_applications
+from lowcal_sessions ls
+ left join flows f on f.id = ls.flow_id
+ left join teams t on t.id = f.team_id
+ left join resumes_per_session rps on rps.session_id = ls.id::text
+ left join payment_requests_agg pr on pr.session_id = ls.id
+ left join payment_status_agg ps on ps.session_id = ls.id
+ left join bops_agg ba on ba.session_id = ls.id::text
+ left join uniform_agg ua on ua.submission_reference = ls.id::text
+ left join email_agg ea on ea.session_id = ls.id
+where f.slug is not null
+ and t.slug is not null;
+
+-- After recreating the view grant Metabase access to it
+GRANT SELECT ON public.submission_services_summary TO metabase_read_only;
\ No newline at end of file
diff --git a/hasura.planx.uk/package.json b/hasura.planx.uk/package.json
index 4545c9d51a..1b859f4845 100644
--- a/hasura.planx.uk/package.json
+++ b/hasura.planx.uk/package.json
@@ -7,7 +7,7 @@
},
"pnpm": {
"overrides": {
- "follow-redirects@<1.15.4": ">=1.15.4"
+ "follow-redirects@<=1.15.5": ">=1.15.6"
}
}
}
diff --git a/hasura.planx.uk/pnpm-lock.yaml b/hasura.planx.uk/pnpm-lock.yaml
index 0f9f11c4a1..95f13bf7fa 100644
--- a/hasura.planx.uk/pnpm-lock.yaml
+++ b/hasura.planx.uk/pnpm-lock.yaml
@@ -5,7 +5,7 @@ settings:
excludeLinksFromLockfile: false
overrides:
- follow-redirects@<1.15.4: '>=1.15.4'
+ follow-redirects@<=1.15.5: '>=1.15.6'
devDependencies:
hasura-cli:
@@ -24,7 +24,7 @@ packages:
/axios@0.21.4:
resolution: {integrity: sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==}
dependencies:
- follow-redirects: 1.15.4
+ follow-redirects: 1.15.6
transitivePeerDependencies:
- debug
dev: true
@@ -53,8 +53,8 @@ packages:
engines: {node: '>=0.8.0'}
dev: true
- /follow-redirects@1.15.4:
- resolution: {integrity: sha512-Cr4D/5wlrb0z9dgERpUL3LrmPKVDsETIJhaCMeDfuFYcqa5bldGV6wBsAN6X/vxlXQtFBMrXdXxdL8CbDTGniw==}
+ /follow-redirects@1.15.6:
+ resolution: {integrity: sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==}
engines: {node: '>=4.0'}
peerDependencies:
debug: '*'
diff --git a/infrastructure/application/Pulumi.production.yaml b/infrastructure/application/Pulumi.production.yaml
index 2077d6da1f..7ccbad24c8 100644
--- a/infrastructure/application/Pulumi.production.yaml
+++ b/infrastructure/application/Pulumi.production.yaml
@@ -15,12 +15,6 @@ config:
application:google-client-id: 987324067365-vpsk3kgeq5n32ihjn760ihf8l7m5rhh8.apps.googleusercontent.com
application:google-client-secret:
secure: AAABAN5E+De3A3HtpLVaSNTDwk9Uz4r2d5g8SIRVbNOd2fj3eU+lGJXjVbEAnxezr14hwabbfwW2ptjcFzqkhG7OmQ==
- application:gov-uk-pay-token-buckinghamshire:
- secure: AAABAA0ya8JhmFcgwuuf5i+XiJZ/uDp4SEGplUiiojyZzDtbjlZZfd8tNw8PjqKHzMBIhgyB2gHSsx9D/FV+cxxBa1AfIFtcj/l3rIrkK9jeG+yXtHnsLKr7MsZUqi6SdFft
- application:gov-uk-pay-token-lambeth:
- secure: AAABAK7qJXCA9mN96DeDWHqRDY7xe5glU9NZ2oTWVxYHR0NhkA83JOnI6dhknhUh8m7IMyy59lFIzeFCG3gOoWvkoTeQNiok7T91ek2f8UQqSSzgfUzgftbRm2sCkS5Yn0bW
- application:gov-uk-pay-token-southwark:
- secure: AAABAEosPvNevt9G7xLgTJIgyPDe9ZmXZnQa2BIsk7glewLz+EFzCvMVFq+9RkiEzkg9GdV2S7ttZ+6fCb5gByNbM7n2JcVG6a+LH9nHv6NwcoZ6U8ORArwiL4uO8xvGQQzD
application:govuk-notify-api-key:
secure: AAABADo05EPv/HWj7Rkf19nBeTcPJd4pEcRi2/uhyB3agraFODpLvNMx2bXfISf5pZ4HA41GYCE4f7OLcJN6hIV6ZMWUlEriPzvkoUAixbLlz1LIERiyk73R8E4F2bV65/9aFqi4l7caLS5c8iDJrE+JAvu2i7oS
application:hasura-admin-secret:
diff --git a/infrastructure/application/Pulumi.staging.yaml b/infrastructure/application/Pulumi.staging.yaml
index ac8e78abb8..7f9f00e596 100644
--- a/infrastructure/application/Pulumi.staging.yaml
+++ b/infrastructure/application/Pulumi.staging.yaml
@@ -16,18 +16,6 @@ config:
application:google-client-id: 987324067365-vpsk3kgeq5n32ihjn760ihf8l7m5rhh8.apps.googleusercontent.com
application:google-client-secret:
secure: AAABAGQuqQDU4S+vR+cQaFoa6xAeWU9clVaNonQ/dq0R8Dke+o0y7ALOmYMy4fOX4Pa6HiZl85npU/cbwy8HdMYaiA==
- application:gov-uk-pay-token-buckinghamshire:
- secure: AAABADroqKJ1/CanxoghKyCutFA8bmiPBuafrNYGNMn1H16jXiHuytHUUByTbXZZHtANciv7rkQEJosUmyay5j/ZFKu9TeS2WaIGBD913EVlv4iXDw3Y5OU2bSocROlYQm7/
- application:gov-uk-pay-token-camden:
- secure: AAABAA2gkhNBs2hOfIkhHiA50MF3X8xnaGvLVzWdg0OOTl9qOKgtCjS76/XBIpGsGEyFbtHwuOgWhPw1qgql7MBO+pTnfLzDc8WcbxQFMbIjKUAgqF4yUMu75jcOiJ9XadNq
- application:gov-uk-pay-token-gloucester:
- secure: AAABAMzflVg0cd5sjaETPp/s+OhgAr9UC4p8B72HiLHLUmNoFlsdWmCi4Z9rX6fEx+R008e0JHTz6REYQXWegH80QYMhKsUonmMON8/QMiz3AbDfZnEOGovMuC7mFLGpRDqn
- application:gov-uk-pay-token-lambeth:
- secure: AAABAPy5USkd8/hwq6vFXP45BXsYFUltR6gj8PoiZkOLRPUd1wgQ3Yhgc1Cyn+lb5cZrXBoVPjuVhm/UvBN82DNzRTl2TxAakCQQIrBU5xil+m9UnbY82CNSMDuEaWwMpR3C
- application:gov-uk-pay-token-medway:
- secure: AAABAOf9pQgmUkPWbyBQpkd2eZDtzx8WhHfPMD+V8lDLP/hqo24ZZyCrDfq6VBrcEeZVL89dvJ/PIVng9V5xFDKwpRcChTsOsen6epWGE/I0zwDdwrONmxgbPXnGgxLDtiEp
- application:gov-uk-pay-token-southwark:
- secure: AAABALGCrA9ZqRLql+ZHRQD/q6GiGNihtdgPL/7k5d37vgjqW115YR30HG9ofE00qP2Hkr2ZkYkJhVCIr9G5l1wSGXBNI+ldXxTCU8PWLGWv+Xa+Sv5Ltgd9egmwBBqUgvwe
application:govuk-notify-api-key:
secure: AAABACgwjEmlLmE19ofRO8e/JpD8sHDV2lcDmSXbU/Mw8ZRh5gTgll8DZ3BVjpDWfQfIecBAIf2TFgeo9CsBSLjfaRJ7eJyKDSWm7i8LlMC2JN/PN+Ig8oeI0H0oLkqJIziNKKjx+e97zDiXO9LZ1CVzrywR
application:hasura-admin-secret:
diff --git a/infrastructure/application/utils/generateTeamSecrets.ts b/infrastructure/application/utils/generateTeamSecrets.ts
index 747c3f195a..9dcf6c1b77 100644
--- a/infrastructure/application/utils/generateTeamSecrets.ts
+++ b/infrastructure/application/utils/generateTeamSecrets.ts
@@ -25,21 +25,6 @@ export const generateTeamSecrets = (
): awsx.ecs.KeyValuePair[] => {
const secrets: awsx.ecs.KeyValuePair[] = [];
teams.forEach((team) => {
- switch(env) {
- case "staging":
- secrets.push({
- name: `GOV_UK_PAY_TOKEN_${name(team.name)}`,
- value: config.require(`gov-uk-pay-token-${value(team.name)}`),
- });
- break;
- case "production":
- if (!team?.govPayStagingOnly) {
- secrets.push({
- name: `GOV_UK_PAY_TOKEN_${name(team.name)}`,
- value: config.require(`gov-uk-pay-token-${value(team.name)}`),
- });
- }
- };
team.uniformInstances?.forEach((instance) => {
secrets.push({
name: `UNIFORM_CLIENT_${name(instance)}`,