diff --git a/app/client/src/actions/onboardingActions.ts b/app/client/src/actions/onboardingActions.ts
index ba7a2efbad..4c4793b3df 100644
--- a/app/client/src/actions/onboardingActions.ts
+++ b/app/client/src/actions/onboardingActions.ts
@@ -223,3 +223,18 @@ export const loadGuidedTour = (guidedTourState: GuidedTourState) => {
payload: guidedTourState,
};
};
+
+export const setCurrentApplicationIdForCreateNewApp = (
+ applicationId: string,
+) => {
+ return {
+ type: ReduxActionTypes.SET_CURRENT_APPLICATION_ID_FOR_CREATE_NEW_APP,
+ payload: applicationId,
+ };
+};
+
+export const resetCurrentApplicationIdForCreateNewApp = () => {
+ return {
+ type: ReduxActionTypes.RESET_CURRENT_APPLICATION_ID_FOR_CREATE_NEW_APP,
+ };
+};
diff --git a/app/client/src/actions/templateActions.ts b/app/client/src/actions/templateActions.ts
index e0a21c5b06..b875e26aef 100644
--- a/app/client/src/actions/templateActions.ts
+++ b/app/client/src/actions/templateActions.ts
@@ -92,3 +92,20 @@ export const hideStarterBuildingBlockDatasourcePrompt = () => ({
export const getTemplateFilters = () => ({
type: ReduxActionTypes.GET_TEMPLATE_FILTERS_INIT,
});
+
+export const importTemplateIntoApplicationViaOnboardingFlow = (
+ templateId: string,
+ templateName: string,
+ pageNames: string[],
+ applicationId: string,
+ workspaceId: string,
+) => ({
+ type: ReduxActionTypes.IMPORT_TEMPLATE_TO_APPLICATION_ONBOARDING_FLOW,
+ payload: {
+ templateId,
+ templateName,
+ pageNames,
+ applicationId,
+ workspaceId,
+ },
+});
diff --git a/app/client/src/assets/images/start-from-scratch.svg b/app/client/src/assets/images/start-from-scratch.svg
new file mode 100644
index 0000000000..5814d9d9ad
--- /dev/null
+++ b/app/client/src/assets/images/start-from-scratch.svg
@@ -0,0 +1,40 @@
+
diff --git a/app/client/src/assets/images/start-from-template.svg b/app/client/src/assets/images/start-from-template.svg
new file mode 100644
index 0000000000..d5457ee089
--- /dev/null
+++ b/app/client/src/assets/images/start-from-template.svg
@@ -0,0 +1,305 @@
+
diff --git a/app/client/src/ce/constants/ReduxActionConstants.tsx b/app/client/src/ce/constants/ReduxActionConstants.tsx
index 23b0cf1012..25e0feb02e 100644
--- a/app/client/src/ce/constants/ReduxActionConstants.tsx
+++ b/app/client/src/ce/constants/ReduxActionConstants.tsx
@@ -886,6 +886,14 @@ const ActionTypes = {
UPDATE_ACTION_DATA: "UPDATE_ACTION_DATA",
SET_ACTIVE_EDITOR_FIELD: "SET_ACTIVE_EDITOR_FIELD",
RESET_ACTIVE_EDITOR_FIELD: "RESET_ACTIVE_EDITOR_FIELD",
+ SET_CURRENT_APPLICATION_ID_FOR_CREATE_NEW_APP:
+ "SET_CURRENT_APPLICATION_ID_FOR_CREATE_NEW_APP",
+ RESET_CURRENT_APPLICATION_ID_FOR_CREATE_NEW_APP:
+ "RESET_CURRENT_APPLICATION_ID_FOR_CREATE_NEW_APP",
+ IMPORT_TEMPLATE_TO_APPLICATION_ONBOARDING_FLOW:
+ "IMPORT_TEMPLATE_TO_APPLICATION_ONBOARDING_FLOW",
+ IMPORT_TEMPLATE_TO_APPLICATION_ONBOARDING_FLOW_SUCCESS:
+ "IMPORT_TEMPLATE_TO_APPLICATION_ONBOARDING_FLOW_SUCCESS",
};
export const ReduxActionTypes = {
@@ -1077,6 +1085,8 @@ export const ReduxActionErrorTypes = {
PUBLISH_APP_AS_COMMUNITY_TEMPLATE_ERROR:
"PUBLISH_APP_AS_COMMUNITY_TEMPLATE_ERROR",
DELETE_MULTIPLE_APPLICATION_ERROR: "DELETE_MULTIPLE_APPLICATION_ERROR",
+ IMPORT_TEMPLATE_TO_APPLICATION_ONBOARDING_FLOW_ERROR:
+ "IMPORT_TEMPLATE_TO_APPLICATION_ONBOARDING_FLOW_ERROR",
};
export const ReduxFormActionTypes = {
@@ -1222,6 +1232,7 @@ export interface ApplicationPayload {
isPublishingAppToCommunityTemplate?: boolean;
isCommunityTemplate?: boolean;
publishedAppToCommunityTemplate?: boolean;
+ forkedFromTemplateTitle?: string;
}
export interface WorkspaceDetails {
diff --git a/app/client/src/ce/constants/messages.ts b/app/client/src/ce/constants/messages.ts
index b609a954d7..723e638e83 100644
--- a/app/client/src/ce/constants/messages.ts
+++ b/app/client/src/ce/constants/messages.ts
@@ -2236,3 +2236,14 @@ export const STARTER_TEMPLATE_PAGE_LAYOUTS = {
dragAndDrop: () => "Drag and Drop Widgets",
importLoadingText: () => "Importing template",
};
+
+// Create New Apps Intermediary step
+export const CREATE_NEW_APPS_STEP_TITLE = () => "How would you like to start?";
+export const CREATE_NEW_APPS_STEP_SUBTITLE = () =>
+ "Choose an option that fits your approach, and let's shape your app together.";
+export const START_FROM_TEMPLATE_TITLE = () => "Start with use-case";
+export const START_FROM_TEMPLATE_SUBTITLE = () =>
+ "Begin with a specific scenario in mind. We'll guide you through tailoring your app.";
+export const START_FROM_SCRATCH_TITLE = () => "Start from scratch";
+export const START_FROM_SCRATCH_SUBTITLE = () =>
+ "Create an app from the ground up. Design every detail of your app on a blank canvas.";
diff --git a/app/client/src/ce/entities/FeatureFlag.ts b/app/client/src/ce/entities/FeatureFlag.ts
index 5654a4dddc..c72991e4ea 100644
--- a/app/client/src/ce/entities/FeatureFlag.ts
+++ b/app/client/src/ce/entities/FeatureFlag.ts
@@ -35,6 +35,7 @@ export const FEATURE_FLAG = {
license_widget_rtl_support_enabled: "license_widget_rtl_support_enabled",
ab_onboarding_flow_start_with_data_dev_only_enabled:
"ab_onboarding_flow_start_with_data_dev_only_enabled",
+ ab_create_new_apps_enabled: "ab_create_new_apps_enabled",
} as const;
export type FeatureFlag = keyof typeof FEATURE_FLAG;
@@ -67,6 +68,7 @@ export const DEFAULT_FEATURE_FLAG_VALUE: FeatureFlags = {
license_git_branch_protection_enabled: false,
license_widget_rtl_support_enabled: false,
ab_onboarding_flow_start_with_data_dev_only_enabled: false,
+ ab_create_new_apps_enabled: false,
};
export const AB_TESTING_EVENT_KEYS = {
diff --git a/app/client/src/ce/pages/Applications/CreateNewAppsOption.tsx b/app/client/src/ce/pages/Applications/CreateNewAppsOption.tsx
new file mode 100644
index 0000000000..83536b96c4
--- /dev/null
+++ b/app/client/src/ce/pages/Applications/CreateNewAppsOption.tsx
@@ -0,0 +1,367 @@
+import {
+ getTemplateFilters,
+ importTemplateIntoApplicationViaOnboardingFlow,
+} from "actions/templateActions";
+import type { Template } from "api/TemplatesApi";
+import type { AppState } from "@appsmith/reducers";
+import { TemplatesContent } from "pages/Templates";
+import React, { useEffect, useState } from "react";
+import { useDispatch, useSelector } from "react-redux";
+import {
+ allTemplatesFiltersSelector,
+ getForkableWorkspaces,
+ getTemplatesSelector,
+ isImportingTemplateToAppSelector,
+} from "selectors/templatesSelectors";
+import styled from "styled-components";
+import { getAllTemplates } from "actions/templateActions";
+import { Link, Text } from "design-system";
+import {
+ CREATE_NEW_APPS_STEP_SUBTITLE,
+ CREATE_NEW_APPS_STEP_TITLE,
+ GO_BACK,
+ START_FROM_SCRATCH_SUBTITLE,
+ START_FROM_SCRATCH_TITLE,
+ START_FROM_TEMPLATE_SUBTITLE,
+ START_FROM_TEMPLATE_TITLE,
+ createMessage,
+} from "@appsmith/constants/messages";
+import Filters from "pages/Templates/Filters";
+import { isEmpty } from "lodash";
+import StartScratch from "assets/images/start-from-scratch.svg";
+import StartTemplate from "assets/images/start-from-template.svg";
+import AnalyticsUtil from "utils/AnalyticsUtil";
+import { TemplateView } from "pages/Templates/TemplateView";
+import {
+ firstTimeUserOnboardingInit,
+ resetCurrentApplicationIdForCreateNewApp,
+} from "actions/onboardingActions";
+import { getApplicationByIdFromWorkspaces } from "@appsmith/selectors/applicationSelectors";
+import urlBuilder from "@appsmith/entities/URLRedirect/URLAssembly";
+
+const SectionWrapper = styled.div`
+ display: flex;
+ flex-direction: column;
+ padding: 0 var(--ads-v2-spaces-7) var(--ads-v2-spaces-7);
+ ${(props) => `
+ height: calc(100vh - ${props.theme.homePage.header}px);
+ margin-top: ${props.theme.homePage.header}px;
+ `}
+`;
+
+const BackWrapper = styled.div<{ hidden?: boolean }>`
+ position: sticky;
+ ${(props) => `
+ top: ${props.theme.homePage.header}px;
+ `}
+ background: var(--ads-v2-color-bg);
+ padding: var(--ads-v2-spaces-3);
+ z-index: 1;
+ margin-left: -4px;
+ ${(props) => `${props.hidden && "visibility: hidden; opacity: 0;"}`}
+`;
+
+const FiltersWrapper = styled.div`
+ width: ${(props) => props.theme.homePage.sidebar}px;
+ height: 100%;
+ display: flex;
+ flex-direction: column;
+ border-right: 1px solid var(--ads-v2-color-border);
+ flex-shrink: 0;
+ .filter-wrapper {
+ height: 100%;
+ }
+`;
+
+const TemplateWrapper = styled.div`
+ display: flex;
+ flex-grow: 1;
+ overflow: hidden;
+`;
+
+const TemplateContentWrapper = styled.div`
+ flex-grow: 1;
+ overflow: auto;
+`;
+
+const OptionWrapper = styled.div`
+ display: flex;
+ align-items: center;
+ flex-direction: column;
+ justify-content: center;
+ flex-grow: 1;
+`;
+
+const CardsWrapper = styled.div`
+ display: flex;
+ gap: 16px;
+ margin-top: 48px;
+`;
+
+const CardContainer = styled.div`
+ display: flex;
+ flex-direction: column;
+ padding: 48px;
+ border: 1px solid var(--ads-v2-color-border);
+ width: 324px;
+ align-items: center;
+ text-align: center;
+ cursor: pointer;
+ border-radius: 4px;
+ img {
+ height: 160px;
+ margin-bottom: 48px;
+ }
+ position: relative;
+`;
+
+interface CardProps {
+ onClick?: () => void;
+ src: string;
+ subTitle: string;
+ testid: string;
+ title: string;
+}
+
+const Card = ({ onClick, src, subTitle, testid, title }: CardProps) => {
+ return (
+
+
+ {title}
+ {subTitle}
+
+ );
+};
+
+const CreateNewAppsOption = ({
+ currentApplicationIdForCreateNewApp,
+ onClickBack,
+}: {
+ currentApplicationIdForCreateNewApp: string;
+ onClickBack: () => void;
+}) => {
+ const [useTemplate, setUseTemplate] = useState(false);
+ const [selectedTemplate, setSelectedTemplate] = useState("");
+ const templatesCount = useSelector(
+ (state: AppState) => state.ui.templates.templates.length,
+ );
+ const filters = useSelector(allTemplatesFiltersSelector);
+ const workspaceList = useSelector(getForkableWorkspaces);
+ const isImportingTemplate = useSelector(isImportingTemplateToAppSelector);
+ const allTemplates = useSelector(getTemplatesSelector);
+ const application = useSelector((state) =>
+ getApplicationByIdFromWorkspaces(
+ state,
+ currentApplicationIdForCreateNewApp,
+ ),
+ );
+ const dispatch = useDispatch();
+ const onClickStartFromTemplate = () => {
+ AnalyticsUtil.logEvent("CREATE_APP_FROM_TEMPLATE");
+
+ if (isEmpty(filters.functions)) {
+ dispatch(getTemplateFilters());
+ }
+
+ if (!templatesCount) {
+ dispatch(getAllTemplates());
+ }
+ setUseTemplate(true);
+ };
+
+ const onClickStartFromScratch = () => {
+ if (application) {
+ AnalyticsUtil.logEvent("CREATE_APP_FROM_SCRATCH");
+ dispatch(
+ firstTimeUserOnboardingInit(
+ application.id,
+ application.defaultPageId as string,
+ ),
+ );
+ }
+ };
+
+ const goBackFromTemplate = () => {
+ setUseTemplate(false);
+ };
+
+ const getTemplateById = (id: string) => {
+ const template = allTemplates.find((template) => template.id === id);
+ return template;
+ };
+
+ const onTemplateClick = (id: string) => {
+ const template = getTemplateById(id);
+ if (template) {
+ AnalyticsUtil.logEvent("CLICK_ON_TEMPLATE_CARD_WHEN_ONBOARDING", {
+ id,
+ title: template.title,
+ });
+ // When template is clicked to view the template details
+ if (!isImportingTemplate) setSelectedTemplate(id);
+ }
+ };
+
+ const resetCreateNewAppFlow = () => {
+ dispatch(resetCurrentApplicationIdForCreateNewApp());
+ };
+
+ const onClickUseTemplate = (id: string) => {
+ const template = getTemplateById(id);
+ if (template) {
+ AnalyticsUtil.logEvent("USE_TEMPLATE_FROM_DETAILS_PAGE_WHEN_ONBOARDING", {
+ title: template.title,
+ });
+ // When Use template is clicked on template view detail screen
+ if (!isImportingTemplate && application) {
+ dispatch(
+ importTemplateIntoApplicationViaOnboardingFlow(
+ id,
+ template.title as string,
+ template.pages.map((p) => p.name),
+ application.id,
+ application.workspaceId,
+ ),
+ );
+ }
+ }
+ };
+
+ const onForkTemplateClick = (template: Template) => {
+ const title = template.title;
+ AnalyticsUtil.logEvent("FORK_TEMPLATE_WHEN_ONBOARDING", { title });
+ // When fork template is clicked to add a new app using the template
+ if (!isImportingTemplate && application) {
+ dispatch(
+ importTemplateIntoApplicationViaOnboardingFlow(
+ template.id,
+ template.title,
+ template.pages.map((p) => p.name),
+ application.id,
+ application.workspaceId,
+ ),
+ );
+ }
+ };
+
+ const onClickBackButton = () => {
+ if (isImportingTemplate) return;
+ if (useTemplate) {
+ if (selectedTemplate) {
+ // Going back from template details view screen
+ const template = getTemplateById(selectedTemplate);
+ if (template) {
+ AnalyticsUtil.logEvent(
+ "ONBOARDING_FLOW_CLICK_BACK_BUTTON_TEMPLATE_DETAILS_PAGE",
+ { title: template.title },
+ );
+ }
+ setSelectedTemplate("");
+ } else {
+ // Going back from start from template screen
+ AnalyticsUtil.logEvent(
+ "ONBOARDING_FLOW_CLICK_BACK_BUTTON_START_FROM_TEMPLATE_PAGE",
+ );
+ goBackFromTemplate();
+ }
+ } else {
+ // Going back from create new app flow
+ AnalyticsUtil.logEvent(
+ "ONBOARDING_FLOW_CLICK_BACK_BUTTON_CREATE_NEW_APP_PAGE",
+ );
+ onClickBack();
+ }
+ };
+
+ const selectionOptions: CardProps[] = [
+ {
+ onClick: onClickStartFromScratch,
+ src: StartScratch,
+ subTitle: createMessage(START_FROM_SCRATCH_SUBTITLE),
+ testid: "t--start-from-scratch",
+ title: createMessage(START_FROM_SCRATCH_TITLE),
+ },
+ {
+ onClick: onClickStartFromTemplate,
+ src: StartTemplate,
+ subTitle: createMessage(START_FROM_TEMPLATE_SUBTITLE),
+ testid: "t--start-from-template",
+ title: createMessage(START_FROM_TEMPLATE_TITLE),
+ },
+ ];
+
+ useEffect(() => {
+ AnalyticsUtil.logEvent("ONBOARDING_CREATE_APP_FLOW", {
+ totalOptions: selectionOptions.length,
+ });
+ if (application)
+ urlBuilder.updateURLParams(
+ {
+ applicationSlug: application.slug,
+ applicationVersion: application.applicationVersion,
+ applicationId: application.id,
+ },
+ application.pages.map((page) => ({
+ pageSlug: page.slug,
+ customSlug: page.customSlug,
+ pageId: page.id,
+ })),
+ );
+
+ return () => {
+ resetCreateNewAppFlow();
+ };
+ }, []);
+
+ return (
+
+
+
+ {createMessage(GO_BACK)}
+
+
+ {useTemplate ? (
+ selectedTemplate ? (
+
+ ) : (
+
+
+
+
+
+
+
+
+ )
+ ) : (
+
+
+ {createMessage(CREATE_NEW_APPS_STEP_TITLE)}
+
+ {createMessage(CREATE_NEW_APPS_STEP_SUBTITLE)}
+
+ {selectionOptions.map((option: CardProps) => (
+
+ ))}
+
+
+ )}
+
+ );
+};
+
+export default CreateNewAppsOption;
diff --git a/app/client/src/ce/pages/Applications/index.tsx b/app/client/src/ce/pages/Applications/index.tsx
index 576cea2e22..d42e3b9f2f 100644
--- a/app/client/src/ce/pages/Applications/index.tsx
+++ b/app/client/src/ce/pages/Applications/index.tsx
@@ -20,6 +20,7 @@ import {
getApplicationList,
getApplicationSearchKeyword,
getCreateApplicationError,
+ getCurrentApplicationIdForCreateNewApp,
getDeletingMultipleApps,
getIsCreatingApplication,
getIsDeletingApplication,
@@ -104,6 +105,8 @@ import { useFeatureFlag } from "utils/hooks/useFeatureFlag";
import { FEATURE_FLAG } from "@appsmith/entities/FeatureFlag";
import { getHasCreateWorkspacePermission } from "@appsmith/utils/BusinessFeatures/permissionPageHelpers";
import { allowManageEnvironmentAccessForUser } from "@appsmith/selectors/environmentSelectors";
+import CreateNewAppsOption from "@appsmith/pages/Applications/CreateNewAppsOption";
+import { resetCurrentApplicationIdForCreateNewApp } from "actions/onboardingActions";
export const { cloudHosting } = getAppsmithConfigs();
@@ -800,6 +803,8 @@ export interface ApplicationProps {
resetEditor: () => void;
queryModuleFeatureFlagEnabled: boolean;
resetCurrentWorkspace: () => void;
+ currentApplicationIdForCreateNewApp?: string;
+ resetCurrentApplicationIdForCreateNewApp: () => void;
}
export interface ApplicationState {
@@ -837,7 +842,14 @@ export class Applications<
}
public render() {
- return (
+ return this.props.currentApplicationIdForCreateNewApp ? (
+
+ ) : (
@@ -869,6 +881,8 @@ export const mapStateToProps = (state: AppState) => ({
userWorkspaces: getUserApplicationsWorkspacesList(state),
currentUser: getCurrentUser(state),
searchKeyword: getApplicationSearchKeyword(state),
+ currentApplicationIdForCreateNewApp:
+ getCurrentApplicationIdForCreateNewApp(state),
});
export const mapDispatchToProps = (dispatch: any) => ({
@@ -893,6 +907,8 @@ export const mapDispatchToProps = (dispatch: any) => ({
dispatch(setHeaderMeta(hideHeaderShadow, showHeaderSeparator));
},
resetCurrentWorkspace: () => dispatch(resetCurrentWorkspace()),
+ resetCurrentApplicationIdForCreateNewApp: () =>
+ dispatch(resetCurrentApplicationIdForCreateNewApp()),
});
export default connect(mapStateToProps, mapDispatchToProps)(Applications);
diff --git a/app/client/src/ce/reducers/uiReducers/applicationsReducer.tsx b/app/client/src/ce/reducers/uiReducers/applicationsReducer.tsx
index 88978b7a38..60d038c74c 100644
--- a/app/client/src/ce/reducers/uiReducers/applicationsReducer.tsx
+++ b/app/client/src/ce/reducers/uiReducers/applicationsReducer.tsx
@@ -816,6 +816,23 @@ export const handlers = {
},
};
},
+ [ReduxActionTypes.SET_CURRENT_APPLICATION_ID_FOR_CREATE_NEW_APP]: (
+ state: ApplicationsReduxState,
+ action: ReduxAction,
+ ) => {
+ return {
+ ...state,
+ currentApplicationIdForCreateNewApp: action.payload,
+ };
+ },
+ [ReduxActionTypes.RESET_CURRENT_APPLICATION_ID_FOR_CREATE_NEW_APP]: (
+ state: ApplicationsReduxState,
+ ) => {
+ return {
+ ...state,
+ currentApplicationIdForCreateNewApp: undefined,
+ };
+ },
};
const applicationsReducer = createReducer(initialState, handlers);
@@ -858,6 +875,7 @@ export interface ApplicationsReduxState {
isFetchingAllRoles: boolean;
isFetchingAllUsers: boolean;
};
+ currentApplicationIdForCreateNewApp?: string;
}
export interface Application {
diff --git a/app/client/src/ce/selectors/applicationSelectors.tsx b/app/client/src/ce/selectors/applicationSelectors.tsx
index 11c53605f1..3145518be1 100644
--- a/app/client/src/ce/selectors/applicationSelectors.tsx
+++ b/app/client/src/ce/selectors/applicationSelectors.tsx
@@ -284,3 +284,23 @@ export const getApplicationLoadingStates = (state: AppState) => {
};
export const getAllAppUsers = () => [];
+
+export const getCurrentApplicationIdForCreateNewApp = (state: AppState) => {
+ return state.ui.applications.currentApplicationIdForCreateNewApp;
+};
+
+// Get application from id from userWorkspaces
+export const getApplicationByIdFromWorkspaces = createSelector(
+ getUserApplicationsWorkspaces,
+ (_: AppState, applicationId: string) => applicationId,
+ (userWorkspaces, applicationId) => {
+ let application: ApplicationPayload | undefined;
+ userWorkspaces.forEach((userWorkspace) => {
+ if (!application)
+ application = userWorkspace.applications.find(
+ (i) => i.id === applicationId,
+ );
+ });
+ return application;
+ },
+);
diff --git a/app/client/src/ce/utils/analyticsUtilTypes.ts b/app/client/src/ce/utils/analyticsUtilTypes.ts
index 1090fb8c65..0659596ff1 100644
--- a/app/client/src/ce/utils/analyticsUtilTypes.ts
+++ b/app/client/src/ce/utils/analyticsUtilTypes.ts
@@ -339,7 +339,20 @@ export type EventName =
| "TEMPLATES_SEARCH_INPUT_EVENT"
| "GET_STARTED_CLICKED"
| "FORK_APLICATIONTEMPLATE"
- | "DATA_FETCH_FAILED_POST_SCHEMA_FETCH";
+ | "DATA_FETCH_FAILED_POST_SCHEMA_FETCH"
+ | ONBOARDING_FLOW_EVENTS;
+
+export type ONBOARDING_FLOW_EVENTS =
+ | "CREATE_APP_FROM_TEMPLATE"
+ | "CREATE_APP_FROM_SCRATCH"
+ | "CLICK_ON_TEMPLATE_CARD_WHEN_ONBOARDING"
+ | "FORK_TEMPLATE_WHEN_ONBOARDING"
+ | "REQUEST_NEW_TEMPLATE"
+ | "USE_TEMPLATE_FROM_DETAILS_PAGE_WHEN_ONBOARDING"
+ | "ONBOARDING_FLOW_CLICK_BACK_BUTTON_TEMPLATE_DETAILS_PAGE"
+ | "ONBOARDING_FLOW_CLICK_BACK_BUTTON_START_FROM_TEMPLATE_PAGE"
+ | "ONBOARDING_FLOW_CLICK_BACK_BUTTON_CREATE_NEW_APP_PAGE"
+ | "ONBOARDING_CREATE_APP_FLOW";
export type DATASOURCE_SCHEMA_EVENTS =
| "DATASOURCE_SCHEMA_SEARCH"
diff --git a/app/client/src/ce/utils/signupHelpers.ts b/app/client/src/ce/utils/signupHelpers.ts
index 43e88400aa..6204f3f83c 100644
--- a/app/client/src/ce/utils/signupHelpers.ts
+++ b/app/client/src/ce/utils/signupHelpers.ts
@@ -1,4 +1,7 @@
-import { firstTimeUserOnboardingInit } from "actions/onboardingActions";
+import {
+ firstTimeUserOnboardingInit,
+ setCurrentApplicationIdForCreateNewApp,
+} from "actions/onboardingActions";
import {
SIGNUP_SUCCESS_URL,
BUILDER_PATH,
@@ -19,6 +22,7 @@ export const redirectUserAfterSignup = (
_validLicense?: boolean,
dispatch?: any,
showStarterTemplatesInsteadofBlankCanvas: boolean = false,
+ isEnabledForCreateNew?: boolean,
): any => {
if (redirectUrl) {
try {
@@ -48,9 +52,26 @@ export const redirectUserAfterSignup = (
showStarterTemplatesInsteadofBlankCanvas &&
applicationId &&
setUsersFirstApplicationId(applicationId);
- dispatch(
- firstTimeUserOnboardingInit(applicationId, pageId as string),
- );
+ if (isEnabledForCreateNew) {
+ dispatch(
+ setCurrentApplicationIdForCreateNewApp(applicationId as string),
+ );
+ history.replace(APPLICATIONS_URL);
+ } else {
+ dispatch(
+ firstTimeUserOnboardingInit(applicationId, pageId as string),
+ );
+ }
+ } else {
+ if (!urlObject) {
+ try {
+ urlObject = new URL(redirectUrl, window.location.origin);
+ } catch (e) {}
+ }
+ const newRedirectUrl = urlObject?.toString() || "";
+ if (getIsSafeRedirectURL(newRedirectUrl)) {
+ window.location.replace(newRedirectUrl);
+ }
}
} else if (getIsSafeRedirectURL(redirectUrl)) {
window.location.replace(redirectUrl);
diff --git a/app/client/src/ee/pages/Applications/CreateNewAppsOption.tsx b/app/client/src/ee/pages/Applications/CreateNewAppsOption.tsx
new file mode 100644
index 0000000000..64765c70ba
--- /dev/null
+++ b/app/client/src/ee/pages/Applications/CreateNewAppsOption.tsx
@@ -0,0 +1,3 @@
+export * from "ce/pages/Applications/CreateNewAppsOption";
+import { default as CE_CreateNewAppsOption } from "ce/pages/Applications/CreateNewAppsOption";
+export default CE_CreateNewAppsOption;
diff --git a/app/client/src/pages/Editor/EditorHeader.tsx b/app/client/src/pages/Editor/EditorHeader.tsx
index 554e196081..ef08760c9e 100644
--- a/app/client/src/pages/Editor/EditorHeader.tsx
+++ b/app/client/src/pages/Editor/EditorHeader.tsx
@@ -159,6 +159,7 @@ export function EditorHeader() {
pageCount,
...navigationSettingsWithPrefix,
isPublic: !!currentApplication?.isPublic,
+ templateTitle: currentApplication?.forkedFromTemplateTitle,
});
}
};
diff --git a/app/client/src/pages/Templates/Filters.tsx b/app/client/src/pages/Templates/Filters.tsx
index 606801bd3a..a70154f7af 100644
--- a/app/client/src/pages/Templates/Filters.tsx
+++ b/app/client/src/pages/Templates/Filters.tsx
@@ -10,10 +10,6 @@ import {
import { thinScrollbar } from "constants/DefaultTheme";
import AnalyticsUtil from "utils/AnalyticsUtil";
-const FilterMainContainer = styled.div`
- /* padding: 0 16px; */
-`;
-
const FilterWrapper = styled.div`
overflow: auto;
height: calc(100vh - ${(props) => props.theme.homePage.header + 256}px);
@@ -196,20 +192,18 @@ function Filters() {
const selectedFilters = useSelector(getTemplateFilterSelector);
return (
-
-
- {Object.keys(filters).map((filter) => {
- return (
-
- );
- })}
-
-
+
+ {Object.keys(filters).map((filter) => {
+ return (
+
+ );
+ })}
+
);
}
diff --git a/app/client/src/pages/Templates/Template/RequestTemplate.tsx b/app/client/src/pages/Templates/Template/RequestTemplate.tsx
index d61d228353..18143b3787 100644
--- a/app/client/src/pages/Templates/Template/RequestTemplate.tsx
+++ b/app/client/src/pages/Templates/Template/RequestTemplate.tsx
@@ -9,6 +9,7 @@ import {
COULDNT_FIND_TEMPLATE_DESCRIPTION,
REQUEST_TEMPLATE,
} from "@appsmith/constants/messages";
+import AnalyticsUtil from "utils/AnalyticsUtil";
const Wrapper = styled.div`
border: 1px solid var(--ads-v2-color-border);
@@ -51,6 +52,7 @@ const REQUEST_TEMPLATE_URL =
function RequestTemplate() {
const onClick = () => {
+ AnalyticsUtil.logEvent("REQUEST_NEW_TEMPLATE");
window.open(REQUEST_TEMPLATE_URL);
};
diff --git a/app/client/src/pages/Templates/Template/index.tsx b/app/client/src/pages/Templates/Template/index.tsx
index baa1972004..eadbb94cf0 100644
--- a/app/client/src/pages/Templates/Template/index.tsx
+++ b/app/client/src/pages/Templates/Template/index.tsx
@@ -12,6 +12,8 @@ import {
} from "@appsmith/constants/messages";
import { templateIdUrl } from "@appsmith/RouteBuilder";
import { Position } from "@blueprintjs/core";
+import { isImportingTemplateToAppSelector } from "selectors/templatesSelectors";
+import { useSelector } from "react-redux";
const TemplateWrapper = styled.div`
border: 1px solid var(--ads-v2-color-border);
@@ -93,6 +95,9 @@ export function TemplateLayout(props: TemplateLayoutProps) {
const { datasources, description, functions, id, screenshotUrls, title } =
props.template;
const [showForkModal, setShowForkModal] = useState(false);
+ const isImportingTemplateToApp = useSelector(
+ isImportingTemplateToAppSelector,
+ );
const onClick = () => {
if (props.onClick) {
props.onClick(id);
@@ -161,6 +166,9 @@ export function TemplateLayout(props: TemplateLayoutProps) {
;
}
-function TemplateView() {
+interface TemplateViewProps {
+ onClickUseTemplate?: (id: string) => void;
+ showBack?: boolean;
+ showSimilarTemplate?: boolean;
+ templateId: string;
+}
+
+export function TemplateView({
+ onClickUseTemplate,
+ showBack = true,
+ showSimilarTemplate = true,
+ templateId,
+}: TemplateViewProps) {
const dispatch = useDispatch();
const similarTemplates = useSelector(
(state: AppState) => state.ui.templates.similarTemplates,
);
const isFetchingTemplate = useSelector(isFetchingTemplateSelector);
const workspaceList = useSelector(getForkableWorkspaces);
- const params = useParams<{ templateId: string }>();
const currentTemplate = useSelector(getActiveTemplateSelector);
const containerRef = useRef(null);
@@ -138,12 +149,12 @@ function TemplateView() {
};
useEffect(() => {
- dispatch(getTemplateInformation(params.templateId));
- dispatch(getSimilarTemplatesInit(params.templateId));
+ dispatch(getTemplateInformation(templateId));
+ dispatch(getSimilarTemplatesInit(templateId));
if (containerRef.current) {
containerRef.current.scrollTo({ top: 0 });
}
- }, [params.templateId]);
+ }, [templateId]);
const onSimilarTemplateClick = (template: TemplateInterface) => {
AnalyticsUtil.logEvent("SIMILAR_TEMPLATE_CLICK", {
@@ -159,38 +170,49 @@ function TemplateView() {
history.push(templateIdUrl({ id: template.id }));
};
+ return isFetchingTemplate ? (
+
+ ) : !currentTemplate ? (
+
+ ) : (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {showSimilarTemplate && (
+
+ )}
+
+ );
+}
+
+function TemplateViewContainer() {
+ const params = useParams<{ templateId: string }>();
return (
- {isFetchingTemplate ? (
-
- ) : !currentTemplate ? (
-
- ) : (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- )}
+
);
}
-export default TemplateView;
+export default TemplateViewContainer;
diff --git a/app/client/src/pages/Templates/TemplateViewHeader.tsx b/app/client/src/pages/Templates/TemplateViewHeader.tsx
index b4b19ebc10..e2030f83a2 100644
--- a/app/client/src/pages/Templates/TemplateViewHeader.tsx
+++ b/app/client/src/pages/Templates/TemplateViewHeader.tsx
@@ -11,6 +11,7 @@ import { useSelector } from "react-redux";
import {
getActiveTemplateSelector,
getForkableWorkspaces,
+ isImportingTemplateToAppSelector,
} from "selectors/templatesSelectors";
import styled from "styled-components";
import history from "utils/history";
@@ -29,13 +30,22 @@ const Title = styled(Text)`
interface Props {
templateId: string;
+ onClickUseTemplate?: (id: string) => void;
+ showBack: boolean;
}
const SHOW_FORK_MODAL_PARAM = "showForkTemplateModal";
-function TemplateViewHeader({ templateId }: Props) {
+function TemplateViewHeader({
+ onClickUseTemplate,
+ showBack,
+ templateId,
+}: Props) {
const currentTemplate = useSelector(getActiveTemplateSelector);
const query = useQuery();
const workspaceList = useSelector(getForkableWorkspaces);
+ const isImportingTemplateToApp = useSelector(
+ isImportingTemplateToAppSelector,
+ );
const goBack = () => {
history.goBack();
};
@@ -43,19 +53,25 @@ function TemplateViewHeader({ templateId }: Props) {
history.replace(`${templateIdUrl({ id: templateId })}`);
};
const onForkButtonTrigger = () => {
- history.replace(
- `${templateIdUrl({ id: templateId })}?${SHOW_FORK_MODAL_PARAM}=true`,
- );
+ if (onClickUseTemplate) {
+ onClickUseTemplate(templateId);
+ } else {
+ history.replace(
+ `${templateIdUrl({ id: templateId })}?${SHOW_FORK_MODAL_PARAM}=true`,
+ );
+ }
};
return (
-
- {createMessage(GO_BACK)}
-
+ {showBack && (
+
+ {createMessage(GO_BACK)}
+
+ )}
{currentTemplate?.title}
@@ -69,6 +85,7 @@ function TemplateViewHeader({ templateId }: Props) {