Skip to content

Commit

Permalink
chore(web): Information Architecture Feedback (#5591)
Browse files Browse the repository at this point in the history
* feat: Add spacing between sidebar items

* fix: Label change per Sok
  • Loading branch information
antonjoel82 authored and denis-kralj-novu committed May 24, 2024
1 parent 1eff211 commit 823ec35
Show file tree
Hide file tree
Showing 13 changed files with 93 additions and 86 deletions.
12 changes: 9 additions & 3 deletions apps/web/src/AppRoutes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import { GetStarted } from './pages/quick-start/steps/GetStarted';
import { InAppSuccess } from './pages/quick-start/steps/InAppSuccess';
import { NotificationCenter } from './pages/quick-start/steps/NotificationCenter';
import { Setup } from './pages/quick-start/steps/Setup';
import { ApiKeysPage, WebhookPage } from './pages/settings/index';
import SubscribersList from './pages/subscribers/SubscribersListPage';
import { ChannelPreview } from './pages/templates/components/ChannelPreview';
import { ChannelStepEditor } from './pages/templates/components/ChannelStepEditor';
Expand Down Expand Up @@ -125,9 +126,14 @@ export const AppRoutes = () => {
<Route path="layouts" element={<LayoutsListPage />} />
</Route>
) : (
<Route path={ROUTES.LAYOUT} element={<LayoutsPage />}>
<Route path="" element={<LayoutsListPage />} />
</Route>
<>
<Route path={ROUTES.LAYOUT} element={<LayoutsPage />}>
<Route path="" element={<LayoutsListPage />} />
</Route>
{/* routes previously under settings */}
<Route path={ROUTES.API_KEYS} element={<ApiKeysPage />} />
<Route path={ROUTES.WEBHOOK} element={<WebhookPage />} />
</>
)}
<Route path="/translations/*" element={<TranslationRoutes />} />
<Route path={ROUTES.ANY} element={<HomePage />} />
Expand Down
5 changes: 1 addition & 4 deletions apps/web/src/SettingsRoutes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,12 @@ import { useFeatureFlag } from './hooks';
import { BillingRoutes } from './pages/BillingPages';
import { BrandingPage } from './pages/brand/tabs/v2';
import { MembersInvitePage as MembersInvitePageNew } from './pages/invites/v2/MembersInvitePage';
import { AccessSecurityPage, ApiKeysPage, BillingPage, TeamPage, UserProfilePage } from './pages/settings';
import { AccessSecurityPage, BillingPage, TeamPage, UserProfilePage } from './pages/settings';
import { SettingsPage as SettingsPageOld } from './pages/settings/SettingsPage';
import { SettingsPageNew as SettingsPage } from './pages/settings/SettingsPageNew';
import { ApiKeysCard } from './pages/settings/tabs';
import { EmailSettings } from './pages/settings/tabs/EmailSettings';
import { OrganizationPage } from './pages/settings/organization';
import { WebhookPage } from './pages/settings/WebhookPage';

/** Note: using a hook is the only way to separate routes */
export const useSettingsRoutes = () => {
Expand All @@ -24,12 +23,10 @@ export const useSettingsRoutes = () => {
return (
<Route path={ROUTES.SETTINGS} element={<SettingsPage />}>
<Route path="" element={<Navigate to={ROUTES.PROFILE} replace />} />
<Route path={ROUTES.API_KEYS} element={<ApiKeysPage />} />
<Route path={ROUTES.BRAND_SETTINGS} element={<BrandingPage />} />
<Route path={ROUTES.ORGANIZATION} element={<OrganizationPage />} />
<Route path={ROUTES.TEAM_SETTINGS} element={<TeamPage />} />
<Route path={`${ROUTES.BILLING}/*`} element={<BillingPage />} />
<Route path={ROUTES.WEBHOOK} element={<WebhookPage />} />
<Route path={ROUTES.SECURITY} element={<AccessSecurityPage />} />
<Route path={`${ROUTES.SETTINGS}`} element={<Navigate to={ROUTES.PROFILE} replace />} />
<Route path="permissions" element={<Navigate to={ROUTES.SECURITY} replace />} />
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/components/layout/components/SideNav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ export function SideNav({}: Props) {
testId: 'side-nav-brand-link',
},
{ icon: <Activity />, link: ROUTES.ACTIVITIES, label: 'Activity Feed', testId: 'side-nav-activities-link' },
{ icon: <Box />, link: ROUTES.INTEGRATIONS, label: 'Integrations Store', testId: 'side-nav-integrations-link' },
{ icon: <Box />, link: ROUTES.INTEGRATIONS, label: 'Integration Store', testId: 'side-nav-integrations-link' },
{
label: 'Team Members',
condition: !isInformationArchitectureEnabled,
Expand Down
44 changes: 21 additions & 23 deletions apps/web/src/components/layout/components/v2/HeaderNav.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { ActionIcon, Group, Header } from '@mantine/core';
import { ActionIcon, Header } from '@mantine/core';

import { colors, IconHelpOutline, Tooltip } from '@novu/design-system';
import { IconHelpOutline, Tooltip } from '@novu/design-system';
import { IS_DOCKER_HOSTED } from '../../../../config';
import { useBootIntercom } from '../../../../hooks';
import useThemeChange from '../../../../hooks/useThemeChange';
import { discordInviteUrl } from '../../../../pages/quick-start/consts';
import { css } from '@novu/novui/css';
import { HStack } from '@novu/novui/jsx';
import { useAuthContext } from '../../../providers/AuthProvider';
import { HEADER_NAV_HEIGHT } from '../../constants';
import { NotificationCenterWidget } from '../NotificationCenterWidget';
Expand All @@ -31,29 +32,26 @@ export function HeaderNav() {
})}
>
{/* TODO: Change position: right to space-between for breadcrumbs */}
<Group position="right" noWrap align="center">
<Group spacing={16}>
<NotificationCenterWidget user={currentUser} />

<ActionIcon variant="transparent" onClick={() => toggleColorScheme()}>
<Tooltip label={themeLabel}>
<div>{themeIcon}</div>
</Tooltip>
</ActionIcon>
{isSelfHosted ? (
<a href={discordInviteUrl} target="_blank" rel="noopener noreferrer">
<ActionIcon variant="transparent">
<IconHelpOutline />
</ActionIcon>
</a>
) : (
<ActionIcon variant="transparent" id="intercom-launcher">
<HStack flexWrap={'nowrap'} justifyContent="flex-end" gap={'100'}>
<ActionIcon variant="transparent" onClick={() => toggleColorScheme()}>
<Tooltip label={themeLabel}>
<div>{themeIcon}</div>
</Tooltip>
</ActionIcon>
<NotificationCenterWidget user={currentUser} />
{isSelfHosted ? (
<a href={discordInviteUrl} target="_blank" rel="noopener noreferrer">
<ActionIcon variant="transparent">
<IconHelpOutline />
</ActionIcon>
)}
<HeaderMenuItems />
</Group>
</Group>
</a>
) : (
<ActionIcon variant="transparent" id="intercom-launcher">
<IconHelpOutline />
</ActionIcon>
)}
<HeaderMenuItems />
</HStack>
</Header>
);
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Popover } from '@mantine/core';
import { Popover, PopoverProps } from '@mantine/core';
import { IconClose } from '@novu/design-system';
import { MouseEventHandler, PropsWithChildren } from 'react';
import { popoverArrowStyle, popoverDropdownStyle, closeButtonStyles, linkStyles } from './EnvironmentPopover.styles';
Expand All @@ -7,13 +7,15 @@ interface IEnvironmentPopoverProps {
handlePopoverLinkClick: MouseEventHandler;
isPopoverOpened: boolean;
setIsPopoverOpened: (newVal: boolean) => void;
position?: PopoverProps['position'];
}

export const EnvironmentPopover: React.FC<PropsWithChildren<IEnvironmentPopoverProps>> = ({
children,
isPopoverOpened,
setIsPopoverOpened,
handlePopoverLinkClick,
position = 'right',
}) => {
return (
<Popover
Expand All @@ -27,7 +29,7 @@ export const EnvironmentPopover: React.FC<PropsWithChildren<IEnvironmentPopoverP
withinPortal={true}
transition="rotate-left"
transitionDuration={250}
position="right"
position={position}
radius="md"
>
<Popover.Target>{children}</Popover.Target>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { type IIconProps, IconConstruction, IconRocketLaunch } from '@novu/desig
import { useEnvController, ROUTES, BaseEnvironmentEnum } from '@novu/shared-web';
import { useState } from 'react';
import { type ISelectProps } from '@novu/design-system';
import { useLocation, useNavigate } from 'react-router-dom';
import { matchPath, useLocation, useMatch, useNavigate } from 'react-router-dom';

const ENVIRONMENT_ICON_LOOKUP: Record<BaseEnvironmentEnum, React.ReactElement<IIconProps>> = {
[BaseEnvironmentEnum.DEVELOPMENT]: <IconConstruction />,
Expand All @@ -11,9 +11,7 @@ const ENVIRONMENT_ICON_LOOKUP: Record<BaseEnvironmentEnum, React.ReactElement<II

export const useEnvironmentSelect = () => {
const [isPopoverOpened, setIsPopoverOpened] = useState<boolean>(false);

const location = useLocation();
const navigate = useNavigate();

const { setEnvironment, isLoading, environment, readonly } = useEnvController({
onSuccess: (newEnvironment) => {
Expand All @@ -34,11 +32,11 @@ export const useEnvironmentSelect = () => {

/*
* this navigates users to the "base" page of the application to avoid sub-pages opened with data from other
* environments.
* environments -- unless the path itself is based on a specific environment (e.g. API Keys)
*/
const urlParts = location.pathname.replace('/', '').split('/');
const baseRoute = urlParts[0];
await setEnvironment(value as BaseEnvironmentEnum, { route: baseRoute });
const redirectRoute: string | undefined = checkIfEnvBasedRoute() ? undefined : urlParts[0];
await setEnvironment(value as BaseEnvironmentEnum, { route: redirectRoute });
};

return {
Expand All @@ -56,3 +54,8 @@ export const useEnvironmentSelect = () => {
handlePopoverLinkClick,
};
};

/** Determine if the current pathname is dependent on the current env */
function checkIfEnvBasedRoute() {
return [ROUTES.API_KEYS, ROUTES.WEBHOOK].some((route) => matchPath(route, window.location.pathname));
}
4 changes: 2 additions & 2 deletions apps/web/src/components/nav/NavMenuSection.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { FC } from 'react';
import { styled } from '@novu/novui/jsx';
import { styled, Stack } from '@novu/novui/jsx';
import { text } from '@novu/novui/recipes';
import { LocalizedMessage } from '@novu/shared-web';
import { css } from '@novu/novui/css';
Expand All @@ -18,7 +18,7 @@ export const NavMenuSection: FC<React.PropsWithChildren<INavMenuSectionProps>> =
{title}
</Title>
)}
{children}
<Stack gap="25">{children}</Stack>
</section>
);
};
29 changes: 27 additions & 2 deletions apps/web/src/components/nav/RootNavMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,17 @@ import {
IconCellTower,
IconDomain,
IconGroup,
IconKey,
IconOutlineMonitorHeart,
IconRoute,
IconSettings,
IconTaskAlt,
IconTranslate,
IconViewQuilt,
IconWebhook,
} from '@novu/design-system';
import { ChangesCountBadge } from '../layout/components/ChangesCountBadge';
import { ROUTES, useEnvController, useSegment } from '@novu/shared-web';
import { BaseEnvironmentEnum, ROUTES, useEnvController, useSegment } from '@novu/shared-web';
import { useUserOnboardingStatus } from '../../api/hooks/useUserOnboardingStatus';
import { EnvironmentSelect } from './EnvironmentSelect';
import { NavMenu } from './NavMenu';
Expand All @@ -22,11 +24,14 @@ import { OrganizationSelect } from './OrganizationSelect/v2/OrganizationSelect';
import { RootNavMenuFooter } from './RootNavMenuFooter';
import { VisibilityButton } from './VisibilityButton';
import { FreeTrialSidebarWidget } from '../layout/components/FreeTrialSidebarWidget';
import { parseUrl } from '../../utils/routeUtils';

const getEnvPageRoute = (route: ROUTES, env: BaseEnvironmentEnum) => parseUrl(route, { env });

export const RootNavMenu: React.FC = () => {
const segment = useSegment();
const { updateOnboardingStatus, showOnboarding, isLoading: isLoadingOnboardingStatus } = useUserOnboardingStatus();
const { readonly: isEnvReadonly } = useEnvController();
const { readonly: isEnvReadonly, environment } = useEnvController();

const handleHideOnboardingClick: React.MouseEventHandler = async () => {
segment.track('Click Hide Get Started Page - [Get Started]');
Expand Down Expand Up @@ -110,6 +115,26 @@ export const RootNavMenu: React.FC = () => {
link={ROUTES.TRANSLATIONS}
testId="side-nav-translations-link"
/>
<NavMenuLinkButton
label="API keys"
isVisible
icon={<IconKey />}
link={getEnvPageRoute(
ROUTES.API_KEYS,
(environment?.name as BaseEnvironmentEnum) ?? BaseEnvironmentEnum.DEVELOPMENT
)}
testId="side-nav-settings-api-keys"
></NavMenuLinkButton>
<NavMenuLinkButton
label="Inbound webhook"
isVisible
icon={<IconWebhook />}
link={getEnvPageRoute(
ROUTES.WEBHOOK,
(environment?.name as BaseEnvironmentEnum) ?? BaseEnvironmentEnum.DEVELOPMENT
)}
testId="side-nav-settings-inbound-webhook"
></NavMenuLinkButton>
</NavMenuSection>
<FreeTrialSidebarWidget />
<RootNavMenuFooter />
Expand Down
40 changes: 6 additions & 34 deletions apps/web/src/components/nav/SettingsNavMenu.tsx
Original file line number Diff line number Diff line change
@@ -1,30 +1,24 @@
import {
IconManageAccounts,
IconRoomPreferences,
IconAdminPanelSettings,
IconCreditCard,
IconGroup,
IconManageAccounts,
IconRoomPreferences,
IconWorkspacePremium,
IconCreditCard,
IconKey,
IconWebhook,
} from '@novu/design-system';
import { BaseEnvironmentEnum, ROUTES, useAuthContext, useEnvController } from '@novu/shared-web';
import { ROUTES, useAuthContext } from '@novu/shared-web';
import { useNavigate } from 'react-router-dom';
import { parseUrl } from '../../utils/routeUtils';
import { FreeTrialSidebarWidget } from '../layout/components/FreeTrialSidebarWidget';
import { NavMenu } from './NavMenu';
import { NavMenuLinkButton } from './NavMenuButton/NavMenuLinkButton';
import { NavMenuSection } from './NavMenuSection';

const getEnvSettingsRoute = (route: ROUTES, env: BaseEnvironmentEnum) => parseUrl(route, { env });

// TODO: Parentheses were not part of designs, but I believe it's much clearer this way
const getScopedTitle = (label: string, scope?: string) => `${label} ${`(${scope})` ?? ''}`;

export const SettingsNavMenu: React.FC = () => {
const navigate = useNavigate();
const { currentOrganization } = useAuthContext();
const { environment } = useEnvController();

const onBackButtonClick = () => {
navigate(ROUTES.HOME);
Expand Down Expand Up @@ -78,29 +72,8 @@ export const SettingsNavMenu: React.FC = () => {
testId="side-nav-settings-billing-link"
></NavMenuLinkButton>
</NavMenuSection>
<NavMenuSection title={getScopedTitle('Environment', environment?.name)}>
<NavMenuLinkButton
label="API keys"
isVisible
icon={<IconKey />}
link={getEnvSettingsRoute(
ROUTES.API_KEYS,
(environment?.name as BaseEnvironmentEnum) ?? BaseEnvironmentEnum.DEVELOPMENT
)}
testId="side-nav-settings-api-keys"
></NavMenuLinkButton>
<NavMenuLinkButton
label="Inbound webhook"
isVisible
icon={<IconWebhook />}
link={getEnvSettingsRoute(
ROUTES.WEBHOOK,
(environment?.name as BaseEnvironmentEnum) ?? BaseEnvironmentEnum.DEVELOPMENT
)}
testId="side-nav-settings-inbound-webhook"
></NavMenuLinkButton>
{/** TODO: we will reinstate the toggle buttons w/ different envs once we have APIs to support the pages */}
{/*
{/** TODO: we will reinstate the toggle buttons w/ different envs once we have APIs to support the pages */}
{/*
<NavMenuToggleButton
icon={<IconConstruction />}
label={'Development'}
Expand Down Expand Up @@ -141,7 +114,6 @@ export const SettingsNavMenu: React.FC = () => {
testId="side-nav-settings-inbound-webhook-production"
></NavMenuLinkButton>
</NavMenuToggleButton>*/}
</NavMenuSection>
<FreeTrialSidebarWidget />
</NavMenu>
);
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/pages/integrations/IntegrationsList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ export const IntegrationsList = ({

return (
<PageContainer title="Integrations">
<PageHeader title="Integrations Store" />
<PageHeader title="Integration Store" />
<When truthy={hasIntegrations}>
<Container fluid sx={{ padding: '0 24px 8px 30px' }}>
<IntegrationsListToolbar onAddProviderClick={onAddProviderClick} areIntegrationsLoading={isLoading} />
Expand Down
1 change: 1 addition & 0 deletions apps/web/src/pages/settings/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ export * from './user-profile-page';
export * from './TeamPage';
export * from './AccessSecurityPage';
export * from './BillingPage';
export * from './WebhookPage';
15 changes: 9 additions & 6 deletions apps/web/src/pages/settings/useSettingsEnvRedirect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,22 @@ const VALID_ENV_VALUES = new Set(Object.values(BaseEnvironmentEnum));
/** Redirect the user to a valid page if an invalid URL param is provided for env */
export const useSettingsEnvRedirect = () => {
const { env } = useParams<typeof URL_PARAM>();
const { environment } = useEnvController();
const { environment, isLoading } = useEnvController();
const navigate = useNavigate();

useEffect(() => {
if (VALID_ENV_VALUES.has(env as BaseEnvironmentEnum)) {
// don't redirect away until we've loaded the environment and checked it
if (isLoading) {
return;
}

const curPathname = window.location.pathname;
const currentEnvName = environment?.name ?? DEFAULT_ENV_NAME;
const redirectPath = !env
? `${window.location.pathname}/${currentEnvName}`
: window.location.pathname.replace(`/${env}`, `/${currentEnvName}`);

const redirectPath = !env || !VALID_ENV_VALUES.has(env as BaseEnvironmentEnum)
? `${curPathname}/${currentEnvName}`
: curPathname.replace(`/${env}`, `/${currentEnvName}`);

navigate(redirectPath);
}, [env, environment, navigate]);
}, [env, environment, navigate, isLoading]);
};
Loading

0 comments on commit 823ec35

Please sign in to comment.