feat: add custom additional drawer link (#1586)

This commit is contained in:
Ali BARIN
2024-02-08 16:33:12 +01:00
committed by GitHub
parent 6bba2c82fe
commit 24451892ff
8 changed files with 134 additions and 44 deletions

View File

@@ -18,7 +18,9 @@ const port = process.env.PORT || '3000';
const serveWebAppSeparately =
process.env.SERVE_WEB_APP_SEPARATELY === 'true' ? true : false;
let apiUrl = new URL(process.env.API_URL || `${protocol}://${host}:${port}`).toString();
let apiUrl = new URL(
process.env.API_URL || `${protocol}://${host}:${port}`
).toString();
apiUrl = apiUrl.substring(0, apiUrl.length - 1);
// use apiUrl by default, which has less priority over the following cases
@@ -90,6 +92,8 @@ const appConfig = {
CI: process.env.CI === 'true',
disableNotificationsPage: process.env.DISABLE_NOTIFICATIONS_PAGE === 'true',
disableFavicon: process.env.DISABLE_FAVICON === 'true',
additionalDrawerLink: process.env.ADDITIONAL_DRAWER_LINK,
additionalDrawerLinkText: process.env.ADDITIONAL_DRAWER_LINK_TEXT,
};
if (!appConfig.encryptionKey) {

View File

@@ -8,6 +8,8 @@ const getConfig = async (_parent, params) => {
const defaultConfig = {
disableNotificationsPage: appConfig.disableNotificationsPage,
disableFavicon: appConfig.disableFavicon,
additionalDrawerLink: appConfig.additionalDrawerLink,
additionalDrawerLinkText: appConfig.additionalDrawerLinkText,
};
const configQuery = Config.query();

View File

@@ -59,6 +59,8 @@ describe('graphQL getConfig query', () => {
[configThree.key]: configThree.value.data,
disableNotificationsPage: false,
disableFavicon: false,
additionalDrawerLink: undefined,
additionalDrawerLinkText: undefined,
},
},
};
@@ -87,6 +89,8 @@ describe('graphQL getConfig query', () => {
[configTwo.key]: configTwo.value.data,
disableNotificationsPage: false,
disableFavicon: false,
additionalDrawerLink: undefined,
additionalDrawerLinkText: undefined,
},
},
};
@@ -101,6 +105,12 @@ describe('graphQL getConfig query', () => {
true
);
vi.spyOn(appConfig, 'disableFavicon', 'get').mockReturnValue(true);
vi.spyOn(appConfig, 'additionalDrawerLink', 'get').mockReturnValue(
'https://automatisch.io'
);
vi.spyOn(appConfig, 'additionalDrawerLinkText', 'get').mockReturnValue(
'Automatisch'
);
});
it('should return custom config', async () => {
@@ -117,6 +127,8 @@ describe('graphQL getConfig query', () => {
[configThree.key]: configThree.value.data,
disableNotificationsPage: true,
disableFavicon: true,
additionalDrawerLink: 'https://automatisch.io',
additionalDrawerLinkText: 'Automatisch',
},
},
};

View File

@@ -15,6 +15,7 @@ import { SvgIconComponent } from '@mui/icons-material';
import AppBar from 'components/AppBar';
import Drawer from 'components/Drawer';
import * as URLS from 'config/urls';
import useFormatMessage from 'hooks/useFormatMessage';
import useCurrentUserAbility from 'hooks/useCurrentUserAbility';
type SettingsLayoutProps = {
@@ -86,19 +87,11 @@ function createDrawerLinks({
return items;
}
const drawerBottomLinks = [
{
Icon: ArrowBackIosNewIcon,
primary: 'adminSettingsDrawer.goBack',
to: '/',
dataTest: 'go-back-drawer-link',
},
];
export default function SettingsLayout({
children,
}: SettingsLayoutProps): React.ReactElement {
const theme = useTheme();
const formatMessage = useFormatMessage();
const currentUserAbility = useCurrentUserAbility();
const matchSmallScreens = useMediaQuery(theme.breakpoints.down('lg'));
const [isDrawerOpen, setDrawerOpen] = React.useState(!matchSmallScreens);
@@ -116,6 +109,15 @@ export default function SettingsLayout({
canUpdateApp: currentUserAbility.can('update', 'App'),
});
const drawerBottomLinks = [
{
Icon: ArrowBackIosNewIcon,
primary: formatMessage('adminSettingsDrawer.goBack'),
to: '/',
dataTest: 'go-back-drawer-link',
},
];
return (
<>
<AppBar

View File

@@ -19,6 +19,7 @@ type DrawerLink = {
Icon: React.ElementType;
primary: string;
to: string;
target?: '_blank';
badgeContent?: React.ReactNode;
dataTest?: string;
};
@@ -69,7 +70,7 @@ export default function Drawer(props: DrawerProps): React.ReactElement {
<List sx={{ py: 0, mt: 3 }}>
{bottomLinks.map(
({ Icon, badgeContent, primary, to, dataTest }, index) => (
({ Icon, badgeContent, primary, to, dataTest, target }, index) => (
<ListItemLink
key={`${to}-${index}`}
icon={
@@ -77,9 +78,10 @@ export default function Drawer(props: DrawerProps): React.ReactElement {
<Icon htmlColor={theme.palette.primary.main} />
</Badge>
}
primary={formatMessage(primary)}
primary={primary}
to={to}
onClick={closeOnClick}
target={target}
data-test={dataTest}
/>
)

View File

@@ -7,8 +7,10 @@ import AppsIcon from '@mui/icons-material/Apps';
import SwapCallsIcon from '@mui/icons-material/SwapCalls';
import HistoryIcon from '@mui/icons-material/History';
import NotificationsIcon from '@mui/icons-material/Notifications';
import ArrowBackIosNew from '@mui/icons-material/ArrowBackIosNew';
import * as URLS from 'config/urls';
import useFormatMessage from 'hooks/useFormatMessage';
import useVersion from 'hooks/useVersion';
import AppBar from 'components/AppBar';
import Drawer from 'components/Drawer';
@@ -41,46 +43,93 @@ const drawerLinks = [
type GenerateDrawerBottomLinksOptions = {
disableNotificationsPage: boolean;
loading: boolean;
notificationBadgeContent: number;
additionalDrawerLink?: string;
additionalDrawerLinkText?: string;
additionalDrawerLinkIcon?: string;
formatMessage: ReturnType<typeof useFormatMessage>;
};
const generateDrawerBottomLinks = ({
const generateDrawerBottomLinks = async ({
disableNotificationsPage,
loading,
notificationBadgeContent = 0,
additionalDrawerLink,
additionalDrawerLinkText,
formatMessage,
}: GenerateDrawerBottomLinksOptions) => {
if (loading || disableNotificationsPage) {
return [];
const notificationsPageLinkObject = {
Icon: NotificationsIcon,
primary: formatMessage('settingsDrawer.notifications'),
to: URLS.UPDATES,
badgeContent: notificationBadgeContent,
};
const hasAdditionalDrawerLink =
additionalDrawerLink && additionalDrawerLinkText;
const additionalDrawerLinkObject = {
Icon: ArrowBackIosNew,
primary: additionalDrawerLinkText || '',
to: additionalDrawerLink || '',
target: '_blank' as const,
};
const links = [];
if (!disableNotificationsPage) {
links.push(notificationsPageLinkObject);
}
return [
{
Icon: NotificationsIcon,
primary: 'settingsDrawer.notifications',
to: URLS.UPDATES,
badgeContent: notificationBadgeContent,
},
];
if (hasAdditionalDrawerLink) {
links.push(additionalDrawerLinkObject);
}
return links;
};
type Link = {
Icon: React.ElementType;
primary: string;
target?: '_blank';
to: string;
badgeContent?: React.ReactNode;
};
export default function PublicLayout({
children,
}: PublicLayoutProps): React.ReactElement {
const version = useVersion();
const { config, loading } = useConfig(['disableNotificationsPage']);
const { config, loading } = useConfig([
'disableNotificationsPage',
'additionalDrawerLink',
'additionalDrawerLinkText',
]);
const theme = useTheme();
const formatMessage = useFormatMessage();
const [bottomLinks, setBottomLinks] = React.useState<Link[]>([]);
const matchSmallScreens = useMediaQuery(theme.breakpoints.down('lg'));
const [isDrawerOpen, setDrawerOpen] = React.useState(!matchSmallScreens);
const openDrawer = () => setDrawerOpen(true);
const closeDrawer = () => setDrawerOpen(false);
const drawerBottomLinks = generateDrawerBottomLinks({
notificationBadgeContent: version.newVersionCount,
loading,
disableNotificationsPage: config?.disableNotificationsPage as boolean,
});
React.useEffect(() => {
async function perform() {
const newBottomLinks = await generateDrawerBottomLinks({
notificationBadgeContent: version.newVersionCount,
disableNotificationsPage: config?.disableNotificationsPage as boolean,
additionalDrawerLink: config?.additionalDrawerLink as string,
additionalDrawerLinkText: config?.additionalDrawerLinkText as string,
formatMessage,
});
setBottomLinks(newBottomLinks);
}
if (loading) return;
perform();
}, [config, loading, version.newVersionCount]);
return (
<>
@@ -93,7 +142,7 @@ export default function PublicLayout({
<Box sx={{ display: 'flex' }}>
<Drawer
links={drawerLinks}
bottomLinks={drawerBottomLinks}
bottomLinks={bottomLinks}
open={isDrawerOpen}
onOpen={openDrawer}
onClose={closeDrawer}

View File

@@ -9,6 +9,7 @@ type ListItemLinkProps = {
icon: React.ReactNode;
primary: string;
to: string;
target?: '_blank';
onClick?: (event: React.SyntheticEvent) => void;
'data-test'?: string;
};
@@ -16,14 +17,29 @@ type ListItemLinkProps = {
export default function ListItemLink(
props: ListItemLinkProps
): React.ReactElement {
const { icon, primary, to, onClick, 'data-test': dataTest } = props;
const { icon, primary, to, onClick, 'data-test': dataTest, target } = props;
const selected = useMatch({ path: to, end: true });
const CustomLink = React.useMemo(
() =>
React.forwardRef<HTMLAnchorElement, Omit<LinkProps, 'to'>>(
function InLineLink(linkProps, ref) {
return <Link ref={ref} to={to} {...linkProps} />;
try {
// challenge the link to check if it's absolute URL
new URL(to); // should throw an error if it's not an absolute URL
return (
<a
{...linkProps}
ref={ref}
href={to}
target={target}
rel="noopener noreferrer"
/>
);
} catch {
return <Link ref={ref} {...linkProps} to={to} />;
}
}
),
[to]
@@ -37,6 +53,7 @@ export default function ListItemLink(
selected={!!selected}
onClick={onClick}
data-test={dataTest}
target={target}
>
<ListItemIcon sx={{ minWidth: 52 }}>{icon}</ListItemIcon>
<ListItemText

View File

@@ -9,6 +9,7 @@ import PaymentIcon from '@mui/icons-material/Payment';
import * as URLS from 'config/urls';
import useAutomatischInfo from 'hooks/useAutomatischInfo';
import useFormatMessage from 'hooks/useFormatMessage';
import AppBar from 'components/AppBar';
import Drawer from 'components/Drawer';
@@ -22,8 +23,8 @@ function createDrawerLinks({ isCloud }: { isCloud: boolean }) {
Icon: AccountCircleIcon,
primary: 'settingsDrawer.myProfile',
to: URLS.SETTINGS_PROFILE,
}
]
},
];
if (isCloud) {
items.push({
@@ -36,19 +37,12 @@ function createDrawerLinks({ isCloud }: { isCloud: boolean }) {
return items;
}
const drawerBottomLinks = [
{
Icon: ArrowBackIosNewIcon,
primary: 'settingsDrawer.goBack',
to: '/',
},
];
export default function SettingsLayout({
children,
}: SettingsLayoutProps): React.ReactElement {
const { isCloud } = useAutomatischInfo();
const theme = useTheme();
const formatMessage = useFormatMessage();
const matchSmallScreens = useMediaQuery(theme.breakpoints.down('lg'));
const [isDrawerOpen, setDrawerOpen] = React.useState(!matchSmallScreens);
@@ -56,6 +50,14 @@ export default function SettingsLayout({
const closeDrawer = () => setDrawerOpen(false);
const drawerLinks = createDrawerLinks({ isCloud });
const drawerBottomLinks = [
{
Icon: ArrowBackIosNewIcon,
primary: formatMessage('settingsDrawer.goBack'),
to: '/',
},
];
return (
<>
<AppBar