368 lines
10 KiB
TypeScript
368 lines
10 KiB
TypeScript
|
|
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 (
|
||
|
|
<CardContainer data-testid={testid} onClick={onClick}>
|
||
|
|
<img alt={title} src={src} />
|
||
|
|
<Text kind="heading-s">{title}</Text>
|
||
|
|
<Text>{subTitle}</Text>
|
||
|
|
</CardContainer>
|
||
|
|
);
|
||
|
|
};
|
||
|
|
|
||
|
|
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 (
|
||
|
|
<SectionWrapper>
|
||
|
|
<BackWrapper hidden={!useTemplate}>
|
||
|
|
<Link
|
||
|
|
className="t--create-new-app-option-goback"
|
||
|
|
data-testid="t--create-new-app-option-goback"
|
||
|
|
onClick={onClickBackButton}
|
||
|
|
startIcon="arrow-left-line"
|
||
|
|
>
|
||
|
|
{createMessage(GO_BACK)}
|
||
|
|
</Link>
|
||
|
|
</BackWrapper>
|
||
|
|
{useTemplate ? (
|
||
|
|
selectedTemplate ? (
|
||
|
|
<TemplateView
|
||
|
|
onClickUseTemplate={onClickUseTemplate}
|
||
|
|
showBack={false}
|
||
|
|
showSimilarTemplate={false}
|
||
|
|
templateId={selectedTemplate}
|
||
|
|
/>
|
||
|
|
) : (
|
||
|
|
<TemplateWrapper>
|
||
|
|
<FiltersWrapper>
|
||
|
|
<Filters />
|
||
|
|
</FiltersWrapper>
|
||
|
|
<TemplateContentWrapper>
|
||
|
|
<TemplatesContent
|
||
|
|
isForkingEnabled={!!workspaceList.length}
|
||
|
|
onForkTemplateClick={onForkTemplateClick}
|
||
|
|
onTemplateClick={onTemplateClick}
|
||
|
|
/>
|
||
|
|
</TemplateContentWrapper>
|
||
|
|
</TemplateWrapper>
|
||
|
|
)
|
||
|
|
) : (
|
||
|
|
<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;
|