Skip to content
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
5 changes: 5 additions & 0 deletions webnext/messages/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@
"cmp_openid_button": "Sign in with {provider}",
"cmp_copy_field_tooltip": "Copied",
"footer_contact": "If you need assistance, please contact your defguard administrator at.",
"session_end_title": "Session expired.",
"session_end_subtitle": "Please start the process again.",
"session_end_link": "Back to main page",
"start_footer_copyright": "Copyright ©2023-{currentYear} Defguard Sp. z o.o.",
"start_multi_title": "Get Started with Defguard",
"start_multi_subtitle": "Please select the option that suits your needs.",
Expand Down Expand Up @@ -70,6 +73,7 @@
"client_download_modal_content": "Please make sure to download at least one client, as you'll need it in the next step to configure your VPN device.",
"client_download_modal_cancel": "Back to download",
"client_download_apple_help_header": "Apple Hardware",
"client_download_mobile_warning": "Enrollment process (setting up device, setting up account password and Multi-Factor) is only supported now on desktop client. You can configure your mobile client later in Defguard - after connecting to vpn from desktop client and accessing defguard-url.com",
"enrollment_start_title": "Select activation type",
"enrollment_start_subtitle": "Select the configuration type based on your organization's approach.",
"enrollment_start_external_title": "Sign in with External SSO",
Expand All @@ -78,6 +82,7 @@
"enrollment_start_internal_subtitle": "Select this option if your administrator has sent you an email or message with your personal token. If you haven't received your token, please contact your administrator.",
"client_setup_title": "Configure your defguard client/app",
"client_setup_subtitle": "Select the activation method according to your device type.",
"client_setup_download_label": "Get the desktop client",
"client_setup_desktop_title": "Desktop client",
"client_setup_desktop_auto_title": "Automatic configuration",
"client_setup_desktop_auto_explain_1": "Click the button below for automatic configuration.",
Expand Down
2 changes: 2 additions & 0 deletions webnext/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,14 @@
"axios": "^1.12.2",
"change-case": "^5.4.4",
"clsx": "^2.1.1",
"dayjs": "^1.11.18",
"lodash-es": "^4.17.21",
"motion": "^12.23.21",
"qrcode.react": "^4.2.0",
"qs": "^6.14.0",
"react": "^19.1.1",
"react-dom": "^19.1.1",
"react-markdown": "^10.1.0",
"rxjs": "^7.8.2",
"zod": "^4.1.11",
"zustand": "^5.0.8"
Expand Down
681 changes: 681 additions & 0 deletions webnext/pnpm-lock.yaml

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions webnext/src/app/App.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import { QueryClientProvider } from '@tanstack/react-query';
import { RouterProvider } from '@tanstack/react-router';
import dayjs from 'dayjs';
import utc from 'dayjs/plugin/utc';
import { queryClient } from './query';
import { router } from './router';

dayjs.extend(utc);

export const App = () => {
return (
<QueryClientProvider client={queryClient}>
Expand Down
32 changes: 32 additions & 0 deletions webnext/src/app/SessionGuard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { useNavigate } from '@tanstack/react-router';
import dayjs from 'dayjs';
import { useCallback, useEffect } from 'react';
import { useEnrollmentStore } from '../shared/hooks/useEnrollmentStore';

export const SessionGuard = () => {
const navigate = useNavigate();
const sessionEnd = useEnrollmentStore((s) => s.enrollmentData?.deadline_timestamp);

const handleSessionEnd = useCallback(() => {
navigate({
to: '/session-end',
replace: true,
});
}, [navigate]);

useEffect(() => {
if (!sessionEnd) return;

const deadline = dayjs.unix(sessionEnd).diff(dayjs());
if (deadline > 0) {
const timeout = setTimeout(handleSessionEnd, deadline);
return () => {
clearTimeout(timeout);
};
} else {
handleSessionEnd();
}
}, [sessionEnd, handleSessionEnd]);

return null;
};
131 changes: 85 additions & 46 deletions webnext/src/pages/ClientDownload/ClientDownloadPage.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import './style.scss';
import { useQuery } from '@tanstack/react-query';
import { type RouterState, useNavigate, useRouterState } from '@tanstack/react-router';
import {
type RouterState,
useLoaderData,
useNavigate,
useRouterState,
} from '@tanstack/react-router';
import { useMemo, useState } from 'react';
import { m } from '../../paraglide/messages';
import { AppleHelpModal } from '../../shared/components/AppleHelpModal/AppleHelpModal';
Expand All @@ -12,6 +17,7 @@ import { Button } from '../../shared/defguard-ui/components/Button/Button';
import { ButtonMenu } from '../../shared/defguard-ui/components/ButtonMenu/MenuButton';
import { Icon } from '../../shared/defguard-ui/components/Icon';
import type { IconKindValue } from '../../shared/defguard-ui/components/Icon/icon-types';
import { InfoBanner } from '../../shared/defguard-ui/components/InfoBanner/InfoBanner';
import type { MenuItemsGroup } from '../../shared/defguard-ui/components/Menu/types';
import { Modal } from '../../shared/defguard-ui/components/Modal/Modal';
import { ModalControls } from '../../shared/defguard-ui/components/ModalControls/ModalControls';
Expand All @@ -23,10 +29,14 @@ import { openVirtualLink } from '../../shared/utils/openVirtualLink';
import androidIcon from './assets/android.png';
import iosIcon from './assets/ios.png';
import laptopIcon from './assets/laptop.png';
import linuxIcon from './assets/linux.png';
import desktopIcon from './assets/pc-tower.png';

export const ClientDownloadPage = () => {
const { data: pageData } = useQuery(getClientArtifactsQueryOptions);
const { enrollmentState } = useLoaderData({
from: '/download',
});
const routerLoading = useRouterState({ select: (s: RouterState) => s.isLoading });

const navigate = useNavigate();
Expand Down Expand Up @@ -58,44 +68,71 @@ export const ClientDownloadPage = () => {
[pageData],
);

const linuxMenu: MenuItemsGroup[] = useMemo(
() => [
const linuxMenu: MenuItemsGroup[] = useMemo(() => {
const res: MenuItemsGroup[] = [
{
items: [
{
text: 'Deb X86',
icon: 'ubuntu',
text: 'Ubuntu 24.04 ARM',
onClick: () => openVirtualLink(pageData?.deb_arm64),
},
{
icon: 'ubuntu',
text: 'Ubuntu 24.04 AMD64',
onClick: () => openVirtualLink(pageData?.deb_amd64),
},
],
},
{
items: [
{
text: 'Deb ARM',
icon: 'debian',
text: 'Ubuntu 22.04 / Debian 12&13 ARM',
onClick: () => openVirtualLink(pageData?.deb_arm64),
},
{
text: 'RPM X86',
onClick: () => openVirtualLink(pageData?.rpm_amd64),
icon: 'debian',
text: 'Ubuntu 22.04 / Debian 12&13 AMD64',
onClick: () => openVirtualLink(pageData?.deb_amd64),
},
],
},
{
items: [
{
icon: 'linux',
text: 'RPM ARM',
onClick: () => openVirtualLink(pageData?.rpm_arm64),
},
{
icon: 'linux',
text: 'RPM AMD64',
onClick: () => openVirtualLink(pageData?.rpm_amd64),
},
],
},
{
items: [
{
icon: 'arch-linux',
text: 'Arch Linux',
onClick: () => openVirtualLink(externalLink.client.desktop.linux.arch),
},
],
},
],
[pageData],
);
];
return res;
}, [pageData]);

return (
<Page id="client-download-page" nav>
<EnrollmentStep current={0} max={2} />
<EnrollmentStep current={1} max={2} />
<header>
<h1>{m.client_download_title()}</h1>
<p>{m.client_download_subtitle()}</p>
</header>
<SizedBox height={ThemeSpacing.Xl4} />
<SizedBox height={ThemeSpacing.Xl3} />
<div className="platforms">
<div className="label">
<Icon icon="desktop" size={20} /> <p>{m.client_download_label_desktop()}</p>
Expand All @@ -121,7 +158,7 @@ export const ClientDownloadPage = () => {
buttonText={m.client_download_for({ platform: 'Linux' })}
buttonIconKind="linux"
menuItems={linuxMenu}
icon={desktopIcon}
icon={linuxIcon}
/>
<Platform
testId="macos"
Expand All @@ -136,33 +173,42 @@ export const ClientDownloadPage = () => {
/>
</div>
<SizedBox height={ThemeSpacing.Xl3} />
<div className="platforms">
<div className="label">
<Icon icon="mobile" size={20} /> <p>{m.client_download_label_mobile()}</p>
{enrollmentState.enrollmentData.user.enrolled && (
<div className="platforms">
<div className="label">
<Icon icon="mobile" size={20} /> <p>{m.client_download_label_mobile()}</p>
</div>
<Platform
testId="android"
title={m.client_download_for({ platform: 'Android' })}
subtitle={m.client_download_supports_newer({
platform: 'Android 12.0 (Snow Cone)',
})}
buttonText={m.client_download_for({ platform: 'Android' })}
buttonIconKind="android"
directLink={externalLink.client.mobile.google}
icon={androidIcon}
/>
<Platform
testId="iOS"
title={m.client_download_for({ platform: 'iOS' })}
subtitle={m.client_download_supports_newer({
platform: 'iOS 15+',
})}
buttonText={m.client_download_for({ platform: 'iOS' })}
buttonIconKind="apple"
directLink={externalLink.client.mobile.apple}
icon={iosIcon}
/>
</div>
<Platform
testId="android"
title={m.client_download_for({ platform: 'Android' })}
subtitle={m.client_download_supports_newer({
platform: 'Android 12.0 (Snow Cone)',
})}
buttonText={m.client_download_for({ platform: 'Android' })}
buttonIconKind="android"
directLink={externalLink.client.mobile.google}
icon={androidIcon}
/>
<Platform
testId="iOS"
title={m.client_download_for({ platform: 'iOS' })}
subtitle={m.client_download_supports_newer({
platform: 'iOS 15+',
})}
buttonText={m.client_download_for({ platform: 'iOS' })}
buttonIconKind="apple"
directLink={externalLink.client.mobile.apple}
icon={iosIcon}
)}
{!enrollmentState.enrollmentData.user.enrolled && (
<InfoBanner
variant="warning"
icon="warning"
text={m.client_download_mobile_warning()}
/>
</div>
)}
<AppleHelpModal
isOpen={appleHelpModalOpen}
onClose={() => {
Expand Down Expand Up @@ -194,21 +240,14 @@ export const ClientDownloadPage = () => {
loading: routerLoading,
onClick: () => {
navigate({
to: '/enrollment-start',
to: '/client-setup',
replace: true,
});
},
}}
/>
</Modal>
<PageNavigation
backText={m.controls_back()}
onBack={() => {
navigate({
to: '/',
replace: true,
});
}}
nextText={m.controls_continue()}
onNext={() => {
setConfirmModalOpen(true);
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 3 additions & 3 deletions webnext/src/pages/Home/components/HomeChoice.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export const HomeChoice = () => {
subtitle={m.start_multi_enrollment_subtitle()}
buttonText={m.start_multi_enrollment_button()}
buttonIcon="arrow-big"
link="/download"
link="/enrollment-start"
onClick={() => {}}
/>
<Card
Expand All @@ -41,7 +41,7 @@ export const HomeChoice = () => {

type CardProps = {
img: 'enroll' | 'password';
link: '/password' | '/download';
link: '/password' | '/enrollment-start';
buttonIcon: IconKindValue;
buttonText: string;
subtitle: string;
Expand Down Expand Up @@ -82,7 +82,7 @@ const Card = ({
<SizedBox height={ThemeSpacing.Md} />
<p className="subtitle">{subtitle}</p>
<SizedBox height={ThemeSpacing.Xl2} />
<Link to={link} data-testId={testId}>
<Link to={link} data-testid={testId}>
<Button
size="primary"
variant="primary"
Expand Down
15 changes: 12 additions & 3 deletions webnext/src/pages/Home/components/style.scss
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
#home-choice {
display: grid;
grid-template-columns: 1fr 1fr;
grid-template-rows: 1fr;
column-gap: var(--spacing-4xl);
grid-template-columns: 1fr;
grid-template-rows: 1fr 1fr;
gap: var(--spacing-4xl);

@include break-up(md) {
grid-template-columns: 1fr 1fr;
grid-template-rows: 1fr;
}

& > .choice {
--border-color: var(--border-default);
Expand All @@ -15,6 +20,10 @@
transition-duration: 400ms;
transition-property: border-color;

@include break-down(md) {
max-width: 390px;
}

.title {
color: var(--fg-faded);
font: var(--t-title-h3);
Expand Down
14 changes: 14 additions & 0 deletions webnext/src/pages/SessionEnd/SessionEndPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { m } from '../../paraglide/messages';
import { PageProcessEnd } from '../../shared/components/PageProcessEnd/PageProcessEnd';

export const SessionEndPage = () => {
return (
<PageProcessEnd
link="/"
icon="disabled"
title={m.session_end_title()}
subtitle={m.session_end_subtitle()}
linkText={m.session_end_link()}
/>
);
};
Loading