Skip to content

Commit be5d0cd

Browse files
handle update service fallback (#187)
* handle update service fallback cache update service response * add apple help modal to configure client page
1 parent a1b823e commit be5d0cd

File tree

13 files changed

+135
-56
lines changed

13 files changed

+135
-56
lines changed

webnext/src/pages/ClientDownload/ClientDownloadPage.tsx

Lines changed: 12 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import './style.scss';
2-
import { useLoaderData, useNavigate } from '@tanstack/react-router';
2+
import { useQuery } from '@tanstack/react-query';
3+
import { useNavigate } from '@tanstack/react-router';
34
import { useMemo, useState } from 'react';
45
import { m } from '../../paraglide/messages';
6+
import { AppleHelpModal } from '../../shared/components/AppleHelpModal/AppleHelpModal';
57
import { Page } from '../../shared/components/Page/Page';
68
import { PageNavigation } from '../../shared/components/PageNavigation/PageNavigation';
79
import { EnrollmentStep } from '../../shared/components/Step/Step';
@@ -16,19 +18,16 @@ import { ModalControls } from '../../shared/defguard-ui/components/ModalControls
1618
import { SizedBox } from '../../shared/defguard-ui/components/SizedBox/SizedBox';
1719
import { ThemeSpacing } from '../../shared/defguard-ui/types';
1820
import { isPresent } from '../../shared/defguard-ui/utils/isPresent';
21+
import { getClientArtifactsQueryOptions } from '../../shared/query/queryOptions';
1922
import { openVirtualLink } from '../../shared/utils/openVirtualLink';
2023
import androidIcon from './assets/android.png';
21-
import apple_video_src from './assets/apple_hardware_help.mp4';
2224
import iosIcon from './assets/ios.png';
2325
import laptopIcon from './assets/laptop.png';
2426
import desktopIcon from './assets/pc-tower.png';
2527

26-
// open link in onClick handler
27-
2828
export const ClientDownloadPage = () => {
29-
const pageData = useLoaderData({
30-
from: '/download',
31-
});
29+
const { data: pageData } = useQuery(getClientArtifactsQueryOptions);
30+
3231
const navigate = useNavigate();
3332

3433
const [confirmModalOpen, setConfirmModalOpen] = useState(false);
@@ -163,6 +162,12 @@ export const ClientDownloadPage = () => {
163162
icon={iosIcon}
164163
/>
165164
</div>
165+
<AppleHelpModal
166+
isOpen={appleHelpModalOpen}
167+
onClose={() => {
168+
setAppleHelpModalOpen(false);
169+
}}
170+
/>
166171
<Modal
167172
title={m.client_download_modal_title()}
168173
size="small"
@@ -188,37 +193,6 @@ export const ClientDownloadPage = () => {
188193
}}
189194
/>
190195
</Modal>
191-
<Modal
192-
title={m.client_download_apple_help_title()}
193-
size="small"
194-
isOpen={appleHelpModalOpen}
195-
onClose={() => {
196-
setAppleHelpModalOpen(false);
197-
}}
198-
>
199-
<p>{m.client_download_apple_help_content_1()}</p>
200-
<SizedBox height={ThemeSpacing.Xl} />
201-
<video
202-
controls
203-
playsInline
204-
preload="metadata"
205-
src={apple_video_src}
206-
style={{
207-
width: '100%',
208-
height: 'auto',
209-
}}
210-
/>
211-
<SizedBox height={ThemeSpacing.Xl} />
212-
<p>{m.client_download_apple_help_content_2()}</p>
213-
<ModalControls
214-
submitProps={{
215-
text: m.controls_got_it(),
216-
onClick: () => {
217-
setAppleHelpModalOpen(false);
218-
},
219-
}}
220-
/>
221-
</Modal>
222196
<PageNavigation
223197
backText={m.controls_back()}
224198
onBack={() => {

webnext/src/pages/ClientDownload/style.scss

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
& > img {
3535
width: 60px;
3636
height: 60px;
37+
user-select: none;
3738
}
3839

3940
.btn {

webnext/src/pages/enrollment/ConfigureClient/ConfigureClientPage.tsx

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
import './style.scss';
22

3+
import { useQuery } from '@tanstack/react-query';
34
import { useLoaderData } from '@tanstack/react-router';
45
import { capitalCase } from 'change-case';
56
import { QRCodeCanvas } from 'qrcode.react';
67
import { useMemo, useState } from 'react';
78
import { m } from '../../../paraglide/messages';
9+
import { AppleHelpModal } from '../../../shared/components/AppleHelpModal/AppleHelpModal';
810
import { ContactFooter } from '../../../shared/components/ContactFooter/ContactFooter';
911
import { ContainerWithIcon } from '../../../shared/components/ContainerWithIcon/ContainerWithIcon';
1012
import { Page } from '../../../shared/components/Page/Page';
@@ -19,13 +21,17 @@ import { Icon } from '../../../shared/defguard-ui/components/Icon';
1921
import type { MenuItemsGroup } from '../../../shared/defguard-ui/components/Menu/types';
2022
import { SizedBox } from '../../../shared/defguard-ui/components/SizedBox/SizedBox';
2123
import { ThemeSpacing } from '../../../shared/defguard-ui/types';
24+
import { getClientArtifactsQueryOptions } from '../../../shared/query/queryOptions';
2225
import { openVirtualLink } from '../../../shared/utils/openVirtualLink';
2326

2427
export const ConfigureClientPage = () => {
2528
const pageData = useLoaderData({
2629
from: '/client-setup',
2730
});
28-
const clientLinks = pageData.clientDownload;
31+
32+
const { data: clientLinks } = useQuery(getClientArtifactsQueryOptions);
33+
34+
const [appleHelpModalOpen, setAppleHelpModalOpen] = useState(false);
2935

3036
const clientDownloadMenu = useMemo(
3137
(): MenuItemsGroup[] => [
@@ -41,6 +47,7 @@ export const ConfigureClientPage = () => {
4147
{
4248
header: {
4349
text: m.client_download_apple_help_header(),
50+
onHelp: () => setAppleHelpModalOpen(true),
4451
},
4552
items: [
4653
{
@@ -214,6 +221,12 @@ export const ConfigureClientPage = () => {
214221
<p className="finish">{m.client_setup_footer_extra()}</p>
215222
<ContactFooter email={pageData.enrollmentData.admin.email} />
216223
</footer>
224+
<AppleHelpModal
225+
isOpen={appleHelpModalOpen}
226+
onClose={() => {
227+
setAppleHelpModalOpen(false);
228+
}}
229+
/>
217230
</Page>
218231
);
219232
};

webnext/src/routes/client-setup.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
import { createFileRoute, redirect } from '@tanstack/react-router';
22
import z from 'zod';
3+
import { queryClient } from '../app/query';
34
import { ConfigureClientPage } from '../pages/enrollment/ConfigureClient/ConfigureClientPage';
45
import { api } from '../shared/api/api';
56
import type { EnrollmentStartResponse } from '../shared/api/types';
6-
import { updateServiceApi } from '../shared/api/update-service';
77
import { isPresent } from '../shared/defguard-ui/utils/isPresent';
88
import { useEnrollmentStore } from '../shared/hooks/useEnrollmentStore';
9+
import { getClientArtifactsQueryOptions } from '../shared/query/queryOptions';
910

1011
const schema = z.object({
1112
code: z.string().trim().optional(),
@@ -39,7 +40,8 @@ export const Route = createFileRoute('/client-setup')({
3940
};
4041
},
4142
loader: async ({ context: { openid } }) => {
42-
const clientDownload = await updateServiceApi.getClientArtifacts().catch(() => null);
43+
void queryClient.ensureQueryData(getClientArtifactsQueryOptions);
44+
4345
if (openid) {
4446
try {
4547
const openIdResponse = await api.openId.enrollmentCallback.callbackFn({
@@ -70,7 +72,6 @@ export const Route = createFileRoute('/client-setup')({
7072
}
7173
const state = useEnrollmentStore.getState();
7274
return {
73-
clientDownload,
7475
token: state.token as string,
7576
enrollmentData: state.enrollmentData as EnrollmentStartResponse,
7677
};

webnext/src/routes/download.tsx

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,9 @@
11
import { createFileRoute } from '@tanstack/react-router';
2+
import { queryClient } from '../app/query';
23
import { ClientDownloadPage } from '../pages/ClientDownload/ClientDownloadPage';
3-
import { updateServiceApi } from '../shared/api/update-service';
4+
import { getClientArtifactsQueryOptions } from '../shared/query/queryOptions';
45

56
export const Route = createFileRoute('/download')({
67
component: ClientDownloadPage,
7-
loader: async () => {
8-
const clientVersionData = await updateServiceApi
9-
.getClientArtifacts()
10-
.catch(() => null);
11-
return clientVersionData;
12-
},
8+
loader: () => queryClient.ensureQueryData(getClientArtifactsQueryOptions),
139
});

webnext/src/shared/api/update-service.ts

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import qs from 'qs';
33

44
const baseUrl = import.meta.env.VITE_UPDATE_BASE_URL as string | undefined;
55

6+
const clientDownloadFallback = 'https://defguard.net/download';
7+
68
const client = axios.create({
79
baseURL: baseUrl ?? 'https://update-service-dev.defguard.net/api',
810
headers: { 'Content-Type': 'application/json' },
@@ -33,7 +35,32 @@ const updateServiceApi = {
3335
source: 'enrollment',
3436
},
3537
})
36-
.then((response) => response.data),
38+
.then((response) => {
39+
const { data } = response;
40+
const res: ClientVersionCheck = {
41+
deb_amd64: data.deb_amd64 ?? clientDownloadFallback,
42+
deb_arm64: data.deb_arm64 ?? clientDownloadFallback,
43+
macos_amd64: data.macos_amd64 ?? clientDownloadFallback,
44+
macos_arm64: data.macos_arm64 ?? clientDownloadFallback,
45+
rpm_amd64: data.rpm_amd64 ?? clientDownloadFallback,
46+
rpm_arm64: data.rpm_arm64 ?? clientDownloadFallback,
47+
windows_amd64: data.windows_amd64 ?? clientDownloadFallback,
48+
};
49+
return res;
50+
})
51+
.catch((e) => {
52+
console.error(e);
53+
const fallback: ClientVersionCheck = {
54+
deb_amd64: clientDownloadFallback,
55+
deb_arm64: clientDownloadFallback,
56+
macos_amd64: clientDownloadFallback,
57+
macos_arm64: clientDownloadFallback,
58+
rpm_amd64: clientDownloadFallback,
59+
rpm_arm64: clientDownloadFallback,
60+
windows_amd64: clientDownloadFallback,
61+
};
62+
return fallback;
63+
}),
3764
} as const;
3865

3966
export { updateServiceApi };
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import { m } from '../../../paraglide/messages';
2+
import { Modal } from '../../defguard-ui/components/Modal/Modal';
3+
import { ModalControls } from '../../defguard-ui/components/ModalControls/ModalControls';
4+
import { SizedBox } from '../../defguard-ui/components/SizedBox/SizedBox';
5+
import { ThemeSpacing } from '../../defguard-ui/types';
6+
import apple_video_src from './assets/apple_hardware_help.mp4';
7+
8+
type Props = {
9+
isOpen: boolean;
10+
onClose: () => void;
11+
};
12+
13+
export const AppleHelpModal = ({ isOpen, onClose }: Props) => {
14+
return (
15+
<Modal
16+
title={m.client_download_apple_help_title()}
17+
size="small"
18+
isOpen={isOpen}
19+
onClose={onClose}
20+
>
21+
<p>{m.client_download_apple_help_content_1()}</p>
22+
<SizedBox height={ThemeSpacing.Xl} />
23+
<video
24+
controls
25+
playsInline
26+
preload="metadata"
27+
src={apple_video_src}
28+
style={{
29+
width: '100%',
30+
height: 'auto',
31+
}}
32+
/>
33+
<SizedBox height={ThemeSpacing.Xl} />
34+
<p>{m.client_download_apple_help_content_2()}</p>
35+
<ModalControls
36+
submitProps={{
37+
text: m.controls_got_it(),
38+
onClick: onClose,
39+
}}
40+
/>
41+
</Modal>
42+
);
43+
};

webnext/src/pages/ClientDownload/assets/apple_hardware_help.mp4 renamed to webnext/src/shared/components/AppleHelpModal/assets/apple_hardware_help.mp4

File renamed without changes.

webnext/src/shared/defguard-ui/components/Menu/Menu.tsx

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,16 @@ import { MenuHeader } from './components/MenuHeader';
77
import { MenuSpacer } from './components/MenuSpacer';
88
import type { MenuProps } from './types';
99

10-
export const Menu = ({ itemGroups, ref, className, onClose, ...props }: MenuProps) => {
10+
export const Menu = ({
11+
itemGroups,
12+
ref,
13+
className,
14+
onClose,
15+
testId,
16+
...props
17+
}: MenuProps) => {
1118
return (
12-
<div className={clsx('menu', className)} ref={ref} {...props}>
19+
<div className={clsx('menu', className)} ref={ref} data-testid={testId} {...props}>
1320
{itemGroups.map((group, groupIndex) => (
1421
<Fragment key={group.header?.text ?? groupIndex}>
1522
{isPresent(group.header) && <MenuHeader {...group.header} onClose={onClose} />}

webnext/src/shared/defguard-ui/components/Menu/components/MenuHeader.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,13 @@ import { isPresent } from '../../../utils/isPresent';
33
import { InteractionBox } from '../../InteractionBox/InteractionBox';
44
import type { MenuHeaderProps } from '../types';
55

6-
export const MenuHeader = ({ text, onHelp, onClose }: MenuHeaderProps) => {
6+
export const MenuHeader = ({ text, testId, onHelp, onClose }: MenuHeaderProps) => {
77
return (
88
<div
99
className={clsx('menu-header', {
1010
'with-help': isPresent(onHelp),
1111
})}
12+
data-testid={testId}
1213
>
1314
<p className="group-title">{text}</p>
1415
{isPresent(onHelp) && (

0 commit comments

Comments
 (0)