Skip to content

Commit 5cfd3a8

Browse files
add lock icon to main navigation (#2074)
1 parent 9ea36a4 commit 5cfd3a8

3 files changed

Lines changed: 69 additions & 7 deletions

File tree

web/src/shared/components/Navigation/Navigation.tsx

Lines changed: 53 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,38 @@
1-
import { useState } from 'react';
1+
import { useMemo, useState } from 'react';
22
import { m } from '../../../paraglide/messages';
3-
import { Icon } from '../../defguard-ui/components/Icon';
3+
import { Icon, IconKind } from '../../defguard-ui/components/Icon';
44
import type { IconKindValue } from '../../defguard-ui/components/Icon/icon-types';
55
import { IconButton } from '../../defguard-ui/components/IconButton/IconButton';
66
import { useApp } from '../../hooks/useApp';
77
import { useAuth } from '../../hooks/useAuth';
88
import { NavLogo } from './assets/NavLogo';
99
import './style.scss';
10+
import { useQuery } from '@tanstack/react-query';
1011
import { Link, type LinkProps } from '@tanstack/react-router';
12+
import { type LicenseInfo, LicenseTier, type LicenseTierValue } from '../../api/types';
1113
import { Fold } from '../../defguard-ui/components/Fold/Fold';
14+
import { TooltipContent } from '../../defguard-ui/providers/tooltip/TooltipContent';
15+
import { TooltipProvider } from '../../defguard-ui/providers/tooltip/TooltipContext';
16+
import { TooltipTrigger } from '../../defguard-ui/providers/tooltip/TooltipTrigger';
17+
import { isPresent } from '../../defguard-ui/utils/isPresent';
1218
import { useTheme } from '../../hooks/theme/useTheme';
19+
import { getLicenseInfoQueryOptions } from '../../query';
20+
import { canUseBusinessFeature } from '../../utils/license';
1321

1422
interface NavGroupProps {
1523
id: string;
1624
label: string;
1725
items: NavItemProps[];
26+
licenseInfo?: LicenseInfo | null;
1827
}
1928

2029
interface NavItemProps {
2130
id: string;
2231
label: string;
2332
icon: IconKindValue;
2433
link: LinkProps['to'];
34+
licenseTier?: LicenseTierValue;
35+
license?: LicenseInfo | null;
2536
testId?: string;
2637
}
2738

@@ -72,18 +83,21 @@ const navigationConfig: NavGroupProps[] = [
7283
icon: 'rules',
7384
label: m.cmp_nav_item_rules(),
7485
link: '/acl/rules',
86+
licenseTier: LicenseTier.Business,
7587
},
7688
{
7789
id: 'destinations',
7890
icon: 'gateway',
7991
label: m.cmp_nav_item_destinations(),
8092
link: '/acl/destinations',
93+
licenseTier: LicenseTier.Business,
8194
},
8295
{
8396
id: 'aliases',
8497
icon: 'access-settings',
8598
label: m.cmp_nav_item_aliases(),
8699
link: '/acl/aliases',
100+
licenseTier: LicenseTier.Business,
87101
},
88102
],
89103
},
@@ -141,6 +155,11 @@ export const Navigation = () => {
141155
const isAdmin = useAuth((s) => s.isAdmin);
142156
const isOpen = useApp((s) => s.navigationOpen);
143157

158+
const { data: licenseInfo } = useQuery({
159+
...getLicenseInfoQueryOptions,
160+
enabled: isAdmin,
161+
});
162+
144163
if (!isAdmin || !isOpen) return null;
145164
return (
146165
<div className="navigation">
@@ -159,7 +178,7 @@ export const Navigation = () => {
159178
</div>
160179
<div className="groups">
161180
{navigationConfig.map((group) => (
162-
<NavGroup key={group.id} {...group} />
181+
<NavGroup key={group.id} {...group} licenseInfo={licenseInfo} />
163182
))}
164183
</div>
165184
<div className="bottom">
@@ -169,7 +188,7 @@ export const Navigation = () => {
169188
);
170189
};
171190

172-
const NavGroup = ({ items, label }: NavGroupProps) => {
191+
const NavGroup = ({ items, label, licenseInfo }: NavGroupProps) => {
173192
const [isOpen, setIsOpen] = useState(true);
174193
return (
175194
<div className="nav-group">
@@ -185,19 +204,47 @@ const NavGroup = ({ items, label }: NavGroupProps) => {
185204
<Fold open={isOpen}>
186205
<div className="items">
187206
{items.map((item) => (
188-
<NavItem key={item.id} {...item} />
207+
<NavItem key={item.id} {...item} license={licenseInfo} />
189208
))}
190209
</div>
191210
</Fold>
192211
</div>
193212
);
194213
};
195214

196-
const NavItem = ({ icon, link, label, testId }: NavItemProps) => {
215+
const NavItem = ({ icon, link, label, testId, license, licenseTier }: NavItemProps) => {
216+
const showLock = useMemo(() => {
217+
if (licenseTier === undefined) {
218+
return isPresent(licenseTier);
219+
}
220+
221+
if (licenseTier !== undefined && licenseTier === LicenseTier.Business) {
222+
return !canUseBusinessFeature(license as LicenseInfo | null).result;
223+
}
224+
225+
if (licenseTier !== undefined && licenseTier === LicenseTier.Enterprise) {
226+
return !canUseBusinessFeature(license as LicenseInfo | null).result;
227+
}
228+
229+
return false;
230+
}, [license, licenseTier]);
231+
197232
return (
198233
<Link to={link} className="nav-item" data-testid={testId}>
199234
<Icon icon={icon} />
200235
<span>{label}</span>
236+
{showLock && isPresent(licenseTier) && (
237+
<div className="right">
238+
<TooltipProvider>
239+
<TooltipTrigger>
240+
<Icon icon={IconKind.LockClosed} size={16} />
241+
</TooltipTrigger>
242+
<TooltipContent>
243+
<p>{`This is ${licenseTier ?? 'Unknown tier'} feature`}</p>
244+
</TooltipContent>
245+
</TooltipProvider>
246+
</div>
247+
)}
201248
</Link>
202249
);
203250
};

web/src/shared/components/Navigation/style.scss

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,4 +125,12 @@
125125
font: var(--t-body-sm-500);
126126
color: inherit;
127127
}
128+
129+
.right {
130+
margin-left: auto;
131+
display: flex;
132+
flex-flow: column;
133+
align-items: center;
134+
justify-content: center;
135+
}
128136
}

web/src/shared/components/PageTopBar/components/TopBarLicenseExpiration/TopBarLicenseExpiration.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,15 @@ const Content = () => {
4848
const variant = useMemo((): MessageVariant => {
4949
if (!isPresent(license) || license.valid_until === null || daysToEnd === null)
5050
return 'safe';
51+
if (license.subscription) {
52+
if (isGracePeriod) {
53+
return 'critical';
54+
}
55+
if (!license.expired) {
56+
return 'safe';
57+
}
58+
}
5159
if (license.expired) return 'expired';
52-
if (isGracePeriod) return 'critical';
5360
if (daysToEnd > 14) return 'safe';
5461
if (daysToEnd <= 14 && daysToEnd > 7) return 'warning';
5562
if (daysToEnd <= 7) return 'critical';

0 commit comments

Comments
 (0)