feat: create Settings/Accounts/Calendars page (#4090)
* feat: create Settings/Accounts/Calendars page Closes #4059 * docs: add SettingsAccountsCalendars stories * refactor: add SettingsNavigationDrawerItem component
This commit is contained in:
@ -20,6 +20,7 @@ import { RecordIndexPage } from '~/pages/object-record/RecordIndexPage';
|
||||
import { RecordShowPage } from '~/pages/object-record/RecordShowPage';
|
||||
import { Opportunities } from '~/pages/opportunities/Opportunities';
|
||||
import { SettingsAccounts } from '~/pages/settings/accounts/SettingsAccounts';
|
||||
import { SettingsAccountsCalendars } from '~/pages/settings/accounts/SettingsAccountsCalendars';
|
||||
import { SettingsAccountsEmails } from '~/pages/settings/accounts/SettingsAccountsEmails';
|
||||
import { SettingsAccountsEmailsInboxSettings } from '~/pages/settings/accounts/SettingsAccountsEmailsInboxSettings';
|
||||
import { SettingsNewAccount } from '~/pages/settings/accounts/SettingsNewAccount';
|
||||
@ -95,6 +96,10 @@ export const App = () => {
|
||||
path={SettingsPath.NewAccount}
|
||||
element={<SettingsNewAccount />}
|
||||
/>
|
||||
<Route
|
||||
path={SettingsPath.AccountsCalendars}
|
||||
element={<SettingsAccountsCalendars />}
|
||||
/>
|
||||
<Route
|
||||
path={SettingsPath.AccountsEmails}
|
||||
element={<SettingsAccountsEmails />}
|
||||
|
||||
@ -2,9 +2,12 @@ import { useNavigate } from 'react-router-dom';
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { SettingsNavigationCard } from '@/settings/components/SettingsNavigationCard';
|
||||
import { getSettingsPagePath } from '@/settings/utils/getSettingsPagePath';
|
||||
import { SettingsPath } from '@/types/SettingsPath';
|
||||
import { IconCalendarEvent, IconMailCog } from '@/ui/display/icon';
|
||||
import { H2Title } from '@/ui/display/typography/components/H2Title';
|
||||
import { Section } from '@/ui/layout/section/components/Section';
|
||||
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||
|
||||
const StyledCardsContainer = styled.div`
|
||||
display: flex;
|
||||
@ -12,12 +15,9 @@ const StyledCardsContainer = styled.div`
|
||||
margin-top: ${({ theme }) => theme.spacing(6)};
|
||||
`;
|
||||
|
||||
const StyledSettingsNavigationCard = styled(SettingsNavigationCard)`
|
||||
color: ${({ theme }) => theme.font.color.extraLight};
|
||||
`;
|
||||
|
||||
export const SettingsAccountsSettingsSection = () => {
|
||||
const navigate = useNavigate();
|
||||
const isCalendarEnabled = useIsFeatureEnabled('IS_CALENDAR_ENABLED');
|
||||
|
||||
return (
|
||||
<Section>
|
||||
@ -29,18 +29,22 @@ export const SettingsAccountsSettingsSection = () => {
|
||||
<SettingsNavigationCard
|
||||
Icon={IconMailCog}
|
||||
title="Emails"
|
||||
onClick={() => navigate('/settings/accounts/emails')}
|
||||
onClick={() =>
|
||||
navigate(getSettingsPagePath(SettingsPath.AccountsEmails))
|
||||
}
|
||||
>
|
||||
Set email visibility, manage your blocklist and more.
|
||||
</SettingsNavigationCard>
|
||||
<StyledSettingsNavigationCard
|
||||
<SettingsNavigationCard
|
||||
Icon={IconCalendarEvent}
|
||||
title="Calendar"
|
||||
disabled
|
||||
hasSoonPill
|
||||
soon={!isCalendarEnabled}
|
||||
onClick={() =>
|
||||
navigate(getSettingsPagePath(SettingsPath.AccountsCalendars))
|
||||
}
|
||||
>
|
||||
Configure and customize your calendar preferences.
|
||||
</StyledSettingsNavigationCard>
|
||||
</SettingsNavigationCard>
|
||||
</StyledCardsContainer>
|
||||
</Section>
|
||||
);
|
||||
|
||||
@ -11,7 +11,7 @@ import { CardContent } from '@/ui/layout/card/components/CardContent';
|
||||
type SettingsNavigationCardProps = {
|
||||
children: ReactNode;
|
||||
disabled?: boolean;
|
||||
hasSoonPill?: boolean;
|
||||
soon?: boolean;
|
||||
Icon: IconComponent;
|
||||
onClick?: () => void;
|
||||
title: string;
|
||||
@ -22,7 +22,8 @@ const StyledCard = styled(Card)<{
|
||||
disabled?: boolean;
|
||||
onClick?: () => void;
|
||||
}>`
|
||||
color: ${({ theme }) => theme.font.color.tertiary};
|
||||
color: ${({ disabled, theme }) =>
|
||||
disabled ? theme.font.color.extraLight : theme.font.color.tertiary};
|
||||
cursor: ${({ disabled, onClick }) =>
|
||||
disabled ? 'not-allowed' : onClick ? 'pointer' : 'default'};
|
||||
`;
|
||||
@ -40,8 +41,9 @@ const StyledHeader = styled.div`
|
||||
gap: ${({ theme }) => theme.spacing(3)};
|
||||
`;
|
||||
|
||||
const StyledTitle = styled.div`
|
||||
color: ${({ theme }) => theme.font.color.secondary};
|
||||
const StyledTitle = styled.div<{ disabled?: boolean }>`
|
||||
color: ${({ disabled, theme }) =>
|
||||
disabled ? 'inherit' : theme.font.color.secondary};
|
||||
display: flex;
|
||||
flex: 1 0 auto;
|
||||
font-weight: ${({ theme }) => theme.font.weight.medium};
|
||||
@ -59,8 +61,8 @@ const StyledDescription = styled.div`
|
||||
|
||||
export const SettingsNavigationCard = ({
|
||||
children,
|
||||
disabled,
|
||||
hasSoonPill,
|
||||
soon,
|
||||
disabled = soon,
|
||||
Icon,
|
||||
onClick,
|
||||
title,
|
||||
@ -69,13 +71,17 @@ export const SettingsNavigationCard = ({
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<StyledCard disabled={disabled} onClick={onClick} className={className}>
|
||||
<StyledCard
|
||||
disabled={disabled}
|
||||
onClick={disabled ? undefined : onClick}
|
||||
className={className}
|
||||
>
|
||||
<StyledCardContent>
|
||||
<StyledHeader>
|
||||
<Icon size={theme.icon.size.lg} stroke={theme.icon.stroke.sm} />
|
||||
<StyledTitle className={className}>
|
||||
<StyledTitle disabled={disabled}>
|
||||
{title}
|
||||
{hasSoonPill && <SoonPill />}
|
||||
{soon && <SoonPill />}
|
||||
</StyledTitle>
|
||||
<StyledIconChevronRight size={theme.icon.size.sm} />
|
||||
</StyledHeader>
|
||||
|
||||
@ -0,0 +1,42 @@
|
||||
import { useMatch, useResolvedPath } from 'react-router-dom';
|
||||
|
||||
import { getSettingsPagePath } from '@/settings/utils/getSettingsPagePath';
|
||||
import { SettingsPath } from '@/types/SettingsPath';
|
||||
import {
|
||||
NavigationDrawerItem,
|
||||
NavigationDrawerItemProps,
|
||||
} from '@/ui/navigation/navigation-drawer/components/NavigationDrawerItem';
|
||||
|
||||
type SettingsNavigationDrawerItemProps = Pick<
|
||||
NavigationDrawerItemProps,
|
||||
'Icon' | 'label' | 'level' | 'soon'
|
||||
> & {
|
||||
matchSubPages?: boolean;
|
||||
path: SettingsPath;
|
||||
};
|
||||
|
||||
export const SettingsNavigationDrawerItem = ({
|
||||
Icon,
|
||||
label,
|
||||
level,
|
||||
matchSubPages = false,
|
||||
path,
|
||||
soon,
|
||||
}: SettingsNavigationDrawerItemProps) => {
|
||||
const href = getSettingsPagePath(path);
|
||||
const isActive = !!useMatch({
|
||||
path: useResolvedPath(href).pathname,
|
||||
end: !matchSubPages,
|
||||
});
|
||||
|
||||
return (
|
||||
<NavigationDrawerItem
|
||||
level={level}
|
||||
label={label}
|
||||
to={href}
|
||||
Icon={Icon}
|
||||
active={isActive}
|
||||
soon={soon}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@ -1,8 +1,10 @@
|
||||
import { useCallback } from 'react';
|
||||
import { useMatch, useNavigate, useResolvedPath } from 'react-router-dom';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import { useAuth } from '@/auth/hooks/useAuth';
|
||||
import { SettingsNavigationDrawerItem } from '@/settings/components/SettingsNavigationDrawerItem';
|
||||
import { AppPath } from '@/types/AppPath';
|
||||
import { SettingsPath } from '@/types/SettingsPath';
|
||||
import {
|
||||
IconApps,
|
||||
IconAt,
|
||||
@ -31,67 +33,45 @@ export const SettingsNavigationDrawerItems = () => {
|
||||
navigate(AppPath.SignIn);
|
||||
}, [signOut, navigate]);
|
||||
|
||||
const isCalendarEnabled = useIsFeatureEnabled('IS_CALENDAR_ENABLED');
|
||||
const isMessagingEnabled = useIsFeatureEnabled('IS_MESSAGING_ENABLED');
|
||||
const isIntegrationsItemActive = !!useMatch({
|
||||
path: useResolvedPath('/settings/integrations').pathname,
|
||||
end: true,
|
||||
});
|
||||
|
||||
const isAccountsItemActive = !!useMatch({
|
||||
path: useResolvedPath('/settings/accounts').pathname,
|
||||
end: true,
|
||||
});
|
||||
const isAccountsEmailsItemActive = !!useMatch({
|
||||
path: useResolvedPath('/settings/accounts/emails').pathname,
|
||||
end: true,
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<NavigationDrawerSection>
|
||||
<NavigationDrawerSectionTitle label="User" />
|
||||
<NavigationDrawerItem
|
||||
<SettingsNavigationDrawerItem
|
||||
label="Profile"
|
||||
to="/settings/profile"
|
||||
path={SettingsPath.ProfilePage}
|
||||
Icon={IconUserCircle}
|
||||
active={
|
||||
!!useMatch({
|
||||
path: useResolvedPath('/settings/profile').pathname,
|
||||
end: true,
|
||||
})
|
||||
}
|
||||
/>
|
||||
<NavigationDrawerItem
|
||||
<SettingsNavigationDrawerItem
|
||||
label="Appearance"
|
||||
to="/settings/profile/appearance"
|
||||
path={SettingsPath.Appearance}
|
||||
Icon={IconColorSwatch}
|
||||
active={
|
||||
!!useMatch({
|
||||
path: useResolvedPath('/settings/profile/appearance').pathname,
|
||||
end: true,
|
||||
})
|
||||
}
|
||||
/>
|
||||
|
||||
{isMessagingEnabled && (
|
||||
<NavigationDrawerItemGroup>
|
||||
<NavigationDrawerItem
|
||||
<SettingsNavigationDrawerItem
|
||||
label="Accounts"
|
||||
to="/settings/accounts"
|
||||
path={SettingsPath.Accounts}
|
||||
Icon={IconAt}
|
||||
active={isAccountsItemActive}
|
||||
/>
|
||||
<NavigationDrawerItem
|
||||
<SettingsNavigationDrawerItem
|
||||
level={2}
|
||||
label="Emails"
|
||||
to="/settings/accounts/emails"
|
||||
path={SettingsPath.AccountsEmails}
|
||||
Icon={IconMail}
|
||||
active={isAccountsEmailsItemActive}
|
||||
matchSubPages
|
||||
/>
|
||||
<NavigationDrawerItem
|
||||
<SettingsNavigationDrawerItem
|
||||
level={2}
|
||||
label="Calendars"
|
||||
path={SettingsPath.AccountsCalendars}
|
||||
Icon={IconCalendarEvent}
|
||||
soon
|
||||
matchSubPages
|
||||
soon={!isCalendarEnabled}
|
||||
/>
|
||||
</NavigationDrawerItemGroup>
|
||||
)}
|
||||
@ -99,55 +79,31 @@ export const SettingsNavigationDrawerItems = () => {
|
||||
|
||||
<NavigationDrawerSection>
|
||||
<NavigationDrawerSectionTitle label="Workspace" />
|
||||
<NavigationDrawerItem
|
||||
<SettingsNavigationDrawerItem
|
||||
label="General"
|
||||
to="/settings/workspace"
|
||||
path={SettingsPath.Workspace}
|
||||
Icon={IconSettings}
|
||||
active={
|
||||
!!useMatch({
|
||||
path: useResolvedPath('/settings/workspace').pathname,
|
||||
end: true,
|
||||
})
|
||||
}
|
||||
/>
|
||||
<NavigationDrawerItem
|
||||
<SettingsNavigationDrawerItem
|
||||
label="Members"
|
||||
to="/settings/workspace-members"
|
||||
path={SettingsPath.WorkspaceMembersPage}
|
||||
Icon={IconUsers}
|
||||
active={
|
||||
!!useMatch({
|
||||
path: useResolvedPath('/settings/workspace-members').pathname,
|
||||
end: true,
|
||||
})
|
||||
}
|
||||
/>
|
||||
<NavigationDrawerItem
|
||||
<SettingsNavigationDrawerItem
|
||||
label="Data model"
|
||||
to="/settings/objects"
|
||||
path={SettingsPath.Objects}
|
||||
Icon={IconHierarchy2}
|
||||
active={
|
||||
!!useMatch({
|
||||
path: useResolvedPath('/settings/objects').pathname,
|
||||
end: false,
|
||||
})
|
||||
}
|
||||
matchSubPages
|
||||
/>
|
||||
<NavigationDrawerItem
|
||||
<SettingsNavigationDrawerItem
|
||||
label="Developers"
|
||||
to="/settings/developers"
|
||||
path={SettingsPath.Developers}
|
||||
Icon={IconRobot}
|
||||
active={
|
||||
!!useMatch({
|
||||
path: useResolvedPath('/settings/developers').pathname,
|
||||
end: true,
|
||||
})
|
||||
}
|
||||
/>
|
||||
<NavigationDrawerItem
|
||||
<SettingsNavigationDrawerItem
|
||||
label="Integrations"
|
||||
to="/settings/integrations"
|
||||
path={SettingsPath.Integrations}
|
||||
Icon={IconApps}
|
||||
active={isIntegrationsItemActive}
|
||||
/>
|
||||
</NavigationDrawerSection>
|
||||
|
||||
|
||||
@ -0,0 +1,4 @@
|
||||
import { SettingsPath } from '@/types/SettingsPath';
|
||||
|
||||
export const getSettingsPagePath = <Path extends SettingsPath>(path: Path) =>
|
||||
`/settings/${path}` as const;
|
||||
@ -3,6 +3,7 @@ export enum SettingsPath {
|
||||
Appearance = 'profile/appearance',
|
||||
Accounts = 'accounts',
|
||||
NewAccount = 'accounts/new',
|
||||
AccountsCalendars = 'accounts/calendars',
|
||||
AccountsEmails = 'accounts/emails',
|
||||
AccountsEmailsInboxSettings = 'accounts/emails/:accountUuid',
|
||||
Objects = 'objects',
|
||||
@ -14,7 +15,7 @@ export enum SettingsPath {
|
||||
NewObject = 'objects/new',
|
||||
WorkspaceMembersPage = 'workspace-members',
|
||||
Workspace = 'workspace',
|
||||
Developers = '',
|
||||
Developers = 'developers',
|
||||
DevelopersNewApiKey = 'api-keys/new',
|
||||
DevelopersApiKeyDetail = 'api-keys/:apiKeyId',
|
||||
Integrations = 'integrations',
|
||||
|
||||
@ -8,7 +8,7 @@ import { isNavigationDrawerOpenState } from '@/ui/navigation/states/isNavigation
|
||||
import { MOBILE_VIEWPORT } from '@/ui/theme/constants/theme';
|
||||
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
|
||||
|
||||
type NavigationDrawerItemProps = {
|
||||
export type NavigationDrawerItemProps = {
|
||||
className?: string;
|
||||
label: string;
|
||||
level?: 1 | 2;
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
import { Meta, StoryObj } from '@storybook/react';
|
||||
|
||||
import { Favorites } from '@/favorites/components/Favorites';
|
||||
import { getSettingsPagePath } from '@/settings/utils/getSettingsPagePath';
|
||||
import { SettingsPath } from '@/types/SettingsPath';
|
||||
import {
|
||||
IconAt,
|
||||
IconBell,
|
||||
@ -91,32 +93,32 @@ export const Submenu: Story = {
|
||||
<NavigationDrawerSectionTitle label="User" />
|
||||
<NavigationDrawerItem
|
||||
label="Profile"
|
||||
to="/settings/profile"
|
||||
to={getSettingsPagePath(SettingsPath.ProfilePage)}
|
||||
Icon={IconUserCircle}
|
||||
active
|
||||
/>
|
||||
<NavigationDrawerItem
|
||||
label="Appearance"
|
||||
to="/settings/profile/appearance"
|
||||
to={getSettingsPagePath(SettingsPath.Appearance)}
|
||||
Icon={IconColorSwatch}
|
||||
/>
|
||||
<NavigationDrawerItemGroup>
|
||||
<NavigationDrawerItem
|
||||
label="Accounts"
|
||||
to="/settings/accounts"
|
||||
to={getSettingsPagePath(SettingsPath.Accounts)}
|
||||
Icon={IconAt}
|
||||
/>
|
||||
<NavigationDrawerItem
|
||||
level={2}
|
||||
label="Emails"
|
||||
to="/settings/accounts/emails"
|
||||
to={getSettingsPagePath(SettingsPath.AccountsEmails)}
|
||||
Icon={IconMail}
|
||||
/>
|
||||
<NavigationDrawerItem
|
||||
level={2}
|
||||
label="Calendar"
|
||||
to={getSettingsPagePath(SettingsPath.AccountsCalendars)}
|
||||
Icon={IconCalendarEvent}
|
||||
soon
|
||||
/>
|
||||
</NavigationDrawerItemGroup>
|
||||
</NavigationDrawerSection>
|
||||
@ -125,12 +127,12 @@ export const Submenu: Story = {
|
||||
<NavigationDrawerSectionTitle label="Workspace" />
|
||||
<NavigationDrawerItem
|
||||
label="General"
|
||||
to="/settings/workspace"
|
||||
to={getSettingsPagePath(SettingsPath.Workspace)}
|
||||
Icon={IconSettings}
|
||||
/>
|
||||
<NavigationDrawerItem
|
||||
label="Members"
|
||||
to="/settings/workspace-members"
|
||||
to={getSettingsPagePath(SettingsPath.WorkspaceMembersPage)}
|
||||
Icon={IconUsers}
|
||||
/>
|
||||
</NavigationDrawerSection>
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
export type FeatureFlagKey =
|
||||
| 'IS_MESSAGING_ENABLED'
|
||||
| 'IS_BLOCKLIST_ENABLED'
|
||||
| 'IS_QUICK_ACTIONS_ENABLED'
|
||||
| 'IS_NEW_RECORD_BOARD_ENABLED';
|
||||
| 'IS_CALENDAR_ENABLED'
|
||||
| 'IS_MESSAGING_ENABLED'
|
||||
| 'IS_NEW_RECORD_BOARD_ENABLED'
|
||||
| 'IS_QUICK_ACTIONS_ENABLED';
|
||||
|
||||
@ -0,0 +1,22 @@
|
||||
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
|
||||
import { getSettingsPagePath } from '@/settings/utils/getSettingsPagePath';
|
||||
import { SettingsPath } from '@/types/SettingsPath';
|
||||
import { IconSettings } from '@/ui/display/icon';
|
||||
import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer';
|
||||
import { Breadcrumb } from '@/ui/navigation/bread-crumb/components/Breadcrumb';
|
||||
|
||||
export const SettingsAccountsCalendars = () => (
|
||||
<SubMenuTopBarContainer Icon={IconSettings} title="Settings">
|
||||
<SettingsPageContainer>
|
||||
<Breadcrumb
|
||||
links={[
|
||||
{
|
||||
children: 'Accounts',
|
||||
href: getSettingsPagePath(SettingsPath.Accounts),
|
||||
},
|
||||
{ children: 'Calendars' },
|
||||
]}
|
||||
/>
|
||||
</SettingsPageContainer>
|
||||
</SubMenuTopBarContainer>
|
||||
);
|
||||
@ -0,0 +1,30 @@
|
||||
import { Meta, StoryObj } from '@storybook/react';
|
||||
|
||||
import { getSettingsPagePath } from '@/settings/utils/getSettingsPagePath';
|
||||
import { SettingsPath } from '@/types/SettingsPath';
|
||||
import {
|
||||
PageDecorator,
|
||||
PageDecoratorArgs,
|
||||
} from '~/testing/decorators/PageDecorator';
|
||||
import { graphqlMocks } from '~/testing/graphqlMocks';
|
||||
|
||||
import { SettingsAccountsCalendars } from '../SettingsAccountsCalendars';
|
||||
|
||||
const meta: Meta<PageDecoratorArgs> = {
|
||||
title: 'Pages/Settings/Accounts/SettingsAccountsCalendars',
|
||||
component: SettingsAccountsCalendars,
|
||||
decorators: [PageDecorator],
|
||||
args: {
|
||||
routePath: getSettingsPagePath(SettingsPath.AccountsCalendars),
|
||||
},
|
||||
parameters: {
|
||||
layout: 'fullscreen',
|
||||
msw: graphqlMocks,
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
|
||||
export type Story = StoryObj<typeof SettingsAccountsCalendars>;
|
||||
|
||||
export const Default: Story = {};
|
||||
@ -14,10 +14,11 @@ import { IDField } from '@ptc-org/nestjs-query-graphql';
|
||||
import { Workspace } from 'src/core/workspace/workspace.entity';
|
||||
|
||||
export enum FeatureFlagKeys {
|
||||
IsMessagingEnabled = 'IS_MESSAGING_ENABLED',
|
||||
IsBlocklistEnabled = 'IS_BLOCKLIST_ENABLED',
|
||||
IsWorkspaceCleanable = 'IS_WORKSPACE_CLEANABLE',
|
||||
IsCalendarEnabled = 'IS_CALENDAR_ENABLED',
|
||||
IsMessagingEnabled = 'IS_MESSAGING_ENABLED',
|
||||
IsNewRecordBoardEnabled = 'IS_NEW_RECORD_BOARD_ENABLED',
|
||||
IsWorkspaceCleanable = 'IS_WORKSPACE_CLEANABLE',
|
||||
}
|
||||
|
||||
@Entity({ name: 'featureFlag', schema: 'core' })
|
||||
|
||||
@ -15,6 +15,11 @@ export const seedFeatureFlags = async (
|
||||
.into(`${schemaName}.${tableName}`, ['key', 'workspaceId', 'value'])
|
||||
.orIgnore()
|
||||
.values([
|
||||
{
|
||||
key: FeatureFlagKeys.IsCalendarEnabled,
|
||||
workspaceId: workspaceId,
|
||||
value: true,
|
||||
},
|
||||
{
|
||||
key: FeatureFlagKeys.IsMessagingEnabled,
|
||||
workspaceId: workspaceId,
|
||||
|
||||
Reference in New Issue
Block a user