From 7721f703ecbf542c4cccc36bad4e26a9fee247b4 Mon Sep 17 00:00:00 2001 From: Aman Agarwal Date: Thu, 9 Nov 2023 09:49:02 +0530 Subject: [PATCH] feat: added an intermediary step to enable new create new app flow (#27457) --- app/client/src/actions/onboardingActions.ts | 15 + app/client/src/actions/templateActions.ts | 17 + .../src/assets/images/start-from-scratch.svg | 40 ++ .../src/assets/images/start-from-template.svg | 305 +++++++++++++++ .../src/ce/constants/ReduxActionConstants.tsx | 11 + app/client/src/ce/constants/messages.ts | 11 + app/client/src/ce/entities/FeatureFlag.ts | 2 + .../Applications/CreateNewAppsOption.tsx | 367 ++++++++++++++++++ .../src/ce/pages/Applications/index.tsx | 18 +- .../uiReducers/applicationsReducer.tsx | 18 + .../src/ce/selectors/applicationSelectors.tsx | 20 + app/client/src/ce/utils/analyticsUtilTypes.ts | 15 +- app/client/src/ce/utils/signupHelpers.ts | 29 +- .../Applications/CreateNewAppsOption.tsx | 3 + app/client/src/pages/Editor/EditorHeader.tsx | 1 + app/client/src/pages/Templates/Filters.tsx | 30 +- .../Templates/Template/RequestTemplate.tsx | 2 + .../src/pages/Templates/Template/index.tsx | 8 + .../src/pages/Templates/TemplateView.tsx | 90 +++-- .../pages/Templates/TemplateViewHeader.tsx | 39 +- app/client/src/pages/common/PageHeader.tsx | 7 +- app/client/src/pages/setup/SignupSuccess.tsx | 7 + .../reducers/uiReducers/templateReducer.ts | 23 ++ app/client/src/sagas/TemplatesSagas.ts | 65 ++++ app/client/src/sagas/WidgetDeletionSagas.ts | 11 +- .../appsmith/server/domains/Application.java | 4 + .../ce/ApplicationTemplateServiceCEImpl.java | 39 +- 27 files changed, 1115 insertions(+), 82 deletions(-) create mode 100644 app/client/src/assets/images/start-from-scratch.svg create mode 100644 app/client/src/assets/images/start-from-template.svg create mode 100644 app/client/src/ce/pages/Applications/CreateNewAppsOption.tsx create mode 100644 app/client/src/ee/pages/Applications/CreateNewAppsOption.tsx 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} + {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 ( + + + {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) {