From 8fb8e0699e0c046fc4ada1f0aa04127edfc2c394 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Dafydd=20Ll=C5=B7r=20Pearson?=
Date: Mon, 2 Oct 2023 16:13:08 +0100
Subject: [PATCH 1/8] feat: Add payment exemption status to Slack notifications
(#2251)
* refactor: Move webhook routes from server to module
* refactor: Update sendSlackNotification to modular structure
* test: Add email-submission tests
* refactor: Rename and restructure folders, simplify service
* feat: Add exemption status to notification
* docs: Add Swagger docs
* fix: Don't use fee to calculate exemption statuses
---
.../_old}/lowcalSessionEvents.test.ts | 6 +-
.../webhooks/_old}/lowcalSessionEvents.ts | 4 +-
.../_old}/paymentRequestEvents.test.ts | 6 +-
.../webhooks/_old}/paymentRequestEvents.ts | 4 +-
.../sanitiseApplicationData/index.test.ts | 2 +-
.../_old}/sanitiseApplicationData/index.ts | 2 +-
.../sanitiseApplicationData/mocks/queries.ts | 0
.../operations.test.ts | 8 +-
.../sanitiseApplicationData/operations.ts | 8 +-
.../_old}/sanitiseApplicationData/types.d.ts | 0
api.planx.uk/modules/webhooks/controller.ts | 31 ++
api.planx.uk/modules/webhooks/docs.yaml | 135 +++++++
api.planx.uk/modules/webhooks/routes.ts | 40 ++
.../webhooks/sendNotification/index.test.ts | 345 ++++++++++++++++++
.../webhooks/sendNotification/schema.ts | 68 ++++
.../webhooks/sendNotification/service.ts | 64 ++++
.../webhooks/sendNotification/types.ts | 33 ++
api.planx.uk/server.ts | 36 +-
api.planx.uk/shared/middleware/validate.ts | 4 +-
.../webhooks/sendNotification.test.ts | 186 ----------
api.planx.uk/webhooks/sendNotifications.ts | 78 ----
21 files changed, 740 insertions(+), 320 deletions(-)
rename api.planx.uk/{webhooks => modules/webhooks/_old}/lowcalSessionEvents.test.ts (97%)
rename api.planx.uk/{webhooks => modules/webhooks/_old}/lowcalSessionEvents.ts (94%)
rename api.planx.uk/{webhooks => modules/webhooks/_old}/paymentRequestEvents.test.ts (98%)
rename api.planx.uk/{webhooks => modules/webhooks/_old}/paymentRequestEvents.ts (97%)
rename api.planx.uk/{webhooks => modules/webhooks/_old}/sanitiseApplicationData/index.test.ts (99%)
rename api.planx.uk/{webhooks => modules/webhooks/_old}/sanitiseApplicationData/index.ts (96%)
rename api.planx.uk/{webhooks => modules/webhooks/_old}/sanitiseApplicationData/mocks/queries.ts (100%)
rename api.planx.uk/{webhooks => modules/webhooks/_old}/sanitiseApplicationData/operations.test.ts (96%)
rename api.planx.uk/{webhooks => modules/webhooks/_old}/sanitiseApplicationData/operations.ts (96%)
rename api.planx.uk/{webhooks => modules/webhooks/_old}/sanitiseApplicationData/types.d.ts (100%)
create mode 100644 api.planx.uk/modules/webhooks/controller.ts
create mode 100644 api.planx.uk/modules/webhooks/docs.yaml
create mode 100644 api.planx.uk/modules/webhooks/routes.ts
create mode 100644 api.planx.uk/modules/webhooks/sendNotification/index.test.ts
create mode 100644 api.planx.uk/modules/webhooks/sendNotification/schema.ts
create mode 100644 api.planx.uk/modules/webhooks/sendNotification/service.ts
create mode 100644 api.planx.uk/modules/webhooks/sendNotification/types.ts
delete mode 100644 api.planx.uk/webhooks/sendNotification.test.ts
delete mode 100644 api.planx.uk/webhooks/sendNotifications.ts
diff --git a/api.planx.uk/webhooks/lowcalSessionEvents.test.ts b/api.planx.uk/modules/webhooks/_old/lowcalSessionEvents.test.ts
similarity index 97%
rename from api.planx.uk/webhooks/lowcalSessionEvents.test.ts
rename to api.planx.uk/modules/webhooks/_old/lowcalSessionEvents.test.ts
index 0158b7f888..0cddfe272b 100644
--- a/api.planx.uk/webhooks/lowcalSessionEvents.test.ts
+++ b/api.planx.uk/modules/webhooks/_old/lowcalSessionEvents.test.ts
@@ -1,10 +1,10 @@
import supertest from "supertest";
-import app from "../server";
-import { createScheduledEvent } from "../hasura/metadata";
+import app from "../../../server";
+import { createScheduledEvent } from "../../../hasura/metadata";
const { post } = supertest(app);
-jest.mock("../hasura/metadata");
+jest.mock("../../../hasura/metadata");
const mockedCreateScheduledEvent = createScheduledEvent as jest.MockedFunction<
typeof createScheduledEvent
>;
diff --git a/api.planx.uk/webhooks/lowcalSessionEvents.ts b/api.planx.uk/modules/webhooks/_old/lowcalSessionEvents.ts
similarity index 94%
rename from api.planx.uk/webhooks/lowcalSessionEvents.ts
rename to api.planx.uk/modules/webhooks/_old/lowcalSessionEvents.ts
index 407f951d82..1d50affdc3 100644
--- a/api.planx.uk/webhooks/lowcalSessionEvents.ts
+++ b/api.planx.uk/modules/webhooks/_old/lowcalSessionEvents.ts
@@ -1,11 +1,11 @@
import { addDays } from "date-fns";
import { Request, Response, NextFunction } from "express";
-import { createScheduledEvent } from "../hasura/metadata";
+import { createScheduledEvent } from "../../../hasura/metadata";
import {
DAYS_UNTIL_EXPIRY,
REMINDER_DAYS_FROM_EXPIRY,
-} from "../saveAndReturn/utils";
+} from "../../../saveAndReturn/utils";
/**
* Create "reminder" events for a lowcal_session record
diff --git a/api.planx.uk/webhooks/paymentRequestEvents.test.ts b/api.planx.uk/modules/webhooks/_old/paymentRequestEvents.test.ts
similarity index 98%
rename from api.planx.uk/webhooks/paymentRequestEvents.test.ts
rename to api.planx.uk/modules/webhooks/_old/paymentRequestEvents.test.ts
index eb0edb9e34..282ca011a5 100644
--- a/api.planx.uk/webhooks/paymentRequestEvents.test.ts
+++ b/api.planx.uk/modules/webhooks/_old/paymentRequestEvents.test.ts
@@ -1,10 +1,10 @@
import supertest from "supertest";
-import app from "../server";
-import { createScheduledEvent } from "../hasura/metadata";
+import app from "../../../server";
+import { createScheduledEvent } from "../../../hasura/metadata";
const { post } = supertest(app);
-jest.mock("../hasura/metadata");
+jest.mock("../../../hasura/metadata");
const mockedCreateScheduledEvent = createScheduledEvent as jest.MockedFunction<
typeof createScheduledEvent
>;
diff --git a/api.planx.uk/webhooks/paymentRequestEvents.ts b/api.planx.uk/modules/webhooks/_old/paymentRequestEvents.ts
similarity index 97%
rename from api.planx.uk/webhooks/paymentRequestEvents.ts
rename to api.planx.uk/modules/webhooks/_old/paymentRequestEvents.ts
index e9d2dda42e..30a387b228 100644
--- a/api.planx.uk/webhooks/paymentRequestEvents.ts
+++ b/api.planx.uk/modules/webhooks/_old/paymentRequestEvents.ts
@@ -1,11 +1,11 @@
import { addDays } from "date-fns";
import { Request, Response, NextFunction } from "express";
-import { createScheduledEvent } from "../hasura/metadata";
+import { createScheduledEvent } from "../../../hasura/metadata";
import {
DAYS_UNTIL_EXPIRY,
REMINDER_DAYS_FROM_EXPIRY,
-} from "../saveAndReturn/utils";
+} from "../../../saveAndReturn/utils";
/**
* Create two "invitation" events for a payments_request record: one for the nominee and one for the agent
diff --git a/api.planx.uk/webhooks/sanitiseApplicationData/index.test.ts b/api.planx.uk/modules/webhooks/_old/sanitiseApplicationData/index.test.ts
similarity index 99%
rename from api.planx.uk/webhooks/sanitiseApplicationData/index.test.ts
rename to api.planx.uk/modules/webhooks/_old/sanitiseApplicationData/index.test.ts
index 1eaf5b572e..96da6ca4b3 100644
--- a/api.planx.uk/webhooks/sanitiseApplicationData/index.test.ts
+++ b/api.planx.uk/modules/webhooks/_old/sanitiseApplicationData/index.test.ts
@@ -1,5 +1,5 @@
import supertest from "supertest";
-import app from "../../server";
+import app from "../../../../server";
import * as operations from "./operations";
const mockSend = jest.fn();
diff --git a/api.planx.uk/webhooks/sanitiseApplicationData/index.ts b/api.planx.uk/modules/webhooks/_old/sanitiseApplicationData/index.ts
similarity index 96%
rename from api.planx.uk/webhooks/sanitiseApplicationData/index.ts
rename to api.planx.uk/modules/webhooks/_old/sanitiseApplicationData/index.ts
index 4716d520aa..9454104edf 100644
--- a/api.planx.uk/webhooks/sanitiseApplicationData/index.ts
+++ b/api.planx.uk/modules/webhooks/_old/sanitiseApplicationData/index.ts
@@ -3,7 +3,7 @@ import { Request, Response, NextFunction } from "express";
import { getOperations, operationHandler } from "./operations";
import { OperationResult } from "./types";
-import { getFormattedEnvironment } from "../../helpers";
+import { getFormattedEnvironment } from "../../../../helpers";
/**
* Called by Hasura cron job `sanitise_application_data` on a nightly basis
diff --git a/api.planx.uk/webhooks/sanitiseApplicationData/mocks/queries.ts b/api.planx.uk/modules/webhooks/_old/sanitiseApplicationData/mocks/queries.ts
similarity index 100%
rename from api.planx.uk/webhooks/sanitiseApplicationData/mocks/queries.ts
rename to api.planx.uk/modules/webhooks/_old/sanitiseApplicationData/mocks/queries.ts
diff --git a/api.planx.uk/webhooks/sanitiseApplicationData/operations.test.ts b/api.planx.uk/modules/webhooks/_old/sanitiseApplicationData/operations.test.ts
similarity index 96%
rename from api.planx.uk/webhooks/sanitiseApplicationData/operations.test.ts
rename to api.planx.uk/modules/webhooks/_old/sanitiseApplicationData/operations.test.ts
index 79e4e3a8a6..9e178f682e 100644
--- a/api.planx.uk/webhooks/sanitiseApplicationData/operations.test.ts
+++ b/api.planx.uk/modules/webhooks/_old/sanitiseApplicationData/operations.test.ts
@@ -1,5 +1,5 @@
-import { runSQL } from "../../hasura/schema";
-import { queryMock } from "../../tests/graphqlQueryMock";
+import { runSQL } from "../../../../hasura/schema";
+import { queryMock } from "../../../../tests/graphqlQueryMock";
import {
mockIds,
mockSanitiseBOPSApplicationsMutation,
@@ -25,11 +25,11 @@ import {
deleteHasuraScheduledEventsForSubmittedSessions,
} from "./operations";
-jest.mock("../../hasura/schema");
+jest.mock("../../../../hasura/schema");
const mockRunSQL = runSQL as jest.MockedFunction;
const mockFindSession = jest.fn();
-jest.mock("../../client", () => {
+jest.mock("../../../../client", () => {
return {
$admin: {
session: {
diff --git a/api.planx.uk/webhooks/sanitiseApplicationData/operations.ts b/api.planx.uk/modules/webhooks/_old/sanitiseApplicationData/operations.ts
similarity index 96%
rename from api.planx.uk/webhooks/sanitiseApplicationData/operations.ts
rename to api.planx.uk/modules/webhooks/_old/sanitiseApplicationData/operations.ts
index 2ab3a08d46..7e27194681 100644
--- a/api.planx.uk/webhooks/sanitiseApplicationData/operations.ts
+++ b/api.planx.uk/modules/webhooks/_old/sanitiseApplicationData/operations.ts
@@ -2,10 +2,10 @@ import { gql } from "graphql-request";
import { subMonths } from "date-fns";
import { Operation, OperationResult } from "./types";
-import { adminGraphQLClient } from "../../hasura";
-import { runSQL } from "../../hasura/schema";
-import { getFilesForSession } from "../../session/files";
-import { deleteFilesByURL } from "../../s3/deleteFile";
+import { adminGraphQLClient } from "../../../../hasura";
+import { runSQL } from "../../../../hasura/schema";
+import { getFilesForSession } from "../../../../session/files";
+import { deleteFilesByURL } from "../../../../s3/deleteFile";
const RETENTION_PERIOD_MONTHS = 6;
export const getRetentionPeriod = () =>
diff --git a/api.planx.uk/webhooks/sanitiseApplicationData/types.d.ts b/api.planx.uk/modules/webhooks/_old/sanitiseApplicationData/types.d.ts
similarity index 100%
rename from api.planx.uk/webhooks/sanitiseApplicationData/types.d.ts
rename to api.planx.uk/modules/webhooks/_old/sanitiseApplicationData/types.d.ts
diff --git a/api.planx.uk/modules/webhooks/controller.ts b/api.planx.uk/modules/webhooks/controller.ts
new file mode 100644
index 0000000000..8b98358f89
--- /dev/null
+++ b/api.planx.uk/modules/webhooks/controller.ts
@@ -0,0 +1,31 @@
+import { ServerError } from "../../errors";
+import { sendSlackNotification } from "./sendNotification/service";
+import { SendSlackNotification } from "./sendNotification/types";
+
+export const sendSlackNotificationController: SendSlackNotification = async (
+ req,
+ res,
+ next,
+) => {
+ const isProduction = process.env.APP_ENVIRONMENT === "production";
+ if (!isProduction) {
+ return res.status(200).send({
+ message: `Staging application submitted, skipping Slack notification`,
+ });
+ }
+
+ const eventData = req.body.event.data.new;
+ const eventType = req.query.type;
+
+ try {
+ const data = await sendSlackNotification(eventData, eventType);
+ return res.status(200).send({ message: "Posted to Slack", data });
+ } catch (error) {
+ return next(
+ new ServerError({
+ message: `Failed to send ${eventType} Slack notification`,
+ cause: error,
+ }),
+ );
+ }
+};
diff --git a/api.planx.uk/modules/webhooks/docs.yaml b/api.planx.uk/modules/webhooks/docs.yaml
new file mode 100644
index 0000000000..837e64a8ea
--- /dev/null
+++ b/api.planx.uk/modules/webhooks/docs.yaml
@@ -0,0 +1,135 @@
+openapi: 3.1.0
+info:
+ title: Plan✕ API
+ version: 0.1.0
+tags:
+ - name: webhooks
+ description: Webhooks for event management
+components:
+ schemas:
+ Payload:
+ type: object
+ properties:
+ sessionId:
+ type: string
+ required:
+ - sessionId
+ BopsSubmissionSchema:
+ type: object
+ properties:
+ body:
+ type: object
+ properties:
+ event:
+ type: object
+ properties:
+ data:
+ type: object
+ properties:
+ new:
+ type: object
+ properties:
+ payload:
+ $ref: "#/components/schemas/Payload"
+ bops_id:
+ type: string
+ destination_url:
+ type: string
+ required:
+ - body
+ UniformSubmissionSchema:
+ type: object
+ properties:
+ body:
+ type: object
+ properties:
+ event:
+ type: object
+ properties:
+ data:
+ type: object
+ properties:
+ new:
+ type: object
+ properties:
+ payload:
+ $ref: "#/components/schemas/Payload"
+ submission_reference:
+ type: string
+ response:
+ type: object
+ properties:
+ organisation:
+ type: string
+ required:
+ - body
+ EmailSubmissionSchema:
+ type: object
+ properties:
+ body:
+ type: object
+ properties:
+ event:
+ type: object
+ properties:
+ data:
+ type: object
+ properties:
+ new:
+ type: object
+ properties:
+ session_id:
+ type: string
+ team_slug:
+ type: string
+ request:
+ type: object
+ properties:
+ personalisation:
+ type: object
+ properties:
+ serviceName:
+ type: string
+ required:
+ - body
+ SendSlackNotificationSchema:
+ oneOf:
+ - $ref: "#/components/schemas/BopsSubmissionSchema"
+ - $ref: "#/components/schemas/UniformSubmissionSchema"
+ - $ref: "#/components/schemas/EmailSubmissionSchema"
+ responses:
+ SlackNotificationSuccessMessage:
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ data:
+ type: string
+ required: false
+ description: The generated Slack message
+ message:
+ type: string
+ required: true
+paths:
+ /webhooks/hasura/sendSlackNotification:
+ post:
+ tags: ["webhooks"]
+ summary: Send Slack notification
+ description: Endpoint to trigger a Slack notification following a submission event
+ parameters:
+ - in: query
+ name: type
+ type: string
+ enum: ["bops-submission", "uniform-submission", "email-submission"]
+ required: true
+ requestBody:
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/SendSlackNotificationSchema"
+ responses:
+ "200":
+ $ref: "#/components/responses/SlackNotificationSuccessMessage"
+ "500":
+ $ref: "#/components/responses/ErrorMessage"
diff --git a/api.planx.uk/modules/webhooks/routes.ts b/api.planx.uk/modules/webhooks/routes.ts
new file mode 100644
index 0000000000..c98421b7e3
--- /dev/null
+++ b/api.planx.uk/modules/webhooks/routes.ts
@@ -0,0 +1,40 @@
+import { Router } from "express";
+import { useHasuraAuth } from "../auth/middleware";
+import { createPaymentSendEvents } from "../../inviteToPay/createPaymentSendEvents";
+import { sanitiseApplicationData } from "./_old/sanitiseApplicationData";
+import {
+ createExpiryEvent,
+ createReminderEvent,
+} from "./_old/lowcalSessionEvents";
+import {
+ createPaymentExpiryEvents,
+ createPaymentInvitationEvents,
+ createPaymentReminderEvents,
+} from "./_old/paymentRequestEvents";
+import { validate } from "../../shared/middleware/validate";
+import { sendSlackNotificationController } from "./controller";
+import { sendSlackNotificationSchema } from "./sendNotification/schema";
+
+const router = Router();
+
+router.use("/hasura", useHasuraAuth);
+router.post("/hasura/create-reminder-event", createReminderEvent);
+router.post("/hasura/create-expiry-event", createExpiryEvent);
+router.post(
+ "/hasura/create-payment-invitation-events",
+ createPaymentInvitationEvents,
+);
+router.post(
+ "/hasura/create-payment-reminder-events",
+ createPaymentReminderEvents,
+);
+router.post("/hasura/create-payment-expiry-events", createPaymentExpiryEvents);
+router.post("/hasura/create-payment-send-events", createPaymentSendEvents);
+router.post(
+ "/hasura/send-slack-notification",
+ validate(sendSlackNotificationSchema),
+ sendSlackNotificationController,
+);
+router.post("/hasura/sanitise-application-data", sanitiseApplicationData);
+
+export default router;
diff --git a/api.planx.uk/modules/webhooks/sendNotification/index.test.ts b/api.planx.uk/modules/webhooks/sendNotification/index.test.ts
new file mode 100644
index 0000000000..e73c115371
--- /dev/null
+++ b/api.planx.uk/modules/webhooks/sendNotification/index.test.ts
@@ -0,0 +1,345 @@
+import supertest from "supertest";
+import app from "../../../server";
+import SlackNotify from "slack-notify";
+import { BOPSBody, EmailBody, UniformBody } from "./types";
+import { $admin } from "../../../client";
+import { CoreDomainClient } from "@opensystemslab/planx-core";
+
+const mockSessionWithFee = {
+ data: {
+ passport: {
+ data: {
+ "application.fee.payable": "123",
+ },
+ },
+ },
+};
+
+const mockSessionWithDisabilityExemption = {
+ data: {
+ passport: {
+ data: {
+ "application.fee.exemption.disability": ["true"],
+ },
+ },
+ },
+};
+
+const mockSessionWithResubmissionExemption = {
+ data: {
+ passport: {
+ data: {
+ "application.fee.exemption.resubmission": ["true"],
+ },
+ },
+ },
+};
+
+jest.mock("../../../client");
+const mockAdmin = jest.mocked($admin);
+
+const mockSend = jest.fn();
+jest.mock("slack-notify", () =>
+ jest.fn().mockImplementation(() => {
+ return { send: mockSend };
+ }),
+);
+
+const { post } = supertest(app);
+
+describe("Send Slack notifications endpoint", () => {
+ const ENDPOINT = "/webhooks/hasura/send-slack-notification";
+ const ORIGINAL_ENV = process.env;
+
+ beforeEach(() => {
+ process.env = { ...ORIGINAL_ENV };
+ mockAdmin.session.find = jest.fn().mockResolvedValue(mockSessionWithFee);
+ mockSend.mockResolvedValue("Success!");
+ });
+
+ afterEach(jest.clearAllMocks);
+
+ afterAll(() => (process.env = ORIGINAL_ENV));
+
+ describe("authentication and validation", () => {
+ it("fails without correct authentication", async () => {
+ await post(ENDPOINT)
+ .expect(401)
+ .then((response) => {
+ expect(response.body).toEqual({
+ error: "Unauthorised",
+ });
+ });
+ });
+
+ it("returns a 400 if 'type' is missing", async () => {
+ const body = { event: {} };
+ await post(ENDPOINT)
+ .set({ Authorization: process.env.HASURA_PLANX_API_KEY })
+ .send(body)
+ .expect(400)
+ .then((response) => {
+ expect(response.body).toHaveProperty("issues");
+ expect(response.body).toHaveProperty("name", "ZodError");
+ });
+ });
+
+ it("returns a 400 if 'type' is incorrect", async () => {
+ const body = { event: {} };
+ await post(ENDPOINT + "?type=test-submission")
+ .set({ Authorization: process.env.HASURA_PLANX_API_KEY })
+ .send(body)
+ .expect(400)
+ .then((response) => {
+ expect(response.body).toHaveProperty("issues");
+ expect(response.body).toHaveProperty("name", "ZodError");
+ });
+ });
+
+ it("returns a 400 if 'event' is missing", async () => {
+ await post(ENDPOINT + "?type=bops-submission")
+ .set({ Authorization: process.env.HASURA_PLANX_API_KEY })
+ .expect(400)
+ .then((response) => {
+ expect(response.body).toHaveProperty("issues");
+ expect(response.body).toHaveProperty("name", "ZodError");
+ });
+ });
+ });
+
+ describe("BOPS notifications", () => {
+ const body: BOPSBody = {
+ event: {
+ data: {
+ new: {
+ payload: { sessionId: "xyz123" },
+ bops_id: "abc123",
+ destination_url: "https://www.bops-production.com",
+ },
+ },
+ },
+ };
+
+ it("skips the staging environment", async () => {
+ process.env.APP_ENVIRONMENT = "staging";
+ await post(ENDPOINT)
+ .query({ type: "bops-submission" })
+ .set({ Authorization: process.env.HASURA_PLANX_API_KEY })
+ .send(body)
+ .expect(200)
+ .then((response) => {
+ expect(response.body.message).toMatch(/skipping Slack notification/);
+ });
+ });
+
+ it("posts to Slack on success", async () => {
+ process.env.APP_ENVIRONMENT = "production";
+
+ await post(ENDPOINT)
+ .query({ type: "bops-submission" })
+ .set({ Authorization: process.env.HASURA_PLANX_API_KEY })
+ .send(body)
+ .expect(200)
+ .then((response) => {
+ expect(SlackNotify).toHaveBeenCalledWith(
+ process.env.SLACK_WEBHOOK_URL,
+ );
+ expect(mockSend).toHaveBeenCalledTimes(1);
+ expect(response.body.message).toBe("Posted to Slack");
+ expect(response.body.data).toMatch(/abc123/);
+ expect(response.body.data).toMatch(/www.bops-production.com/);
+ });
+ });
+
+ it("returns error when Slack fails", async () => {
+ process.env.APP_ENVIRONMENT = "production";
+ mockSend.mockRejectedValue("Fail!");
+
+ await post(ENDPOINT)
+ .query({ type: "bops-submission" })
+ .set({ Authorization: process.env.HASURA_PLANX_API_KEY })
+ .send(body)
+ .expect(500)
+ .then((response) => {
+ expect(mockSend).toHaveBeenCalledTimes(1);
+ expect(response.body.error).toMatch(/Failed to send/);
+ });
+ });
+ });
+
+ describe("Uniform notifications", () => {
+ const body: UniformBody = {
+ event: {
+ data: {
+ new: {
+ payload: { sessionId: "xyz123" },
+ submission_reference: "abc123",
+ response: {
+ organisation: "test-council",
+ },
+ },
+ },
+ },
+ };
+
+ it("skips the staging environment", async () => {
+ process.env.APP_ENVIRONMENT = "staging";
+ await post(ENDPOINT)
+ .query({ type: "uniform-submission" })
+ .set({ Authorization: process.env.HASURA_PLANX_API_KEY })
+ .send(body)
+ .expect(200)
+ .then((response) => {
+ expect(response.body.message).toMatch(/skipping Slack notification/);
+ });
+ });
+
+ it("posts to Slack on success", async () => {
+ process.env.APP_ENVIRONMENT = "production";
+
+ await post(ENDPOINT)
+ .query({ type: "uniform-submission" })
+ .set({ Authorization: process.env.HASURA_PLANX_API_KEY })
+ .send(body)
+ .expect(200)
+ .then((response) => {
+ expect(SlackNotify).toHaveBeenCalledWith(
+ process.env.SLACK_WEBHOOK_URL,
+ );
+ expect(mockSend).toHaveBeenCalledTimes(1);
+ expect(mockAdmin.session.find).toHaveBeenCalledTimes(1);
+
+ expect(response.body.message).toBe("Posted to Slack");
+ expect(response.body.data).toMatch(/abc123/);
+ expect(response.body.data).toMatch(/test-council/);
+ });
+ });
+
+ it("adds a status to the Slack message for a disability exemption", async () => {
+ process.env.APP_ENVIRONMENT = "production";
+ mockAdmin.session.find = jest
+ .fn()
+ .mockResolvedValue(mockSessionWithDisabilityExemption);
+
+ await post(ENDPOINT)
+ .query({ type: "uniform-submission" })
+ .set({ Authorization: process.env.HASURA_PLANX_API_KEY })
+ .send(body)
+ .expect(200)
+ .then((response) => {
+ expect(response.body.data).toMatch(/[Exempt]/);
+ });
+ });
+
+ it("adds a status to the Slack message for a resubmission exemption", async () => {
+ process.env.APP_ENVIRONMENT = "production";
+ mockAdmin.session.find = jest
+ .fn()
+ .mockResolvedValue(mockSessionWithResubmissionExemption);
+
+ await post(ENDPOINT)
+ .query({ type: "uniform-submission" })
+ .set({ Authorization: process.env.HASURA_PLANX_API_KEY })
+ .send(body)
+ .expect(200)
+ .then((response) => {
+ expect(response.body.data).toMatch(/[Resubmission]/);
+ });
+ });
+
+ it("handles missing sessions", async () => {
+ process.env.APP_ENVIRONMENT = "production";
+ mockAdmin.session.find = jest.fn().mockResolvedValueOnce(null);
+
+ await post(ENDPOINT)
+ .query({ type: "uniform-submission" })
+ .set({ Authorization: process.env.HASURA_PLANX_API_KEY })
+ .send(body)
+ .expect(500)
+ .then((response) => {
+ expect(mockAdmin.session.find).toHaveBeenCalledTimes(1);
+ expect(response.body.error).toMatch(/Failed to send/);
+ });
+ });
+
+ it("returns error when Slack fails", async () => {
+ process.env.APP_ENVIRONMENT = "production";
+ mockSend.mockRejectedValue("Fail!");
+
+ await post(ENDPOINT)
+ .query({ type: "uniform-submission" })
+ .set({ Authorization: process.env.HASURA_PLANX_API_KEY })
+ .send(body)
+ .expect(500)
+ .then((response) => {
+ expect(mockSend).toHaveBeenCalledTimes(1);
+ expect(response.body.error).toMatch(/Failed to send/);
+ });
+ });
+ });
+
+ describe("Email notifications", () => {
+ const body: EmailBody = {
+ event: {
+ data: {
+ new: {
+ session_id: "abc123",
+ team_slug: "testTeam",
+ request: {
+ personalisation: {
+ serviceName: "testServiceName",
+ },
+ },
+ },
+ },
+ },
+ };
+
+ it("skips the staging environment", async () => {
+ process.env.APP_ENVIRONMENT = "staging";
+ await post(ENDPOINT)
+ .query({ type: "email-submission" })
+ .set({ Authorization: process.env.HASURA_PLANX_API_KEY })
+ .send(body)
+ .expect(200)
+ .then((response) => {
+ expect(response.body.message).toMatch(/skipping Slack notification/);
+ });
+ });
+
+ it("posts to Slack on success", async () => {
+ process.env.APP_ENVIRONMENT = "production";
+
+ await post(ENDPOINT)
+ .query({ type: "email-submission" })
+ .set({ Authorization: process.env.HASURA_PLANX_API_KEY })
+ .send(body)
+ .expect(200)
+ .then((response) => {
+ expect(SlackNotify).toHaveBeenCalledWith(
+ process.env.SLACK_WEBHOOK_URL,
+ );
+ expect(mockSend).toHaveBeenCalledTimes(1);
+ expect(response.body.message).toBe("Posted to Slack");
+ expect(response.body.data).toMatch(/abc123/);
+ expect(response.body.data).toMatch(/testTeam/);
+ expect(response.body.data).toMatch(/testServiceName/);
+ });
+ });
+
+ it("returns error when Slack fails", async () => {
+ process.env.APP_ENVIRONMENT = "production";
+ mockSend.mockRejectedValue("Fail!");
+
+ await post(ENDPOINT)
+ .query({ type: "email-submission" })
+ .set({ Authorization: process.env.HASURA_PLANX_API_KEY })
+ .send(body)
+ .expect(500)
+ .then((response) => {
+ expect(mockSend).toHaveBeenCalledTimes(1);
+ expect(response.body.error).toMatch(/Failed to send/);
+ });
+ });
+ });
+});
diff --git a/api.planx.uk/modules/webhooks/sendNotification/schema.ts b/api.planx.uk/modules/webhooks/sendNotification/schema.ts
new file mode 100644
index 0000000000..e8cb1c4f74
--- /dev/null
+++ b/api.planx.uk/modules/webhooks/sendNotification/schema.ts
@@ -0,0 +1,68 @@
+import { z } from "zod";
+
+const payload = z.object({
+ sessionId: z.string(),
+});
+
+export const bopsSubmissionSchema = z.object({
+ body: z.object({
+ event: z.object({
+ data: z.object({
+ new: z.object({
+ payload,
+ bops_id: z.string(),
+ destination_url: z.string(),
+ }),
+ }),
+ }),
+ }),
+ query: z.object({
+ type: z.literal("bops-submission"),
+ }),
+});
+
+export const uniformSubmissionSchema = z.object({
+ body: z.object({
+ event: z.object({
+ data: z.object({
+ new: z.object({
+ payload,
+ submission_reference: z.string(),
+ response: z.object({
+ organisation: z.string(),
+ }),
+ }),
+ }),
+ }),
+ }),
+ query: z.object({
+ type: z.literal("uniform-submission"),
+ }),
+});
+
+export const emailSubmissionSchema = z.object({
+ body: z.object({
+ event: z.object({
+ data: z.object({
+ new: z.object({
+ session_id: z.string(),
+ team_slug: z.string(),
+ request: z.object({
+ personalisation: z.object({
+ serviceName: z.string(),
+ }),
+ }),
+ }),
+ }),
+ }),
+ }),
+ query: z.object({
+ type: z.literal("email-submission"),
+ }),
+});
+
+export const sendSlackNotificationSchema = z.union([
+ bopsSubmissionSchema,
+ uniformSubmissionSchema,
+ emailSubmissionSchema,
+]);
diff --git a/api.planx.uk/modules/webhooks/sendNotification/service.ts b/api.planx.uk/modules/webhooks/sendNotification/service.ts
new file mode 100644
index 0000000000..be8678a0e9
--- /dev/null
+++ b/api.planx.uk/modules/webhooks/sendNotification/service.ts
@@ -0,0 +1,64 @@
+import { Passport } from "@opensystemslab/planx-core";
+import SlackNotify from "slack-notify";
+import {
+ BOPSEventData,
+ EmailEventData,
+ EventData,
+ EventType,
+ UniformEventData,
+} from "./types";
+import { $admin } from "../../../client";
+
+export const sendSlackNotification = async (
+ data: EventData,
+ type: EventType,
+) => {
+ const slack = SlackNotify(process.env.SLACK_WEBHOOK_URL!);
+ let message = getMessageForEventType(data, type);
+
+ const sessionId = getSessionIdFromEvent(data, type);
+ const { disability, resubmission } =
+ await getExemptionStatusesForSession(sessionId);
+ if (disability) message += " [Exempt]";
+ if (resubmission) message += " [Resubmission]";
+
+ await slack.send(":incoming_envelope: " + message);
+ return message;
+};
+
+const getMessageForEventType = (data: EventData, type: EventType) => {
+ if (type === "bops-submission") {
+ const { bops_id, destination_url } = data as BOPSEventData;
+ return `New BOPS submission *${bops_id}* [${destination_url}]`;
+ }
+
+ if (type === "uniform-submission") {
+ const { submission_reference, response } = data as UniformEventData;
+ return `New Uniform submission *${submission_reference}* [${response.organisation}]`;
+ }
+
+ if (type === "email-submission") {
+ const { request, session_id, team_slug } = data as EmailEventData;
+ return `New email submission "${request.personalisation.serviceName}" *${session_id}* [${team_slug}]`;
+ }
+};
+
+const getSessionIdFromEvent = (data: EventData, type: EventType) =>
+ ({
+ "bops-submission": (data as BOPSEventData).payload?.sessionId,
+ "uniform-submission": (data as UniformEventData).payload?.sessionId,
+ "email-submission": (data as EmailEventData).session_id,
+ })[type];
+
+const getExemptionStatusesForSession = async (sessionId: string) => {
+ const session = await $admin.session.find(sessionId);
+ if (!session) throw Error(`Unable to find session with ID ${sessionId}`);
+
+ const passport = new Passport(session.data.passport);
+ const disability = passport.boolean(["application.fee.exemption.disability"]);
+ const resubmission = passport.boolean([
+ "application.fee.exemption.resubmission",
+ ]);
+
+ return { disability, resubmission };
+};
diff --git a/api.planx.uk/modules/webhooks/sendNotification/types.ts b/api.planx.uk/modules/webhooks/sendNotification/types.ts
new file mode 100644
index 0000000000..cd1e293bf9
--- /dev/null
+++ b/api.planx.uk/modules/webhooks/sendNotification/types.ts
@@ -0,0 +1,33 @@
+import { z } from "zod";
+import { ValidatedRequestHandler } from "../../../shared/middleware/validate";
+import {
+ bopsSubmissionSchema,
+ emailSubmissionSchema,
+ sendSlackNotificationSchema,
+ uniformSubmissionSchema,
+} from "./schema";
+
+interface SendSlackNotificationResponse {
+ message: string;
+ data?: string;
+}
+
+export type EventType = z.infer<
+ typeof sendSlackNotificationSchema
+>["query"]["type"];
+
+export type BOPSBody = z.infer["body"];
+export type BOPSEventData = BOPSBody["event"]["data"]["new"];
+
+export type UniformBody = z.infer["body"];
+export type UniformEventData = UniformBody["event"]["data"]["new"];
+
+export type EmailBody = z.infer["body"];
+export type EmailEventData = EmailBody["event"]["data"]["new"];
+
+export type EventData = BOPSEventData | UniformEventData | EmailEventData;
+
+export type SendSlackNotification = ValidatedRequestHandler<
+ typeof sendSlackNotificationSchema,
+ SendSlackNotificationResponse
+>;
diff --git a/api.planx.uk/server.ts b/api.planx.uk/server.ts
index 6db24eb716..bb8e293c08 100644
--- a/api.planx.uk/server.ts
+++ b/api.planx.uk/server.ts
@@ -40,10 +40,6 @@ import {
} from "./modules/auth/middleware";
import airbrake from "./airbrake";
-import {
- createReminderEvent,
- createExpiryEvent,
-} from "./webhooks/lowcalSessionEvents";
import { adminGraphQLClient as adminClient } from "./hasura";
import { sendEmailLimiter, apiLimiter } from "./rateLimit";
import {
@@ -56,31 +52,24 @@ import { sendToBOPS } from "./send/bops";
import { createSendEvents } from "./send/createSendEvents";
import { downloadApplicationFiles, sendToEmail } from "./send/email";
import { sendToUniform } from "./send/uniform";
-import { sendSlackNotification } from "./webhooks/sendNotifications";
import { copyFlow } from "./editor/copyFlow";
import { moveFlow } from "./editor/moveFlow";
import { useOrdnanceSurveyProxy } from "./proxy/ordnanceSurvey";
import { downloadFeedbackCSV } from "./admin/feedback/downloadFeedbackCSV";
-import { sanitiseApplicationData } from "./webhooks/sanitiseApplicationData";
import { getOneAppXML } from "./admin/session/oneAppXML";
import { gql } from "graphql-request";
-import {
- createPaymentExpiryEvents,
- createPaymentInvitationEvents,
- createPaymentReminderEvents,
-} from "./webhooks/paymentRequestEvents";
import { classifiedRoadsSearch } from "./gis/classifiedRoads";
import { getBOPSPayload } from "./admin/session/bops";
import { getCSVData, getRedactedCSVData } from "./admin/session/csv";
import { getHTMLExport, getRedactedHTMLExport } from "./admin/session/html";
import { generateZip } from "./admin/session/zip";
-import { createPaymentSendEvents } from "./inviteToPay/createPaymentSendEvents";
import { getSessionSummary } from "./admin/session/summary";
import { googleStrategy } from "./modules/auth/strategy/google";
import authRoutes from "./modules/auth/routes";
import teamRoutes from "./modules/team/routes";
import miscRoutes from "./modules/misc/routes";
import userRoutes from "./modules/user/routes";
+import webhookRoutes from "./modules/webhooks/routes";
import { useSwaggerDocs } from "./docs";
import { Role } from "@opensystemslab/planx-core/types";
@@ -197,6 +186,7 @@ app.use(authRoutes);
app.use(miscRoutes);
app.use("/user", userRoutes);
app.use("/team", teamRoutes);
+app.use("/webhooks", webhookRoutes);
app.use("/gis", router);
@@ -456,28 +446,6 @@ app.post("/validate-session", validateSession);
app.post("/invite-to-pay/:sessionId", inviteToPay);
-app.use("/webhooks/hasura", useHasuraAuth);
-app.post("/webhooks/hasura/create-reminder-event", createReminderEvent);
-app.post("/webhooks/hasura/create-expiry-event", createExpiryEvent);
-app.post(
- "/webhooks/hasura/create-payment-invitation-events",
- createPaymentInvitationEvents,
-);
-app.post(
- "/webhooks/hasura/create-payment-reminder-events",
- createPaymentReminderEvents,
-);
-app.post(
- "/webhooks/hasura/create-payment-expiry-events",
- createPaymentExpiryEvents,
-);
-app.post(
- "/webhooks/hasura/create-payment-send-events",
- createPaymentSendEvents,
-);
-app.post("/webhooks/hasura/send-slack-notification", sendSlackNotification);
-app.post("/webhooks/hasura/sanitise-application-data", sanitiseApplicationData);
-
app.use("/proxy/ordnance-survey", useOrdnanceSurveyProxy);
app.get("/error", async (res, req, next) => {
diff --git a/api.planx.uk/shared/middleware/validate.ts b/api.planx.uk/shared/middleware/validate.ts
index a5ed42650f..ce647f2f16 100644
--- a/api.planx.uk/shared/middleware/validate.ts
+++ b/api.planx.uk/shared/middleware/validate.ts
@@ -1,12 +1,12 @@
import { Request, RequestHandler, Response, NextFunction } from "express";
-import { AnyZodObject, ZodSchema, z } from "zod";
+import { AnyZodObject, ZodSchema, ZodTypeAny, ZodUnion, z } from "zod";
/**
* Middleware to validate incoming requests to the API
* Takes a ZodSchema and returns a validated, and typed, Request object
*/
export const validate =
- (schema: AnyZodObject) =>
+ (schema: AnyZodObject | ZodUnion) =>
async (
req: Request>,
res: Response,
diff --git a/api.planx.uk/webhooks/sendNotification.test.ts b/api.planx.uk/webhooks/sendNotification.test.ts
deleted file mode 100644
index 5d8eda61f6..0000000000
--- a/api.planx.uk/webhooks/sendNotification.test.ts
+++ /dev/null
@@ -1,186 +0,0 @@
-import supertest from "supertest";
-import app from "../server";
-import SlackNotify from "slack-notify";
-
-const ENDPOINT = "/webhooks/hasura/send-slack-notification";
-
-const mockSend = jest.fn();
-jest.mock("slack-notify", () =>
- jest.fn().mockImplementation(() => {
- return { send: mockSend };
- }),
-);
-
-const { post } = supertest(app);
-
-describe("Send Slack notifications endpoint", () => {
- const ORIGINAL_ENV = process.env;
-
- beforeEach(() => {
- jest.resetModules();
- process.env = { ...ORIGINAL_ENV };
- });
-
- afterAll(() => {
- process.env = ORIGINAL_ENV;
- });
-
- describe("authentication and validation", () => {
- it("fails without correct authentication", async () => {
- await post(ENDPOINT)
- .expect(401)
- .then((response) => {
- expect(response.body).toEqual({
- error: "Unauthorised",
- });
- });
- });
-
- it("returns a 404 if 'type' is missing", async () => {
- const body = { event: {} };
- await post(ENDPOINT)
- .set({ Authorization: process.env.HASURA_PLANX_API_KEY })
- .send(body)
- .expect(404)
- .then((response) => {
- expect(response.body).toEqual({
- message: "Missing info required to send a Slack notification",
- });
- });
- });
-
- it("returns a 404 if 'type' is incorrect", async () => {
- const body = { event: {} };
- await post(ENDPOINT + "?type=test-submission")
- .set({ Authorization: process.env.HASURA_PLANX_API_KEY })
- .send(body)
- .expect(404)
- .then((response) => {
- expect(response.body).toEqual({
- message: "Missing info required to send a Slack notification",
- });
- });
- });
-
- it("returns a 404 if 'event' is missing", async () => {
- await post(ENDPOINT + "?type=bops-submission")
- .set({ Authorization: process.env.HASURA_PLANX_API_KEY })
- .expect(404)
- .then((response) => {
- expect(response.body).toEqual({
- message: "Missing info required to send a Slack notification",
- });
- });
- });
- });
-
- const destinations = [
- {
- name: "BOPS",
- type: "bops-submission",
- stagingBody: {
- event: {
- data: {
- new: {
- destination_url: "https://www.bops-staging.com",
- },
- },
- },
- },
- prodBody: {
- event: {
- data: {
- new: {
- destination_url: "https://www.bops-production.com",
- },
- },
- },
- },
- },
- {
- name: "Uniform",
- type: "uniform-submission",
- stagingBody: {
- event: {
- data: {
- new: {
- response: {
- _links: {
- self: {
- href: "https://www.uniform-staging.com",
- },
- },
- },
- },
- },
- },
- },
- prodBody: {
- event: {
- data: {
- new: {
- response: {
- _links: {
- self: {
- href: "https://www.uniform-production.com",
- },
- },
- },
- },
- },
- },
- },
- },
- ];
-
- for (const destination of destinations) {
- describe(`${destination.name} notifications`, () => {
- afterEach(() => jest.clearAllMocks());
-
- it("skips the staging environment", async () => {
- process.env.APP_ENVIRONMENT = "staging";
- await post(ENDPOINT + `?type=${destination.type}`)
- .set({ Authorization: process.env.HASURA_PLANX_API_KEY })
- .send(destination.stagingBody)
- .expect(200)
- .then((response) => {
- expect(response.body.message).toMatch(
- /skipping Slack notification/,
- );
- });
- });
-
- it("posts to Slack on success", async () => {
- process.env.APP_ENVIRONMENT = "production";
- mockSend.mockResolvedValue("Success!");
-
- await post(ENDPOINT + `?type=${destination.type}`)
- .set({ Authorization: process.env.HASURA_PLANX_API_KEY })
- .send(destination.prodBody)
- .expect(200)
- .then((response) => {
- expect(SlackNotify).toHaveBeenCalledWith(
- process.env.SLACK_WEBHOOK_URL,
- );
- expect(mockSend).toHaveBeenCalledTimes(1);
- expect(response.body.message).toBe("Posted to Slack");
- });
- });
-
- it("returns error when Slack fails", async () => {
- process.env.APP_ENVIRONMENT = "production";
- mockSend.mockRejectedValue("Fail!");
-
- await post(ENDPOINT + `?type=${destination.type}`)
- .set({ Authorization: process.env.HASURA_PLANX_API_KEY })
- .send(destination.prodBody)
- .expect(500)
- .then((response) => {
- expect(mockSend).toHaveBeenCalledTimes(1);
- expect(response.body.error).toMatch(/Failed to send/);
- expect(response.body.error).toMatch(/Fail!/);
- });
- });
- });
- }
-});
diff --git a/api.planx.uk/webhooks/sendNotifications.ts b/api.planx.uk/webhooks/sendNotifications.ts
deleted file mode 100644
index c05061f3b1..0000000000
--- a/api.planx.uk/webhooks/sendNotifications.ts
+++ /dev/null
@@ -1,78 +0,0 @@
-import SlackNotify from "slack-notify";
-
-import { Request, Response, NextFunction } from "express";
-
-const sendSlackNotification = async (
- req: Request,
- res: Response,
- next: NextFunction,
-): Promise => {
- const isProduction = process.env.APP_ENVIRONMENT === "production";
- const supportedTypes = [
- "bops-submission",
- "uniform-submission",
- "email-submission",
- ];
- if (
- !req.body?.event ||
- !req.query?.type ||
- !supportedTypes.includes(req.query.type as string)
- ) {
- return res.status(404).send({
- message: "Missing info required to send a Slack notification",
- });
- }
-
- try {
- // hook into the #planx-notifications channel
- const slack = SlackNotify(process.env.SLACK_WEBHOOK_URL!);
-
- const data = req.body?.event?.data?.new;
-
- if (req.query.type === "bops-submission") {
- if (isProduction) {
- const bopsMessage = `:incoming_envelope: New BOPS submission *${data?.bops_id}* [${data?.destination_url}]`;
- await slack.send(bopsMessage);
- return res
- .status(200)
- .send({ message: "Posted to Slack", data: bopsMessage });
- }
- return res.status(200).send({
- message: `Staging application submitted, skipping Slack notification`,
- });
- }
-
- if (req.query.type === "uniform-submission") {
- if (isProduction) {
- const uniformMessage = `:incoming_envelope: New Uniform submission *${data?.submission_reference}* [${data?.response?.organisation}]`;
- await slack.send(uniformMessage);
- return res
- .status(200)
- .send({ message: "Posted to Slack", data: uniformMessage });
- }
- return res.status(200).send({
- message: `Staging application submitted, skipping Slack notification`,
- });
- }
-
- if (req.query.type === "email-submission") {
- if (isProduction) {
- const emailMessage = `:incoming_envelope: New email submission "${data?.request?.personalisation?.serviceName}" *${data?.session_id}* [${data?.team_slug}]`;
- await slack.send(emailMessage);
- return res
- .status(200)
- .send({ message: "Posted to Slack", data: emailMessage });
- }
- return res.status(200).send({
- message: `Staging application submitted, skipping Slack notification`,
- });
- }
- } catch (error) {
- return next({
- error,
- message: `Failed to send ${req.query.type} Slack notification. Error: ${error}`,
- });
- }
-};
-
-export { sendSlackNotification };
From 7f5e2e8f0d8ff3230650112be24ab84d83309989 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Dafydd=20Ll=C5=B7r=20Pearson?=
Date: Wed, 4 Oct 2023 09:00:18 +0100
Subject: [PATCH 2/8] chore: Bump tinycolor to v4 (#2275)
* chore: Bump tinycolor to v4
* chore: Linting
* test: Use full hex to match test cases
---
editor.planx.uk/package.json | 2 +-
editor.planx.uk/pnpm-lock.yaml | 10 +++---
.../src/components/Header.test.tsx | 6 ++--
editor.planx.uk/src/lib/dataMergedHotfix.ts | 6 ++--
.../lib/__tests__/externalPortals.test.ts | 2 +-
.../mocks/multipleExternalPortals.json | 33 +++++--------------
.../__tests__/mocks/singleExternalPortal.json | 24 ++++----------
.../src/pages/FlowEditor/lib/store/preview.ts | 3 +-
.../src/pages/FlowEditor/lib/store/team.ts | 4 +--
.../src/pages/FlowEditor/lib/store/user.ts | 4 +--
editor.planx.uk/src/routes/authenticated.tsx | 2 +-
editor.planx.uk/src/routes/team.tsx | 2 +-
editor.planx.uk/src/routes/views/team.tsx | 13 ++++----
13 files changed, 42 insertions(+), 69 deletions(-)
diff --git a/editor.planx.uk/package.json b/editor.planx.uk/package.json
index d5195c1e33..1fc3c696d6 100644
--- a/editor.planx.uk/package.json
+++ b/editor.planx.uk/package.json
@@ -5,7 +5,7 @@
"dependencies": {
"@airbrake/browser": "^2.1.8",
"@apollo/client": "^3.7.16",
- "@ctrl/tinycolor": "^3.6.0",
+ "@ctrl/tinycolor": "^4.0.2",
"@emotion/react": "^11.11.1",
"@emotion/styled": "^11.11.0",
"@feedback-fish/react": "^1.2.2",
diff --git a/editor.planx.uk/pnpm-lock.yaml b/editor.planx.uk/pnpm-lock.yaml
index ab8619d7d4..cb8d6007b1 100644
--- a/editor.planx.uk/pnpm-lock.yaml
+++ b/editor.planx.uk/pnpm-lock.yaml
@@ -19,8 +19,8 @@ dependencies:
specifier: ^3.7.16
version: 3.7.16(graphql@16.8.1)(react-dom@18.2.0)(react@18.2.0)
'@ctrl/tinycolor':
- specifier: ^3.6.0
- version: 3.6.0
+ specifier: ^4.0.2
+ version: 4.0.2
'@emotion/react':
specifier: ^11.11.1
version: 11.11.1(@types/react@18.2.20)(react@18.2.0)
@@ -3804,9 +3804,9 @@ packages:
dependencies:
postcss-selector-parser: 6.0.13
- /@ctrl/tinycolor@3.6.0:
- resolution: {integrity: sha512-/Z3l6pXthq0JvMYdUFyX9j0MaCltlIn6mfh9jLyQwg5aPKxkyNa0PTHtU1AlFXLNk55ZuAeJRcpvq+tmLfKmaQ==}
- engines: {node: '>=10'}
+ /@ctrl/tinycolor@4.0.2:
+ resolution: {integrity: sha512-fKQinXE9pJ83J1n+C3rDl2xNLJwfoYNvXLRy5cYZA9hBJJw2q+sbb/AOSNKmLxnTWyNTmy4994dueSwP4opi5g==}
+ engines: {node: '>=14'}
dev: false
/@discoveryjs/json-ext@0.5.7:
diff --git a/editor.planx.uk/src/components/Header.test.tsx b/editor.planx.uk/src/components/Header.test.tsx
index 483361cb05..ac64dcebc0 100644
--- a/editor.planx.uk/src/components/Header.test.tsx
+++ b/editor.planx.uk/src/components/Header.test.tsx
@@ -27,9 +27,9 @@ const mockTeam2: Team = {
slug: "closedsystemslab",
};
-jest.spyOn(ReactNavi, "useNavigation").mockReturnValue(({
- navigate: jest.fn()
-}) as any);
+jest.spyOn(ReactNavi, "useNavigation").mockReturnValue({
+ navigate: jest.fn(),
+} as any);
describe("Header Component - Editor Route", () => {
beforeAll(() => {
diff --git a/editor.planx.uk/src/lib/dataMergedHotfix.ts b/editor.planx.uk/src/lib/dataMergedHotfix.ts
index 2e1d40c309..48228f173c 100644
--- a/editor.planx.uk/src/lib/dataMergedHotfix.ts
+++ b/editor.planx.uk/src/lib/dataMergedHotfix.ts
@@ -25,11 +25,13 @@ const getFlowData = async (id: string) => {
// in order to load frontend /preview routes for flows that are not published
export const dataMerged = async (id: string, ob: Record = {}) => {
// get the primary flow data
- const { slug, data }: { slug: string; data: Record } = await getFlowData(id);
+ const { slug, data }: { slug: string; data: Record } =
+ 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 isExternalPortalRoot =
+ nodeId === "_root" && Object.keys(ob).length > 0;
const isExternalPortal = node.type === TYPES.ExternalPortal;
const isMerged = ob[node.data?.flowId];
diff --git a/editor.planx.uk/src/pages/FlowEditor/lib/__tests__/externalPortals.test.ts b/editor.planx.uk/src/pages/FlowEditor/lib/__tests__/externalPortals.test.ts
index 564469d48e..6646cafadd 100644
--- a/editor.planx.uk/src/pages/FlowEditor/lib/__tests__/externalPortals.test.ts
+++ b/editor.planx.uk/src/pages/FlowEditor/lib/__tests__/externalPortals.test.ts
@@ -60,4 +60,4 @@ describe("A flow with repeated external portals can be navigated as expected", (
record("withinExternalPortal", { answers: [] });
expect(upcomingCardIds()[0]).toEqual("finalNode");
});
-});
\ No newline at end of file
+});
diff --git a/editor.planx.uk/src/pages/FlowEditor/lib/__tests__/mocks/multipleExternalPortals.json b/editor.planx.uk/src/pages/FlowEditor/lib/__tests__/mocks/multipleExternalPortals.json
index b2693fa5e4..4ea6daea92 100644
--- a/editor.planx.uk/src/pages/FlowEditor/lib/__tests__/mocks/multipleExternalPortals.json
+++ b/editor.planx.uk/src/pages/FlowEditor/lib/__tests__/mocks/multipleExternalPortals.json
@@ -1,15 +1,10 @@
{
"_root": {
- "edges": [
- "firstNode",
- "finalNode"
- ]
+ "edges": ["firstNode", "finalNode"]
},
"externalPortal1": {
"type": 300,
- "edges": [
- "externalFlowId"
- ]
+ "edges": ["externalFlowId"]
},
"option3": {
"data": {
@@ -19,29 +14,21 @@
},
"externalPortal2": {
"type": 300,
- "edges": [
- "externalFlowId"
- ]
+ "edges": ["externalFlowId"]
},
"firstNode": {
"data": {
"text": "This is a question"
},
"type": 100,
- "edges": [
- "option1",
- "option2",
- "option3"
- ]
+ "edges": ["option1", "option2", "option3"]
},
"option2": {
"data": {
"text": "Option 2"
},
"type": 200,
- "edges": [
- "externalPortal2"
- ]
+ "edges": ["externalPortal2"]
},
"finalNode": {
"data": {
@@ -65,17 +52,13 @@
"text": "Option 1"
},
"type": 200,
- "edges": [
- "externalPortal1"
- ]
+ "edges": ["externalPortal1"]
},
"externalFlowId": {
"data": {
"text": "daf-external-portal-test"
},
"type": 300,
- "edges": [
- "withinExternalPortal"
- ]
+ "edges": ["withinExternalPortal"]
}
-}
\ No newline at end of file
+}
diff --git a/editor.planx.uk/src/pages/FlowEditor/lib/__tests__/mocks/singleExternalPortal.json b/editor.planx.uk/src/pages/FlowEditor/lib/__tests__/mocks/singleExternalPortal.json
index dea585a6dc..7b408035a9 100644
--- a/editor.planx.uk/src/pages/FlowEditor/lib/__tests__/mocks/singleExternalPortal.json
+++ b/editor.planx.uk/src/pages/FlowEditor/lib/__tests__/mocks/singleExternalPortal.json
@@ -1,9 +1,6 @@
{
"_root": {
- "edges": [
- "firstNode",
- "finalNode"
- ]
+ "edges": ["firstNode", "finalNode"]
},
"option2": {
"data": {
@@ -21,28 +18,21 @@
},
"externalPortal": {
"type": 300,
- "edges": [
- "externalFlowId"
- ]
+ "edges": ["externalFlowId"]
},
"firstNode": {
"data": {
"text": "This is a question with many options"
},
"type": 100,
- "edges": [
- "option1",
- "option2"
- ]
+ "edges": ["option1", "option2"]
},
"option1": {
"data": {
"text": "Option 1"
},
"type": 200,
- "edges": [
- "externalPortal"
- ]
+ "edges": ["externalPortal"]
},
"withinExternalPortal": {
"data": {
@@ -58,8 +48,6 @@
"text": "test-external-portal"
},
"type": 300,
- "edges": [
- "withinExternalPortal"
- ]
+ "edges": ["withinExternalPortal"]
}
-}
\ No newline at end of file
+}
diff --git a/editor.planx.uk/src/pages/FlowEditor/lib/store/preview.ts b/editor.planx.uk/src/pages/FlowEditor/lib/store/preview.ts
index e5be16d5fc..629982166e 100644
--- a/editor.planx.uk/src/pages/FlowEditor/lib/store/preview.ts
+++ b/editor.planx.uk/src/pages/FlowEditor/lib/store/preview.ts
@@ -1,4 +1,3 @@
-import tinycolor from "@ctrl/tinycolor";
import type {
Flag,
FlagSet,
@@ -742,7 +741,7 @@ export const getResultData = (
text: "No result",
category: category as FlagSet,
bgColor: "#EEEEEE",
- color: tinycolor("black").toHexString(),
+ color: "#000000",
description: "",
};
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 d120327e6d..8eeaed720d 100644
--- a/editor.planx.uk/src/pages/FlowEditor/lib/store/team.ts
+++ b/editor.planx.uk/src/pages/FlowEditor/lib/store/team.ts
@@ -7,7 +7,7 @@ import { Team } from "types";
import type { StateCreator } from "zustand";
export interface TeamStore {
- teamId: number,
+ teamId: number;
teamTheme?: TeamTheme;
teamName: string;
teamSettings?: TeamSettings;
@@ -88,7 +88,7 @@ export const teamStore: StateCreator = (
get().setTeam(team);
},
- clearTeamStore: () =>
+ clearTeamStore: () =>
set({
teamId: 0,
teamTheme: undefined,
diff --git a/editor.planx.uk/src/pages/FlowEditor/lib/store/user.ts b/editor.planx.uk/src/pages/FlowEditor/lib/store/user.ts
index a102b1f6f5..0f8cec17fd 100644
--- a/editor.planx.uk/src/pages/FlowEditor/lib/store/user.ts
+++ b/editor.planx.uk/src/pages/FlowEditor/lib/store/user.ts
@@ -35,8 +35,8 @@ export const userStore: StateCreator = (
},
async initUserStore(jwt: string) {
- const { getUser, setUser } = get();
-
+ const { getUser, setUser } = get();
+
if (getUser()) return;
const id = (jwtDecode(jwt) as any)["sub"];
diff --git a/editor.planx.uk/src/routes/authenticated.tsx b/editor.planx.uk/src/routes/authenticated.tsx
index eb6a45ac85..d8a7966536 100644
--- a/editor.planx.uk/src/routes/authenticated.tsx
+++ b/editor.planx.uk/src/routes/authenticated.tsx
@@ -27,7 +27,7 @@ const editorRoutes = compose(
});
useStore.getState().clearTeamStore();
-
+
return {
title: makeTitle("Teams"),
view: ,
diff --git a/editor.planx.uk/src/routes/team.tsx b/editor.planx.uk/src/routes/team.tsx
index 3c70c7a521..12e3f355b2 100644
--- a/editor.planx.uk/src/routes/team.tsx
+++ b/editor.planx.uk/src/routes/team.tsx
@@ -19,7 +19,7 @@ const routes = compose(
mount({
"/": route(() => ({
title: makeTitle(useStore.getState().teamName),
- view: ,
+ view: ,
})),
"/:flow": lazy(async (req) => {
diff --git a/editor.planx.uk/src/routes/views/team.tsx b/editor.planx.uk/src/routes/views/team.tsx
index bbf1898d6e..3a2a02d296 100644
--- a/editor.planx.uk/src/routes/views/team.tsx
+++ b/editor.planx.uk/src/routes/views/team.tsx
@@ -1,7 +1,7 @@
-import { NaviRequest, NotFoundError } from "navi"
+import { NaviRequest, NotFoundError } from "navi";
import { useStore } from "pages/FlowEditor/lib/store";
import React from "react";
-import { View } from "react-navi"
+import { View } from "react-navi";
import { getTeamFromDomain } from "routes/utils";
/**
@@ -10,7 +10,8 @@ import { getTeamFromDomain } from "routes/utils";
*/
export const teamView = async (req: NaviRequest) => {
const { initTeamStore, teamSlug: currentSlug } = useStore.getState();
- const routeSlug = req.params.team || await getTeamFromDomain(window.location.hostname)
+ const routeSlug =
+ req.params.team || (await getTeamFromDomain(window.location.hostname));
if (currentSlug !== routeSlug) {
try {
@@ -19,6 +20,6 @@ export const teamView = async (req: NaviRequest) => {
throw new NotFoundError(`Team not found: ${error}`);
}
}
-
- return
-}
\ No newline at end of file
+
+ return ;
+};
From 8984f399acf3459ca8a0be37b59f9030b962cdce Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Dafydd=20Ll=C5=B7r=20Pearson?=
Date: Wed, 4 Oct 2023 09:00:38 +0100
Subject: [PATCH 3/8] chore: Remove Hasura seeds (#2276)
---
hasura.planx.uk/seeds/1595528762802_teams_and_users.sql | 6 ------
1 file changed, 6 deletions(-)
delete mode 100644 hasura.planx.uk/seeds/1595528762802_teams_and_users.sql
diff --git a/hasura.planx.uk/seeds/1595528762802_teams_and_users.sql b/hasura.planx.uk/seeds/1595528762802_teams_and_users.sql
deleted file mode 100644
index 1728178b6d..0000000000
--- a/hasura.planx.uk/seeds/1595528762802_teams_and_users.sql
+++ /dev/null
@@ -1,6 +0,0 @@
-INSERT INTO public.users (id, first_name, last_name, email, is_platform_admin) VALUES (2, 'Alastair', 'Parvin', 'alastair@opensystemslab.io', true) ON CONFLICT (id) DO NOTHING;
-INSERT INTO public.users (id, first_name, last_name, email, is_platform_admin) VALUES (20, 'Jessica', 'McInchak', 'jessica@opensystemslab.io', true) ON CONFLICT (id) DO NOTHING;
-INSERT INTO public.users (id, first_name, last_name, email, is_platform_admin) VALUES (33, 'Dafydd', 'Pearson', 'dafydd@opensystemslab.io', true) ON CONFLICT (id) DO NOTHING;
-INSERT INTO public.users (id, first_name, last_name, email, is_platform_admin) VALUES (65, 'Ian', 'Jones', 'ian@opensystemslab.io', true) ON CONFLICT (id) DO NOTHING;
-SELECT setval('users_id_seq', max(id)) FROM users;
-SELECT setval('teams_id_seq', max(id)) FROM teams;
From fadbae7a16fe54c34a36dd00cf1747e703f28ca1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Dafydd=20Ll=C5=B7r=20Pearson?=
Date: Wed, 4 Oct 2023 09:01:02 +0100
Subject: [PATCH 4/8] refactor(api): Update remaining webhook routes to modular
structure (#2252)
---
api.planx.uk/hasura/metadata/index.ts | 7 +-
.../createPaymentSendEvents.test.ts | 17 +-
.../inviteToPay/createPaymentSendEvents.ts | 11 +-
.../webhooks/_old/lowcalSessionEvents.ts | 76 --------
.../webhooks/_old/paymentRequestEvents.ts | 136 --------------
api.planx.uk/modules/webhooks/controller.ts | 108 ++++++++++-
api.planx.uk/modules/webhooks/docs.yaml | 177 +++++++++++++++++-
api.planx.uk/modules/webhooks/routes.ts | 57 ++++--
.../lowcalSessionEvents/index.test.ts} | 55 ++++--
.../service/lowcalSessionEvents/index.ts | 44 +++++
.../service/lowcalSessionEvents/schema.ts | 21 +++
.../paymentRequestEvents/index.test.ts} | 83 +++++---
.../service/paymentRequestEvents/index.ts | 86 +++++++++
.../service/paymentRequestEvents/schema.ts | 21 +++
.../sanitiseApplicationData/index.test.ts | 6 +-
.../sanitiseApplicationData/index.ts | 32 +---
.../sanitiseApplicationData/mocks/queries.ts | 0
.../operations.test.ts | 0
.../sanitiseApplicationData/operations.ts | 0
.../sanitiseApplicationData/types.ts} | 8 +
.../sendNotification/index.test.ts | 8 +-
.../sendNotification/index.ts} | 2 +-
.../{ => service}/sendNotification/schema.ts | 0
.../{ => service}/sendNotification/types.ts | 2 +-
api.planx.uk/send/createSendEvents.ts | 11 +-
api.planx.uk/shared/middleware/validate.ts | 1 +
26 files changed, 637 insertions(+), 332 deletions(-)
delete mode 100644 api.planx.uk/modules/webhooks/_old/lowcalSessionEvents.ts
delete mode 100644 api.planx.uk/modules/webhooks/_old/paymentRequestEvents.ts
rename api.planx.uk/modules/webhooks/{_old/lowcalSessionEvents.test.ts => service/lowcalSessionEvents/index.test.ts} (76%)
create mode 100644 api.planx.uk/modules/webhooks/service/lowcalSessionEvents/index.ts
create mode 100644 api.planx.uk/modules/webhooks/service/lowcalSessionEvents/schema.ts
rename api.planx.uk/modules/webhooks/{_old/paymentRequestEvents.test.ts => service/paymentRequestEvents/index.test.ts} (80%)
create mode 100644 api.planx.uk/modules/webhooks/service/paymentRequestEvents/index.ts
create mode 100644 api.planx.uk/modules/webhooks/service/paymentRequestEvents/schema.ts
rename api.planx.uk/modules/webhooks/{_old => service}/sanitiseApplicationData/index.test.ts (96%)
rename api.planx.uk/modules/webhooks/{_old => service}/sanitiseApplicationData/index.ts (61%)
rename api.planx.uk/modules/webhooks/{_old => service}/sanitiseApplicationData/mocks/queries.ts (100%)
rename api.planx.uk/modules/webhooks/{_old => service}/sanitiseApplicationData/operations.test.ts (100%)
rename api.planx.uk/modules/webhooks/{_old => service}/sanitiseApplicationData/operations.ts (100%)
rename api.planx.uk/modules/webhooks/{_old/sanitiseApplicationData/types.d.ts => service/sanitiseApplicationData/types.ts} (53%)
rename api.planx.uk/modules/webhooks/{ => service}/sendNotification/index.test.ts (98%)
rename api.planx.uk/modules/webhooks/{sendNotification/service.ts => service/sendNotification/index.ts} (97%)
rename api.planx.uk/modules/webhooks/{ => service}/sendNotification/schema.ts (100%)
rename api.planx.uk/modules/webhooks/{ => service}/sendNotification/types.ts (92%)
diff --git a/api.planx.uk/hasura/metadata/index.ts b/api.planx.uk/hasura/metadata/index.ts
index c9f7e79364..14829616b7 100644
--- a/api.planx.uk/hasura/metadata/index.ts
+++ b/api.planx.uk/hasura/metadata/index.ts
@@ -25,13 +25,18 @@ type RequiredScheduledEventArgs = Pick<
"webhook" | "schedule_at" | "comment" | "payload"
>;
+export interface ScheduledEventResponse {
+ message: "success";
+ event_id: string;
+}
+
/**
* POST a request to the Hasura Metadata API
* https://hasura.io/docs/latest/graphql/core/api-reference/metadata-api/index/
*/
const postToMetadataAPI = async (
body: ScheduledEvent,
-): Promise> => {
+): Promise> => {
try {
return await Axios.post(
process.env.HASURA_METADATA_URL!,
diff --git a/api.planx.uk/inviteToPay/createPaymentSendEvents.test.ts b/api.planx.uk/inviteToPay/createPaymentSendEvents.test.ts
index d9eac45774..cb75bf84b1 100644
--- a/api.planx.uk/inviteToPay/createPaymentSendEvents.test.ts
+++ b/api.planx.uk/inviteToPay/createPaymentSendEvents.test.ts
@@ -9,6 +9,11 @@ const mockedCreateScheduledEvent = createScheduledEvent as jest.MockedFunction<
typeof createScheduledEvent
>;
+const mockScheduledEventResponse = {
+ message: "success",
+ event_id: "abc123",
+} as const;
+
describe("Create payment send events webhook", () => {
const ENDPOINT = "/webhooks/hasura/create-payment-send-events";
@@ -83,7 +88,7 @@ describe("Create payment send events webhook", () => {
});
it("returns a 200 on successful event setup", async () => {
- mockedCreateScheduledEvent.mockResolvedValue("test-event-id");
+ mockedCreateScheduledEvent.mockResolvedValue(mockScheduledEventResponse);
await supertest(app)
.post(ENDPOINT)
@@ -91,13 +96,15 @@ describe("Create payment send events webhook", () => {
.send({ payload: { sessionId: "123" } })
.expect(200)
.then((response) => {
- expect(response.body).toMatchObject({ email: "test-event-id" });
+ expect(response.body).toMatchObject({
+ email: mockScheduledEventResponse,
+ });
});
});
it("passes the correct arguments along to createScheduledEvent", async () => {
const body = { createdAt: new Date(), payload: { sessionId: "123" } };
- mockedCreateScheduledEvent.mockResolvedValue("test-event-id");
+ mockedCreateScheduledEvent.mockResolvedValue(mockScheduledEventResponse);
await supertest(app)
.post(ENDPOINT)
@@ -105,7 +112,9 @@ describe("Create payment send events webhook", () => {
.send(body)
.expect(200)
.then((response) => {
- expect(response.body).toMatchObject({ email: "test-event-id" });
+ expect(response.body).toMatchObject({
+ email: mockScheduledEventResponse,
+ });
});
const mockArgs = mockedCreateScheduledEvent.mock.calls[0][0];
diff --git a/api.planx.uk/inviteToPay/createPaymentSendEvents.ts b/api.planx.uk/inviteToPay/createPaymentSendEvents.ts
index 3b34947ec1..7e2b7e05ef 100644
--- a/api.planx.uk/inviteToPay/createPaymentSendEvents.ts
+++ b/api.planx.uk/inviteToPay/createPaymentSendEvents.ts
@@ -3,7 +3,10 @@ import { NextFunction, Request, Response } from "express";
import { gql } from "graphql-request";
import { $admin } from "../client";
import { adminGraphQLClient as adminClient } from "../hasura";
-import { createScheduledEvent } from "../hasura/metadata";
+import {
+ ScheduledEventResponse,
+ createScheduledEvent,
+} from "../hasura/metadata";
import { getMostRecentPublishedFlow } from "../helpers";
import { Flow, Node, Team } from "../types";
@@ -14,9 +17,9 @@ enum Destination {
}
interface CombinedResponse {
- bops?: Record;
- uniform?: Record;
- email?: Record;
+ bops?: ScheduledEventResponse;
+ uniform?: ScheduledEventResponse;
+ email?: ScheduledEventResponse;
}
// Create "One-off Scheduled Events" in Hasura when a payment request is paid
diff --git a/api.planx.uk/modules/webhooks/_old/lowcalSessionEvents.ts b/api.planx.uk/modules/webhooks/_old/lowcalSessionEvents.ts
deleted file mode 100644
index 1d50affdc3..0000000000
--- a/api.planx.uk/modules/webhooks/_old/lowcalSessionEvents.ts
+++ /dev/null
@@ -1,76 +0,0 @@
-import { addDays } from "date-fns";
-import { Request, Response, NextFunction } from "express";
-
-import { createScheduledEvent } from "../../../hasura/metadata";
-import {
- DAYS_UNTIL_EXPIRY,
- REMINDER_DAYS_FROM_EXPIRY,
-} from "../../../saveAndReturn/utils";
-
-/**
- * Create "reminder" events for a lowcal_session record
- */
-const createReminderEvent = async (
- req: Request,
- res: Response,
- next: NextFunction,
-): Promise => {
- try {
- const { createdAt, payload } = req.body;
- if (!createdAt || !payload)
- return next({
- status: 400,
- message: "Required value missing",
- });
- const response = await Promise.all(
- REMINDER_DAYS_FROM_EXPIRY.map((day: number) =>
- createScheduledEvent({
- webhook: "{{HASURA_PLANX_API_URL}}/send-email/reminder",
- schedule_at: addDays(Date.parse(createdAt), DAYS_UNTIL_EXPIRY - day),
- payload: payload,
- comment: `reminder_${payload.sessionId}_${day}day`,
- }),
- ),
- );
- res.json(response);
- } catch (error) {
- return next({
- error,
- message: `Failed to create reminder event. Error: ${error}`,
- });
- }
-};
-
-/**
- * Create an "expiry" event for a lowcal_session record
- */
-const createExpiryEvent = async (
- req: Request,
- res: Response,
- next: NextFunction,
-): Promise => {
- try {
- const { createdAt, payload } = req.body;
- if (!createdAt || !payload)
- return next({
- status: 400,
- message: "Required value missing",
- });
- const response = await createScheduledEvent({
- webhook: "{{HASURA_PLANX_API_URL}}/send-email/expiry",
- schedule_at: addDays(Date.parse(createdAt), DAYS_UNTIL_EXPIRY),
- payload: payload,
- comment: `expiry_${payload.sessionId}`,
- });
- res.json(response);
- } catch (error) {
- return next({
- error,
- message: `Failed to create expiry event. Error: ${
- (error as Error).message
- }`,
- });
- }
-};
-
-export { createReminderEvent, createExpiryEvent };
diff --git a/api.planx.uk/modules/webhooks/_old/paymentRequestEvents.ts b/api.planx.uk/modules/webhooks/_old/paymentRequestEvents.ts
deleted file mode 100644
index 30a387b228..0000000000
--- a/api.planx.uk/modules/webhooks/_old/paymentRequestEvents.ts
+++ /dev/null
@@ -1,136 +0,0 @@
-import { addDays } from "date-fns";
-import { Request, Response, NextFunction } from "express";
-
-import { createScheduledEvent } from "../../../hasura/metadata";
-import {
- DAYS_UNTIL_EXPIRY,
- REMINDER_DAYS_FROM_EXPIRY,
-} from "../../../saveAndReturn/utils";
-
-/**
- * Create two "invitation" events for a payments_request record: one for the nominee and one for the agent
- */
-const createPaymentInvitationEvents = async (
- req: Request,
- res: Response,
- next: NextFunction,
-): Promise => {
- try {
- const { createdAt, payload } = req.body;
- if (!createdAt || !payload)
- return next({
- status: 400,
- message: "Required value missing",
- });
- const response = await Promise.all([
- createScheduledEvent({
- webhook: "{{HASURA_PLANX_API_URL}}/send-email/invite-to-pay",
- schedule_at: createdAt,
- payload: payload,
- comment: `payment_invitation_${payload.paymentRequestId}`,
- }),
- createScheduledEvent({
- webhook: "{{HASURA_PLANX_API_URL}}/send-email/invite-to-pay-agent",
- schedule_at: createdAt,
- payload: payload,
- comment: `payment_invitation_agent_${payload.paymentRequestId}`,
- }),
- ]);
- res.json(response);
- } catch (error) {
- return next({
- error,
- message: `Failed to create payment invitation events. Error: ${error}`,
- });
- }
-};
-
-/**
- * Create "reminder" events for a payment_requests record: one for the nominee and one for the agent
- */
-const createPaymentReminderEvents = async (
- req: Request,
- res: Response,
- next: NextFunction,
-): Promise => {
- try {
- const { createdAt, payload } = req.body;
- if (!createdAt || !payload)
- return next({
- status: 400,
- message: "Required value missing",
- });
- const applicantResponse = await Promise.all(
- REMINDER_DAYS_FROM_EXPIRY.map((day: number) =>
- createScheduledEvent({
- webhook: "{{HASURA_PLANX_API_URL}}/send-email/payment-reminder",
- schedule_at: addDays(Date.parse(createdAt), DAYS_UNTIL_EXPIRY - day),
- payload: payload,
- comment: `payment_reminder_${payload.paymentRequestId}_${day}day`,
- }),
- ),
- );
- const agentResponse = await Promise.all(
- REMINDER_DAYS_FROM_EXPIRY.map((day: number) =>
- createScheduledEvent({
- webhook: "{{HASURA_PLANX_API_URL}}/send-email/payment-reminder-agent",
- schedule_at: addDays(Date.parse(createdAt), DAYS_UNTIL_EXPIRY - day),
- payload: payload,
- comment: `payment_reminder_agent_${payload.paymentRequestId}_${day}day`,
- }),
- ),
- );
- res.json([...applicantResponse, ...agentResponse]);
- } catch (error) {
- return next({
- error,
- message: `Failed to create payment reminder events. Error: ${error}`,
- });
- }
-};
-
-/**
- * Create two "expiry" events for a payment_requests record: one for the nominee and one for the agent
- */
-const createPaymentExpiryEvents = async (
- req: Request,
- res: Response,
- next: NextFunction,
-): Promise => {
- try {
- const { createdAt, payload } = req.body;
- if (!createdAt || !payload)
- return next({
- status: 400,
- message: "Required value missing",
- });
- const response = await Promise.all([
- createScheduledEvent({
- webhook: "{{HASURA_PLANX_API_URL}}/send-email/payment-expiry",
- schedule_at: addDays(Date.parse(createdAt), DAYS_UNTIL_EXPIRY),
- payload: payload,
- comment: `payment_expiry_${payload.paymentRequestId}`,
- }),
- createScheduledEvent({
- webhook: "{{HASURA_PLANX_API_URL}}/send-email/payment-expiry-agent",
- schedule_at: addDays(Date.parse(createdAt), DAYS_UNTIL_EXPIRY),
- payload: payload,
- comment: `payment_expiry_agent_${payload.paymentRequestId}`,
- }),
- ]);
- res.json(response);
- } catch (error) {
- return next({
- error,
- message: `Failed to create payment expiry events. Error: ${
- (error as Error).message
- }`,
- });
- }
-};
-
-export {
- createPaymentInvitationEvents,
- createPaymentReminderEvents,
- createPaymentExpiryEvents,
-};
diff --git a/api.planx.uk/modules/webhooks/controller.ts b/api.planx.uk/modules/webhooks/controller.ts
index 8b98358f89..1621159778 100644
--- a/api.planx.uk/modules/webhooks/controller.ts
+++ b/api.planx.uk/modules/webhooks/controller.ts
@@ -1,6 +1,19 @@
import { ServerError } from "../../errors";
-import { sendSlackNotification } from "./sendNotification/service";
-import { SendSlackNotification } from "./sendNotification/types";
+import { CreateSessionEventController } from "./service/lowcalSessionEvents/schema";
+import {
+ createSessionExpiryEvent,
+ createSessionReminderEvent,
+} from "./service/lowcalSessionEvents";
+import { SendSlackNotification } from "./service/sendNotification/types";
+import { sendSlackNotification } from "./service/sendNotification";
+import { CreatePaymentEventController } from "./service/paymentRequestEvents/schema";
+import {
+ createPaymentExpiryEvents,
+ createPaymentInvitationEvents,
+ createPaymentReminderEvents,
+} from "./service/paymentRequestEvents";
+import { SanitiseApplicationData } from "./service/sanitiseApplicationData/types";
+import { sanitiseApplicationData } from "./service/sanitiseApplicationData";
export const sendSlackNotificationController: SendSlackNotification = async (
req,
@@ -29,3 +42,94 @@ export const sendSlackNotificationController: SendSlackNotification = async (
);
}
};
+
+export const createPaymentInvitationEventsController: CreatePaymentEventController =
+ async (req, res, next) => {
+ try {
+ const response = await createPaymentInvitationEvents(req.body);
+ res.json(response);
+ } catch (error) {
+ return next(
+ new ServerError({
+ message: `Failed to create payment invitation events`,
+ cause: error,
+ }),
+ );
+ }
+ };
+
+export const createPaymentReminderEventsController: CreatePaymentEventController =
+ async (req, res, next) => {
+ try {
+ const response = await createPaymentReminderEvents(req.body);
+ res.json(response);
+ } catch (error) {
+ return next(
+ new ServerError({
+ message: "Failed to create payment reminder events",
+ cause: error,
+ }),
+ );
+ }
+ };
+
+export const createPaymentExpiryEventsController: CreatePaymentEventController =
+ async (req, res, next) => {
+ try {
+ const response = await createPaymentExpiryEvents(req.body);
+ res.json(response);
+ } catch (error) {
+ return next(
+ new ServerError({
+ message: "Failed to create payment expiry events",
+ cause: error,
+ }),
+ );
+ }
+ };
+
+export const createSessionReminderEventController: CreateSessionEventController =
+ async (req, res, next) => {
+ try {
+ const response = await createSessionReminderEvent(req.body);
+ res.json(response);
+ } catch (error) {
+ return next(
+ new ServerError({
+ message: "Failed to create session reminder event",
+ cause: error,
+ }),
+ );
+ }
+ };
+
+export const createSessionExpiryEventController: CreateSessionEventController =
+ async (req, res, next) => {
+ try {
+ const response = await createSessionExpiryEvent(req.body);
+ res.json(response);
+ } catch (error) {
+ return next(
+ new ServerError({
+ message: "Failed to create session expiry event",
+ cause: error,
+ }),
+ );
+ }
+ };
+
+export const sanitiseApplicationDataController: SanitiseApplicationData =
+ async (_req, res, next) => {
+ try {
+ const { operationFailed, results } = await sanitiseApplicationData();
+ if (operationFailed) res.status(500);
+ return res.json(results);
+ } catch (error) {
+ return next(
+ new ServerError({
+ message: "Failed to sanitise application data",
+ cause: error,
+ }),
+ );
+ }
+ };
diff --git a/api.planx.uk/modules/webhooks/docs.yaml b/api.planx.uk/modules/webhooks/docs.yaml
index 837e64a8ea..b200378244 100644
--- a/api.planx.uk/modules/webhooks/docs.yaml
+++ b/api.planx.uk/modules/webhooks/docs.yaml
@@ -14,7 +14,7 @@ components:
type: string
required:
- sessionId
- BopsSubmissionSchema:
+ BopsSubmission:
type: object
properties:
body:
@@ -37,7 +37,7 @@ components:
type: string
required:
- body
- UniformSubmissionSchema:
+ UniformSubmission:
type: object
properties:
body:
@@ -63,7 +63,7 @@ components:
type: string
required:
- body
- EmailSubmissionSchema:
+ EmailSubmission:
type: object
properties:
body:
@@ -92,11 +92,37 @@ components:
type: string
required:
- body
- SendSlackNotificationSchema:
+ SendSlackNotification:
oneOf:
- - $ref: "#/components/schemas/BopsSubmissionSchema"
- - $ref: "#/components/schemas/UniformSubmissionSchema"
- - $ref: "#/components/schemas/EmailSubmissionSchema"
+ - $ref: "#/components/schemas/BopsSubmission"
+ - $ref: "#/components/schemas/UniformSubmission"
+ - $ref: "#/components/schemas/EmailSubmission"
+ CreatePaymentEvent:
+ type: object
+ properties:
+ createdAt:
+ required: true
+ type: string
+ format: date-time
+ payload:
+ required: true
+ type: object
+ properties:
+ paymentRequestId:
+ type: string
+ CreateSessionEvent:
+ type: object
+ properties:
+ createdAt:
+ required: true
+ type: string
+ format: date-time
+ payload:
+ required: true
+ type: object
+ properties:
+ sessionId:
+ type: string
responses:
SlackNotificationSuccessMessage:
content:
@@ -111,6 +137,56 @@ components:
message:
type: string
required: true
+ ScheduledEvent:
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ type: object
+ properties:
+ message:
+ type: string
+ enum: ["success"]
+ event_id:
+ type: string
+ description: Internal Hasura ID of the generated event
+ OperationResultSuccess:
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ type: object
+ properties:
+ operationName:
+ type: string
+ status:
+ type: string
+ enum:
+ - success
+ count:
+ type: integer
+ OperationResultFailure:
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ type: object
+ properties:
+ operationName:
+ type: string
+ status:
+ type: string
+ enum:
+ - success
+ - failure
+ count:
+ type: integer
+ errorMessage:
+ type: string
+ required: false
paths:
/webhooks/hasura/sendSlackNotification:
post:
@@ -127,9 +203,94 @@ paths:
content:
application/json:
schema:
- $ref: "#/components/schemas/SendSlackNotificationSchema"
+ $ref: "#/components/schemas/SendSlackNotification"
responses:
"200":
$ref: "#/components/responses/SlackNotificationSuccessMessage"
"500":
$ref: "#/components/responses/ErrorMessage"
+ /webhooks/hasura/create-payment-invitation-events:
+ post:
+ tags: ["webhooks"]
+ summary: Create payment invitation events
+ description: Setup events which will trigger payment invitation emails to be generated
+ requestBody:
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/CreatePaymentEvent"
+ responses:
+ "200":
+ $ref: "#/components/responses/ScheduledEvent"
+ "500":
+ $ref: "#/components/responses/ErrorMessage"
+ /webhooks/hasura/create-payment-reminder-events:
+ post:
+ tags: ["webhooks"]
+ summary: Create payment reminder events
+ description: Setup events which will trigger payment reminder emails to be generated
+ requestBody:
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/CreatePaymentEvent"
+ responses:
+ "200":
+ $ref: "#/components/responses/ScheduledEvent"
+ "500":
+ $ref: "#/components/responses/ErrorMessage"
+ /webhooks/hasura/create-payment-expiry-events:
+ post:
+ tags: ["webhooks"]
+ summary: Create payment expiry events
+ description: Setup events which will trigger payment expiry emails to be generated
+ requestBody:
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/CreatePaymentEvent"
+ responses:
+ "200":
+ $ref: "#/components/responses/ScheduledEvent"
+ "500":
+ $ref: "#/components/responses/ErrorMessage"
+ /webhooks/hasura/create-reminder-event:
+ post:
+ tags: ["webhooks"]
+ summary: Create session reminder event
+ description: Setup events which will trigger session reminder emails to be generated
+ requestBody:
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/CreateSessionEvent"
+ responses:
+ "200":
+ $ref: "#/components/responses/ScheduledEvent"
+ "500":
+ $ref: "#/components/responses/ErrorMessage"
+ /webhooks/hasura/create-expiry-event:
+ post:
+ tags: ["webhooks"]
+ summary: Create session expiry event
+ description: Setup events which will trigger session expiry email to be generated
+ requestBody:
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/CreateSessionEvent"
+ responses:
+ "200":
+ $ref: "#/components/responses/ScheduledEvent"
+ "500":
+ $ref: "#/components/responses/ErrorMessage"
+ /webhooks/hasura/sanitise-application-data:
+ post:
+ tags: ["webhooks"]
+ summary: Sanitise application data
+ description: Called by Hasura cron job "sanitise_application_data" on a nightly basis
+ responses:
+ "200":
+ $ref: "#/components/responses/OperationResultSuccess"
+ "500":
+ $ref: "#/components/responses/OperationResultFailure"
diff --git a/api.planx.uk/modules/webhooks/routes.ts b/api.planx.uk/modules/webhooks/routes.ts
index c98421b7e3..dc7c1bbbcd 100644
--- a/api.planx.uk/modules/webhooks/routes.ts
+++ b/api.planx.uk/modules/webhooks/routes.ts
@@ -1,40 +1,59 @@
import { Router } from "express";
import { useHasuraAuth } from "../auth/middleware";
import { createPaymentSendEvents } from "../../inviteToPay/createPaymentSendEvents";
-import { sanitiseApplicationData } from "./_old/sanitiseApplicationData";
-import {
- createExpiryEvent,
- createReminderEvent,
-} from "./_old/lowcalSessionEvents";
-import {
- createPaymentExpiryEvents,
- createPaymentInvitationEvents,
- createPaymentReminderEvents,
-} from "./_old/paymentRequestEvents";
import { validate } from "../../shared/middleware/validate";
-import { sendSlackNotificationController } from "./controller";
-import { sendSlackNotificationSchema } from "./sendNotification/schema";
+import {
+ createPaymentExpiryEventsController,
+ createPaymentInvitationEventsController,
+ createPaymentReminderEventsController,
+ createSessionExpiryEventController,
+ createSessionReminderEventController,
+ sanitiseApplicationDataController,
+ sendSlackNotificationController,
+} from "./controller";
+import { sendSlackNotificationSchema } from "./service/sendNotification/schema";
+import { createPaymentEventSchema } from "./service/paymentRequestEvents/schema";
+import { createSessionEventSchema } from "./service/lowcalSessionEvents/schema";
const router = Router();
router.use("/hasura", useHasuraAuth);
-router.post("/hasura/create-reminder-event", createReminderEvent);
-router.post("/hasura/create-expiry-event", createExpiryEvent);
router.post(
"/hasura/create-payment-invitation-events",
- createPaymentInvitationEvents,
+ validate(createPaymentEventSchema),
+ createPaymentInvitationEventsController,
);
router.post(
"/hasura/create-payment-reminder-events",
- createPaymentReminderEvents,
+ validate(createPaymentEventSchema),
+ createPaymentReminderEventsController,
+);
+router.post(
+ "/hasura/create-payment-expiry-events",
+ validate(createPaymentEventSchema),
+ createPaymentExpiryEventsController,
);
-router.post("/hasura/create-payment-expiry-events", createPaymentExpiryEvents);
-router.post("/hasura/create-payment-send-events", createPaymentSendEvents);
router.post(
"/hasura/send-slack-notification",
validate(sendSlackNotificationSchema),
sendSlackNotificationController,
);
-router.post("/hasura/sanitise-application-data", sanitiseApplicationData);
+router.post(
+ "/hasura/create-reminder-event",
+ validate(createSessionEventSchema),
+ createSessionReminderEventController,
+);
+router.post(
+ "/hasura/create-expiry-event",
+ validate(createSessionEventSchema),
+ createSessionExpiryEventController,
+);
+router.post(
+ "/hasura/sanitise-application-data",
+ sanitiseApplicationDataController,
+);
+
+// TODO: Convert to the new API module structure
+router.post("/hasura/create-payment-send-events", createPaymentSendEvents);
export default router;
diff --git a/api.planx.uk/modules/webhooks/_old/lowcalSessionEvents.test.ts b/api.planx.uk/modules/webhooks/service/lowcalSessionEvents/index.test.ts
similarity index 76%
rename from api.planx.uk/modules/webhooks/_old/lowcalSessionEvents.test.ts
rename to api.planx.uk/modules/webhooks/service/lowcalSessionEvents/index.test.ts
index 0cddfe272b..8f557a4686 100644
--- a/api.planx.uk/modules/webhooks/_old/lowcalSessionEvents.test.ts
+++ b/api.planx.uk/modules/webhooks/service/lowcalSessionEvents/index.test.ts
@@ -1,14 +1,19 @@
import supertest from "supertest";
-import app from "../../../server";
-import { createScheduledEvent } from "../../../hasura/metadata";
+import app from "../../../../server";
+import { createScheduledEvent } from "../../../../hasura/metadata";
const { post } = supertest(app);
-jest.mock("../../../hasura/metadata");
+jest.mock("../../../../hasura/metadata");
const mockedCreateScheduledEvent = createScheduledEvent as jest.MockedFunction<
typeof createScheduledEvent
>;
+const mockScheduledEventResponse = {
+ message: "success",
+ event_id: "abc123",
+} as const;
+
describe("Create reminder event webhook", () => {
const ENDPOINT = "/webhooks/hasura/create-reminder-event";
@@ -33,15 +38,16 @@ describe("Create reminder event webhook", () => {
.set({ Authorization: process.env.HASURA_PLANX_API_KEY })
.send(body)
.expect(400)
- .then((response) =>
- expect(response.body.error).toEqual("Required value missing"),
- );
+ .then((response) => {
+ expect(response.body).toHaveProperty("issues");
+ expect(response.body).toHaveProperty("name", "ZodError");
+ });
}
});
it("returns a 200 on successful event setup", async () => {
const body = { createdAt: new Date(), payload: { sessionId: "123" } };
- mockedCreateScheduledEvent.mockResolvedValue("test");
+ mockedCreateScheduledEvent.mockResolvedValue(mockScheduledEventResponse);
await post(ENDPOINT)
.set({ Authorization: process.env.HASURA_PLANX_API_KEY })
@@ -50,20 +56,26 @@ describe("Create reminder event webhook", () => {
.then((response) => {
// it's queued up x2 reminders for 7 days and 1 day from expiry
expect(response.body).toHaveLength(2);
- expect(response.body).toStrictEqual(["test", "test"]);
+ expect(response.body).toStrictEqual([
+ mockScheduledEventResponse,
+ mockScheduledEventResponse,
+ ]);
});
});
it("passes the correct arguments along to createScheduledEvent", async () => {
const body = { createdAt: new Date(), payload: { sessionId: "123" } };
- mockedCreateScheduledEvent.mockResolvedValue("test");
+ mockedCreateScheduledEvent.mockResolvedValue(mockScheduledEventResponse);
await post(ENDPOINT)
.set({ Authorization: process.env.HASURA_PLANX_API_KEY })
.send(body)
.expect(200)
.then((response) => {
- expect(response.body).toStrictEqual(["test", "test"]);
+ expect(response.body).toStrictEqual([
+ mockScheduledEventResponse,
+ mockScheduledEventResponse,
+ ]);
});
const mockArgs = mockedCreateScheduledEvent.mock.calls[0][0];
@@ -92,7 +104,9 @@ describe("Create reminder event webhook", () => {
.send(body)
.expect(500)
.then((response) => {
- expect(response.body.error).toMatch(/Failed to create reminder event/);
+ expect(response.body.error).toMatch(
+ /Failed to create session reminder event/,
+ );
});
});
});
@@ -121,35 +135,36 @@ describe("Create expiry event webhook", () => {
.set({ Authorization: process.env.HASURA_PLANX_API_KEY })
.send(body)
.expect(400)
- .then((response) =>
- expect(response.body.error).toEqual("Required value missing"),
- );
+ .then((response) => {
+ expect(response.body).toHaveProperty("issues");
+ expect(response.body).toHaveProperty("name", "ZodError");
+ });
}
});
it("returns a 200 on successful event setup", async () => {
const body = { createdAt: new Date(), payload: { sessionId: "123" } };
- mockedCreateScheduledEvent.mockResolvedValue("test");
+ mockedCreateScheduledEvent.mockResolvedValue(mockScheduledEventResponse);
await post(ENDPOINT)
.set({ Authorization: process.env.HASURA_PLANX_API_KEY })
.send(body)
.expect(200)
.then((response) => {
- expect(response.body).toBe("test");
+ expect(response.body).toStrictEqual([mockScheduledEventResponse]);
});
});
it("passes the correct arguments along to createScheduledEvent", async () => {
const body = { createdAt: new Date(), payload: { sessionId: "123" } };
- mockedCreateScheduledEvent.mockResolvedValue("test");
+ mockedCreateScheduledEvent.mockResolvedValue(mockScheduledEventResponse);
await post(ENDPOINT)
.set({ Authorization: process.env.HASURA_PLANX_API_KEY })
.send(body)
.expect(200)
.then((response) => {
- expect(response.body).toBe("test");
+ expect(response.body).toStrictEqual([mockScheduledEventResponse]);
});
const mockArgs = mockedCreateScheduledEvent.mock.calls[0][0];
expect(mockArgs.webhook).toBe("{{HASURA_PLANX_API_URL}}/send-email/expiry");
@@ -166,7 +181,9 @@ describe("Create expiry event webhook", () => {
.send(body)
.expect(500)
.then((response) => {
- expect(response.body.error).toMatch(/Failed to create expiry event/);
+ expect(response.body.error).toMatch(
+ /Failed to create session expiry event/,
+ );
});
});
});
diff --git a/api.planx.uk/modules/webhooks/service/lowcalSessionEvents/index.ts b/api.planx.uk/modules/webhooks/service/lowcalSessionEvents/index.ts
new file mode 100644
index 0000000000..b8ec4cf072
--- /dev/null
+++ b/api.planx.uk/modules/webhooks/service/lowcalSessionEvents/index.ts
@@ -0,0 +1,44 @@
+import { addDays } from "date-fns";
+
+import { createScheduledEvent } from "../../../../hasura/metadata";
+import {
+ DAYS_UNTIL_EXPIRY,
+ REMINDER_DAYS_FROM_EXPIRY,
+} from "../../../../saveAndReturn/utils";
+import { CreateSessionEvent } from "./schema";
+
+/**
+ * Create "reminder" events for a lowcal_session record
+ */
+export const createSessionReminderEvent = async ({
+ createdAt,
+ payload,
+}: CreateSessionEvent) => {
+ const response = await Promise.all(
+ REMINDER_DAYS_FROM_EXPIRY.map((day: number) =>
+ createScheduledEvent({
+ webhook: "{{HASURA_PLANX_API_URL}}/send-email/reminder",
+ schedule_at: addDays(createdAt, DAYS_UNTIL_EXPIRY - day),
+ payload: payload,
+ comment: `reminder_${payload.sessionId}_${day}day`,
+ }),
+ ),
+ );
+ return response;
+};
+
+/**
+ * Create an "expiry" event for a lowcal_session record
+ */
+export const createSessionExpiryEvent = async ({
+ createdAt,
+ payload,
+}: CreateSessionEvent) => {
+ const response = await createScheduledEvent({
+ webhook: "{{HASURA_PLANX_API_URL}}/send-email/expiry",
+ schedule_at: addDays(createdAt, DAYS_UNTIL_EXPIRY),
+ payload: payload,
+ comment: `expiry_${payload.sessionId}`,
+ });
+ return [response];
+};
diff --git a/api.planx.uk/modules/webhooks/service/lowcalSessionEvents/schema.ts b/api.planx.uk/modules/webhooks/service/lowcalSessionEvents/schema.ts
new file mode 100644
index 0000000000..53e73f0b06
--- /dev/null
+++ b/api.planx.uk/modules/webhooks/service/lowcalSessionEvents/schema.ts
@@ -0,0 +1,21 @@
+import { z } from "zod";
+import { ValidatedRequestHandler } from "../../../../shared/middleware/validate";
+import { ScheduledEventResponse } from "../../../../hasura/metadata";
+
+export const createSessionEventSchema = z.object({
+ body: z.object({
+ createdAt: z.string().transform((val) => new Date(val)),
+ payload: z.object({
+ sessionId: z.string(),
+ }),
+ }),
+});
+
+export type CreateSessionEvent = z.infer<
+ typeof createSessionEventSchema
+>["body"];
+
+export type CreateSessionEventController = ValidatedRequestHandler<
+ typeof createSessionEventSchema,
+ ScheduledEventResponse[]
+>;
diff --git a/api.planx.uk/modules/webhooks/_old/paymentRequestEvents.test.ts b/api.planx.uk/modules/webhooks/service/paymentRequestEvents/index.test.ts
similarity index 80%
rename from api.planx.uk/modules/webhooks/_old/paymentRequestEvents.test.ts
rename to api.planx.uk/modules/webhooks/service/paymentRequestEvents/index.test.ts
index 282ca011a5..20beeefd98 100644
--- a/api.planx.uk/modules/webhooks/_old/paymentRequestEvents.test.ts
+++ b/api.planx.uk/modules/webhooks/service/paymentRequestEvents/index.test.ts
@@ -1,14 +1,20 @@
import supertest from "supertest";
-import app from "../../../server";
-import { createScheduledEvent } from "../../../hasura/metadata";
+import app from "../../../../server";
+import { createScheduledEvent } from "../../../../hasura/metadata";
+import { CreatePaymentEvent } from "./schema";
const { post } = supertest(app);
-jest.mock("../../../hasura/metadata");
+jest.mock("../../../../hasura/metadata");
const mockedCreateScheduledEvent = createScheduledEvent as jest.MockedFunction<
typeof createScheduledEvent
>;
+const mockScheduledEventResponse = {
+ message: "success",
+ event_id: "abc123",
+} as const;
+
describe("Create payment invitation events webhook", () => {
const ENDPOINT = "/webhooks/hasura/create-payment-invitation-events";
@@ -33,18 +39,19 @@ describe("Create payment invitation events webhook", () => {
.set({ Authorization: process.env.HASURA_PLANX_API_KEY })
.send(body)
.expect(400)
- .then((response) =>
- expect(response.body.error).toEqual("Required value missing"),
- );
+ .then((response) => {
+ expect(response.body).toHaveProperty("issues");
+ expect(response.body).toHaveProperty("name", "ZodError");
+ });
}
});
it("returns a 200 on successful event setup", async () => {
- const body = {
+ const body: CreatePaymentEvent = {
createdAt: new Date(),
payload: { paymentRequestId: "123" },
};
- mockedCreateScheduledEvent.mockResolvedValue("test");
+ mockedCreateScheduledEvent.mockResolvedValue(mockScheduledEventResponse);
await post(ENDPOINT)
.set({ Authorization: process.env.HASURA_PLANX_API_KEY })
@@ -53,7 +60,10 @@ describe("Create payment invitation events webhook", () => {
.then((response) => {
// it's queued up x2 invitations: one for the payee and one for the agent
expect(response.body).toHaveLength(2);
- expect(response.body).toStrictEqual(["test", "test"]);
+ expect(response.body).toStrictEqual([
+ mockScheduledEventResponse,
+ mockScheduledEventResponse,
+ ]);
});
});
@@ -62,7 +72,7 @@ describe("Create payment invitation events webhook", () => {
createdAt: new Date(),
payload: { paymentRequestId: "123" },
};
- mockedCreateScheduledEvent.mockResolvedValue("test");
+ mockedCreateScheduledEvent.mockResolvedValue(mockScheduledEventResponse);
await post(ENDPOINT)
.set({ Authorization: process.env.HASURA_PLANX_API_KEY })
@@ -70,7 +80,10 @@ describe("Create payment invitation events webhook", () => {
.expect(200)
.then((response) => {
expect(response.body).toHaveLength(2);
- expect(response.body).toStrictEqual(["test", "test"]);
+ expect(response.body).toStrictEqual([
+ mockScheduledEventResponse,
+ mockScheduledEventResponse,
+ ]);
});
const mockArgs = mockedCreateScheduledEvent.mock.calls[0][0];
@@ -135,9 +148,10 @@ describe("Create payment reminder events webhook", () => {
.set({ Authorization: process.env.HASURA_PLANX_API_KEY })
.send(body)
.expect(400)
- .then((response) =>
- expect(response.body.error).toEqual("Required value missing"),
- );
+ .then((response) => {
+ expect(response.body).toHaveProperty("issues");
+ expect(response.body).toHaveProperty("name", "ZodError");
+ });
}
});
@@ -146,7 +160,7 @@ describe("Create payment reminder events webhook", () => {
createdAt: new Date(),
payload: { paymentRequestId: "123" },
};
- mockedCreateScheduledEvent.mockResolvedValue("test");
+ mockedCreateScheduledEvent.mockResolvedValue(mockScheduledEventResponse);
await post(ENDPOINT)
.set({ Authorization: process.env.HASURA_PLANX_API_KEY })
@@ -155,7 +169,12 @@ describe("Create payment reminder events webhook", () => {
.then((response) => {
// it's queued up x4 reminders: 2 for the payee and 2 for the agent at 7 days and 1 day from expiry
expect(response.body).toHaveLength(4);
- expect(response.body).toStrictEqual(["test", "test", "test", "test"]);
+ expect(response.body).toStrictEqual([
+ mockScheduledEventResponse,
+ mockScheduledEventResponse,
+ mockScheduledEventResponse,
+ mockScheduledEventResponse,
+ ]);
});
});
@@ -164,7 +183,7 @@ describe("Create payment reminder events webhook", () => {
createdAt: new Date(),
payload: { paymentRequestId: "123" },
};
- mockedCreateScheduledEvent.mockResolvedValue("test");
+ mockedCreateScheduledEvent.mockResolvedValue(mockScheduledEventResponse);
await post(ENDPOINT)
.set({ Authorization: process.env.HASURA_PLANX_API_KEY })
@@ -172,7 +191,12 @@ describe("Create payment reminder events webhook", () => {
.expect(200)
.then((response) => {
expect(response.body).toHaveLength(4);
- expect(response.body).toStrictEqual(["test", "test", "test", "test"]);
+ expect(response.body).toStrictEqual([
+ mockScheduledEventResponse,
+ mockScheduledEventResponse,
+ mockScheduledEventResponse,
+ mockScheduledEventResponse,
+ ]);
});
const mockArgs = mockedCreateScheduledEvent.mock.calls[0][0];
@@ -255,9 +279,10 @@ describe("Create payment expiry events webhook", () => {
.set({ Authorization: process.env.HASURA_PLANX_API_KEY })
.send(body)
.expect(400)
- .then((response) =>
- expect(response.body.error).toEqual("Required value missing"),
- );
+ .then((response) => {
+ expect(response.body).toHaveProperty("issues");
+ expect(response.body).toHaveProperty("name", "ZodError");
+ });
}
});
@@ -266,16 +291,19 @@ describe("Create payment expiry events webhook", () => {
createdAt: new Date(),
payload: { paymentRequestId: "123" },
};
- mockedCreateScheduledEvent.mockResolvedValue("test");
+ mockedCreateScheduledEvent.mockResolvedValue(mockScheduledEventResponse);
await post(ENDPOINT)
.set({ Authorization: process.env.HASURA_PLANX_API_KEY })
.send(body)
.expect(200)
.then((response) => {
- // it's queued up x2 expirys: one for the payee and one for the agent
+ // it's queued up x2 expiries: one for the payee and one for the agent
expect(response.body).toHaveLength(2);
- expect(response.body).toStrictEqual(["test", "test"]);
+ expect(response.body).toStrictEqual([
+ mockScheduledEventResponse,
+ mockScheduledEventResponse,
+ ]);
});
});
@@ -284,7 +312,7 @@ describe("Create payment expiry events webhook", () => {
createdAt: new Date(),
payload: { paymentRequestId: "123" },
};
- mockedCreateScheduledEvent.mockResolvedValue("test");
+ mockedCreateScheduledEvent.mockResolvedValue(mockScheduledEventResponse);
await post(ENDPOINT)
.set({ Authorization: process.env.HASURA_PLANX_API_KEY })
@@ -292,7 +320,10 @@ describe("Create payment expiry events webhook", () => {
.expect(200)
.then((response) => {
expect(response.body).toHaveLength(2);
- expect(response.body).toStrictEqual(["test", "test"]);
+ expect(response.body).toStrictEqual([
+ mockScheduledEventResponse,
+ mockScheduledEventResponse,
+ ]);
});
const mockArgs = mockedCreateScheduledEvent.mock.calls[0][0];
diff --git a/api.planx.uk/modules/webhooks/service/paymentRequestEvents/index.ts b/api.planx.uk/modules/webhooks/service/paymentRequestEvents/index.ts
new file mode 100644
index 0000000000..9679c7931e
--- /dev/null
+++ b/api.planx.uk/modules/webhooks/service/paymentRequestEvents/index.ts
@@ -0,0 +1,86 @@
+import { addDays } from "date-fns";
+
+import { createScheduledEvent } from "../../../../hasura/metadata";
+import {
+ DAYS_UNTIL_EXPIRY,
+ REMINDER_DAYS_FROM_EXPIRY,
+} from "../../../../saveAndReturn/utils";
+import { CreatePaymentEvent } from "./schema";
+
+/**
+ * Create two "invitation" events for a payments_request record: one for the nominee and one for the agent
+ */
+export const createPaymentInvitationEvents = async ({
+ createdAt,
+ payload,
+}: CreatePaymentEvent) => {
+ const response = await Promise.all([
+ createScheduledEvent({
+ webhook: "{{HASURA_PLANX_API_URL}}/send-email/invite-to-pay",
+ schedule_at: createdAt,
+ payload: payload,
+ comment: `payment_invitation_${payload.paymentRequestId}`,
+ }),
+ createScheduledEvent({
+ webhook: "{{HASURA_PLANX_API_URL}}/send-email/invite-to-pay-agent",
+ schedule_at: createdAt,
+ payload: payload,
+ comment: `payment_invitation_agent_${payload.paymentRequestId}`,
+ }),
+ ]);
+ return response;
+};
+
+/**
+ * Create "reminder" events for a payment_requests record: one for the nominee and one for the agent
+ */
+export const createPaymentReminderEvents = async ({
+ createdAt,
+ payload,
+}: CreatePaymentEvent) => {
+ const applicantResponse = await Promise.all(
+ REMINDER_DAYS_FROM_EXPIRY.map((day: number) =>
+ createScheduledEvent({
+ webhook: "{{HASURA_PLANX_API_URL}}/send-email/payment-reminder",
+ schedule_at: addDays(createdAt, DAYS_UNTIL_EXPIRY - day),
+ payload: payload,
+ comment: `payment_reminder_${payload.paymentRequestId}_${day}day`,
+ }),
+ ),
+ );
+ const agentResponse = await Promise.all(
+ REMINDER_DAYS_FROM_EXPIRY.map((day: number) =>
+ createScheduledEvent({
+ webhook: "{{HASURA_PLANX_API_URL}}/send-email/payment-reminder-agent",
+ schedule_at: addDays(createdAt, DAYS_UNTIL_EXPIRY - day),
+ payload: payload,
+ comment: `payment_reminder_agent_${payload.paymentRequestId}_${day}day`,
+ }),
+ ),
+ );
+ return [...applicantResponse, ...agentResponse];
+};
+
+/**
+ * Create two "expiry" events for a payment_requests record: one for the nominee and one for the agent
+ */
+export const createPaymentExpiryEvents = async ({
+ createdAt,
+ payload,
+}: CreatePaymentEvent) => {
+ const response = await Promise.all([
+ createScheduledEvent({
+ webhook: "{{HASURA_PLANX_API_URL}}/send-email/payment-expiry",
+ schedule_at: addDays(createdAt, DAYS_UNTIL_EXPIRY),
+ payload: payload,
+ comment: `payment_expiry_${payload.paymentRequestId}`,
+ }),
+ createScheduledEvent({
+ webhook: "{{HASURA_PLANX_API_URL}}/send-email/payment-expiry-agent",
+ schedule_at: addDays(createdAt, DAYS_UNTIL_EXPIRY),
+ payload: payload,
+ comment: `payment_expiry_agent_${payload.paymentRequestId}`,
+ }),
+ ]);
+ return response;
+};
diff --git a/api.planx.uk/modules/webhooks/service/paymentRequestEvents/schema.ts b/api.planx.uk/modules/webhooks/service/paymentRequestEvents/schema.ts
new file mode 100644
index 0000000000..5b7a48404c
--- /dev/null
+++ b/api.planx.uk/modules/webhooks/service/paymentRequestEvents/schema.ts
@@ -0,0 +1,21 @@
+import { z } from "zod";
+import { ValidatedRequestHandler } from "../../../../shared/middleware/validate";
+import { ScheduledEventResponse } from "../../../../hasura/metadata";
+
+export const createPaymentEventSchema = z.object({
+ body: z.object({
+ createdAt: z.string().transform((val) => new Date(val)),
+ payload: z.object({
+ paymentRequestId: z.string(),
+ }),
+ }),
+});
+
+export type CreatePaymentEvent = z.infer<
+ typeof createPaymentEventSchema
+>["body"];
+
+export type CreatePaymentEventController = ValidatedRequestHandler<
+ typeof createPaymentEventSchema,
+ ScheduledEventResponse[]
+>;
diff --git a/api.planx.uk/modules/webhooks/_old/sanitiseApplicationData/index.test.ts b/api.planx.uk/modules/webhooks/service/sanitiseApplicationData/index.test.ts
similarity index 96%
rename from api.planx.uk/modules/webhooks/_old/sanitiseApplicationData/index.test.ts
rename to api.planx.uk/modules/webhooks/service/sanitiseApplicationData/index.test.ts
index 96da6ca4b3..a4930c9178 100644
--- a/api.planx.uk/modules/webhooks/_old/sanitiseApplicationData/index.test.ts
+++ b/api.planx.uk/modules/webhooks/service/sanitiseApplicationData/index.test.ts
@@ -30,13 +30,15 @@ describe("Sanitise application data webhook", () => {
it("returns a 500 if an unhandled error is thrown whilst running operations", async () => {
const mockOperationHandler = jest.spyOn(operations, "operationHandler");
- mockOperationHandler.mockRejectedValueOnce(new Error("Unhandled error!"));
+ mockOperationHandler.mockRejectedValueOnce("Unhandled error!");
await post(ENDPOINT)
.set({ Authorization: process.env.HASURA_PLANX_API_KEY })
.expect(500)
.then((response) =>
- expect(response.body.error).toMatch(/Unhandled error!/),
+ expect(response.body.error).toMatch(
+ /Failed to sanitise application data/,
+ ),
);
});
diff --git a/api.planx.uk/modules/webhooks/_old/sanitiseApplicationData/index.ts b/api.planx.uk/modules/webhooks/service/sanitiseApplicationData/index.ts
similarity index 61%
rename from api.planx.uk/modules/webhooks/_old/sanitiseApplicationData/index.ts
rename to api.planx.uk/modules/webhooks/service/sanitiseApplicationData/index.ts
index 9454104edf..af9cecf7c3 100644
--- a/api.planx.uk/modules/webhooks/_old/sanitiseApplicationData/index.ts
+++ b/api.planx.uk/modules/webhooks/service/sanitiseApplicationData/index.ts
@@ -9,35 +9,19 @@ import { getFormattedEnvironment } from "../../../../helpers";
* Called by Hasura cron job `sanitise_application_data` on a nightly basis
* See hasura.planx.uk/metadata/cron_triggers.yaml
*/
-export const sanitiseApplicationData = async (
- _req: Request,
- res: Response,
- next: NextFunction,
-): Promise => {
+export const sanitiseApplicationData = async () => {
const operations = getOperations();
const results: OperationResult[] = [];
- try {
- for (const operation of operations) {
- const result = await operationHandler(operation);
- results.push(result);
- }
- } catch (error) {
- // Unhandled error, flag with Airbrake
- return next({
- error,
- message: `Failed to sanitise application data. ${
- (error as Error).message
- }`,
- });
- }
- const operationFailed = results.find((result) => result.status === "failure");
- if (operationFailed) {
- await postToSlack(results);
- res.status(500);
+ for (const operation of operations) {
+ const result = await operationHandler(operation);
+ results.push(result);
}
- return res.json(results);
+ const operationFailed = results.some((result) => result.status === "failure");
+ if (operationFailed) await postToSlack(results);
+
+ return { operationFailed, results };
};
export const postToSlack = async (results: OperationResult[]) => {
diff --git a/api.planx.uk/modules/webhooks/_old/sanitiseApplicationData/mocks/queries.ts b/api.planx.uk/modules/webhooks/service/sanitiseApplicationData/mocks/queries.ts
similarity index 100%
rename from api.planx.uk/modules/webhooks/_old/sanitiseApplicationData/mocks/queries.ts
rename to api.planx.uk/modules/webhooks/service/sanitiseApplicationData/mocks/queries.ts
diff --git a/api.planx.uk/modules/webhooks/_old/sanitiseApplicationData/operations.test.ts b/api.planx.uk/modules/webhooks/service/sanitiseApplicationData/operations.test.ts
similarity index 100%
rename from api.planx.uk/modules/webhooks/_old/sanitiseApplicationData/operations.test.ts
rename to api.planx.uk/modules/webhooks/service/sanitiseApplicationData/operations.test.ts
diff --git a/api.planx.uk/modules/webhooks/_old/sanitiseApplicationData/operations.ts b/api.planx.uk/modules/webhooks/service/sanitiseApplicationData/operations.ts
similarity index 100%
rename from api.planx.uk/modules/webhooks/_old/sanitiseApplicationData/operations.ts
rename to api.planx.uk/modules/webhooks/service/sanitiseApplicationData/operations.ts
diff --git a/api.planx.uk/modules/webhooks/_old/sanitiseApplicationData/types.d.ts b/api.planx.uk/modules/webhooks/service/sanitiseApplicationData/types.ts
similarity index 53%
rename from api.planx.uk/modules/webhooks/_old/sanitiseApplicationData/types.d.ts
rename to api.planx.uk/modules/webhooks/service/sanitiseApplicationData/types.ts
index 55bd0d8bc5..88b9df2804 100644
--- a/api.planx.uk/modules/webhooks/_old/sanitiseApplicationData/types.d.ts
+++ b/api.planx.uk/modules/webhooks/service/sanitiseApplicationData/types.ts
@@ -1,3 +1,6 @@
+import { z } from "zod";
+import { ValidatedRequestHandler } from "../../../../shared/middleware/validate";
+
export interface OperationResult {
operationName: string;
status: "processing" | "success" | "failure";
@@ -8,3 +11,8 @@ export interface OperationResult {
export type QueryResult = string[];
export type Operation = () => Promise;
+
+export type SanitiseApplicationData = ValidatedRequestHandler<
+ z.ZodUndefined,
+ OperationResult[]
+>;
diff --git a/api.planx.uk/modules/webhooks/sendNotification/index.test.ts b/api.planx.uk/modules/webhooks/service/sendNotification/index.test.ts
similarity index 98%
rename from api.planx.uk/modules/webhooks/sendNotification/index.test.ts
rename to api.planx.uk/modules/webhooks/service/sendNotification/index.test.ts
index e73c115371..d3a4d25a54 100644
--- a/api.planx.uk/modules/webhooks/sendNotification/index.test.ts
+++ b/api.planx.uk/modules/webhooks/service/sendNotification/index.test.ts
@@ -1,8 +1,8 @@
import supertest from "supertest";
-import app from "../../../server";
+import app from "../../../../server";
import SlackNotify from "slack-notify";
import { BOPSBody, EmailBody, UniformBody } from "./types";
-import { $admin } from "../../../client";
+import { $admin } from "../../../../client";
import { CoreDomainClient } from "@opensystemslab/planx-core";
const mockSessionWithFee = {
@@ -35,7 +35,7 @@ const mockSessionWithResubmissionExemption = {
},
};
-jest.mock("../../../client");
+jest.mock("../../../../client");
const mockAdmin = jest.mocked($admin);
const mockSend = jest.fn();
@@ -208,10 +208,8 @@ describe("Send Slack notifications endpoint", () => {
);
expect(mockSend).toHaveBeenCalledTimes(1);
expect(mockAdmin.session.find).toHaveBeenCalledTimes(1);
-
expect(response.body.message).toBe("Posted to Slack");
expect(response.body.data).toMatch(/abc123/);
- expect(response.body.data).toMatch(/test-council/);
});
});
diff --git a/api.planx.uk/modules/webhooks/sendNotification/service.ts b/api.planx.uk/modules/webhooks/service/sendNotification/index.ts
similarity index 97%
rename from api.planx.uk/modules/webhooks/sendNotification/service.ts
rename to api.planx.uk/modules/webhooks/service/sendNotification/index.ts
index be8678a0e9..e0dfd36947 100644
--- a/api.planx.uk/modules/webhooks/sendNotification/service.ts
+++ b/api.planx.uk/modules/webhooks/service/sendNotification/index.ts
@@ -7,7 +7,7 @@ import {
EventType,
UniformEventData,
} from "./types";
-import { $admin } from "../../../client";
+import { $admin } from "../../../../client";
export const sendSlackNotification = async (
data: EventData,
diff --git a/api.planx.uk/modules/webhooks/sendNotification/schema.ts b/api.planx.uk/modules/webhooks/service/sendNotification/schema.ts
similarity index 100%
rename from api.planx.uk/modules/webhooks/sendNotification/schema.ts
rename to api.planx.uk/modules/webhooks/service/sendNotification/schema.ts
diff --git a/api.planx.uk/modules/webhooks/sendNotification/types.ts b/api.planx.uk/modules/webhooks/service/sendNotification/types.ts
similarity index 92%
rename from api.planx.uk/modules/webhooks/sendNotification/types.ts
rename to api.planx.uk/modules/webhooks/service/sendNotification/types.ts
index cd1e293bf9..1575f45429 100644
--- a/api.planx.uk/modules/webhooks/sendNotification/types.ts
+++ b/api.planx.uk/modules/webhooks/service/sendNotification/types.ts
@@ -1,5 +1,5 @@
import { z } from "zod";
-import { ValidatedRequestHandler } from "../../../shared/middleware/validate";
+import { ValidatedRequestHandler } from "../../../../shared/middleware/validate";
import {
bopsSubmissionSchema,
emailSubmissionSchema,
diff --git a/api.planx.uk/send/createSendEvents.ts b/api.planx.uk/send/createSendEvents.ts
index ed611c121f..8f5c12af4e 100644
--- a/api.planx.uk/send/createSendEvents.ts
+++ b/api.planx.uk/send/createSendEvents.ts
@@ -1,10 +1,13 @@
import { NextFunction, Request, Response } from "express";
-import { createScheduledEvent } from "../hasura/metadata";
+import {
+ ScheduledEventResponse,
+ createScheduledEvent,
+} from "../hasura/metadata";
interface CombinedResponse {
- bops?: Record;
- uniform?: Record;
- email?: Record;
+ bops?: ScheduledEventResponse;
+ uniform?: ScheduledEventResponse;
+ email?: ScheduledEventResponse;
}
// Create "One-off Scheduled Events" in Hasura from Send component for selected destinations
diff --git a/api.planx.uk/shared/middleware/validate.ts b/api.planx.uk/shared/middleware/validate.ts
index ce647f2f16..854a37938e 100644
--- a/api.planx.uk/shared/middleware/validate.ts
+++ b/api.planx.uk/shared/middleware/validate.ts
@@ -20,6 +20,7 @@ export const validate =
});
return next();
} catch (error) {
+ console.error(error);
return res.status(400).json(error);
}
};
From eca3963b22531c8bcb96c79437f756a2795a9a27 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Dafydd=20Ll=C5=B7r=20Pearson?=
Date: Wed, 4 Oct 2023 09:48:13 +0100
Subject: [PATCH 5/8] chore: Update Zod (CVE-2023-4316) (#2277)
* chore: Update Zod (CVE-2023-4316)
* chore: Bump planx-core
---
api.planx.uk/package.json | 4 +-
api.planx.uk/pnpm-lock.yaml | 78 ++++++++---------
e2e/tests/api-driven/package.json | 2 +-
e2e/tests/api-driven/pnpm-lock.yaml | 74 ++++++++---------
e2e/tests/ui-driven/package.json | 2 +-
e2e/tests/ui-driven/pnpm-lock.yaml | 74 ++++++++---------
editor.planx.uk/package.json | 4 +-
editor.planx.uk/pnpm-lock.yaml | 124 +++++++++++++++++++++-------
8 files changed, 212 insertions(+), 150 deletions(-)
diff --git a/api.planx.uk/package.json b/api.planx.uk/package.json
index eb86c57d6c..a8d81c4717 100644
--- a/api.planx.uk/package.json
+++ b/api.planx.uk/package.json
@@ -4,7 +4,7 @@
"private": true,
"dependencies": {
"@airbrake/node": "^2.1.8",
- "@opensystemslab/planx-core": "git+https://github.com/theopensystemslab/planx-core#3d395fa",
+ "@opensystemslab/planx-core": "git+https://github.com/theopensystemslab/planx-core#d92224b",
"@types/isomorphic-fetch": "^0.0.36",
"adm-zip": "^0.5.10",
"aws-sdk": "^2.1467.0",
@@ -42,7 +42,7 @@
"string-to-stream": "^3.0.1",
"swagger-jsdoc": "^6.2.8",
"swagger-ui-express": "^5.0.0",
- "zod": "^3.22.2"
+ "zod": "^3.22.3"
},
"scripts": {
"dev": "ts-node-dev --files index.ts",
diff --git a/api.planx.uk/pnpm-lock.yaml b/api.planx.uk/pnpm-lock.yaml
index e5dad92bca..7948276e87 100644
--- a/api.planx.uk/pnpm-lock.yaml
+++ b/api.planx.uk/pnpm-lock.yaml
@@ -9,8 +9,8 @@ dependencies:
specifier: ^2.1.8
version: 2.1.8
'@opensystemslab/planx-core':
- specifier: git+https://github.com/theopensystemslab/planx-core#3d395fa
- version: github.com/theopensystemslab/planx-core/3d395fa
+ specifier: git+https://github.com/theopensystemslab/planx-core#d92224b
+ version: github.com/theopensystemslab/planx-core/d92224b
'@types/isomorphic-fetch':
specifier: ^0.0.36
version: 0.0.36
@@ -123,8 +123,8 @@ dependencies:
specifier: ^5.0.0
version: 5.0.0(express@4.18.2)
zod:
- specifier: ^3.22.2
- version: 3.22.2
+ specifier: ^3.22.3
+ version: 3.22.3
devDependencies:
'@babel/core':
@@ -1624,8 +1624,8 @@ packages:
resolution: {integrity: sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==}
dev: false
- /@mui/base@5.0.0-beta.16(react-dom@18.2.0)(react@18.2.0):
- resolution: {integrity: sha512-OYxhC81c9bO0wobGcM8rrY5bRwpCXAI21BL0P2wz/2vTv4ek7ALz9+U5M8wgdmtRNUhmCmAB4L2WRwFRf5Cd8Q==}
+ /@mui/base@5.0.0-beta.17(react-dom@18.2.0)(react@18.2.0):
+ resolution: {integrity: sha512-xNbk7iOXrglNdIxFBN0k3ySsPIFLWCnFxqsAYl7CIcDkD9low4kJ7IUuy6ctwx/HAy2fenrT3KXHr1sGjAMgpQ==}
engines: {node: '>=12.0.0'}
peerDependencies:
'@types/react': ^17.0.0 || ^18.0.0
@@ -1642,7 +1642,7 @@ packages:
'@babel/runtime': 7.22.15
'@floating-ui/react-dom': 2.0.2(react-dom@18.2.0)(react@18.2.0)
'@mui/types': 7.2.4
- '@mui/utils': 5.14.10(react@18.2.0)
+ '@mui/utils': 5.14.11(react@18.2.0)
'@popperjs/core': 2.11.8
clsx: 2.0.0
prop-types: 15.8.1
@@ -1650,12 +1650,12 @@ packages:
react-dom: 18.2.0(react@18.2.0)
dev: false
- /@mui/core-downloads-tracker@5.14.10:
- resolution: {integrity: sha512-kPHu/NhZq1k+vSZR5wq3AyUfD4bnfWAeuKpps0+8PS7ZHQ2Lyv1cXJh+PlFdCIOa0PK98rk3JPwMzS8BMhdHwQ==}
+ /@mui/core-downloads-tracker@5.14.11:
+ resolution: {integrity: sha512-uY8FLQURhXe3f3O4dS5OSGML9KDm9+IE226cBu78jarVIzdQGPlXwGIlSI9VJR8MvZDA6C0+6XfWDhWCHruC5Q==}
dev: false
- /@mui/material@5.14.10(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(react-dom@18.2.0)(react@18.2.0):
- resolution: {integrity: sha512-ejFMppnO+lzBXpzju+N4SSz0Mhmi5sihXUGcr5FxpgB6bfUP0Lpe32O0Sw/3s8xlmLEvG1fqVT0rRyAVMlCA+A==}
+ /@mui/material@5.14.11(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(react-dom@18.2.0)(react@18.2.0):
+ resolution: {integrity: sha512-DnSdJzcR7lwG12JA5L2t8JF+RDzMygu5rCNW+logWb/KW2/TRzwLyVWO+CorHTBjBRd38DBxnwOCDiYkDd+N3A==}
engines: {node: '>=12.0.0'}
peerDependencies:
'@emotion/react': ^11.5.0
@@ -1678,11 +1678,11 @@ packages:
'@babel/runtime': 7.22.15
'@emotion/react': 11.11.1(react@18.2.0)
'@emotion/styled': 11.11.0(@emotion/react@11.11.1)(react@18.2.0)
- '@mui/base': 5.0.0-beta.16(react-dom@18.2.0)(react@18.2.0)
- '@mui/core-downloads-tracker': 5.14.10
- '@mui/system': 5.14.10(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(react@18.2.0)
+ '@mui/base': 5.0.0-beta.17(react-dom@18.2.0)(react@18.2.0)
+ '@mui/core-downloads-tracker': 5.14.11
+ '@mui/system': 5.14.11(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(react@18.2.0)
'@mui/types': 7.2.4
- '@mui/utils': 5.14.10(react@18.2.0)
+ '@mui/utils': 5.14.11(react@18.2.0)
'@types/react-transition-group': 4.4.6
clsx: 2.0.0
csstype: 3.1.2
@@ -1693,8 +1693,8 @@ packages:
react-transition-group: 4.4.5(react-dom@18.2.0)(react@18.2.0)
dev: false
- /@mui/private-theming@5.14.10(react@18.2.0):
- resolution: {integrity: sha512-f67xOj3H06wWDT9xBg7hVL/HSKNF+HG1Kx0Pm23skkbEqD2Ef2Lif64e5nPdmWVv+7cISCYtSuE2aeuzrZe78w==}
+ /@mui/private-theming@5.14.11(react@18.2.0):
+ resolution: {integrity: sha512-MSnNNzTu9pfKLCKs1ZAKwOTgE4bz+fQA0fNr8Jm7NDmuWmw0CaN9Vq2/MHsatE7+S0A25IAKby46Uv1u53rKVQ==}
engines: {node: '>=12.0.0'}
peerDependencies:
'@types/react': ^17.0.0 || ^18.0.0
@@ -1706,13 +1706,13 @@ packages:
optional: true
dependencies:
'@babel/runtime': 7.22.15
- '@mui/utils': 5.14.10(react@18.2.0)
+ '@mui/utils': 5.14.11(react@18.2.0)
prop-types: 15.8.1
react: 18.2.0
dev: false
- /@mui/styled-engine@5.14.10(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(react@18.2.0):
- resolution: {integrity: sha512-EJckxmQHrsBvDbFu1trJkvjNw/1R7jfNarnqPSnL+jEQawCkQIqVELWLrlOa611TFtxSJGkdUfCFXeJC203HVg==}
+ /@mui/styled-engine@5.14.11(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(react@18.2.0):
+ resolution: {integrity: sha512-jdUlqRgTYQ8RMtPX4MbRZqar6W2OiIb6J5KEFbIu4FqvPrk44Each4ppg/LAqp1qNlBYq5i+7Q10MYLMpDxX9A==}
engines: {node: '>=12.0.0'}
peerDependencies:
'@emotion/react': ^11.4.1
@@ -1735,8 +1735,8 @@ packages:
react: 18.2.0
dev: false
- /@mui/system@5.14.10(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(react@18.2.0):
- resolution: {integrity: sha512-QQmtTG/R4gjmLiL5ECQ7kRxLKDm8aKKD7seGZfbINtRVJDyFhKChA1a+K2bfqIAaBo1EMDv+6FWNT1Q5cRKjFA==}
+ /@mui/system@5.14.11(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(react@18.2.0):
+ resolution: {integrity: sha512-yl8xV+y0k7j6dzBsHabKwoShmjqLa8kTxrhUI3JpqLG358VRVMJRW/ES0HhvfcCi4IVXde+Tc2P3K1akGL8zoA==}
engines: {node: '>=12.0.0'}
peerDependencies:
'@emotion/react': ^11.5.0
@@ -1756,10 +1756,10 @@ packages:
'@babel/runtime': 7.22.15
'@emotion/react': 11.11.1(react@18.2.0)
'@emotion/styled': 11.11.0(@emotion/react@11.11.1)(react@18.2.0)
- '@mui/private-theming': 5.14.10(react@18.2.0)
- '@mui/styled-engine': 5.14.10(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(react@18.2.0)
+ '@mui/private-theming': 5.14.11(react@18.2.0)
+ '@mui/styled-engine': 5.14.11(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(react@18.2.0)
'@mui/types': 7.2.4
- '@mui/utils': 5.14.10(react@18.2.0)
+ '@mui/utils': 5.14.11(react@18.2.0)
clsx: 2.0.0
csstype: 3.1.2
prop-types: 15.8.1
@@ -1775,8 +1775,8 @@ packages:
optional: true
dev: false
- /@mui/utils@5.14.10(react@18.2.0):
- resolution: {integrity: sha512-Rn+vYQX7FxkcW0riDX/clNUwKuOJFH45HiULxwmpgnzQoQr3A0lb+QYwaZ+FAkZrR7qLoHKmLQlcItu6LT0y/Q==}
+ /@mui/utils@5.14.11(react@18.2.0):
+ resolution: {integrity: sha512-fmkIiCPKyDssYrJ5qk+dime1nlO3dmWfCtaPY/uVBqCRMBZ11JhddB9m8sjI2mgqQQwRJG5bq3biaosNdU/s4Q==}
engines: {node: '>=12.0.0'}
peerDependencies:
'@types/react': ^17.0.0 || ^18.0.0
@@ -3475,8 +3475,8 @@ packages:
dependencies:
esutils: 2.0.3
- /docx@8.2.2:
- resolution: {integrity: sha512-dWI5WfD/fDCLdjIA7CcDzV/1uyBD+mmr7jDLTUN997hcbPz56E701Kf1EEWdibdH9kk+0tFSmE+C0jTlkRZ7kQ==}
+ /docx@8.2.3:
+ resolution: {integrity: sha512-Rlr/wPTDh+xQpFew3m4zYQ5OWEZO36HItyPCUbGdicB+ar4zIgseeJdqfcZIY0uQtMSrhGRpSFOTjii7h9cpHw==}
engines: {node: '>=10'}
dependencies:
'@types/node': 20.4.1
@@ -7741,8 +7741,8 @@ packages:
engines: {node: '>=10'}
dev: true
- /type-fest@4.3.1:
- resolution: {integrity: sha512-pphNW/msgOUSkJbH58x8sqpq8uQj6b0ZKGxEsLKMUnGorRcDjrUaLS+39+/ub41JNTwrrMyJcUB8+YZs3mbwqw==}
+ /type-fest@4.3.3:
+ resolution: {integrity: sha512-bxhiFii6BBv6UiSDq7uKTMyADT9unXEl3ydGefndVLxFeB44LRbT4K7OJGDYSyDrKnklCC1Pre68qT2wbUl2Aw==}
engines: {node: '>=16'}
dev: false
@@ -8099,12 +8099,12 @@ packages:
commander: 9.5.0
dev: false
- /zod@3.22.2:
- resolution: {integrity: sha512-wvWkphh5WQsJbVk1tbx1l1Ly4yg+XecD+Mq280uBGt9wa5BKSWf4Mhp6GmrkPixhMxmabYY7RbzlwVP32pbGCg==}
+ /zod@3.22.3:
+ resolution: {integrity: sha512-EjIevzuJRiRPbVH4mGc8nApb/lVLKVpmUhAaR5R5doKGfAnGJ6Gr3CViAVjP+4FWSxCsybeWQdcgCtbX+7oZug==}
dev: false
- github.com/theopensystemslab/planx-core/3d395fa:
- resolution: {tarball: https://codeload.github.com/theopensystemslab/planx-core/tar.gz/3d395fa}
+ github.com/theopensystemslab/planx-core/d92224b:
+ resolution: {tarball: https://codeload.github.com/theopensystemslab/planx-core/tar.gz/d92224b}
name: '@opensystemslab/planx-core'
version: 1.0.0
prepare: true
@@ -8112,12 +8112,12 @@ packages:
dependencies:
'@emotion/react': 11.11.1(react@18.2.0)
'@emotion/styled': 11.11.0(@emotion/react@11.11.1)(react@18.2.0)
- '@mui/material': 5.14.10(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(react-dom@18.2.0)(react@18.2.0)
+ '@mui/material': 5.14.11(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(react-dom@18.2.0)(react@18.2.0)
'@types/geojson': 7946.0.11
ajv: 8.12.0
ajv-formats: 2.1.1(ajv@8.12.0)
copyfiles: 2.4.1
- docx: 8.2.2
+ docx: 8.2.3
eslint: 8.50.0
fast-xml-parser: 4.3.1
graphql: 16.8.1
@@ -8138,9 +8138,9 @@ packages:
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
striptags: 3.2.0
- type-fest: 4.3.1
+ type-fest: 4.3.3
uuid: 9.0.1
- zod: 3.22.2
+ zod: 3.22.3
transitivePeerDependencies:
- '@types/react'
- encoding
diff --git a/e2e/tests/api-driven/package.json b/e2e/tests/api-driven/package.json
index 0f7618398f..5d607b6297 100644
--- a/e2e/tests/api-driven/package.json
+++ b/e2e/tests/api-driven/package.json
@@ -6,7 +6,7 @@
},
"dependencies": {
"@cucumber/cucumber": "^9.3.0",
- "@opensystemslab/planx-core": "git+https://github.com/theopensystemslab/planx-core#44e9ebe",
+ "@opensystemslab/planx-core": "git+https://github.com/theopensystemslab/planx-core#d92224b",
"axios": "^1.4.0",
"dotenv": "^16.3.1",
"dotenv-expand": "^10.0.0",
diff --git a/e2e/tests/api-driven/pnpm-lock.yaml b/e2e/tests/api-driven/pnpm-lock.yaml
index 34035b4a87..88e4c5bfa6 100644
--- a/e2e/tests/api-driven/pnpm-lock.yaml
+++ b/e2e/tests/api-driven/pnpm-lock.yaml
@@ -9,8 +9,8 @@ dependencies:
specifier: ^9.3.0
version: 9.3.0
'@opensystemslab/planx-core':
- specifier: git+https://github.com/theopensystemslab/planx-core#44e9ebe
- version: github.com/theopensystemslab/planx-core/44e9ebe
+ specifier: git+https://github.com/theopensystemslab/planx-core#d92224b
+ version: github.com/theopensystemslab/planx-core/d92224b
axios:
specifier: ^1.4.0
version: 1.4.0
@@ -494,8 +494,8 @@ packages:
resolution: {integrity: sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==}
dev: false
- /@mui/base@5.0.0-beta.16(react-dom@18.2.0)(react@18.2.0):
- resolution: {integrity: sha512-OYxhC81c9bO0wobGcM8rrY5bRwpCXAI21BL0P2wz/2vTv4ek7ALz9+U5M8wgdmtRNUhmCmAB4L2WRwFRf5Cd8Q==}
+ /@mui/base@5.0.0-beta.17(react-dom@18.2.0)(react@18.2.0):
+ resolution: {integrity: sha512-xNbk7iOXrglNdIxFBN0k3ySsPIFLWCnFxqsAYl7CIcDkD9low4kJ7IUuy6ctwx/HAy2fenrT3KXHr1sGjAMgpQ==}
engines: {node: '>=12.0.0'}
peerDependencies:
'@types/react': ^17.0.0 || ^18.0.0
@@ -508,7 +508,7 @@ packages:
'@babel/runtime': 7.22.15
'@floating-ui/react-dom': 2.0.2(react-dom@18.2.0)(react@18.2.0)
'@mui/types': 7.2.4
- '@mui/utils': 5.14.10(react@18.2.0)
+ '@mui/utils': 5.14.11(react@18.2.0)
'@popperjs/core': 2.11.8
clsx: 2.0.0
prop-types: 15.8.1
@@ -516,12 +516,12 @@ packages:
react-dom: 18.2.0(react@18.2.0)
dev: false
- /@mui/core-downloads-tracker@5.14.10:
- resolution: {integrity: sha512-kPHu/NhZq1k+vSZR5wq3AyUfD4bnfWAeuKpps0+8PS7ZHQ2Lyv1cXJh+PlFdCIOa0PK98rk3JPwMzS8BMhdHwQ==}
+ /@mui/core-downloads-tracker@5.14.11:
+ resolution: {integrity: sha512-uY8FLQURhXe3f3O4dS5OSGML9KDm9+IE226cBu78jarVIzdQGPlXwGIlSI9VJR8MvZDA6C0+6XfWDhWCHruC5Q==}
dev: false
- /@mui/material@5.14.10(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(react-dom@18.2.0)(react@18.2.0):
- resolution: {integrity: sha512-ejFMppnO+lzBXpzju+N4SSz0Mhmi5sihXUGcr5FxpgB6bfUP0Lpe32O0Sw/3s8xlmLEvG1fqVT0rRyAVMlCA+A==}
+ /@mui/material@5.14.11(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(react-dom@18.2.0)(react@18.2.0):
+ resolution: {integrity: sha512-DnSdJzcR7lwG12JA5L2t8JF+RDzMygu5rCNW+logWb/KW2/TRzwLyVWO+CorHTBjBRd38DBxnwOCDiYkDd+N3A==}
engines: {node: '>=12.0.0'}
peerDependencies:
'@emotion/react': ^11.5.0
@@ -540,11 +540,11 @@ packages:
'@babel/runtime': 7.22.15
'@emotion/react': 11.11.1(react@18.2.0)
'@emotion/styled': 11.11.0(@emotion/react@11.11.1)(react@18.2.0)
- '@mui/base': 5.0.0-beta.16(react-dom@18.2.0)(react@18.2.0)
- '@mui/core-downloads-tracker': 5.14.10
- '@mui/system': 5.14.10(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(react@18.2.0)
+ '@mui/base': 5.0.0-beta.17(react-dom@18.2.0)(react@18.2.0)
+ '@mui/core-downloads-tracker': 5.14.11
+ '@mui/system': 5.14.11(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(react@18.2.0)
'@mui/types': 7.2.4
- '@mui/utils': 5.14.10(react@18.2.0)
+ '@mui/utils': 5.14.11(react@18.2.0)
'@types/react-transition-group': 4.4.6
clsx: 2.0.0
csstype: 3.1.2
@@ -555,8 +555,8 @@ packages:
react-transition-group: 4.4.5(react-dom@18.2.0)(react@18.2.0)
dev: false
- /@mui/private-theming@5.14.10(react@18.2.0):
- resolution: {integrity: sha512-f67xOj3H06wWDT9xBg7hVL/HSKNF+HG1Kx0Pm23skkbEqD2Ef2Lif64e5nPdmWVv+7cISCYtSuE2aeuzrZe78w==}
+ /@mui/private-theming@5.14.11(react@18.2.0):
+ resolution: {integrity: sha512-MSnNNzTu9pfKLCKs1ZAKwOTgE4bz+fQA0fNr8Jm7NDmuWmw0CaN9Vq2/MHsatE7+S0A25IAKby46Uv1u53rKVQ==}
engines: {node: '>=12.0.0'}
peerDependencies:
'@types/react': ^17.0.0 || ^18.0.0
@@ -566,13 +566,13 @@ packages:
optional: true
dependencies:
'@babel/runtime': 7.22.15
- '@mui/utils': 5.14.10(react@18.2.0)
+ '@mui/utils': 5.14.11(react@18.2.0)
prop-types: 15.8.1
react: 18.2.0
dev: false
- /@mui/styled-engine@5.14.10(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(react@18.2.0):
- resolution: {integrity: sha512-EJckxmQHrsBvDbFu1trJkvjNw/1R7jfNarnqPSnL+jEQawCkQIqVELWLrlOa611TFtxSJGkdUfCFXeJC203HVg==}
+ /@mui/styled-engine@5.14.11(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(react@18.2.0):
+ resolution: {integrity: sha512-jdUlqRgTYQ8RMtPX4MbRZqar6W2OiIb6J5KEFbIu4FqvPrk44Each4ppg/LAqp1qNlBYq5i+7Q10MYLMpDxX9A==}
engines: {node: '>=12.0.0'}
peerDependencies:
'@emotion/react': ^11.4.1
@@ -593,8 +593,8 @@ packages:
react: 18.2.0
dev: false
- /@mui/system@5.14.10(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(react@18.2.0):
- resolution: {integrity: sha512-QQmtTG/R4gjmLiL5ECQ7kRxLKDm8aKKD7seGZfbINtRVJDyFhKChA1a+K2bfqIAaBo1EMDv+6FWNT1Q5cRKjFA==}
+ /@mui/system@5.14.11(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(react@18.2.0):
+ resolution: {integrity: sha512-yl8xV+y0k7j6dzBsHabKwoShmjqLa8kTxrhUI3JpqLG358VRVMJRW/ES0HhvfcCi4IVXde+Tc2P3K1akGL8zoA==}
engines: {node: '>=12.0.0'}
peerDependencies:
'@emotion/react': ^11.5.0
@@ -612,10 +612,10 @@ packages:
'@babel/runtime': 7.22.15
'@emotion/react': 11.11.1(react@18.2.0)
'@emotion/styled': 11.11.0(@emotion/react@11.11.1)(react@18.2.0)
- '@mui/private-theming': 5.14.10(react@18.2.0)
- '@mui/styled-engine': 5.14.10(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(react@18.2.0)
+ '@mui/private-theming': 5.14.11(react@18.2.0)
+ '@mui/styled-engine': 5.14.11(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(react@18.2.0)
'@mui/types': 7.2.4
- '@mui/utils': 5.14.10(react@18.2.0)
+ '@mui/utils': 5.14.11(react@18.2.0)
clsx: 2.0.0
csstype: 3.1.2
prop-types: 15.8.1
@@ -631,8 +631,8 @@ packages:
optional: true
dev: false
- /@mui/utils@5.14.10(react@18.2.0):
- resolution: {integrity: sha512-Rn+vYQX7FxkcW0riDX/clNUwKuOJFH45HiULxwmpgnzQoQr3A0lb+QYwaZ+FAkZrR7qLoHKmLQlcItu6LT0y/Q==}
+ /@mui/utils@5.14.11(react@18.2.0):
+ resolution: {integrity: sha512-fmkIiCPKyDssYrJ5qk+dime1nlO3dmWfCtaPY/uVBqCRMBZ11JhddB9m8sjI2mgqQQwRJG5bq3biaosNdU/s4Q==}
engines: {node: '>=12.0.0'}
peerDependencies:
'@types/react': ^17.0.0 || ^18.0.0
@@ -1124,8 +1124,8 @@ packages:
esutils: 2.0.3
dev: false
- /docx@8.2.2:
- resolution: {integrity: sha512-dWI5WfD/fDCLdjIA7CcDzV/1uyBD+mmr7jDLTUN997hcbPz56E701Kf1EEWdibdH9kk+0tFSmE+C0jTlkRZ7kQ==}
+ /docx@8.2.3:
+ resolution: {integrity: sha512-Rlr/wPTDh+xQpFew3m4zYQ5OWEZO36HItyPCUbGdicB+ar4zIgseeJdqfcZIY0uQtMSrhGRpSFOTjii7h9cpHw==}
engines: {node: '>=10'}
dependencies:
'@types/node': 20.4.5
@@ -2531,8 +2531,8 @@ packages:
engines: {node: '>=10'}
dev: false
- /type-fest@4.3.1:
- resolution: {integrity: sha512-pphNW/msgOUSkJbH58x8sqpq8uQj6b0ZKGxEsLKMUnGorRcDjrUaLS+39+/ub41JNTwrrMyJcUB8+YZs3mbwqw==}
+ /type-fest@4.3.3:
+ resolution: {integrity: sha512-bxhiFii6BBv6UiSDq7uKTMyADT9unXEl3ydGefndVLxFeB44LRbT4K7OJGDYSyDrKnklCC1Pre68qT2wbUl2Aw==}
engines: {node: '>=16'}
dev: false
@@ -2711,12 +2711,12 @@ packages:
toposort: 2.0.2
dev: false
- /zod@3.22.2:
- resolution: {integrity: sha512-wvWkphh5WQsJbVk1tbx1l1Ly4yg+XecD+Mq280uBGt9wa5BKSWf4Mhp6GmrkPixhMxmabYY7RbzlwVP32pbGCg==}
+ /zod@3.22.3:
+ resolution: {integrity: sha512-EjIevzuJRiRPbVH4mGc8nApb/lVLKVpmUhAaR5R5doKGfAnGJ6Gr3CViAVjP+4FWSxCsybeWQdcgCtbX+7oZug==}
dev: false
- github.com/theopensystemslab/planx-core/44e9ebe:
- resolution: {tarball: https://codeload.github.com/theopensystemslab/planx-core/tar.gz/44e9ebe}
+ github.com/theopensystemslab/planx-core/d92224b:
+ resolution: {tarball: https://codeload.github.com/theopensystemslab/planx-core/tar.gz/d92224b}
name: '@opensystemslab/planx-core'
version: 1.0.0
prepare: true
@@ -2724,12 +2724,12 @@ packages:
dependencies:
'@emotion/react': 11.11.1(react@18.2.0)
'@emotion/styled': 11.11.0(@emotion/react@11.11.1)(react@18.2.0)
- '@mui/material': 5.14.10(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(react-dom@18.2.0)(react@18.2.0)
+ '@mui/material': 5.14.11(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(react-dom@18.2.0)(react@18.2.0)
'@types/geojson': 7946.0.11
ajv: 8.12.0
ajv-formats: 2.1.1(ajv@8.12.0)
copyfiles: 2.4.1
- docx: 8.2.2
+ docx: 8.2.3
eslint: 8.50.0
fast-xml-parser: 4.3.1
graphql: 16.8.1
@@ -2750,9 +2750,9 @@ packages:
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
striptags: 3.2.0
- type-fest: 4.3.1
+ type-fest: 4.3.3
uuid: 9.0.1
- zod: 3.22.2
+ zod: 3.22.3
transitivePeerDependencies:
- '@types/react'
- encoding
diff --git a/e2e/tests/ui-driven/package.json b/e2e/tests/ui-driven/package.json
index c834ab6182..65fcf76221 100644
--- a/e2e/tests/ui-driven/package.json
+++ b/e2e/tests/ui-driven/package.json
@@ -8,7 +8,7 @@
"postinstall": "./install-dependencies.sh"
},
"dependencies": {
- "@opensystemslab/planx-core": "git+https://github.com/theopensystemslab/planx-core#44e9ebe",
+ "@opensystemslab/planx-core": "git+https://github.com/theopensystemslab/planx-core#d92224b",
"axios": "^1.4.0",
"dotenv": "^16.3.1",
"eslint": "^8.44.0",
diff --git a/e2e/tests/ui-driven/pnpm-lock.yaml b/e2e/tests/ui-driven/pnpm-lock.yaml
index 6dd5769323..cf65f35302 100644
--- a/e2e/tests/ui-driven/pnpm-lock.yaml
+++ b/e2e/tests/ui-driven/pnpm-lock.yaml
@@ -6,8 +6,8 @@ settings:
dependencies:
'@opensystemslab/planx-core':
- specifier: git+https://github.com/theopensystemslab/planx-core#44e9ebe
- version: github.com/theopensystemslab/planx-core/44e9ebe
+ specifier: git+https://github.com/theopensystemslab/planx-core#d92224b
+ version: github.com/theopensystemslab/planx-core/d92224b
axios:
specifier: ^1.4.0
version: 1.4.0
@@ -343,8 +343,8 @@ packages:
resolution: {integrity: sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==}
dev: false
- /@mui/base@5.0.0-beta.16(react-dom@18.2.0)(react@18.2.0):
- resolution: {integrity: sha512-OYxhC81c9bO0wobGcM8rrY5bRwpCXAI21BL0P2wz/2vTv4ek7ALz9+U5M8wgdmtRNUhmCmAB4L2WRwFRf5Cd8Q==}
+ /@mui/base@5.0.0-beta.17(react-dom@18.2.0)(react@18.2.0):
+ resolution: {integrity: sha512-xNbk7iOXrglNdIxFBN0k3ySsPIFLWCnFxqsAYl7CIcDkD9low4kJ7IUuy6ctwx/HAy2fenrT3KXHr1sGjAMgpQ==}
engines: {node: '>=12.0.0'}
peerDependencies:
'@types/react': ^17.0.0 || ^18.0.0
@@ -357,7 +357,7 @@ packages:
'@babel/runtime': 7.22.15
'@floating-ui/react-dom': 2.0.2(react-dom@18.2.0)(react@18.2.0)
'@mui/types': 7.2.4
- '@mui/utils': 5.14.10(react@18.2.0)
+ '@mui/utils': 5.14.11(react@18.2.0)
'@popperjs/core': 2.11.8
clsx: 2.0.0
prop-types: 15.8.1
@@ -365,12 +365,12 @@ packages:
react-dom: 18.2.0(react@18.2.0)
dev: false
- /@mui/core-downloads-tracker@5.14.10:
- resolution: {integrity: sha512-kPHu/NhZq1k+vSZR5wq3AyUfD4bnfWAeuKpps0+8PS7ZHQ2Lyv1cXJh+PlFdCIOa0PK98rk3JPwMzS8BMhdHwQ==}
+ /@mui/core-downloads-tracker@5.14.11:
+ resolution: {integrity: sha512-uY8FLQURhXe3f3O4dS5OSGML9KDm9+IE226cBu78jarVIzdQGPlXwGIlSI9VJR8MvZDA6C0+6XfWDhWCHruC5Q==}
dev: false
- /@mui/material@5.14.10(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(react-dom@18.2.0)(react@18.2.0):
- resolution: {integrity: sha512-ejFMppnO+lzBXpzju+N4SSz0Mhmi5sihXUGcr5FxpgB6bfUP0Lpe32O0Sw/3s8xlmLEvG1fqVT0rRyAVMlCA+A==}
+ /@mui/material@5.14.11(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(react-dom@18.2.0)(react@18.2.0):
+ resolution: {integrity: sha512-DnSdJzcR7lwG12JA5L2t8JF+RDzMygu5rCNW+logWb/KW2/TRzwLyVWO+CorHTBjBRd38DBxnwOCDiYkDd+N3A==}
engines: {node: '>=12.0.0'}
peerDependencies:
'@emotion/react': ^11.5.0
@@ -389,11 +389,11 @@ packages:
'@babel/runtime': 7.22.15
'@emotion/react': 11.11.1(react@18.2.0)
'@emotion/styled': 11.11.0(@emotion/react@11.11.1)(react@18.2.0)
- '@mui/base': 5.0.0-beta.16(react-dom@18.2.0)(react@18.2.0)
- '@mui/core-downloads-tracker': 5.14.10
- '@mui/system': 5.14.10(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(react@18.2.0)
+ '@mui/base': 5.0.0-beta.17(react-dom@18.2.0)(react@18.2.0)
+ '@mui/core-downloads-tracker': 5.14.11
+ '@mui/system': 5.14.11(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(react@18.2.0)
'@mui/types': 7.2.4
- '@mui/utils': 5.14.10(react@18.2.0)
+ '@mui/utils': 5.14.11(react@18.2.0)
'@types/react-transition-group': 4.4.6
clsx: 2.0.0
csstype: 3.1.2
@@ -404,8 +404,8 @@ packages:
react-transition-group: 4.4.5(react-dom@18.2.0)(react@18.2.0)
dev: false
- /@mui/private-theming@5.14.10(react@18.2.0):
- resolution: {integrity: sha512-f67xOj3H06wWDT9xBg7hVL/HSKNF+HG1Kx0Pm23skkbEqD2Ef2Lif64e5nPdmWVv+7cISCYtSuE2aeuzrZe78w==}
+ /@mui/private-theming@5.14.11(react@18.2.0):
+ resolution: {integrity: sha512-MSnNNzTu9pfKLCKs1ZAKwOTgE4bz+fQA0fNr8Jm7NDmuWmw0CaN9Vq2/MHsatE7+S0A25IAKby46Uv1u53rKVQ==}
engines: {node: '>=12.0.0'}
peerDependencies:
'@types/react': ^17.0.0 || ^18.0.0
@@ -415,13 +415,13 @@ packages:
optional: true
dependencies:
'@babel/runtime': 7.22.15
- '@mui/utils': 5.14.10(react@18.2.0)
+ '@mui/utils': 5.14.11(react@18.2.0)
prop-types: 15.8.1
react: 18.2.0
dev: false
- /@mui/styled-engine@5.14.10(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(react@18.2.0):
- resolution: {integrity: sha512-EJckxmQHrsBvDbFu1trJkvjNw/1R7jfNarnqPSnL+jEQawCkQIqVELWLrlOa611TFtxSJGkdUfCFXeJC203HVg==}
+ /@mui/styled-engine@5.14.11(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(react@18.2.0):
+ resolution: {integrity: sha512-jdUlqRgTYQ8RMtPX4MbRZqar6W2OiIb6J5KEFbIu4FqvPrk44Each4ppg/LAqp1qNlBYq5i+7Q10MYLMpDxX9A==}
engines: {node: '>=12.0.0'}
peerDependencies:
'@emotion/react': ^11.4.1
@@ -442,8 +442,8 @@ packages:
react: 18.2.0
dev: false
- /@mui/system@5.14.10(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(react@18.2.0):
- resolution: {integrity: sha512-QQmtTG/R4gjmLiL5ECQ7kRxLKDm8aKKD7seGZfbINtRVJDyFhKChA1a+K2bfqIAaBo1EMDv+6FWNT1Q5cRKjFA==}
+ /@mui/system@5.14.11(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(react@18.2.0):
+ resolution: {integrity: sha512-yl8xV+y0k7j6dzBsHabKwoShmjqLa8kTxrhUI3JpqLG358VRVMJRW/ES0HhvfcCi4IVXde+Tc2P3K1akGL8zoA==}
engines: {node: '>=12.0.0'}
peerDependencies:
'@emotion/react': ^11.5.0
@@ -461,10 +461,10 @@ packages:
'@babel/runtime': 7.22.15
'@emotion/react': 11.11.1(react@18.2.0)
'@emotion/styled': 11.11.0(@emotion/react@11.11.1)(react@18.2.0)
- '@mui/private-theming': 5.14.10(react@18.2.0)
- '@mui/styled-engine': 5.14.10(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(react@18.2.0)
+ '@mui/private-theming': 5.14.11(react@18.2.0)
+ '@mui/styled-engine': 5.14.11(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(react@18.2.0)
'@mui/types': 7.2.4
- '@mui/utils': 5.14.10(react@18.2.0)
+ '@mui/utils': 5.14.11(react@18.2.0)
clsx: 2.0.0
csstype: 3.1.2
prop-types: 15.8.1
@@ -480,8 +480,8 @@ packages:
optional: true
dev: false
- /@mui/utils@5.14.10(react@18.2.0):
- resolution: {integrity: sha512-Rn+vYQX7FxkcW0riDX/clNUwKuOJFH45HiULxwmpgnzQoQr3A0lb+QYwaZ+FAkZrR7qLoHKmLQlcItu6LT0y/Q==}
+ /@mui/utils@5.14.11(react@18.2.0):
+ resolution: {integrity: sha512-fmkIiCPKyDssYrJ5qk+dime1nlO3dmWfCtaPY/uVBqCRMBZ11JhddB9m8sjI2mgqQQwRJG5bq3biaosNdU/s4Q==}
engines: {node: '>=12.0.0'}
peerDependencies:
'@types/react': ^17.0.0 || ^18.0.0
@@ -989,8 +989,8 @@ packages:
dependencies:
esutils: 2.0.3
- /docx@8.2.2:
- resolution: {integrity: sha512-dWI5WfD/fDCLdjIA7CcDzV/1uyBD+mmr7jDLTUN997hcbPz56E701Kf1EEWdibdH9kk+0tFSmE+C0jTlkRZ7kQ==}
+ /docx@8.2.3:
+ resolution: {integrity: sha512-Rlr/wPTDh+xQpFew3m4zYQ5OWEZO36HItyPCUbGdicB+ar4zIgseeJdqfcZIY0uQtMSrhGRpSFOTjii7h9cpHw==}
engines: {node: '>=10'}
dependencies:
'@types/node': 20.5.1
@@ -2336,8 +2336,8 @@ packages:
engines: {node: '>=12.20'}
dev: false
- /type-fest@4.3.1:
- resolution: {integrity: sha512-pphNW/msgOUSkJbH58x8sqpq8uQj6b0ZKGxEsLKMUnGorRcDjrUaLS+39+/ub41JNTwrrMyJcUB8+YZs3mbwqw==}
+ /type-fest@4.3.3:
+ resolution: {integrity: sha512-bxhiFii6BBv6UiSDq7uKTMyADT9unXEl3ydGefndVLxFeB44LRbT4K7OJGDYSyDrKnklCC1Pre68qT2wbUl2Aw==}
engines: {node: '>=16'}
dev: false
@@ -2487,12 +2487,12 @@ packages:
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
engines: {node: '>=10'}
- /zod@3.22.2:
- resolution: {integrity: sha512-wvWkphh5WQsJbVk1tbx1l1Ly4yg+XecD+Mq280uBGt9wa5BKSWf4Mhp6GmrkPixhMxmabYY7RbzlwVP32pbGCg==}
+ /zod@3.22.3:
+ resolution: {integrity: sha512-EjIevzuJRiRPbVH4mGc8nApb/lVLKVpmUhAaR5R5doKGfAnGJ6Gr3CViAVjP+4FWSxCsybeWQdcgCtbX+7oZug==}
dev: false
- github.com/theopensystemslab/planx-core/44e9ebe:
- resolution: {tarball: https://codeload.github.com/theopensystemslab/planx-core/tar.gz/44e9ebe}
+ github.com/theopensystemslab/planx-core/d92224b:
+ resolution: {tarball: https://codeload.github.com/theopensystemslab/planx-core/tar.gz/d92224b}
name: '@opensystemslab/planx-core'
version: 1.0.0
prepare: true
@@ -2500,12 +2500,12 @@ packages:
dependencies:
'@emotion/react': 11.11.1(react@18.2.0)
'@emotion/styled': 11.11.0(@emotion/react@11.11.1)(react@18.2.0)
- '@mui/material': 5.14.10(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(react-dom@18.2.0)(react@18.2.0)
+ '@mui/material': 5.14.11(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(react-dom@18.2.0)(react@18.2.0)
'@types/geojson': 7946.0.11
ajv: 8.12.0
ajv-formats: 2.1.1(ajv@8.12.0)
copyfiles: 2.4.1
- docx: 8.2.2
+ docx: 8.2.3
eslint: 8.50.0
fast-xml-parser: 4.3.1
graphql: 16.8.1
@@ -2526,9 +2526,9 @@ packages:
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
striptags: 3.2.0
- type-fest: 4.3.1
+ type-fest: 4.3.3
uuid: 9.0.1
- zod: 3.22.2
+ zod: 3.22.3
transitivePeerDependencies:
- '@types/react'
- encoding
diff --git a/editor.planx.uk/package.json b/editor.planx.uk/package.json
index 1fc3c696d6..b59747f3e4 100644
--- a/editor.planx.uk/package.json
+++ b/editor.planx.uk/package.json
@@ -14,7 +14,7 @@
"@mui/styles": "^5.14.5",
"@mui/utils": "^5.14.5",
"@opensystemslab/map": "^0.7.5",
- "@opensystemslab/planx-core": "git+https://github.com/theopensystemslab/planx-core#44420b9",
+ "@opensystemslab/planx-core": "git+https://github.com/theopensystemslab/planx-core#d92224b",
"@tiptap/core": "^2.0.3",
"@tiptap/extension-bold": "^2.0.3",
"@tiptap/extension-bubble-menu": "^2.1.6",
@@ -89,7 +89,7 @@
"uuid": "^9.0.0",
"wkt": "^0.1.1",
"yup": "^0.32.11",
- "zod": "^3.22.2",
+ "zod": "^3.22.3",
"zustand": "^4.3.8"
},
"devDependencies": {
diff --git a/editor.planx.uk/pnpm-lock.yaml b/editor.planx.uk/pnpm-lock.yaml
index cb8d6007b1..a89798ab6f 100644
--- a/editor.planx.uk/pnpm-lock.yaml
+++ b/editor.planx.uk/pnpm-lock.yaml
@@ -46,8 +46,8 @@ dependencies:
specifier: ^0.7.5
version: 0.7.5
'@opensystemslab/planx-core':
- specifier: git+https://github.com/theopensystemslab/planx-core#44420b9
- version: github.com/theopensystemslab/planx-core/44420b9(@types/react@18.2.20)
+ specifier: git+https://github.com/theopensystemslab/planx-core#d92224b
+ version: github.com/theopensystemslab/planx-core/d92224b(@types/react@18.2.20)
'@tiptap/core':
specifier: ^2.0.3
version: 2.0.3(@tiptap/pm@2.0.3)
@@ -271,8 +271,8 @@ dependencies:
specifier: ^0.32.11
version: 0.32.11
zod:
- specifier: ^3.22.2
- version: 3.22.2
+ specifier: ^3.22.3
+ version: 3.22.3
zustand:
specifier: ^4.3.8
version: 4.3.9(immer@9.0.21)(react@18.2.0)
@@ -5225,8 +5225,8 @@ packages:
react-is: 18.2.0
dev: false
- /@mui/base@5.0.0-beta.16(@types/react@18.2.20)(react-dom@18.2.0)(react@18.2.0):
- resolution: {integrity: sha512-OYxhC81c9bO0wobGcM8rrY5bRwpCXAI21BL0P2wz/2vTv4ek7ALz9+U5M8wgdmtRNUhmCmAB4L2WRwFRf5Cd8Q==}
+ /@mui/base@5.0.0-beta.17(@types/react@18.2.20)(react-dom@18.2.0)(react@18.2.0):
+ resolution: {integrity: sha512-xNbk7iOXrglNdIxFBN0k3ySsPIFLWCnFxqsAYl7CIcDkD9low4kJ7IUuy6ctwx/HAy2fenrT3KXHr1sGjAMgpQ==}
engines: {node: '>=12.0.0'}
peerDependencies:
'@types/react': ^17.0.0 || ^18.0.0
@@ -5239,7 +5239,7 @@ packages:
'@babel/runtime': 7.22.15
'@floating-ui/react-dom': 2.0.2(react-dom@18.2.0)(react@18.2.0)
'@mui/types': 7.2.4(@types/react@18.2.20)
- '@mui/utils': 5.14.10(@types/react@18.2.20)(react@18.2.0)
+ '@mui/utils': 5.14.11(@types/react@18.2.20)(react@18.2.0)
'@popperjs/core': 2.11.8
'@types/react': 18.2.20
clsx: 2.0.0
@@ -5248,8 +5248,8 @@ packages:
react-dom: 18.2.0(react@18.2.0)
dev: false
- /@mui/core-downloads-tracker@5.14.10:
- resolution: {integrity: sha512-kPHu/NhZq1k+vSZR5wq3AyUfD4bnfWAeuKpps0+8PS7ZHQ2Lyv1cXJh+PlFdCIOa0PK98rk3JPwMzS8BMhdHwQ==}
+ /@mui/core-downloads-tracker@5.14.11:
+ resolution: {integrity: sha512-uY8FLQURhXe3f3O4dS5OSGML9KDm9+IE226cBu78jarVIzdQGPlXwGIlSI9VJR8MvZDA6C0+6XfWDhWCHruC5Q==}
dev: false
/@mui/core-downloads-tracker@5.14.5:
@@ -5273,8 +5273,8 @@ packages:
react: 18.2.0
dev: false
- /@mui/material@5.14.10(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(@types/react@18.2.20)(react-dom@18.2.0)(react@18.2.0):
- resolution: {integrity: sha512-ejFMppnO+lzBXpzju+N4SSz0Mhmi5sihXUGcr5FxpgB6bfUP0Lpe32O0Sw/3s8xlmLEvG1fqVT0rRyAVMlCA+A==}
+ /@mui/material@5.14.11(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(@types/react@18.2.20)(react-dom@18.2.0)(react@18.2.0):
+ resolution: {integrity: sha512-DnSdJzcR7lwG12JA5L2t8JF+RDzMygu5rCNW+logWb/KW2/TRzwLyVWO+CorHTBjBRd38DBxnwOCDiYkDd+N3A==}
engines: {node: '>=12.0.0'}
peerDependencies:
'@emotion/react': ^11.5.0
@@ -5293,11 +5293,11 @@ packages:
'@babel/runtime': 7.22.15
'@emotion/react': 11.11.1(@types/react@18.2.20)(react@18.2.0)
'@emotion/styled': 11.11.0(@emotion/react@11.11.1)(@types/react@18.2.20)(react@18.2.0)
- '@mui/base': 5.0.0-beta.16(@types/react@18.2.20)(react-dom@18.2.0)(react@18.2.0)
- '@mui/core-downloads-tracker': 5.14.10
- '@mui/system': 5.14.10(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(@types/react@18.2.20)(react@18.2.0)
+ '@mui/base': 5.0.0-beta.17(@types/react@18.2.20)(react-dom@18.2.0)(react@18.2.0)
+ '@mui/core-downloads-tracker': 5.14.11
+ '@mui/system': 5.14.11(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(@types/react@18.2.20)(react@18.2.0)
'@mui/types': 7.2.4(@types/react@18.2.20)
- '@mui/utils': 5.14.10(@types/react@18.2.20)(react@18.2.0)
+ '@mui/utils': 5.14.11(@types/react@18.2.20)(react@18.2.0)
'@types/react': 18.2.20
'@types/react-transition-group': 4.4.6
clsx: 2.0.0
@@ -5362,6 +5362,23 @@ packages:
react: 18.2.0
dev: false
+ /@mui/private-theming@5.14.11(@types/react@18.2.20)(react@18.2.0):
+ resolution: {integrity: sha512-MSnNNzTu9pfKLCKs1ZAKwOTgE4bz+fQA0fNr8Jm7NDmuWmw0CaN9Vq2/MHsatE7+S0A25IAKby46Uv1u53rKVQ==}
+ engines: {node: '>=12.0.0'}
+ peerDependencies:
+ '@types/react': ^17.0.0 || ^18.0.0
+ react: ^17.0.0 || ^18.0.0
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ dependencies:
+ '@babel/runtime': 7.22.15
+ '@mui/utils': 5.14.11(@types/react@18.2.20)(react@18.2.0)
+ '@types/react': 18.2.20
+ prop-types: 15.8.1
+ react: 18.2.0
+ dev: false
+
/@mui/private-theming@5.14.5(@types/react@18.2.20)(react@18.2.0):
resolution: {integrity: sha512-cC4C5RrpXpDaaZyH9QwmPhRLgz+f2SYbOty3cPkk4qPSOSfif2ZEcDD9HTENKDDd9deB+xkPKzzZhi8cxIx8Ig==}
engines: {node: '>=12.0.0'}
@@ -5401,6 +5418,28 @@ packages:
react: 18.2.0
dev: false
+ /@mui/styled-engine@5.14.11(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(react@18.2.0):
+ resolution: {integrity: sha512-jdUlqRgTYQ8RMtPX4MbRZqar6W2OiIb6J5KEFbIu4FqvPrk44Each4ppg/LAqp1qNlBYq5i+7Q10MYLMpDxX9A==}
+ engines: {node: '>=12.0.0'}
+ peerDependencies:
+ '@emotion/react': ^11.4.1
+ '@emotion/styled': ^11.3.0
+ react: ^17.0.0 || ^18.0.0
+ peerDependenciesMeta:
+ '@emotion/react':
+ optional: true
+ '@emotion/styled':
+ optional: true
+ dependencies:
+ '@babel/runtime': 7.22.15
+ '@emotion/cache': 11.11.0
+ '@emotion/react': 11.11.1(@types/react@18.2.20)(react@18.2.0)
+ '@emotion/styled': 11.11.0(@emotion/react@11.11.1)(@types/react@18.2.20)(react@18.2.0)
+ csstype: 3.1.2
+ prop-types: 15.8.1
+ react: 18.2.0
+ dev: false
+
/@mui/styles@5.14.5(@types/react@18.2.20)(react@18.2.0):
resolution: {integrity: sha512-yss4BRGae6ib4gq6YpVLnPyhHiuSIENwlDPWrTEqgc1UhTMmDBGZ7ZCdZ15YGdwviJuNDDf5Bcp3GK4rE5wZNQ==}
engines: {node: '>=12.0.0'}
@@ -5432,8 +5471,8 @@ packages:
react: 18.2.0
dev: false
- /@mui/system@5.14.10(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(@types/react@18.2.20)(react@18.2.0):
- resolution: {integrity: sha512-QQmtTG/R4gjmLiL5ECQ7kRxLKDm8aKKD7seGZfbINtRVJDyFhKChA1a+K2bfqIAaBo1EMDv+6FWNT1Q5cRKjFA==}
+ /@mui/system@5.14.11(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(@types/react@18.2.20)(react@18.2.0):
+ resolution: {integrity: sha512-yl8xV+y0k7j6dzBsHabKwoShmjqLa8kTxrhUI3JpqLG358VRVMJRW/ES0HhvfcCi4IVXde+Tc2P3K1akGL8zoA==}
engines: {node: '>=12.0.0'}
peerDependencies:
'@emotion/react': ^11.5.0
@@ -5451,10 +5490,10 @@ packages:
'@babel/runtime': 7.22.15
'@emotion/react': 11.11.1(@types/react@18.2.20)(react@18.2.0)
'@emotion/styled': 11.11.0(@emotion/react@11.11.1)(@types/react@18.2.20)(react@18.2.0)
- '@mui/private-theming': 5.14.10(@types/react@18.2.20)(react@18.2.0)
- '@mui/styled-engine': 5.14.10(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(react@18.2.0)
+ '@mui/private-theming': 5.14.11(@types/react@18.2.20)(react@18.2.0)
+ '@mui/styled-engine': 5.14.11(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(react@18.2.0)
'@mui/types': 7.2.4(@types/react@18.2.20)
- '@mui/utils': 5.14.10(@types/react@18.2.20)(react@18.2.0)
+ '@mui/utils': 5.14.11(@types/react@18.2.20)(react@18.2.0)
'@types/react': 18.2.20
clsx: 2.0.0
csstype: 3.1.2
@@ -5521,6 +5560,24 @@ packages:
react-is: 18.2.0
dev: false
+ /@mui/utils@5.14.11(@types/react@18.2.20)(react@18.2.0):
+ resolution: {integrity: sha512-fmkIiCPKyDssYrJ5qk+dime1nlO3dmWfCtaPY/uVBqCRMBZ11JhddB9m8sjI2mgqQQwRJG5bq3biaosNdU/s4Q==}
+ engines: {node: '>=12.0.0'}
+ peerDependencies:
+ '@types/react': ^17.0.0 || ^18.0.0
+ react: ^17.0.0 || ^18.0.0
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ dependencies:
+ '@babel/runtime': 7.22.15
+ '@types/prop-types': 15.7.5
+ '@types/react': 18.2.20
+ prop-types: 15.8.1
+ react: 18.2.0
+ react-is: 18.2.0
+ dev: false
+
/@mui/utils@5.14.5(react@18.2.0):
resolution: {integrity: sha512-6Hzw63VR9C5xYv+CbjndoRLU6Gntal8rJ5W+GUzkyHrGWIyYPWZPa6AevnyGioySNETATe1H9oXS8f/7qgIHJA==}
engines: {node: '>=12.0.0'}
@@ -10765,8 +10822,8 @@ packages:
dependencies:
esutils: 2.0.3
- /docx@8.2.2:
- resolution: {integrity: sha512-dWI5WfD/fDCLdjIA7CcDzV/1uyBD+mmr7jDLTUN997hcbPz56E701Kf1EEWdibdH9kk+0tFSmE+C0jTlkRZ7kQ==}
+ /docx@8.2.3:
+ resolution: {integrity: sha512-Rlr/wPTDh+xQpFew3m4zYQ5OWEZO36HItyPCUbGdicB+ar4zIgseeJdqfcZIY0uQtMSrhGRpSFOTjii7h9cpHw==}
engines: {node: '>=10'}
dependencies:
'@types/node': 20.4.2
@@ -19793,6 +19850,11 @@ packages:
engines: {node: '>=16'}
dev: false
+ /type-fest@4.3.3:
+ resolution: {integrity: sha512-bxhiFii6BBv6UiSDq7uKTMyADT9unXEl3ydGefndVLxFeB44LRbT4K7OJGDYSyDrKnklCC1Pre68qT2wbUl2Aw==}
+ engines: {node: '>=16'}
+ dev: false
+
/type-is@1.6.18:
resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==}
engines: {node: '>= 0.6'}
@@ -20843,8 +20905,8 @@ packages:
resolution: {integrity: sha512-PQ2PC7R9rslx84ndNBZB/Dkv8V8fZEpk83RLgXtYd0fwUgEjseMn1Dgajh2x6S8QbZAFa9p2qVCEuYZNgve0dQ==}
dev: false
- /zod@3.22.2:
- resolution: {integrity: sha512-wvWkphh5WQsJbVk1tbx1l1Ly4yg+XecD+Mq280uBGt9wa5BKSWf4Mhp6GmrkPixhMxmabYY7RbzlwVP32pbGCg==}
+ /zod@3.22.3:
+ resolution: {integrity: sha512-EjIevzuJRiRPbVH4mGc8nApb/lVLKVpmUhAaR5R5doKGfAnGJ6Gr3CViAVjP+4FWSxCsybeWQdcgCtbX+7oZug==}
dev: false
/zustand@4.3.9(immer@9.0.21)(react@18.2.0):
@@ -20864,9 +20926,9 @@ packages:
use-sync-external-store: 1.2.0(react@18.2.0)
dev: false
- github.com/theopensystemslab/planx-core/44420b9(@types/react@18.2.20):
- resolution: {tarball: https://codeload.github.com/theopensystemslab/planx-core/tar.gz/44420b9}
- id: github.com/theopensystemslab/planx-core/44420b9
+ github.com/theopensystemslab/planx-core/d92224b(@types/react@18.2.20):
+ resolution: {tarball: https://codeload.github.com/theopensystemslab/planx-core/tar.gz/d92224b}
+ id: github.com/theopensystemslab/planx-core/d92224b
name: '@opensystemslab/planx-core'
version: 1.0.0
prepare: true
@@ -20874,12 +20936,12 @@ packages:
dependencies:
'@emotion/react': 11.11.1(@types/react@18.2.20)(react@18.2.0)
'@emotion/styled': 11.11.0(@emotion/react@11.11.1)(@types/react@18.2.20)(react@18.2.0)
- '@mui/material': 5.14.10(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(@types/react@18.2.20)(react-dom@18.2.0)(react@18.2.0)
+ '@mui/material': 5.14.11(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(@types/react@18.2.20)(react-dom@18.2.0)(react@18.2.0)
'@types/geojson': 7946.0.11
ajv: 8.12.0
ajv-formats: 2.1.1(ajv@8.12.0)
copyfiles: 2.4.1
- docx: 8.2.2
+ docx: 8.2.3
eslint: 8.50.0
fast-xml-parser: 4.3.1
graphql: 16.8.1
@@ -20900,9 +20962,9 @@ packages:
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
striptags: 3.2.0
- type-fest: 4.3.1
+ type-fest: 4.3.3
uuid: 9.0.1
- zod: 3.22.2
+ zod: 3.22.3
transitivePeerDependencies:
- '@types/react'
- encoding
From 65f2218dc15d84ae5ffae41b85d55ed26f6f2d80 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Wed, 4 Oct 2023 11:36:25 +0100
Subject: [PATCH 6/8] chore(deps-dev): bump postcss from 8.4.25 to 8.4.31 in
/editor.planx.uk (#2281)
Bumps [postcss](https://github.com/postcss/postcss) from 8.4.25 to 8.4.31.
- [Release notes](https://github.com/postcss/postcss/releases)
- [Changelog](https://github.com/postcss/postcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/postcss/postcss/compare/8.4.25...8.4.31)
---
updated-dependencies:
- dependency-name: postcss
dependency-type: direct:development
...
Signed-off-by: dependabot[bot]
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
editor.planx.uk/package.json | 2 +-
editor.planx.uk/pnpm-lock.yaml | 606 ++++++++++++++++-----------------
2 files changed, 304 insertions(+), 304 deletions(-)
diff --git a/editor.planx.uk/package.json b/editor.planx.uk/package.json
index b59747f3e4..7424e59526 100644
--- a/editor.planx.uk/package.json
+++ b/editor.planx.uk/package.json
@@ -149,7 +149,7 @@
"jest-axe": "^6.0.1",
"jest-localstorage-mock": "^2.4.26",
"lint-staged": "^13.2.3",
- "postcss": "^8.4.24",
+ "postcss": "^8.4.31",
"prettier": "^3.0.0",
"react-app-rewired": "^2.2.1",
"react-refresh": "^0.14.0",
diff --git a/editor.planx.uk/pnpm-lock.yaml b/editor.planx.uk/pnpm-lock.yaml
index a89798ab6f..d9cb4b60e1 100644
--- a/editor.planx.uk/pnpm-lock.yaml
+++ b/editor.planx.uk/pnpm-lock.yaml
@@ -47,7 +47,7 @@ dependencies:
version: 0.7.5
'@opensystemslab/planx-core':
specifier: git+https://github.com/theopensystemslab/planx-core#d92224b
- version: github.com/theopensystemslab/planx-core/d92224b(@types/react@18.2.20)
+ version: git/github.com+theopensystemslab/planx-core/d92224b(@types/react@18.2.20)
'@tiptap/core':
specifier: ^2.0.3
version: 2.0.3(@tiptap/pm@2.0.3)
@@ -400,7 +400,7 @@ devDependencies:
version: 5.61.0(eslint@8.44.0)(typescript@4.9.5)
autoprefixer:
specifier: ^10.4.14
- version: 10.4.14(postcss@8.4.25)
+ version: 10.4.14(postcss@8.4.31)
craco-esbuild:
specifier: ^0.5.2
version: 0.5.2(@craco/craco@6.4.5)(esbuild@0.14.54)(react-scripts@5.0.1)(webpack@5.88.1)
@@ -447,8 +447,8 @@ devDependencies:
specifier: ^13.2.3
version: 13.2.3
postcss:
- specifier: ^8.4.24
- version: 8.4.25
+ specifier: ^8.4.31
+ version: 8.4.31
prettier:
specifier: ^3.0.0
version: 3.0.0
@@ -3666,135 +3666,135 @@ packages:
/@csstools/normalize.css@12.0.0:
resolution: {integrity: sha512-M0qqxAcwCsIVfpFQSlGN5XjXWu8l5JDZN+fPt1LeW5SZexQTgnaEvgXAY+CeygRw0EeppWHi12JxESWiWrB0Sg==}
- /@csstools/postcss-cascade-layers@1.1.1(postcss@8.4.25):
+ /@csstools/postcss-cascade-layers@1.1.1(postcss@8.4.31):
resolution: {integrity: sha512-+KdYrpKC5TgomQr2DlZF4lDEpHcoxnj5IGddYYfBWJAKfj1JtuHUIqMa+E1pJJ+z3kvDViWMqyqPlG4Ja7amQA==}
engines: {node: ^12 || ^14 || >=16}
peerDependencies:
postcss: ^8.2
dependencies:
'@csstools/selector-specificity': 2.2.0(postcss-selector-parser@6.0.13)
- postcss: 8.4.25
+ postcss: 8.4.31
postcss-selector-parser: 6.0.13
- /@csstools/postcss-color-function@1.1.1(postcss@8.4.25):
+ /@csstools/postcss-color-function@1.1.1(postcss@8.4.31):
resolution: {integrity: sha512-Bc0f62WmHdtRDjf5f3e2STwRAl89N2CLb+9iAwzrv4L2hncrbDwnQD9PCq0gtAt7pOI2leIV08HIBUd4jxD8cw==}
engines: {node: ^12 || ^14 || >=16}
peerDependencies:
postcss: ^8.2
dependencies:
- '@csstools/postcss-progressive-custom-properties': 1.3.0(postcss@8.4.25)
- postcss: 8.4.25
+ '@csstools/postcss-progressive-custom-properties': 1.3.0(postcss@8.4.31)
+ postcss: 8.4.31
postcss-value-parser: 4.2.0
- /@csstools/postcss-font-format-keywords@1.0.1(postcss@8.4.25):
+ /@csstools/postcss-font-format-keywords@1.0.1(postcss@8.4.31):
resolution: {integrity: sha512-ZgrlzuUAjXIOc2JueK0X5sZDjCtgimVp/O5CEqTcs5ShWBa6smhWYbS0x5cVc/+rycTDbjjzoP0KTDnUneZGOg==}
engines: {node: ^12 || ^14 || >=16}
peerDependencies:
postcss: ^8.2
dependencies:
- postcss: 8.4.25
+ postcss: 8.4.31
postcss-value-parser: 4.2.0
- /@csstools/postcss-hwb-function@1.0.2(postcss@8.4.25):
+ /@csstools/postcss-hwb-function@1.0.2(postcss@8.4.31):
resolution: {integrity: sha512-YHdEru4o3Rsbjmu6vHy4UKOXZD+Rn2zmkAmLRfPet6+Jz4Ojw8cbWxe1n42VaXQhD3CQUXXTooIy8OkVbUcL+w==}
engines: {node: ^12 || ^14 || >=16}
peerDependencies:
postcss: ^8.2
dependencies:
- postcss: 8.4.25
+ postcss: 8.4.31
postcss-value-parser: 4.2.0
- /@csstools/postcss-ic-unit@1.0.1(postcss@8.4.25):
+ /@csstools/postcss-ic-unit@1.0.1(postcss@8.4.31):
resolution: {integrity: sha512-Ot1rcwRAaRHNKC9tAqoqNZhjdYBzKk1POgWfhN4uCOE47ebGcLRqXjKkApVDpjifL6u2/55ekkpnFcp+s/OZUw==}
engines: {node: ^12 || ^14 || >=16}
peerDependencies:
postcss: ^8.2
dependencies:
- '@csstools/postcss-progressive-custom-properties': 1.3.0(postcss@8.4.25)
- postcss: 8.4.25
+ '@csstools/postcss-progressive-custom-properties': 1.3.0(postcss@8.4.31)
+ postcss: 8.4.31
postcss-value-parser: 4.2.0
- /@csstools/postcss-is-pseudo-class@2.0.7(postcss@8.4.25):
+ /@csstools/postcss-is-pseudo-class@2.0.7(postcss@8.4.31):
resolution: {integrity: sha512-7JPeVVZHd+jxYdULl87lvjgvWldYu+Bc62s9vD/ED6/QTGjy0jy0US/f6BG53sVMTBJ1lzKZFpYmofBN9eaRiA==}
engines: {node: ^12 || ^14 || >=16}
peerDependencies:
postcss: ^8.2
dependencies:
'@csstools/selector-specificity': 2.2.0(postcss-selector-parser@6.0.13)
- postcss: 8.4.25
+ postcss: 8.4.31
postcss-selector-parser: 6.0.13
- /@csstools/postcss-nested-calc@1.0.0(postcss@8.4.25):
+ /@csstools/postcss-nested-calc@1.0.0(postcss@8.4.31):
resolution: {integrity: sha512-JCsQsw1wjYwv1bJmgjKSoZNvf7R6+wuHDAbi5f/7MbFhl2d/+v+TvBTU4BJH3G1X1H87dHl0mh6TfYogbT/dJQ==}
engines: {node: ^12 || ^14 || >=16}
peerDependencies:
postcss: ^8.2
dependencies:
- postcss: 8.4.25
+ postcss: 8.4.31
postcss-value-parser: 4.2.0
- /@csstools/postcss-normalize-display-values@1.0.1(postcss@8.4.25):
+ /@csstools/postcss-normalize-display-values@1.0.1(postcss@8.4.31):
resolution: {integrity: sha512-jcOanIbv55OFKQ3sYeFD/T0Ti7AMXc9nM1hZWu8m/2722gOTxFg7xYu4RDLJLeZmPUVQlGzo4jhzvTUq3x4ZUw==}
engines: {node: ^12 || ^14 || >=16}
peerDependencies:
postcss: ^8.2
dependencies:
- postcss: 8.4.25
+ postcss: 8.4.31
postcss-value-parser: 4.2.0
- /@csstools/postcss-oklab-function@1.1.1(postcss@8.4.25):
+ /@csstools/postcss-oklab-function@1.1.1(postcss@8.4.31):
resolution: {integrity: sha512-nJpJgsdA3dA9y5pgyb/UfEzE7W5Ka7u0CX0/HIMVBNWzWemdcTH3XwANECU6anWv/ao4vVNLTMxhiPNZsTK6iA==}
engines: {node: ^12 || ^14 || >=16}
peerDependencies:
postcss: ^8.2
dependencies:
- '@csstools/postcss-progressive-custom-properties': 1.3.0(postcss@8.4.25)
- postcss: 8.4.25
+ '@csstools/postcss-progressive-custom-properties': 1.3.0(postcss@8.4.31)
+ postcss: 8.4.31
postcss-value-parser: 4.2.0
- /@csstools/postcss-progressive-custom-properties@1.3.0(postcss@8.4.25):
+ /@csstools/postcss-progressive-custom-properties@1.3.0(postcss@8.4.31):
resolution: {integrity: sha512-ASA9W1aIy5ygskZYuWams4BzafD12ULvSypmaLJT2jvQ8G0M3I8PRQhC0h7mG0Z3LI05+agZjqSR9+K9yaQQjA==}
engines: {node: ^12 || ^14 || >=16}
peerDependencies:
postcss: ^8.3
dependencies:
- postcss: 8.4.25
+ postcss: 8.4.31
postcss-value-parser: 4.2.0
- /@csstools/postcss-stepped-value-functions@1.0.1(postcss@8.4.25):
+ /@csstools/postcss-stepped-value-functions@1.0.1(postcss@8.4.31):
resolution: {integrity: sha512-dz0LNoo3ijpTOQqEJLY8nyaapl6umbmDcgj4AD0lgVQ572b2eqA1iGZYTTWhrcrHztWDDRAX2DGYyw2VBjvCvQ==}
engines: {node: ^12 || ^14 || >=16}
peerDependencies:
postcss: ^8.2
dependencies:
- postcss: 8.4.25
+ postcss: 8.4.31
postcss-value-parser: 4.2.0
- /@csstools/postcss-text-decoration-shorthand@1.0.0(postcss@8.4.25):
+ /@csstools/postcss-text-decoration-shorthand@1.0.0(postcss@8.4.31):
resolution: {integrity: sha512-c1XwKJ2eMIWrzQenN0XbcfzckOLLJiczqy+YvfGmzoVXd7pT9FfObiSEfzs84bpE/VqfpEuAZ9tCRbZkZxxbdw==}
engines: {node: ^12 || ^14 || >=16}
peerDependencies:
postcss: ^8.2
dependencies:
- postcss: 8.4.25
+ postcss: 8.4.31
postcss-value-parser: 4.2.0
- /@csstools/postcss-trigonometric-functions@1.0.2(postcss@8.4.25):
+ /@csstools/postcss-trigonometric-functions@1.0.2(postcss@8.4.31):
resolution: {integrity: sha512-woKaLO///4bb+zZC2s80l+7cm07M7268MsyG3M0ActXXEFi6SuhvriQYcb58iiKGbjwwIU7n45iRLEHypB47Og==}
engines: {node: ^14 || >=16}
peerDependencies:
postcss: ^8.2
dependencies:
- postcss: 8.4.25
+ postcss: 8.4.31
postcss-value-parser: 4.2.0
- /@csstools/postcss-unset-value@1.0.2(postcss@8.4.25):
+ /@csstools/postcss-unset-value@1.0.2(postcss@8.4.31):
resolution: {integrity: sha512-c8J4roPBILnelAsdLr4XOAR/GsTm0GJi4XpcfvoWk3U6KiTCqiFYc63KhRMQQX35jYMp4Ao8Ij9+IZRgMfJp1g==}
engines: {node: ^12 || ^14 || >=16}
peerDependencies:
postcss: ^8.2
dependencies:
- postcss: 8.4.25
+ postcss: 8.4.31
/@csstools/selector-specificity@2.2.0(postcss-selector-parser@6.0.13):
resolution: {integrity: sha512-+OJ9konv95ClSTOJCmMZqpd5+YGsB2S+x6w3E1oaM8UuR5j8nTNHYSz8c9BEPGDOCMQYIEEGlVPj/VY64iTbGw==}
@@ -5356,7 +5356,7 @@ packages:
optional: true
dependencies:
'@babel/runtime': 7.22.15
- '@mui/utils': 5.14.10(@types/react@18.2.20)(react@18.2.0)
+ '@mui/utils': 5.14.11(@types/react@18.2.20)(react@18.2.0)
'@types/react': 18.2.20
prop-types: 15.8.1
react: 18.2.0
@@ -5390,7 +5390,7 @@ packages:
optional: true
dependencies:
'@babel/runtime': 7.22.15
- '@mui/utils': 5.14.10(@types/react@18.2.20)(react@18.2.0)
+ '@mui/utils': 5.14.11(@types/react@18.2.20)(react@18.2.0)
'@types/react': 18.2.20
prop-types: 15.8.1
react: 18.2.0
@@ -8971,7 +8971,7 @@ packages:
engines: {node: '>=4'}
dev: false
- /autoprefixer@10.4.14(postcss@8.4.25):
+ /autoprefixer@10.4.14(postcss@8.4.31):
resolution: {integrity: sha512-FQzyfOsTlwVzjHxKEqRIAdJx9niO6VCBCoEwax/VLSoQF29ggECcPuBqUMZ+u8jCZOPSy8b8/8KnuFbp0SaFZQ==}
engines: {node: ^10 || ^12 || >=14}
hasBin: true
@@ -8983,7 +8983,7 @@ packages:
fraction.js: 4.2.0
normalize-range: 0.1.2
picocolors: 1.0.0
- postcss: 8.4.25
+ postcss: 8.4.31
postcss-value-parser: 4.2.0
/available-typed-arrays@1.0.5:
@@ -10265,14 +10265,14 @@ packages:
resolution: {integrity: sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==}
engines: {node: '>=8'}
- /css-blank-pseudo@3.0.3(postcss@8.4.25):
+ /css-blank-pseudo@3.0.3(postcss@8.4.31):
resolution: {integrity: sha512-VS90XWtsHGqoM0t4KpH053c4ehxZ2E6HtGI7x68YFV0pTo/QmkV/YFA+NnlvK8guxZVNWGQhVNJGC39Q8XF4OQ==}
engines: {node: ^12 || ^14 || >=16}
hasBin: true
peerDependencies:
postcss: ^8.4
dependencies:
- postcss: 8.4.25
+ postcss: 8.4.31
postcss-selector-parser: 6.0.13
/css-box-model@1.2.1:
@@ -10285,22 +10285,22 @@ packages:
resolution: {integrity: sha512-zj5D7X1U2h2zsXOAM8EyUREBnnts6H+Jm+d1M2DbiQQcUtnqgQsMrdo8JW9R80YFUmIdBZeMu5wvYM7hcgWP/Q==}
dev: true
- /css-declaration-sorter@6.4.0(postcss@8.4.25):
+ /css-declaration-sorter@6.4.0(postcss@8.4.31):
resolution: {integrity: sha512-jDfsatwWMWN0MODAFuHszfjphEXfNw9JUAhmY4pLu3TyTU+ohUpsbVtbU+1MZn4a47D9kqh03i4eyOm+74+zew==}
engines: {node: ^10 || ^12 || >=14}
peerDependencies:
postcss: ^8.0.9
dependencies:
- postcss: 8.4.25
+ postcss: 8.4.31
- /css-has-pseudo@3.0.4(postcss@8.4.25):
+ /css-has-pseudo@3.0.4(postcss@8.4.31):
resolution: {integrity: sha512-Vse0xpR1K9MNlp2j5w1pgWIJtm1a8qS0JwS9goFYcImjlHEmywP9VUF05aGBXzGpDJF86QXk4L0ypBmwPhGArw==}
engines: {node: ^12 || ^14 || >=16}
hasBin: true
peerDependencies:
postcss: ^8.4
dependencies:
- postcss: 8.4.25
+ postcss: 8.4.31
postcss-selector-parser: 6.0.13
/css-in-js-utils@3.1.0:
@@ -10323,12 +10323,12 @@ packages:
peerDependencies:
webpack: ^5.0.0
dependencies:
- icss-utils: 5.1.0(postcss@8.4.25)
- postcss: 8.4.25
- postcss-modules-extract-imports: 3.0.0(postcss@8.4.25)
- postcss-modules-local-by-default: 4.0.3(postcss@8.4.25)
- postcss-modules-scope: 3.0.0(postcss@8.4.25)
- postcss-modules-values: 4.0.0(postcss@8.4.25)
+ icss-utils: 5.1.0(postcss@8.4.31)
+ postcss: 8.4.31
+ postcss-modules-extract-imports: 3.0.0(postcss@8.4.31)
+ postcss-modules-local-by-default: 4.0.3(postcss@8.4.31)
+ postcss-modules-scope: 3.0.0(postcss@8.4.31)
+ postcss-modules-values: 4.0.0(postcss@8.4.31)
postcss-value-parser: 4.2.0
semver: 7.5.3
webpack: 5.88.1(@swc/core@1.3.71)(esbuild@0.14.54)
@@ -10352,23 +10352,23 @@ packages:
esbuild:
optional: true
dependencies:
- cssnano: 5.1.15(postcss@8.4.25)
+ cssnano: 5.1.15(postcss@8.4.31)
esbuild: 0.14.54
jest-worker: 27.5.1
- postcss: 8.4.25
+ postcss: 8.4.31
schema-utils: 4.2.0
serialize-javascript: 6.0.1
source-map: 0.6.1
webpack: 5.88.1(@swc/core@1.3.71)(esbuild@0.14.54)
- /css-prefers-color-scheme@6.0.3(postcss@8.4.25):
+ /css-prefers-color-scheme@6.0.3(postcss@8.4.31):
resolution: {integrity: sha512-4BqMbZksRkJQx2zAjrokiGMd07RqOa2IxIrrN10lyBe9xhn9DEvjUK79J6jkeiv9D9hQFXKb6g1jwU62jziJZA==}
engines: {node: ^12 || ^14 || >=16}
hasBin: true
peerDependencies:
postcss: ^8.4
dependencies:
- postcss: 8.4.25
+ postcss: 8.4.31
/css-select-base-adapter@0.1.1:
resolution: {integrity: sha512-jQVeeRG70QI08vSTwf1jHxp74JoZsr2XSgETae8/xC8ovSnL2WF87GTLO86Sbwdt2lK4Umg4HnnwMO4YF3Ce7w==}
@@ -10434,60 +10434,60 @@ packages:
engines: {node: '>=4'}
hasBin: true
- /cssnano-preset-default@5.2.14(postcss@8.4.25):
+ /cssnano-preset-default@5.2.14(postcss@8.4.31):
resolution: {integrity: sha512-t0SFesj/ZV2OTylqQVOrFgEh5uanxbO6ZAdeCrNsUQ6fVuXwYTxJPNAGvGTxHbD68ldIJNec7PyYZDBrfDQ+6A==}
engines: {node: ^10 || ^12 || >=14.0}
peerDependencies:
postcss: ^8.2.15
dependencies:
- css-declaration-sorter: 6.4.0(postcss@8.4.25)
- cssnano-utils: 3.1.0(postcss@8.4.25)
- postcss: 8.4.25
- postcss-calc: 8.2.4(postcss@8.4.25)
- postcss-colormin: 5.3.1(postcss@8.4.25)
- postcss-convert-values: 5.1.3(postcss@8.4.25)
- postcss-discard-comments: 5.1.2(postcss@8.4.25)
- postcss-discard-duplicates: 5.1.0(postcss@8.4.25)
- postcss-discard-empty: 5.1.1(postcss@8.4.25)
- postcss-discard-overridden: 5.1.0(postcss@8.4.25)
- postcss-merge-longhand: 5.1.7(postcss@8.4.25)
- postcss-merge-rules: 5.1.4(postcss@8.4.25)
- postcss-minify-font-values: 5.1.0(postcss@8.4.25)
- postcss-minify-gradients: 5.1.1(postcss@8.4.25)
- postcss-minify-params: 5.1.4(postcss@8.4.25)
- postcss-minify-selectors: 5.2.1(postcss@8.4.25)
- postcss-normalize-charset: 5.1.0(postcss@8.4.25)
- postcss-normalize-display-values: 5.1.0(postcss@8.4.25)
- postcss-normalize-positions: 5.1.1(postcss@8.4.25)
- postcss-normalize-repeat-style: 5.1.1(postcss@8.4.25)
- postcss-normalize-string: 5.1.0(postcss@8.4.25)
- postcss-normalize-timing-functions: 5.1.0(postcss@8.4.25)
- postcss-normalize-unicode: 5.1.1(postcss@8.4.25)
- postcss-normalize-url: 5.1.0(postcss@8.4.25)
- postcss-normalize-whitespace: 5.1.1(postcss@8.4.25)
- postcss-ordered-values: 5.1.3(postcss@8.4.25)
- postcss-reduce-initial: 5.1.2(postcss@8.4.25)
- postcss-reduce-transforms: 5.1.0(postcss@8.4.25)
- postcss-svgo: 5.1.0(postcss@8.4.25)
- postcss-unique-selectors: 5.1.1(postcss@8.4.25)
-
- /cssnano-utils@3.1.0(postcss@8.4.25):
+ css-declaration-sorter: 6.4.0(postcss@8.4.31)
+ cssnano-utils: 3.1.0(postcss@8.4.31)
+ postcss: 8.4.31
+ postcss-calc: 8.2.4(postcss@8.4.31)
+ postcss-colormin: 5.3.1(postcss@8.4.31)
+ postcss-convert-values: 5.1.3(postcss@8.4.31)
+ postcss-discard-comments: 5.1.2(postcss@8.4.31)
+ postcss-discard-duplicates: 5.1.0(postcss@8.4.31)
+ postcss-discard-empty: 5.1.1(postcss@8.4.31)
+ postcss-discard-overridden: 5.1.0(postcss@8.4.31)
+ postcss-merge-longhand: 5.1.7(postcss@8.4.31)
+ postcss-merge-rules: 5.1.4(postcss@8.4.31)
+ postcss-minify-font-values: 5.1.0(postcss@8.4.31)
+ postcss-minify-gradients: 5.1.1(postcss@8.4.31)
+ postcss-minify-params: 5.1.4(postcss@8.4.31)
+ postcss-minify-selectors: 5.2.1(postcss@8.4.31)
+ postcss-normalize-charset: 5.1.0(postcss@8.4.31)
+ postcss-normalize-display-values: 5.1.0(postcss@8.4.31)
+ postcss-normalize-positions: 5.1.1(postcss@8.4.31)
+ postcss-normalize-repeat-style: 5.1.1(postcss@8.4.31)
+ postcss-normalize-string: 5.1.0(postcss@8.4.31)
+ postcss-normalize-timing-functions: 5.1.0(postcss@8.4.31)
+ postcss-normalize-unicode: 5.1.1(postcss@8.4.31)
+ postcss-normalize-url: 5.1.0(postcss@8.4.31)
+ postcss-normalize-whitespace: 5.1.1(postcss@8.4.31)
+ postcss-ordered-values: 5.1.3(postcss@8.4.31)
+ postcss-reduce-initial: 5.1.2(postcss@8.4.31)
+ postcss-reduce-transforms: 5.1.0(postcss@8.4.31)
+ postcss-svgo: 5.1.0(postcss@8.4.31)
+ postcss-unique-selectors: 5.1.1(postcss@8.4.31)
+
+ /cssnano-utils@3.1.0(postcss@8.4.31):
resolution: {integrity: sha512-JQNR19/YZhz4psLX/rQ9M83e3z2Wf/HdJbryzte4a3NSuafyp9w/I4U+hx5C2S9g41qlstH7DEWnZaaj83OuEA==}
engines: {node: ^10 || ^12 || >=14.0}
peerDependencies:
postcss: ^8.2.15
dependencies:
- postcss: 8.4.25
+ postcss: 8.4.31
- /cssnano@5.1.15(postcss@8.4.25):
+ /cssnano@5.1.15(postcss@8.4.31):
resolution: {integrity: sha512-j+BKgDcLDQA+eDifLx0EO4XSA56b7uut3BQFH+wbSaSTuGLuiyTa/wbRYthUXX8LC9mLg+WWKe8h+qJuwTAbHw==}
engines: {node: ^10 || ^12 || >=14.0}
peerDependencies:
postcss: ^8.2.15
dependencies:
- cssnano-preset-default: 5.2.14(postcss@8.4.25)
+ cssnano-preset-default: 5.2.14(postcss@8.4.31)
lilconfig: 2.1.0
- postcss: 8.4.25
+ postcss: 8.4.31
yaml: 1.10.2
/csso@4.2.0:
@@ -13147,13 +13147,13 @@ packages:
dependencies:
safer-buffer: 2.1.2
- /icss-utils@5.1.0(postcss@8.4.25):
+ /icss-utils@5.1.0(postcss@8.4.31):
resolution: {integrity: sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==}
engines: {node: ^10 || ^12 || >= 14}
peerDependencies:
postcss: ^8.1.0
dependencies:
- postcss: 8.4.25
+ postcss: 8.4.31
/idb@7.1.1:
resolution: {integrity: sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==}
@@ -16238,16 +16238,16 @@ packages:
engines: {node: '>= 10.0.0'}
dev: false
- /postcss-attribute-case-insensitive@5.0.2(postcss@8.4.25):
+ /postcss-attribute-case-insensitive@5.0.2(postcss@8.4.31):
resolution: {integrity: sha512-XIidXV8fDr0kKt28vqki84fRK8VW8eTuIa4PChv2MqKuT6C9UjmSKzen6KaWhWEoYvwxFCa7n/tC1SZ3tyq4SQ==}
engines: {node: ^12 || ^14 || >=16}
peerDependencies:
postcss: ^8.2
dependencies:
- postcss: 8.4.25
+ postcss: 8.4.31
postcss-selector-parser: 6.0.13
- /postcss-browser-comments@4.0.0(browserslist@4.21.9)(postcss@8.4.25):
+ /postcss-browser-comments@4.0.0(browserslist@4.21.9)(postcss@8.4.31):
resolution: {integrity: sha512-X9X9/WN3KIvY9+hNERUqX9gncsgBA25XaeR+jshHz2j8+sYyHktHw1JdKuMjeLpGktXidqDhA7b/qm1mrBDmgg==}
engines: {node: '>=8'}
peerDependencies:
@@ -16255,54 +16255,54 @@ packages:
postcss: '>=8'
dependencies:
browserslist: 4.21.9
- postcss: 8.4.25
+ postcss: 8.4.31
- /postcss-calc@8.2.4(postcss@8.4.25):
+ /postcss-calc@8.2.4(postcss@8.4.31):
resolution: {integrity: sha512-SmWMSJmB8MRnnULldx0lQIyhSNvuDl9HfrZkaqqE/WHAhToYsAvDq+yAsA/kIyINDszOp3Rh0GFoNuH5Ypsm3Q==}
peerDependencies:
postcss: ^8.2.2
dependencies:
- postcss: 8.4.25
+ postcss: 8.4.31
postcss-selector-parser: 6.0.13
postcss-value-parser: 4.2.0
- /postcss-clamp@4.1.0(postcss@8.4.25):
+ /postcss-clamp@4.1.0(postcss@8.4.31):
resolution: {integrity: sha512-ry4b1Llo/9zz+PKC+030KUnPITTJAHeOwjfAyyB60eT0AorGLdzp52s31OsPRHRf8NchkgFoG2y6fCfn1IV1Ow==}
engines: {node: '>=7.6.0'}
peerDependencies:
postcss: ^8.4.6
dependencies:
- postcss: 8.4.25
+ postcss: 8.4.31
postcss-value-parser: 4.2.0
- /postcss-color-functional-notation@4.2.4(postcss@8.4.25):
+ /postcss-color-functional-notation@4.2.4(postcss@8.4.31):
resolution: {integrity: sha512-2yrTAUZUab9s6CpxkxC4rVgFEVaR6/2Pipvi6qcgvnYiVqZcbDHEoBDhrXzyb7Efh2CCfHQNtcqWcIruDTIUeg==}
engines: {node: ^12 || ^14 || >=16}
peerDependencies:
postcss: ^8.2
dependencies:
- postcss: 8.4.25
+ postcss: 8.4.31
postcss-value-parser: 4.2.0
- /postcss-color-hex-alpha@8.0.4(postcss@8.4.25):
+ /postcss-color-hex-alpha@8.0.4(postcss@8.4.31):
resolution: {integrity: sha512-nLo2DCRC9eE4w2JmuKgVA3fGL3d01kGq752pVALF68qpGLmx2Qrk91QTKkdUqqp45T1K1XV8IhQpcu1hoAQflQ==}
engines: {node: ^12 || ^14 || >=16}
peerDependencies:
postcss: ^8.4
dependencies:
- postcss: 8.4.25
+ postcss: 8.4.31
postcss-value-parser: 4.2.0
- /postcss-color-rebeccapurple@7.1.1(postcss@8.4.25):
+ /postcss-color-rebeccapurple@7.1.1(postcss@8.4.31):
resolution: {integrity: sha512-pGxkuVEInwLHgkNxUc4sdg4g3py7zUeCQ9sMfwyHAT+Ezk8a4OaaVZ8lIY5+oNqA/BXXgLyXv0+5wHP68R79hg==}
engines: {node: ^12 || ^14 || >=16}
peerDependencies:
postcss: ^8.2
dependencies:
- postcss: 8.4.25
+ postcss: 8.4.31
postcss-value-parser: 4.2.0
- /postcss-colormin@5.3.1(postcss@8.4.25):
+ /postcss-colormin@5.3.1(postcss@8.4.31):
resolution: {integrity: sha512-UsWQG0AqTFQmpBegeLLc1+c3jIqBNB0zlDGRWR+dQ3pRKJL1oeMzyqmH3o2PIfn9MBdNrVPWhDbT769LxCTLJQ==}
engines: {node: ^10 || ^12 || >=14.0}
peerDependencies:
@@ -16311,193 +16311,193 @@ packages:
browserslist: 4.21.9
caniuse-api: 3.0.0
colord: 2.9.3
- postcss: 8.4.25
+ postcss: 8.4.31
postcss-value-parser: 4.2.0
- /postcss-convert-values@5.1.3(postcss@8.4.25):
+ /postcss-convert-values@5.1.3(postcss@8.4.31):
resolution: {integrity: sha512-82pC1xkJZtcJEfiLw6UXnXVXScgtBrjlO5CBmuDQc+dlb88ZYheFsjTn40+zBVi3DkfF7iezO0nJUPLcJK3pvA==}
engines: {node: ^10 || ^12 || >=14.0}
peerDependencies:
postcss: ^8.2.15
dependencies:
browserslist: 4.21.9
- postcss: 8.4.25
+ postcss: 8.4.31
postcss-value-parser: 4.2.0
- /postcss-custom-media@8.0.2(postcss@8.4.25):
+ /postcss-custom-media@8.0.2(postcss@8.4.31):
resolution: {integrity: sha512-7yi25vDAoHAkbhAzX9dHx2yc6ntS4jQvejrNcC+csQJAXjj15e7VcWfMgLqBNAbOvqi5uIa9huOVwdHbf+sKqg==}
engines: {node: ^12 || ^14 || >=16}
peerDependencies:
postcss: ^8.3
dependencies:
- postcss: 8.4.25
+ postcss: 8.4.31
postcss-value-parser: 4.2.0
- /postcss-custom-properties@12.1.11(postcss@8.4.25):
+ /postcss-custom-properties@12.1.11(postcss@8.4.31):
resolution: {integrity: sha512-0IDJYhgU8xDv1KY6+VgUwuQkVtmYzRwu+dMjnmdMafXYv86SWqfxkc7qdDvWS38vsjaEtv8e0vGOUQrAiMBLpQ==}
engines: {node: ^12 || ^14 || >=16}
peerDependencies:
postcss: ^8.2
dependencies:
- postcss: 8.4.25
+ postcss: 8.4.31
postcss-value-parser: 4.2.0
- /postcss-custom-selectors@6.0.3(postcss@8.4.25):
+ /postcss-custom-selectors@6.0.3(postcss@8.4.31):
resolution: {integrity: sha512-fgVkmyiWDwmD3JbpCmB45SvvlCD6z9CG6Ie6Iere22W5aHea6oWa7EM2bpnv2Fj3I94L3VbtvX9KqwSi5aFzSg==}
engines: {node: ^12 || ^14 || >=16}
peerDependencies:
postcss: ^8.3
dependencies:
- postcss: 8.4.25
+ postcss: 8.4.31
postcss-selector-parser: 6.0.13
- /postcss-dir-pseudo-class@6.0.5(postcss@8.4.25):
+ /postcss-dir-pseudo-class@6.0.5(postcss@8.4.31):
resolution: {integrity: sha512-eqn4m70P031PF7ZQIvSgy9RSJ5uI2171O/OO/zcRNYpJbvaeKFUlar1aJ7rmgiQtbm0FSPsRewjpdS0Oew7MPA==}
engines: {node: ^12 || ^14 || >=16}
peerDependencies:
postcss: ^8.2
dependencies:
- postcss: 8.4.25
+ postcss: 8.4.31
postcss-selector-parser: 6.0.13
- /postcss-discard-comments@5.1.2(postcss@8.4.25):
+ /postcss-discard-comments@5.1.2(postcss@8.4.31):
resolution: {integrity: sha512-+L8208OVbHVF2UQf1iDmRcbdjJkuBF6IS29yBDSiWUIzpYaAhtNl6JYnYm12FnkeCwQqF5LeklOu6rAqgfBZqQ==}
engines: {node: ^10 || ^12 || >=14.0}
peerDependencies:
postcss: ^8.2.15
dependencies:
- postcss: 8.4.25
+ postcss: 8.4.31
- /postcss-discard-duplicates@5.1.0(postcss@8.4.25):
+ /postcss-discard-duplicates@5.1.0(postcss@8.4.31):
resolution: {integrity: sha512-zmX3IoSI2aoenxHV6C7plngHWWhUOV3sP1T8y2ifzxzbtnuhk1EdPwm0S1bIUNaJ2eNbWeGLEwzw8huPD67aQw==}
engines: {node: ^10 || ^12 || >=14.0}
peerDependencies:
postcss: ^8.2.15
dependencies:
- postcss: 8.4.25
+ postcss: 8.4.31
- /postcss-discard-empty@5.1.1(postcss@8.4.25):
+ /postcss-discard-empty@5.1.1(postcss@8.4.31):
resolution: {integrity: sha512-zPz4WljiSuLWsI0ir4Mcnr4qQQ5e1Ukc3i7UfE2XcrwKK2LIPIqE5jxMRxO6GbI3cv//ztXDsXwEWT3BHOGh3A==}
engines: {node: ^10 || ^12 || >=14.0}
peerDependencies:
postcss: ^8.2.15
dependencies:
- postcss: 8.4.25
+ postcss: 8.4.31
- /postcss-discard-overridden@5.1.0(postcss@8.4.25):
+ /postcss-discard-overridden@5.1.0(postcss@8.4.31):
resolution: {integrity: sha512-21nOL7RqWR1kasIVdKs8HNqQJhFxLsyRfAnUDm4Fe4t4mCWL9OJiHvlHPjcd8zc5Myu89b/7wZDnOSjFgeWRtw==}
engines: {node: ^10 || ^12 || >=14.0}
peerDependencies:
postcss: ^8.2.15
dependencies:
- postcss: 8.4.25
+ postcss: 8.4.31
- /postcss-double-position-gradients@3.1.2(postcss@8.4.25):
+ /postcss-double-position-gradients@3.1.2(postcss@8.4.31):
resolution: {integrity: sha512-GX+FuE/uBR6eskOK+4vkXgT6pDkexLokPaz/AbJna9s5Kzp/yl488pKPjhy0obB475ovfT1Wv8ho7U/cHNaRgQ==}
engines: {node: ^12 || ^14 || >=16}
peerDependencies:
postcss: ^8.2
dependencies:
- '@csstools/postcss-progressive-custom-properties': 1.3.0(postcss@8.4.25)
- postcss: 8.4.25
+ '@csstools/postcss-progressive-custom-properties': 1.3.0(postcss@8.4.31)
+ postcss: 8.4.31
postcss-value-parser: 4.2.0
- /postcss-env-function@4.0.6(postcss@8.4.25):
+ /postcss-env-function@4.0.6(postcss@8.4.31):
resolution: {integrity: sha512-kpA6FsLra+NqcFnL81TnsU+Z7orGtDTxcOhl6pwXeEq1yFPpRMkCDpHhrz8CFQDr/Wfm0jLiNQ1OsGGPjlqPwA==}
engines: {node: ^12 || ^14 || >=16}
peerDependencies:
postcss: ^8.4
dependencies:
- postcss: 8.4.25
+ postcss: 8.4.31
postcss-value-parser: 4.2.0
- /postcss-flexbugs-fixes@5.0.2(postcss@8.4.25):
+ /postcss-flexbugs-fixes@5.0.2(postcss@8.4.31):
resolution: {integrity: sha512-18f9voByak7bTktR2QgDveglpn9DTbBWPUzSOe9g0N4WR/2eSt6Vrcbf0hmspvMI6YWGywz6B9f7jzpFNJJgnQ==}
peerDependencies:
postcss: ^8.1.4
dependencies:
- postcss: 8.4.25
+ postcss: 8.4.31
- /postcss-focus-visible@6.0.4(postcss@8.4.25):
+ /postcss-focus-visible@6.0.4(postcss@8.4.31):
resolution: {integrity: sha512-QcKuUU/dgNsstIK6HELFRT5Y3lbrMLEOwG+A4s5cA+fx3A3y/JTq3X9LaOj3OC3ALH0XqyrgQIgey/MIZ8Wczw==}
engines: {node: ^12 || ^14 || >=16}
peerDependencies:
postcss: ^8.4
dependencies:
- postcss: 8.4.25
+ postcss: 8.4.31
postcss-selector-parser: 6.0.13
- /postcss-focus-within@5.0.4(postcss@8.4.25):
+ /postcss-focus-within@5.0.4(postcss@8.4.31):
resolution: {integrity: sha512-vvjDN++C0mu8jz4af5d52CB184ogg/sSxAFS+oUJQq2SuCe7T5U2iIsVJtsCp2d6R4j0jr5+q3rPkBVZkXD9fQ==}
engines: {node: ^12 || ^14 || >=16}
peerDependencies:
postcss: ^8.4
dependencies:
- postcss: 8.4.25
+ postcss: 8.4.31
postcss-selector-parser: 6.0.13
- /postcss-font-variant@5.0.0(postcss@8.4.25):
+ /postcss-font-variant@5.0.0(postcss@8.4.31):
resolution: {integrity: sha512-1fmkBaCALD72CK2a9i468mA/+tr9/1cBxRRMXOUaZqO43oWPR5imcyPjXwuv7PXbCid4ndlP5zWhidQVVa3hmA==}
peerDependencies:
postcss: ^8.1.0
dependencies:
- postcss: 8.4.25
+ postcss: 8.4.31
- /postcss-gap-properties@3.0.5(postcss@8.4.25):
+ /postcss-gap-properties@3.0.5(postcss@8.4.31):
resolution: {integrity: sha512-IuE6gKSdoUNcvkGIqdtjtcMtZIFyXZhmFd5RUlg97iVEvp1BZKV5ngsAjCjrVy+14uhGBQl9tzmi1Qwq4kqVOg==}
engines: {node: ^12 || ^14 || >=16}
peerDependencies:
postcss: ^8.2
dependencies:
- postcss: 8.4.25
+ postcss: 8.4.31
- /postcss-image-set-function@4.0.7(postcss@8.4.25):
+ /postcss-image-set-function@4.0.7(postcss@8.4.31):
resolution: {integrity: sha512-9T2r9rsvYzm5ndsBE8WgtrMlIT7VbtTfE7b3BQnudUqnBcBo7L758oc+o+pdj/dUV0l5wjwSdjeOH2DZtfv8qw==}
engines: {node: ^12 || ^14 || >=16}
peerDependencies:
postcss: ^8.2
dependencies:
- postcss: 8.4.25
+ postcss: 8.4.31
postcss-value-parser: 4.2.0
- /postcss-import@15.1.0(postcss@8.4.25):
+ /postcss-import@15.1.0(postcss@8.4.31):
resolution: {integrity: sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==}
engines: {node: '>=14.0.0'}
peerDependencies:
postcss: ^8.0.0
dependencies:
- postcss: 8.4.25
+ postcss: 8.4.31
postcss-value-parser: 4.2.0
read-cache: 1.0.0
resolve: 1.22.2
- /postcss-initial@4.0.1(postcss@8.4.25):
+ /postcss-initial@4.0.1(postcss@8.4.31):
resolution: {integrity: sha512-0ueD7rPqX8Pn1xJIjay0AZeIuDoF+V+VvMt/uOnn+4ezUKhZM/NokDeP6DwMNyIoYByuN/94IQnt5FEkaN59xQ==}
peerDependencies:
postcss: ^8.0.0
dependencies:
- postcss: 8.4.25
+ postcss: 8.4.31
- /postcss-js@4.0.1(postcss@8.4.25):
+ /postcss-js@4.0.1(postcss@8.4.31):
resolution: {integrity: sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==}
engines: {node: ^12 || ^14 || >= 16}
peerDependencies:
postcss: ^8.4.21
dependencies:
camelcase-css: 2.0.1
- postcss: 8.4.25
+ postcss: 8.4.31
- /postcss-lab-function@4.2.1(postcss@8.4.25):
+ /postcss-lab-function@4.2.1(postcss@8.4.31):
resolution: {integrity: sha512-xuXll4isR03CrQsmxyz92LJB2xX9n+pZJ5jE9JgcnmsCammLyKdlzrBin+25dy6wIjfhJpKBAN80gsTlCgRk2w==}
engines: {node: ^12 || ^14 || >=16}
peerDependencies:
postcss: ^8.2
dependencies:
- '@csstools/postcss-progressive-custom-properties': 1.3.0(postcss@8.4.25)
- postcss: 8.4.25
+ '@csstools/postcss-progressive-custom-properties': 1.3.0(postcss@8.4.31)
+ postcss: 8.4.31
postcss-value-parser: 4.2.0
- /postcss-load-config@4.0.1(postcss@8.4.25):
+ /postcss-load-config@4.0.1(postcss@8.4.31):
resolution: {integrity: sha512-vEJIc8RdiBRu3oRAI0ymerOn+7rPuMvRXslTvZUKZonDHFIczxztIyJ1urxM1x9JXEikvpWWTUUqal5j/8QgvA==}
engines: {node: '>= 14'}
peerDependencies:
@@ -16510,10 +16510,10 @@ packages:
optional: true
dependencies:
lilconfig: 2.1.0
- postcss: 8.4.25
+ postcss: 8.4.31
yaml: 2.3.1
- /postcss-loader@6.2.1(postcss@8.4.25)(webpack@5.88.1):
+ /postcss-loader@6.2.1(postcss@8.4.31)(webpack@5.88.1):
resolution: {integrity: sha512-WbbYpmAaKcux/P66bZ40bpWsBucjx/TTgVVzRZ9yUO8yQfVBlameJ0ZGVaPfH64hNSBh63a+ICP5nqOpBA0w+Q==}
engines: {node: '>= 12.13.0'}
peerDependencies:
@@ -16522,37 +16522,37 @@ packages:
dependencies:
cosmiconfig: 7.1.0
klona: 2.0.6
- postcss: 8.4.25
+ postcss: 8.4.31
semver: 7.5.3
webpack: 5.88.1(@swc/core@1.3.71)(esbuild@0.14.54)
- /postcss-logical@5.0.4(postcss@8.4.25):
+ /postcss-logical@5.0.4(postcss@8.4.31):
resolution: {integrity: sha512-RHXxplCeLh9VjinvMrZONq7im4wjWGlRJAqmAVLXyZaXwfDWP73/oq4NdIp+OZwhQUMj0zjqDfM5Fj7qby+B4g==}
engines: {node: ^12 || ^14 || >=16}
peerDependencies:
postcss: ^8.4
dependencies:
- postcss: 8.4.25
+ postcss: 8.4.31
- /postcss-media-minmax@5.0.0(postcss@8.4.25):
+ /postcss-media-minmax@5.0.0(postcss@8.4.31):
resolution: {integrity: sha512-yDUvFf9QdFZTuCUg0g0uNSHVlJ5X1lSzDZjPSFaiCWvjgsvu8vEVxtahPrLMinIDEEGnx6cBe6iqdx5YWz08wQ==}
engines: {node: '>=10.0.0'}
peerDependencies:
postcss: ^8.1.0
dependencies:
- postcss: 8.4.25
+ postcss: 8.4.31
- /postcss-merge-longhand@5.1.7(postcss@8.4.25):
+ /postcss-merge-longhand@5.1.7(postcss@8.4.31):
resolution: {integrity: sha512-YCI9gZB+PLNskrK0BB3/2OzPnGhPkBEwmwhfYk1ilBHYVAZB7/tkTHFBAnCrvBBOmeYyMYw3DMjT55SyxMBzjQ==}
engines: {node: ^10 || ^12 || >=14.0}
peerDependencies:
postcss: ^8.2.15
dependencies:
- postcss: 8.4.25
+ postcss: 8.4.31
postcss-value-parser: 4.2.0
- stylehacks: 5.1.1(postcss@8.4.25)
+ stylehacks: 5.1.1(postcss@8.4.31)
- /postcss-merge-rules@5.1.4(postcss@8.4.25):
+ /postcss-merge-rules@5.1.4(postcss@8.4.31):
resolution: {integrity: sha512-0R2IuYpgU93y9lhVbO/OylTtKMVcHb67zjWIfCiKR9rWL3GUk1677LAqD/BcHizukdZEjT8Ru3oHRoAYoJy44g==}
engines: {node: ^10 || ^12 || >=14.0}
peerDependencies:
@@ -16560,189 +16560,189 @@ packages:
dependencies:
browserslist: 4.21.9
caniuse-api: 3.0.0
- cssnano-utils: 3.1.0(postcss@8.4.25)
- postcss: 8.4.25
+ cssnano-utils: 3.1.0(postcss@8.4.31)
+ postcss: 8.4.31
postcss-selector-parser: 6.0.13
- /postcss-minify-font-values@5.1.0(postcss@8.4.25):
+ /postcss-minify-font-values@5.1.0(postcss@8.4.31):
resolution: {integrity: sha512-el3mYTgx13ZAPPirSVsHqFzl+BBBDrXvbySvPGFnQcTI4iNslrPaFq4muTkLZmKlGk4gyFAYUBMH30+HurREyA==}
engines: {node: ^10 || ^12 || >=14.0}
peerDependencies:
postcss: ^8.2.15
dependencies:
- postcss: 8.4.25
+ postcss: 8.4.31
postcss-value-parser: 4.2.0
- /postcss-minify-gradients@5.1.1(postcss@8.4.25):
+ /postcss-minify-gradients@5.1.1(postcss@8.4.31):
resolution: {integrity: sha512-VGvXMTpCEo4qHTNSa9A0a3D+dxGFZCYwR6Jokk+/3oB6flu2/PnPXAh2x7x52EkY5xlIHLm+Le8tJxe/7TNhzw==}
engines: {node: ^10 || ^12 || >=14.0}
peerDependencies:
postcss: ^8.2.15
dependencies:
colord: 2.9.3
- cssnano-utils: 3.1.0(postcss@8.4.25)
- postcss: 8.4.25
+ cssnano-utils: 3.1.0(postcss@8.4.31)
+ postcss: 8.4.31
postcss-value-parser: 4.2.0
- /postcss-minify-params@5.1.4(postcss@8.4.25):
+ /postcss-minify-params@5.1.4(postcss@8.4.31):
resolution: {integrity: sha512-+mePA3MgdmVmv6g+30rn57USjOGSAyuxUmkfiWpzalZ8aiBkdPYjXWtHuwJGm1v5Ojy0Z0LaSYhHaLJQB0P8Jw==}
engines: {node: ^10 || ^12 || >=14.0}
peerDependencies:
postcss: ^8.2.15
dependencies:
browserslist: 4.21.9
- cssnano-utils: 3.1.0(postcss@8.4.25)
- postcss: 8.4.25
+ cssnano-utils: 3.1.0(postcss@8.4.31)
+ postcss: 8.4.31
postcss-value-parser: 4.2.0
- /postcss-minify-selectors@5.2.1(postcss@8.4.25):
+ /postcss-minify-selectors@5.2.1(postcss@8.4.31):
resolution: {integrity: sha512-nPJu7OjZJTsVUmPdm2TcaiohIwxP+v8ha9NehQ2ye9szv4orirRU3SDdtUmKH+10nzn0bAyOXZ0UEr7OpvLehg==}
engines: {node: ^10 || ^12 || >=14.0}
peerDependencies:
postcss: ^8.2.15
dependencies:
- postcss: 8.4.25
+ postcss: 8.4.31
postcss-selector-parser: 6.0.13
- /postcss-modules-extract-imports@3.0.0(postcss@8.4.25):
+ /postcss-modules-extract-imports@3.0.0(postcss@8.4.31):
resolution: {integrity: sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==}
engines: {node: ^10 || ^12 || >= 14}
peerDependencies:
postcss: ^8.1.0
dependencies:
- postcss: 8.4.25
+ postcss: 8.4.31
- /postcss-modules-local-by-default@4.0.3(postcss@8.4.25):
+ /postcss-modules-local-by-default@4.0.3(postcss@8.4.31):
resolution: {integrity: sha512-2/u2zraspoACtrbFRnTijMiQtb4GW4BvatjaG/bCjYQo8kLTdevCUlwuBHx2sCnSyrI3x3qj4ZK1j5LQBgzmwA==}
engines: {node: ^10 || ^12 || >= 14}
peerDependencies:
postcss: ^8.1.0
dependencies:
- icss-utils: 5.1.0(postcss@8.4.25)
- postcss: 8.4.25
+ icss-utils: 5.1.0(postcss@8.4.31)
+ postcss: 8.4.31
postcss-selector-parser: 6.0.13
postcss-value-parser: 4.2.0
- /postcss-modules-scope@3.0.0(postcss@8.4.25):
+ /postcss-modules-scope@3.0.0(postcss@8.4.31):
resolution: {integrity: sha512-hncihwFA2yPath8oZ15PZqvWGkWf+XUfQgUGamS4LqoP1anQLOsOJw0vr7J7IwLpoY9fatA2qiGUGmuZL0Iqlg==}
engines: {node: ^10 || ^12 || >= 14}
peerDependencies:
postcss: ^8.1.0
dependencies:
- postcss: 8.4.25
+ postcss: 8.4.31
postcss-selector-parser: 6.0.13
- /postcss-modules-values@4.0.0(postcss@8.4.25):
+ /postcss-modules-values@4.0.0(postcss@8.4.31):
resolution: {integrity: sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==}
engines: {node: ^10 || ^12 || >= 14}
peerDependencies:
postcss: ^8.1.0
dependencies:
- icss-utils: 5.1.0(postcss@8.4.25)
- postcss: 8.4.25
+ icss-utils: 5.1.0(postcss@8.4.31)
+ postcss: 8.4.31
- /postcss-nested@6.0.1(postcss@8.4.25):
+ /postcss-nested@6.0.1(postcss@8.4.31):
resolution: {integrity: sha512-mEp4xPMi5bSWiMbsgoPfcP74lsWLHkQbZc3sY+jWYd65CUwXrUaTp0fmNpa01ZcETKlIgUdFN/MpS2xZtqL9dQ==}
engines: {node: '>=12.0'}
peerDependencies:
postcss: ^8.2.14
dependencies:
- postcss: 8.4.25
+ postcss: 8.4.31
postcss-selector-parser: 6.0.13
- /postcss-nesting@10.2.0(postcss@8.4.25):
+ /postcss-nesting@10.2.0(postcss@8.4.31):
resolution: {integrity: sha512-EwMkYchxiDiKUhlJGzWsD9b2zvq/r2SSubcRrgP+jujMXFzqvANLt16lJANC+5uZ6hjI7lpRmI6O8JIl+8l1KA==}
engines: {node: ^12 || ^14 || >=16}
peerDependencies:
postcss: ^8.2
dependencies:
'@csstools/selector-specificity': 2.2.0(postcss-selector-parser@6.0.13)
- postcss: 8.4.25
+ postcss: 8.4.31
postcss-selector-parser: 6.0.13
- /postcss-normalize-charset@5.1.0(postcss@8.4.25):
+ /postcss-normalize-charset@5.1.0(postcss@8.4.31):
resolution: {integrity: sha512-mSgUJ+pd/ldRGVx26p2wz9dNZ7ji6Pn8VWBajMXFf8jk7vUoSrZ2lt/wZR7DtlZYKesmZI680qjr2CeFF2fbUg==}
engines: {node: ^10 || ^12 || >=14.0}
peerDependencies:
postcss: ^8.2.15
dependencies:
- postcss: 8.4.25
+ postcss: 8.4.31
- /postcss-normalize-display-values@5.1.0(postcss@8.4.25):
+ /postcss-normalize-display-values@5.1.0(postcss@8.4.31):
resolution: {integrity: sha512-WP4KIM4o2dazQXWmFaqMmcvsKmhdINFblgSeRgn8BJ6vxaMyaJkwAzpPpuvSIoG/rmX3M+IrRZEz2H0glrQNEA==}
engines: {node: ^10 || ^12 || >=14.0}
peerDependencies:
postcss: ^8.2.15
dependencies:
- postcss: 8.4.25
+ postcss: 8.4.31
postcss-value-parser: 4.2.0
- /postcss-normalize-positions@5.1.1(postcss@8.4.25):
+ /postcss-normalize-positions@5.1.1(postcss@8.4.31):
resolution: {integrity: sha512-6UpCb0G4eofTCQLFVuI3EVNZzBNPiIKcA1AKVka+31fTVySphr3VUgAIULBhxZkKgwLImhzMR2Bw1ORK+37INg==}
engines: {node: ^10 || ^12 || >=14.0}
peerDependencies:
postcss: ^8.2.15
dependencies:
- postcss: 8.4.25
+ postcss: 8.4.31
postcss-value-parser: 4.2.0
- /postcss-normalize-repeat-style@5.1.1(postcss@8.4.25):
+ /postcss-normalize-repeat-style@5.1.1(postcss@8.4.31):
resolution: {integrity: sha512-mFpLspGWkQtBcWIRFLmewo8aC3ImN2i/J3v8YCFUwDnPu3Xz4rLohDO26lGjwNsQxB3YF0KKRwspGzE2JEuS0g==}
engines: {node: ^10 || ^12 || >=14.0}
peerDependencies:
postcss: ^8.2.15
dependencies:
- postcss: 8.4.25
+ postcss: 8.4.31
postcss-value-parser: 4.2.0
- /postcss-normalize-string@5.1.0(postcss@8.4.25):
+ /postcss-normalize-string@5.1.0(postcss@8.4.31):
resolution: {integrity: sha512-oYiIJOf4T9T1N4i+abeIc7Vgm/xPCGih4bZz5Nm0/ARVJ7K6xrDlLwvwqOydvyL3RHNf8qZk6vo3aatiw/go3w==}
engines: {node: ^10 || ^12 || >=14.0}
peerDependencies:
postcss: ^8.2.15
dependencies:
- postcss: 8.4.25
+ postcss: 8.4.31
postcss-value-parser: 4.2.0
- /postcss-normalize-timing-functions@5.1.0(postcss@8.4.25):
+ /postcss-normalize-timing-functions@5.1.0(postcss@8.4.31):
resolution: {integrity: sha512-DOEkzJ4SAXv5xkHl0Wa9cZLF3WCBhF3o1SKVxKQAa+0pYKlueTpCgvkFAHfk+Y64ezX9+nITGrDZeVGgITJXjg==}
engines: {node: ^10 || ^12 || >=14.0}
peerDependencies:
postcss: ^8.2.15
dependencies:
- postcss: 8.4.25
+ postcss: 8.4.31
postcss-value-parser: 4.2.0
- /postcss-normalize-unicode@5.1.1(postcss@8.4.25):
+ /postcss-normalize-unicode@5.1.1(postcss@8.4.31):
resolution: {integrity: sha512-qnCL5jzkNUmKVhZoENp1mJiGNPcsJCs1aaRmURmeJGES23Z/ajaln+EPTD+rBeNkSryI+2WTdW+lwcVdOikrpA==}
engines: {node: ^10 || ^12 || >=14.0}
peerDependencies:
postcss: ^8.2.15
dependencies:
browserslist: 4.21.9
- postcss: 8.4.25
+ postcss: 8.4.31
postcss-value-parser: 4.2.0
- /postcss-normalize-url@5.1.0(postcss@8.4.25):
+ /postcss-normalize-url@5.1.0(postcss@8.4.31):
resolution: {integrity: sha512-5upGeDO+PVthOxSmds43ZeMeZfKH+/DKgGRD7TElkkyS46JXAUhMzIKiCa7BabPeIy3AQcTkXwVVN7DbqsiCew==}
engines: {node: ^10 || ^12 || >=14.0}
peerDependencies:
postcss: ^8.2.15
dependencies:
normalize-url: 6.1.0
- postcss: 8.4.25
+ postcss: 8.4.31
postcss-value-parser: 4.2.0
- /postcss-normalize-whitespace@5.1.1(postcss@8.4.25):
+ /postcss-normalize-whitespace@5.1.1(postcss@8.4.31):
resolution: {integrity: sha512-83ZJ4t3NUDETIHTa3uEg6asWjSBYL5EdkVB0sDncx9ERzOKBVJIUeDO9RyA9Zwtig8El1d79HBp0JEi8wvGQnA==}
engines: {node: ^10 || ^12 || >=14.0}
peerDependencies:
postcss: ^8.2.15
dependencies:
- postcss: 8.4.25
+ postcss: 8.4.31
postcss-value-parser: 4.2.0
- /postcss-normalize@10.0.1(browserslist@4.21.9)(postcss@8.4.25):
+ /postcss-normalize@10.0.1(browserslist@4.21.9)(postcss@8.4.31):
resolution: {integrity: sha512-+5w18/rDev5mqERcG3W5GZNMJa1eoYYNGo8gB7tEwaos0ajk3ZXAI4mHGcNT47NE+ZnZD1pEpUOFLvltIwmeJA==}
engines: {node: '>= 12'}
peerDependencies:
@@ -16751,120 +16751,120 @@ packages:
dependencies:
'@csstools/normalize.css': 12.0.0
browserslist: 4.21.9
- postcss: 8.4.25
- postcss-browser-comments: 4.0.0(browserslist@4.21.9)(postcss@8.4.25)
+ postcss: 8.4.31
+ postcss-browser-comments: 4.0.0(browserslist@4.21.9)(postcss@8.4.31)
sanitize.css: 13.0.0
- /postcss-opacity-percentage@1.1.3(postcss@8.4.25):
+ /postcss-opacity-percentage@1.1.3(postcss@8.4.31):
resolution: {integrity: sha512-An6Ba4pHBiDtyVpSLymUUERMo2cU7s+Obz6BTrS+gxkbnSBNKSuD0AVUc+CpBMrpVPKKfoVz0WQCX+Tnst0i4A==}
engines: {node: ^12 || ^14 || >=16}
peerDependencies:
postcss: ^8.2
dependencies:
- postcss: 8.4.25
+ postcss: 8.4.31
- /postcss-ordered-values@5.1.3(postcss@8.4.25):
+ /postcss-ordered-values@5.1.3(postcss@8.4.31):
resolution: {integrity: sha512-9UO79VUhPwEkzbb3RNpqqghc6lcYej1aveQteWY+4POIwlqkYE21HKWaLDF6lWNuqCobEAyTovVhtI32Rbv2RQ==}
engines: {node: ^10 || ^12 || >=14.0}
peerDependencies:
postcss: ^8.2.15
dependencies:
- cssnano-utils: 3.1.0(postcss@8.4.25)
- postcss: 8.4.25
+ cssnano-utils: 3.1.0(postcss@8.4.31)
+ postcss: 8.4.31
postcss-value-parser: 4.2.0
- /postcss-overflow-shorthand@3.0.4(postcss@8.4.25):
+ /postcss-overflow-shorthand@3.0.4(postcss@8.4.31):
resolution: {integrity: sha512-otYl/ylHK8Y9bcBnPLo3foYFLL6a6Ak+3EQBPOTR7luMYCOsiVTUk1iLvNf6tVPNGXcoL9Hoz37kpfriRIFb4A==}
engines: {node: ^12 || ^14 || >=16}
peerDependencies:
postcss: ^8.2
dependencies:
- postcss: 8.4.25
+ postcss: 8.4.31
postcss-value-parser: 4.2.0
- /postcss-page-break@3.0.4(postcss@8.4.25):
+ /postcss-page-break@3.0.4(postcss@8.4.31):
resolution: {integrity: sha512-1JGu8oCjVXLa9q9rFTo4MbeeA5FMe00/9C7lN4va606Rdb+HkxXtXsmEDrIraQ11fGz/WvKWa8gMuCKkrXpTsQ==}
peerDependencies:
postcss: ^8
dependencies:
- postcss: 8.4.25
+ postcss: 8.4.31
- /postcss-place@7.0.5(postcss@8.4.25):
+ /postcss-place@7.0.5(postcss@8.4.31):
resolution: {integrity: sha512-wR8igaZROA6Z4pv0d+bvVrvGY4GVHihBCBQieXFY3kuSuMyOmEnnfFzHl/tQuqHZkfkIVBEbDvYcFfHmpSet9g==}
engines: {node: ^12 || ^14 || >=16}
peerDependencies:
postcss: ^8.2
dependencies:
- postcss: 8.4.25
+ postcss: 8.4.31
postcss-value-parser: 4.2.0
- /postcss-preset-env@7.8.3(postcss@8.4.25):
+ /postcss-preset-env@7.8.3(postcss@8.4.31):
resolution: {integrity: sha512-T1LgRm5uEVFSEF83vHZJV2z19lHg4yJuZ6gXZZkqVsqv63nlr6zabMH3l4Pc01FQCyfWVrh2GaUeCVy9Po+Aag==}
engines: {node: ^12 || ^14 || >=16}
peerDependencies:
postcss: ^8.2
dependencies:
- '@csstools/postcss-cascade-layers': 1.1.1(postcss@8.4.25)
- '@csstools/postcss-color-function': 1.1.1(postcss@8.4.25)
- '@csstools/postcss-font-format-keywords': 1.0.1(postcss@8.4.25)
- '@csstools/postcss-hwb-function': 1.0.2(postcss@8.4.25)
- '@csstools/postcss-ic-unit': 1.0.1(postcss@8.4.25)
- '@csstools/postcss-is-pseudo-class': 2.0.7(postcss@8.4.25)
- '@csstools/postcss-nested-calc': 1.0.0(postcss@8.4.25)
- '@csstools/postcss-normalize-display-values': 1.0.1(postcss@8.4.25)
- '@csstools/postcss-oklab-function': 1.1.1(postcss@8.4.25)
- '@csstools/postcss-progressive-custom-properties': 1.3.0(postcss@8.4.25)
- '@csstools/postcss-stepped-value-functions': 1.0.1(postcss@8.4.25)
- '@csstools/postcss-text-decoration-shorthand': 1.0.0(postcss@8.4.25)
- '@csstools/postcss-trigonometric-functions': 1.0.2(postcss@8.4.25)
- '@csstools/postcss-unset-value': 1.0.2(postcss@8.4.25)
- autoprefixer: 10.4.14(postcss@8.4.25)
+ '@csstools/postcss-cascade-layers': 1.1.1(postcss@8.4.31)
+ '@csstools/postcss-color-function': 1.1.1(postcss@8.4.31)
+ '@csstools/postcss-font-format-keywords': 1.0.1(postcss@8.4.31)
+ '@csstools/postcss-hwb-function': 1.0.2(postcss@8.4.31)
+ '@csstools/postcss-ic-unit': 1.0.1(postcss@8.4.31)
+ '@csstools/postcss-is-pseudo-class': 2.0.7(postcss@8.4.31)
+ '@csstools/postcss-nested-calc': 1.0.0(postcss@8.4.31)
+ '@csstools/postcss-normalize-display-values': 1.0.1(postcss@8.4.31)
+ '@csstools/postcss-oklab-function': 1.1.1(postcss@8.4.31)
+ '@csstools/postcss-progressive-custom-properties': 1.3.0(postcss@8.4.31)
+ '@csstools/postcss-stepped-value-functions': 1.0.1(postcss@8.4.31)
+ '@csstools/postcss-text-decoration-shorthand': 1.0.0(postcss@8.4.31)
+ '@csstools/postcss-trigonometric-functions': 1.0.2(postcss@8.4.31)
+ '@csstools/postcss-unset-value': 1.0.2(postcss@8.4.31)
+ autoprefixer: 10.4.14(postcss@8.4.31)
browserslist: 4.21.9
- css-blank-pseudo: 3.0.3(postcss@8.4.25)
- css-has-pseudo: 3.0.4(postcss@8.4.25)
- css-prefers-color-scheme: 6.0.3(postcss@8.4.25)
+ css-blank-pseudo: 3.0.3(postcss@8.4.31)
+ css-has-pseudo: 3.0.4(postcss@8.4.31)
+ css-prefers-color-scheme: 6.0.3(postcss@8.4.31)
cssdb: 7.6.0
- postcss: 8.4.25
- postcss-attribute-case-insensitive: 5.0.2(postcss@8.4.25)
- postcss-clamp: 4.1.0(postcss@8.4.25)
- postcss-color-functional-notation: 4.2.4(postcss@8.4.25)
- postcss-color-hex-alpha: 8.0.4(postcss@8.4.25)
- postcss-color-rebeccapurple: 7.1.1(postcss@8.4.25)
- postcss-custom-media: 8.0.2(postcss@8.4.25)
- postcss-custom-properties: 12.1.11(postcss@8.4.25)
- postcss-custom-selectors: 6.0.3(postcss@8.4.25)
- postcss-dir-pseudo-class: 6.0.5(postcss@8.4.25)
- postcss-double-position-gradients: 3.1.2(postcss@8.4.25)
- postcss-env-function: 4.0.6(postcss@8.4.25)
- postcss-focus-visible: 6.0.4(postcss@8.4.25)
- postcss-focus-within: 5.0.4(postcss@8.4.25)
- postcss-font-variant: 5.0.0(postcss@8.4.25)
- postcss-gap-properties: 3.0.5(postcss@8.4.25)
- postcss-image-set-function: 4.0.7(postcss@8.4.25)
- postcss-initial: 4.0.1(postcss@8.4.25)
- postcss-lab-function: 4.2.1(postcss@8.4.25)
- postcss-logical: 5.0.4(postcss@8.4.25)
- postcss-media-minmax: 5.0.0(postcss@8.4.25)
- postcss-nesting: 10.2.0(postcss@8.4.25)
- postcss-opacity-percentage: 1.1.3(postcss@8.4.25)
- postcss-overflow-shorthand: 3.0.4(postcss@8.4.25)
- postcss-page-break: 3.0.4(postcss@8.4.25)
- postcss-place: 7.0.5(postcss@8.4.25)
- postcss-pseudo-class-any-link: 7.1.6(postcss@8.4.25)
- postcss-replace-overflow-wrap: 4.0.0(postcss@8.4.25)
- postcss-selector-not: 6.0.1(postcss@8.4.25)
+ postcss: 8.4.31
+ postcss-attribute-case-insensitive: 5.0.2(postcss@8.4.31)
+ postcss-clamp: 4.1.0(postcss@8.4.31)
+ postcss-color-functional-notation: 4.2.4(postcss@8.4.31)
+ postcss-color-hex-alpha: 8.0.4(postcss@8.4.31)
+ postcss-color-rebeccapurple: 7.1.1(postcss@8.4.31)
+ postcss-custom-media: 8.0.2(postcss@8.4.31)
+ postcss-custom-properties: 12.1.11(postcss@8.4.31)
+ postcss-custom-selectors: 6.0.3(postcss@8.4.31)
+ postcss-dir-pseudo-class: 6.0.5(postcss@8.4.31)
+ postcss-double-position-gradients: 3.1.2(postcss@8.4.31)
+ postcss-env-function: 4.0.6(postcss@8.4.31)
+ postcss-focus-visible: 6.0.4(postcss@8.4.31)
+ postcss-focus-within: 5.0.4(postcss@8.4.31)
+ postcss-font-variant: 5.0.0(postcss@8.4.31)
+ postcss-gap-properties: 3.0.5(postcss@8.4.31)
+ postcss-image-set-function: 4.0.7(postcss@8.4.31)
+ postcss-initial: 4.0.1(postcss@8.4.31)
+ postcss-lab-function: 4.2.1(postcss@8.4.31)
+ postcss-logical: 5.0.4(postcss@8.4.31)
+ postcss-media-minmax: 5.0.0(postcss@8.4.31)
+ postcss-nesting: 10.2.0(postcss@8.4.31)
+ postcss-opacity-percentage: 1.1.3(postcss@8.4.31)
+ postcss-overflow-shorthand: 3.0.4(postcss@8.4.31)
+ postcss-page-break: 3.0.4(postcss@8.4.31)
+ postcss-place: 7.0.5(postcss@8.4.31)
+ postcss-pseudo-class-any-link: 7.1.6(postcss@8.4.31)
+ postcss-replace-overflow-wrap: 4.0.0(postcss@8.4.31)
+ postcss-selector-not: 6.0.1(postcss@8.4.31)
postcss-value-parser: 4.2.0
- /postcss-pseudo-class-any-link@7.1.6(postcss@8.4.25):
+ /postcss-pseudo-class-any-link@7.1.6(postcss@8.4.31):
resolution: {integrity: sha512-9sCtZkO6f/5ML9WcTLcIyV1yz9D1rf0tWc+ulKcvV30s0iZKS/ONyETvoWsr6vnrmW+X+KmuK3gV/w5EWnT37w==}
engines: {node: ^12 || ^14 || >=16}
peerDependencies:
postcss: ^8.2
dependencies:
- postcss: 8.4.25
+ postcss: 8.4.31
postcss-selector-parser: 6.0.13
- /postcss-reduce-initial@5.1.2(postcss@8.4.25):
+ /postcss-reduce-initial@5.1.2(postcss@8.4.31):
resolution: {integrity: sha512-dE/y2XRaqAi6OvjzD22pjTUQ8eOfc6m/natGHgKFBK9DxFmIm69YmaRVQrGgFlEfc1HePIurY0TmDeROK05rIg==}
engines: {node: ^10 || ^12 || >=14.0}
peerDependencies:
@@ -16872,31 +16872,31 @@ packages:
dependencies:
browserslist: 4.21.9
caniuse-api: 3.0.0
- postcss: 8.4.25
+ postcss: 8.4.31
- /postcss-reduce-transforms@5.1.0(postcss@8.4.25):
+ /postcss-reduce-transforms@5.1.0(postcss@8.4.31):
resolution: {integrity: sha512-2fbdbmgir5AvpW9RLtdONx1QoYG2/EtqpNQbFASDlixBbAYuTcJ0dECwlqNqH7VbaUnEnh8SrxOe2sRIn24XyQ==}
engines: {node: ^10 || ^12 || >=14.0}
peerDependencies:
postcss: ^8.2.15
dependencies:
- postcss: 8.4.25
+ postcss: 8.4.31
postcss-value-parser: 4.2.0
- /postcss-replace-overflow-wrap@4.0.0(postcss@8.4.25):
+ /postcss-replace-overflow-wrap@4.0.0(postcss@8.4.31):
resolution: {integrity: sha512-KmF7SBPphT4gPPcKZc7aDkweHiKEEO8cla/GjcBK+ckKxiZslIu3C4GCRW3DNfL0o7yW7kMQu9xlZ1kXRXLXtw==}
peerDependencies:
postcss: ^8.0.3
dependencies:
- postcss: 8.4.25
+ postcss: 8.4.31
- /postcss-selector-not@6.0.1(postcss@8.4.25):
+ /postcss-selector-not@6.0.1(postcss@8.4.31):
resolution: {integrity: sha512-1i9affjAe9xu/y9uqWH+tD4r6/hDaXJruk8xn2x1vzxC2U3J3LKO3zJW4CyxlNhA56pADJ/djpEwpH1RClI2rQ==}
engines: {node: ^12 || ^14 || >=16}
peerDependencies:
postcss: ^8.2
dependencies:
- postcss: 8.4.25
+ postcss: 8.4.31
postcss-selector-parser: 6.0.13
/postcss-selector-parser@6.0.13:
@@ -16906,23 +16906,23 @@ packages:
cssesc: 3.0.0
util-deprecate: 1.0.2
- /postcss-svgo@5.1.0(postcss@8.4.25):
+ /postcss-svgo@5.1.0(postcss@8.4.31):
resolution: {integrity: sha512-D75KsH1zm5ZrHyxPakAxJWtkyXew5qwS70v56exwvw542d9CRtTo78K0WeFxZB4G7JXKKMbEZtZayTGdIky/eA==}
engines: {node: ^10 || ^12 || >=14.0}
peerDependencies:
postcss: ^8.2.15
dependencies:
- postcss: 8.4.25
+ postcss: 8.4.31
postcss-value-parser: 4.2.0
svgo: 2.8.0
- /postcss-unique-selectors@5.1.1(postcss@8.4.25):
+ /postcss-unique-selectors@5.1.1(postcss@8.4.31):
resolution: {integrity: sha512-5JiODlELrz8L2HwxfPnhOWZYWDxVHWL83ufOv84NrcgipI7TaeRsatAhK4Tr2/ZiYldpK/wBvw5BD3qfaK96GA==}
engines: {node: ^10 || ^12 || >=14.0}
peerDependencies:
postcss: ^8.2.15
dependencies:
- postcss: 8.4.25
+ postcss: 8.4.31
postcss-selector-parser: 6.0.13
/postcss-value-parser@4.2.0:
@@ -16935,8 +16935,8 @@ packages:
picocolors: 0.2.1
source-map: 0.6.1
- /postcss@8.4.25:
- resolution: {integrity: sha512-7taJ/8t2av0Z+sQEvNzCkpDynl0tX3uJMCODi6nT3PfASC7dYCWV9aQ+uiCf+KBD4SEFcu+GvJdGdwzQ6OSjCw==}
+ /postcss@8.4.31:
+ resolution: {integrity: sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==}
engines: {node: ^10 || ^12 || >=14}
dependencies:
nanoid: 3.3.6
@@ -17791,11 +17791,11 @@ packages:
jest-resolve: 27.5.1
jest-watch-typeahead: 1.1.0(jest@27.5.1)
mini-css-extract-plugin: 2.7.6(webpack@5.88.1)
- postcss: 8.4.25
- postcss-flexbugs-fixes: 5.0.2(postcss@8.4.25)
- postcss-loader: 6.2.1(postcss@8.4.25)(webpack@5.88.1)
- postcss-normalize: 10.0.1(browserslist@4.21.9)(postcss@8.4.25)
- postcss-preset-env: 7.8.3(postcss@8.4.25)
+ postcss: 8.4.31
+ postcss-flexbugs-fixes: 5.0.2(postcss@8.4.31)
+ postcss-loader: 6.2.1(postcss@8.4.31)(webpack@5.88.1)
+ postcss-normalize: 10.0.1(browserslist@4.21.9)(postcss@8.4.31)
+ postcss-preset-env: 7.8.3(postcss@8.4.31)
prompts: 2.4.2
react: 18.2.0
react-app-polyfill: 3.0.0
@@ -19238,14 +19238,14 @@ packages:
inline-style-parser: 0.1.1
dev: false
- /stylehacks@5.1.1(postcss@8.4.25):
+ /stylehacks@5.1.1(postcss@8.4.31):
resolution: {integrity: sha512-sBpcd5Hx7G6seo7b1LkpttvTz7ikD0LlH5RmdcBNb6fFR0Fl7LQwHDFr300q4cwUqi+IYrFGmsIHieMBfnN/Bw==}
engines: {node: ^10 || ^12 || >=14.0}
peerDependencies:
postcss: ^8.2.15
dependencies:
browserslist: 4.21.9
- postcss: 8.4.25
+ postcss: 8.4.31
postcss-selector-parser: 6.0.13
/stylis@4.2.0:
@@ -19391,11 +19391,11 @@ packages:
normalize-path: 3.0.0
object-hash: 3.0.0
picocolors: 1.0.0
- postcss: 8.4.25
- postcss-import: 15.1.0(postcss@8.4.25)
- postcss-js: 4.0.1(postcss@8.4.25)
- postcss-load-config: 4.0.1(postcss@8.4.25)
- postcss-nested: 6.0.1(postcss@8.4.25)
+ postcss: 8.4.31
+ postcss-import: 15.1.0(postcss@8.4.31)
+ postcss-js: 4.0.1(postcss@8.4.31)
+ postcss-load-config: 4.0.1(postcss@8.4.31)
+ postcss-nested: 6.0.1(postcss@8.4.31)
postcss-selector-parser: 6.0.13
postcss-value-parser: 4.2.0
resolve: 1.22.2
@@ -20926,9 +20926,9 @@ packages:
use-sync-external-store: 1.2.0(react@18.2.0)
dev: false
- github.com/theopensystemslab/planx-core/d92224b(@types/react@18.2.20):
- resolution: {tarball: https://codeload.github.com/theopensystemslab/planx-core/tar.gz/d92224b}
- id: github.com/theopensystemslab/planx-core/d92224b
+ git/github.com+theopensystemslab/planx-core/d92224b(@types/react@18.2.20):
+ resolution: {commit: d92224b, repo: git@github.com:theopensystemslab/planx-core.git, type: git}
+ id: git@github.com+theopensystemslab/planx-core/d92224b
name: '@opensystemslab/planx-core'
version: 1.0.0
prepare: true
From 2ce327edf582b1270ff4a0c4bc5f45d1c935d807 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Dafydd=20Ll=C5=B7r=20Pearson?=
Date: Fri, 6 Oct 2023 15:59:15 +0100
Subject: [PATCH 7/8] dp/fix-date-handling (#2285)
---
.../webhooks/service/lowcalSessionEvents/schema.ts | 2 +-
.../webhooks/service/paymentRequestEvents/schema.ts | 2 +-
api.planx.uk/shared/middleware/validate.ts | 9 ++++++++-
3 files changed, 10 insertions(+), 3 deletions(-)
diff --git a/api.planx.uk/modules/webhooks/service/lowcalSessionEvents/schema.ts b/api.planx.uk/modules/webhooks/service/lowcalSessionEvents/schema.ts
index 53e73f0b06..2f35683b0e 100644
--- a/api.planx.uk/modules/webhooks/service/lowcalSessionEvents/schema.ts
+++ b/api.planx.uk/modules/webhooks/service/lowcalSessionEvents/schema.ts
@@ -4,7 +4,7 @@ import { ScheduledEventResponse } from "../../../../hasura/metadata";
export const createSessionEventSchema = z.object({
body: z.object({
- createdAt: z.string().transform((val) => new Date(val)),
+ createdAt: z.string().pipe(z.coerce.date()),
payload: z.object({
sessionId: z.string(),
}),
diff --git a/api.planx.uk/modules/webhooks/service/paymentRequestEvents/schema.ts b/api.planx.uk/modules/webhooks/service/paymentRequestEvents/schema.ts
index 5b7a48404c..042ea05f71 100644
--- a/api.planx.uk/modules/webhooks/service/paymentRequestEvents/schema.ts
+++ b/api.planx.uk/modules/webhooks/service/paymentRequestEvents/schema.ts
@@ -4,7 +4,7 @@ import { ScheduledEventResponse } from "../../../../hasura/metadata";
export const createPaymentEventSchema = z.object({
body: z.object({
- createdAt: z.string().transform((val) => new Date(val)),
+ createdAt: z.string().pipe(z.coerce.date()),
payload: z.object({
paymentRequestId: z.string(),
}),
diff --git a/api.planx.uk/shared/middleware/validate.ts b/api.planx.uk/shared/middleware/validate.ts
index 854a37938e..b2b740df00 100644
--- a/api.planx.uk/shared/middleware/validate.ts
+++ b/api.planx.uk/shared/middleware/validate.ts
@@ -13,11 +13,18 @@ export const validate =
next: NextFunction,
) => {
try {
- schema.parse({
+ const parsedReq = schema.parse({
params: req.params,
body: req.body,
query: req.query,
});
+
+ // Assign parsed values to the request object
+ // Required for schemas to transform or coerce raw requests
+ req.params = parsedReq.params;
+ req.body = parsedReq.body;
+ req.query = parsedReq.query;
+
return next();
} catch (error) {
console.error(error);
From 380f27e35758148a022f7de2bd7f30290a7aadf3 Mon Sep 17 00:00:00 2001
From: Jessica McInchak
Date: Mon, 9 Oct 2023 07:51:33 +0100
Subject: [PATCH 8/8] feat: add option to use "Pay without pay" and show error
in cases of undefined fee (#2284)
---
.../src/@planx/components/Pay/Editor.tsx | 39 ++--
.../@planx/components/Pay/Public/Confirm.tsx | 120 +++++++-----
.../components/Pay/Public/Pay.stories.tsx | 14 ++
.../@planx/components/Pay/Public/Pay.test.tsx | 183 ++++++++++++++++--
.../src/@planx/components/Pay/Public/Pay.tsx | 50 +++--
.../src/@planx/components/Pay/model.ts | 2 +
6 files changed, 310 insertions(+), 98 deletions(-)
diff --git a/editor.planx.uk/src/@planx/components/Pay/Editor.tsx b/editor.planx.uk/src/@planx/components/Pay/Editor.tsx
index e37945e05b..b6fac2b43e 100644
--- a/editor.planx.uk/src/@planx/components/Pay/Editor.tsx
+++ b/editor.planx.uk/src/@planx/components/Pay/Editor.tsx
@@ -31,6 +31,7 @@ function Component(props: any) {
`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 ||
@@ -113,20 +114,30 @@ function Component(props: any) {
/>
-
-
- {
- formik.setFieldValue(
- "allowInviteToPay",
- !formik.values.allowInviteToPay,
- );
- }}
- >
- Allow applicants to invite someone else to pay
-
-
+ {
+ formik.setFieldValue("hidePay", !formik.values.hidePay);
+ }}
+ style={{ width: "100%" }}
+ >
+ Hide the pay buttons and show fee for information only
+
+
+
+
+ {
+ formik.setFieldValue(
+ "allowInviteToPay",
+ !formik.values.allowInviteToPay,
+ );
+ }}
+ style={{ width: "100%" }}
+ >
+ Allow applicants to invite someone else to pay
+
{formik.values.allowInviteToPay ? (
<>
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 a482eaa863..2b5c6efa93 100644
--- a/editor.planx.uk/src/@planx/components/Pay/Public/Confirm.tsx
+++ b/editor.planx.uk/src/@planx/components/Pay/Public/Confirm.tsx
@@ -15,6 +15,7 @@ import ReactMarkdownOrHtml from "ui/ReactMarkdownOrHtml";
import { formattedPriceWithCurrencySymbol } from "../model";
import InviteToPayForm, { InviteToPayFormProps } from "./InviteToPayForm";
+import { PAY_API_ERROR_UNSUPPORTED_TEAM } from "./Pay";
export interface Props {
title?: string;
@@ -35,6 +36,7 @@ export interface Props {
onConfirm: () => void;
error?: string;
hideFeeBanner?: boolean;
+ hidePay?: boolean;
}
interface PayBodyProps extends Props {
@@ -60,53 +62,9 @@ const PayBody: React.FC = (props) => {
const path = useStore((state) => state.path);
const isSaveReturn = path === ApplicationPath.SaveAndReturn;
- return (
- <>
- {!props.error ? (
-
-
-
- {props.instructionsTitle || "How to pay"}
-
- 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.
`
- }
- openLinksOnNewTab
- />
-
- {props.showInviteToPay && (
- <>
-
- >
- )}
- {isSaveReturn && }
-
-
- ) : (
+ if (props.error) {
+ if (props.error.startsWith(PAY_API_ERROR_UNSUPPORTED_TEAM)) {
+ return (
@@ -118,8 +76,68 @@ const PayBody: React.FC = (props) => {
- )}
- >
+ );
+ } else {
+ return (
+
+
+
+ {props.error}
+
+
+ This error has been logged and our team will see it soon. You can
+ safely close this tab and try resuming again soon by returning to
+ this URL.
+
+
+
+ );
+ }
+ }
+
+ return (
+
+
+
+ {props.instructionsTitle || "How to pay"}
+
+ 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.
`
+ }
+ openLinksOnNewTab
+ />
+
+ {!props.hidePay && props.showInviteToPay && (
+ <>
+
+ >
+ )}
+ {isSaveReturn && }
+
+
);
};
@@ -176,7 +194,9 @@ export default function Confirm(props: Props) {
className="marginBottom"
component="span"
>
- {formattedPriceWithCurrencySymbol(props.fee)}
+ {isNaN(props.fee)
+ ? "Unknown"
+ : formattedPriceWithCurrencySymbol(props.fee)}
{
- const handleSubmit = jest.fn();
+const flowWithUndefinedFee: Store.flow = {
+ _root: {
+ edges: ["setValue", "pay"],
+ },
+ setValue: {
+ type: TYPES.SetValue,
+ edges: ["pay"],
+ data: {
+ fn: "application.fee.payable",
+ val: "0",
+ },
+ },
+ pay: {
+ type: TYPES.Pay,
+ data: {
+ fn: "application.fee.typo",
+ },
+ },
+};
- // if no props.fn, then fee defaults to 0
- setup();
+const flowWithZeroFee: Store.flow = {
+ _root: {
+ edges: ["setValue", "pay"],
+ },
+ setValue: {
+ type: TYPES.SetValue,
+ edges: ["pay"],
+ data: {
+ fn: "application.fee.payable",
+ val: "0",
+ },
+ },
+ pay: {
+ type: TYPES.Pay,
+ data: {
+ fn: "application.fee.payable",
+ },
+ },
+};
- // handleSubmit is still called to set auto = true so Pay isn't seen in card sequence
- expect(handleSubmit).toHaveBeenCalled();
-});
+// Mimic having passed setValue to reach Pay
+const breadcrumbs: Breadcrumbs = {
+ setValue: {
+ auto: true,
+ data: {
+ "application.fee.payable": ["0"],
+ },
+ },
+};
+
+describe("Pay component when fee is undefined or £0", () => {
+ beforeEach(() => {
+ getState().resetPreview();
+ });
+
+ it("Shows an error if fee is undefined", () => {
+ const handleSubmit = jest.fn();
+
+ setState({ flow: flowWithUndefinedFee, breadcrumbs: breadcrumbs });
+ expect(getState().computePassport()).toEqual({
+ data: { "application.fee.payable": ["0"] },
+ });
+
+ setup();
+
+ // handleSubmit has NOT been called (not skipped), Pay shows error instead
+ expect(handleSubmit).not.toHaveBeenCalled();
+ expect(
+ screen.getByText("We are unable to calculate your fee right now"),
+ ).toBeInTheDocument();
+ expect(screen.queryByText("Continue")).not.toBeInTheDocument();
+ });
+
+ it("Skips pay if fee = 0", () => {
+ const handleSubmit = jest.fn();
+
+ setState({ flow: flowWithZeroFee, breadcrumbs: breadcrumbs });
+ expect(getState().computePassport()).toEqual({
+ data: { "application.fee.payable": ["0"] },
+ });
-it("should not have any accessibility violations", async () => {
- const handleSubmit = jest.fn();
- const { container } = setup();
- const results = await axe(container);
- expect(results).toHaveNoViolations();
+ setup();
+
+ // handleSubmit is called to auto-answer Pay (aka "skip" in card sequence)
+ expect(handleSubmit).toHaveBeenCalled();
+ });
});
const defaultProps = {
@@ -89,7 +161,8 @@ describe("Confirm component without inviteToPay", () => {
it("displays an error and continue-with-testing button if Pay is not enabled for this team", async () => {
const handleSubmit = jest.fn();
- const errorMessage = "No pay token found for this team!";
+ const errorMessage =
+ "GOV.UK Pay is not enabled for this local authority (testing)";
const { user } = setup(
{
expect(handleSubmit).toHaveBeenCalled();
});
- it("should not have any accessibility violations", async () => {
- const { container } = setup();
- const results = await axe(container);
- expect(results).toHaveNoViolations();
- });
-
it("displays the Save/Resume option if the application path requires it", () => {
act(() =>
setState({
@@ -141,6 +208,12 @@ describe("Confirm component without inviteToPay", () => {
expect(screen.queryByText(saveButtonText)).not.toBeInTheDocument();
expect(screen.queryByText(resumeButtonText)).not.toBeInTheDocument();
});
+
+ it("should not have any accessibility violations", async () => {
+ const { container } = setup();
+ const results = await axe(container);
+ expect(results).toHaveNoViolations();
+ });
});
describe("Confirm component with inviteToPay", () => {
@@ -293,3 +366,71 @@ describe("Confirm component with inviteToPay", () => {
expect(results).toHaveNoViolations();
});
});
+
+describe("Confirm component in information-only mode", () => {
+ beforeAll(() => (initialState = getState()));
+ afterEach(() => act(() => setState(initialState)));
+
+ it("renders correctly", async () => {
+ const handleSubmit = jest.fn();
+ const { user } = setup(
+ ,
+ );
+
+ expect(screen.getByRole("heading", { level: 1 })).toHaveTextContent(
+ "Pay for your application",
+ );
+ expect(screen.getByRole("heading", { level: 2 })).toHaveTextContent(
+ "The fee is",
+ );
+ expect(screen.getByRole("heading", { level: 3 })).toHaveTextContent(
+ "How to pay",
+ );
+
+ expect(screen.getByRole("button")).toHaveTextContent("Continue");
+ expect(screen.getByRole("button")).not.toHaveTextContent("Pay");
+
+ await user.click(screen.getByText("Continue"));
+ expect(handleSubmit).toHaveBeenCalled();
+ });
+
+ it("renders correctly when inviteToPay is also toggled on by an editor", async () => {
+ const handleSubmit = jest.fn();
+ const { user } = setup(
+ ,
+ );
+
+ expect(screen.getByRole("heading", { level: 1 })).toHaveTextContent(
+ "Pay for your application",
+ );
+ expect(screen.getByRole("heading", { level: 2 })).toHaveTextContent(
+ "The fee is",
+ );
+ expect(screen.getByRole("heading", { level: 3 })).toHaveTextContent(
+ "How to pay",
+ );
+
+ expect(screen.getByRole("button")).toHaveTextContent("Continue");
+ expect(screen.getByRole("button")).not.toHaveTextContent("Pay");
+ expect(screen.getByRole("button")).not.toHaveTextContent(
+ "Invite someone else to pay for this application",
+ );
+
+ await user.click(screen.getByText("Continue"));
+ expect(handleSubmit).toHaveBeenCalled();
+ });
+
+ it("should not have any accessibility violations", async () => {
+ const handleSubmit = jest.fn();
+ const { container } = setup(
+ ,
+ );
+ const results = await axe(container);
+ expect(results).toHaveNoViolations();
+ });
+});
diff --git a/editor.planx.uk/src/@planx/components/Pay/Public/Pay.tsx b/editor.planx.uk/src/@planx/components/Pay/Public/Pay.tsx
index c843b3d5dc..6925de00fd 100644
--- a/editor.planx.uk/src/@planx/components/Pay/Public/Pay.tsx
+++ b/editor.planx.uk/src/@planx/components/Pay/Public/Pay.tsx
@@ -30,9 +30,11 @@ type ComponentState =
| { status: "fetching_payment"; displayText?: string }
| { status: "retry" }
| { status: "success"; displayText?: string }
- | { status: "unsupported_team" };
+ | { status: "unsupported_team" }
+ | { status: "undefined_fee" };
enum Action {
+ NoFeeFound,
NoPaymentFound,
IncompletePaymentFound,
IncompletePaymentConfirmed,
@@ -42,6 +44,9 @@ enum Action {
Success,
}
+export const PAY_API_ERROR_UNSUPPORTED_TEAM =
+ "GOV.UK Pay is not enabled for this local authority";
+
function Component(props: Props) {
const [
flowId,
@@ -67,6 +72,8 @@ function Component(props: Props) {
// Handles UI states
const reducer = (_state: ComponentState, action: Action): ComponentState => {
switch (action) {
+ case Action.NoFeeFound:
+ return { status: "undefined_fee" };
case Action.NoPaymentFound:
return { status: "init" };
case Action.IncompletePaymentFound:
@@ -101,11 +108,18 @@ function Component(props: Props) {
const handleError = useErrorHandler();
useEffect(() => {
- if (isNaN(fee) || fee <= 0) {
- // skip the pay component because there's no fee to charge
+ // Auto-skip component when fee=0
+ if (fee <= 0) {
return props.handleSubmit({ auto: true });
}
+ // If props.fn is undefined, display & log an error
+ if (isNaN(fee)) {
+ dispatch(Action.NoFeeFound);
+ logger.notify(`Unable to calculate fee for session ${sessionId}`);
+ return;
+ }
+
if (!govUkPayment) {
dispatch(Action.NoPaymentFound);
return;
@@ -231,7 +245,11 @@ function Component(props: Props) {
window.location.replace(payment._links.next_url.href);
})
.catch((error) => {
- if (error.response?.data?.error?.endsWith("local authority")) {
+ if (
+ error.response?.data?.error?.startsWith(
+ PAY_API_ERROR_UNSUPPORTED_TEAM,
+ )
+ ) {
// Show a custom message if this team isn't set up to use Pay yet
dispatch(Action.StartNewPaymentError);
} else {
@@ -247,18 +265,19 @@ function Component(props: Props) {
<>
{state.status === "init" ||
state.status === "retry" ||
- state.status === "unsupported_team" ? (
+ state.status === "unsupported_team" ||
+ state.status === "undefined_fee" ? (
{
- if (state.status === "init") {
+ if (props.hidePay || state.status === "unsupported_team") {
+ // Show "Continue" button to proceed
+ props.handleSubmit({ auto: false });
+ } else if (state.status === "init") {
startNewPayment();
} else if (state.status === "retry") {
resumeExistingPayment();
- } else if (state.status === "unsupported_team") {
- // Allow "Continue" button to skip Pay
- props.handleSubmit({ auto: true });
}
}}
buttonTitle={
@@ -267,14 +286,19 @@ function Component(props: Props) {
: "Retry payment"
}
error={
- state.status === "unsupported_team"
- ? "GOV.UK Pay is not enabled for this local authority"
- : undefined
+ (state.status === "unsupported_team" &&
+ "GOV.UK Pay is not enabled for this local authority") ||
+ (state.status === "undefined_fee" &&
+ "We are unable to calculate your fee right now") ||
+ undefined
}
showInviteToPay={
- props.allowInviteToPay && state.status !== "unsupported_team"
+ props.allowInviteToPay &&
+ !props.hidePay &&
+ state.status !== "unsupported_team"
}
paymentStatus={govUkPayment?.state?.status}
+ hidePay={props.hidePay}
/>
) : (
diff --git a/editor.planx.uk/src/@planx/components/Pay/model.ts b/editor.planx.uk/src/@planx/components/Pay/model.ts
index 7ff2c6d0e7..9d0fe202d3 100644
--- a/editor.planx.uk/src/@planx/components/Pay/model.ts
+++ b/editor.planx.uk/src/@planx/components/Pay/model.ts
@@ -12,6 +12,7 @@ export interface Pay extends MoreInformation {
fn?: string;
instructionsTitle?: string;
instructionsDescription?: string;
+ hidePay?: boolean;
allowInviteToPay?: boolean;
secondaryPageTitle?: string;
nomineeTitle?: string;
@@ -87,6 +88,7 @@ export const validationSchema = object({
fn: string().trim().required("Data field is required"),
instructionsTitle: string().trim().required(),
instructionsDescription: string().trim().required(),
+ hidePay: boolean(),
allowInviteToPay: boolean(),
nomineeTitle: string().trim().when("allowInviteToPay", {
is: true,