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

Layouts in add page flow #376

Merged
merged 36 commits into from
Sep 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
bc3f51d
basic solution
alextaing Sep 20, 2023
f510ded
playwright test
alextaing Sep 20, 2023
84ec9b2
Automated linting update and features.json sync
github-actions[bot] Sep 20, 2023
164733e
Updated snapshots for windows-latest 1 of 4
github-actions[bot] Sep 20, 2023
bb79985
fix tests
alextaing Sep 20, 2023
1155599
Automated linting update and features.json sync
github-actions[bot] Sep 20, 2023
0f0aec3
simplify logic and move validation
alextaing Sep 21, 2023
619de8b
Automated linting update and features.json sync
github-actions[bot] Sep 21, 2023
3bdce53
comments!
alextaing Sep 21, 2023
e6a3e47
Automated linting update and features.json sync
github-actions[bot] Sep 21, 2023
5f5bec1
nidhi comments!
alextaing Sep 21, 2023
2fab2c4
Automated linting update and features.json sync
github-actions[bot] Sep 21, 2023
28cff28
remove defaulting
alextaing Sep 21, 2023
ff9f629
add aria label, remove key
alextaing Sep 21, 2023
cc6192f
Automated linting update and features.json sync
github-actions[bot] Sep 21, 2023
564b768
consolidate state updates
alextaing Sep 21, 2023
3b589ff
Automated linting update and features.json sync
github-actions[bot] Sep 21, 2023
6682d94
Merge branch 'main' into dev/layouts-in-add-page-flow
alextaing Sep 21, 2023
e7c85be
fix tests
alextaing Sep 21, 2023
53ea64b
Automated linting update and features.json sync
github-actions[bot] Sep 21, 2023
35ebb8d
remove extra add page check
alextaing Sep 22, 2023
6896629
Automated linting update and features.json sync
github-actions[bot] Sep 22, 2023
1364b8f
remove aria labels, remove redundant addpage test
alextaing Sep 22, 2023
6da0413
Automated linting update and features.json sync
github-actions[bot] Sep 22, 2023
59af2d3
forgot to change test
alextaing Sep 22, 2023
0facf49
Version 0.23.0. (#382)
tmeyer2115 Sep 22, 2023
b174585
Merge branch 'main' into dev/layouts-in-add-page-flow
alextaing Sep 22, 2023
8e8f797
regenerate windows screenshots
alextaing Sep 22, 2023
740094c
Updated snapshots for windows-latest 1 of 4
github-actions[bot] Sep 22, 2023
d3a4d48
retake screenshot windows
alextaing Sep 22, 2023
681b80c
Updated snapshots for macos-latest 1 of 4
github-actions[bot] Sep 22, 2023
b4dbbf9
empty commit
alextaing Sep 22, 2023
7dad218
page validator changes
alextaing Sep 22, 2023
f2d2294
Automated linting update and features.json sync
github-actions[bot] Sep 22, 2023
4405d70
update streamScope updateState
alextaing Sep 22, 2023
b06c904
Merge branch 'main' into dev/layouts-in-add-page-flow
alextaing Sep 22, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
13 changes: 13 additions & 0 deletions e2e-tests/src/layouts/LocationLayout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import Header from "../components/Header";

function LocationLayout() {
return (
<Header
title="Yext"
logo="https://a.mktgcdn.com/p/R9FjcYjRNA5dAespqgHFLMvu2m18-E5Apnb3KON0oJY/300x300.png"
backgroundColor="#BAD8FD"
/>
);
}

export default LocationLayout;
16 changes: 16 additions & 0 deletions e2e-tests/tests/__fixtures__/add-layout-page-expected.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { GetPath, TemplateProps } from "@yext/pages";
import Header from "../components/Header";

export const getPath: GetPath<TemplateProps> = () => {
return "index.html";
};

export default function LayoutPage() {
return (
<Header
title="Yext"
logo="https://a.mktgcdn.com/p/R9FjcYjRNA5dAespqgHFLMvu2m18-E5Apnb3KON0oJY/300x300.png"
backgroundColor="#BAD8FD"
/>
);
}
22 changes: 22 additions & 0 deletions e2e-tests/tests/add-page-with-layout.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { expect } from "@playwright/test";
import { studioTest } from "./infra/studioTest.js";
import fs from "fs";

const expectedPage = fs.readFileSync(
"./tests/__fixtures__/add-layout-page-expected.tsx",
"utf-8"
);

studioTest("can add a page using a layout", async ({ page, studioPage }) => {
const pageName = "LayoutPage";
await studioPage.takePageScreenshotAfterImgRender();
const pageInTree = page.getByText(pageName);
await expect(pageInTree).toHaveCount(0);
await studioPage.addStaticPage(pageName, "index.html", "LocationLayout");
await expect(pageInTree).toHaveCount(1);
await studioPage.takePageScreenshotAfterImgRender();
await studioPage.saveButton.click();
const pagePath = studioPage.getPagePath(pageName);
await expect(pagePath).toHaveContents(expectedPage);
await studioPage.takePageScreenshotAfterImgRender();
});
22 changes: 19 additions & 3 deletions e2e-tests/tests/infra/StudioPlaywrightPage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,21 +59,24 @@ export default class StudioPlaywrightPage {
this.gitOps = new GitOperations(git);
}

async addStaticPage(pageName: string, urlSlug: string) {
async addStaticPage(pageName: string, urlSlug: string, layoutName?: string) {
await this.addPageButton.click();
await this.selectPageType(false);
await this.enterBasicPageData(pageName, urlSlug);
await this.selectLayout(layoutName);
}

async addEntityPage(
pageName: string,
streamScopeForm?: StreamScopeForm,
urlSlug?: string
urlSlug?: string,
layoutName?: string
) {
await this.addPageButton.click();
await this.selectPageType(true);
await this.enterStreamScope(streamScopeForm);
await this.enterBasicPageData(pageName, urlSlug);
await this.selectLayout(layoutName);
}

private async selectPageType(isEntityPage: boolean) {
Expand Down Expand Up @@ -112,7 +115,20 @@ export default class StudioPlaywrightPage {
await this.typeIntoModal(basicDataModal, "URL Slug", urlSlug);
}
await this.takePageScreenshotAfterImgRender();
await this.clickModalButton(basicDataModal, "Save");
await this.clickModalButton(basicDataModal, "Next");
}

private async selectLayout(layoutName?: string) {
const modalName = "Select Layout";
await this.takePageScreenshotAfterImgRender();
if (layoutName) {
const layoutModal = this.page.getByRole("dialog", { name: modalName });
await layoutModal
.getByRole("combobox")
.selectOption({ label: layoutName });
await this.takePageScreenshotAfterImgRender();
}
await this.clickModalButton(modalName, "Save");
await this.page.getByRole("dialog").waitFor({ state: "hidden" });
}

Expand Down
14 changes: 11 additions & 3 deletions packages/studio-ui/src/components/AddPageButton/AddPageButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import AddPageContext from "./AddPageContext";
import useStudioStore from "../../store/useStudioStore";
import AddPageContextProvider from "./AddPageContextProvider";
import { FlowStep, flowStepModalMap } from "./FlowStep";
import { GetPathVal } from "@yext/studio-plugin";
import { LayoutState } from "@yext/studio-plugin";

export default function AddPageButton() {
return (
Expand All @@ -27,7 +27,7 @@ function AddPageButtonInternal() {
const { resetState } = actions;

const handleConfirm = useCallback(
async (pageName = "", getPathVal?: GetPathVal) => {
async (layout?: LayoutState) => {
switch (step) {
case FlowStep.SelectPageType:
setStep(
Expand All @@ -38,7 +38,15 @@ function AddPageButtonInternal() {
setStep(FlowStep.GetBasicPageData);
break;
case FlowStep.GetBasicPageData:
await createPage(pageName, getPathVal, state.streamScope);
setStep(FlowStep.SelectLayout);
break;
case FlowStep.SelectLayout:
await createPage(
state.pageName,
state.getPathVal,
state.streamScope,
layout
);
break;
}
},
Expand Down
16 changes: 5 additions & 11 deletions packages/studio-ui/src/components/AddPageButton/AddPageContext.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import { StreamScope } from "@yext/studio-plugin";
import { createContext, useContext } from "react";
import { GetPathVal, StreamScope } from "@yext/studio-plugin";
import { createContext } from "react";

export interface AddPageData {
tmeyer2115 marked this conversation as resolved.
Show resolved Hide resolved
isStatic: boolean;
streamScope?: StreamScope;
pageName: string;
getPathVal?: GetPathVal;
}

export interface AddPageActions {
setIsStatic: (isStatic: boolean) => void;
setStreamScope: (streamScope: StreamScope) => void;
updateState: (newState: Partial<AddPageData>) => void;
resetState: () => void;
}

Expand All @@ -17,13 +18,6 @@ export interface AddPageContextValue {
actions: AddPageActions;
}

export function useStreamScope() {
const { state, actions } = useContext(AddPageContext);
const { streamScope } = state;
const { setStreamScope } = actions;
return [streamScope, setStreamScope] as const;
}

const AddPageContext = createContext<AddPageContextValue>(
{} as AddPageContextValue
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ import AddPageContext, {
AddPageContextValue,
AddPageData,
} from "./AddPageContext";
import { StreamScope } from "@yext/studio-plugin";

const initialPageData: AddPageData = {
isStatic: true,
pageName: "",
};

export default function AddPageContextProvider(props: PropsWithChildren) {
Expand All @@ -16,9 +16,13 @@ export default function AddPageContextProvider(props: PropsWithChildren) {
() => ({
state,
actions: {
setIsStatic: (isStatic: boolean) => setState({ ...state, isStatic }),
setStreamScope: (streamScope: StreamScope) =>
setState({ ...state, streamScope }),
updateState: (newState: Partial<AddPageData>) =>
setState((oldState) => {
return {
...oldState,
...newState,
};
}),
resetState: () => setState(initialPageData),
},
}),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,15 @@ export default function BasicPageDataCollector({
handleConfirm,
}: FlowStepModalProps) {
const [errorMessage, setErrorMessage] = useState<string>("");
const isPagesJSRepo = useStudioStore(
(store) => store.studioConfig.isPagesJSRepo
);
const { state } = useContext(AddPageContext);
const [isPagesJSRepo, pages] = useStudioStore((store) => [
store.studioConfig.isPagesJSRepo,
store.pages.pages,
]);
const { state, actions } = useContext(AddPageContext);
const isEntityPage = isPagesJSRepo && !state.isStatic;
const pageDataValidator = useMemo(
() => new PageDataValidator(isEntityPage),
[isEntityPage]
() => new PageDataValidator({ isEntityPage, isPagesJSRepo, pages }),
[isEntityPage, isPagesJSRepo, pages]
);

const formData: FormData<BasicPageData> = useMemo(
Expand Down Expand Up @@ -53,19 +54,14 @@ export default function BasicPageDataCollector({
setErrorMessage(validationResult.errorMessages.join("\r\n"));
return false;
}
try {
await handleConfirm(data.pageName, getPathValue);
return true;
} catch (err: unknown) {
if (err instanceof Error) {
setErrorMessage(err.message);
return false;
} else {
throw err;
}
}
actions.updateState({
pageName: data.pageName,
getPathVal: getPathValue,
});
await handleConfirm();
return true;
},
[handleConfirm, isEntityPage, pageDataValidator]
[actions, handleConfirm, isEntityPage, pageDataValidator]
);

const transformOnChangeValue = useCallback(
Expand All @@ -84,10 +80,12 @@ export default function BasicPageDataCollector({
title={modalTitle}
formData={formData}
initialFormValue={initialFormValue}
closeOnConfirm={false}
errorMessage={errorMessage}
handleClose={handleClose}
handleConfirm={onConfirm}
transformOnChangeValue={transformOnChangeValue}
confirmButtonText="Next"
/>
);
}
Expand Down
7 changes: 5 additions & 2 deletions packages/studio-ui/src/components/AddPageButton/FlowStep.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
import { GetPathVal } from "@yext/studio-plugin";
import BasicPageDataCollector from "./BasicPageDataCollector";
import PageTypeSelector from "./PageTypeSelector";
import StreamScopeCollector from "./StreamScopeCollector";
import LayoutSelector from "./LayoutSelector";
import { LayoutState } from "@yext/studio-plugin";

export enum FlowStep {
SelectPageType,
GetStreamScope,
GetBasicPageData,
SelectLayout,
}

export interface FlowStepModalProps {
isOpen: boolean;
handleClose: () => Promise<void>;
handleConfirm: (pageName?: string, getPathVal?: GetPathVal) => Promise<void>;
handleConfirm: (layout?: LayoutState) => Promise<void>;
}

type FlowStepModalMap = {
Expand All @@ -23,4 +25,5 @@ export const flowStepModalMap: FlowStepModalMap = {
[FlowStep.SelectPageType]: PageTypeSelector,
[FlowStep.GetStreamScope]: StreamScopeCollector,
[FlowStep.GetBasicPageData]: BasicPageDataCollector,
[FlowStep.SelectLayout]: LayoutSelector,
};
72 changes: 72 additions & 0 deletions packages/studio-ui/src/components/AddPageButton/LayoutSelector.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { ChangeEvent, useCallback, useState } from "react";
import { FlowStepModalProps } from "./FlowStep";
import useStudioStore from "../../store/useStudioStore";
import DialogModal from "../common/DialogModal";
import getSelectCssClasses from "../../utils/getSelectCssClasses";

/**
* This modal allows the user to select a layout for their
* new page. If no layout is selected, then no layout will
* be applied.
*/
export default function LayoutSelector({
alextaing marked this conversation as resolved.
Show resolved Hide resolved
isOpen,
handleClose,
handleConfirm,
}: FlowStepModalProps) {
const layouts = useStudioStore(
(store) => store.layouts.layoutNameToLayoutState
);
const [selectedLayout, setSelectedLayout] = useState<string>("");
const [errorMessage, setErrorMessage] = useState<string>();

const onConfirm = useCallback(async () => {
const layoutState = layouts[selectedLayout];

try {
await handleConfirm(layoutState);
await handleClose();
tmeyer2115 marked this conversation as resolved.
Show resolved Hide resolved
} catch (err: unknown) {
if (err instanceof Error) {
setErrorMessage(err.message);
} else {
throw err;
}
}
}, [handleClose, handleConfirm, layouts, selectedLayout]);

const handleChange = useCallback(
(e: ChangeEvent<HTMLSelectElement>) => {
setSelectedLayout(e.target.value);
},
[setSelectedLayout]
);

const selectClasses = getSelectCssClasses("w-full mb-6");
const body = (
<select
className={selectClasses}
onChange={handleChange}
aria-label="Layout picker"
>
<option>No layout selected</option>
{Object.keys(layouts).map((layoutName) => (
<option key={layoutName} value={layoutName}>
{layoutName}
</option>
))}
</select>
);

return (
<DialogModal
isOpen={isOpen}
handleClose={handleClose}
handleConfirm={onConfirm}
title="Select Layout"
body={body}
confirmButtonText="Save"
errorMessage={errorMessage}
/>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,13 @@ export default function PageTypeSelector({
}: FlowStepModalProps) {
const { state, actions } = useContext(AddPageContext);
const { isStatic } = state;
const { setIsStatic } = actions;

const onChange = useCallback(
(e: ChangeEvent<HTMLInputElement>) => {
setIsStatic(e.target.name === PageType.Static);
const isStatic = e.target.name === PageType.Static;
actions.updateState({ isStatic });
},
[setIsStatic]
[actions]
);

const body = useMemo(
Expand Down
Loading