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
1 change: 1 addition & 0 deletions webnext/messages/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"form_error_min_len": "Minimum length of {length}",
"form_error_email": "Enter valid email",
"form_error_required": "Field is required",
"form_error_token": "Token is not valid",
"form_label_token": "Token",
"form_label_email": "email",
"form_label_url": "URL",
Expand Down
112 changes: 97 additions & 15 deletions webnext/src/pages/ClientDownload/ClientDownloadPage.tsx
Original file line number Diff line number Diff line change
@@ -1,26 +1,95 @@
import './style.scss';
import { useNavigate } from '@tanstack/react-router';
import { useState } from 'react';
import { useMemo, useState } from 'react';
import { m } from '../../paraglide/messages';
import { Page } from '../../shared/components/Page/Page';
import { PageNavigation } from '../../shared/components/PageNavigation/PageNavigation';
import { EnrollmentStep } from '../../shared/components/Step/Step';
import { externalLink } from '../../shared/consts';
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 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';
import { SizedBox } from '../../shared/defguard-ui/components/SizedBox/SizedBox';
import { ThemeSpacing } from '../../shared/defguard-ui/types';
import { isPresent } from '../../shared/defguard-ui/utils/isPresent';
import androidIcon from './assets/android.png';
import iosIcon from './assets/ios.png';
import laptopIcon from './assets/laptop.png';
import desktopIcon from './assets/pc-tower.png';

// open link in onClick handler
const openLink = (value: string): void => {
const anchorElement = document.createElement('a');
anchorElement.style.display = 'none';
anchorElement.href = value;
anchorElement.target = '_blank';
anchorElement.rel = 'noopener noreferrer';
document.body.appendChild(anchorElement);
anchorElement.click();
anchorElement.remove();
};

const linuxMenu: MenuItemsGroup[] = [
{
items: [
{
text: 'Deb X86',
onClick: () => openLink(externalLink.client.desktop.linux.deb.amd),
},
{
text: 'Deb ARM',
onClick: () => openLink(externalLink.client.desktop.linux.deb.arm),
},
{
text: 'RPM X86',
onClick: () => openLink(externalLink.client.desktop.linux.rpm.amd),
},
{
text: 'RPM ARM',
onClick: () => openLink(externalLink.client.desktop.linux.rpm.arm),
},
{
text: 'ArchLinux',
onClick: () => openLink(externalLink.client.desktop.linux.arch),
},
],
},
];

export const ClientDownloadPage = () => {
const [modalOpen, setModalOpen] = useState(false);
const navigate = useNavigate();

const [confirmModalOpen, setConfirmModalOpen] = useState(false);
const [_appleHelpModalOpen, setAppleHelpModalOpen] = useState(false);

const appleMenu = useMemo(
(): MenuItemsGroup[] => [
{
header: {
text: 'Apple',
onHelp: () => {
setAppleHelpModalOpen(true);
},
},
items: [
{
text: 'Intel',
onClick: () => openLink(externalLink.client.desktop.macos.intel),
},
{
text: 'ARM',
onClick: () => openLink(externalLink.client.desktop.macos.arm),
},
],
},
],
[],
);

return (
<Page id="client-download-page" nav>
<EnrollmentStep current={0} max={2} />
Expand All @@ -42,6 +111,7 @@ export const ClientDownloadPage = () => {
})}
buttonText={m.client_download_for({ platform: 'Windows' })}
buttonIconKind="windows"
directLink={externalLink.client.desktop.windows}
icon={desktopIcon}
/>
<Platform
Expand All @@ -52,6 +122,7 @@ export const ClientDownloadPage = () => {
})}
buttonText={m.client_download_for({ platform: 'Linux' })}
buttonIconKind="linux"
menuItems={linuxMenu}
icon={desktopIcon}
/>
<Platform
Expand All @@ -62,6 +133,7 @@ export const ClientDownloadPage = () => {
})}
buttonText={m.client_download_for({ platform: 'Mac' })}
buttonIconKind="app-store"
menuItems={appleMenu}
icon={laptopIcon}
/>
</div>
Expand All @@ -78,6 +150,7 @@ export const ClientDownloadPage = () => {
})}
buttonText={m.client_download_for({ platform: 'Android' })}
buttonIconKind="android"
directLink={externalLink.client.mobile.google}
icon={androidIcon}
/>
<Platform
Expand All @@ -88,22 +161,23 @@ export const ClientDownloadPage = () => {
})}
buttonText={m.client_download_for({ platform: 'iOS' })}
buttonIconKind="apple"
directLink={externalLink.client.mobile.apple}
icon={iosIcon}
/>
</div>
<Modal
title={m.client_download_modal_title()}
size="small"
isOpen={modalOpen}
isOpen={confirmModalOpen}
onClose={() => {
setModalOpen(false);
setConfirmModalOpen(false);
}}
>
<p>{m.client_download_modal_content()}</p>
<ModalControls
cancelProps={{
text: m.client_download_modal_cancel(),
onClick: () => setModalOpen(false),
onClick: () => setConfirmModalOpen(false),
}}
submitProps={{
text: m.controls_continue(),
Expand All @@ -126,7 +200,7 @@ export const ClientDownloadPage = () => {
}}
nextText={m.controls_continue()}
onNext={() => {
setModalOpen(true);
setConfirmModalOpen(true);
}}
/>
</Page>
Expand All @@ -140,13 +214,17 @@ const Platform = ({
subtitle,
title,
testId,
menuItems,
directLink,
}: {
icon: string;
title: string;
subtitle: string;
buttonText: string;
buttonIconKind: IconKindValue;
testId: string;
directLink?: string;
menuItems?: MenuItemsGroup[];
}) => {
return (
<div className="platform" data-testid={testId}>
Expand All @@ -155,15 +233,19 @@ const Platform = ({
<p>{title}</p>
<p>{subtitle}</p>
</div>
<Button
variant="outlined"
iconLeft={buttonIconKind}
text={buttonText}
onClick={() => {
//TODO: Links ?
console.log('todo');
}}
/>
{isPresent(directLink) && (
<a href={directLink} target="_blank">
<Button variant="outlined" iconLeft={buttonIconKind} text={buttonText} />
</a>
)}
{isPresent(menuItems) && (
<ButtonMenu
variant="outlined"
menuItems={menuItems}
iconLeft={buttonIconKind}
text={buttonText}
/>
)}
</div>
);
};
4 changes: 2 additions & 2 deletions webnext/src/pages/ClientDownload/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
flex-flow: row nowrap;
align-items: center;
justify-content: flex-start;
column-gap: var(--spacing-lg);
column-gap: var(--spacing-sm);

p {
font: var(--t-body-sm-400);
Expand All @@ -36,7 +36,7 @@
height: 60px;
}

& > .btn {
.btn {
min-width: 212px;
}

Expand Down
2 changes: 1 addition & 1 deletion webnext/src/pages/Home/components/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@

.title {
color: var(--fg-faded);
font: var(--t-titles-h3);
font: var(--t-title-h3);
}

.subtitle {
Expand Down
2 changes: 2 additions & 0 deletions webnext/src/pages/Home/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@
}

& > .subtitle {
font: var(--t-body-primary-400);
text-align: center;
color: var(--fg-muted);
}

& > footer {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { EnrollmentStep } from '../../../shared/components/Step/Step';
import { Divider } from '../../../shared/defguard-ui/components/Divider/Divider';
import './style.scss';
import { revalidateLogic } from '@tanstack/react-form';
import { useMutation } from '@tanstack/react-query';
import type { AxiosError } from 'axios';
import z from 'zod';
import { api } from '../../../shared/api/api';
import { Button } from '../../../shared/defguard-ui/components/Button/Button';
Expand All @@ -34,13 +34,6 @@ export const EnrollmentStartPage = () => {
});
const setEnrollment = useEnrollmentStore((s) => s.setState);

const { mutateAsync } = useMutation({
mutationFn: api.enrollment.start.callbackFn,
onError: (e) => {
console.error(e);
},
});

const form = useAppForm({
defaultValues,
validationLogic: revalidateLogic({
Expand All @@ -51,12 +44,28 @@ export const EnrollmentStartPage = () => {
onDynamic: formSchema,
onSubmit: formSchema,
},
onSubmit: async ({ value }) => {
const response = await mutateAsync({
data: {
token: value.token,
},
});
onSubmit: async ({ value, formApi }) => {
const response = await api.enrollment.start
.callbackFn({
data: {
token: value.token,
},
})
.catch((e: AxiosError) => {
if (e.status === 401) {
formApi.setErrorMap({
onSubmit: {
fields: {
token: m.form_error_token(),
},
},
});
}
});

if (!response) {
return;
}

setEnrollment({
enrollmentData: response.data,
Expand All @@ -83,24 +92,28 @@ export const EnrollmentStartPage = () => {
<ContainerWithIcon icon="globe">
<header>
<h5>{m.enrollment_start_external_title()}</h5>
<SizedBox height={ThemeSpacing.Xs} />
<p>{m.enrollment_start_external_subtitle()}</p>
</header>
<a href={loaderData.url} target="_blank">
<Button
size="big"
iconRight="open-in-new-window"
text={m.cmp_openid_button({
provider: loaderData.button_display_name,
})}
/>
</a>
<div className="openid-link">
<a href={loaderData.url} target="_blank">
<Button
size="big"
iconRight="open-in-new-window"
text={m.cmp_openid_button({
provider: loaderData.button_display_name,
})}
/>
</a>
</div>
</ContainerWithIcon>
<Divider text={m.misc_or()} orientation="horizontal" />
</>
)}
<ContainerWithIcon icon="file">
<header>
<h5>{m.enrollment_start_internal_title()}</h5>
<SizedBox height={ThemeSpacing.Xs} />
<p>{m.enrollment_start_internal_subtitle()}</p>
</header>
<form.AppForm>
Expand Down
7 changes: 7 additions & 0 deletions webnext/src/pages/enrollment/EnrollmentStart/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,13 @@
color: var(--fg-neutral);
}
}

.openid-link {
display: flex;
flex-flow: row;
align-items: center;
justify-content: flex-start;
}
}

.divider {
Expand Down
Loading