Files
twenty/packages/twenty-front/src/modules/settings/components/SettingsNavigationDrawerItems.tsx
Antoine Moreaux 0f0a7966b1 feat(sso): allow to use OIDC and SAML (#7246)
## What it does
### Backend
- [x] Add a mutation to create OIDC and SAML configuration
- [x] Add a mutation to delete an SSO config
- [x] Add a feature flag to toggle SSO
- [x] Add a mutation to activate/deactivate an SSO config
- [x] Add a mutation to delete an SSO config
- [x] Add strategy to use OIDC or SAML
- [ ] Improve error management

### Frontend
- [x] Add section "security" in settings
- [x] Add page to list SSO configurations
- [x] Add page and forms to create OIDC or SAML configuration
- [x] Add field to "connect with SSO" in the signin/signup process
- [x] Trigger auth when a user switch to a workspace with SSO enable
- [x] Add an option on the security page to activate/deactivate the
global invitation link
- [ ] Add new Icons for SSO Identity Providers (okta, Auth0, Azure,
Microsoft)

---------

Co-authored-by: Félix Malfait <felix@twenty.com>
Co-authored-by: Charles Bochet <charles@twenty.com>
2024-10-21 20:07:08 +02:00

247 lines
7.8 KiB
TypeScript

import { useRecoilValue } from 'recoil';
import {
IconApps,
IconAt,
IconCalendarEvent,
IconCode,
IconColorSwatch,
IconComponent,
IconCurrencyDollar,
IconDoorEnter,
IconFunction,
IconHierarchy2,
IconMail,
IconRocket,
IconSettings,
IconTool,
IconUserCircle,
IconUsers,
IconKey,
MAIN_COLORS,
} from 'twenty-ui';
import { useAuth } from '@/auth/hooks/useAuth';
import { billingState } from '@/client-config/states/billingState';
import { SettingsNavigationDrawerItem } from '@/settings/components/SettingsNavigationDrawerItem';
import { useExpandedHeightAnimation } from '@/settings/hooks/useExpandedHeightAnimation';
import { getSettingsPagePath } from '@/settings/utils/getSettingsPagePath';
import { SettingsPath } from '@/types/SettingsPath';
import {
NavigationDrawerItem,
NavigationDrawerItemIndentationLevel,
} from '@/ui/navigation/navigation-drawer/components/NavigationDrawerItem';
import { NavigationDrawerItemGroup } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerItemGroup';
import { NavigationDrawerSection } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerSection';
import { NavigationDrawerSectionTitle } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerSectionTitle';
import { isAdvancedModeEnabledState } from '@/ui/navigation/navigation-drawer/states/isAdvancedModeEnabledState';
import { getNavigationSubItemState } from '@/ui/navigation/navigation-drawer/utils/getNavigationSubItemState';
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
import styled from '@emotion/styled';
import { AnimatePresence, motion } from 'framer-motion';
import { matchPath, resolvePath, useLocation } from 'react-router-dom';
type SettingsNavigationItem = {
label: string;
path: SettingsPath;
Icon: IconComponent;
indentationLevel?: NavigationDrawerItemIndentationLevel;
matchSubPages?: boolean;
};
const StyledIconContainer = styled.div`
border-right: 1px solid ${MAIN_COLORS.yellow};
position: absolute;
left: ${({ theme }) => theme.spacing(-5)};
margin-top: ${({ theme }) => theme.spacing(2)};
height: 75%;
`;
const StyledDeveloperSection = styled.div`
display: flex;
width: 100%;
gap: ${({ theme }) => theme.spacing(1)};
position: relative;
`;
const StyledIconTool = styled(IconTool)`
margin-right: ${({ theme }) => theme.spacing(0.5)};
`;
export const SettingsNavigationDrawerItems = () => {
const isAdvancedModeEnabled = useRecoilValue(isAdvancedModeEnabledState);
const { contentRef, motionAnimationVariants } = useExpandedHeightAnimation(
isAdvancedModeEnabled,
);
const { signOut } = useAuth();
const billing = useRecoilValue(billingState);
const isFunctionSettingsEnabled = useIsFeatureEnabled(
'IS_FUNCTION_SETTINGS_ENABLED',
);
const isFreeAccessEnabled = useIsFeatureEnabled('IS_FREE_ACCESS_ENABLED');
const isCRMMigrationEnabled = useIsFeatureEnabled('IS_CRM_MIGRATION_ENABLED');
const isSSOEnabled = useIsFeatureEnabled('IS_SSO_ENABLED');
const isBillingPageEnabled =
billing?.isBillingEnabled && !isFreeAccessEnabled;
// TODO: Refactor this part to only have arrays of navigation items
const currentPathName = useLocation().pathname;
const accountSubSettings: SettingsNavigationItem[] = [
{
label: 'Emails',
path: SettingsPath.AccountsEmails,
Icon: IconMail,
indentationLevel: 2,
},
{
label: 'Calendars',
path: SettingsPath.AccountsCalendars,
Icon: IconCalendarEvent,
indentationLevel: 2,
},
];
const selectedIndex = accountSubSettings.findIndex((accountSubSetting) => {
const href = getSettingsPagePath(accountSubSetting.path);
const pathName = resolvePath(href).pathname;
return matchPath(
{
path: pathName,
end: accountSubSetting.matchSubPages === false,
},
currentPathName,
);
});
return (
<>
<NavigationDrawerSection>
<NavigationDrawerSectionTitle label="User" />
<SettingsNavigationDrawerItem
label="Profile"
path={SettingsPath.ProfilePage}
Icon={IconUserCircle}
/>
<SettingsNavigationDrawerItem
label="Experience"
path={SettingsPath.Appearance}
Icon={IconColorSwatch}
/>
<NavigationDrawerItemGroup>
<SettingsNavigationDrawerItem
label="Accounts"
path={SettingsPath.Accounts}
Icon={IconAt}
matchSubPages={false}
/>
{accountSubSettings.map((navigationItem, index) => (
<SettingsNavigationDrawerItem
key={index}
label={navigationItem.label}
path={navigationItem.path}
Icon={navigationItem.Icon}
indentationLevel={navigationItem.indentationLevel}
subItemState={getNavigationSubItemState({
arrayLength: accountSubSettings.length,
index,
selectedIndex,
})}
/>
))}
</NavigationDrawerItemGroup>
</NavigationDrawerSection>
<NavigationDrawerSection>
<NavigationDrawerSectionTitle label="Workspace" />
<SettingsNavigationDrawerItem
label="General"
path={SettingsPath.Workspace}
Icon={IconSettings}
/>
<SettingsNavigationDrawerItem
label="Members"
path={SettingsPath.WorkspaceMembersPage}
Icon={IconUsers}
/>
{isBillingPageEnabled && (
<SettingsNavigationDrawerItem
label="Billing"
path={SettingsPath.Billing}
Icon={IconCurrencyDollar}
/>
)}
<SettingsNavigationDrawerItem
label="Data model"
path={SettingsPath.Objects}
Icon={IconHierarchy2}
/>
<SettingsNavigationDrawerItem
label="Integrations"
path={SettingsPath.Integrations}
Icon={IconApps}
/>
{isCRMMigrationEnabled && (
<SettingsNavigationDrawerItem
label="CRM Migration"
path={SettingsPath.CRMMigration}
Icon={IconCode}
/>
)}
{isSSOEnabled && (
<SettingsNavigationDrawerItem
label="Security"
path={SettingsPath.Security}
Icon={IconKey}
/>
)}
</NavigationDrawerSection>
<AnimatePresence>
{isAdvancedModeEnabled && (
<motion.div
ref={contentRef}
initial="initial"
animate="animate"
exit="exit"
variants={motionAnimationVariants}
>
<StyledDeveloperSection>
<StyledIconContainer>
<StyledIconTool size={12} color={MAIN_COLORS.yellow} />
</StyledIconContainer>
<NavigationDrawerSection>
<NavigationDrawerSectionTitle label="Developers" />
<SettingsNavigationDrawerItem
label="API & Webhooks"
path={SettingsPath.Developers}
Icon={IconCode}
/>
{isFunctionSettingsEnabled && (
<SettingsNavigationDrawerItem
label="Functions"
path={SettingsPath.ServerlessFunctions}
Icon={IconFunction}
/>
)}
</NavigationDrawerSection>
</StyledDeveloperSection>
</motion.div>
)}
</AnimatePresence>
<NavigationDrawerSection>
<NavigationDrawerSectionTitle label="Other" />
<SettingsNavigationDrawerItem
label="Releases"
path={SettingsPath.Releases}
Icon={IconRocket}
/>
<NavigationDrawerItem
label="Logout"
onClick={signOut}
Icon={IconDoorEnter}
/>
</NavigationDrawerSection>
</>
);
};