poc - cal.com integration in onboarding flow (#12530)
This commit is contained in:
86
packages/twenty-front/src/pages/onboarding/BookCall.tsx
Normal file
86
packages/twenty-front/src/pages/onboarding/BookCall.tsx
Normal 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>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@ -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>
|
||||
);
|
||||
};
|
||||
@ -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>
|
||||
|
||||
@ -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
|
||||
|
||||
Reference in New Issue
Block a user