Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add Preview/Draft Layout Wrapper for Testing Links #4090

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
9 changes: 7 additions & 2 deletions editor.planx.uk/src/@planx/components/Send/Public.test.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { SendIntegration } from "@opensystemslab/planx-core/types";
import { waitFor } from "@testing-library/react";
import { screen, waitFor } from "@testing-library/react";
import axios from "axios";
import { FullStore, useStore } from "pages/FlowEditor/lib/store";
import React from "react";
import { act } from "react-dom/test-utils";
import * as ReactNavi from "react-navi";
import { setup } from "testUtils";
import { it,vi } from "vitest";
import { it, vi } from "vitest";
import { axe } from "vitest-axe";

import hasuraEventsResponseMock from "./mocks/hasuraEventsResponseMock";
Expand All @@ -16,6 +17,10 @@ const { getState, setState } = useStore;

let initialState: FullStore;

vi.spyOn(ReactNavi, "useNavigation").mockImplementation(
() => ({ navigate: vi.fn() }) as any,
);

/**
* Adds a small tick to allow MUI to render (e.g. card transitions)
*/
Expand Down
23 changes: 4 additions & 19 deletions editor.planx.uk/src/@planx/components/shared/Preview/Card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,11 @@ import Button from "@mui/material/Button";
import Container from "@mui/material/Container";
import Fade from "@mui/material/Fade";
import { styled, Theme, useTheme } from "@mui/material/styles";
import { ComponentType as TYPES } from "@opensystemslab/planx-core/types";
import { useAnalyticsTracking } from "pages/FlowEditor/lib/analytics/provider";
import { useStore } from "pages/FlowEditor/lib/store";
import React, { useEffect } from "react";
import { ApplicationPath } from "types";

import SaveResumeButton from "./SaveResumeButton";
import OrNavigationButton from "./OrNavigationButton";

interface Props {
children: React.ReactNode;
Expand Down Expand Up @@ -41,6 +39,7 @@ const InnerContainer = styled(Box)(({ theme }) => ({
* @param {object} props Component props
* @param {bool} props.handleSubmit if included then show the Continue button
* @param {bool} props.isValid if falsey then disable Continue button, otherwise enable
* @param {bool} props.isTestWarningWrapper if truthy then show navigate to publish Or button
*/
const Card: React.FC<Props> = ({
children,
Expand All @@ -49,21 +48,7 @@ const Card: React.FC<Props> = ({
...props
}) => {
const theme = useTheme();
const [path, visibleNode, breadcrumbs, flow] = useStore((state) => [
state.path,
state.currentCard,
state.breadcrumbs,
state.flow,
]);

// Check if we have a Send node in our breadcrumbs
// This is a better/more immediate proxy for "submitted" in the frontend because actual send events that populate lowcal_sessions.submitted_at are queued via Hasura
const hasSent = Object.keys(breadcrumbs).some(
(breadcrumbNodeId: string) => flow[breadcrumbNodeId]?.type === TYPES.Send,
);

const showSaveResumeButton =
path === ApplicationPath.SaveAndReturn && handleSubmit && !hasSent;
const [visibleNode] = useStore((state) => [state.currentCard]);
const { track } = useAnalyticsTracking();

useEffect(() => {
Expand Down Expand Up @@ -101,7 +86,7 @@ const Card: React.FC<Props> = ({
Continue
</Button>
)}
{showSaveResumeButton && <SaveResumeButton />}
<OrNavigationButton handleSubmit={handleSubmit} />
</Box>
</InnerContainer>
</Container>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import Link from "@mui/material/Link";
import Typography from "@mui/material/Typography";
import { clearLocalFlow } from "lib/local";
import { useStore } from "pages/FlowEditor/lib/store";
import React from "react";
import { useNavigation } from "react-navi";

const NavigateToPublishedButton: React.FC = () => {
const { navigate } = useNavigation();
const id = useStore().id;

const handleClick = () => {
clearLocalFlow(id);
navigate("published?analytics=false");
window.location.reload();
};

return (
<Link onClick={handleClick} component="button">
<Typography variant="body1" textAlign="left">
Go to the published version of this service
</Typography>
</Link>
);
};

export default NavigateToPublishedButton;
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import Box from "@mui/material/Box";
import { styled } from "@mui/material/styles";
import Typography from "@mui/material/Typography";
import { ComponentType as TYPES } from "@opensystemslab/planx-core/types";
import { useStore } from "pages/FlowEditor/lib/store";
import React from "react";
import { ApplicationPath } from "types";

import { contentFlowSpacing } from "./Card";
import NavigateToPublishedButton from "./NavigateToPublishedButton";
import SaveResumeButton from "./SaveResumeButton";

type OrNavigationType = "save-resume" | "navigate-to-published";

export const InnerContainer = styled(Box)(({ theme }) => ({
"& p": {
...contentFlowSpacing(theme),
},
}));

const BUTTON_COMPONENTS = {
"save-resume": SaveResumeButton,
"navigate-to-published": NavigateToPublishedButton,
} as const;

const TEST_ENVIRONMENTS = new Set(["preview", "draft"]);

const OrNavigationButton = ({
handleSubmit,
}: {
handleSubmit: ((data?: any) => void) | undefined;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
handleSubmit: ((data?: any) => void) | undefined;
handleSubmit: ((data?: unknown) => void) | undefined;

We should actually come back and type handleSubmit properly, but for now let's work to not introduce any new anys to the codebase.

}) => {
const [path, breadcrumbs, flow] = useStore((state) => [
state.path,
state.breadcrumbs,
state.flow,
]);

const endOfUrl = window.location.pathname.split("/").slice(-1)[0];

const isTestEnvironment: boolean = TEST_ENVIRONMENTS.has(endOfUrl);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Redundant type?

P.S. love to see a Set being used 👏


const defineNavigationType = (): OrNavigationType | undefined => {
// Check if we have a Send node in our breadcrumbs
// This is a better/more immediate proxy for "submitted" in the frontend because actual send events that populate lowcal_sessions.submitted_at are queued via Hasura
const hasSent = Object.keys(breadcrumbs).some(
(breadcrumbNodeId: string) => flow[breadcrumbNodeId]?.type === TYPES.Send,
);

const showSaveResumeButton =
path === ApplicationPath.SaveAndReturn && handleSubmit && !hasSent;

if (showSaveResumeButton && !isTestEnvironment) {
return "save-resume";
}

if (!showSaveResumeButton && isTestEnvironment) {
return "navigate-to-published";
}
};

const orNavigationType = defineNavigationType();

const ButtonComponent =
orNavigationType && BUTTON_COMPONENTS[orNavigationType];

return (
ButtonComponent && (
<InnerContainer>
<Typography variant="body1">or</Typography>
<ButtonComponent />
</InnerContainer>
)
);
Comment on lines +62 to +74
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const orNavigationType = defineNavigationType();
const ButtonComponent =
orNavigationType && BUTTON_COMPONENTS[orNavigationType];
return (
ButtonComponent && (
<InnerContainer>
<Typography variant="body1">or</Typography>
<ButtonComponent />
</InnerContainer>
)
);
const orNavigationType = defineNavigationType();
if (!orNavigationType) return null;
const ButtonComponent = BUTTON_COMPONENTS[orNavigationType];
return (
<InnerContainer>
<Typography variant="body1">or</Typography>
<ButtonComponent />
</InnerContainer>
);

An early return here feels more idiomatic imho.

This is a bit more clear and explicit - if the condition it not met, don't show this component.

It also helps us with type narrowing - we no longer need to check for the ButtonComponent which must always be defined.

};

export default OrNavigationButton;
Original file line number Diff line number Diff line change
@@ -1,20 +1,9 @@
import Box from "@mui/material/Box";
import Link from "@mui/material/Link";
import { styled } from "@mui/material/styles";
import Typography from "@mui/material/Typography";
import { useAnalyticsTracking } from "pages/FlowEditor/lib/analytics/provider";
import { useStore } from "pages/FlowEditor/lib/store";
import React from "react";
import { ApplicationPath } from "types";

import { contentFlowSpacing } from "./Card";

const InnerContainer = styled(Box)(({ theme }) => ({
"& p": {
...contentFlowSpacing(theme),
},
}));

const SaveResumeButton: React.FC = () => {
const saveToEmail = useStore((state) => state.saveToEmail);
const { trackEvent } = useAnalyticsTracking();
Expand All @@ -33,19 +22,14 @@ const SaveResumeButton: React.FC = () => {
}
};

const onClick = () => handleClick();

return (
<InnerContainer>
<Typography variant="body1">or</Typography>
<Link component="button" onClick={onClick}>
<Typography variant="body1" textAlign="left">
{saveToEmail
? "Save and return to this application later"
: "Resume an application you have already started"}
</Typography>
</Link>
</InnerContainer>
<Link component="button" onClick={handleClick}>
<Typography variant="body1" textAlign="left">
{saveToEmail
? "Save and return to this application later"
: "Resume an application you have already started"}
</Typography>
</Link>
);
};

Expand Down
24 changes: 24 additions & 0 deletions editor.planx.uk/src/pages/Preview/TestWarningPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import Box from "@mui/material/Box";
import Card from "@planx/components/shared/Preview/Card";
import { CardHeader } from "@planx/components/shared/Preview/CardHeader/CardHeader";
import React, { PropsWithChildren, useState } from "react";

export const TestWarningPage = ({ children }: PropsWithChildren) => {
const [hasAcknowledgedWarning, setHasAcknowledgedWarning] = useState(false);
return (
<>
{hasAcknowledgedWarning ? (
children
) : (
<Box width="100%">
<Card handleSubmit={() => setHasAcknowledgedWarning(true)}>
<CardHeader
title="This is a test environment"
description="This version of the service is unpublished and for testing only. Do not use it to submit real applications"
></CardHeader>
</Card>
</Box>
)}
</>
);
};
5 changes: 4 additions & 1 deletion editor.planx.uk/src/routes/views/draft.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { publicClient } from "lib/graphql";
import { NaviRequest, NotFoundError } from "navi";
import { useStore } from "pages/FlowEditor/lib/store";
import PublicLayout from "pages/layout/PublicLayout";
import { TestWarningPage } from "pages/Preview/TestWarningPage";
import React from "react";
import { View } from "react-navi";
import { Flow, GlobalSettings } from "types";
Expand Down Expand Up @@ -39,7 +40,9 @@ export const draftView = async (req: NaviRequest) => {

return (
<PublicLayout>
<View />
<TestWarningPage>
<View />
</TestWarningPage>
</PublicLayout>
);
};
Expand Down
5 changes: 4 additions & 1 deletion editor.planx.uk/src/routes/views/preview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import axios, { AxiosError } from "axios";
import { NaviRequest, NotFoundError } from "navi";
import { useStore } from "pages/FlowEditor/lib/store";
import PublicLayout from "pages/layout/PublicLayout";
import { TestWarningPage } from "pages/Preview/TestWarningPage";
import React from "react";
import { View } from "react-navi";
import { getTeamFromDomain } from "routes/utils";
Expand Down Expand Up @@ -40,7 +41,9 @@ export const previewView = async (req: NaviRequest) => {

return (
<PublicLayout>
<View />
<TestWarningPage>
<View />
</TestWarningPage>
</PublicLayout>
);
};
Expand Down
Loading