diff --git a/components/dashboard/src/app/AppRoutes.tsx b/components/dashboard/src/app/AppRoutes.tsx index 5cf926240b56e5..bd0d4f6a44bb1b 100644 --- a/components/dashboard/src/app/AppRoutes.tsx +++ b/components/dashboard/src/app/AppRoutes.tsx @@ -45,6 +45,7 @@ import { StartWorkspaceModalContext } from "../workspaces/start-workspace-modal- import { OrgRequiredRoute } from "./OrgRequiredRoute"; import { WebsocketClients } from "./WebsocketClients"; import { StartWorkspaceOptions } from "../start/start-workspace-options"; +import { useFeatureFlags } from "../contexts/FeatureFlagContext"; const Setup = React.lazy(() => import(/* webpackPrefetch: true */ "../Setup")); const Workspaces = React.lazy(() => import(/* webpackPrefetch: true */ "../workspaces/Workspaces")); @@ -88,6 +89,7 @@ const ProjectsSearch = React.lazy(() => import(/* webpackPrefetch: true */ "../a const TeamsSearch = React.lazy(() => import(/* webpackPrefetch: true */ "../admin/TeamsSearch")); const License = React.lazy(() => import(/* webpackPrefetch: true */ "../admin/License")); const Usage = React.lazy(() => import(/* webpackPrefetch: true */ "../Usage")); +const UserOnboarding = React.lazy(() => import(/* webpackPrefetch: true */ "../onboarding/UserOnboarding")); type AppRoutesProps = { user: User; @@ -99,6 +101,7 @@ export const AppRoutes: FunctionComponent = ({ user, teams }) => const [isWhatsNewShown, setWhatsNewShown] = useState(shouldSeeWhatsNew(user)); const newCreateWsPage = useNewCreateWorkspacePage(); const location = useLocation(); + const { newSignupFlow } = useFeatureFlags(); // Prefix with `/#referrer` will specify an IDE for workspace // We don't need to show IDE preference in this case @@ -120,12 +123,18 @@ export const AppRoutes: FunctionComponent = ({ user, teams }) => return setWhatsNewShown(false)} />; } + // Placeholder for new signup flow + if (newSignupFlow && User.isOnboardingUser(user)) { + return ; + } + // TODO: Try and encapsulate this in a route for "/" (check for hash in route component, render or redirect accordingly) const isCreation = location.pathname === "/" && hash !== ""; if (isCreation) { if (showUserIdePreference) { return ( + {/* TODO: ensure we don't show this after new onboarding flow */} setShowUserIdePreference(false)} /> ); diff --git a/components/dashboard/src/contexts/FeatureFlagContext.tsx b/components/dashboard/src/contexts/FeatureFlagContext.tsx index f994822eba9983..ad4e00679b8bbb 100644 --- a/components/dashboard/src/contexts/FeatureFlagContext.tsx +++ b/components/dashboard/src/contexts/FeatureFlagContext.tsx @@ -4,7 +4,7 @@ * See License.AGPL.txt in the project root for license information. */ -import React, { createContext, useContext, useState, useEffect } from "react"; +import React, { createContext, useContext, useState, useEffect, useMemo } from "react"; import { getExperimentsClient } from "../experiments/client"; import { ProjectContext } from "../projects/project-context"; import { useCurrentTeam, useTeams } from "../teams/teams-context"; @@ -14,17 +14,11 @@ interface FeatureFlagConfig { [flagName: string]: { defaultValue: boolean; setter: React.Dispatch> }; } -const FeatureFlagContext = createContext<{ - startWithOptions: boolean; - showUsageView: boolean; - isUsageBasedBillingEnabled: boolean; - showUseLastSuccessfulPrebuild: boolean; - usePublicApiWorkspacesService: boolean; - enablePersonalAccessTokens: boolean; - oidcServiceEnabled: boolean; - orgGitAuthProviders: boolean; - switchToPAYG: boolean; -}>({ +type FeatureFlagsType = { + [k in keyof typeof defaultFeatureFlags]: boolean; +}; + +const defaultFeatureFlags = { startWithOptions: false, showUsageView: false, isUsageBasedBillingEnabled: false, @@ -34,7 +28,10 @@ const FeatureFlagContext = createContext<{ oidcServiceEnabled: false, orgGitAuthProviders: false, switchToPAYG: false, -}); + newSignupFlow: false, +}; + +const FeatureFlagContext = createContext(defaultFeatureFlags); const FeatureFlagContextProvider: React.FC = ({ children }) => { const { user } = useContext(UserContext); @@ -50,6 +47,7 @@ const FeatureFlagContextProvider: React.FC = ({ children }) => { const [oidcServiceEnabled, setOidcServiceEnabled] = useState(false); const [orgGitAuthProviders, setOrgGitAuthProviders] = useState(false); const [switchToPAYG, setSwitchToPAYG] = useState(false); + const [newSignupFlow, setNewSignupFlow] = useState(false); useEffect(() => { if (!user) return; @@ -67,6 +65,7 @@ const FeatureFlagContextProvider: React.FC = ({ children }) => { oidcServiceEnabled: { defaultValue: false, setter: setOidcServiceEnabled }, orgGitAuthProviders: { defaultValue: false, setter: setOrgGitAuthProviders }, switchToPAYG: { defaultValue: false, setter: setSwitchToPAYG }, + newSignupFlow: { defaultValue: false, setter: setNewSignupFlow }, }; for (const [flagName, config] of Object.entries(featureFlags)) { @@ -103,23 +102,33 @@ const FeatureFlagContextProvider: React.FC = ({ children }) => { })(); }, [user, teams, team, project]); - return ( - - {children} - - ); + const flags = useMemo(() => { + return { + startWithOptions, + showUsageView, + isUsageBasedBillingEnabled, + showUseLastSuccessfulPrebuild, + enablePersonalAccessTokens, + usePublicApiWorkspacesService, + oidcServiceEnabled, + orgGitAuthProviders, + newSignupFlow, + switchToPAYG, + }; + }, [ + enablePersonalAccessTokens, + isUsageBasedBillingEnabled, + newSignupFlow, + oidcServiceEnabled, + orgGitAuthProviders, + showUsageView, + showUseLastSuccessfulPrebuild, + startWithOptions, + switchToPAYG, + usePublicApiWorkspacesService, + ]); + + return {children}; }; export { FeatureFlagContext, FeatureFlagContextProvider }; diff --git a/components/dashboard/src/onboarding/UserOnboarding.tsx b/components/dashboard/src/onboarding/UserOnboarding.tsx new file mode 100644 index 00000000000000..26bdea832ea01e --- /dev/null +++ b/components/dashboard/src/onboarding/UserOnboarding.tsx @@ -0,0 +1,23 @@ +/** + * Copyright (c) 2023 Gitpod GmbH. All rights reserved. + * Licensed under the GNU Affero General Public License (AGPL). + * See License.AGPL.txt in the project root for license information. + */ + +import { User } from "@gitpod/gitpod-protocol"; +import { FunctionComponent } from "react"; + +type Props = { + user: User; +}; +const UserOnboarding: FunctionComponent = ({ user }) => { + // Placeholder UI to start stubbing out new flow + return ( +
+

Welcome

+ +

Help us get to know you a bit better

+
+ ); +}; +export default UserOnboarding; diff --git a/components/dashboard/src/workspaces/Workspaces.tsx b/components/dashboard/src/workspaces/Workspaces.tsx index de86ad648d5c2b..91eda460d65c08 100644 --- a/components/dashboard/src/workspaces/Workspaces.tsx +++ b/components/dashboard/src/workspaces/Workspaces.tsx @@ -19,6 +19,7 @@ import { EmptyWorkspacesContent } from "./EmptyWorkspacesContent"; import { WorkspacesSearchBar } from "./WorkspacesSearchBar"; import { hoursBefore, isDateSmallerOrEqual } from "@gitpod/gitpod-protocol/lib/util/timeutil"; import { useDeleteInactiveWorkspacesMutation } from "../data/workspaces/delete-inactive-workspaces-mutation"; +import { useFeatureFlags } from "../contexts/FeatureFlagContext"; const WorkspacesPage: FunctionComponent = () => { const user = useCurrentUser(); @@ -29,6 +30,7 @@ const WorkspacesPage: FunctionComponent = () => { const { data, isLoading } = useListWorkspacesQuery({ limit }); const isOnboardingUser = useMemo(() => user && User.isOnboardingUser(user), [user]); const deleteInactiveWorkspaces = useDeleteInactiveWorkspacesMutation(); + const { newSignupFlow } = useFeatureFlags(); // Sort workspaces into active/inactive groups const { activeWorkspaces, inactiveWorkspaces } = useMemo(() => { @@ -94,12 +96,9 @@ const WorkspacesPage: FunctionComponent = () => { /> )} - {isOnboardingUser ? ( - - ) : ( - // modal hides itself - - )} + {isOnboardingUser && !newSignupFlow && } + + {!isOnboardingUser && !newSignupFlow && } {!isLoading && (activeWorkspaces.length > 0 || inactiveWorkspaces.length > 0 || searchTerm ? (