PromucFlow_constructor/app/client/src/ce/pages/Applications/CreateNewAppsOption.tsx
Aman Agarwal 6ba2f350bf
fix: added margin top to the onboarding flow if banner visible (#31497)
## Description
Earlier the since the license banner had fixed position which was making
the skip section gets hidden. Hence as the page header is positioned
40px below the banner if it is visible. We are doing the same for the
onboarding flow.
<img width="1439" alt="Screenshot 2024-03-05 at 11 33 47 PM"
src="https://github.com/appsmithorg/appsmith/assets/7565635/e7912fb5-b84d-49fd-86fa-3baa888c51fa">

#### PR fixes following issue(s)
Fixes #30953

#### Media


https://github.com/appsmithorg/appsmith/assets/7565635/c090e06a-0cee-49a4-a48d-5b14dd79bf19
2024-03-07 05:49:42 +05:30

561 lines
17 KiB
TypeScript

import {
CREATE_NEW_APPS_STEP_SUBTITLE,
CREATE_NEW_APPS_STEP_TITLE,
GO_BACK,
SKIP_START_WITH_USE_CASE_TEMPLATES,
START_FROM_SCRATCH_SUBTITLE,
START_FROM_SCRATCH_TITLE,
START_FROM_TEMPLATE_SUBTITLE,
START_FROM_TEMPLATE_TITLE,
START_WITH_DATA_CONNECT_HEADING,
START_WITH_DATA_CONNECT_SUBHEADING,
START_WITH_DATA_SUBTITLE,
START_WITH_DATA_TITLE,
createMessage,
} from "@appsmith/constants/messages";
import urlBuilder from "@appsmith/entities/URLRedirect/URLAssembly";
import type { AppState } from "@appsmith/reducers";
import { getCurrentPluginIdForCreateNewApp } from "@appsmith/selectors/applicationSelectors";
import { getAssetUrl } from "@appsmith/utils/airgapHelpers";
import {
firstTimeUserOnboardingInit,
resetCurrentApplicationIdForCreateNewApp,
resetCurrentPluginIdForCreateNewApp,
} from "actions/onboardingActions";
import { fetchPlugins } from "actions/pluginActions";
import {
getAllTemplates,
getTemplateFilters,
importTemplateIntoApplicationViaOnboardingFlow,
setActiveLoadingTemplateId,
} from "actions/templateActions";
import { ASSETS_CDN_URL } from "constants/ThirdPartyConstants";
import { Flex, Link, Text } from "design-system";
import { isEmpty } from "lodash";
import CreateNewDatasourceTab from "pages/Editor/IntegrationEditor/CreateNewDatasourceTab";
import { getApplicationsOfWorkspace } from "@appsmith/selectors/selectedWorkspaceSelectors";
import { TemplateView } from "pages/Templates/TemplateView";
import { default as React, useCallback, useEffect, useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import {
allTemplatesFiltersSelector,
getTemplatesSelector,
isImportingTemplateToAppSelector,
} from "selectors/templatesSelectors";
import styled from "styled-components";
import AnalyticsUtil from "utils/AnalyticsUtil";
import history from "utils/history";
import { builderURL } from "@appsmith/RouteBuilder";
import { getDatasource, getPlugin } from "@appsmith/selectors/entitiesSelector";
import type { Plugin } from "api/PluginApi";
import { PluginPackageName, PluginType } from "entities/Action";
import DataSourceEditor from "pages/Editor/DataSourceEditor";
import { TEMP_DATASOURCE_ID } from "constants/Datasource";
import { fetchMockDatasources } from "actions/datasourceActions";
import DatasourceForm from "pages/Editor/SaaSEditor/DatasourceForm";
import type { Datasource } from "entities/Datasource";
import { fetchingEnvironmentConfigs } from "@appsmith/actions/environmentAction";
import StartWithTemplatesWrapper from "./StartWithTemplatesWrapper";
import { useFeatureFlag } from "utils/hooks/useFeatureFlag";
import { FEATURE_FLAG } from "@appsmith/entities/FeatureFlag";
import type { Template } from "api/TemplatesApi";
import { shouldShowLicenseBanner } from "@appsmith/selectors/tenantSelectors";
const SectionWrapper = styled.div<{ isBannerVisible: boolean }>`
display: flex;
flex-direction: column;
padding: 0 var(--ads-v2-spaces-7) var(--ads-v2-spaces-7);
${(props) => `
margin-top: ${
props.theme.homePage.header + (props.isBannerVisible ? 40 : 0)
}px;
`}
background: var(--ads-v2-color-gray-50);
${(props) => `
min-height: calc(100vh - ${props.theme.homePage.header}px);
`}
`;
const BackWrapper = styled.div<{ hidden?: boolean; isBannerVisible: boolean }>`
position: sticky;
display: flex;
justify-content: space-between;
${(props) => `
top: ${props.theme.homePage.header + (props.isBannerVisible ? 40 : 0)}px;
`}
background: inherit;
padding: var(--ads-v2-spaces-3);
z-index: 1;
margin-left: -4px;
${(props) => `${props.hidden && "visibility: hidden; opacity: 0;"}`}
`;
const LinkWrapper = styled(Link)<{ hidden?: boolean }>`
${(props) => `${props.hidden && "visibility: hidden; opacity: 0;"}`}
`;
const OptionWrapper = styled.div`
display: flex;
align-items: center;
flex-direction: column;
justify-content: center;
flex-grow: 1;
padding: var(--ads-v2-spaces-7) 0;
`;
const CardsWrapper = styled.div`
display: flex;
gap: 16px;
margin-top: 48px;
flex-wrap: wrap;
justify-content: center;
`;
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;
background: var(--ads-v2-color-bg);
img {
height: 160px;
margin-bottom: 48px;
}
position: relative;
`;
const WithDataWrapper = styled.div`
background: var(--ads-v2-color-bg);
padding: var(--ads-v2-spaces-13);
border: 1px solid var(--ads-v2-color-gray-300);
border-radius: 5px;
`;
const Header = ({ subtitle, title }: { subtitle: string; title: string }) => {
return (
<Flex flexDirection="column" mb="spaces-14" mt="spaces-7">
<Text kind="heading-xl">{title}</Text>
<Text>{subtitle}</Text>
</Flex>
);
};
interface CardProps {
onClick?: () => void;
src: string;
subTitle: string;
testid: string;
title: string;
}
const Card = ({ onClick, src, subTitle, testid, title }: CardProps) => {
return (
<CardContainer data-testid={testid} onClick={onClick}>
<img alt={title} src={src} />
<Text kind="heading-s">{title}</Text>
<Text>{subTitle}</Text>
</CardContainer>
);
};
const START_WITH_TYPE = {
TEMPLATE: "TEMPLATE",
DATA: "DATA",
};
type TYPE_START_WITH_TYPE = keyof typeof START_WITH_TYPE;
type TYPE_START_WITH_TYPE_VALUE =
(typeof START_WITH_TYPE)[TYPE_START_WITH_TYPE];
const CreateNewAppsOption = ({
currentApplicationIdForCreateNewApp,
onClickBack,
}: {
currentApplicationIdForCreateNewApp: string;
onClickBack: () => void;
}) => {
const [useType, setUseType] = useState<
TYPE_START_WITH_TYPE_VALUE | undefined
>();
const [selectedTemplate, setSelectedTemplate] = useState("");
const templatesCount = useSelector(
(state: AppState) => state.ui.templates.templates.length,
);
const filters = useSelector(allTemplatesFiltersSelector);
const isImportingTemplate = useSelector(isImportingTemplateToAppSelector);
const allTemplates = useSelector(getTemplatesSelector);
const createNewAppPluginId = useSelector(getCurrentPluginIdForCreateNewApp);
const applications = useSelector(getApplicationsOfWorkspace);
const selectedPlugin: Plugin | undefined = useSelector((state) =>
getPlugin(state, createNewAppPluginId || ""),
);
const application = applications.find(
(app) => app.id === currentApplicationIdForCreateNewApp,
);
const selectedDatasource: Datasource | undefined = useSelector((state) =>
getDatasource(state, TEMP_DATASOURCE_ID || ""),
);
const isEnabledForStartWithDataDefault = useFeatureFlag(
FEATURE_FLAG.ab_start_with_data_default_enabled,
);
const isBannerVisible = useSelector(shouldShowLicenseBanner);
const dispatch = useDispatch();
const onClickStartFromTemplate = () => {
AnalyticsUtil.logEvent("CREATE_APP_FROM_TEMPLATE");
if (isEmpty(filters.functions)) {
dispatch(getTemplateFilters());
}
if (!templatesCount) {
dispatch(getAllTemplates());
}
setUseType(START_WITH_TYPE.TEMPLATE);
};
const onClickStartFromScratch = () => {
if (application) {
AnalyticsUtil.logEvent("CREATE_APP_FROM_SCRATCH");
dispatch(
firstTimeUserOnboardingInit(
application.id,
application.defaultPageId as string,
),
);
}
};
const onClickStartWithData = () => {
AnalyticsUtil.logEvent("CREATE_APP_FROM_DATA", {
[FEATURE_FLAG.ab_start_with_data_default_enabled]:
isEnabledForStartWithDataDefault,
});
// fetch plugins information to show list of all plugins
dispatch(fetchPlugins({ workspaceId: application?.workspaceId }));
dispatch(fetchMockDatasources());
if (application?.workspaceId) {
dispatch(
fetchingEnvironmentConfigs({
editorId: application.id,
fetchDatasourceMeta: true,
workspaceId: application?.workspaceId,
}),
);
}
setUseType(START_WITH_TYPE.DATA);
};
const goBackToInitialScreen = () => {
setUseType(undefined);
};
const getTemplateById = (id: string) => {
const template = allTemplates.find((template) => template.id === id);
return template;
};
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 addAnalyticEventsForSkip = () => {
if (useType === START_WITH_TYPE.TEMPLATE) {
if (selectedTemplate) {
const template = getTemplateById(selectedTemplate);
if (template) {
AnalyticsUtil.logEvent(
"ONBOARDING_FLOW_CLICK_SKIP_BUTTON_TEMPLATE_DETAILS_PAGE",
{ title: template.title },
);
}
} else {
AnalyticsUtil.logEvent(
"ONBOARDING_FLOW_CLICK_SKIP_BUTTON_START_FROM_TEMPLATE_PAGE",
);
}
} else if (useType === START_WITH_TYPE.DATA) {
if (createNewAppPluginId) {
AnalyticsUtil.logEvent(
"ONBOARDING_FLOW_CLICK_SKIP_BUTTON_DATASOURCE_FORM_PAGE",
{
pluginId: createNewAppPluginId,
[FEATURE_FLAG.ab_start_with_data_default_enabled]:
isEnabledForStartWithDataDefault,
},
);
} else {
AnalyticsUtil.logEvent(
"ONBOARDING_FLOW_CLICK_SKIP_BUTTON_START_FROM_DATA_PAGE",
{
[FEATURE_FLAG.ab_start_with_data_default_enabled]:
isEnabledForStartWithDataDefault,
},
);
}
}
};
const onClickSkipButton = () => {
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,
})),
);
history.push(
builderURL({
pageId: application.pages[0].id,
}),
);
}
addAnalyticEventsForSkip();
};
const onClickBackButton = () => {
if (isImportingTemplate) return;
if (useType === START_WITH_TYPE.TEMPLATE) {
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",
);
goBackToInitialScreen();
}
} else if (useType === START_WITH_TYPE.DATA) {
if (createNewAppPluginId) {
AnalyticsUtil.logEvent(
"ONBOARDING_FLOW_CLICK_BACK_BUTTON_DATASOURCE_FORM_PAGE",
{ pluginId: createNewAppPluginId },
);
dispatch(resetCurrentPluginIdForCreateNewApp());
} else {
// Going back from start from data screen
AnalyticsUtil.logEvent(
"ONBOARDING_FLOW_CLICK_BACK_BUTTON_START_FROM_DATA_PAGE",
);
goBackToInitialScreen();
}
} else {
// Going back from create new app flow
AnalyticsUtil.logEvent(
"ONBOARDING_FLOW_CLICK_BACK_BUTTON_CREATE_NEW_APP_PAGE",
);
onClickBack();
}
};
const renderStartWithData = useCallback(() => {
return (
<Flex flexDirection="column" pl="spaces-3" pr="spaces-3">
<Header
subtitle={createMessage(START_WITH_DATA_CONNECT_SUBHEADING)}
title={createMessage(START_WITH_DATA_CONNECT_HEADING)}
/>
<WithDataWrapper>
{createNewAppPluginId && !!selectedDatasource ? (
selectedPlugin?.type === PluginType.SAAS ? (
<DatasourceForm
datasourceId={TEMP_DATASOURCE_ID}
isOnboardingFlow
pageId={application?.defaultPageId || ""}
pluginPackageName={PluginPackageName.GOOGLE_SHEETS}
/>
) : (
<DataSourceEditor
applicationId={currentApplicationIdForCreateNewApp}
datasourceId={TEMP_DATASOURCE_ID}
isOnboardingFlow
pageId={application?.defaultPageId}
/>
)
) : (
<CreateNewDatasourceTab isOnboardingScreen />
)}
</WithDataWrapper>
</Flex>
);
}, [createNewAppPluginId, selectedDatasource, selectedPlugin]);
const selectionOptions: CardProps[] = [
{
onClick: onClickStartWithData,
src: getAssetUrl(`${ASSETS_CDN_URL}/start-with-data.svg`),
subTitle: createMessage(START_WITH_DATA_SUBTITLE),
testid: "t--start-from-data",
title: createMessage(START_WITH_DATA_TITLE),
},
{
onClick: onClickStartFromScratch,
src: getAssetUrl(`${ASSETS_CDN_URL}/start-from-scratch.svg`),
subTitle: createMessage(START_FROM_SCRATCH_SUBTITLE),
testid: "t--start-from-scratch",
title: createMessage(START_FROM_SCRATCH_TITLE),
},
{
onClick: onClickStartFromTemplate,
src: getAssetUrl(`${ASSETS_CDN_URL}/start-from-templates.svg`),
subTitle: createMessage(START_FROM_TEMPLATE_SUBTITLE),
testid: "t--start-from-template",
title: createMessage(START_FROM_TEMPLATE_TITLE),
},
];
useEffect(() => {
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,
})),
);
if (isEnabledForStartWithDataDefault) {
onClickStartWithData();
}
}
}, [application]);
useEffect(() => {
AnalyticsUtil.logEvent("ONBOARDING_CREATE_APP_FLOW", {
totalOptions: selectionOptions.length,
});
return () => {
resetCreateNewAppFlow();
};
}, []);
const isBackButtonHidden =
isEnabledForStartWithDataDefault &&
(!createNewAppPluginId || !selectedDatasource);
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(setActiveLoadingTemplateId(template.id));
dispatch(
importTemplateIntoApplicationViaOnboardingFlow(
template.id,
template.title,
template.pages.map((p) => p.name),
application.id,
application.workspaceId,
),
);
}
};
return (
<SectionWrapper isBannerVisible={!!isBannerVisible}>
<BackWrapper hidden={!useType} isBannerVisible={!!isBannerVisible}>
<LinkWrapper
className="t--create-new-app-option-goback"
data-testid="t--create-new-app-option-goback"
hidden={isBackButtonHidden}
onClick={onClickBackButton}
startIcon="arrow-left-line"
>
{createMessage(GO_BACK)}
</LinkWrapper>
<LinkWrapper
className="t--create-new-app-option-skip"
data-testid="t--create-new-app-option-skip"
endIcon="arrow-right-line"
onClick={onClickSkipButton}
>
{createMessage(SKIP_START_WITH_USE_CASE_TEMPLATES)}
</LinkWrapper>
</BackWrapper>
{isEnabledForStartWithDataDefault ? (
renderStartWithData()
) : useType === START_WITH_TYPE.TEMPLATE ? (
selectedTemplate ? (
<TemplateView
onClickUseTemplate={onClickUseTemplate}
showBack={false}
showSimilarTemplate={false}
templateId={selectedTemplate}
/>
) : (
<StartWithTemplatesWrapper
onForkTemplateClick={onForkTemplateClick}
setSelectedTemplate={setSelectedTemplate}
/>
)
) : useType === START_WITH_TYPE.DATA ? (
renderStartWithData()
) : (
<OptionWrapper>
<Text kind="heading-xl">
{createMessage(CREATE_NEW_APPS_STEP_TITLE)}
</Text>
<Text>{createMessage(CREATE_NEW_APPS_STEP_SUBTITLE)}</Text>
<CardsWrapper>
{selectionOptions.map((option: CardProps) => (
<Card key={option.testid} {...option} />
))}
</CardsWrapper>
</OptionWrapper>
)}
</SectionWrapper>
);
};
export default CreateNewAppsOption;