Toggle your service between "offline" and "online".
-
- A service must be online to be accessed by the public, and to
- enable analytics gathering.
-
-
Offline services can still be edited and published as normal.
-
-
-
-
-
-
-
-
-
-
-
- );
-};
-
-export default ServiceSettings;
diff --git a/editor.planx.uk/src/pages/FlowEditor/components/Settings/tests/ServiceSettings/PublicLinks.test.tsx b/editor.planx.uk/src/pages/FlowEditor/components/Settings/ServiceSettings/FlowStatus/PublicLink.test.tsx
similarity index 99%
rename from editor.planx.uk/src/pages/FlowEditor/components/Settings/tests/ServiceSettings/PublicLinks.test.tsx
rename to editor.planx.uk/src/pages/FlowEditor/components/Settings/ServiceSettings/FlowStatus/PublicLink.test.tsx
index a2569d1a4e..54b1a11359 100644
--- a/editor.planx.uk/src/pages/FlowEditor/components/Settings/tests/ServiceSettings/PublicLinks.test.tsx
+++ b/editor.planx.uk/src/pages/FlowEditor/components/Settings/ServiceSettings/FlowStatus/PublicLink.test.tsx
@@ -4,7 +4,7 @@ import { vi } from "vitest";
import setupServiceSettingsScreen, {
mockWindowLocationObject,
-} from "../helpers/setupServiceSettingsScreen";
+} from "./testUtils";
const { getState, setState } = useStore;
@@ -62,6 +62,7 @@ describe("A team with a subdomain has an offline, published service.", () => {
await inactiveLinkCheck(`https://${teamDomain}/${flowSlug}`);
});
+
it("has a disabled copy button", disabledCopyCheck);
});
@@ -83,6 +84,7 @@ describe("A team with a subdomain has an online, unpublished service.", () => {
await inactiveLinkCheck(`https://${teamDomain}/${flowSlug}`);
});
+
it("has a disabled copy button", disabledCopyCheck);
});
@@ -107,11 +109,13 @@ describe("A team with a subdomain has an online, published service.", () => {
await setupServiceSettingsScreen();
await activeLinkCheck(`https://${teamDomain}/${flowSlug}`);
});
+
it("has an enabled copy button", async () => {
// render the comp
await setupServiceSettingsScreen();
enabledCopyCheck();
});
+
it("can be copied to the clipboard", async () => {
const { flowSlug, teamDomain } = getState();
// render the comp
@@ -127,6 +131,7 @@ describe("A team with a subdomain has an online, published service.", () => {
);
});
});
+
describe("A team with a subdomain has an offline, unpublished service.", () => {
beforeEach(async () => {
// setup state values that depends on
@@ -145,8 +150,10 @@ describe("A team with a subdomain has an offline, unpublished service.", () => {
await inactiveLinkCheck(`https://${teamDomain}/${flowSlug}`);
});
+
it("has a disabled copy button", disabledCopyCheck);
});
+
describe("A team without a subdomain has an offline, published service.", () => {
beforeEach(async () => {
// setup state values that depends on
@@ -168,6 +175,7 @@ describe("A team without a subdomain has an offline, published service.", () =>
it("has a public link with the url in a
tag", async () => {
await inactiveLinkCheck(publishedUrl);
});
+
it("has a disabled copy button", disabledCopyCheck);
});
@@ -192,6 +200,7 @@ describe("A team without a subdomain has an online, unpublished service.", () =>
it("has a public link with the url in a
tag", async () => {
await inactiveLinkCheck(publishedUrl);
});
+
it("has a disabled copy button", disabledCopyCheck);
});
@@ -219,6 +228,7 @@ describe("A team without a subdomain has an online, published service.", () => {
setupServiceSettingsScreen();
await activeLinkCheck(publishedUrl);
});
+
it("has an enabled copy button", () => {
// render the comp
setupServiceSettingsScreen();
@@ -258,5 +268,6 @@ describe("A team without a subdomain has an offline, unpublished service.", () =
it("has a public link with the url in a
tag", async () => {
await inactiveLinkCheck(publishedUrl);
});
+
it("has a disabled copy button", disabledCopyCheck);
});
diff --git a/editor.planx.uk/src/pages/FlowEditor/components/Settings/ServiceSettings/FlowStatus/PublicLink.tsx b/editor.planx.uk/src/pages/FlowEditor/components/Settings/ServiceSettings/FlowStatus/PublicLink.tsx
new file mode 100644
index 0000000000..6bc4a1fab3
--- /dev/null
+++ b/editor.planx.uk/src/pages/FlowEditor/components/Settings/ServiceSettings/FlowStatus/PublicLink.tsx
@@ -0,0 +1,130 @@
+import ContentCopyIcon from "@mui/icons-material/ContentCopy";
+import Box from "@mui/material/Box";
+import Button from "@mui/material/Button";
+import Link from "@mui/material/Link";
+import Tooltip from "@mui/material/Tooltip";
+import Typography from "@mui/material/Typography";
+import { FlowStatus } from "@opensystemslab/planx-core/types";
+import React, { useState } from "react";
+import SettingsDescription from "ui/editor/SettingsDescription";
+
+const CopyButton = (props: { link: string; isActive: boolean }) => {
+ const [copyMessage, setCopyMessage] = useState<"copy" | "copied">("copy");
+ return (
+
+
+
+ );
+};
+
+const TitledLink: React.FC<{
+ link: string;
+ isActive: boolean;
+ helpText: string | undefined;
+}> = ({ link, isActive, helpText }) => {
+ return (
+
+
+ Your public link
+
+
+
+ {helpText}
+
+ {isActive ? (
+
+ {link}
+
+ ) : (
+
+ {link}
+
+ )}
+
+ );
+};
+
+export const PublicLink: React.FC<{
+ isFlowPublished: boolean;
+ status: FlowStatus;
+ subdomain: string;
+ publishedLink: string;
+}> = ({ isFlowPublished, status, subdomain, publishedLink }) => {
+ const isFlowPublic = isFlowPublished && status === "online";
+ const hasSubdomain = Boolean(subdomain);
+
+ const publicLinkHelpText = () => {
+ const isFlowOnline = status === "online";
+ switch (true) {
+ case isFlowPublished && isFlowOnline:
+ return undefined;
+ case !isFlowPublished && isFlowOnline:
+ return "Publish your flow to activate the public link.";
+ case isFlowPublished && !isFlowOnline:
+ return "Switch your flow to 'online' to activate the public link.";
+ case !isFlowPublished && !isFlowOnline:
+ return "Publish your flow and switch it to 'online' to activate the public link.";
+ }
+ };
+
+ switch (true) {
+ case isFlowPublic && hasSubdomain:
+ return (
+
+ );
+ case isFlowPublic && !hasSubdomain:
+ return (
+
+ );
+ case !isFlowPublic && hasSubdomain:
+ return (
+
+ );
+ case !isFlowPublic && !hasSubdomain:
+ return (
+
+ );
+ }
+};
diff --git a/editor.planx.uk/src/pages/FlowEditor/components/Settings/ServiceSettings/FlowStatus/index.tsx b/editor.planx.uk/src/pages/FlowEditor/components/Settings/ServiceSettings/FlowStatus/index.tsx
new file mode 100644
index 0000000000..40a2afaef7
--- /dev/null
+++ b/editor.planx.uk/src/pages/FlowEditor/components/Settings/ServiceSettings/FlowStatus/index.tsx
@@ -0,0 +1,169 @@
+import Box from "@mui/material/Box";
+import Button from "@mui/material/Button";
+import FormControlLabel, {
+ formControlLabelClasses,
+} from "@mui/material/FormControlLabel";
+import Switch from "@mui/material/Switch";
+import Typography from "@mui/material/Typography";
+import type { FlowStatus } from "@opensystemslab/planx-core/types";
+import axios from "axios";
+import { useFormik } from "formik";
+import { useToast } from "hooks/useToast";
+import React from "react";
+import { rootFlowPath } from "routes/utils";
+import { FONT_WEIGHT_BOLD } from "theme";
+import SettingsDescription from "ui/editor/SettingsDescription";
+import SettingsSection from "ui/editor/SettingsSection";
+
+import { useStore } from "../../../../lib/store";
+import { PublicLink } from "./PublicLink";
+
+const FlowStatus = () => {
+ const [
+ flowStatus,
+ updateFlowStatus,
+ token,
+ teamSlug,
+ flowSlug,
+ teamDomain,
+ isFlowPublished,
+ ] = useStore((state) => [
+ state.flowStatus,
+ state.updateFlowStatus,
+ state.jwt,
+ state.teamSlug,
+ state.flowSlug,
+ state.teamDomain,
+ state.isFlowPublished,
+ ]);
+ const toast = useToast();
+
+ const statusForm = useFormik<{ status: FlowStatus }>({
+ initialValues: {
+ status: flowStatus || "online",
+ },
+ onSubmit: async (values, { resetForm }) => {
+ const isSuccess = await updateFlowStatus(values.status);
+ if (isSuccess) {
+ toast.success("Service settings updated successfully");
+ // Send a Slack notification to #planx-notifications
+ sendFlowStatusSlackNotification(values.status);
+ // Reset "dirty" status to disable Save & Reset buttons
+ resetForm({ values });
+ }
+ },
+ });
+
+ const publishedLink = `${window.location.origin}${rootFlowPath(
+ false,
+ )}/published`;
+
+ const subdomainLink = teamDomain && `https://${teamDomain}/${flowSlug}`;
+
+ const sendFlowStatusSlackNotification = async (status: FlowStatus) => {
+ const skipTeamSlugs = [
+ "open-digital-planning",
+ "opensystemslab",
+ "planx",
+ "templates",
+ "testing",
+ "wikihouse",
+ ];
+ if (skipTeamSlugs.includes(teamSlug)) return;
+
+ const emoji = {
+ online: ":large_green_circle:",
+ offline: ":no_entry:",
+ };
+ const message = `${emoji[status]} *${teamSlug}/${flowSlug}* is now ${status} (@Silvia)`;
+
+ return axios.post(
+ `${import.meta.env.VITE_APP_API_URL}/send-slack-notification`,
+ {
+ message: message,
+ },
+ {
+ headers: {
+ Authorization: `Bearer ${token}`,
+ },
+ },
+ );
+ };
+
+ return (
+
+
+
+ Status
+
+
+ Manage the status of your service.
+
+
+
+
+ statusForm.setFieldValue(
+ "status",
+ statusForm.values.status === "online" ? "offline" : "online",
+ )
+ }
+ />
+ }
+ />
+
+
Toggle your service between "offline" and "online".
+
+ A service must be online to be accessed by the public, and to enable
+ analytics gathering.
+
+
Offline services can still be edited and published as normal.