diff --git a/editor.planx.uk/package.json b/editor.planx.uk/package.json index 1c232cddff..fdd05e098a 100644 --- a/editor.planx.uk/package.json +++ b/editor.planx.uk/package.json @@ -14,7 +14,7 @@ "@mui/material": "^5.15.10", "@mui/utils": "^5.15.11", "@opensystemslab/map": "^0.8.3", - "@opensystemslab/planx-core": "git+https://github.com/theopensystemslab/planx-core#6b2fd26", + "@opensystemslab/planx-core": "git+https://github.com/theopensystemslab/planx-core#7c37fc3", "@tiptap/core": "^2.4.0", "@tiptap/extension-bold": "^2.0.3", "@tiptap/extension-bubble-menu": "^2.1.13", diff --git a/editor.planx.uk/pnpm-lock.yaml b/editor.planx.uk/pnpm-lock.yaml index 625e0b510a..5f158a8aaf 100644 --- a/editor.planx.uk/pnpm-lock.yaml +++ b/editor.planx.uk/pnpm-lock.yaml @@ -46,8 +46,8 @@ dependencies: specifier: ^0.8.3 version: 0.8.3 '@opensystemslab/planx-core': - specifier: git+https://github.com/theopensystemslab/planx-core#6b2fd26 - version: github.com/theopensystemslab/planx-core/6b2fd26(@types/react@18.2.45) + specifier: git+https://github.com/theopensystemslab/planx-core#7c37fc3 + version: github.com/theopensystemslab/planx-core/7c37fc3(@types/react@18.2.45) '@tiptap/core': specifier: ^2.4.0 version: 2.4.0(@tiptap/pm@2.0.3) @@ -15182,8 +15182,8 @@ packages: /json-parse-even-better-errors@2.3.1: resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} - /json-schema-to-typescript@14.1.0: - resolution: {integrity: sha512-VIeAFQkn88gFh26MSHWG4uX7TjK/arTw0NVLMZn6vX1WrSF+P6xu5MyEdovu+9PJ0uiS5gm0wzwQvYW9eSq1uw==} + /json-schema-to-typescript@15.0.0: + resolution: {integrity: sha512-gOX3cJB4eL1ztMc3WUh569ubRcKnr8MnYk++6+/WaaN4bufGHSR6EcbUbvLZgirPQOfvni5SSGkRx0pYloYU8A==} engines: {node: '>=16.0.0'} hasBin: true dependencies: @@ -21898,9 +21898,9 @@ packages: use-sync-external-store: 1.2.0(react@18.2.0) dev: false - github.com/theopensystemslab/planx-core/6b2fd26(@types/react@18.2.45): - resolution: {tarball: https://codeload.github.com/theopensystemslab/planx-core/tar.gz/6b2fd26} - id: github.com/theopensystemslab/planx-core/6b2fd26 + github.com/theopensystemslab/planx-core/7c37fc3(@types/react@18.2.45): + resolution: {tarball: https://codeload.github.com/theopensystemslab/planx-core/tar.gz/7c37fc3} + id: github.com/theopensystemslab/planx-core/7c37fc3 name: '@opensystemslab/planx-core' version: 1.0.0 prepare: true @@ -21920,7 +21920,7 @@ packages: fast-xml-parser: 4.4.1 graphql: 16.9.0 graphql-request: 6.1.0(graphql@16.9.0) - json-schema-to-typescript: 14.1.0 + json-schema-to-typescript: 15.0.0 lodash: 4.17.21 marked: 13.0.3 prettier: 3.3.3 diff --git a/editor.planx.uk/src/components/Header.tsx b/editor.planx.uk/src/components/Header.tsx index e15200994e..3ecf77297b 100644 --- a/editor.planx.uk/src/components/Header.tsx +++ b/editor.planx.uk/src/components/Header.tsx @@ -24,7 +24,7 @@ import { clearLocalFlow } from "lib/local"; import { capitalize } from "lodash"; import { Route } from "navi"; import { useAnalyticsTracking } from "pages/FlowEditor/lib/analytics/provider"; -import React, { RefObject, useRef, useState } from "react"; +import React, { RefObject, useEffect, useRef, useState } from "react"; import { Link as ReactNaviLink, useCurrentRoute, @@ -577,8 +577,20 @@ const Toolbar: React.FC = ({ headerRef }) => { }; const Header: React.FC = () => { + const [isOutsideEditor, setIsOutsideEditor] = useState(false); const headerRef = useRef(null); const theme = useStore((state) => state.teamTheme); + const environment = useStore((state) => state.previewEnvironment); + const pathnameArray = window.location.pathname.split("/"); + + useEffect(() => { + const findPathname = + pathnameArray.slice(-1)[0] === "preview" || + pathnameArray.slice(-1)[0] === "draft"; + + setIsOutsideEditor(Boolean(findPathname)); + }, pathnameArray); + return ( { color="transparent" ref={headerRef} sx={{ - backgroundColor: theme?.primaryColour || "#2c2c2c", + backgroundColor: + isOutsideEditor || environment === "standalone" + ? theme.primaryColour + : "#2c2c2c", "@media print": { backgroundColor: "white", color: "black" }, }} > diff --git a/editor.planx.uk/src/pages/FlowEditor/components/Flow/index.tsx b/editor.planx.uk/src/pages/FlowEditor/components/Flow/index.tsx index fc43669d25..1ef876af01 100644 --- a/editor.planx.uk/src/pages/FlowEditor/components/Flow/index.tsx +++ b/editor.planx.uk/src/pages/FlowEditor/components/Flow/index.tsx @@ -24,6 +24,8 @@ const Flow = ({ breadcrumbs = [] }: any) => { href: `${window.location.pathname.split(id)[0]}${id}`, })); + console.log(useStore.getState().flowStatus); + return ( <>
    diff --git a/editor.planx.uk/src/pages/FlowEditor/components/Sidebar/LinkDialog.tsx b/editor.planx.uk/src/pages/FlowEditor/components/Sidebar/LinkDialog.tsx new file mode 100644 index 0000000000..4e003ce185 --- /dev/null +++ b/editor.planx.uk/src/pages/FlowEditor/components/Sidebar/LinkDialog.tsx @@ -0,0 +1,305 @@ +import CloseIcon from "@mui/icons-material/Close"; +import ContentCopyIcon from "@mui/icons-material/ContentCopy"; +import LanguageIcon from "@mui/icons-material/Language"; +import OpenInNewIcon from "@mui/icons-material/OpenInNew"; +import OpenInNewOffIcon from "@mui/icons-material/OpenInNewOff"; +import Box from "@mui/material/Box"; +import Button from "@mui/material/Button"; +import Dialog from "@mui/material/Dialog"; +import DialogContent from "@mui/material/DialogContent"; +import DialogTitle from "@mui/material/DialogTitle"; +import Divider from "@mui/material/Divider"; +import Link from "@mui/material/Link"; +import Stack from "@mui/material/Stack"; +import { styled } from "@mui/material/styles"; +import { SvgIconProps } from "@mui/material/SvgIcon"; +import Tooltip from "@mui/material/Tooltip"; +import Typography from "@mui/material/Typography"; +import gql from "graphql-tag"; +import { useStore } from "pages/FlowEditor/lib/store"; +import React, { useEffect, useState } from "react"; + +import { client } from "../../../../lib/graphql"; +import Permission from "../../../../ui/editor/Permission"; + +interface DialogBaseProps { + linkDialogOpen: boolean; + teamDomain?: string; + teamSlug: string; + flowSlug: string; + isFlowPublished: boolean; + url: string; + setLinkDialogOpen: React.Dispatch>; +} + +interface LinkProps { + subdomain?: string; + titleIcon?: SvgIconProps; + title: string; + link: string; + description?: string; + status?: string; + type: "published" | "draft" | "preview"; + isPublished?: boolean; +} + +type PublishedLinkProps = Pick< + LinkProps, + "status" | "subdomain" | "link" | "isPublished" +>; + +const InactiveLink = styled(Typography)(({ theme }) => ({ + width: "100%", + textAlign: "left", + color: theme.palette.text.secondary, +})); + +const PaddedText = styled(Typography)(({ theme }) => ({ + paddingLeft: "31px", +})); + +const LinkBox = styled(Box)(({ theme }) => ({ + display: "flex", + flexDirection: "column", + gap: "10px", +})); + +const CopyButton = (props: any) => { + const [copyMessage, setCopyMessage] = useState<"copy" | "copied">("copy"); + return ( + + + + ); +}; + +const InactivePublishedLink = (props: any) => { + return ( + + {" "} + {props.title} + {props.message}{" "} + + ); +}; + +const ActivePublishedLink = (props: any) => { + return ( + + {" "} + + {props.title} + + + {props.link} + + + ); +}; + +const PublishedLinkContent = (props: PublishedLinkProps) => { + return ( + + {props.subdomain && props.status === "online" && props.isPublished ? ( + + ) : props.subdomain && props.status === "online" && !props.isPublished ? ( + + ) : props.subdomain && props.status === "offline" && props.isPublished ? ( + + ) : ( + + )} + {props.isPublished && props.status === "online" ? ( + + ) : ( + + )} + + ); +}; + +const LinkContainer = (props: LinkProps) => { + const infoPadding = "31px"; + return ( + + + + <>{props.titleIcon} + + {props.title} + + {props.type !== "published" && } + + {props.description} + {props.status === "offline" && props.type === "published" && ( + + You can switch it to “Online” in your{" "} + + Service Settings + + + )} + + {props.type === "published" ? ( + + ) : ( + + {props.link} + + )} + + ); +}; + +export default function LinkDialog(props: DialogBaseProps) { + const [setFlowStatus, flowStatus] = useStore((state) => [ + state.setFlowStatus, + state.flowStatus, + ]); + // Retrieving flow status to determine which links to show in View Links + useEffect(() => { + const fetchFlowStatus = async () => { + const { data } = await client.query({ + query: gql` + query GetFlow($slug: String!, $team_slug: String!) { + flows( + limit: 1 + where: { + slug: { _eq: $slug } + team: { slug: { _eq: $team_slug } } + } + ) { + status + } + } + `, + variables: { + slug: props.flowSlug, + team_slug: props.teamSlug, + }, + }); + setFlowStatus(data.flows[0].status); + }; + + fetchFlowStatus(); + }, []); + + const unpublishedOfflineDescription = + "The flow is not yet published and is not yet viewable by the public."; + + const unpublishedOnlineDescription = + "The flow is not yet published and is not viewable by the public"; + + const publishedOfflineDescription = + "The published version of this flow. It is not yet viewable by the public."; + + const publishedOnlineDescription = + "The published version of the flow that is viewable by the public."; + + return ( + props.setLinkDialogOpen(false)} + aria-labelledby="alert-dialog-title" + aria-describedby="alert-dialog-description" + fullWidth + maxWidth="md" + > + + + + + + {`Share this flow`} + + + + } + title={"Published"} + link={props.url} + description={ + !props.isFlowPublished && flowStatus !== "online" + ? unpublishedOfflineDescription + : props.isFlowPublished && flowStatus !== "online" + ? publishedOfflineDescription + : publishedOnlineDescription + } + type="published" + isPublished={props.isFlowPublished} + status={flowStatus} + subdomain={ + props.teamDomain && `${props.teamDomain}/${props.flowSlug}` + } + /> + } + title={"Preview"} + link={props.url.replace("/published", "/preview")} + description="This link is representative of what your next published version will look like. It contains the draft data of the main flow and the latest published version of nested flows. " + type="preview" + />{" "} + + } + title={"Draft"} + link={props.url.replace("/published", "/draft")} + description="This link is not representative of what your next published version will look like. It contains the draft data of the main flow and the draft data of nested flows." + type="draft" + /> + + + + + ); +} diff --git a/editor.planx.uk/src/pages/FlowEditor/components/Sidebar/index.tsx b/editor.planx.uk/src/pages/FlowEditor/components/Sidebar/index.tsx index 5479a2b7fe..a54579eb92 100644 --- a/editor.planx.uk/src/pages/FlowEditor/components/Sidebar/index.tsx +++ b/editor.planx.uk/src/pages/FlowEditor/components/Sidebar/index.tsx @@ -1,8 +1,8 @@ -import LanguageIcon from "@mui/icons-material/Language"; +import AccountTreeIcon from "@mui/icons-material/AccountTree"; +import LinkIcon from "@mui/icons-material/Link"; import MenuOpenIcon from "@mui/icons-material/MenuOpen"; -import OpenInNewIcon from "@mui/icons-material/OpenInNew"; -import OpenInNewOffIcon from "@mui/icons-material/OpenInNewOff"; import RefreshIcon from "@mui/icons-material/Refresh"; +import SchemaIcon from "@mui/icons-material/Schema"; import Badge from "@mui/material/Badge"; import Box from "@mui/material/Box"; import Button from "@mui/material/Button"; @@ -11,6 +11,7 @@ import Dialog from "@mui/material/Dialog"; import DialogActions from "@mui/material/DialogActions"; import DialogContent from "@mui/material/DialogContent"; import DialogTitle from "@mui/material/DialogTitle"; +import IconButton from "@mui/material/IconButton"; import Link from "@mui/material/Link"; import { styled } from "@mui/material/styles"; import Tab, { tabClasses } from "@mui/material/Tab"; @@ -22,12 +23,12 @@ import { hasFeatureFlag } from "lib/featureFlags"; import { formatLastPublishMessage } from "pages/FlowEditor/utils"; import React, { useState } from "react"; import { useAsync } from "react-use"; -import Permission from "ui/editor/Permission"; import Input from "ui/shared/Input"; import Questions from "../../../Preview/Questions"; import { useStore } from "../../lib/store"; import EditHistory from "./EditHistory"; +import LinkDialog from "./LinkDialog"; import { AlteredNode, AlteredNodesSummaryContent, @@ -35,7 +36,6 @@ import { ValidationChecks, } from "./PublishDialog"; import Search from "./Search"; - type SidebarTabs = "PreviewBrowser" | "History" | "Search"; const Console = styled(Box)(() => ({ @@ -72,14 +72,29 @@ const Header = styled("header")(({ theme }) => ({ flex: "1", padding: "5px", marginRight: "5px", - background: theme.palette.common.white, + background: theme.palette.background.paper, + border: "1px solid rgba(0, 0, 0, 0.2)", }, - "& svg": { - cursor: "pointer", - opacity: "0.7", - margin: "6px 4px 1px 4px", - fontSize: "1.2rem", +})); + +const SecondaryButton = styled(IconButton)(({ theme }) => ({ + color: theme.palette.common.black, +})); + +const ViewButton = styled(Button)(({ theme }) => ({ + background: theme.palette.common.white, + border: `1px solid ${theme.palette.border.main}`, + boxShadow: "none", + color: theme.palette.common.black, + width: "30%", + display: "flex", + flexDirection: "row", + gap: "8px", + borderRadius: "3px", + padding: "8px", + "&:hover": { + boxShadow: "none", }, })); @@ -164,6 +179,11 @@ const Sidebar: React.FC<{ lastPublisher, validateAndDiffFlow, isFlowPublished, + fetchCurrentTeam, + togglePreview, + flowSlug, + teamSlug, + teamDomain, ] = useStore((state) => [ state.id, state.resetPreview, @@ -172,6 +192,11 @@ const Sidebar: React.FC<{ state.lastPublisher, state.validateAndDiffFlow, state.isFlowPublished, + state.fetchCurrentTeam, + state.togglePreview, + state.flowSlug, + state.teamSlug, + state.teamDomain, ]); const [key, setKey] = useState(false); const [lastPublishedTitle, setLastPublishedTitle] = useState( @@ -184,6 +209,7 @@ const Sidebar: React.FC<{ const [dialogOpen, setDialogOpen] = useState(false); const [summary, setSummary] = useState(); const [activeTab, setActiveTab] = useState("PreviewBrowser"); + const [linkDialogOpen, setLinkDialogOpen] = useState(false); const handleChange = (event: React.SyntheticEvent, newValue: SidebarTabs) => { setActiveTab(newValue); @@ -246,86 +272,63 @@ const Sidebar: React.FC<{ ); }); - // useStore.getState().getTeam().slug undefined here, use window instead - const teamSlug = window.location.pathname.split("/")[1]; - return (
    - - - - - { - resetPreview(); - setKey((a) => !a); - }} - /> - - - - setDebugConsoleVisibility(!showDebugConsole)} - /> - - - - - - - - - - - - - - - - - {isFlowPublished ? ( - - - - + + + + { + resetPreview(); + setKey((a) => !a); + }} + /> - ) : ( - - - - - - + + + + setDebugConsoleVisibility(!showDebugConsole)} + /> - )} + - - + setLinkDialogOpen(true)} + disabled={!useStore.getState().canUserEditTeam(teamSlug)} > + View links + + + + - CHECK FOR CHANGES TO PUBLISH + Check for changes to publish - - {lastPublishedTitle} + + + {lastPublishedTitle} + 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 824e6cb93b..2b424399ab 100644 --- a/editor.planx.uk/src/pages/FlowEditor/lib/store/team.ts +++ b/editor.planx.uk/src/pages/FlowEditor/lib/store/team.ts @@ -14,6 +14,7 @@ export interface TeamStore { teamId: number; teamIntegrations: TeamIntegrations; teamName: string; + teamDomain?: string; teamSettings: TeamSettings; teamSlug: string; teamTheme: TeamTheme; @@ -37,6 +38,7 @@ export const teamStore: StateCreator< teamId: 0, teamIntegrations: {} as TeamIntegrations, teamName: "", + teamDomain: "", teamSettings: {} as TeamSettings, teamSlug: "", teamTheme: {} as TeamTheme, @@ -46,6 +48,7 @@ export const teamStore: StateCreator< teamId: team.id, teamIntegrations: team.integrations, teamName: team.name, + teamDomain: team.domain, teamSettings: team.settings, teamSlug: team.slug, teamTheme: team.theme, @@ -61,6 +64,7 @@ export const teamStore: StateCreator< id: get().teamId, integrations: get().teamIntegrations, name: get().teamName, + domain: get().teamDomain, settings: get().teamSettings, slug: get().teamSlug, theme: get().teamTheme, @@ -84,6 +88,11 @@ export const teamStore: StateCreator< id name slug + domain + theme { + primaryColour: primary_colour + logo + } flows(order_by: { updated_at: desc }) { slug updated_at