diff --git a/app/client/cypress/e2e/Regression/ClientSide/Templates/ForkTemplateToGitConnectedApp.js b/app/client/cypress/e2e/Regression/ClientSide/Templates/ForkTemplateToGitConnectedApp.js index 091e0bef94ff..1140a8591fcf 100644 --- a/app/client/cypress/e2e/Regression/ClientSide/Templates/ForkTemplateToGitConnectedApp.js +++ b/app/client/cypress/e2e/Regression/ClientSide/Templates/ForkTemplateToGitConnectedApp.js @@ -32,10 +32,12 @@ describe( }); it("1.Bug #17002 Forking a template into an existing app which is connected to git makes the application go into a bad state ", function () { - PageList.AddNewPage("Add page from template"); - _.agHelper.AssertElementExist(template.templateDialogBox); - _.agHelper.GetNClick(template.templateCard); - _.agHelper.GetNClick(template.templateViewForkButton); + cy.get(template.startFromTemplateCard).click(); + _.assertHelper.AssertNetworkStatus("fetchTemplate"); + + cy.get(template.templateDialogBox).should("be.visible"); + cy.get(template.templateCard).first().click(); + cy.get(template.templateViewForkButton).first().click(); cy.waitUntil(() => cy.xpath("//span[text()='Setting up the template']"), { errorMsg: "Setting Templates did not finish even after 75 seconds", timeout: 75000, diff --git a/app/client/cypress/e2e/Regression/ClientSide/Templates/Fork_Template_To_App_spec.ts b/app/client/cypress/e2e/Regression/ClientSide/Templates/Fork_Template_To_App_spec.ts index 2838e49a8674..e39a130e675c 100644 --- a/app/client/cypress/e2e/Regression/ClientSide/Templates/Fork_Template_To_App_spec.ts +++ b/app/client/cypress/e2e/Regression/ClientSide/Templates/Fork_Template_To_App_spec.ts @@ -33,7 +33,7 @@ describe( it("2. Add selected pages from template to an app", () => { homePage.CreateNewApplication(); - PageList.AddNewPage("Add page from template"); + agHelper.GetNClick(template.startFromTemplateCard); agHelper.AssertElementVisibility(template.templateDialogBox); agHelper.GetNClick("//h1[text()='Applicant Tracker-test']"); agHelper.FailIfErrorToast( diff --git a/app/client/src/ce/constants/messages.ts b/app/client/src/ce/constants/messages.ts index fe5bb57ef570..233a78e4b7ff 100644 --- a/app/client/src/ce/constants/messages.ts +++ b/app/client/src/ce/constants/messages.ts @@ -1771,6 +1771,9 @@ export const WIDGET_USED = () => "Widgets"; export const SIMILAR_TEMPLATES = () => "Similar templates"; export const VIEW_ALL_TEMPLATES = () => "View all templates"; export const FILTERS = () => "Filters"; +export const TEMPLATE_CARD_TITLE = () => "Start from a template"; +export const TEMPLATE_CARD_DESCRIPTION = () => + "Create app from template by selecting pages"; export const FILTER_SELECTALL = () => "Select all"; export const FILTER_SELECT_PAGE = () => "Add selected page"; export const FILTER_SELECT_PAGES = () => "Add selected pages"; @@ -1823,6 +1826,9 @@ export const SEARCH_USERS = ( export const CREATE_PAGE = () => "New blank page"; export const CANVAS_NEW_PAGE_CARD = () => "Create new page"; +export const GENERATE_PAGE = () => "Generate page from data table"; +export const GENERATE_PAGE_DESCRIPTION = () => + "Start app with a simple CRUD UI and customize it"; export const ADD_PAGE_FROM_TEMPLATE = () => "Add page from template"; export const INVALID_URL = () => "Please enter a valid URL, for example, https://example.com"; diff --git a/app/client/src/pages/Editor/WidgetsEditor/WidgetEditorHeader.tsx b/app/client/src/pages/Editor/WidgetsEditor/WidgetEditorHeader.tsx index 122f29fa4f8a..6e579064db62 100644 --- a/app/client/src/pages/Editor/WidgetsEditor/WidgetEditorHeader.tsx +++ b/app/client/src/pages/Editor/WidgetsEditor/WidgetEditorHeader.tsx @@ -1,6 +1,12 @@ -import useMissingModuleNotification from "@appsmith/pages/Editor/IDE/MainPane/useMissingModuleNotification"; -import AnonymousDataPopup from "pages/Editor/FirstTimeUserOnboarding/AnonymousDataPopup"; import React from "react"; +import AnonymousDataPopup from "pages/Editor/FirstTimeUserOnboarding/AnonymousDataPopup"; +import EmptyCanvasPrompts from "./components/EmptyCanvasPrompts"; +import { useSelector } from "react-redux"; +import { combinedPreviewModeSelector } from "selectors/editorSelectors"; +import { getIsAppSettingsPaneWithNavigationTabOpen } from "selectors/appSettingsPaneSelectors"; +import { useCurrentAppState } from "pages/Editor/IDE/hooks"; +import { EditorState } from "@appsmith/entities/IDE/constants"; +import useMissingModuleNotification from "@appsmith/pages/Editor/IDE/MainPane/useMissingModuleNotification"; /** * WidgetEditorHeader @@ -11,9 +17,19 @@ import React from "react"; * - missing module notification */ export const WidgetEditorHeader = () => { + const isNavigationSelectedInSettings = useSelector( + getIsAppSettingsPaneWithNavigationTabOpen, + ); + const appState = useCurrentAppState(); + const isAppSettingsPaneWithNavigationTabOpen = + appState === EditorState.SETTINGS && isNavigationSelectedInSettings; + const isPreviewMode = useSelector(combinedPreviewModeSelector); const missingModuleNotification = useMissingModuleNotification(); return ( <> + {!isAppSettingsPaneWithNavigationTabOpen && ( + + )} {missingModuleNotification} diff --git a/app/client/src/pages/Editor/WidgetsEditor/components/EmptyCanvasPrompts.tsx b/app/client/src/pages/Editor/WidgetsEditor/components/EmptyCanvasPrompts.tsx new file mode 100644 index 000000000000..71dfa584bafa --- /dev/null +++ b/app/client/src/pages/Editor/WidgetsEditor/components/EmptyCanvasPrompts.tsx @@ -0,0 +1,170 @@ +import React, { useEffect } from "react"; +import styled from "styled-components"; +import { Text, TextType } from "design-system-old"; +import { useDispatch, useSelector } from "react-redux"; +import { + selectURLSlugs, + showCanvasTopSectionSelector, +} from "selectors/editorSelectors"; +import AnalyticsUtil from "@appsmith/utils/AnalyticsUtil"; +import history from "utils/history"; +import { generateTemplateFormURL } from "@appsmith/RouteBuilder"; +import { useParams } from "react-router"; +import type { ExplorerURLParams } from "@appsmith/pages/Editor/Explorer/helpers"; +import { showTemplatesModal as showTemplatesModalAction } from "actions/templateActions"; +import { + createMessage, + GENERATE_PAGE, + GENERATE_PAGE_DESCRIPTION, + TEMPLATE_CARD_DESCRIPTION, + TEMPLATE_CARD_TITLE, +} from "@appsmith/constants/messages"; +import { deleteCanvasCardsState } from "actions/editorActions"; +import { isAirgapped } from "@appsmith/utils/airgapHelpers"; +import { Icon } from "design-system"; +import { + LayoutSystemFeatures, + useLayoutSystemFeatures, +} from "layoutSystems/common/useLayoutSystemFeatures"; + +const Wrapper = styled.div` + margin: ${(props) => + `${props.theme.spaces[7]}px ${props.theme.spaces[16]}px 0px ${props.theme.spaces[13]}px`}; + display: flex; + flex-direction: row; + gap: ${(props) => props.theme.spaces[7]}px; +`; + +const Card = styled.div<{ centerAlign?: boolean }>` + padding: ${(props) => + `${props.theme.spaces[5]}px ${props.theme.spaces[9]}px`}; + border: solid 1px var(--ads-v2-color-border); + background: var(--ads-v2-color-bg); + flex: 1; + display: flex; + flex-direction: row; + align-items: center; + border-radius: var(--ads-v2-border-radius); + ${(props) => + props.centerAlign && + ` + justify-content: center; + `} + cursor: pointer; + + svg { + height: 24px; + width: 24px; + } + &:hover { + background-color: var(--ads-v2-color-bg-subtle); + } +`; + +const Content = styled.div` + display: flex; + flex-direction: column; + padding-left: ${(props) => props.theme.spaces[7]}px; +`; + +interface routeId { + applicationSlug: string; + pageId: string; + pageSlug: string; +} + +const goToGenPageForm = ({ pageId }: routeId): void => { + AnalyticsUtil.logEvent("GEN_CRUD_PAGE_ACTION_CARD_CLICK"); + history.push(generateTemplateFormURL({ pageId })); +}; + +interface EmptyCanvasPromptsProps { + isPreview: boolean; +} + +/** + * OldName: CanvasTopSection + */ +/** + * This Component encompasses the prompts for empty canvas + * prompts like generate crud app or import from template + * @param props Object that contains + * @prop isPreview, boolean to indicate preview mode + * @returns + */ +function EmptyCanvasPrompts(props: EmptyCanvasPromptsProps) { + const dispatch = useDispatch(); + const showCanvasTopSection = useSelector(showCanvasTopSectionSelector); + const { isPreview } = props; + const { pageId } = useParams(); + const { applicationSlug, pageSlug } = useSelector(selectURLSlugs); + + const checkLayoutSystemFeatures = useLayoutSystemFeatures(); + const [enableForkingFromTemplates, enableGenerateCrud] = + checkLayoutSystemFeatures([ + LayoutSystemFeatures.ENABLE_FORKING_FROM_TEMPLATES, + LayoutSystemFeatures.ENABLE_GENERATE_CRUD_APP, + ]); + + useEffect(() => { + if (!showCanvasTopSection && !isPreview) { + dispatch(deleteCanvasCardsState()); + } + }, [showCanvasTopSection, isPreview]); + + if (!showCanvasTopSection || isPreview) return null; + + const showTemplatesModal = () => { + dispatch(showTemplatesModalAction({ isOpenFromCanvas: false })); + AnalyticsUtil.logEvent("CANVAS_BLANK_PAGE_CTA_CLICK", { + item: "ADD_PAGE_FROM_TEMPLATE", + }); + }; + + const onGeneratePageClick = () => { + goToGenPageForm({ applicationSlug, pageSlug, pageId }); + AnalyticsUtil.logEvent("CANVAS_BLANK_PAGE_CTA_CLICK", { + item: "GENERATE_PAGE", + }); + }; + + const isAirgappedInstance = isAirgapped(); + const showCanvasPrompts = + (enableForkingFromTemplates && !isAirgappedInstance) || enableGenerateCrud; + return showCanvasPrompts ? ( + + {enableForkingFromTemplates && !isAirgappedInstance && ( + + + + + {createMessage(TEMPLATE_CARD_TITLE)} + + + {createMessage(TEMPLATE_CARD_DESCRIPTION)} + + + + )} + {enableGenerateCrud && ( + + + + + {createMessage(GENERATE_PAGE)} + + + {createMessage(GENERATE_PAGE_DESCRIPTION)} + + + + )} + + ) : null; +} + +export default EmptyCanvasPrompts;