Refactor Calendar Settings into tabs (#6153)
This PR migrates Calendar Settings into Tabs: <img width="1512" alt="image" src="https://github.com/twentyhq/twenty/assets/12035771/2531d0f1-ddfd-46c6-8678-bd76d78447b6">
This commit is contained in:
@ -53,7 +53,6 @@ import { PaymentSuccess } from '~/pages/onboarding/PaymentSuccess';
|
|||||||
import { SyncEmails } from '~/pages/onboarding/SyncEmails';
|
import { SyncEmails } from '~/pages/onboarding/SyncEmails';
|
||||||
import { SettingsAccounts } from '~/pages/settings/accounts/SettingsAccounts';
|
import { SettingsAccounts } from '~/pages/settings/accounts/SettingsAccounts';
|
||||||
import { SettingsAccountsCalendars } from '~/pages/settings/accounts/SettingsAccountsCalendars';
|
import { SettingsAccountsCalendars } from '~/pages/settings/accounts/SettingsAccountsCalendars';
|
||||||
import { SettingsAccountsCalendarsSettings } from '~/pages/settings/accounts/SettingsAccountsCalendarsSettings';
|
|
||||||
import { SettingsAccountsEmails } from '~/pages/settings/accounts/SettingsAccountsEmails';
|
import { SettingsAccountsEmails } from '~/pages/settings/accounts/SettingsAccountsEmails';
|
||||||
import { SettingsNewAccount } from '~/pages/settings/accounts/SettingsNewAccount';
|
import { SettingsNewAccount } from '~/pages/settings/accounts/SettingsNewAccount';
|
||||||
import { SettingsNewObject } from '~/pages/settings/data-model/SettingsNewObject';
|
import { SettingsNewObject } from '~/pages/settings/data-model/SettingsNewObject';
|
||||||
@ -179,10 +178,6 @@ const createRouter = (isBillingEnabled?: boolean) =>
|
|||||||
path={SettingsPath.AccountsCalendars}
|
path={SettingsPath.AccountsCalendars}
|
||||||
element={<SettingsAccountsCalendars />}
|
element={<SettingsAccountsCalendars />}
|
||||||
/>
|
/>
|
||||||
<Route
|
|
||||||
path={SettingsPath.AccountsCalendarsSettings}
|
|
||||||
element={<SettingsAccountsCalendarsSettings />}
|
|
||||||
/>
|
|
||||||
<Route
|
<Route
|
||||||
path={SettingsPath.AccountsEmails}
|
path={SettingsPath.AccountsEmails}
|
||||||
element={<SettingsAccountsEmails />}
|
element={<SettingsAccountsEmails />}
|
||||||
|
|||||||
@ -0,0 +1,118 @@
|
|||||||
|
import { CalendarChannel } from '@/accounts/types/CalendarChannel';
|
||||||
|
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||||
|
import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord';
|
||||||
|
import { SettingsAccountsEventVisibilitySettingsCard } from '@/settings/accounts/components/SettingsAccountsCalendarVisibilitySettingsCard';
|
||||||
|
import { SettingsAccountsCardMedia } from '@/settings/accounts/components/SettingsAccountsCardMedia';
|
||||||
|
import { SettingsAccountsToggleSettingCard } from '@/settings/accounts/components/SettingsAccountsToggleSettingCard';
|
||||||
|
import { useTheme } from '@emotion/react';
|
||||||
|
import styled from '@emotion/styled';
|
||||||
|
import { Section } from '@react-email/components';
|
||||||
|
import { H2Title, IconRefresh, IconUser } from 'twenty-ui';
|
||||||
|
import { CalendarChannelVisibility } from '~/generated-metadata/graphql';
|
||||||
|
|
||||||
|
const StyledCardMedia = styled(SettingsAccountsCardMedia)`
|
||||||
|
height: ${({ theme }) => theme.spacing(6)};
|
||||||
|
`;
|
||||||
|
|
||||||
|
const StyledDetailsContainer = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: ${({ theme }) => theme.spacing(6)};
|
||||||
|
padding-top: ${({ theme }) => theme.spacing(6)};
|
||||||
|
`;
|
||||||
|
|
||||||
|
type SettingsAccountsCalendarChannelDetailsProps = {
|
||||||
|
calendarChannel: Pick<
|
||||||
|
CalendarChannel,
|
||||||
|
'id' | 'visibility' | 'isContactAutoCreationEnabled' | 'isSyncEnabled'
|
||||||
|
>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SettingsAccountsCalendarChannelDetails = ({
|
||||||
|
calendarChannel,
|
||||||
|
}: SettingsAccountsCalendarChannelDetailsProps) => {
|
||||||
|
const theme = useTheme();
|
||||||
|
|
||||||
|
const { updateOneRecord } = useUpdateOneRecord<CalendarChannel>({
|
||||||
|
objectNameSingular: CoreObjectNameSingular.CalendarChannel,
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleVisibilityChange = (value: CalendarChannelVisibility) => {
|
||||||
|
updateOneRecord({
|
||||||
|
idToUpdate: calendarChannel.id,
|
||||||
|
updateOneRecordInput: {
|
||||||
|
visibility: value,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleContactAutoCreationToggle = (value: boolean) => {
|
||||||
|
updateOneRecord({
|
||||||
|
idToUpdate: calendarChannel.id,
|
||||||
|
updateOneRecordInput: {
|
||||||
|
isContactAutoCreationEnabled: value,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSyncEventsToggle = (value: boolean) => {
|
||||||
|
updateOneRecord({
|
||||||
|
idToUpdate: calendarChannel.id,
|
||||||
|
updateOneRecordInput: {
|
||||||
|
isSyncEnabled: value,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<StyledDetailsContainer>
|
||||||
|
<Section>
|
||||||
|
<H2Title
|
||||||
|
title="Event visibility"
|
||||||
|
description="Define what will be visible to other users in your workspace"
|
||||||
|
/>
|
||||||
|
<SettingsAccountsEventVisibilitySettingsCard
|
||||||
|
value={calendarChannel.visibility}
|
||||||
|
onChange={handleVisibilityChange}
|
||||||
|
/>
|
||||||
|
</Section>
|
||||||
|
<Section>
|
||||||
|
<H2Title
|
||||||
|
title="Contact auto-creation"
|
||||||
|
description="Automatically create contacts for people you've participated in an event with."
|
||||||
|
/>
|
||||||
|
<SettingsAccountsToggleSettingCard
|
||||||
|
cardMedia={
|
||||||
|
<StyledCardMedia>
|
||||||
|
<IconUser
|
||||||
|
size={theme.icon.size.sm}
|
||||||
|
stroke={theme.icon.stroke.lg}
|
||||||
|
/>
|
||||||
|
</StyledCardMedia>
|
||||||
|
}
|
||||||
|
title="Auto-creation"
|
||||||
|
value={!!calendarChannel.isContactAutoCreationEnabled}
|
||||||
|
onToggle={handleContactAutoCreationToggle}
|
||||||
|
/>
|
||||||
|
</Section>
|
||||||
|
<Section>
|
||||||
|
<H2Title
|
||||||
|
title="Synchronization"
|
||||||
|
description="Past and future calendar events will automatically be synced to this workspace"
|
||||||
|
/>
|
||||||
|
<SettingsAccountsToggleSettingCard
|
||||||
|
cardMedia={
|
||||||
|
<StyledCardMedia>
|
||||||
|
<IconRefresh
|
||||||
|
size={theme.icon.size.sm}
|
||||||
|
stroke={theme.icon.stroke.lg}
|
||||||
|
/>
|
||||||
|
</StyledCardMedia>
|
||||||
|
}
|
||||||
|
title="Sync events"
|
||||||
|
value={!!calendarChannel.isSyncEnabled}
|
||||||
|
onToggle={handleSyncEventsToggle}
|
||||||
|
/>
|
||||||
|
</Section>
|
||||||
|
</StyledDetailsContainer>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -0,0 +1,75 @@
|
|||||||
|
import { useRecoilValue } from 'recoil';
|
||||||
|
|
||||||
|
import { CalendarChannel } from '@/accounts/types/CalendarChannel';
|
||||||
|
import { ConnectedAccount } from '@/accounts/types/ConnectedAccount';
|
||||||
|
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
|
||||||
|
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||||
|
import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
|
||||||
|
import { SettingsAccountsCalendarChannelDetails } from '@/settings/accounts/components/SettingsAccountsCalendarChannelDetails';
|
||||||
|
import { SettingsAccountsCalendarChannelsGeneral } from '@/settings/accounts/components/SettingsAccountsCalendarChannelsGeneral';
|
||||||
|
import { SettingsAccountsListEmptyStateCard } from '@/settings/accounts/components/SettingsAccountsListEmptyStateCard';
|
||||||
|
import { SETTINGS_ACCOUNT_CALENDAR_CHANNELS_TAB_LIST_COMPONENT_ID } from '@/settings/accounts/constants/SettingsAccountCalendarChannelsTabListComponentId';
|
||||||
|
import { TabList } from '@/ui/layout/tab/components/TabList';
|
||||||
|
import { useTabList } from '@/ui/layout/tab/hooks/useTabList';
|
||||||
|
|
||||||
|
export const SettingsAccountsCalendarChannelsContainer = () => {
|
||||||
|
const { activeTabIdState } = useTabList(
|
||||||
|
SETTINGS_ACCOUNT_CALENDAR_CHANNELS_TAB_LIST_COMPONENT_ID,
|
||||||
|
);
|
||||||
|
const activeTabId = useRecoilValue(activeTabIdState);
|
||||||
|
const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState);
|
||||||
|
|
||||||
|
const { records: accounts } = useFindManyRecords<ConnectedAccount>({
|
||||||
|
objectNameSingular: CoreObjectNameSingular.ConnectedAccount,
|
||||||
|
filter: {
|
||||||
|
accountOwnerId: {
|
||||||
|
eq: currentWorkspaceMember?.id,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const { records: calendarChannels } = useFindManyRecords<
|
||||||
|
CalendarChannel & {
|
||||||
|
connectedAccount: ConnectedAccount;
|
||||||
|
}
|
||||||
|
>({
|
||||||
|
objectNameSingular: CoreObjectNameSingular.CalendarChannel,
|
||||||
|
filter: {
|
||||||
|
connectedAccountId: {
|
||||||
|
in: accounts.map((account) => account.id),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const tabs = [
|
||||||
|
...calendarChannels.map((calendarChannel) => ({
|
||||||
|
id: calendarChannel.id,
|
||||||
|
title: calendarChannel.handle,
|
||||||
|
})),
|
||||||
|
];
|
||||||
|
|
||||||
|
if (!calendarChannels.length) {
|
||||||
|
return <SettingsAccountsListEmptyStateCard />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<TabList
|
||||||
|
tabListId={SETTINGS_ACCOUNT_CALENDAR_CHANNELS_TAB_LIST_COMPONENT_ID}
|
||||||
|
tabs={tabs}
|
||||||
|
/>
|
||||||
|
{calendarChannels.map((calendarChannel) => (
|
||||||
|
<>
|
||||||
|
{calendarChannel.id === activeTabId && (
|
||||||
|
<SettingsAccountsCalendarChannelDetails
|
||||||
|
calendarChannel={calendarChannel}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
))}
|
||||||
|
{false && activeTabId === 'general' && (
|
||||||
|
<SettingsAccountsCalendarChannelsGeneral />
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -0,0 +1,93 @@
|
|||||||
|
import { CalendarMonthCard } from '@/activities/calendar/components/CalendarMonthCard';
|
||||||
|
import { CalendarContext } from '@/activities/calendar/contexts/CalendarContext';
|
||||||
|
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
|
||||||
|
import { SettingsAccountsCalendarDisplaySettings } from '@/settings/accounts/components/SettingsAccountsCalendarDisplaySettings';
|
||||||
|
import styled from '@emotion/styled';
|
||||||
|
import { Section } from '@react-email/components';
|
||||||
|
import { addMinutes, endOfDay, min, startOfDay } from 'date-fns';
|
||||||
|
import { useRecoilValue } from 'recoil';
|
||||||
|
import { H2Title } from 'twenty-ui';
|
||||||
|
import {
|
||||||
|
CalendarChannelVisibility,
|
||||||
|
TimelineCalendarEvent,
|
||||||
|
} from '~/generated-metadata/graphql';
|
||||||
|
|
||||||
|
const StyledGeneralContainer = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: ${({ theme }) => theme.spacing(6)};
|
||||||
|
padding-top: ${({ theme }) => theme.spacing(6)};
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const SettingsAccountsCalendarChannelsGeneral = () => {
|
||||||
|
const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState);
|
||||||
|
|
||||||
|
const exampleStartDate = new Date();
|
||||||
|
const exampleEndDate = min([
|
||||||
|
addMinutes(exampleStartDate, 30),
|
||||||
|
endOfDay(exampleStartDate),
|
||||||
|
]);
|
||||||
|
const exampleDayTime = startOfDay(exampleStartDate).getTime();
|
||||||
|
const exampleCalendarEvent: TimelineCalendarEvent = {
|
||||||
|
id: '',
|
||||||
|
participants: [
|
||||||
|
{
|
||||||
|
firstName: currentWorkspaceMember?.name.firstName || '',
|
||||||
|
lastName: currentWorkspaceMember?.name.lastName || '',
|
||||||
|
displayName: currentWorkspaceMember
|
||||||
|
? [
|
||||||
|
currentWorkspaceMember.name.firstName,
|
||||||
|
currentWorkspaceMember.name.lastName,
|
||||||
|
].join(' ')
|
||||||
|
: '',
|
||||||
|
avatarUrl: currentWorkspaceMember?.avatarUrl || '',
|
||||||
|
handle: '',
|
||||||
|
personId: '',
|
||||||
|
workspaceMemberId: currentWorkspaceMember?.id || '',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
endsAt: exampleEndDate.toISOString(),
|
||||||
|
isFullDay: false,
|
||||||
|
startsAt: exampleStartDate.toISOString(),
|
||||||
|
conferenceSolution: '',
|
||||||
|
conferenceLink: {
|
||||||
|
label: '',
|
||||||
|
url: '',
|
||||||
|
},
|
||||||
|
description: '',
|
||||||
|
isCanceled: false,
|
||||||
|
location: '',
|
||||||
|
title: 'Onboarding call',
|
||||||
|
visibility: CalendarChannelVisibility.ShareEverything,
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledGeneralContainer>
|
||||||
|
<Section>
|
||||||
|
<H2Title
|
||||||
|
title="Display"
|
||||||
|
description="Configure how we should display your events in your calendar"
|
||||||
|
/>
|
||||||
|
<SettingsAccountsCalendarDisplaySettings />
|
||||||
|
</Section>
|
||||||
|
<Section>
|
||||||
|
<H2Title
|
||||||
|
title="Color code"
|
||||||
|
description="Events you participated in are displayed in red."
|
||||||
|
/>
|
||||||
|
<CalendarContext.Provider
|
||||||
|
value={{
|
||||||
|
currentCalendarEvent: exampleCalendarEvent,
|
||||||
|
calendarEventsByDayTime: {
|
||||||
|
[exampleDayTime]: [exampleCalendarEvent],
|
||||||
|
},
|
||||||
|
getNextCalendarEvent: () => undefined,
|
||||||
|
updateCurrentCalendarEvent: () => {},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CalendarMonthCard dayTimes={[exampleDayTime]} />
|
||||||
|
</CalendarContext.Provider>
|
||||||
|
</Section>
|
||||||
|
</StyledGeneralContainer>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -1,95 +0,0 @@
|
|||||||
import { useNavigate } from 'react-router-dom';
|
|
||||||
import styled from '@emotion/styled';
|
|
||||||
import { useRecoilValue } from 'recoil';
|
|
||||||
import { IconChevronRight, IconGoogleCalendar } from 'twenty-ui';
|
|
||||||
|
|
||||||
import { CalendarChannel } from '@/accounts/types/CalendarChannel';
|
|
||||||
import { ConnectedAccount } from '@/accounts/types/ConnectedAccount';
|
|
||||||
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
|
|
||||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
|
||||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
|
||||||
import { generateDepthOneRecordGqlFields } from '@/object-record/graphql/utils/generateDepthOneRecordGqlFields';
|
|
||||||
import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
|
|
||||||
import { SettingsAccountsListEmptyStateCard } from '@/settings/accounts/components/SettingsAccountsListEmptyStateCard';
|
|
||||||
import {
|
|
||||||
SettingsAccountsSynchronizationStatus,
|
|
||||||
SettingsAccountsSynchronizationStatusProps,
|
|
||||||
} from '@/settings/accounts/components/SettingsAccountsSynchronizationStatus';
|
|
||||||
import { SettingsListCard } from '@/settings/components/SettingsListCard';
|
|
||||||
import { LightIconButton } from '@/ui/input/button/components/LightIconButton';
|
|
||||||
|
|
||||||
const StyledRowRightContainer = styled.div`
|
|
||||||
align-items: center;
|
|
||||||
display: flex;
|
|
||||||
gap: ${({ theme }) => theme.spacing(1)};
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const SettingsAccountsCalendarChannelsListCard = () => {
|
|
||||||
const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState);
|
|
||||||
const navigate = useNavigate();
|
|
||||||
const { objectMetadataItem } = useObjectMetadataItem({
|
|
||||||
objectNameSingular: CoreObjectNameSingular.CalendarChannel,
|
|
||||||
});
|
|
||||||
|
|
||||||
const { records: accounts, loading: accountsLoading } =
|
|
||||||
useFindManyRecords<ConnectedAccount>({
|
|
||||||
objectNameSingular: CoreObjectNameSingular.ConnectedAccount,
|
|
||||||
filter: {
|
|
||||||
accountOwnerId: {
|
|
||||||
eq: currentWorkspaceMember?.id,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const { records: calendarChannels, loading: calendarChannelsLoading } =
|
|
||||||
useFindManyRecords<
|
|
||||||
CalendarChannel & {
|
|
||||||
connectedAccount: ConnectedAccount;
|
|
||||||
}
|
|
||||||
>({
|
|
||||||
objectNameSingular: CoreObjectNameSingular.CalendarChannel,
|
|
||||||
skip: !accounts.length,
|
|
||||||
filter: {
|
|
||||||
connectedAccountId: {
|
|
||||||
in: accounts.map((account) => account.id),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
recordGqlFields: generateDepthOneRecordGqlFields({ objectMetadataItem }),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!calendarChannels.length) {
|
|
||||||
return <SettingsAccountsListEmptyStateCard />;
|
|
||||||
}
|
|
||||||
|
|
||||||
const calendarChannelsWithSyncStatus: (CalendarChannel & {
|
|
||||||
connectedAccount: ConnectedAccount;
|
|
||||||
} & SettingsAccountsSynchronizationStatusProps)[] = calendarChannels.map(
|
|
||||||
(calendarChannel) => ({
|
|
||||||
...calendarChannel,
|
|
||||||
syncStatus: calendarChannel.connectedAccount?.authFailedAt
|
|
||||||
? 'FAILED'
|
|
||||||
: 'SUCCEEDED',
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<SettingsListCard
|
|
||||||
items={calendarChannelsWithSyncStatus}
|
|
||||||
getItemLabel={(calendarChannel) => calendarChannel.handle}
|
|
||||||
isLoading={accountsLoading || calendarChannelsLoading}
|
|
||||||
onRowClick={(calendarChannel) =>
|
|
||||||
navigate(`/settings/accounts/calendars/${calendarChannel.id}`)
|
|
||||||
}
|
|
||||||
RowIcon={IconGoogleCalendar}
|
|
||||||
RowRightComponent={({ item: calendarChannel }) => (
|
|
||||||
<StyledRowRightContainer>
|
|
||||||
<SettingsAccountsSynchronizationStatus
|
|
||||||
syncStatus={calendarChannel.syncStatus}
|
|
||||||
isSyncEnabled={calendarChannel.isSyncEnabled}
|
|
||||||
/>
|
|
||||||
<LightIconButton Icon={IconChevronRight} accent="tertiary" />
|
|
||||||
</StyledRowRightContainer>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@ -0,0 +1,30 @@
|
|||||||
|
import { Meta, StoryObj } from '@storybook/react';
|
||||||
|
import { ComponentDecorator } from 'twenty-ui';
|
||||||
|
|
||||||
|
import { SettingsAccountsCalendarChannelDetails } from '@/settings/accounts/components/SettingsAccountsCalendarChannelDetails';
|
||||||
|
import { CalendarChannelVisibility } from '~/generated/graphql';
|
||||||
|
|
||||||
|
const meta: Meta<typeof SettingsAccountsCalendarChannelDetails> = {
|
||||||
|
title:
|
||||||
|
'Modules/Settings/Accounts/CalendarChannels/SettingsAccountsCalendarChannelDetails',
|
||||||
|
component: SettingsAccountsCalendarChannelDetails,
|
||||||
|
decorators: [ComponentDecorator],
|
||||||
|
args: {
|
||||||
|
calendarChannel: {
|
||||||
|
id: '20202020-ef5a-4822-9e08-ce6e6a4dcb6a',
|
||||||
|
isContactAutoCreationEnabled: true,
|
||||||
|
isSyncEnabled: true,
|
||||||
|
visibility: CalendarChannelVisibility.ShareEverything,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
argTypes: {
|
||||||
|
calendarChannel: { control: false },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default meta;
|
||||||
|
type Story = StoryObj<typeof SettingsAccountsCalendarChannelDetails>;
|
||||||
|
|
||||||
|
export const Default: Story = {
|
||||||
|
play: async () => {},
|
||||||
|
};
|
||||||
@ -0,0 +1,18 @@
|
|||||||
|
import { Meta, StoryObj } from '@storybook/react';
|
||||||
|
import { ComponentDecorator } from 'twenty-ui';
|
||||||
|
|
||||||
|
import { SettingsAccountsCalendarChannelsGeneral } from '@/settings/accounts/components/SettingsAccountsCalendarChannelsGeneral';
|
||||||
|
|
||||||
|
const meta: Meta<typeof SettingsAccountsCalendarChannelsGeneral> = {
|
||||||
|
title:
|
||||||
|
'Modules/Settings/Accounts/CalendarChannels/SettingsAccountsCalendarChannelsGeneral',
|
||||||
|
component: SettingsAccountsCalendarChannelsGeneral,
|
||||||
|
decorators: [ComponentDecorator],
|
||||||
|
};
|
||||||
|
|
||||||
|
export default meta;
|
||||||
|
type Story = StoryObj<typeof SettingsAccountsCalendarChannelsGeneral>;
|
||||||
|
|
||||||
|
export const Default: Story = {
|
||||||
|
play: async () => {},
|
||||||
|
};
|
||||||
@ -0,0 +1,2 @@
|
|||||||
|
export const SETTINGS_ACCOUNT_CALENDAR_CHANNELS_TAB_LIST_COMPONENT_ID =
|
||||||
|
'settings-account-calendar-channels-tab-list';
|
||||||
@ -1,85 +1,14 @@
|
|||||||
import { addMinutes, endOfDay, min, startOfDay } from 'date-fns';
|
import { IconSettings } from 'twenty-ui';
|
||||||
import { useRecoilValue } from 'recoil';
|
|
||||||
import { H2Title, IconSettings } from 'twenty-ui';
|
|
||||||
|
|
||||||
import { CalendarChannel } from '@/accounts/types/CalendarChannel';
|
import { SettingsAccountsCalendarChannelsContainer } from '@/settings/accounts/components/SettingsAccountsCalendarChannelsContainer';
|
||||||
import { ConnectedAccount } from '@/accounts/types/ConnectedAccount';
|
|
||||||
import { CalendarMonthCard } from '@/activities/calendar/components/CalendarMonthCard';
|
|
||||||
import { CalendarContext } from '@/activities/calendar/contexts/CalendarContext';
|
|
||||||
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
|
|
||||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
|
||||||
import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
|
|
||||||
import { SettingsAccountsCalendarChannelsListCard } from '@/settings/accounts/components/SettingsAccountsCalendarChannelsListCard';
|
|
||||||
import { SettingsAccountsCalendarDisplaySettings } from '@/settings/accounts/components/SettingsAccountsCalendarDisplaySettings';
|
|
||||||
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
|
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
|
||||||
import { getSettingsPagePath } from '@/settings/utils/getSettingsPagePath';
|
import { getSettingsPagePath } from '@/settings/utils/getSettingsPagePath';
|
||||||
import { SettingsPath } from '@/types/SettingsPath';
|
import { SettingsPath } from '@/types/SettingsPath';
|
||||||
import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer';
|
import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer';
|
||||||
import { Section } from '@/ui/layout/section/components/Section';
|
import { Section } from '@/ui/layout/section/components/Section';
|
||||||
import { Breadcrumb } from '@/ui/navigation/bread-crumb/components/Breadcrumb';
|
import { Breadcrumb } from '@/ui/navigation/bread-crumb/components/Breadcrumb';
|
||||||
import { CalendarChannelVisibility } from '~/generated/graphql';
|
|
||||||
import { TimelineCalendarEvent } from '~/generated-metadata/graphql';
|
|
||||||
|
|
||||||
export const SettingsAccountsCalendars = () => {
|
export const SettingsAccountsCalendars = () => {
|
||||||
const calendarSettingsEnabled = false;
|
|
||||||
const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState);
|
|
||||||
const { records: accounts } = useFindManyRecords<ConnectedAccount>({
|
|
||||||
objectNameSingular: CoreObjectNameSingular.ConnectedAccount,
|
|
||||||
filter: {
|
|
||||||
accountOwnerId: {
|
|
||||||
eq: currentWorkspaceMember?.id,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const { records: calendarChannels } = useFindManyRecords<CalendarChannel>({
|
|
||||||
objectNameSingular: CoreObjectNameSingular.CalendarChannel,
|
|
||||||
filter: {
|
|
||||||
connectedAccountId: {
|
|
||||||
in: accounts.map((account) => account.id),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const exampleStartDate = new Date();
|
|
||||||
const exampleEndDate = min([
|
|
||||||
addMinutes(exampleStartDate, 30),
|
|
||||||
endOfDay(exampleStartDate),
|
|
||||||
]);
|
|
||||||
const exampleDayTime = startOfDay(exampleStartDate).getTime();
|
|
||||||
const exampleCalendarEvent: TimelineCalendarEvent = {
|
|
||||||
id: '',
|
|
||||||
participants: [
|
|
||||||
{
|
|
||||||
firstName: currentWorkspaceMember?.name.firstName || '',
|
|
||||||
lastName: currentWorkspaceMember?.name.lastName || '',
|
|
||||||
displayName: currentWorkspaceMember
|
|
||||||
? [
|
|
||||||
currentWorkspaceMember.name.firstName,
|
|
||||||
currentWorkspaceMember.name.lastName,
|
|
||||||
].join(' ')
|
|
||||||
: '',
|
|
||||||
avatarUrl: currentWorkspaceMember?.avatarUrl || '',
|
|
||||||
handle: '',
|
|
||||||
personId: '',
|
|
||||||
workspaceMemberId: currentWorkspaceMember?.id || '',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
endsAt: exampleEndDate.toISOString(),
|
|
||||||
isFullDay: false,
|
|
||||||
startsAt: exampleStartDate.toISOString(),
|
|
||||||
conferenceSolution: '',
|
|
||||||
conferenceLink: {
|
|
||||||
label: '',
|
|
||||||
url: '',
|
|
||||||
},
|
|
||||||
description: '',
|
|
||||||
isCanceled: false,
|
|
||||||
location: '',
|
|
||||||
title: 'Onboarding call',
|
|
||||||
visibility: CalendarChannelVisibility.ShareEverything,
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SubMenuTopBarContainer Icon={IconSettings} title="Settings">
|
<SubMenuTopBarContainer Icon={IconSettings} title="Settings">
|
||||||
<SettingsPageContainer>
|
<SettingsPageContainer>
|
||||||
@ -93,41 +22,8 @@ export const SettingsAccountsCalendars = () => {
|
|||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
<Section>
|
<Section>
|
||||||
<H2Title
|
<SettingsAccountsCalendarChannelsContainer />
|
||||||
title="Calendar settings"
|
|
||||||
description="Sync your calendars and set your preferences"
|
|
||||||
/>
|
|
||||||
<SettingsAccountsCalendarChannelsListCard />
|
|
||||||
</Section>
|
</Section>
|
||||||
{!!calendarChannels.length && calendarSettingsEnabled && (
|
|
||||||
<>
|
|
||||||
<Section>
|
|
||||||
<H2Title
|
|
||||||
title="Display"
|
|
||||||
description="Configure how we should display your events in your calendar"
|
|
||||||
/>
|
|
||||||
<SettingsAccountsCalendarDisplaySettings />
|
|
||||||
</Section>
|
|
||||||
<Section>
|
|
||||||
<H2Title
|
|
||||||
title="Color code"
|
|
||||||
description="Events you participated in are displayed in red."
|
|
||||||
/>
|
|
||||||
<CalendarContext.Provider
|
|
||||||
value={{
|
|
||||||
currentCalendarEvent: exampleCalendarEvent,
|
|
||||||
calendarEventsByDayTime: {
|
|
||||||
[exampleDayTime]: [exampleCalendarEvent],
|
|
||||||
},
|
|
||||||
getNextCalendarEvent: () => undefined,
|
|
||||||
updateCurrentCalendarEvent: () => {},
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<CalendarMonthCard dayTimes={[exampleDayTime]} />
|
|
||||||
</CalendarContext.Provider>
|
|
||||||
</Section>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</SettingsPageContainer>
|
</SettingsPageContainer>
|
||||||
</SubMenuTopBarContainer>
|
</SubMenuTopBarContainer>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,143 +0,0 @@
|
|||||||
import { useEffect } from 'react';
|
|
||||||
import { useNavigate, useParams } from 'react-router-dom';
|
|
||||||
import { useTheme } from '@emotion/react';
|
|
||||||
import styled from '@emotion/styled';
|
|
||||||
import { H2Title, IconRefresh, IconSettings, IconUser } from 'twenty-ui';
|
|
||||||
|
|
||||||
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';
|
|
||||||
import { SettingsAccountsEventVisibilitySettingsCard } from '@/settings/accounts/components/SettingsAccountsCalendarVisibilitySettingsCard';
|
|
||||||
import { SettingsAccountsCardMedia } from '@/settings/accounts/components/SettingsAccountsCardMedia';
|
|
||||||
import { SettingsAccountsToggleSettingCard } from '@/settings/accounts/components/SettingsAccountsToggleSettingCard';
|
|
||||||
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
|
|
||||||
import { getSettingsPagePath } from '@/settings/utils/getSettingsPagePath';
|
|
||||||
import { AppPath } from '@/types/AppPath';
|
|
||||||
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)};
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const SettingsAccountsCalendarsSettings = () => {
|
|
||||||
const theme = useTheme();
|
|
||||||
const navigate = useNavigate();
|
|
||||||
|
|
||||||
const { accountUuid: calendarChannelId = '' } = useParams();
|
|
||||||
|
|
||||||
const { record: calendarChannel, loading } =
|
|
||||||
useFindOneRecord<CalendarChannel>({
|
|
||||||
objectNameSingular: CoreObjectNameSingular.CalendarChannel,
|
|
||||||
objectRecordId: calendarChannelId,
|
|
||||||
});
|
|
||||||
|
|
||||||
const { updateOneRecord } = useUpdateOneRecord<CalendarChannel>({
|
|
||||||
objectNameSingular: CoreObjectNameSingular.CalendarChannel,
|
|
||||||
});
|
|
||||||
|
|
||||||
const handleVisibilityChange = (value: CalendarChannelVisibility) => {
|
|
||||||
updateOneRecord({
|
|
||||||
idToUpdate: calendarChannelId,
|
|
||||||
updateOneRecordInput: {
|
|
||||||
visibility: value,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleContactAutoCreationToggle = (value: boolean) => {
|
|
||||||
updateOneRecord({
|
|
||||||
idToUpdate: calendarChannelId,
|
|
||||||
updateOneRecordInput: {
|
|
||||||
isContactAutoCreationEnabled: value,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSyncEventsToggle = (value: boolean) => {
|
|
||||||
updateOneRecord({
|
|
||||||
idToUpdate: calendarChannelId,
|
|
||||||
updateOneRecordInput: {
|
|
||||||
isSyncEnabled: value,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!loading && !calendarChannel) navigate(AppPath.NotFound);
|
|
||||||
}, [loading, calendarChannel, navigate]);
|
|
||||||
|
|
||||||
if (!calendarChannel) return null;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<SubMenuTopBarContainer Icon={IconSettings} title="Settings">
|
|
||||||
<SettingsPageContainer>
|
|
||||||
<Breadcrumb
|
|
||||||
links={[
|
|
||||||
{
|
|
||||||
children: 'Accounts',
|
|
||||||
href: getSettingsPagePath(SettingsPath.Accounts),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
children: 'Calendars',
|
|
||||||
href: getSettingsPagePath(SettingsPath.AccountsCalendars),
|
|
||||||
},
|
|
||||||
{ children: calendarChannel?.handle || '' },
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
<Section>
|
|
||||||
<H2Title
|
|
||||||
title="Event visibility"
|
|
||||||
description="Define what will be visible to other users in your workspace"
|
|
||||||
/>
|
|
||||||
<SettingsAccountsEventVisibilitySettingsCard
|
|
||||||
value={calendarChannel.visibility}
|
|
||||||
onChange={handleVisibilityChange}
|
|
||||||
/>
|
|
||||||
</Section>
|
|
||||||
<Section>
|
|
||||||
<H2Title
|
|
||||||
title="Contact auto-creation"
|
|
||||||
description="Automatically create contacts for people you've participated in an event with."
|
|
||||||
/>
|
|
||||||
<SettingsAccountsToggleSettingCard
|
|
||||||
cardMedia={
|
|
||||||
<StyledCardMedia>
|
|
||||||
<IconUser
|
|
||||||
size={theme.icon.size.sm}
|
|
||||||
stroke={theme.icon.stroke.lg}
|
|
||||||
/>
|
|
||||||
</StyledCardMedia>
|
|
||||||
}
|
|
||||||
title="Auto-creation"
|
|
||||||
value={!!calendarChannel.isContactAutoCreationEnabled}
|
|
||||||
onToggle={handleContactAutoCreationToggle}
|
|
||||||
/>
|
|
||||||
</Section>
|
|
||||||
<Section>
|
|
||||||
<H2Title
|
|
||||||
title="Synchronization"
|
|
||||||
description="Past and future calendar events will automatically be synced to this workspace"
|
|
||||||
/>
|
|
||||||
<SettingsAccountsToggleSettingCard
|
|
||||||
cardMedia={
|
|
||||||
<StyledCardMedia>
|
|
||||||
<IconRefresh
|
|
||||||
size={theme.icon.size.sm}
|
|
||||||
stroke={theme.icon.stroke.lg}
|
|
||||||
/>
|
|
||||||
</StyledCardMedia>
|
|
||||||
}
|
|
||||||
title="Sync events"
|
|
||||||
value={!!calendarChannel.isSyncEnabled}
|
|
||||||
onToggle={handleSyncEventsToggle}
|
|
||||||
/>
|
|
||||||
</Section>
|
|
||||||
</SettingsPageContainer>
|
|
||||||
</SubMenuTopBarContainer>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@ -1,23 +1,19 @@
|
|||||||
import { Meta, StoryObj } from '@storybook/react';
|
import { Meta, StoryObj } from '@storybook/react';
|
||||||
import { within } from '@storybook/test';
|
import { HttpResponse, graphql } from 'msw';
|
||||||
|
import { SettingsAccountsCalendars } from '~/pages/settings/accounts/SettingsAccountsCalendars';
|
||||||
|
|
||||||
import { getSettingsPagePath } from '@/settings/utils/getSettingsPagePath';
|
|
||||||
import { SettingsPath } from '@/types/SettingsPath';
|
|
||||||
import {
|
import {
|
||||||
PageDecorator,
|
PageDecorator,
|
||||||
PageDecoratorArgs,
|
PageDecoratorArgs,
|
||||||
} from '~/testing/decorators/PageDecorator';
|
} from '~/testing/decorators/PageDecorator';
|
||||||
import { graphqlMocks } from '~/testing/graphqlMocks';
|
import { graphqlMocks } from '~/testing/graphqlMocks';
|
||||||
import { sleep } from '~/utils/sleep';
|
|
||||||
|
|
||||||
import { SettingsAccountsCalendars } from '../SettingsAccountsCalendars';
|
|
||||||
|
|
||||||
const meta: Meta<PageDecoratorArgs> = {
|
const meta: Meta<PageDecoratorArgs> = {
|
||||||
title: 'Pages/Settings/Accounts/SettingsAccountsCalendars',
|
title: 'Pages/Settings/Accounts/SettingsAccountsCalendars',
|
||||||
component: SettingsAccountsCalendars,
|
component: SettingsAccountsCalendars,
|
||||||
decorators: [PageDecorator],
|
decorators: [PageDecorator],
|
||||||
args: {
|
args: {
|
||||||
routePath: getSettingsPagePath(SettingsPath.AccountsCalendars),
|
routePath: '/settings/accounts/calendars',
|
||||||
},
|
},
|
||||||
parameters: {
|
parameters: {
|
||||||
layout: 'fullscreen',
|
layout: 'fullscreen',
|
||||||
@ -29,11 +25,120 @@ export default meta;
|
|||||||
|
|
||||||
export type Story = StoryObj<typeof SettingsAccountsCalendars>;
|
export type Story = StoryObj<typeof SettingsAccountsCalendars>;
|
||||||
|
|
||||||
export const Default: Story = {
|
export const NoConnectedAccount: Story = {};
|
||||||
play: async ({ canvasElement }) => {
|
|
||||||
const canvas = within(canvasElement);
|
|
||||||
sleep(100);
|
|
||||||
|
|
||||||
await canvas.findByText('Calendar settings');
|
export const TwoConnectedAccounts: Story = {
|
||||||
|
parameters: {
|
||||||
|
msw: {
|
||||||
|
handlers: [
|
||||||
|
...graphqlMocks.handlers,
|
||||||
|
graphql.query('FindManyConnectedAccounts', () => {
|
||||||
|
return HttpResponse.json({
|
||||||
|
data: {
|
||||||
|
connectedAccounts: {
|
||||||
|
__typename: 'ConnectedAccountConnection',
|
||||||
|
totalCount: 1,
|
||||||
|
pageInfo: {
|
||||||
|
__typename: 'PageInfo',
|
||||||
|
hasNextPage: false,
|
||||||
|
startCursor: '',
|
||||||
|
endCursor: '',
|
||||||
|
},
|
||||||
|
edges: [
|
||||||
|
{
|
||||||
|
__typename: 'ConnectedAccountEdge',
|
||||||
|
cursor: '',
|
||||||
|
node: {
|
||||||
|
__typename: 'ConnectedAccount',
|
||||||
|
accessToken: '',
|
||||||
|
refreshToken: '',
|
||||||
|
updatedAt: '2024-07-03T20:03:35.064Z',
|
||||||
|
createdAt: '2024-07-03T20:03:35.064Z',
|
||||||
|
id: '20202020-954c-4d76-9a87-e5f072d4b7ef',
|
||||||
|
provider: 'google',
|
||||||
|
accountOwnerId: '20202020-03f2-4d83-b0d5-2ec2bcee72d4',
|
||||||
|
lastSyncHistoryId: '',
|
||||||
|
emailAliases: '',
|
||||||
|
handle: 'test.test@gmail.com',
|
||||||
|
authFailedAt: null,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
graphql.query('FindManyCalendarChannels', () => {
|
||||||
|
return HttpResponse.json({
|
||||||
|
data: {
|
||||||
|
calendarChannels: {
|
||||||
|
__typename: 'CalendarChannelConnection',
|
||||||
|
totalCount: 2,
|
||||||
|
pageInfo: {
|
||||||
|
__typename: 'PageInfo',
|
||||||
|
hasNextPage: false,
|
||||||
|
startCursor: '',
|
||||||
|
endCursor: '',
|
||||||
|
},
|
||||||
|
edges: [
|
||||||
|
{
|
||||||
|
__typename: 'CalendarChannelEdge',
|
||||||
|
cursor: '',
|
||||||
|
node: {
|
||||||
|
__typename: 'CalendarChannel',
|
||||||
|
handle: 'test.test@gmail.com',
|
||||||
|
excludeNonProfessionalEmails: true,
|
||||||
|
syncStageStartedAt: null,
|
||||||
|
id: '20202020-ef5a-4822-9e08-ce6e6a4dcb6f',
|
||||||
|
updatedAt: '2024-07-03T20:03:11.903Z',
|
||||||
|
createdAt: '2024-07-03T20:03:11.903Z',
|
||||||
|
connectedAccountId:
|
||||||
|
'20202020-954c-4d76-9a87-e5f072d4b7ef',
|
||||||
|
contactAutoCreationPolicy: 'SENT',
|
||||||
|
syncStage: 'PARTIAL_CALENDAR_EVENT_LIST_FETCH_PENDING',
|
||||||
|
type: 'email',
|
||||||
|
isContactAutoCreationEnabled: true,
|
||||||
|
syncCursor: '1562764',
|
||||||
|
excludeGroupEmails: true,
|
||||||
|
throttleFailureCount: 0,
|
||||||
|
isSyncEnabled: true,
|
||||||
|
visibility: 'SHARE_EVERYTHING',
|
||||||
|
syncStatus: 'COMPLETED',
|
||||||
|
syncedAt: '2024-07-04T16:25:04.960Z',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
__typename: 'CalendarChannelEdge',
|
||||||
|
cursor: '',
|
||||||
|
node: {
|
||||||
|
__typename: 'CalendarChannel',
|
||||||
|
handle: 'test.test2@gmail.com',
|
||||||
|
excludeNonProfessionalEmails: true,
|
||||||
|
syncStageStartedAt: null,
|
||||||
|
id: '20202020-ef5a-4822-9e08-ce6e6a4dcb6a',
|
||||||
|
updatedAt: '2024-07-03T20:03:11.903Z',
|
||||||
|
createdAt: '2024-07-03T20:03:11.903Z',
|
||||||
|
connectedAccountId:
|
||||||
|
'20202020-954c-4d76-9a87-e5f072d4b7ef',
|
||||||
|
contactAutoCreationPolicy: 'SENT',
|
||||||
|
syncStage: 'PARTIAL_CALENDAR_EVENT_LIST_FETCH_PENDING',
|
||||||
|
type: 'email',
|
||||||
|
isContactAutoCreationEnabled: true,
|
||||||
|
syncCursor: '1562764',
|
||||||
|
excludeGroupEmails: true,
|
||||||
|
throttleFailureCount: 0,
|
||||||
|
isSyncEnabled: true,
|
||||||
|
visibility: 'SHARE_EVERYTHING',
|
||||||
|
syncStatus: 'COMPLETED',
|
||||||
|
syncedAt: '2024-07-04T16:25:04.960Z',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,61 +0,0 @@
|
|||||||
import { Meta, StoryObj } from '@storybook/react';
|
|
||||||
import { within } from '@storybook/test';
|
|
||||||
import { graphql, HttpResponse } from 'msw';
|
|
||||||
|
|
||||||
import { getSettingsPagePath } from '@/settings/utils/getSettingsPagePath';
|
|
||||||
import { SettingsPath } from '@/types/SettingsPath';
|
|
||||||
import {
|
|
||||||
PageDecorator,
|
|
||||||
PageDecoratorArgs,
|
|
||||||
} from '~/testing/decorators/PageDecorator';
|
|
||||||
import { graphqlMocks } from '~/testing/graphqlMocks';
|
|
||||||
import { mockedConnectedAccounts } from '~/testing/mock-data/accounts';
|
|
||||||
import { sleep } from '~/utils/sleep';
|
|
||||||
|
|
||||||
import { SettingsAccountsCalendarsSettings } from '../SettingsAccountsCalendarsSettings';
|
|
||||||
|
|
||||||
const meta: Meta<PageDecoratorArgs> = {
|
|
||||||
title: 'Pages/Settings/Accounts/SettingsAccountsCalendarsSettings',
|
|
||||||
component: SettingsAccountsCalendarsSettings,
|
|
||||||
decorators: [PageDecorator],
|
|
||||||
args: {
|
|
||||||
routePath: getSettingsPagePath(SettingsPath.AccountsCalendarsSettings),
|
|
||||||
routeParams: { ':accountUuid': mockedConnectedAccounts[0].id },
|
|
||||||
},
|
|
||||||
parameters: {
|
|
||||||
layout: 'fullscreen',
|
|
||||||
msw: {
|
|
||||||
handlers: [
|
|
||||||
...graphqlMocks.handlers,
|
|
||||||
graphql.query('FindOneCalendarChannel', () => {
|
|
||||||
return HttpResponse.json({
|
|
||||||
data: {
|
|
||||||
calendarChannel: {
|
|
||||||
edges: [],
|
|
||||||
pageInfo: {
|
|
||||||
hasNextPage: false,
|
|
||||||
hasPreviousPage: false,
|
|
||||||
startCursor: null,
|
|
||||||
endCursor: null,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export default meta;
|
|
||||||
|
|
||||||
export type Story = StoryObj<typeof SettingsAccountsCalendarsSettings>;
|
|
||||||
|
|
||||||
export const Default: Story = {
|
|
||||||
play: async ({ canvasElement }) => {
|
|
||||||
const canvas = within(canvasElement);
|
|
||||||
sleep(100);
|
|
||||||
|
|
||||||
await canvas.findByText('Event visibility');
|
|
||||||
},
|
|
||||||
};
|
|
||||||
Reference in New Issue
Block a user