5622 add a syncemail onboarding step (#5689)
- add sync email onboarding step - refactor calendar and email visibility enums - add a new table `keyValuePair` in `core` schema - add a new resolved boolean field `skipSyncEmail` in current user https://github.com/twentyhq/twenty/assets/29927851/de791475-5bfe-47f9-8e90-76c349fba56f
This commit is contained in:
@ -1,6 +1,5 @@
|
||||
import { useCallback } from 'react';
|
||||
import { Controller, SubmitHandler, useForm } from 'react-hook-form';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import styled from '@emotion/styled';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { useSetRecoilState } from 'recoil';
|
||||
@ -16,7 +15,6 @@ import { OnboardingStatus } from '@/auth/utils/getOnboardingStatus';
|
||||
import { FIND_MANY_OBJECT_METADATA_ITEMS } from '@/object-metadata/graphql/queries';
|
||||
import { useApolloMetadataClient } from '@/object-metadata/hooks/useApolloMetadataClient';
|
||||
import { WorkspaceLogoUploader } from '@/settings/workspace/components/WorkspaceLogoUploader';
|
||||
import { AppPath } from '@/types/AppPath';
|
||||
import { Loader } from '@/ui/feedback/loader/components/Loader';
|
||||
import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
|
||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||
@ -47,8 +45,6 @@ const validationSchema = z
|
||||
type Form = z.infer<typeof validationSchema>;
|
||||
|
||||
export const CreateWorkspace = () => {
|
||||
const navigate = useNavigate();
|
||||
|
||||
const { enqueueSnackBar } = useSnackBar();
|
||||
const onboardingStatus = useOnboardingStatus();
|
||||
|
||||
@ -88,10 +84,6 @@ export const CreateWorkspace = () => {
|
||||
if (isDefined(result.errors)) {
|
||||
throw result.errors ?? new Error('Unknown error');
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
navigate(AppPath.CreateProfile);
|
||||
}, 20);
|
||||
} catch (error: any) {
|
||||
enqueueSnackBar(error?.message, {
|
||||
variant: SnackBarVariant.Error,
|
||||
@ -102,7 +94,6 @@ export const CreateWorkspace = () => {
|
||||
activateWorkspace,
|
||||
setIsCurrentUserLoaded,
|
||||
apolloMetadataClient,
|
||||
navigate,
|
||||
enqueueSnackBar,
|
||||
],
|
||||
);
|
||||
94
packages/twenty-front/src/pages/onboarding/SyncEmails.tsx
Normal file
94
packages/twenty-front/src/pages/onboarding/SyncEmails.tsx
Normal file
@ -0,0 +1,94 @@
|
||||
import React, { useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import { useSetRecoilState } from 'recoil';
|
||||
import { IconGoogle } from 'twenty-ui';
|
||||
|
||||
import { SubTitle } from '@/auth/components/SubTitle';
|
||||
import { Title } from '@/auth/components/Title';
|
||||
import { isCurrentUserLoadedState } from '@/auth/states/isCurrentUserLoadingState';
|
||||
import { OnboardingSyncEmailsSettingsCard } from '@/onboarding/components/OnboardingSyncEmailsSettingsCard';
|
||||
import { useTriggerGoogleApisOAuth } from '@/settings/accounts/hooks/useTriggerGoogleApisOAuth';
|
||||
import { AppPath } from '@/types/AppPath';
|
||||
import { MainButton } from '@/ui/input/button/components/MainButton';
|
||||
import { ActionLink } from '@/ui/navigation/link/components/ActionLink';
|
||||
import {
|
||||
CalendarChannelVisibility,
|
||||
MessageChannelVisibility,
|
||||
useSkipSyncEmailOnboardingStepMutation,
|
||||
} from '~/generated/graphql';
|
||||
|
||||
const StyledSyncEmailsContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
width: 100%;
|
||||
margin: ${({ theme }) => theme.spacing(8)} 0;
|
||||
gap: ${({ theme }) => theme.spacing(2)};
|
||||
`;
|
||||
|
||||
const StyledActionLinkContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
margin: ${({ theme }) => theme.spacing(3)} 0 0;
|
||||
`;
|
||||
|
||||
export const SyncEmails = () => {
|
||||
const theme = useTheme();
|
||||
const navigate = useNavigate();
|
||||
const { triggerGoogleApisOAuth } = useTriggerGoogleApisOAuth();
|
||||
const setIsCurrentUserLoaded = useSetRecoilState(isCurrentUserLoadedState);
|
||||
const [visibility, setVisibility] = useState<MessageChannelVisibility>(
|
||||
MessageChannelVisibility.ShareEverything,
|
||||
);
|
||||
const [skipSyncEmailOnboardingStepMutation] =
|
||||
useSkipSyncEmailOnboardingStepMutation();
|
||||
|
||||
const handleButtonClick = async () => {
|
||||
const calendarChannelVisibility =
|
||||
visibility === MessageChannelVisibility.ShareEverything
|
||||
? CalendarChannelVisibility.ShareEverything
|
||||
: CalendarChannelVisibility.Metadata;
|
||||
|
||||
await triggerGoogleApisOAuth(
|
||||
AppPath.Index,
|
||||
visibility,
|
||||
calendarChannelVisibility,
|
||||
);
|
||||
};
|
||||
|
||||
const continueWithoutSync = async () => {
|
||||
await skipSyncEmailOnboardingStepMutation();
|
||||
setIsCurrentUserLoaded(false);
|
||||
navigate(AppPath.Index);
|
||||
};
|
||||
|
||||
const isSubmitting = false;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Title withMarginTop={false}>Emails and Calendar</Title>
|
||||
<SubTitle>
|
||||
Sync your Emails and Calendar with Twenty. Choose your privacy settings.
|
||||
</SubTitle>
|
||||
<StyledSyncEmailsContainer>
|
||||
<OnboardingSyncEmailsSettingsCard
|
||||
value={visibility}
|
||||
onChange={setVisibility}
|
||||
/>
|
||||
</StyledSyncEmailsContainer>
|
||||
<MainButton
|
||||
title="Sync with Google"
|
||||
onClick={handleButtonClick}
|
||||
width={200}
|
||||
Icon={() => <IconGoogle size={theme.icon.size.sm} />}
|
||||
disabled={isSubmitting}
|
||||
/>
|
||||
<StyledActionLinkContainer>
|
||||
<ActionLink onClick={continueWithoutSync}>
|
||||
Continue without sync
|
||||
</ActionLink>
|
||||
</StyledActionLinkContainer>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@ -5,6 +5,7 @@ import { graphql, HttpResponse } from 'msw';
|
||||
|
||||
import { AppPath } from '@/types/AppPath';
|
||||
import { GET_CURRENT_USER } from '@/users/graphql/queries/getCurrentUser';
|
||||
import { ChooseYourPlan } from '~/pages/onboarding/ChooseYourPlan';
|
||||
import {
|
||||
PageDecorator,
|
||||
PageDecoratorArgs,
|
||||
@ -13,10 +14,8 @@ import { graphqlMocks } from '~/testing/graphqlMocks';
|
||||
import { mockedOnboardingUsersData } from '~/testing/mock-data/users';
|
||||
import { sleep } from '~/testing/sleep';
|
||||
|
||||
import { ChooseYourPlan } from '../ChooseYourPlan';
|
||||
|
||||
const meta: Meta<PageDecoratorArgs> = {
|
||||
title: 'Pages/Auth/ChooseYourPlan',
|
||||
title: 'Pages/Onboarding/ChooseYourPlan',
|
||||
component: ChooseYourPlan,
|
||||
decorators: [PageDecorator],
|
||||
args: { routePath: AppPath.PlanRequired },
|
||||
@ -5,6 +5,7 @@ import { graphql, HttpResponse } from 'msw';
|
||||
|
||||
import { AppPath } from '@/types/AppPath';
|
||||
import { GET_CURRENT_USER } from '@/users/graphql/queries/getCurrentUser';
|
||||
import { CreateProfile } from '~/pages/onboarding/CreateProfile';
|
||||
import {
|
||||
PageDecorator,
|
||||
PageDecoratorArgs,
|
||||
@ -12,10 +13,8 @@ import {
|
||||
import { graphqlMocks } from '~/testing/graphqlMocks';
|
||||
import { mockedOnboardingUsersData } from '~/testing/mock-data/users';
|
||||
|
||||
import { CreateProfile } from '../CreateProfile';
|
||||
|
||||
const meta: Meta<PageDecoratorArgs> = {
|
||||
title: 'Pages/Auth/CreateProfile',
|
||||
title: 'Pages/Onboarding/CreateProfile',
|
||||
component: CreateProfile,
|
||||
decorators: [PageDecorator],
|
||||
args: { routePath: AppPath.CreateProfile },
|
||||
@ -7,6 +7,7 @@ import { useSetRecoilState } from 'recoil';
|
||||
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
|
||||
import { AppPath } from '@/types/AppPath';
|
||||
import { GET_CURRENT_USER } from '@/users/graphql/queries/getCurrentUser';
|
||||
import { CreateWorkspace } from '~/pages/onboarding/CreateWorkspace';
|
||||
import {
|
||||
PageDecorator,
|
||||
PageDecoratorArgs,
|
||||
@ -14,10 +15,8 @@ import {
|
||||
import { graphqlMocks } from '~/testing/graphqlMocks';
|
||||
import { mockedOnboardingUsersData } from '~/testing/mock-data/users';
|
||||
|
||||
import { CreateWorkspace } from '../CreateWorkspace';
|
||||
|
||||
const meta: Meta<PageDecoratorArgs> = {
|
||||
title: 'Pages/Auth/CreateWorkspace',
|
||||
title: 'Pages/Onboarding/CreateWorkspace',
|
||||
component: CreateWorkspace,
|
||||
decorators: [
|
||||
(Story) => {
|
||||
@ -5,6 +5,7 @@ import { graphql, HttpResponse } from 'msw';
|
||||
|
||||
import { AppPath } from '@/types/AppPath';
|
||||
import { GET_CURRENT_USER } from '@/users/graphql/queries/getCurrentUser';
|
||||
import { PaymentSuccess } from '~/pages/onboarding/PaymentSuccess';
|
||||
import {
|
||||
PageDecorator,
|
||||
PageDecoratorArgs,
|
||||
@ -12,10 +13,8 @@ import {
|
||||
import { graphqlMocks } from '~/testing/graphqlMocks';
|
||||
import { mockedOnboardingUsersData } from '~/testing/mock-data/users';
|
||||
|
||||
import { PaymentSuccess } from '../PaymentSuccess';
|
||||
|
||||
const meta: Meta<PageDecoratorArgs> = {
|
||||
title: 'Pages/Auth/PaymentSuccess',
|
||||
title: 'Pages/Onboarding/PaymentSuccess',
|
||||
component: PaymentSuccess,
|
||||
decorators: [PageDecorator],
|
||||
args: { routePath: AppPath.PlanRequiredSuccess },
|
||||
@ -0,0 +1,46 @@
|
||||
import { getOperationName } from '@apollo/client/utilities';
|
||||
import { Meta, StoryObj } from '@storybook/react';
|
||||
import { within } from '@storybook/test';
|
||||
import { graphql, HttpResponse } from 'msw';
|
||||
|
||||
import { AppPath } from '~/modules/types/AppPath';
|
||||
import { GET_CURRENT_USER } from '~/modules/users/graphql/queries/getCurrentUser';
|
||||
import { SyncEmails } from '~/pages/onboarding/SyncEmails';
|
||||
import {
|
||||
PageDecorator,
|
||||
PageDecoratorArgs,
|
||||
} from '~/testing/decorators/PageDecorator';
|
||||
import { graphqlMocks } from '~/testing/graphqlMocks';
|
||||
import { mockedOnboardingUsersData } from '~/testing/mock-data/users';
|
||||
|
||||
const meta: Meta<PageDecoratorArgs> = {
|
||||
title: 'Pages/Onboarding/SyncEmails',
|
||||
component: SyncEmails,
|
||||
decorators: [PageDecorator],
|
||||
args: { routePath: AppPath.SyncEmails },
|
||||
parameters: {
|
||||
msw: {
|
||||
handlers: [
|
||||
graphql.query(getOperationName(GET_CURRENT_USER) ?? '', () => {
|
||||
return HttpResponse.json({
|
||||
data: {
|
||||
currentUser: mockedOnboardingUsersData[0],
|
||||
},
|
||||
});
|
||||
}),
|
||||
graphqlMocks.handlers,
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
|
||||
export type Story = StoryObj<typeof SyncEmails>;
|
||||
|
||||
export const Default: Story = {
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
await canvas.findByText('Emails and Calendar');
|
||||
},
|
||||
};
|
||||
@ -17,10 +17,8 @@ import { SettingsPath } from '@/types/SettingsPath';
|
||||
import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer';
|
||||
import { Section } from '@/ui/layout/section/components/Section';
|
||||
import { Breadcrumb } from '@/ui/navigation/bread-crumb/components/Breadcrumb';
|
||||
import {
|
||||
TimelineCalendarEvent,
|
||||
TimelineCalendarEventVisibility,
|
||||
} from '~/generated-metadata/graphql';
|
||||
import { CalendarChannelVisibility } from '~/generated/graphql';
|
||||
import { TimelineCalendarEvent } from '~/generated-metadata/graphql';
|
||||
|
||||
export const SettingsAccountsCalendars = () => {
|
||||
const calendarSettingsEnabled = false;
|
||||
@ -79,7 +77,7 @@ export const SettingsAccountsCalendars = () => {
|
||||
isCanceled: false,
|
||||
location: '',
|
||||
title: 'Onboarding call',
|
||||
visibility: TimelineCalendarEventVisibility.ShareEverything,
|
||||
visibility: CalendarChannelVisibility.ShareEverything,
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@ -4,10 +4,7 @@ import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import { H2Title, IconRefresh, IconSettings, IconUser } from 'twenty-ui';
|
||||
|
||||
import {
|
||||
CalendarChannel,
|
||||
CalendarChannelVisibility,
|
||||
} from '@/accounts/types/CalendarChannel';
|
||||
import { CalendarChannel } from '@/accounts/types/CalendarChannel';
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { useFindOneRecord } from '@/object-record/hooks/useFindOneRecord';
|
||||
import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord';
|
||||
@ -21,6 +18,7 @@ import { SettingsPath } from '@/types/SettingsPath';
|
||||
import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer';
|
||||
import { Section } from '@/ui/layout/section/components/Section';
|
||||
import { Breadcrumb } from '@/ui/navigation/bread-crumb/components/Breadcrumb';
|
||||
import { CalendarChannelVisibility } from '~/generated/graphql';
|
||||
|
||||
const StyledCardMedia = styled(SettingsAccountsCardMedia)`
|
||||
height: ${({ theme }) => theme.spacing(6)};
|
||||
|
||||
@ -8,16 +8,14 @@ import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSi
|
||||
import { useFindOneRecord } from '@/object-record/hooks/useFindOneRecord';
|
||||
import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord';
|
||||
import { SettingsAccountsCardMedia } from '@/settings/accounts/components/SettingsAccountsCardMedia';
|
||||
import {
|
||||
InboxSettingsVisibilityValue,
|
||||
SettingsAccountsInboxVisibilitySettingsCard,
|
||||
} from '@/settings/accounts/components/SettingsAccountsInboxVisibilitySettingsCard';
|
||||
import { SettingsAccountsInboxVisibilitySettingsCard } from '@/settings/accounts/components/SettingsAccountsInboxVisibilitySettingsCard';
|
||||
import { SettingsAccountsToggleSettingCard } from '@/settings/accounts/components/SettingsAccountsToggleSettingCard';
|
||||
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
|
||||
import { AppPath } from '@/types/AppPath';
|
||||
import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer';
|
||||
import { Section } from '@/ui/layout/section/components/Section';
|
||||
import { Breadcrumb } from '@/ui/navigation/bread-crumb/components/Breadcrumb';
|
||||
import { MessageChannelVisibility } from '~/generated/graphql';
|
||||
|
||||
export const SettingsAccountsEmailsInboxSettings = () => {
|
||||
const theme = useTheme();
|
||||
@ -33,7 +31,7 @@ export const SettingsAccountsEmailsInboxSettings = () => {
|
||||
objectNameSingular: CoreObjectNameSingular.MessageChannel,
|
||||
});
|
||||
|
||||
const handleVisibilityChange = (value: InboxSettingsVisibilityValue) => {
|
||||
const handleVisibilityChange = (value: MessageChannelVisibility) => {
|
||||
updateOneRecord({
|
||||
idToUpdate: messageChannelId,
|
||||
updateOneRecordInput: {
|
||||
|
||||
@ -2,6 +2,7 @@ import { Meta, StoryObj } from '@storybook/react';
|
||||
import { within } from '@storybook/test';
|
||||
import { graphql, HttpResponse } from 'msw';
|
||||
|
||||
import { MessageChannelVisibility } from '~/generated/graphql';
|
||||
import { SettingsAccountsEmailsInboxSettings } from '~/pages/settings/accounts/SettingsAccountsEmailsInboxSettings';
|
||||
import {
|
||||
PageDecorator,
|
||||
@ -26,7 +27,7 @@ const meta: Meta<PageDecoratorArgs> = {
|
||||
data: {
|
||||
messageChannel: {
|
||||
id: '1',
|
||||
visibility: 'share_everything',
|
||||
visibility: MessageChannelVisibility.ShareEverything,
|
||||
messageThreads: { edges: [] },
|
||||
createdAt: '2021-08-27T12:00:00Z',
|
||||
type: 'email',
|
||||
|
||||
Reference in New Issue
Block a user