diff --git a/designer/client/index.tsx b/designer/client/index.tsx
index d0d2d1c755..01ad2d56f0 100644
--- a/designer/client/index.tsx
+++ b/designer/client/index.tsx
@@ -3,9 +3,17 @@ import ReactDOM from "react-dom";
import { LandingChoice, NewConfig, ChooseExisting } from "./pages/LandingPage";
import "./styles/index.scss";
import { initI18n } from "./i18n";
-import { BrowserRouter as Router, Switch, Route } from "react-router-dom";
+import {
+ BrowserRouter as Router,
+ Switch,
+ Route,
+ Redirect,
+ useLocation,
+} from "react-router-dom";
import Designer from "./designer";
import { SaveError } from "./pages/ErrorPages";
+import { CookiesProvider, useCookies } from "react-cookie";
+import { ProgressPlugin } from "webpack";
initI18n();
@@ -13,30 +21,117 @@ function NoMatch() {
return
404 Not found
;
}
+function UserChoice() {
+ let [cookies, setcookie] = useCookies(["user"]);
+ let [userState, updateUserState] = React.useState(cookies.user);
+
+ let updateUser = (e: React.FormEvent) => {
+ setcookie("user", userState, {
+ path: "/",
+ sameSite: "strict",
+ });
+
+ return true;
+ };
+
+ // return (
+ //
+ // );
+ return Logged in as: {cookies.user}
;
+}
+
+function useQuery() {
+ const { search } = useLocation();
+
+ return React.useMemo(() => new URLSearchParams(search), [search]);
+}
+
+function AuthProvider({ children }) {
+ let [cookies, setCookie] = useCookies(["user"]);
+ let query = useQuery();
+
+ if (query.get("token")) {
+ setCookie("user", query.get("token"), {
+ path: "/",
+ sameSite: "strict",
+ });
+ }
+
+ if (cookies.user || query.get("token")) return children;
+
+ window.location.href = "/api/login";
+}
+
+function Auth() {
+ let [_, setcookie] = useCookies(["user"]);
+ let query = useQuery();
+
+ setcookie("user", query.get("token"), {
+ path: "/",
+ sameSite: "strict",
+ });
+ return ;
+}
+
+function Logout() {
+ let [_, __, removeCookie] = useCookies(["user"]);
+
+ let doLogout = (e) => {
+ e.preventDefault();
+ removeCookie("user");
+ window.location.href = "/api/login";
+ };
+
+ return (
+ doLogout(e)} href="#">
+ Logout
+
+ );
+}
+
export class App extends React.Component {
render() {
return (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
);
}
diff --git a/designer/package.json b/designer/package.json
index fd24178738..fb934f22e1 100644
--- a/designer/package.json
+++ b/designer/package.json
@@ -60,6 +60,7 @@
"lodash": "^4.17.21",
"moment-timezone": "^0.5.31",
"nanoid": "^3.1.12",
+ "react-cookie": "^4.1.1",
"react-helmet": "^6.1.0",
"react-router-dom": "^5.2.0",
"resolve": "^1.19.0",
diff --git a/designer/server/config.ts b/designer/server/config.ts
index 01984ee2d2..344aef6dff 100644
--- a/designer/server/config.ts
+++ b/designer/server/config.ts
@@ -11,6 +11,7 @@ export interface Config {
previewUrl: string;
publishUrl: string;
formsApiUrl: string;
+ managementUrl: string;
persistentBackend: "s3" | "blob" | "preview" | "api";
s3Bucket?: string;
logLevel: "trace" | "info" | "debug" | "error";
@@ -39,6 +40,7 @@ const schema = joi.object({
previewUrl: joi.string(),
publishUrl: joi.string(),
formsApiUrl: joi.string(),
+ managementUrl: joi.string(),
persistentBackend: joi
.string()
.valid("s3", "blob", "preview", "api")
@@ -63,6 +65,7 @@ const config = {
previewUrl: process.env.PREVIEW_URL || "http://localhost:3009",
publishUrl: process.env.PUBLISH_URL || "http://localhost:3009",
formsApiUrl: process.env.FORMS_API_URL || "http://localhost:4567",
+ managementUrl: process.env.MANAGEMENT_URL || "http://localhost:3030",
persistentBackend: process.env.PERSISTENT_BACKEND || "preview",
s3Bucket: process.env.S3_BUCKET,
logLevel: process.env.LOG_LEVEL || "error",
diff --git a/designer/server/lib/persistence/apiPersistenceService.ts b/designer/server/lib/persistence/apiPersistenceService.ts
index 44bc1d051c..336b481f75 100644
--- a/designer/server/lib/persistence/apiPersistenceService.ts
+++ b/designer/server/lib/persistence/apiPersistenceService.ts
@@ -1,6 +1,7 @@
import type { PersistenceService } from "./persistenceService";
import Wreck from "@hapi/wreck";
import config from "../../config";
+import { FormConfiguration } from "@xgovformbuilder/model";
export class ApiPersistenceService implements PersistenceService {
logger: any;
@@ -12,22 +13,73 @@ export class ApiPersistenceService implements PersistenceService {
});
}
+ async uploadConfigurationForUser(
+ id: string,
+ configuration: string,
+ user: string
+ ): Promise {
+ return Wreck.post(`${config.formsApiUrl}/publish`, {
+ payload: JSON.stringify({ id, configuration: JSON.parse(configuration) }),
+ headers: {
+ "x-api-key": user,
+ },
+ });
+ }
+
async copyConfiguration(configurationId: string, newName: string) {
const configuration = await this.getConfiguration(configurationId);
return this.uploadConfiguration(newName, configuration);
}
- async listAllConfigurations() {
- const { payload } = await Wreck.get(`${config.formsApiUrl}/published`);
- return JSON.parse(payload.toString());
+ async copyConfigurationForUser(
+ configurationId: string,
+ newName: string,
+ user: string
+ ): Promise {
+ const configuration = await this.getConfigurationForUser(
+ configurationId,
+ user
+ );
+ return this.uploadConfigurationForUser(newName, configuration, user);
}
async getConfiguration(id: string) {
- console.log("Getting: ", id);
const { payload } = await Wreck.get(
`${config.formsApiUrl}/published/${id}`
);
var configuration = JSON.parse(payload.toString()).values;
return JSON.stringify(configuration);
}
+
+ async getConfigurationForUser(id: string, user: string): Promise {
+ const { payload } = await Wreck.get(
+ `${config.formsApiUrl}/published/${id}`,
+ {
+ headers: {
+ "x-api-key": user,
+ },
+ }
+ );
+
+ var configuration = JSON.parse(payload.toString()).values;
+ return JSON.stringify(configuration);
+ }
+
+ async listAllConfigurations() {
+ const { payload } = await Wreck.get(`${config.formsApiUrl}/published`);
+ return JSON.parse(payload.toString());
+ }
+
+ async listAllConfigurationsForUser(
+ user: string
+ ): Promise {
+ console.log("Getting forms for: ", user);
+
+ const { payload } = await Wreck.get(`${config.formsApiUrl}/published`, {
+ headers: {
+ "x-api-key": user,
+ },
+ });
+ return JSON.parse(payload.toString());
+ }
}
diff --git a/designer/server/lib/persistence/persistenceService.ts b/designer/server/lib/persistence/persistenceService.ts
index 9e02366ffc..5690fd0d10 100644
--- a/designer/server/lib/persistence/persistenceService.ts
+++ b/designer/server/lib/persistence/persistenceService.ts
@@ -6,6 +6,18 @@ export interface PersistenceService {
getConfiguration(id: string): Promise;
uploadConfiguration(id: string, configuration: string): Promise;
copyConfiguration(configurationId: string, newName: string): Promise;
+ listAllConfigurationsForUser(user: string): Promise;
+ getConfigurationForUser(id: string, user: string): Promise;
+ uploadConfigurationForUser(
+ id: string,
+ configuration: string,
+ user: string
+ ): Promise;
+ copyConfigurationForUser(
+ configurationId: string,
+ newName: string,
+ user: string
+ ): Promise;
}
export class StubPersistenceService implements PersistenceService {
@@ -21,6 +33,7 @@ export class StubPersistenceService implements PersistenceService {
getConfiguration(_id: string) {
return Promise.resolve("");
}
+
copyConfiguration(_configurationId: string, _newName: string) {
return Promise.resolve("");
}
diff --git a/designer/server/plugins/designer.ts b/designer/server/plugins/designer.ts
index ae9d4b87a9..e9a6eaffb1 100644
--- a/designer/server/plugins/designer.ts
+++ b/designer/server/plugins/designer.ts
@@ -52,6 +52,7 @@ export const designerPlugin = {
});
server.route(newConfig.registerNewFormWithRunner);
+ server.route(api.loginRedirect);
server.route(api.getFormWithId);
server.route(api.putFormWithId);
server.route(api.getAllPersistedConfigurations);
diff --git a/designer/server/plugins/routes/api.ts b/designer/server/plugins/routes/api.ts
index 01d128e662..6772d902cc 100644
--- a/designer/server/plugins/routes/api.ts
+++ b/designer/server/plugins/routes/api.ts
@@ -12,6 +12,16 @@ const getPublished = async function (id) {
return payload.toString();
};
+export const loginRedirect: ServerRoute = {
+ method: "GET",
+ path: "/api/login",
+ options: {
+ handler: (_, h) => {
+ return h.redirect(config.managementUrl);
+ },
+ },
+};
+
export const getFormWithId: ServerRoute = {
// GET DATA
method: "GET",
@@ -19,14 +29,14 @@ export const getFormWithId: ServerRoute = {
options: {
handler: async (request, h) => {
const { id } = request.params;
+ const { persistenceService } = request.services([]);
let formJson = newFormJson;
try {
- const response = await getPublished(id);
- const { values } = JSON.parse(response);
-
- if (values) {
- formJson = values;
- }
+ const response = await persistenceService.getConfigurationForUser(
+ `${id}`,
+ request.state["user"]
+ );
+ formJson = JSON.parse(response);
} catch (error) {
request.logger.error(error);
}
@@ -61,11 +71,22 @@ export const putFormWithId: ServerRoute = {
throw new Error("Schema validation failed, reason: " + error.message);
}
- await persistenceService.uploadConfiguration(
- `${id}`,
- JSON.stringify(value)
- );
- await publish(id, value);
+
+ if (request.state["user"]) {
+ await persistenceService.uploadConfigurationForUser(
+ `${id}`,
+ JSON.stringify(value),
+ request.state["user"]
+ );
+ } else {
+ await persistenceService.uploadConfiguration(
+ `${id}`,
+ JSON.stringify(value)
+ );
+ }
+
+ // Remove publishing for now
+ // await publish(id, value);
return h.response({ ok: true }).code(204);
} catch (err) {
request.logger.error("Designer Server PUT /api/{id}/data error:", err);
@@ -89,7 +110,16 @@ export const getAllPersistedConfigurations: ServerRoute = {
handler: async (request, h): Promise => {
const { persistenceService } = request.services([]);
try {
- const response = await persistenceService.listAllConfigurations();
+ let response;
+
+ if (request.state["user"]) {
+ response = await persistenceService.listAllConfigurationsForUser(
+ request.state["user"]
+ );
+ } else {
+ response = await persistenceService.listAllConfigurations();
+ }
+
return h.response(response).type("application/json");
} catch (error) {
request.server.log(["error", "/configurations"], error);
diff --git a/designer/server/plugins/routes/newConfig.ts b/designer/server/plugins/routes/newConfig.ts
index 424aa11fb7..79ce352413 100644
--- a/designer/server/plugins/routes/newConfig.ts
+++ b/designer/server/plugins/routes/newConfig.ts
@@ -24,20 +24,47 @@ export const registerNewFormWithRunner: ServerRoute = {
try {
if (selected.Key === "New") {
- if (config.persistentBackend !== "preview") {
- await persistenceService.uploadConfiguration(
- `${newName}.json`,
- JSON.stringify(newFormJson)
- );
+ if (
+ config.persistentBackend !== "preview" &&
+ config.persistentBackend !== "api"
+ ) {
+ if (request.state["user"]) {
+ await persistenceService.uploadConfigurationForUser(
+ `${newName}`,
+ JSON.stringify(newFormJson),
+ request.state["user"]
+ );
+ } else {
+ await persistenceService.uploadConfiguration(
+ `${newName}`,
+ JSON.stringify(newFormJson)
+ );
+ }
}
- await publish(newName, newFormJson);
+
+ // Remove publishing for now
+ // await publish(newName, newFormJson);
} else {
- await persistenceService.copyConfiguration(
- `${selected.Key}`,
- newName
- );
- const copied = await persistenceService.getConfiguration(newName);
- await publish(newName, copied);
+ let copied;
+ if (request.state["user"]) {
+ await persistenceService.copyConfigurationForUser(
+ `${selected.Key}`,
+ newName,
+ request.state["user"]
+ );
+ copied = await persistenceService.getConfigurationForUser(
+ newName,
+ request.state["user"]
+ );
+ } else {
+ await persistenceService.copyConfiguration(
+ `${selected.Key}`,
+ newName
+ );
+ copied = await persistenceService.getConfiguration(newName);
+ }
+ // Remove publishing for now
+ // await publish(newName, copied);
}
} catch (e) {
request.logger.error(e);
diff --git a/runner/src/server/config.ts b/runner/src/server/config.ts
index c9ef7e916f..1771163102 100644
--- a/runner/src/server/config.ts
+++ b/runner/src/server/config.ts
@@ -54,6 +54,7 @@ const schema = Joi.object({
serviceName: Joi.string().optional(),
documentUploadApiUrl: Joi.string().default(DEFAULT_DOCUMENT_UPLOAD_API_URL),
previewMode: Joi.boolean().optional(),
+ formsApiUrl: Joi.string().optional(),
sslKey: Joi.string().optional(),
sslCert: Joi.string().optional(),
sessionTimeout: Joi.number().default(DEFAULT_SESSION_TTL),
@@ -126,6 +127,7 @@ export function buildConfig() {
serviceName: process.env.SERVICE_NAME,
documentUploadApiUrl: process.env.DOCUMENT_UPLOAD_API_URL,
previewMode: process.env.PREVIEW_MODE === "true",
+ formsApiUrl: process.env.FORM_API_URL,
sslKey: process.env.SSL_KEY,
sslCert: process.env.SSL_CERT,
sessionTimeout: process.env.SESSION_TIMEOUT,
diff --git a/runner/src/server/plugins/engine/configureEnginePlugin.ts b/runner/src/server/plugins/engine/configureEnginePlugin.ts
index c9e1f1566b..cab31e0181 100644
--- a/runner/src/server/plugins/engine/configureEnginePlugin.ts
+++ b/runner/src/server/plugins/engine/configureEnginePlugin.ts
@@ -23,6 +23,7 @@ type ConfigureEnginePlugin = (
id: string;
}[];
previewMode: boolean;
+ formsApiUrl: string;
};
};
@@ -56,6 +57,11 @@ export const configureEnginePlugin: ConfigureEnginePlugin = (
return {
plugin,
- options: { modelOptions, configs, previewMode: config.previewMode },
+ options: {
+ modelOptions,
+ configs,
+ previewMode: config.previewMode,
+ formsApiUrl: config.formsApiUrl,
+ },
};
};
diff --git a/runner/src/server/plugins/engine/plugin.ts b/runner/src/server/plugins/engine/plugin.ts
index aa674f3002..8bcf401eca 100644
--- a/runner/src/server/plugins/engine/plugin.ts
+++ b/runner/src/server/plugins/engine/plugin.ts
@@ -7,8 +7,10 @@ import { HapiRequest, HapiResponseToolkit, HapiServer } from "server/types";
import { FormModel } from "./models";
import Boom from "boom";
import { PluginSpecificConfiguration } from "@hapi/hapi";
+import Wreck from "@hapi/wreck";
import { FormPayload } from "./types";
import { shouldLogin } from "server/plugins/auth";
+import config from "src/server/config";
configure([
// Configure Nunjucks to allow rendering of content that is revealed conditionally.
@@ -24,6 +26,17 @@ function normalisePath(path: string) {
return path.replace(/^\//, "").replace(/\/$/, "");
}
+async function getForm(
+ id: string,
+ formsApiUrl: string,
+ modelOptions,
+ basePath: string
+): Promise {
+ const { payload } = await Wreck.get(`${formsApiUrl}/published/${id}`);
+ var configuration = JSON.parse(payload.toString()).values;
+ return new FormModel(configuration, { ...modelOptions, basePath });
+}
+
function getStartPageRedirect(
request: HapiRequest,
h: HapiResponseToolkit,
@@ -47,6 +60,7 @@ type PluginOptions = {
modelOptions: any;
configs: any[];
previewMode: boolean;
+ formsApiUrl: string;
};
export const plugin = {
@@ -64,93 +78,93 @@ export const plugin = {
});
});
- if (previewMode) {
- /**
- * The following endpoints are used from the designer for operating in 'preview' mode.
- * I.E. Designs saved in the designer can be accessed in the runner for viewing.
- * The designer also uses these endpoints as a persistence mechanism for storing and retrieving data
- * for it's own purposes so if you're changing these endpoints you likely need to go and amend
- * the designer too!
- */
- server.route({
- method: "post",
- path: "/publish",
- handler: (request: HapiRequest, h: HapiResponseToolkit) => {
- const payload = request.payload as FormPayload;
- const { id, configuration } = payload;
-
- const parsedConfiguration =
- typeof configuration === "string"
- ? JSON.parse(configuration)
- : configuration;
- forms[id] = new FormModel(parsedConfiguration, {
- ...modelOptions,
- basePath: id,
- });
- return h.response({}).code(204);
- },
- });
+ // if (previewMode) {
+ // /**
+ // * The following endpoints are used from the designer for operating in 'preview' mode.
+ // * I.E. Designs saved in the designer can be accessed in the runner for viewing.
+ // * The designer also uses these endpoints as a persistence mechanism for storing and retrieving data
+ // * for it's own purposes so if you're changing these endpoints you likely need to go and amend
+ // * the designer too!
+ // */
+ // server.route({
+ // method: "post",
+ // path: "/publish",
+ // handler: (request: HapiRequest, h: HapiResponseToolkit) => {
+ // const payload = request.payload as FormPayload;
+ // const { id, configuration } = payload;
- server.route({
- method: "get",
- path: "/published/{id}",
- handler: (request: HapiRequest, h: HapiResponseToolkit) => {
- const { id } = request.params;
- if (forms[id]) {
- const { values } = forms[id];
- return h.response(JSON.stringify({ id, values })).code(200);
- } else {
- return h.response({}).code(204);
- }
- },
- });
+ // const parsedConfiguration =
+ // typeof configuration === "string"
+ // ? JSON.parse(configuration)
+ // : configuration;
+ // forms[id] = new FormModel(parsedConfiguration, {
+ // ...modelOptions,
+ // basePath: id,
+ // });
+ // return h.response({}).code(204);
+ // },
+ // });
- server.route({
- method: "get",
- path: "/published",
- handler: (_request: HapiRequest, h: HapiResponseToolkit) => {
- return h
- .response(
- JSON.stringify(
- Object.keys(forms).map(
- (key) =>
- new FormConfiguration(
- key,
- forms[key].name,
- undefined,
- forms[key].def.feedback?.feedbackForm
- )
- )
- )
- )
- .code(200);
- },
- });
- }
+ // server.route({
+ // method: "get",
+ // path: "/published/{id}",
+ // handler: (request: HapiRequest, h: HapiResponseToolkit) => {
+ // const { id } = request.params;
+ // if (forms[id]) {
+ // const { values } = forms[id];
+ // return h.response(JSON.stringify({ id, values })).code(200);
+ // } else {
+ // return h.response({}).code(204);
+ // }
+ // },
+ // });
- server.route({
- method: "get",
- path: "/",
- handler: (request: HapiRequest, h: HapiResponseToolkit) => {
- const keys = Object.keys(forms);
- let id = "";
- if (keys.length === 1) {
- id = keys[0];
- }
- const model = forms[id];
- if (model) {
- return getStartPageRedirect(request, h, id, model);
- }
- throw Boom.notFound("No default form found");
- },
- });
+ // server.route({
+ // method: "get",
+ // path: "/published",
+ // handler: (_request: HapiRequest, h: HapiResponseToolkit) => {
+ // return h
+ // .response(
+ // JSON.stringify(
+ // Object.keys(forms).map(
+ // (key) =>
+ // new FormConfiguration(
+ // key,
+ // forms[key].name,
+ // undefined,
+ // forms[key].def.feedback?.feedbackForm
+ // )
+ // )
+ // )
+ // )
+ // .code(200);
+ // },
+ // });
+ // }
+
+ // server.route({
+ // method: "get",
+ // path: "/",
+ // handler: (request: HapiRequest, h: HapiResponseToolkit) => {
+ // const keys = Object.keys(forms);
+ // let id = "";
+ // if (keys.length === 1) {
+ // id = keys[0];
+ // }
+ // const model = forms[id];
+ // if (model) {
+ // return getStartPageRedirect(request, h, id, model);
+ // }
+ // throw Boom.notFound("No default form found");
+ // },
+ // });
server.route({
method: "get",
path: "/{id}",
- handler: (request: HapiRequest, h: HapiResponseToolkit) => {
+ handler: async (request: HapiRequest, h: HapiResponseToolkit) => {
const { id } = request.params;
- const model = forms[id];
+ const model = await getForm(id, options.formsApiUrl, modelOptions, id);
if (model) {
return getStartPageRedirect(request, h, id, model);
}
@@ -161,9 +175,9 @@ export const plugin = {
server.route({
method: "get",
path: "/{id}/{path*}",
- handler: (request: HapiRequest, h: HapiResponseToolkit) => {
+ handler: async (request: HapiRequest, h: HapiResponseToolkit) => {
const { path, id } = request.params;
- const model = forms[id];
+ const model = await getForm(id, options.formsApiUrl, modelOptions, id);
const page = model?.pages.find(
(page) => normalisePath(page.path) === normalisePath(path)
);
@@ -196,7 +210,7 @@ export const plugin = {
h: HapiResponseToolkit
) => {
const { path, id } = request.params;
- const model = forms[id];
+ const model = await getForm(id, options.formsApiUrl, modelOptions, id);
if (model) {
const page = model.pages.find(
diff --git a/yarn.lock b/yarn.lock
index 85eb50daca..6579898965 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -3541,6 +3541,13 @@ __metadata:
languageName: node
linkType: hard
+"@types/cookie@npm:^0.3.3":
+ version: 0.3.3
+ resolution: "@types/cookie@npm:0.3.3"
+ checksum: 35d603d5e620ed8a0c4dedcf6b6e19e4981a4c44d40402de39e53f041ce74bf66b40272a355db37271b9557709fd3e6dfc8fe6ea4031f59b8b16cf0606ea3797
+ languageName: node
+ linkType: hard
+
"@types/cookie@npm:^0.4.0":
version: 0.4.0
resolution: "@types/cookie@npm:0.4.0"
@@ -3715,6 +3722,16 @@ __metadata:
languageName: node
linkType: hard
+"@types/hoist-non-react-statics@npm:^3.0.1":
+ version: 3.3.1
+ resolution: "@types/hoist-non-react-statics@npm:3.3.1"
+ dependencies:
+ "@types/react": "*"
+ hoist-non-react-statics: ^3.3.0
+ checksum: 16ab4c45d4920fa378c8be76554b10061247fc04d2c8af11bdb7d520b3967e9c06d7ad5efd9b0f1657fbc4d095f62c6e1325f03b9141eb1ef2c8095b96fd42f8
+ languageName: node
+ linkType: hard
+
"@types/html-minifier-terser@npm:^5.0.0":
version: 5.1.1
resolution: "@types/html-minifier-terser@npm:5.1.1"
@@ -4024,6 +4041,17 @@ __metadata:
languageName: node
linkType: hard
+"@types/react@npm:*":
+ version: 17.0.38
+ resolution: "@types/react@npm:17.0.38"
+ dependencies:
+ "@types/prop-types": "*"
+ "@types/scheduler": "*"
+ csstype: ^3.0.2
+ checksum: b04dcee8a5e530ec22d9d624c76ad198130b29751b28d66678def14d2def8be9c6fc1e3515d4700f23feb765a8eae7e278f27eaf841b006670a41b421c0a6866
+ languageName: node
+ linkType: hard
+
"@types/react@npm:^16, @types/react@npm:^16.9.52":
version: 16.14.2
resolution: "@types/react@npm:16.14.2"
@@ -4052,6 +4080,13 @@ __metadata:
languageName: node
linkType: hard
+"@types/scheduler@npm:*":
+ version: 0.16.2
+ resolution: "@types/scheduler@npm:0.16.2"
+ checksum: e78d1bb50ca9321a76826c0f5a5ed7f1b6e995597127993cad658bd18d7f7de4324cd7efdfd0140079ee212a05774bbbd95eecfdf72370d14023acb99b841a30
+ languageName: node
+ linkType: hard
+
"@types/selenium-standalone@npm:^7.0.0":
version: 7.0.0
resolution: "@types/selenium-standalone@npm:7.0.0"
@@ -4960,6 +4995,7 @@ __metadata:
postcss-loader: ^4.1.0
prismjs: 1.23.0
react: 16.13.1
+ react-cookie: ^4.1.1
react-dom: 16.13.1
react-helmet: ^6.1.0
react-router-dom: ^5.2.0
@@ -8158,7 +8194,7 @@ __metadata:
languageName: node
linkType: hard
-"cookie@npm:^0.4.1":
+"cookie@npm:^0.4.0, cookie@npm:^0.4.1":
version: 0.4.1
resolution: "cookie@npm:0.4.1"
checksum: b8e0928e3e7aba013087974b33a6eec730b0a68b7ec00fc3c089a56ba2883bcf671252fc2ed64775aa1ca64796b6e1f6fdddba25a66808aef77614d235fd3e06
@@ -12821,7 +12857,7 @@ fsevents@^1.2.7:
languageName: node
linkType: hard
-"hoist-non-react-statics@npm:^3.1.0":
+"hoist-non-react-statics@npm:^3.0.0, hoist-non-react-statics@npm:^3.1.0, hoist-non-react-statics@npm:^3.3.0":
version: 3.3.2
resolution: "hoist-non-react-statics@npm:3.3.2"
dependencies:
@@ -19318,6 +19354,19 @@ fsevents@^1.2.7:
languageName: node
linkType: hard
+"react-cookie@npm:^4.1.1":
+ version: 4.1.1
+ resolution: "react-cookie@npm:4.1.1"
+ dependencies:
+ "@types/hoist-non-react-statics": ^3.0.1
+ hoist-non-react-statics: ^3.0.0
+ universal-cookie: ^4.0.0
+ peerDependencies:
+ react: ">= 16.3.0"
+ checksum: 0a011b7cbe8d72e5d3482708abe9a00616c18b2c6c6f4f911373d76f1307d6c3b2a5a096e855181009b3d6461ae587f27483820d511fdaaf2821d9daf54b1103
+ languageName: node
+ linkType: hard
+
"react-dom@npm:16.13.1":
version: 16.13.1
resolution: "react-dom@npm:16.13.1"
@@ -23147,6 +23196,16 @@ resolve@^1.1.6:
languageName: node
linkType: hard
+"universal-cookie@npm:^4.0.0":
+ version: 4.0.4
+ resolution: "universal-cookie@npm:4.0.4"
+ dependencies:
+ "@types/cookie": ^0.3.3
+ cookie: ^0.4.0
+ checksum: 182b97a2dd6a368f1940f8dd2d0ec3908f1add6f651a6bd30dac1a8083dd7f64c35e0a7fd4b3c923004f4fe85e838d2996bcd0057061a2f494782f65c2a105fc
+ languageName: node
+ linkType: hard
+
"universalify@npm:^0.1.0, universalify@npm:^0.1.2":
version: 0.1.2
resolution: "universalify@npm:0.1.2"