Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore(web): Information Architecture Feedback #5591

Merged
merged 9 commits into from
May 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/web/cypress/tests/settings/api-keys.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ describe('API Keys Page', () => {
cy.intercept('GET', '/v1/environments/api-keys').as('getApiKeys');

cy.waitLoadEnv(() => {
cy.visit('/settings/api-keys/Development');
cy.visit('/api-keys/Development');
});
cy.wait('@getApiKeys');
});
Expand Down
2 changes: 1 addition & 1 deletion apps/web/cypress/tests/settings/inbound-webhook.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ const mockEnvs = ({ envName = 'Development', mxRecordConfigured = false, inbound
};

describe('Inbound Webhook Page', () => {
const TEST_URL = '/settings/webhook/Development';
const TEST_URL = '/webhook/Development';
const launchTestPage = () =>
cy.waitLoadEnv(() => {
cy.visit(TEST_URL);
Expand Down
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';
Loading
Loading