From eef97881cea05a5ec71044ee05a73cd400f56402 Mon Sep 17 00:00:00 2001 From: Sebastian Florek Date: Mon, 13 Nov 2023 17:51:07 +0100 Subject: [PATCH] feat: add cd onboarding path (#1269) Co-authored-by: Klink <85062+dogmar@users.noreply.github.com> --- .../shell/onboarding/Onboarding.tsx | 20 ++- .../shell/onboarding/OnboardingCardButton.tsx | 1 + .../shell/onboarding/OnboardingFlow.tsx | 7 +- .../shell/onboarding/OnboardingHeader.tsx | 7 +- .../shell/onboarding/context/hooks.ts | 35 ++++-- .../shell/onboarding/context/onboarding.ts | 5 + .../shell/onboarding/context/types.ts | 8 ++ .../onboarding/sections/cloud/CloudOption.tsx | 6 +- .../sections/cloud/ProviderSelection.tsx | 55 +++++---- .../sections/overview/OverviewStep.tsx | 77 ++++++++---- .../sections/welcome/PathOption.tsx | 50 ++++++++ .../sections/welcome/WelcomeStep.tsx | 114 ++++++++++++++++++ 12 files changed, 316 insertions(+), 69 deletions(-) create mode 100644 www/src/components/shell/onboarding/sections/welcome/PathOption.tsx create mode 100644 www/src/components/shell/onboarding/sections/welcome/WelcomeStep.tsx diff --git a/www/src/components/shell/onboarding/Onboarding.tsx b/www/src/components/shell/onboarding/Onboarding.tsx index 47cbe5e9a..810f243da 100644 --- a/www/src/components/shell/onboarding/Onboarding.tsx +++ b/www/src/components/shell/onboarding/Onboarding.tsx @@ -17,6 +17,7 @@ import { ContextProps, OnboardingContext } from './context/onboarding' import { CloudProps, CreateCloudShellSectionState, + OnboardingPath, SCMProps, Section, SectionKey, @@ -112,13 +113,18 @@ interface OnboardingProps { function OnboardingWithContext({ ...props }: OnboardingProps): ReactElement { const { restoredContext, reset } = useContextStorage() - const [scm, setSCM] = useState(restoredContext?.scm ?? {}) + const [scm, setSCM] = useState( + restoredContext?.scm ?? ({} as SCMProps) + ) const [valid, setValid] = useState(restoredContext?.valid ?? true) - const [cloud, setCloud] = useState(restoredContext?.cloud ?? {}) - const [sections, setSections] = useState(defaultSections()) - const [section, setSection] = useState
( - sections[SectionKey.ONBOARDING_OVERVIEW]! + const [cloud, setCloud] = useState( + restoredContext?.cloud ?? ({} as CloudProps) ) + const [path, setPath] = useState( + restoredContext?.path ?? OnboardingPath.None + ) + const [sections, setSections] = useState(defaultSections()) + const [section, setSection] = useState
(sections[SectionKey.WELCOME]!) const [workspace, setWorkspace] = useState( restoredContext?.workspace ?? {} ) @@ -146,8 +152,10 @@ function OnboardingWithContext({ ...props }: OnboardingProps): ReactElement { workspace, setWorkspace, impersonation, + path, + setPath, }), - [scm, cloud, valid, sections, section, workspace, impersonation] + [scm, cloud, valid, sections, section, workspace, impersonation, path] ) return ( diff --git a/www/src/components/shell/onboarding/OnboardingCardButton.tsx b/www/src/components/shell/onboarding/OnboardingCardButton.tsx index 91c0697bb..5bf3f8100 100644 --- a/www/src/components/shell/onboarding/OnboardingCardButton.tsx +++ b/www/src/components/shell/onboarding/OnboardingCardButton.tsx @@ -33,6 +33,7 @@ function OnboardingCardButton({ selected = false, children, ...props }: any) { backgroundColor="fill-two" border="1px solid border-fill-two" borderColor={selected ? 'action-link-inline' : 'border-fill-two'} + width="100%" _hover={{ backgroundColor: 'fill-two-hover', borderColor: selected ? 'action-link-inline' : 'border-fill-two', diff --git a/www/src/components/shell/onboarding/OnboardingFlow.tsx b/www/src/components/shell/onboarding/OnboardingFlow.tsx index 3a68ab10d..63937fc63 100644 --- a/www/src/components/shell/onboarding/OnboardingFlow.tsx +++ b/www/src/components/shell/onboarding/OnboardingFlow.tsx @@ -16,6 +16,7 @@ import { CreateCloudShellSectionState, SectionKey } from './context/types' import CreateShellStep from './sections/shell/CreateShellStep' import OverviewStep from './sections/overview/OverviewStep' import OnboardingTips from './OnboardingTips' +import WelcomeStep from './sections/welcome/WelcomeStep' function OnboardingFlow({ onNext, onBack }) { const token = useToken() || '' @@ -48,8 +49,12 @@ function OnboardingFlow({ onNext, onBack }) { title={isCreating ? '' : section.title} mode={isCreating ? 'Compact' : 'Default'} > + {section?.key === SectionKey.WELCOME && } {section?.key === SectionKey.ONBOARDING_OVERVIEW && ( - + )} {section?.key === SectionKey.CONFIGURE_CLOUD && ( { const sections: Sections = { - [SectionKey.ONBOARDING_OVERVIEW]: { + [SectionKey.WELCOME]: { index: 0, + key: SectionKey.WELCOME, + title: 'Welcome to Plural!', + IconComponent: AppsIcon, + }, + [SectionKey.ONBOARDING_OVERVIEW]: { + index: 1, key: SectionKey.ONBOARDING_OVERVIEW, title: 'Onboarding overview', IconComponent: ChecklistIcon, }, [SectionKey.CONFIGURE_CLOUD]: { - index: 1, + index: 2, key: SectionKey.CONFIGURE_CLOUD, title: 'Configure credentials', IconComponent: CloudIcon, }, [SectionKey.CONFIGURE_WORKSPACE]: { - index: 2, + index: 3, key: SectionKey.CONFIGURE_WORKSPACE, title: 'Configure workspace', IconComponent: WorkspaceIcon, }, [SectionKey.CREATE_CLOUD_SHELL]: { - index: 3, + index: 4, key: SectionKey.CREATE_CLOUD_SHELL, title: 'Create cloud shell', IconComponent: TerminalIcon, @@ -53,8 +60,11 @@ const defaultSections = (): Sections => { } // build sections flow + sections[SectionKey.WELCOME]!.next = sections[SectionKey.ONBOARDING_OVERVIEW] + sections[SectionKey.ONBOARDING_OVERVIEW]!.next = sections[SectionKey.CONFIGURE_CLOUD] + sections[SectionKey.ONBOARDING_OVERVIEW]!.prev = sections[SectionKey.WELCOME] sections[SectionKey.CONFIGURE_CLOUD]!.prev = sections[SectionKey.ONBOARDING_OVERVIEW] @@ -74,26 +84,32 @@ const defaultSections = (): Sections => { const localCLISections = (): Sections => { const sections: Sections = { - [SectionKey.ONBOARDING_OVERVIEW]: { + [SectionKey.WELCOME]: { index: 0, + key: SectionKey.WELCOME, + title: 'Welcome to Plural!', + IconComponent: ChecklistIcon, + }, + [SectionKey.ONBOARDING_OVERVIEW]: { + index: 1, key: SectionKey.ONBOARDING_OVERVIEW, title: 'Onboarding overview', IconComponent: ChecklistIcon, }, [SectionKey.CONFIGURE_CLOUD]: { - index: 1, + index: 2, key: SectionKey.CONFIGURE_CLOUD, title: 'Configure credentials', IconComponent: CloudIcon, }, [SectionKey.INSTALL_CLI]: { - index: 2, + index: 3, key: SectionKey.INSTALL_CLI, title: 'Install Plural CLI', IconComponent: TerminalIcon, }, [SectionKey.COMPLETE_SETUP]: { - index: 3, + index: 4, key: SectionKey.COMPLETE_SETUP, title: 'Complete Setup', IconComponent: ListIcon, @@ -101,8 +117,11 @@ const localCLISections = (): Sections => { } // build sections flow + sections[SectionKey.WELCOME]!.next = sections[SectionKey.ONBOARDING_OVERVIEW] + sections[SectionKey.ONBOARDING_OVERVIEW]!.next = sections[SectionKey.CONFIGURE_CLOUD] + sections[SectionKey.ONBOARDING_OVERVIEW]!.prev = sections[SectionKey.WELCOME] sections[SectionKey.CONFIGURE_CLOUD]!.prev = sections[SectionKey.ONBOARDING_OVERVIEW] diff --git a/www/src/components/shell/onboarding/context/onboarding.ts b/www/src/components/shell/onboarding/context/onboarding.ts index ab234f651..86bc486b6 100644 --- a/www/src/components/shell/onboarding/context/onboarding.ts +++ b/www/src/components/shell/onboarding/context/onboarding.ts @@ -3,6 +3,7 @@ import { Dispatch, SetStateAction, createContext } from 'react' import { CloudProps, Impersonation, + OnboardingPath, SCMProps, Section, Sections, @@ -22,6 +23,8 @@ interface ContextProps { setSections: Dispatch> section: Section setSection: Dispatch> + path: OnboardingPath + setPath: Dispatch> } interface SerializableContextProps { @@ -32,6 +35,7 @@ interface SerializableContextProps { section: Section sections?: Sections impersonation?: Impersonation + path?: OnboardingPath } const toSerializableSection = (section: Section): Partial
=> ({ @@ -54,6 +58,7 @@ const toSerializableContext = ( }, section: toSerializableSection(context.section) as Section, impersonation, + path: context?.path, }) const OnboardingContext = createContext({} as ContextProps) diff --git a/www/src/components/shell/onboarding/context/types.ts b/www/src/components/shell/onboarding/context/types.ts index 3126ef1eb..c5b9c40aa 100644 --- a/www/src/components/shell/onboarding/context/types.ts +++ b/www/src/components/shell/onboarding/context/types.ts @@ -28,6 +28,7 @@ enum CloudProviderToProvider { } enum SectionKey { + WELCOME = 'WELCOME', ONBOARDING_OVERVIEW = 'ONBOARDING_OVERVIEW', CONFIGURE_CLOUD = 'CONFIGURE_CLOUD', CONFIGURE_WORKSPACE = 'CONFIGURE_WORKSPACE', @@ -133,6 +134,12 @@ interface Impersonation { user?: User } +enum OnboardingPath { + None, + CD, + OSS, +} + export type { Sections, Section, @@ -155,4 +162,5 @@ export { CloudProviderToProvider, CreateCloudShellSectionState, ConfigureCloudSectionState, + OnboardingPath, } diff --git a/www/src/components/shell/onboarding/sections/cloud/CloudOption.tsx b/www/src/components/shell/onboarding/sections/cloud/CloudOption.tsx index ba5b391fb..370396b78 100644 --- a/www/src/components/shell/onboarding/sections/cloud/CloudOption.tsx +++ b/www/src/components/shell/onboarding/sections/cloud/CloudOption.tsx @@ -22,7 +22,11 @@ function CloudOption({ /> } > -
+
path !== undefined, [path]) useEffect(() => setCloud((c) => ({ ...c, type: path })), [path, setCloud]) @@ -49,27 +56,29 @@ function ProviderSelection() { header="Use your own cloud" description="Connect your own cloud credentials and spin up your own cluster." /> - setPath(CloudType.Demo)} - disabled={demoed} - tooltip={ - demoed - ? 'You have reached the maximum number of demo environment usage.' - : undefined - } - icon={ - - } - header="Try free demo" - description="A six-hour GCP sandbox for you to test-drive Plural." - /> + {onboardingPath === OnboardingPath.OSS && ( + setPath(CloudType.Demo)} + disabled={demoed} + tooltip={ + demoed + ? 'You have reached the maximum number of demo environment usage.' + : undefined + } + icon={ + + } + header="Try free demo" + description="A six-hour GCP sandbox for you to test-drive Plural." + /> + )} - + {onboardingPath === OnboardingPath.OSS && } {(path === CloudType.Cloud || path === CloudType.Local) && ( - - Deploy your cluster and applications with Plural in about 30 minutes, - then access it via Plural Console.  - - View a demo environment of our Console. - - + {path === OnboardingPath.OSS && ( + + Deploy your cluster and applications with Plural in about 30 + minutes, then access it via Plural Console.  + + View a demo environment of our Console. + + + )} + + {path === OnboardingPath.CD && ( + + Deployments are managed in the Plural Console, which we’ll deploy on + a Management Cluster for you in your cloud environment. You can then + navigate to the Console to start deploying your applications. + + )} - 1. Configure your cloud and git credentials. - 2. Configure your cluster’s workspace. - - 3. Create your cloud shell where you can install applications. (25 - minutes deploy wait time) - + {path === OnboardingPath.CD && ( + <> + 1. Configure your cloud and Git credentials. + 2. Configure your Management Cluster's workspace. + + 3. Create your cloud shell to deploy your Plural Console. (25 + minute deploy wait time) + + + )} + + {path === OnboardingPath.OSS && ( + <> + 1. Configure your cloud and Git credentials. + 2. Configure your cluster’s workspace. + + 3. Create your cloud shell where you can install applications. + (25 minute deploy wait time) + + + )} - Get started + Continue diff --git a/www/src/components/shell/onboarding/sections/welcome/PathOption.tsx b/www/src/components/shell/onboarding/sections/welcome/PathOption.tsx new file mode 100644 index 000000000..ab66939d2 --- /dev/null +++ b/www/src/components/shell/onboarding/sections/welcome/PathOption.tsx @@ -0,0 +1,50 @@ +import { Div, Flex, Span } from 'honorable' + +import OnboardingCardButton from '../../OnboardingCardButton' + +function PathOption({ + icon, + header, + description, + selected, + disabled, + ...props +}: any) { + return ( + + +
+ {icon} +
+ + {header} + + + {description} + +
+
+ ) +} + +export { PathOption } diff --git a/www/src/components/shell/onboarding/sections/welcome/WelcomeStep.tsx b/www/src/components/shell/onboarding/sections/welcome/WelcomeStep.tsx new file mode 100644 index 000000000..ba31c0f83 --- /dev/null +++ b/www/src/components/shell/onboarding/sections/welcome/WelcomeStep.tsx @@ -0,0 +1,114 @@ +import { Flex, Span } from 'honorable' +import { BrowseAppsIcon, Button, CloudIcon } from '@pluralsh/design-system' +import { useNavigate } from 'react-router-dom' +import { PopupModal } from 'react-calendly' +import { useContext, useState } from 'react' + +import useOnboarded from '../../../hooks/useOnboarded' +import CalendarIcon from '../../assets/CalendarIcon.svg' + +import { OnboardingContext } from '../../context/onboarding' + +import { OnboardingPath } from '../../context/types' + +import { PathOption } from './PathOption' + +function WelcomeStep({ onNext }) { + const navigate = useNavigate() + const { fresh: isOnboarding, mutation } = useOnboarded() + const { path, setPath } = useContext(OnboardingContext) + + const [calendlyOpen, setCalendlyOpen] = useState(false) + + return ( + + + How would you like to get started today? + + + setPath(OnboardingPath.CD)} + icon={ + + } + header="Deploy your own applications" + description="Use Plural Continuous Deployments to deploy applications and services from your organization." + /> + setPath(OnboardingPath.OSS)} + icon={ + + } + header="Deploy OSS applications" + description="Explore our marketplace for open-source applications to deploy." + /> + + + + {isOnboarding && ( + + )} + + + setCalendlyOpen(false)} + /> + + + + + ) +} + +export default WelcomeStep