poc - cal.com integration in onboarding flow (#12530)

This commit is contained in:
nitin
2025-06-19 15:27:38 +05:30
committed by GitHub
parent e4d44e9c39
commit a8fb039e65
36 changed files with 526 additions and 34 deletions

View File

@ -0,0 +1,86 @@
import Cal from '@calcom/embed-react';
import styled from '@emotion/styled';
import { Link } from 'react-router-dom';
import { currentUserState } from '@/auth/states/currentUserState';
import { calendarBookingPageIdState } from '@/client-config/states/calendarBookingPageIdState';
import { useSetNextOnboardingStatus } from '@/onboarding/hooks/useSetNextOnboardingStatus';
import { AppPath } from '@/types/AppPath';
import { Modal } from '@/ui/layout/modal/components/Modal';
import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper';
import { useTheme } from '@emotion/react';
import { useLingui } from '@lingui/react/macro';
import { useRecoilValue } from 'recoil';
import { IconChevronLeft, IconChevronRightPipe } from 'twenty-ui/display';
import { LightButton } from 'twenty-ui/input';
import { useIsMobile } from 'twenty-ui/utilities';
import {
OnboardingStatus,
useSkipBookOnboardingStepMutation,
} from '~/generated/graphql';
const StyledModalFooter = styled(Modal.Footer)`
height: auto;
justify-content: center;
padding: ${({ theme }) => theme.spacing(3)};
`;
const StyledModalContent = styled(Modal.Content)`
overflow: hidden;
padding: 0;
`;
const StyledScrollWrapper = styled(ScrollWrapper)<{ isMobile: boolean }>`
${({ isMobile }) => !isMobile && 'height: auto;'}
`;
export const BookCall = () => {
const { t } = useLingui();
const theme = useTheme();
const calendarBookingPageId = useRecoilValue(calendarBookingPageIdState);
const setNextOnboardingStatus = useSetNextOnboardingStatus();
const currentUser = useRecoilValue(currentUserState);
const [skipBookOnboardingStepMutation] = useSkipBookOnboardingStepMutation();
const isMobile = useIsMobile();
const isPlanRequired =
currentUser?.onboardingStatus === OnboardingStatus.PLAN_REQUIRED;
const handleCompleteOnboarding = async () => {
await skipBookOnboardingStepMutation();
setNextOnboardingStatus();
};
return (
<>
<StyledModalContent isHorizontalCentered isVerticalCentered>
<StyledScrollWrapper
componentInstanceId="scroll-wrapper-modal-content"
isMobile={isMobile}
>
<Cal
calLink={calendarBookingPageId ?? ''}
config={{
layout: 'month_view',
theme: theme.name === 'light' ? 'light' : 'dark',
email: currentUser?.email ?? '',
}}
/>
</StyledScrollWrapper>
</StyledModalContent>
<StyledModalFooter>
{isPlanRequired ? (
<Link to={AppPath.PlanRequired}>
<LightButton Icon={IconChevronLeft} title={t`Back`} />
</Link>
) : (
<LightButton
Icon={IconChevronRightPipe}
title={t`Skip`}
onClick={handleCompleteOnboarding}
/>
)}
</StyledModalFooter>
</>
);
};

View File

@ -0,0 +1,74 @@
import { SubTitle } from '@/auth/components/SubTitle';
import { Title } from '@/auth/components/Title';
import { useSetNextOnboardingStatus } from '@/onboarding/hooks/useSetNextOnboardingStatus';
import { AppPath } from '@/types/AppPath';
import { Modal } from '@/ui/layout/modal/components/Modal';
import styled from '@emotion/styled';
import { Trans, useLingui } from '@lingui/react/macro';
import { Link } from 'react-router-dom';
import { LightButton, MainButton } from 'twenty-ui/input';
import { useSkipBookOnboardingStepMutation } from '~/generated/graphql';
const StyledCoverImage = styled.img`
border-radius: ${({ theme }) => theme.border.radius.sm};
height: 204px;
object-fit: cover;
width: 320px;
`;
const StyledModalContent = styled(Modal.Content)`
gap: ${({ theme }) => theme.spacing(8)};
`;
const StyledTitleContainer = styled.div`
align-items: center;
display: flex;
flex-direction: column;
justify-content: center;
`;
const StyledButtonContainer = styled.div`
align-items: center;
display: flex;
flex-direction: column;
gap: ${({ theme }) => theme.spacing(4)};
width: 100%;
`;
const StyledLink = styled(Link)`
text-decoration: none;
`;
export const BookCallDecision = () => {
const { t } = useLingui();
const setNextOnboardingStatus = useSetNextOnboardingStatus();
const [skipBookOnboardingStepMutation] = useSkipBookOnboardingStepMutation();
const handleFinish = async () => {
await skipBookOnboardingStepMutation();
setNextOnboardingStatus();
};
return (
<StyledModalContent isVerticalCentered isHorizontalCentered>
<StyledTitleContainer>
<Title noMarginTop>
<Trans>Book your onboarding</Trans>
</Title>
<SubTitle>
<Trans>
Our team can help you set up your workspace to match your specific
needs and workflows.
</Trans>
</SubTitle>
</StyledTitleContainer>
<StyledCoverImage src="/images/placeholders/onboarding-covers/onboarding-book-call-decision-cover.png" />
<StyledButtonContainer>
<StyledLink to={AppPath.BookCall}>
<MainButton title={t`Book onboarding`} width={198} />
</StyledLink>
<LightButton title={t`Finish`} onClick={handleFinish} />
</StyledButtonContainer>
</StyledModalContent>
);
};

View File

@ -9,6 +9,8 @@ import { TrialCard } from '@/billing/components/TrialCard';
import { useHandleCheckoutSession } from '@/billing/hooks/useHandleCheckoutSession';
import { isBillingPriceLicensed } from '@/billing/utils/isBillingPriceLicensed';
import { billingState } from '@/client-config/states/billingState';
import { calendarBookingPageIdState } from '@/client-config/states/calendarBookingPageIdState';
import { AppPath } from '@/types/AppPath';
import { Modal } from '@/ui/layout/modal/components/Modal';
import styled from '@emotion/styled';
import { Trans, useLingui } from '@lingui/react/macro';
@ -97,6 +99,8 @@ export const ChooseYourPlan = () => {
billingCheckoutSessionState,
);
const calendarBookingPageId = useRecoilValue(calendarBookingPageIdState);
const [verifyEmailNextPath, setVerifyEmailNextPath] = useRecoilState(
verifyEmailNextPathState,
);
@ -250,7 +254,11 @@ export const ChooseYourPlan = () => {
<Trans>Change Plan</Trans>
</ClickToActionLink>
<span />
<ClickToActionLink href={CAL_LINK} target="_blank" rel="noreferrer">
<ClickToActionLink
href={calendarBookingPageId ? AppPath.BookCall : CAL_LINK}
target={calendarBookingPageId ? '_self' : '_blank'}
rel={calendarBookingPageId ? '' : 'noreferrer'}
>
<Trans>Book a Call</Trans>
</ClickToActionLink>
</StyledLinkGroup>

View File

@ -1,6 +1,7 @@
import { SubTitle } from '@/auth/components/SubTitle';
import { Title } from '@/auth/components/Title';
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
import { calendarBookingPageIdState } from '@/client-config/states/calendarBookingPageIdState';
import { useSetNextOnboardingStatus } from '@/onboarding/hooks/useSetNextOnboardingStatus';
import { PageHotkeyScope } from '@/types/PageHotkeyScope';
import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
@ -66,9 +67,11 @@ export const InviteTeam = () => {
const theme = useTheme();
const { enqueueSnackBar } = useSnackBar();
const { sendInvitation } = useCreateWorkspaceInvitation();
const setNextOnboardingStatus = useSetNextOnboardingStatus();
const currentWorkspace = useRecoilValue(currentWorkspaceState);
const calendarBookingPageId = useRecoilValue(calendarBookingPageIdState);
const hasCalendarBooking = isDefined(calendarBookingPageId);
const {
control,
handleSubmit,
@ -136,8 +139,6 @@ export const InviteTeam = () => {
);
const result = await sendInvitation({ emails });
setNextOnboardingStatus();
if (isDefined(result.errors)) {
throw result.errors;
}
@ -147,6 +148,8 @@ export const InviteTeam = () => {
duration: 2000,
});
}
setNextOnboardingStatus();
},
[enqueueSnackBar, sendInvitation, setNextOnboardingStatus, t],
);
@ -214,7 +217,7 @@ export const InviteTeam = () => {
</StyledAnimatedContainer>
<StyledButtonContainer>
<MainButton
title={t`Continue`}
title={hasCalendarBooking ? t`Continue` : t`Finish`}
disabled={!isValid || isSubmitting}
onClick={handleSubmit(onSubmit)}
fullWidth