Polish onboarding v2 verify animation and step screens (#22585)
https://github.com/user-attachments/assets/1d9b8dc2-ef01-4202-a97e-b41cab048f87 A few polish tweaks to onboarding v2: - **Verify/workspace-creation animation:** emphasize the key phrase of each message in medium weight, the rest regular (e.g. "Creating your **workspace**…"). - **Wider content column:** 340px → 440px. Collapses to full width on mobile via the existing `max-width: 100%` on every consumer. - **Sticky disabled buttons:** step submit buttons now stay disabled from submit through navigation instead of briefly re-enabling once the mutation resolves. - **Fewer pulse loaders:** stop the pulsing logo from flashing when navigating between onboarding steps (removed the step-page Suspense fallback loader). The verify animation, cold-boot gates, and sign-in fallbacks are unchanged. Note: the reworded activation messages get new Lingui catalog IDs, so non-English locales fall back to English until catalogs are re-extracted. <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22585?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. -->
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
import { AppPath } from 'twenty-shared/types';
|
||||
|
||||
export const ONBOARDING_VERIFY_PATHS = [
|
||||
AppPath.Verify,
|
||||
AppPath.WorkspaceActivation,
|
||||
];
|
||||
-8
@@ -1,8 +0,0 @@
|
||||
import { msg } from '@lingui/core/macro';
|
||||
|
||||
export const WORKSPACE_ACTIVATION_MESSAGES = [
|
||||
msg`Creating your workspace...`,
|
||||
msg`Setting up your database...`,
|
||||
msg`Creating your data model...`,
|
||||
msg`Prefilling your workspace data...`,
|
||||
];
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
import { StyledOnboardingActivationMessageEmphasis } from '@/onboarding/components/StyledOnboardingActivationMessageEmphasis';
|
||||
import { type OnboardingActivationMessage } from '@/onboarding/types/OnboardingActivationMessage';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
|
||||
export const WORKSPACE_ACTIVATION_MESSAGES: OnboardingActivationMessage[] = [
|
||||
{
|
||||
id: 'creating-workspace',
|
||||
content: (
|
||||
<Trans>
|
||||
Creating your{' '}
|
||||
<StyledOnboardingActivationMessageEmphasis>
|
||||
workspace
|
||||
</StyledOnboardingActivationMessageEmphasis>
|
||||
...
|
||||
</Trans>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'setting-up-database',
|
||||
content: (
|
||||
<Trans>
|
||||
Setting up your{' '}
|
||||
<StyledOnboardingActivationMessageEmphasis>
|
||||
database
|
||||
</StyledOnboardingActivationMessageEmphasis>
|
||||
...
|
||||
</Trans>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'creating-data-model',
|
||||
content: (
|
||||
<Trans>
|
||||
Creating your{' '}
|
||||
<StyledOnboardingActivationMessageEmphasis>
|
||||
data model
|
||||
</StyledOnboardingActivationMessageEmphasis>
|
||||
...
|
||||
</Trans>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'prefilling-workspace-data',
|
||||
content: (
|
||||
<Trans>
|
||||
Prefilling your{' '}
|
||||
<StyledOnboardingActivationMessageEmphasis>
|
||||
workspace data
|
||||
</StyledOnboardingActivationMessageEmphasis>
|
||||
...
|
||||
</Trans>
|
||||
),
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,9 @@
|
||||
import { matchPath } from 'react-router-dom';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { ONBOARDING_VERIFY_PATHS } from '@/auth/constants/OnboardingVerifyPaths';
|
||||
|
||||
export const isOnOnboardingVerifyPath = (pathname: string) =>
|
||||
ONBOARDING_VERIFY_PATHS.some((verifyPath) =>
|
||||
isDefined(matchPath(verifyPath, pathname)),
|
||||
);
|
||||
+3
-5
@@ -1,8 +1,7 @@
|
||||
import { SubTitle } from '@/auth/components/SubTitle';
|
||||
import { useOnboardingMotionTransition } from '@/onboarding/hooks/useOnboardingMotionTransition';
|
||||
import { type OnboardingActivationMessage } from '@/onboarding/types/OnboardingActivationMessage';
|
||||
import { styled } from '@linaria/react';
|
||||
import { type MessageDescriptor } from '@lingui/core';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { motion } from 'framer-motion';
|
||||
|
||||
const STEP_OPACITIES = [1, 0.4, 0.12];
|
||||
@@ -26,7 +25,7 @@ const StyledStepBase = styled.div`
|
||||
const StyledStep = motion.create(StyledStepBase);
|
||||
|
||||
type OnboardingActivationStepsProps = {
|
||||
messages: MessageDescriptor[];
|
||||
messages: OnboardingActivationMessage[];
|
||||
messageIndex: number;
|
||||
};
|
||||
|
||||
@@ -34,7 +33,6 @@ export const OnboardingActivationSteps = ({
|
||||
messages,
|
||||
messageIndex,
|
||||
}: OnboardingActivationStepsProps) => {
|
||||
const { i18n } = useLingui();
|
||||
const transition = useOnboardingMotionTransition();
|
||||
|
||||
return (
|
||||
@@ -53,7 +51,7 @@ export const OnboardingActivationSteps = ({
|
||||
}}
|
||||
transition={transition}
|
||||
>
|
||||
<SubTitle>{i18n._(message)}</SubTitle>
|
||||
<SubTitle>{message.content}</SubTitle>
|
||||
</StyledStep>
|
||||
);
|
||||
})}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import { useOnboardingContentWidth } from '@/onboarding/hooks/useOnboardingContentWidth';
|
||||
import { useOnboardingMotionTransition } from '@/onboarding/hooks/useOnboardingMotionTransition';
|
||||
import { ONBOARDING_CONTENT_BLOCK_WIDTH } from '@/onboarding/constants/OnboardingContentBlockWidth';
|
||||
import { styled } from '@linaria/react';
|
||||
import { motion } from 'framer-motion';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { IconChevronLeft, IconCoins, IconInfoCircle } from 'twenty-ui/icon';
|
||||
@@ -30,10 +28,10 @@ const StyledLeftSide = styled(StyledSide)`
|
||||
padding-right: ${themeCssVariables.spacing[1]};
|
||||
`;
|
||||
|
||||
const StyledCenter = styled.div<{ contentWidth: number }>`
|
||||
const StyledCenter = styled.div`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex: 0 1 ${({ contentWidth }) => `${contentWidth}px`};
|
||||
flex: 0 1 ${ONBOARDING_CONTENT_BLOCK_WIDTH}px;
|
||||
justify-content: flex-start;
|
||||
min-width: 0;
|
||||
`;
|
||||
@@ -43,7 +41,7 @@ const StyledRightSide = styled(StyledSide)`
|
||||
padding-left: ${themeCssVariables.spacing[1]};
|
||||
`;
|
||||
|
||||
const StyledLogoBase = styled.div`
|
||||
const StyledLogo = styled.div`
|
||||
background-image: url('/images/integrations/twenty-logo.svg');
|
||||
background-size: cover;
|
||||
height: ${themeCssVariables.spacing[6]};
|
||||
@@ -51,8 +49,6 @@ const StyledLogoBase = styled.div`
|
||||
width: ${themeCssVariables.spacing[6]};
|
||||
`;
|
||||
|
||||
const StyledLogo = motion.create(StyledLogoBase);
|
||||
|
||||
const StyledFreeCredits = styled.div`
|
||||
align-items: center;
|
||||
color: ${themeCssVariables.font.color.tertiary};
|
||||
@@ -109,8 +105,6 @@ export const OnboardingHeader = ({
|
||||
}: OnboardingHeaderProps) => {
|
||||
const { t } = useLingui();
|
||||
const theme = useTheme();
|
||||
const contentWidth = useOnboardingContentWidth();
|
||||
const transition = useOnboardingMotionTransition();
|
||||
|
||||
return (
|
||||
<StyledHeader>
|
||||
@@ -125,8 +119,8 @@ export const OnboardingHeader = ({
|
||||
/>
|
||||
)}
|
||||
</StyledLeftSide>
|
||||
<StyledCenter contentWidth={contentWidth}>
|
||||
<StyledLogo layout transition={transition} />
|
||||
<StyledCenter>
|
||||
<StyledLogo />
|
||||
</StyledCenter>
|
||||
<StyledRightSide>
|
||||
{isDefined(freeCredits) && (
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { isOnOnboardingVerifyPath } from '@/auth/utils/isOnOnboardingVerifyPath';
|
||||
import { OnboardingPulsingLogo } from '@/onboarding/components/OnboardingPulsingLogo';
|
||||
import { styled } from '@linaria/react';
|
||||
import { themeCssVariables } from 'twenty-ui/theme-constants';
|
||||
@@ -12,8 +13,12 @@ const StyledContainer = styled.div`
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
// The verify screen renders its own pulsing logo, so the loader shown before it
|
||||
// omits the logo to avoid a double-logo flash on cold boot into the verify step.
|
||||
export const OnboardingPageLoader = () => (
|
||||
<StyledContainer>
|
||||
<OnboardingPulsingLogo />
|
||||
{!isOnOnboardingVerifyPath(window.location.pathname) && (
|
||||
<OnboardingPulsingLogo />
|
||||
)}
|
||||
</StyledContainer>
|
||||
);
|
||||
|
||||
+3
-9
@@ -1,17 +1,11 @@
|
||||
import { OnboardingPulsingLogo } from '@/onboarding/components/OnboardingPulsingLogo';
|
||||
import { styled } from '@linaria/react';
|
||||
import { themeCssVariables } from 'twenty-ui/theme-constants';
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
background: ${themeCssVariables.background.secondary};
|
||||
flex: 1;
|
||||
justify-content: center;
|
||||
min-height: 0;
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
export const OnboardingStepPageLoader = () => (
|
||||
<StyledContainer>
|
||||
<OnboardingPulsingLogo />
|
||||
</StyledContainer>
|
||||
);
|
||||
export const OnboardingStepPageLoader = () => <StyledContainer />;
|
||||
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
import { styled } from '@linaria/react';
|
||||
import { themeCssVariables } from 'twenty-ui/theme-constants';
|
||||
|
||||
export const StyledOnboardingActivationMessageEmphasis = styled.span`
|
||||
color: ${themeCssVariables.font.color.primary};
|
||||
font-weight: ${themeCssVariables.font.weight.medium};
|
||||
`;
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
import { ONBOARDING_CONTENT_BLOCK_WIDTH } from '@/onboarding/constants/OnboardingContentBlockWidth';
|
||||
import { styled } from '@linaria/react';
|
||||
|
||||
export const StyledOnboardingContentBlock = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
max-width: 100%;
|
||||
width: ${ONBOARDING_CONTENT_BLOCK_WIDTH}px;
|
||||
`;
|
||||
+2
-6
@@ -1,11 +1,7 @@
|
||||
import { ONBOARDING_CONTENT_BLOCK_WIDTH } from '@/onboarding/constants/OnboardingContentBlockWidth';
|
||||
import { StyledOnboardingContentBlock } from '@/onboarding/components/StyledOnboardingContentBlock';
|
||||
import { styled } from '@linaria/react';
|
||||
import { themeCssVariables } from 'twenty-ui/theme-constants';
|
||||
|
||||
export const StyledOnboardingStepHeading = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
export const StyledOnboardingStepHeading = styled(StyledOnboardingContentBlock)`
|
||||
gap: ${themeCssVariables.spacing[4]};
|
||||
max-width: 100%;
|
||||
width: ${ONBOARDING_CONTENT_BLOCK_WIDTH}px;
|
||||
`;
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
import { WORKSPACE_ACTIVATION_MESSAGES } from '@/auth/sign-in-up/constants/WorkspaceActivationMessages';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
|
||||
export const ONBOARDING_ACTIVATION_MESSAGES = [
|
||||
msg`Verifying your login token`,
|
||||
...WORKSPACE_ACTIVATION_MESSAGES,
|
||||
];
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
import { WORKSPACE_ACTIVATION_MESSAGES } from '@/auth/sign-in-up/constants/WorkspaceActivationMessages';
|
||||
import { StyledOnboardingActivationMessageEmphasis } from '@/onboarding/components/StyledOnboardingActivationMessageEmphasis';
|
||||
import { type OnboardingActivationMessage } from '@/onboarding/types/OnboardingActivationMessage';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
|
||||
export const ONBOARDING_ACTIVATION_MESSAGES: OnboardingActivationMessage[] = [
|
||||
{
|
||||
id: 'verifying-login-token',
|
||||
content: (
|
||||
<Trans>
|
||||
Verifying your{' '}
|
||||
<StyledOnboardingActivationMessageEmphasis>
|
||||
login token
|
||||
</StyledOnboardingActivationMessageEmphasis>
|
||||
</Trans>
|
||||
),
|
||||
},
|
||||
...WORKSPACE_ACTIVATION_MESSAGES,
|
||||
];
|
||||
+1
-1
@@ -1 +1 @@
|
||||
export const ONBOARDING_CONTENT_BLOCK_WIDTH = 340;
|
||||
export const ONBOARDING_CONTENT_BLOCK_WIDTH = 440;
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
export const UPGRADE_STEP_CONTENT_WIDTH = 440;
|
||||
@@ -31,6 +31,8 @@ export const useInviteTeam = () => {
|
||||
const setOnboardingFreeCredits = useSetAtomState(onboardingFreeCreditsState);
|
||||
const onboardingConfig = useAtomStateValue(onboardingConfigState);
|
||||
|
||||
const [isNavigating, setIsNavigating] = useState(false);
|
||||
|
||||
const {
|
||||
control,
|
||||
handleSubmit,
|
||||
@@ -152,6 +154,7 @@ export const useInviteTeam = () => {
|
||||
}
|
||||
|
||||
setNextOnboardingStatus();
|
||||
setIsNavigating(true);
|
||||
},
|
||||
[
|
||||
enqueueSuccessSnackBar,
|
||||
@@ -186,5 +189,6 @@ export const useInviteTeam = () => {
|
||||
getPlaceholder,
|
||||
isValid,
|
||||
isSubmitting,
|
||||
isNavigating,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
import { ONBOARDING_CONTENT_BLOCK_WIDTH } from '@/onboarding/constants/OnboardingContentBlockWidth';
|
||||
import { UPGRADE_STEP_CONTENT_WIDTH } from '@/onboarding/constants/UpgradeStepContentWidth';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { AppPath } from 'twenty-shared/types';
|
||||
import { isMatchingLocation } from '~/utils/isMatchingLocation';
|
||||
|
||||
export const useOnboardingContentWidth = () => {
|
||||
const location = useLocation();
|
||||
|
||||
return isMatchingLocation(location, AppPath.PlanRequired)
|
||||
? UPGRADE_STEP_CONTENT_WIDTH
|
||||
: ONBOARDING_CONTENT_BLOCK_WIDTH;
|
||||
};
|
||||
@@ -0,0 +1,6 @@
|
||||
import { type ReactNode } from 'react';
|
||||
|
||||
export type OnboardingActivationMessage = {
|
||||
id: string;
|
||||
content: ReactNode;
|
||||
};
|
||||
@@ -3,7 +3,7 @@ import { Outlet } from 'react-router-dom';
|
||||
import { themeCssVariables } from 'twenty-ui/theme-constants';
|
||||
|
||||
const StyledLayout = styled.div`
|
||||
background: ${themeCssVariables.background.noisy};
|
||||
background: ${themeCssVariables.background.secondary};
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100dvh;
|
||||
|
||||
Reference in New Issue
Block a user