Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions app/client/src/actions/onboardingActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,3 +71,10 @@ export const showEndOnboardingHelper = () => {
type: ReduxActionTypes.SHOW_END_ONBOARDING_HELPER,
};
};

export const toggleInOnboardingWidgetSelection = (payload: boolean) => {
return {
type: ReduxActionTypes.TOGGLE_ONBOARDING_WIDGET_SELECTION,
payload,
};
};
5 changes: 5 additions & 0 deletions app/client/src/actions/widgetSidebarActions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ export const successFetchingWidgetCards = (cards: {
};
};

export const forceOpenWidgetPanel = (flag: boolean) => ({
type: ReduxActionTypes.SET_FORCE_WIDGET_PANEL_OPEN,
payload: flag,
});

export default {
fetchWidgetCards,
errorFetchingWidgetCards,
Expand Down
15 changes: 15 additions & 0 deletions app/client/src/components/editorComponents/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@ import PerformanceTracker, {
PerformanceTransactionName,
} from "utils/PerformanceTracker";
import { Layers } from "constants/Layers";
import { useSelector } from "store";
import {
getFirstTimeUserOnboardingComplete,
getIsFirstTimeUserOnboardingEnabled,
} from "selectors/onboardingSelectors";
import OnboardingStatusbar from "pages/Editor/FirstTimeUserOnboarding/Statusbar";

const SidebarWrapper = styled.div`
background-color: ${Colors.MINE_SHAFT};
Expand All @@ -28,12 +34,21 @@ const SidebarWrapper = styled.div`
const initialPanel = { component: ExplorerSidebar };

export const Sidebar = memo(() => {
const enableFirstTimeUserOnboarding = useSelector(
getIsFirstTimeUserOnboardingEnabled,
);
const isFirstTimeUserOnboardingComplete = useSelector(
getFirstTimeUserOnboardingComplete,
);
PerformanceTracker.startTracking(PerformanceTransactionName.SIDE_BAR_MOUNT);
useEffect(() => {
PerformanceTracker.stopTracking();
});
return (
<SidebarWrapper className="t--sidebar">
{(enableFirstTimeUserOnboarding || isFirstTimeUserOnboardingComplete) && (
<OnboardingStatusbar />
)}
<PanelStack initialPanel={initialPanel} showPanelHeader={false} />
</SidebarWrapper>
);
Expand Down
6 changes: 6 additions & 0 deletions app/client/src/constants/DefaultTheme.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2665,6 +2665,12 @@ export const theme: Theme = {
letterSpacing: -0.221538,
fontWeight: "normal",
},
p4: {
fontSize: 13,
lineHeight: 16,
letterSpacing: -0.221538,
fontWeight: 600,
},
btnLarge: {
fontSize: 13,
lineHeight: 15,
Expand Down
13 changes: 13 additions & 0 deletions app/client/src/constants/ReduxActionConstants.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -549,6 +549,18 @@ export const ReduxActionTypes = {
SET_PAGE_ORDER_SUCCESS: "SET_PAGE_ORDER_SUCCESS",
GROUP_WIDGETS_INIT: "GROUP_WIDGETS_INIT",
REFACTOR_JS_ACTION_NAME: "REFACTOR_JS_ACTION_NAME",
TOGGLE_ONBOARDING_WIDGET_SELECTION: "TOGGLE_ONBOARDING_WIDGET_SELECTION",
SET_ENABLE_FIRST_TIME_USER_ONBOARDING:
"SET_ENABLE_FIRST_TIME_USER_ONBOARDING",
SET_FIRST_TIME_USER_ONBOARDING_APPLICATION_ID:
"SET_FIRST_TIME_USER_ONBOARDING_APPLICATION_ID",
SET_FIRST_TIME_USER_ONBOARDING_COMPLETE:
"SET_FIRST_TIME_USER_ONBOARDING_COMPLETE",
SET_SHOW_FIRST_TIME_USER_ONBOARDING_MODAL:
"SET_SHOW_FIRST_TIME_USER_ONBOARDING_MODAL",
SET_FORCE_WIDGET_PANEL_OPEN: "SET_FORCE_WIDGET_PANEL_OPEN",
END_FIRST_TIME_USER_ONBOARDING: "END_FIRST_TIME_USER_ONBOARDING",
UNDO_END_FIRST_TIME_USER_ONBOARDING: "UNDO_END_FIRST_TIME_USER_ONBOARDING",
};

export type ReduxActionType = typeof ReduxActionTypes[keyof typeof ReduxActionTypes];
Expand Down Expand Up @@ -793,6 +805,7 @@ export type ApplicationPayload = {
appIsExample: boolean;
forkingEnabled?: boolean;
appLayout?: AppLayoutConfig;
lastDeployedAt?: string;
};

export type OrganizationDetails = {
Expand Down
1 change: 1 addition & 0 deletions app/client/src/constants/ThirdPartyConstants.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ export const S3_BUCKET_URL =
"https://s3.us-east-2.amazonaws.com/assets.appsmith.com";
export const TELEMETRY_URL = "https://docs.appsmith.com/telemetry";
export const DISCORD_URL = "https://discord.gg/rBTTVJp";
export const ASSETS_CDN_URL = "https://assets.appsmith.com";
100 changes: 100 additions & 0 deletions app/client/src/constants/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -524,3 +524,103 @@ export const SNIPING_SELECT_WIDGET_AGAIN = () =>

export const SNIPING_NOT_SUPPORTED = () =>
"Binding on selection is not supported for this type of widget!";

//First Time User Onboarding
//Checklist page
export enum ONBOARDING_CHECKLIST_ACTIONS {
CONNECT_A_DATASOURCE = "CONNECT DATA SOURCE",
CREATE_A_QUERY = "CREATE A QUERY",
ADD_WIDGETS = "ADD WIDGETS",
CONNECT_DATA_TO_WIDGET = "CONNECT DATA TO WIDGET",
DEPLOY_APPLICATIONS = "DEPLOY APPLICATION",
}

export const ONBOARDING_CHECKLIST_BANNER_HEADER = () =>
"Amazing work! You’ve explored the basics of Appsmith";
export const ONBOARDING_CHECKLIST_BANNER_BODY = () =>
"You can carry on here, or explore the homepage to see how your projects are stored.";
export const ONBOARDING_CHECKLIST_BANNER_BUTTON = () => "Explore homepage";

export const ONBOARDING_CHECKLIST_HEADER = () => "πŸ‘‹ Welcome to Appsmith!";
export const ONBOARDING_CHECKLIST_BODY = () =>
"Let’s get you started on your first application, explore Appsmith yourself or follow our guide below to discover what Appsmith can do.";
export const ONBOARDING_CHECKLIST_COMPLETE_TEXT = () => "complete";

export const ONBOARDING_CHECKLIST_CONNECT_DATA_SOURCE = {
bold: () => "Connect your data source",
normal: () => "to start building an application.",
};

export const ONBOARDING_CHECKLIST_CREATE_A_QUERY = {
bold: () => "Create a query",
normal: () => "of your data source.",
};

export const ONBOARDING_CHECKLIST_ADD_WIDGETS = {
bold: () => "Start visualising your application",
normal: () => "using widgets.",
};

export const ONBOARDING_CHECKLIST_CONNECT_DATA_TO_WIDGET = {
bold: () => "Connect your data to the widgets",
normal: () => "using JavaScript.",
};

export const ONBOARDING_CHECKLIST_DEPLOY_APPLICATIONS = {
bold: () => "Deploy your application",
normal: () => ", and see your creation live.",
};

export const ONBOARDING_CHECKLIST_FOOTER = () =>
"Not sure where to start? Take the welcome tour";

//Introduction modal
export const HOW_APPSMITH_WORKS = () => "Here’s how Appsmith works";
export const ONBOARDING_INTRO_CONNECT_YOUR_DATABASE = () =>
"Connect your database or API";
export const ONBOARDING_INTRO_CONNECT_DATA_WIDGET = () =>
"Connect queried data to pre-built widgets and customise with Javascript.";
export const ONBOARDING_INTRO_PUBLISH = () =>
"Instantly publish and share your apps";
export const BUILD_MY_FIRST_APP = () => "Build my first app";
export const ONBOARDING_INTRO_FOOTER = () =>
"Want more help getting started, let’s";
export const BUILD_APP_TOGETHER = () => "build an app together.";

//Statusbar
export const ONBOARDING_STATUS_STEPS_FIRST = () => "First: Create a Datasource";
export const ONBOARDING_STATUS_STEPS_FIRST_ALT = () =>
"Next: Create a Datasource";
export const ONBOARDING_STATUS_STEPS_SECOND = () => "Next: Create a Query";
export const ONBOARDING_STATUS_STEPS_THIRD = () => "Next: Add a Widget";
export const ONBOARDING_STATUS_STEPS_FOURTH = () =>
"Next: Connect data to Widget";
export const ONBOARDING_STATUS_STEPS_FIVETH = () =>
"Next: Deploy your application";
export const ONBOARDING_STATUS_STEPS_SIXTH = () => "Completed πŸŽ‰";
export const ONBOARDING_STATUS_GET_STARTED = () => "GET STARTED";

//Tasks
//1. datasource
export const ONBOARDING_TASK_DATASOURCE_HEADER = () =>
"Start by adding your first Data source";
export const ONBOARDING_TASK_DATASOURCE_BODY = () =>
"Adding a data source makes creating applications more powerful. Don’t worry if you don’t have any data to hand, we have sample data you can use.";
export const ONBOARDING_TASK_DATASOURCE_BUTTON = () => "+ Add a data source";
export const ONBOARDING_TASK_DATASOURCE_FOOTER_ACTION = () => "add a widget";
export const ONBOARDING_TASK_DATASOURCE_FOOTER = () => "first.";
//2. query
export const ONBOARDING_TASK_QUERY_HEADER = () => "Next, create a query";
export const ONBOARDING_TASK_QUERY_BODY = () =>
"Great job adding a data source! The next thing you can do is create a query on your data.";
export const ONBOARDING_TASK_QUERY_BUTTON = () => "+ create a query";
export const ONBOARDING_TASK_QUERY_FOOTER_ACTION = () => "add a widget";
//2. widget
export const ONBOARDING_TASK_WIDGET_HEADER = () =>
"Next, add a widget to start displaying data";
export const ONBOARDING_TASK_WIDGET_BODY = () =>
"Great job adding a data source! The next thing you can do is add widget to start start making your data visual.";
export const ONBOARDING_TASK_WIDGET_BUTTON = () => "+ Add a Widget";
export const ONBOARDING_TASK_WIDGET_FOOTER_ACTION = () =>
"deploy your application";
export const ONBOARDING_TASK_FOOTER = () => "Alternatively you can also";
19 changes: 19 additions & 0 deletions app/client/src/constants/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export const USERS_URL = "/users";
export const VIEWER_URL_REGEX = /applications\/.*?\/pages\/.*/;
export const UNSUBSCRIBE_EMAIL_URL = "/unsubscribe/discussion/:threadId";
export const SETUP = "/setup/welcome";
export const BUILDER_CHECKLIST_URL = `${BUILDER_URL}/checklist`;

export type BuilderRouteParams = {
applicationId: string;
Expand Down Expand Up @@ -243,6 +244,11 @@ export const getGenerateTemplateFormURL = (
pageId,
)}${GEN_TEMPLATE_URL}${GEN_TEMPLATE_FORM_ROUTE}`;

export const getOnboardingCheckListUrl = (
applicationId = ":applicationId",
pageId = ":pageId",
): string => `${BUILDER_PAGE_URL(applicationId, pageId)}/checklist`;

export const FORGOT_PASSWORD_URL = `${USER_AUTH_URL}/forgotPassword`;
export const RESET_PASSWORD_URL = `${USER_AUTH_URL}/resetPassword`;
export const BASE_SIGNUP_URL = `/signup`;
Expand All @@ -261,3 +267,16 @@ export const matchBuilderPath = match(BUILDER_URL);
export const matchJSObjectPath = match(JS_COLLECTION_ID_URL());

export const matchViewerPath = match(getApplicationViewerPageURL());

export const BUILDER_URL_REGEX = /\/applications\/(.[^\/]*)\/pages\/(.[^\/]*)\//;
export const extractAppIdAndPageIdFromUrl = (url = "") => {
const matched = url.match(BUILDER_URL_REGEX);
if (matched) {
return {
applicationId: matched[1],
pageId: matched[2],
};
}

return {};
};
55 changes: 43 additions & 12 deletions app/client/src/pages/Applications/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,10 @@ import AnalyticsUtil from "utils/AnalyticsUtil";
import { createOrganizationSubmitHandler } from "../organization/helpers";
import UserApi from "api/UserApi";
import ImportApplicationModal from "./ImportApplicationModal";
import { SIGNUP_SUCCESS_URL } from "constants/routes";
import {
extractAppIdAndPageIdFromUrl,
SIGNUP_SUCCESS_URL,
} from "constants/routes";

import { getIsSafeRedirectURL } from "utils/helpers";

Expand Down Expand Up @@ -879,7 +882,6 @@ function ApplicationsSection(props: any) {
}
type ApplicationProps = {
applicationList: ApplicationPayload[];
createApplication: (appName: string) => void;
searchApplications: (keyword: string) => void;
isCreatingApplication: creatingApplicationMap;
isFetchingApplications: boolean;
Expand All @@ -891,6 +893,7 @@ type ApplicationProps = {
userOrgs: any;
currentUser?: User;
searchKeyword: string | undefined;
enableFirstTimeUserOnboarding: (applicationId: string) => void;
};

const getIsFromSignup = () => {
Expand Down Expand Up @@ -923,9 +926,30 @@ class Applications extends Component<
redirectUsingQueryParam = () => {
const urlObject = new URL(window.location.href);
const redirectUrl = urlObject?.searchParams.get("redirectUrl");
const shouldEnableFirstTimeUserOnboarding = urlObject?.searchParams.get(
"enableFirstTimeUserExperience",
);
if (redirectUrl) {
try {
if (getIsSafeRedirectURL(redirectUrl)) {
if (
window.location.pathname == SIGNUP_SUCCESS_URL &&
shouldEnableFirstTimeUserOnboarding === "true"
) {
const { applicationId, pageId } = extractAppIdAndPageIdFromUrl(
redirectUrl,
);
if (applicationId && pageId) {
this.props.enableFirstTimeUserOnboarding(applicationId);
/*
* window.location.replace resets the application, adding
* this timeout to store onboarding variables in indexdb
*/

setTimeout(() => {
window.location.replace(redirectUrl);
Comment thread
rishabhsaxena marked this conversation as resolved.
});
}
} else if (getIsSafeRedirectURL(redirectUrl)) {
window.location.replace(redirectUrl);
}
} catch (e) {
Expand Down Expand Up @@ -965,15 +989,8 @@ const mapStateToProps = (state: AppState) => ({
});

const mapDispatchToProps = (dispatch: any) => ({
getAllApplication: () =>
dispatch({ type: ReduxActionTypes.GET_ALL_APPLICATION_INIT }),
createApplication: (appName: string) => {
dispatch({
type: ReduxActionTypes.CREATE_APPLICATION_INIT,
payload: {
name: appName,
},
});
getAllApplication: () => {
dispatch({ type: ReduxActionTypes.GET_ALL_APPLICATION_INIT });
},
searchApplications: (keyword: string) => {
dispatch({
Expand All @@ -983,6 +1000,20 @@ const mapDispatchToProps = (dispatch: any) => ({
},
});
},
enableFirstTimeUserOnboarding: (applicationId: string) => {
dispatch({
type: ReduxActionTypes.SET_ENABLE_FIRST_TIME_USER_ONBOARDING,
payload: true,
});
dispatch({
type: ReduxActionTypes.SET_FIRST_TIME_USER_ONBOARDING_APPLICATION_ID,
payload: applicationId,
});
dispatch({
type: ReduxActionTypes.SET_SHOW_FIRST_TIME_USER_ONBOARDING_MODAL,
payload: true,
});
},
});

export default connect(mapStateToProps, mapDispatchToProps)(Applications);
20 changes: 19 additions & 1 deletion app/client/src/pages/Editor/Explorer/index.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,31 @@
import { IPanelProps } from "@blueprintjs/core";
import { forceOpenWidgetPanel } from "actions/widgetSidebarActions";
import React from "react";
import { useSelector } from "react-redux";
import { useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import { AppState } from "reducers";
import { inOnboarding, isAddWidgetComplete } from "sagas/OnboardingSagas";
import WidgetSidebar from "../WidgetSidebar";
import EntityExplorer from "./EntityExplorer";
import OnboardingExplorer from "./Onboarding";

function ExplorerContent(props: IPanelProps) {
const isInOnboarding = useSelector(inOnboarding);
const addWidgetComplete = useSelector(isAddWidgetComplete);
const isForceOpenWidgetPanel = useSelector(
(state: AppState) => state.ui.onBoarding.forceOpenWidgetPanel,
);
const dispatch = useDispatch();
useEffect(() => {
if (isForceOpenWidgetPanel) {
props.openPanel({ component: WidgetSidebar });
}
return () => {
if (isForceOpenWidgetPanel) {
dispatch(forceOpenWidgetPanel(false));
}
};
}, [isForceOpenWidgetPanel]);

if (isInOnboarding && !addWidgetComplete) {
return <OnboardingExplorer {...props} />;
Expand Down
Loading