1 import { isValidElement, useState } from 'react';
3 import { useWelcomeFlags } from '@proton/account';
4 import { useUserSettings } from '@proton/account/userSettings/hooks';
5 import type { ModalSize } from '@proton/components/components/modalTwo/Modal';
6 import ModalTwo from '@proton/components/components/modalTwo/Modal';
7 import ModalTwoContent from '@proton/components/components/modalTwo/ModalContent';
8 import StepDot from '@proton/components/components/stepDot/StepDot';
9 import StepDots from '@proton/components/components/stepDots/StepDots';
10 import useApi from '@proton/components/hooks/useApi';
11 import { updateFlags, updateWelcomeFlags } from '@proton/shared/lib/api/settings';
12 import clsx from '@proton/utils/clsx';
13 import noop from '@proton/utils/noop';
14 import range from '@proton/utils/range';
16 import type { OnboardingStepComponent, OnboardingStepProps } from './interface';
17 import useGenericSteps from './useGenericSteps';
19 import './OnboardingModal.scss';
22 children?: OnboardingStepComponent[];
24 extraProductStep?: OnboardingStepComponent[];
25 genericSteps?: Partial<Record<'discoverAppsStep' | 'setupThemeStep' | 'organizationStep', OnboardingStepComponent>>;
26 hideDiscoverApps?: boolean;
27 /** Some onboarding modals need custom dimension / margins */
28 hideOrganizationSetup?: boolean;
29 modalClassname?: string;
30 modalContentClassname?: string;
34 showGenericSteps?: boolean;
36 stepDotClassName?: string;
39 const OnboardingModal = ({
43 hideDiscoverApps = false,
44 hideOrganizationSetup = false,
46 modalContentClassname = 'm-8',
53 const [userSettings] = useUserSettings();
55 const { welcomeFlags } = useWelcomeFlags();
56 // Using useState so that isReplay is only updated when the modal closes, not when welcomeFlags change.
57 const [isReplay] = useState(welcomeFlags.isReplay);
58 let isLastStep = false;
60 const [step, setStep] = useState(0);
62 const handleNext = () => {
64 if (welcomeFlags.isWelcomeFlow) {
65 // Set generic welcome to true
66 api(updateFlags({ Welcomed: 1 })).catch(noop);
68 if (!userSettings.WelcomeFlag) {
69 // Set product specific welcome to true
70 api(updateWelcomeFlags()).catch(noop);
76 setStep((step) => step + 1);
79 const handleBack = () => {
80 setStep((step) => step - 1);
83 const displayGenericSteps = welcomeFlags.hasGenericWelcomeStep || isReplay || showGenericSteps;
84 const genericStepsComponents = useGenericSteps({
88 hideOrganizationSetup,
92 const productSteps = children
93 ? (Array.isArray(children) ? children : [children]).map(
103 const extraSteps = extraProductStep
104 ? (Array.isArray(extraProductStep) ? extraProductStep : [extraProductStep]).map(
114 const steps = [...productSteps, ...(displayGenericSteps ? genericStepsComponents : []), ...extraSteps].filter(
117 isLastStep = steps.length - 1 === step;
118 const childStep = steps[step];
119 const displayDots = steps.length > 1 && step < steps.length;
125 if (!isValidElement<OnboardingStepProps>(childStep)) {
126 throw new Error('Missing step');
130 <ModalTwo {...rest} size={size} className={clsx('onboarding-modal', modalClassname)}>
131 <ModalTwoContent className={modalContentClassname}>
134 <div className="text-center">
135 <StepDots value={step} ulClassName={clsx('mb-0', stepDotClassName)}>
136 {range(0, steps.length).map((index) => (
138 active={index === step}
141 aria-controls={`onboarding-${index}`}
155 export default OnboardingModal;