Skip to content

Commit dd9d55f

Browse files
authored
video tutorials modal (#2593)
* add tutorials button in the navbar * remove duplicate theme buttons * change naming convention * update json schema * add error log if parsing JSON fails * update source file validation * update video to page mapping * update tests * setup tutorials modal infrastructure * show basic tutorials modal * disable autoplay * add video icons * sections styling and fold * update video list styling & behavior * fix video section styling * adjust video corners * fix modal layout * update readme * adjust modal size to handle list scroll * cleanup * reorganize components * restore widget naming * move modal state * use a better key * improve parametrized route handling * clear selection on modal close * update ui submodule * update genereal description in readme * replace custom CSS with existing variables * simplify version resolution * minimize CSS changes * shorten css class names * adjust naming * remove unnecessary comments * formatting
1 parent 62695c3 commit dd9d55f

41 files changed

Lines changed: 1954 additions & 977 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

web/messages/en/components.json

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -176,9 +176,14 @@
176176
"acl_rule_error_permissions_required": "Configure at least one allowed user, group, or network device.",
177177
"acl_rule_error_manual_destination_required": "Manual destination settings are enabled. Provide a value or enable Any.",
178178
"cmp_video_support_launcher": "Video support",
179-
"cmp_video_support_list_label": "Video tutorials",
180-
"cmp_video_support_overlay_error": "Video unavailable",
181-
"cmp_video_support_overlay_watch_on_youtube": "Watch on YouTube:",
179+
"cmp_video_support_list_label": "Video support",
180+
"cmp_video_tutorials_overlay_error": "Video unavailable",
181+
"cmp_video_tutorials_overlay_watch_on_youtube": "Watch on YouTube:",
182+
"cmp_nav_item_tutorials": "Tutorials",
183+
"cmp_video_tutorials_modal_title": "Video tutorials",
184+
"cmp_video_tutorials_modal_search_placeholder": "Search",
185+
"cmp_video_tutorials_modal_go_to": "Go to {page}",
186+
"cmp_video_tutorials_modal_learn_more": "Learn more in Documentation",
182187
"cmp_helper_overview_period_select": "",
183188
"cmp_helper_search": ""
184189
}

web/src/routes/_authorized/_default.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { createFileRoute, Outlet } from '@tanstack/react-router';
22
import { Navigation } from '../../shared/components/Navigation/Navigation';
3-
import { VideoSupportWidget } from '../../shared/video-support/VideoSupportWidget';
3+
import { VideoSupportWidget } from '../../shared/video-tutorials/VideoSupportWidget';
4+
import { VideoTutorialsModal } from '../../shared/video-tutorials/VideoTutorialsModal';
45

56
export const Route = createFileRoute('/_authorized/_default')({
67
component: RouteComponent,
@@ -12,6 +13,7 @@ function RouteComponent() {
1213
<Outlet />
1314
<Navigation />
1415
<VideoSupportWidget />
16+
<VideoTutorialsModal />
1517
</>
1618
);
1719
}

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

Lines changed: 9 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -10,22 +10,21 @@ import { NavLogo } from './assets/NavLogo';
1010
import './style.scss';
1111
import { useQuery } from '@tanstack/react-query';
1212
import { Link, type LinkProps } from '@tanstack/react-router';
13-
import clsx from 'clsx';
1413
import { type LicenseInfo, LicenseTier, type LicenseTierValue } from '../../api/types';
15-
import { externalLink } from '../../constants';
1614
import { Fold } from '../../defguard-ui/components/Fold/Fold';
1715
import { TooltipContent } from '../../defguard-ui/providers/tooltip/TooltipContent';
1816
import { TooltipProvider } from '../../defguard-ui/providers/tooltip/TooltipContext';
1917
import { TooltipTrigger } from '../../defguard-ui/providers/tooltip/TooltipTrigger';
2018
import { isPresent } from '../../defguard-ui/utils/isPresent';
21-
import { useTheme } from '../../hooks/theme/useTheme';
2219
import {
2320
getAliasesCountQueryOptions,
2421
getDestinationsCountQueryOptions,
2522
getLicenseInfoQueryOptions,
2623
getRulesCountQueryOptions,
24+
videoTutorialsQueryOptions,
2725
} from '../../query';
2826
import { canUseBusinessFeature } from '../../utils/license';
27+
import { NavTutorialsButton } from '../../video-tutorials/components/widget/NavTutorialsButton/NavTutorialsButton';
2928

3029
interface NavGroupProps {
3130
id: string;
@@ -196,6 +195,8 @@ export const Navigation = () => {
196195
enabled: isAdmin,
197196
});
198197

198+
const { data: videoTutorialsData } = useQuery(videoTutorialsQueryOptions);
199+
199200
const navigationGroups = useMemo(() => {
200201
const pendingCounts = {
201202
rules: rulesCount?.pending,
@@ -234,7 +235,11 @@ export const Navigation = () => {
234235
))}
235236
</div>
236237
<div className="bottom">
237-
<NavControls />
238+
{videoTutorialsData && (
239+
<div className="nav-group">
240+
<NavTutorialsButton />
241+
</div>
242+
)}
238243
</div>
239244
</div>
240245
);
@@ -314,35 +319,3 @@ const NavItem = ({
314319
</Link>
315320
);
316321
};
317-
318-
const NavControls = () => {
319-
const { changeTheme, theme } = useTheme();
320-
321-
return (
322-
<div className="nav-controls">
323-
<IconButton
324-
className={clsx({
325-
active: theme === 'light',
326-
})}
327-
icon="light-theme"
328-
onClick={() => {
329-
changeTheme('light');
330-
}}
331-
/>
332-
<IconButton
333-
className={clsx({
334-
active: theme === 'dark',
335-
})}
336-
icon="dark-theme"
337-
onClick={() => {
338-
changeTheme('dark');
339-
}}
340-
/>
341-
<div className="right">
342-
<a rel="noopener noreferrer" target="_blank" href={externalLink.defguard.docs}>
343-
<IconButton icon="help" />
344-
</a>
345-
</div>
346-
</div>
347-
);
348-
};

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

Lines changed: 4 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@
4545
width: 100%;
4646
margin-top: auto;
4747
box-sizing: border-box;
48-
padding: var(--spacing-md);
48+
padding: var(--spacing-sm) var(--spacing-lg);
4949
display: flex;
5050
flex-flow: row nowrap;
5151
align-items: center;
@@ -58,6 +58,7 @@
5858

5959
.navigation .nav-group {
6060
user-select: none;
61+
width: 100%;
6162

6263
& > .track {
6364
display: flex;
@@ -103,6 +104,8 @@
103104
width: 100%;
104105
user-select: none;
105106
border-radius: var(--radius-md);
107+
border: none;
108+
cursor: pointer;
106109

107110
@include animate(color, background-color);
108111

@@ -135,17 +138,3 @@
135138
row-gap: var(--spacing-xxs);
136139
}
137140
}
138-
139-
.navigation .nav-controls {
140-
display: flex;
141-
flex-flow: row nowrap;
142-
column-gap: var(--spacing-md);
143-
flex-basis: 100%;
144-
145-
.right {
146-
display: flex;
147-
flex-flow: row nowrap;
148-
column-gap: var(--spacing-md);
149-
margin-left: auto;
150-
}
151-
}

web/src/shared/query.ts

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import api from './api/api';
33
import { AclDeploymentState, type UserProfile } from './api/types';
44
import { updateServiceApi, updateServiceClient } from './api/update-service';
55
import { resourceDisplayMap } from './utils/resourceById';
6-
import { parseVideoSupport, videoSupportPath } from './video-support/data';
6+
import { parseVideoTutorials, videoTutorialsPath } from './video-tutorials/data';
77

88
export const getExternalProviderQueryOptions = queryOptions({
99
queryFn: api.openIdProvider.getOpenIdProvider,
@@ -117,10 +117,20 @@ export const clientArtifactsQueryOptions = queryOptions({
117117
refetchOnReconnect: true,
118118
});
119119

120-
export const videoSupportQueryOptions = queryOptions({
121-
queryKey: ['update-service', 'video-support'],
122-
queryFn: () => updateServiceClient.get<unknown>(videoSupportPath),
123-
select: (resp) => parseVideoSupport(resp.data),
120+
export const videoTutorialsQueryOptions = queryOptions({
121+
queryKey: ['update-service', 'video-tutorials'],
122+
queryFn: () => updateServiceClient.get<unknown>(videoTutorialsPath),
123+
select: (resp) => {
124+
try {
125+
return parseVideoTutorials(resp.data);
126+
} catch (err) {
127+
console.error(
128+
'[video-tutorials] Fetched successfully but failed to parse response:',
129+
err,
130+
);
131+
throw err;
132+
}
133+
},
124134
// Mappings are version-tied and won't meaningfully change within a session.
125135
staleTime: Infinity,
126136
// Silent failure: if the fetch or parse fails, the widget simply won't appear.

web/src/shared/video-support/README.md

Lines changed: 0 additions & 191 deletions
This file was deleted.

0 commit comments

Comments
 (0)