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:
Thaïs
2024-02-20 15:28:15 -03:00
committed by GitHub
parent 4552e98b7f
commit 11581ca9c3
14 changed files with 184 additions and 105 deletions

View File

@ -20,6 +20,7 @@ import { RecordIndexPage } from '~/pages/object-record/RecordIndexPage';
import { RecordShowPage } from '~/pages/object-record/RecordShowPage'; import { RecordShowPage } from '~/pages/object-record/RecordShowPage';
import { Opportunities } from '~/pages/opportunities/Opportunities'; import { Opportunities } from '~/pages/opportunities/Opportunities';
import { SettingsAccounts } from '~/pages/settings/accounts/SettingsAccounts'; import { SettingsAccounts } from '~/pages/settings/accounts/SettingsAccounts';
import { SettingsAccountsCalendars } from '~/pages/settings/accounts/SettingsAccountsCalendars';
import { SettingsAccountsEmails } from '~/pages/settings/accounts/SettingsAccountsEmails'; import { SettingsAccountsEmails } from '~/pages/settings/accounts/SettingsAccountsEmails';
import { SettingsAccountsEmailsInboxSettings } from '~/pages/settings/accounts/SettingsAccountsEmailsInboxSettings'; import { SettingsAccountsEmailsInboxSettings } from '~/pages/settings/accounts/SettingsAccountsEmailsInboxSettings';
import { SettingsNewAccount } from '~/pages/settings/accounts/SettingsNewAccount'; import { SettingsNewAccount } from '~/pages/settings/accounts/SettingsNewAccount';
@ -95,6 +96,10 @@ export const App = () => {
path={SettingsPath.NewAccount} path={SettingsPath.NewAccount}
element={<SettingsNewAccount />} element={<SettingsNewAccount />}
/> />
<Route
path={SettingsPath.AccountsCalendars}
element={<SettingsAccountsCalendars />}
/>
<Route <Route
path={SettingsPath.AccountsEmails} path={SettingsPath.AccountsEmails}
element={<SettingsAccountsEmails />} element={<SettingsAccountsEmails />}

View File

@ -2,9 +2,12 @@ import { useNavigate } from 'react-router-dom';
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { SettingsNavigationCard } from '@/settings/components/SettingsNavigationCard'; 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 { IconCalendarEvent, IconMailCog } from '@/ui/display/icon';
import { H2Title } from '@/ui/display/typography/components/H2Title'; import { H2Title } from '@/ui/display/typography/components/H2Title';
import { Section } from '@/ui/layout/section/components/Section'; import { Section } from '@/ui/layout/section/components/Section';
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
const StyledCardsContainer = styled.div` const StyledCardsContainer = styled.div`
display: flex; display: flex;
@ -12,12 +15,9 @@ const StyledCardsContainer = styled.div`
margin-top: ${({ theme }) => theme.spacing(6)}; margin-top: ${({ theme }) => theme.spacing(6)};
`; `;
const StyledSettingsNavigationCard = styled(SettingsNavigationCard)`
color: ${({ theme }) => theme.font.color.extraLight};
`;
export const SettingsAccountsSettingsSection = () => { export const SettingsAccountsSettingsSection = () => {
const navigate = useNavigate(); const navigate = useNavigate();
const isCalendarEnabled = useIsFeatureEnabled('IS_CALENDAR_ENABLED');
return ( return (
<Section> <Section>
@ -29,18 +29,22 @@ export const SettingsAccountsSettingsSection = () => {
<SettingsNavigationCard <SettingsNavigationCard
Icon={IconMailCog} Icon={IconMailCog}
title="Emails" title="Emails"
onClick={() => navigate('/settings/accounts/emails')} onClick={() =>
navigate(getSettingsPagePath(SettingsPath.AccountsEmails))
}
> >
Set email visibility, manage your blocklist and more. Set email visibility, manage your blocklist and more.
</SettingsNavigationCard> </SettingsNavigationCard>
<StyledSettingsNavigationCard <SettingsNavigationCard
Icon={IconCalendarEvent} Icon={IconCalendarEvent}
title="Calendar" title="Calendar"
disabled soon={!isCalendarEnabled}
hasSoonPill onClick={() =>
navigate(getSettingsPagePath(SettingsPath.AccountsCalendars))
}
> >
Configure and customize your calendar preferences. Configure and customize your calendar preferences.
</StyledSettingsNavigationCard> </SettingsNavigationCard>
</StyledCardsContainer> </StyledCardsContainer>
</Section> </Section>
); );

View File

@ -11,7 +11,7 @@ import { CardContent } from '@/ui/layout/card/components/CardContent';
type SettingsNavigationCardProps = { type SettingsNavigationCardProps = {
children: ReactNode; children: ReactNode;
disabled?: boolean; disabled?: boolean;
hasSoonPill?: boolean; soon?: boolean;
Icon: IconComponent; Icon: IconComponent;
onClick?: () => void; onClick?: () => void;
title: string; title: string;
@ -22,7 +22,8 @@ const StyledCard = styled(Card)<{
disabled?: boolean; disabled?: boolean;
onClick?: () => void; onClick?: () => void;
}>` }>`
color: ${({ theme }) => theme.font.color.tertiary}; color: ${({ disabled, theme }) =>
disabled ? theme.font.color.extraLight : theme.font.color.tertiary};
cursor: ${({ disabled, onClick }) => cursor: ${({ disabled, onClick }) =>
disabled ? 'not-allowed' : onClick ? 'pointer' : 'default'}; disabled ? 'not-allowed' : onClick ? 'pointer' : 'default'};
`; `;
@ -40,8 +41,9 @@ const StyledHeader = styled.div`
gap: ${({ theme }) => theme.spacing(3)}; gap: ${({ theme }) => theme.spacing(3)};
`; `;
const StyledTitle = styled.div` const StyledTitle = styled.div<{ disabled?: boolean }>`
color: ${({ theme }) => theme.font.color.secondary}; color: ${({ disabled, theme }) =>
disabled ? 'inherit' : theme.font.color.secondary};
display: flex; display: flex;
flex: 1 0 auto; flex: 1 0 auto;
font-weight: ${({ theme }) => theme.font.weight.medium}; font-weight: ${({ theme }) => theme.font.weight.medium};
@ -59,8 +61,8 @@ const StyledDescription = styled.div`
export const SettingsNavigationCard = ({ export const SettingsNavigationCard = ({
children, children,
disabled, soon,
hasSoonPill, disabled = soon,
Icon, Icon,
onClick, onClick,
title, title,
@ -69,13 +71,17 @@ export const SettingsNavigationCard = ({
const theme = useTheme(); const theme = useTheme();
return ( return (
<StyledCard disabled={disabled} onClick={onClick} className={className}> <StyledCard
disabled={disabled}
onClick={disabled ? undefined : onClick}
className={className}
>
<StyledCardContent> <StyledCardContent>
<StyledHeader> <StyledHeader>
<Icon size={theme.icon.size.lg} stroke={theme.icon.stroke.sm} /> <Icon size={theme.icon.size.lg} stroke={theme.icon.stroke.sm} />
<StyledTitle className={className}> <StyledTitle disabled={disabled}>
{title} {title}
{hasSoonPill && <SoonPill />} {soon && <SoonPill />}
</StyledTitle> </StyledTitle>
<StyledIconChevronRight size={theme.icon.size.sm} /> <StyledIconChevronRight size={theme.icon.size.sm} />
</StyledHeader> </StyledHeader>

View File

@ -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}
/>
);
};

View File

@ -1,8 +1,10 @@
import { useCallback } from 'react'; 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 { useAuth } from '@/auth/hooks/useAuth';
import { SettingsNavigationDrawerItem } from '@/settings/components/SettingsNavigationDrawerItem';
import { AppPath } from '@/types/AppPath'; import { AppPath } from '@/types/AppPath';
import { SettingsPath } from '@/types/SettingsPath';
import { import {
IconApps, IconApps,
IconAt, IconAt,
@ -31,67 +33,45 @@ export const SettingsNavigationDrawerItems = () => {
navigate(AppPath.SignIn); navigate(AppPath.SignIn);
}, [signOut, navigate]); }, [signOut, navigate]);
const isCalendarEnabled = useIsFeatureEnabled('IS_CALENDAR_ENABLED');
const isMessagingEnabled = useIsFeatureEnabled('IS_MESSAGING_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 ( return (
<> <>
<NavigationDrawerSection> <NavigationDrawerSection>
<NavigationDrawerSectionTitle label="User" /> <NavigationDrawerSectionTitle label="User" />
<NavigationDrawerItem <SettingsNavigationDrawerItem
label="Profile" label="Profile"
to="/settings/profile" path={SettingsPath.ProfilePage}
Icon={IconUserCircle} Icon={IconUserCircle}
active={
!!useMatch({
path: useResolvedPath('/settings/profile').pathname,
end: true,
})
}
/> />
<NavigationDrawerItem <SettingsNavigationDrawerItem
label="Appearance" label="Appearance"
to="/settings/profile/appearance" path={SettingsPath.Appearance}
Icon={IconColorSwatch} Icon={IconColorSwatch}
active={
!!useMatch({
path: useResolvedPath('/settings/profile/appearance').pathname,
end: true,
})
}
/> />
{isMessagingEnabled && ( {isMessagingEnabled && (
<NavigationDrawerItemGroup> <NavigationDrawerItemGroup>
<NavigationDrawerItem <SettingsNavigationDrawerItem
label="Accounts" label="Accounts"
to="/settings/accounts" path={SettingsPath.Accounts}
Icon={IconAt} Icon={IconAt}
active={isAccountsItemActive}
/> />
<NavigationDrawerItem <SettingsNavigationDrawerItem
level={2} level={2}
label="Emails" label="Emails"
to="/settings/accounts/emails" path={SettingsPath.AccountsEmails}
Icon={IconMail} Icon={IconMail}
active={isAccountsEmailsItemActive} matchSubPages
/> />
<NavigationDrawerItem <SettingsNavigationDrawerItem
level={2} level={2}
label="Calendars" label="Calendars"
path={SettingsPath.AccountsCalendars}
Icon={IconCalendarEvent} Icon={IconCalendarEvent}
soon matchSubPages
soon={!isCalendarEnabled}
/> />
</NavigationDrawerItemGroup> </NavigationDrawerItemGroup>
)} )}
@ -99,55 +79,31 @@ export const SettingsNavigationDrawerItems = () => {
<NavigationDrawerSection> <NavigationDrawerSection>
<NavigationDrawerSectionTitle label="Workspace" /> <NavigationDrawerSectionTitle label="Workspace" />
<NavigationDrawerItem <SettingsNavigationDrawerItem
label="General" label="General"
to="/settings/workspace" path={SettingsPath.Workspace}
Icon={IconSettings} Icon={IconSettings}
active={
!!useMatch({
path: useResolvedPath('/settings/workspace').pathname,
end: true,
})
}
/> />
<NavigationDrawerItem <SettingsNavigationDrawerItem
label="Members" label="Members"
to="/settings/workspace-members" path={SettingsPath.WorkspaceMembersPage}
Icon={IconUsers} Icon={IconUsers}
active={
!!useMatch({
path: useResolvedPath('/settings/workspace-members').pathname,
end: true,
})
}
/> />
<NavigationDrawerItem <SettingsNavigationDrawerItem
label="Data model" label="Data model"
to="/settings/objects" path={SettingsPath.Objects}
Icon={IconHierarchy2} Icon={IconHierarchy2}
active={ matchSubPages
!!useMatch({
path: useResolvedPath('/settings/objects').pathname,
end: false,
})
}
/> />
<NavigationDrawerItem <SettingsNavigationDrawerItem
label="Developers" label="Developers"
to="/settings/developers" path={SettingsPath.Developers}
Icon={IconRobot} Icon={IconRobot}
active={
!!useMatch({
path: useResolvedPath('/settings/developers').pathname,
end: true,
})
}
/> />
<NavigationDrawerItem <SettingsNavigationDrawerItem
label="Integrations" label="Integrations"
to="/settings/integrations" path={SettingsPath.Integrations}
Icon={IconApps} Icon={IconApps}
active={isIntegrationsItemActive}
/> />
</NavigationDrawerSection> </NavigationDrawerSection>

View File

@ -0,0 +1,4 @@
import { SettingsPath } from '@/types/SettingsPath';
export const getSettingsPagePath = <Path extends SettingsPath>(path: Path) =>
`/settings/${path}` as const;

View File

@ -3,6 +3,7 @@ export enum SettingsPath {
Appearance = 'profile/appearance', Appearance = 'profile/appearance',
Accounts = 'accounts', Accounts = 'accounts',
NewAccount = 'accounts/new', NewAccount = 'accounts/new',
AccountsCalendars = 'accounts/calendars',
AccountsEmails = 'accounts/emails', AccountsEmails = 'accounts/emails',
AccountsEmailsInboxSettings = 'accounts/emails/:accountUuid', AccountsEmailsInboxSettings = 'accounts/emails/:accountUuid',
Objects = 'objects', Objects = 'objects',
@ -14,7 +15,7 @@ export enum SettingsPath {
NewObject = 'objects/new', NewObject = 'objects/new',
WorkspaceMembersPage = 'workspace-members', WorkspaceMembersPage = 'workspace-members',
Workspace = 'workspace', Workspace = 'workspace',
Developers = '', Developers = 'developers',
DevelopersNewApiKey = 'api-keys/new', DevelopersNewApiKey = 'api-keys/new',
DevelopersApiKeyDetail = 'api-keys/:apiKeyId', DevelopersApiKeyDetail = 'api-keys/:apiKeyId',
Integrations = 'integrations', Integrations = 'integrations',

View File

@ -8,7 +8,7 @@ import { isNavigationDrawerOpenState } from '@/ui/navigation/states/isNavigation
import { MOBILE_VIEWPORT } from '@/ui/theme/constants/theme'; import { MOBILE_VIEWPORT } from '@/ui/theme/constants/theme';
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile'; import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
type NavigationDrawerItemProps = { export type NavigationDrawerItemProps = {
className?: string; className?: string;
label: string; label: string;
level?: 1 | 2; level?: 1 | 2;

View File

@ -1,6 +1,8 @@
import { Meta, StoryObj } from '@storybook/react'; import { Meta, StoryObj } from '@storybook/react';
import { Favorites } from '@/favorites/components/Favorites'; import { Favorites } from '@/favorites/components/Favorites';
import { getSettingsPagePath } from '@/settings/utils/getSettingsPagePath';
import { SettingsPath } from '@/types/SettingsPath';
import { import {
IconAt, IconAt,
IconBell, IconBell,
@ -91,32 +93,32 @@ export const Submenu: Story = {
<NavigationDrawerSectionTitle label="User" /> <NavigationDrawerSectionTitle label="User" />
<NavigationDrawerItem <NavigationDrawerItem
label="Profile" label="Profile"
to="/settings/profile" to={getSettingsPagePath(SettingsPath.ProfilePage)}
Icon={IconUserCircle} Icon={IconUserCircle}
active active
/> />
<NavigationDrawerItem <NavigationDrawerItem
label="Appearance" label="Appearance"
to="/settings/profile/appearance" to={getSettingsPagePath(SettingsPath.Appearance)}
Icon={IconColorSwatch} Icon={IconColorSwatch}
/> />
<NavigationDrawerItemGroup> <NavigationDrawerItemGroup>
<NavigationDrawerItem <NavigationDrawerItem
label="Accounts" label="Accounts"
to="/settings/accounts" to={getSettingsPagePath(SettingsPath.Accounts)}
Icon={IconAt} Icon={IconAt}
/> />
<NavigationDrawerItem <NavigationDrawerItem
level={2} level={2}
label="Emails" label="Emails"
to="/settings/accounts/emails" to={getSettingsPagePath(SettingsPath.AccountsEmails)}
Icon={IconMail} Icon={IconMail}
/> />
<NavigationDrawerItem <NavigationDrawerItem
level={2} level={2}
label="Calendar" label="Calendar"
to={getSettingsPagePath(SettingsPath.AccountsCalendars)}
Icon={IconCalendarEvent} Icon={IconCalendarEvent}
soon
/> />
</NavigationDrawerItemGroup> </NavigationDrawerItemGroup>
</NavigationDrawerSection> </NavigationDrawerSection>
@ -125,12 +127,12 @@ export const Submenu: Story = {
<NavigationDrawerSectionTitle label="Workspace" /> <NavigationDrawerSectionTitle label="Workspace" />
<NavigationDrawerItem <NavigationDrawerItem
label="General" label="General"
to="/settings/workspace" to={getSettingsPagePath(SettingsPath.Workspace)}
Icon={IconSettings} Icon={IconSettings}
/> />
<NavigationDrawerItem <NavigationDrawerItem
label="Members" label="Members"
to="/settings/workspace-members" to={getSettingsPagePath(SettingsPath.WorkspaceMembersPage)}
Icon={IconUsers} Icon={IconUsers}
/> />
</NavigationDrawerSection> </NavigationDrawerSection>

View File

@ -1,5 +1,6 @@
export type FeatureFlagKey = export type FeatureFlagKey =
| 'IS_MESSAGING_ENABLED'
| 'IS_BLOCKLIST_ENABLED' | 'IS_BLOCKLIST_ENABLED'
| 'IS_QUICK_ACTIONS_ENABLED' | 'IS_CALENDAR_ENABLED'
| 'IS_NEW_RECORD_BOARD_ENABLED'; | 'IS_MESSAGING_ENABLED'
| 'IS_NEW_RECORD_BOARD_ENABLED'
| 'IS_QUICK_ACTIONS_ENABLED';

View File

@ -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>
);

View File

@ -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 = {};

View File

@ -14,10 +14,11 @@ import { IDField } from '@ptc-org/nestjs-query-graphql';
import { Workspace } from 'src/core/workspace/workspace.entity'; import { Workspace } from 'src/core/workspace/workspace.entity';
export enum FeatureFlagKeys { export enum FeatureFlagKeys {
IsMessagingEnabled = 'IS_MESSAGING_ENABLED',
IsBlocklistEnabled = 'IS_BLOCKLIST_ENABLED', IsBlocklistEnabled = 'IS_BLOCKLIST_ENABLED',
IsWorkspaceCleanable = 'IS_WORKSPACE_CLEANABLE', IsCalendarEnabled = 'IS_CALENDAR_ENABLED',
IsMessagingEnabled = 'IS_MESSAGING_ENABLED',
IsNewRecordBoardEnabled = 'IS_NEW_RECORD_BOARD_ENABLED', IsNewRecordBoardEnabled = 'IS_NEW_RECORD_BOARD_ENABLED',
IsWorkspaceCleanable = 'IS_WORKSPACE_CLEANABLE',
} }
@Entity({ name: 'featureFlag', schema: 'core' }) @Entity({ name: 'featureFlag', schema: 'core' })

View File

@ -15,6 +15,11 @@ export const seedFeatureFlags = async (
.into(`${schemaName}.${tableName}`, ['key', 'workspaceId', 'value']) .into(`${schemaName}.${tableName}`, ['key', 'workspaceId', 'value'])
.orIgnore() .orIgnore()
.values([ .values([
{
key: FeatureFlagKeys.IsCalendarEnabled,
workspaceId: workspaceId,
value: true,
},
{ {
key: FeatureFlagKeys.IsMessagingEnabled, key: FeatureFlagKeys.IsMessagingEnabled,
workspaceId: workspaceId, workspaceId: workspaceId,