From d095c4458720c7d008e0bc493ae485b0885726d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20=C5=9Al=C4=99zak?= Date: Tue, 7 Oct 2025 16:31:51 +0200 Subject: [PATCH 1/6] reorder links wip --- webnext/messages/en.json | 3 + webnext/package.json | 1 + webnext/pnpm-lock.yaml | 8 +++ webnext/src/app/App.tsx | 4 ++ webnext/src/app/SessionGuard.tsx | 32 ++++++++++ .../ClientDownload/ClientDownloadPage.tsx | 7 --- .../src/pages/Home/components/HomeChoice.tsx | 4 +- .../src/pages/SessionEnd/SessionEndPage.tsx | 14 +++++ .../EnrollmentStart/EnrollmentStartPage.tsx | 5 +- webnext/src/routeTree.gen.ts | 21 +++++++ webnext/src/routes/__root.tsx | 8 ++- webnext/src/routes/client-setup.tsx | 63 ++----------------- webnext/src/routes/openid/callback.tsx | 23 ++++++- webnext/src/routes/session-end.tsx | 10 +++ .../PageNavigation/PageNavigation.tsx | 20 +++--- .../components/PageNavigation/style.scss | 11 ++++ .../PageProcessEnd/PageProcessEnd.tsx | 12 +++- .../components/PageProcessEnd/style.scss | 6 ++ .../defguard-ui/components/Icon/Icon.tsx | 3 + .../defguard-ui/components/Icon/icon-types.ts | 1 + .../components/Icon/icons/IconDisabled.tsx | 19 ++++++ .../components/SSOButton/style.scss | 2 + 22 files changed, 196 insertions(+), 81 deletions(-) create mode 100644 webnext/src/app/SessionGuard.tsx create mode 100644 webnext/src/pages/SessionEnd/SessionEndPage.tsx create mode 100644 webnext/src/routes/session-end.tsx create mode 100644 webnext/src/shared/defguard-ui/components/Icon/icons/IconDisabled.tsx diff --git a/webnext/messages/en.json b/webnext/messages/en.json index e0031690..88e50373 100644 --- a/webnext/messages/en.json +++ b/webnext/messages/en.json @@ -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.", diff --git a/webnext/package.json b/webnext/package.json index 9946d771..86369b79 100644 --- a/webnext/package.json +++ b/webnext/package.json @@ -26,6 +26,7 @@ "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", diff --git a/webnext/pnpm-lock.yaml b/webnext/pnpm-lock.yaml index 1045dfd5..5c17877d 100644 --- a/webnext/pnpm-lock.yaml +++ b/webnext/pnpm-lock.yaml @@ -47,6 +47,9 @@ importers: clsx: specifier: ^2.1.1 version: 2.1.1 + dayjs: + specifier: ^1.11.18 + version: 1.11.18 lodash-es: specifier: ^4.17.21 version: 4.17.21 @@ -1497,6 +1500,9 @@ packages: csstype@3.1.3: resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} + dayjs@1.11.18: + resolution: {integrity: sha512-zFBQ7WFRvVRhKcWoUh+ZA1g2HVgUbsZm9sbddh8EC5iv93sui8DVVz1Npvz+r6meo9VKfa8NyLWBsQK1VvIKPA==} + debug@4.4.3: resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} engines: {node: '>=6.0'} @@ -3876,6 +3882,8 @@ snapshots: csstype@3.1.3: {} + dayjs@1.11.18: {} + debug@4.4.3: dependencies: ms: 2.1.3 diff --git a/webnext/src/app/App.tsx b/webnext/src/app/App.tsx index ada329c7..bffd2673 100644 --- a/webnext/src/app/App.tsx +++ b/webnext/src/app/App.tsx @@ -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 ( diff --git a/webnext/src/app/SessionGuard.tsx b/webnext/src/app/SessionGuard.tsx new file mode 100644 index 00000000..20479889 --- /dev/null +++ b/webnext/src/app/SessionGuard.tsx @@ -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; +}; diff --git a/webnext/src/pages/ClientDownload/ClientDownloadPage.tsx b/webnext/src/pages/ClientDownload/ClientDownloadPage.tsx index b43ed8fe..d9ce0942 100644 --- a/webnext/src/pages/ClientDownload/ClientDownloadPage.tsx +++ b/webnext/src/pages/ClientDownload/ClientDownloadPage.tsx @@ -202,13 +202,6 @@ export const ClientDownloadPage = () => { /> { - navigate({ - to: '/', - replace: true, - }); - }} nextText={m.controls_continue()} onNext={() => { setConfirmModalOpen(true); diff --git a/webnext/src/pages/Home/components/HomeChoice.tsx b/webnext/src/pages/Home/components/HomeChoice.tsx index 9db9a149..4a48afa9 100644 --- a/webnext/src/pages/Home/components/HomeChoice.tsx +++ b/webnext/src/pages/Home/components/HomeChoice.tsx @@ -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={() => {}} /> { type CardProps = { img: 'enroll' | 'password'; - link: '/password' | '/download'; + link: '/password' | '/enrollment-start'; buttonIcon: IconKindValue; buttonText: string; subtitle: string; diff --git a/webnext/src/pages/SessionEnd/SessionEndPage.tsx b/webnext/src/pages/SessionEnd/SessionEndPage.tsx new file mode 100644 index 00000000..38d2d67f --- /dev/null +++ b/webnext/src/pages/SessionEnd/SessionEndPage.tsx @@ -0,0 +1,14 @@ +import { m } from '../../paraglide/messages'; +import { PageProcessEnd } from '../../shared/components/PageProcessEnd/PageProcessEnd'; + +export const SessionEndPage = () => { + return ( + + ); +}; diff --git a/webnext/src/pages/enrollment/EnrollmentStart/EnrollmentStartPage.tsx b/webnext/src/pages/enrollment/EnrollmentStart/EnrollmentStartPage.tsx index 156cd311..6b857ab7 100644 --- a/webnext/src/pages/enrollment/EnrollmentStart/EnrollmentStartPage.tsx +++ b/webnext/src/pages/enrollment/EnrollmentStart/EnrollmentStartPage.tsx @@ -73,7 +73,7 @@ export const EnrollmentStartPage = () => { }); navigate({ - to: '/client-setup', + to: '/download', replace: true, }); }, @@ -131,7 +131,8 @@ export const EnrollmentStartPage = () => { backText={m.controls_back()} onBack={() => { navigate({ - to: '/download', + to: '/', + replace: true, }); }} nextText={m.controls_continue()} diff --git a/webnext/src/routeTree.gen.ts b/webnext/src/routeTree.gen.ts index 2c91b64d..2dc0610c 100644 --- a/webnext/src/routeTree.gen.ts +++ b/webnext/src/routeTree.gen.ts @@ -10,6 +10,7 @@ import { Route as rootRouteImport } from './routes/__root' import { Route as TestRouteImport } from './routes/test' +import { Route as SessionEndRouteImport } from './routes/session-end' import { Route as PasswordResetRouteImport } from './routes/password-reset' import { Route as EnrollmentStartRouteImport } from './routes/enrollment-start' import { Route as DownloadRouteImport } from './routes/download' @@ -25,6 +26,11 @@ const TestRoute = TestRouteImport.update({ path: '/test', getParentRoute: () => rootRouteImport, } as any) +const SessionEndRoute = SessionEndRouteImport.update({ + id: '/session-end', + path: '/session-end', + getParentRoute: () => rootRouteImport, +} as any) const PasswordResetRoute = PasswordResetRouteImport.update({ id: '/password-reset', path: '/password-reset', @@ -77,6 +83,7 @@ export interface FileRoutesByFullPath { '/download': typeof DownloadRoute '/enrollment-start': typeof EnrollmentStartRoute '/password-reset': typeof PasswordResetRoute + '/session-end': typeof SessionEndRoute '/test': typeof TestRoute '/openid/callback': typeof OpenidCallbackRoute '/password/finish': typeof PasswordFinishRoute @@ -89,6 +96,7 @@ export interface FileRoutesByTo { '/download': typeof DownloadRoute '/enrollment-start': typeof EnrollmentStartRoute '/password-reset': typeof PasswordResetRoute + '/session-end': typeof SessionEndRoute '/test': typeof TestRoute '/openid/callback': typeof OpenidCallbackRoute '/password/finish': typeof PasswordFinishRoute @@ -102,6 +110,7 @@ export interface FileRoutesById { '/download': typeof DownloadRoute '/enrollment-start': typeof EnrollmentStartRoute '/password-reset': typeof PasswordResetRoute + '/session-end': typeof SessionEndRoute '/test': typeof TestRoute '/openid/callback': typeof OpenidCallbackRoute '/password/finish': typeof PasswordFinishRoute @@ -116,6 +125,7 @@ export interface FileRouteTypes { | '/download' | '/enrollment-start' | '/password-reset' + | '/session-end' | '/test' | '/openid/callback' | '/password/finish' @@ -128,6 +138,7 @@ export interface FileRouteTypes { | '/download' | '/enrollment-start' | '/password-reset' + | '/session-end' | '/test' | '/openid/callback' | '/password/finish' @@ -140,6 +151,7 @@ export interface FileRouteTypes { | '/download' | '/enrollment-start' | '/password-reset' + | '/session-end' | '/test' | '/openid/callback' | '/password/finish' @@ -153,6 +165,7 @@ export interface RootRouteChildren { DownloadRoute: typeof DownloadRoute EnrollmentStartRoute: typeof EnrollmentStartRoute PasswordResetRoute: typeof PasswordResetRoute + SessionEndRoute: typeof SessionEndRoute TestRoute: typeof TestRoute OpenidCallbackRoute: typeof OpenidCallbackRoute PasswordFinishRoute: typeof PasswordFinishRoute @@ -169,6 +182,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof TestRouteImport parentRoute: typeof rootRouteImport } + '/session-end': { + id: '/session-end' + path: '/session-end' + fullPath: '/session-end' + preLoaderRoute: typeof SessionEndRouteImport + parentRoute: typeof rootRouteImport + } '/password-reset': { id: '/password-reset' path: '/password-reset' @@ -241,6 +261,7 @@ const rootRouteChildren: RootRouteChildren = { DownloadRoute: DownloadRoute, EnrollmentStartRoute: EnrollmentStartRoute, PasswordResetRoute: PasswordResetRoute, + SessionEndRoute: SessionEndRoute, TestRoute: TestRoute, OpenidCallbackRoute: OpenidCallbackRoute, PasswordFinishRoute: PasswordFinishRoute, diff --git a/webnext/src/routes/__root.tsx b/webnext/src/routes/__root.tsx index 50ac8942..11a82203 100644 --- a/webnext/src/routes/__root.tsx +++ b/webnext/src/routes/__root.tsx @@ -1,9 +1,15 @@ import { createRootRoute, Outlet } from '@tanstack/react-router'; +import { SessionGuard } from '../app/SessionGuard'; export const Route = createRootRoute({ component: RootComponent, }); function RootComponent() { - return ; + return ( + <> + + + + ); } diff --git a/webnext/src/routes/client-setup.tsx b/webnext/src/routes/client-setup.tsx index 297b894b..70abd21c 100644 --- a/webnext/src/routes/client-setup.tsx +++ b/webnext/src/routes/client-setup.tsx @@ -1,75 +1,24 @@ import { createFileRoute, redirect } from '@tanstack/react-router'; -import z from 'zod'; import { queryClient } from '../app/query'; import { ConfigureClientPage } from '../pages/enrollment/ConfigureClient/ConfigureClientPage'; -import { api } from '../shared/api/api'; import type { EnrollmentStartResponse } from '../shared/api/types'; -import { isPresent } from '../shared/defguard-ui/utils/isPresent'; import { useEnrollmentStore } from '../shared/hooks/useEnrollmentStore'; import { getClientArtifactsQueryOptions } from '../shared/query/queryOptions'; -const schema = z.object({ - code: z.string().trim().optional(), - state: z.string().trim().optional(), -}); - export const Route = createFileRoute('/client-setup')({ component: ConfigureClientPage, - validateSearch: schema, - loaderDeps: ({ search }) => ({ search }), - beforeLoad: ({ search }) => { - // if openId flow just pass the search to the context - if (search && isPresent(search.code) && isPresent(search.state)) { - return { - openid: { - code: search.code, - state: search.state, - }, - }; - } - // if not openId then expect state to be in session - const state = useEnrollmentStore.getState(); - if (state.token === undefined || state.enrollmentData === undefined) { + // check if required session state is present + beforeLoad: () => { + const { enrollmentData, token } = useEnrollmentStore.getState(); + if (!enrollmentData || !token) { throw redirect({ - to: '/enrollment-start', + to: '/', replace: true, }); } - return { - openid: undefined, - }; }, - loader: async ({ context: { openid } }) => { + loader: async () => { void queryClient.ensureQueryData(getClientArtifactsQueryOptions); - - if (openid) { - try { - const openIdResponse = await api.openId.enrollmentCallback.callbackFn({ - data: { - code: openid.code, - state: openid.state, - type: 'enrollment', - }, - }); - - const enrollResponse = await api.enrollment.start.callbackFn({ - data: { - token: openIdResponse.data.token, - }, - }); - - return { - token: openIdResponse.data.token, - enrollmentData: enrollResponse.data, - }; - } catch (e) { - console.error(e); - throw redirect({ - to: '/', - replace: true, - }); - } - } const state = useEnrollmentStore.getState(); return { token: state.token as string, diff --git a/webnext/src/routes/openid/callback.tsx b/webnext/src/routes/openid/callback.tsx index 47173022..2304ba0a 100644 --- a/webnext/src/routes/openid/callback.tsx +++ b/webnext/src/routes/openid/callback.tsx @@ -1,5 +1,7 @@ import { createFileRoute, redirect } from '@tanstack/react-router'; import z from 'zod'; +import { api } from '../../shared/api/api'; +import { useEnrollmentStore } from '../../shared/hooks/useEnrollmentStore'; const schema = z.object({ state: z.string().trim().min(1), @@ -11,10 +13,27 @@ export const Route = createFileRoute('/openid/callback')({ component: RouteComponent, validateSearch: schema, loaderDeps: ({ search }) => ({ search }), - beforeLoad: ({ search }) => { + beforeLoad: async ({ search }) => { + const openIdResponse = await api.openId.enrollmentCallback.callbackFn({ + data: { + code: search.code, + state: search.state, + type: 'enrollment', + }, + }); + const enrollmentStartResponse = await api.enrollment.start.callbackFn({ + data: { + token: openIdResponse.data.token, + }, + }); + useEnrollmentStore.setState({ + enrollmentData: enrollmentStartResponse.data, + token: openIdResponse.data.token, + }); throw redirect({ - to: '/client-setup', + to: '/download', search: search, + replace: true, }); }, }); diff --git a/webnext/src/routes/session-end.tsx b/webnext/src/routes/session-end.tsx new file mode 100644 index 00000000..56612c28 --- /dev/null +++ b/webnext/src/routes/session-end.tsx @@ -0,0 +1,10 @@ +import { createFileRoute } from '@tanstack/react-router'; +import { SessionEndPage } from '../pages/SessionEnd/SessionEndPage'; +import { useEnrollmentStore } from '../shared/hooks/useEnrollmentStore'; + +export const Route = createFileRoute('/session-end')({ + component: SessionEndPage, + loader: () => { + useEnrollmentStore.getState().reset(); + }, +}); diff --git a/webnext/src/shared/components/PageNavigation/PageNavigation.tsx b/webnext/src/shared/components/PageNavigation/PageNavigation.tsx index b25bb33e..192e6a1d 100644 --- a/webnext/src/shared/components/PageNavigation/PageNavigation.tsx +++ b/webnext/src/shared/components/PageNavigation/PageNavigation.tsx @@ -1,10 +1,11 @@ import { Button } from '../../defguard-ui/components/Button/Button'; +import { isPresent } from '../../defguard-ui/utils/isPresent'; import './style.scss'; type Props = { loading?: boolean; nextText: string; - backText: string; + backText?: string; onNext?: () => void; onBack?: () => void; backDisabled?: boolean; @@ -24,13 +25,16 @@ export const PageNavigation = ({
- - - -
-
{m.client_setup_mobile_title()}
-

{m.client_setup_mobile_subtitle()}

-
-
-
- -
-
-

{m.client_setup_mobile_forgot()}

-
- -
+
-
-
- + + + )}

{m.client_setup_footer_extra()}

diff --git a/webnext/src/pages/enrollment/ConfigureClient/style.scss b/webnext/src/pages/enrollment/ConfigureClient/style.scss index 0f1efb35..b54130dc 100644 --- a/webnext/src/pages/enrollment/ConfigureClient/style.scss +++ b/webnext/src/pages/enrollment/ConfigureClient/style.scss @@ -18,11 +18,18 @@ .finish { color: var(--fg-success); padding-bottom: var(--spacing-sm); + font: var(--t-body-xs-500); } } } #configure-client-page #setup-desktop { + header { + h5 { + padding-bottom: var(--spacing-xl); + } + } + .buttons { display: flex; flex-flow: row; diff --git a/webnext/src/shared/components/Page/style.scss b/webnext/src/shared/components/Page/style.scss index d82dcfda..5f585a68 100644 --- a/webnext/src/shared/components/Page/style.scss +++ b/webnext/src/shared/components/Page/style.scss @@ -1,15 +1,47 @@ .page { - --content-cols: 4 / 10; + --content-cols: 1 / 13; width: 100%; min-height: inherit; - &.variant-home { + @include break-up(md) { + --content-cols: 2 / 12; + } + + @include break-up(lg) { --content-cols: 3 / 11; } + @include break-up(xl) { + --content-cols: 4 / 10; + } + + &.variant-home { + --content-cols: 1 / 13; + + @include break-up(lg) { + --content-cols: 2 / 12; + } + + @include break-up(xl) { + --content-cols: 3 / 11; + } + } + &.variant-small { - --content-cols: 5 / 9; + --content-cols: 1 / 13; + + @include break-up(sm) { + --content-cols: 2 / 12; + } + + @include break-up(md) { + --content-cols: 4 / 10; + } + + @include break-up(xl) { + --content-cols: 5 / 9; + } } &.nav { diff --git a/webnext/src/shared/components/Step/Step.tsx b/webnext/src/shared/components/Step/Step.tsx index 1efbb149..1067e4ff 100644 --- a/webnext/src/shared/components/Step/Step.tsx +++ b/webnext/src/shared/components/Step/Step.tsx @@ -15,8 +15,8 @@ export const EnrollmentStep = ({ current, max }: Props) => { className={clsx('enrollment-step', { final: isFinal, })} - data-stepCurrent={current} - data-stepMax={max} + data-step-current={current} + data-step-max={max} > {isFinal diff --git a/webnext/src/shared/consts.ts b/webnext/src/shared/consts.ts index a49c392a..3750b7eb 100644 --- a/webnext/src/shared/consts.ts +++ b/webnext/src/shared/consts.ts @@ -7,23 +7,8 @@ export const motionTransitionStandard = { export const externalLink = { client: { desktop: { - windows: - 'https://github.com/DefGuard/client/releases/download/v1.5.1/defguard-client_1.5.1_x64_en-US.exe', linux: { arch: 'https://aur.archlinux.org/packages/defguard-client', - deb: { - amd: 'https://github.com/DefGuard/client/releases/download/v1.5.1/defguard-client_1.5.1_amd64.deb', - arm: 'https://github.com/DefGuard/client/releases/download/v1.5.1/dg-linux-aarch64-v1.5.1.deb', - }, - rpm: { - amd: 'https://github.com/DefGuard/client/releases/download/v1.5.1/defguard-client-1.5.1-1.x86_64.rpm', - arm: 'https://github.com/DefGuard/client/releases/download/v1.5.1/defguard-client-1.5.1-1.aarch64.rpm', - }, - }, - macos: { - intel: - 'https://github.com/DefGuard/client/releases/download/v1.5.1/defguard-x86_64-apple-darwin-1.5.1.pkg', - arm: 'https://github.com/DefGuard/client/releases/download/v1.5.1/defguard-aarch64-apple-darwin-1.5.1.pkg', }, }, mobile: { diff --git a/webnext/src/shared/defguard-ui/components/Icon/Icon.tsx b/webnext/src/shared/defguard-ui/components/Icon/Icon.tsx index d0b93006..ddfff55f 100644 --- a/webnext/src/shared/defguard-ui/components/Icon/Icon.tsx +++ b/webnext/src/shared/defguard-ui/components/Icon/Icon.tsx @@ -5,6 +5,7 @@ import type { Direction } from '../../types'; import { IconAndroid } from './icons/IconAndroid'; import { IconApple } from './icons/IconApple'; import { IconAppStore } from './icons/IconAppstore'; +import { IconArchLinux } from './icons/IconArchLinux'; import { IconArrowBig } from './icons/IconArrowBig'; import { IconArrowSmall } from './icons/IconArrowSmall'; import { IconCheckCircle } from './icons/IconCheckCircle'; @@ -12,6 +13,7 @@ import { IconCheckFilled } from './icons/IconCheckFilled'; import { IconClose } from './icons/IconClose'; import { IconConfig } from './icons/IconConfig'; import { IconCopy } from './icons/IconCopy'; +import { IconDebian } from './icons/IconDebian'; import { IconDesktop } from './icons/IconDesktop'; import { IconDisabled } from './icons/IconDisabled'; import { IconEmptyPoint } from './icons/IconEmptyPoint'; @@ -27,6 +29,7 @@ import { IconOpenInNewWindow } from './icons/IconOpenInNewWindow'; import { IconPlus } from './icons/IconPlus'; import { IconShow } from './icons/IconShow'; import { IconStatusSimple } from './icons/IconStatusSimple'; +import { IconUbuntu } from './icons/IconUbuntu'; import { IconWindows } from './icons/IconWindows'; type Props = { @@ -67,6 +70,12 @@ export const Icon = ({ }: Props) => { const IconToRender = useMemo(() => { switch (iconKind) { + case 'ubuntu': + return IconUbuntu; + case 'debian': + return IconDebian; + case 'arch-linux': + return IconArchLinux; case 'disabled': return IconDisabled; case 'show': diff --git a/webnext/src/shared/defguard-ui/components/Icon/icon-types.ts b/webnext/src/shared/defguard-ui/components/Icon/icon-types.ts index 0059b58c..554b2839 100644 --- a/webnext/src/shared/defguard-ui/components/Icon/icon-types.ts +++ b/webnext/src/shared/defguard-ui/components/Icon/icon-types.ts @@ -88,6 +88,9 @@ export const IconKind = { StatusAvailable: 'status-available', StatusImportant: 'status-important', Disabled: 'disabled', + ArchLinux: 'arch-linux', + Debian: 'debian', + Ubuntu: 'ubuntu', } as const; export type IconKindValue = (typeof IconKind)[keyof typeof IconKind]; diff --git a/webnext/src/shared/defguard-ui/components/Icon/icons/IconArchLinux.tsx b/webnext/src/shared/defguard-ui/components/Icon/icons/IconArchLinux.tsx new file mode 100644 index 00000000..ab1de5be --- /dev/null +++ b/webnext/src/shared/defguard-ui/components/Icon/icons/IconArchLinux.tsx @@ -0,0 +1,19 @@ +import type { SVGProps } from 'react'; + +export const IconArchLinux = (props: SVGProps) => { + return ( + + + + ); +}; diff --git a/webnext/src/shared/defguard-ui/components/Icon/icons/IconDebian.tsx b/webnext/src/shared/defguard-ui/components/Icon/icons/IconDebian.tsx new file mode 100644 index 00000000..74295c5b --- /dev/null +++ b/webnext/src/shared/defguard-ui/components/Icon/icons/IconDebian.tsx @@ -0,0 +1,19 @@ +import type { SVGProps } from 'react'; + +export const IconDebian = (props: SVGProps) => { + return ( + + + + ); +}; diff --git a/webnext/src/shared/defguard-ui/components/Icon/icons/IconUbuntu.tsx b/webnext/src/shared/defguard-ui/components/Icon/icons/IconUbuntu.tsx new file mode 100644 index 00000000..4279ff44 --- /dev/null +++ b/webnext/src/shared/defguard-ui/components/Icon/icons/IconUbuntu.tsx @@ -0,0 +1,19 @@ +import type { SVGProps } from 'react'; + +export const IconUbuntu = (props: SVGProps) => { + return ( + + + + ); +}; diff --git a/webnext/src/shared/defguard-ui/components/InfoBanner/InfoBanner.tsx b/webnext/src/shared/defguard-ui/components/InfoBanner/InfoBanner.tsx new file mode 100644 index 00000000..891c6fe2 --- /dev/null +++ b/webnext/src/shared/defguard-ui/components/InfoBanner/InfoBanner.tsx @@ -0,0 +1,37 @@ +import { useMemo } from 'react'; +import { Icon } from '../Icon'; +import type { IconKindValue } from '../Icon/icon-types'; +import './style.scss'; +import clsx from 'clsx'; +import { RenderMarkdown } from '../RenderMarkdown/RenderMarkdown'; + +type Props = { + icon: IconKindValue; + text: string | string[]; + variant?: 'info' | 'warning'; + markdown?: boolean; +}; + +export const InfoBanner = ({ icon, text, markdown = false, variant = 'info' }: Props) => { + const textContents = useMemo(() => { + if (markdown) { + const prop = Array.isArray(text) ? text.join('\n') : text; + return ; + } + if (Array.isArray(text)) { + return text.map((content, index) =>

{content}

); + } + return

{text}

; + }, [text, markdown]); + + return ( +
+
+
+ +
+
{textContents}
+
+
+ ); +}; diff --git a/webnext/src/shared/defguard-ui/components/InfoBanner/style.scss b/webnext/src/shared/defguard-ui/components/InfoBanner/style.scss new file mode 100644 index 00000000..39381448 --- /dev/null +++ b/webnext/src/shared/defguard-ui/components/InfoBanner/style.scss @@ -0,0 +1,69 @@ +.info-banner { + & > .inner { + --bg-color: transparent; + --icon-color: transparent; + --color: transparent; + + width: 100%; + max-width: 100%; + overflow: hidden; + display: grid; + grid-template-columns: 20px 1fr; + grid-template-rows: 1fr; + column-gap: var(--spacing-md); + background-color: var(--bg-color); + border-radius: var(--radius-lg); + box-sizing: border-box; + padding: var(--spacing-sm) var(--spacing-lg); + align-items: start; + justify-content: start; + + @include animate(background-color); + + &.variant-info { + --bg-color: var(--bg-disabled); + --color: var(--fg-muted); + --icon-color: var(--fg-muted); + } + + &.variant-warning { + --bg-color: var(--bg-warning); + --color: var(--fg-attention); + --icon-color: var(--fg-attention); + } + + & > .icon-track { + display: block; + width: 20px; + height: 20px; + user-select: none; + + svg { + path { + fill: var(--icon-color); + + @include animate(fill); + } + } + } + + & > .content { + p { + max-width: 100%; + overflow: hidden; + text-wrap: wrap; + text-wrap: balance; + text-align: left; + } + + p, + span, + a { + font: var(--t-body-xs-500); + color: var(--color); + + @include animate(color); + } + } + } +} diff --git a/webnext/src/shared/defguard-ui/components/Menu/style.scss b/webnext/src/shared/defguard-ui/components/Menu/style.scss index d12a694c..963e563f 100644 --- a/webnext/src/shared/defguard-ui/components/Menu/style.scss +++ b/webnext/src/shared/defguard-ui/components/Menu/style.scss @@ -7,6 +7,7 @@ border-radius: var(--radius-lg); border: 1px solid var(--border-disabled); background-color: var(--bg-default); + z-index: 3; .menu-spacer { user-select: none; diff --git a/webnext/src/shared/defguard-ui/components/RenderMarkdown/RenderMarkdown.tsx b/webnext/src/shared/defguard-ui/components/RenderMarkdown/RenderMarkdown.tsx new file mode 100644 index 00000000..4f3577d2 --- /dev/null +++ b/webnext/src/shared/defguard-ui/components/RenderMarkdown/RenderMarkdown.tsx @@ -0,0 +1,5 @@ +import ReactMarkdown from 'react-markdown'; + +export const RenderMarkdown = ({ content }: { content?: string | null | undefined }) => { + return {content}; +}; From 946578094d12743b14b76b012ba2f3e1f38a226b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20=C5=9Al=C4=99zak?= Date: Wed, 8 Oct 2025 13:00:36 +0200 Subject: [PATCH 3/6] new style of download on client config page --- webnext/messages/en.json | 2 + .../ClientDownload/ClientDownloadPage.tsx | 70 ++++++++++------ .../ConfigureClient/ConfigureClientPage.tsx | 81 ++++++++++++------- .../enrollment/ConfigureClient/style.scss | 26 ++++-- webnext/src/routes/download.tsx | 23 +++++- webnext/src/shared/consts.ts | 3 + .../components/ButtonMenu/MenuButton.tsx | 5 +- .../components/IconButton/IconButton.tsx | 16 +--- .../components/IconButton/style.scss | 8 +- .../components/IconButton/types.ts | 9 +++ .../IconButtonMenu/IconButtonMenu.tsx | 78 ++++++++++++++++++ .../defguard-ui/components/Menu/style.scss | 5 +- 12 files changed, 246 insertions(+), 80 deletions(-) create mode 100644 webnext/src/shared/defguard-ui/components/IconButton/types.ts create mode 100644 webnext/src/shared/defguard-ui/components/IconButtonMenu/IconButtonMenu.tsx diff --git a/webnext/messages/en.json b/webnext/messages/en.json index 88e50373..0ef7a737 100644 --- a/webnext/messages/en.json +++ b/webnext/messages/en.json @@ -73,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", @@ -81,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.", diff --git a/webnext/src/pages/ClientDownload/ClientDownloadPage.tsx b/webnext/src/pages/ClientDownload/ClientDownloadPage.tsx index 5ce4cf0c..31294f87 100644 --- a/webnext/src/pages/ClientDownload/ClientDownloadPage.tsx +++ b/webnext/src/pages/ClientDownload/ClientDownloadPage.tsx @@ -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'; @@ -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'; @@ -28,6 +34,9 @@ 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(); @@ -164,33 +173,42 @@ export const ClientDownloadPage = () => { />
-
-
-

{m.client_download_label_mobile()}

+ {enrollmentState.enrollmentData.user.enrolled && ( +
+
+

{m.client_download_label_mobile()}

+
+ +
- - -
+ )} { diff --git a/webnext/src/pages/enrollment/ConfigureClient/ConfigureClientPage.tsx b/webnext/src/pages/enrollment/ConfigureClient/ConfigureClientPage.tsx index 1f6949e9..7211c4c9 100644 --- a/webnext/src/pages/enrollment/ConfigureClient/ConfigureClientPage.tsx +++ b/webnext/src/pages/enrollment/ConfigureClient/ConfigureClientPage.tsx @@ -13,11 +13,12 @@ import { Page } from '../../../shared/components/Page/Page'; 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 { CopyField } from '../../../shared/defguard-ui/components/CopyField/CopyField'; import { Divider } from '../../../shared/defguard-ui/components/Divider/Divider'; import { Fold } from '../../../shared/defguard-ui/components/Fold/Fold'; import { Icon } from '../../../shared/defguard-ui/components/Icon'; +import { IconButton } from '../../../shared/defguard-ui/components/IconButton/IconButton'; +import { IconButtonMenu } from '../../../shared/defguard-ui/components/IconButtonMenu/IconButtonMenu'; import type { MenuItemsGroup } from '../../../shared/defguard-ui/components/Menu/types'; import { SizedBox } from '../../../shared/defguard-ui/components/SizedBox/SizedBox'; import { ThemeSpacing } from '../../../shared/defguard-ui/types'; @@ -33,17 +34,8 @@ export const ConfigureClientPage = () => { const [appleHelpModalOpen, setAppleHelpModalOpen] = useState(false); - const clientDownloadMenu = useMemo( + const appleMenu = useMemo( (): MenuItemsGroup[] => [ - { - items: [ - { - text: 'Windows', - icon: 'windows', - onClick: () => openVirtualLink(clientLinks?.windows_amd64), - }, - ], - }, { header: { text: m.client_download_apple_help_header(), @@ -62,26 +54,45 @@ export const ConfigureClientPage = () => { }, ], }, + ], + [clientLinks], + ); + + const linuxMenu = useMemo(() => { + const res: MenuItemsGroup[] = [ { header: { text: `${capitalCase(m.misc_for())} Linux`, }, items: [ { - icon: 'linux', - text: 'Deb X86', + icon: 'ubuntu', + text: 'Ubuntu 24.04 ARM', + onClick: () => openVirtualLink(clientLinks?.deb_arm64), + }, + { + icon: 'ubuntu', + text: 'Ubuntu 24.04 AMD64', onClick: () => openVirtualLink(clientLinks?.deb_amd64), }, + ], + }, + { + items: [ { - icon: 'linux', - text: 'Deb ARM', + icon: 'debian', + text: 'Ubuntu 22.04 / Debian 12&13 ARM', onClick: () => openVirtualLink(clientLinks?.deb_arm64), }, { - icon: 'linux', - text: 'RPM X86', - onClick: () => openVirtualLink(clientLinks?.rpm_amd64), + icon: 'debian', + text: 'Ubuntu 22.04 / Debian 12&13 AMD64', + onClick: () => openVirtualLink(clientLinks?.deb_amd64), }, + ], + }, + { + items: [ { icon: 'linux', text: 'RPM ARM', @@ -89,14 +100,23 @@ export const ConfigureClientPage = () => { }, { icon: 'linux', + text: 'RPM AMD64', + onClick: () => openVirtualLink(clientLinks?.rpm_amd64), + }, + ], + }, + { + items: [ + { + icon: 'arch-linux', text: 'Arch Linux', onClick: () => openVirtualLink(externalLink.client.desktop.linux.arch), }, ], }, - ], - [clientLinks], - ); + ]; + return res; + }, [clientLinks]); const [manualOpen, setManualOpen] = useState(false); @@ -139,13 +159,18 @@ export const ConfigureClientPage = () => { iconRight="open-in-new-window" /> - +
+

{m.client_setup_download_label()}

+ + + + + +
diff --git a/webnext/src/pages/enrollment/ConfigureClient/style.scss b/webnext/src/pages/enrollment/ConfigureClient/style.scss index b54130dc..a9dcff7f 100644 --- a/webnext/src/pages/enrollment/ConfigureClient/style.scss +++ b/webnext/src/pages/enrollment/ConfigureClient/style.scss @@ -24,17 +24,25 @@ } #configure-client-page #setup-desktop { - header { - h5 { - padding-bottom: var(--spacing-xl); - } - } - .buttons { display: flex; - flex-flow: row; + flex-flow: row wrap; align-items: center; justify-content: space-between; + gap: var(--spacing-lg); + + & > .download { + display: flex; + flex-flow: row; + align-items: center; + justify-content: flex-end; + column-gap: var(--spacing-md); + + & > p { + font: var(--t-body-xs-400); + color: var(--fg-muted); + } + } } .divider { @@ -64,6 +72,10 @@ } header { + h5 { + padding-bottom: var(--spacing-xl); + } + p { &:nth-child(2) { font: var(--t-body-sm-600); diff --git a/webnext/src/routes/download.tsx b/webnext/src/routes/download.tsx index 81378f90..a9f1ddfa 100644 --- a/webnext/src/routes/download.tsx +++ b/webnext/src/routes/download.tsx @@ -1,9 +1,28 @@ -import { createFileRoute } from '@tanstack/react-router'; +import { createFileRoute, redirect } from '@tanstack/react-router'; import { queryClient } from '../app/query'; import { ClientDownloadPage } from '../pages/ClientDownload/ClientDownloadPage'; +import { useEnrollmentStore } from '../shared/hooks/useEnrollmentStore'; import { getClientArtifactsQueryOptions } from '../shared/query/queryOptions'; export const Route = createFileRoute('/download')({ component: ClientDownloadPage, - loader: () => queryClient.ensureQueryData(getClientArtifactsQueryOptions), + loaderDeps: () => { + const storeState = useEnrollmentStore.getState(); + if (storeState.enrollmentData === undefined || storeState.token === undefined) { + throw redirect({ + to: '/', + replace: true, + }); + } + return { + enrollmentState: { + token: storeState.token, + enrollmentData: storeState.enrollmentData, + }, + }; + }, + loader: ({ deps }) => { + void queryClient.ensureQueryData(getClientArtifactsQueryOptions); + return deps; + }, }); diff --git a/webnext/src/shared/consts.ts b/webnext/src/shared/consts.ts index 3750b7eb..7d578318 100644 --- a/webnext/src/shared/consts.ts +++ b/webnext/src/shared/consts.ts @@ -5,6 +5,9 @@ export const motionTransitionStandard = { } as const; export const externalLink = { + defguard: { + download: 'https://defguard.net/download', + }, client: { desktop: { linux: { diff --git a/webnext/src/shared/defguard-ui/components/ButtonMenu/MenuButton.tsx b/webnext/src/shared/defguard-ui/components/ButtonMenu/MenuButton.tsx index 769fbc3b..841f841f 100644 --- a/webnext/src/shared/defguard-ui/components/ButtonMenu/MenuButton.tsx +++ b/webnext/src/shared/defguard-ui/components/ButtonMenu/MenuButton.tsx @@ -2,6 +2,7 @@ import { autoUpdate, FloatingPortal, offset, + shift, size, useClick, useDismiss, @@ -28,10 +29,12 @@ export const ButtonMenu = ({ open: isOpen, middleware: [ offset(4), + shift(), size({ - apply({ rects, elements }) { + apply({ rects, elements, availableHeight }) { const refWidth = `${rects.reference.width}px`; elements.floating.style.minWidth = refWidth; + elements.floating.style.maxHeight = `${availableHeight - 10}px`; }, }), ], diff --git a/webnext/src/shared/defguard-ui/components/IconButton/IconButton.tsx b/webnext/src/shared/defguard-ui/components/IconButton/IconButton.tsx index a0976442..bd776c2e 100644 --- a/webnext/src/shared/defguard-ui/components/IconButton/IconButton.tsx +++ b/webnext/src/shared/defguard-ui/components/IconButton/IconButton.tsx @@ -1,26 +1,18 @@ import './style.scss'; import clsx from 'clsx'; -import type { Ref } from 'react'; import { Icon } from '../Icon/Icon'; -import type { IconKindValue } from '../Icon/icon-types'; +import type { IconButtonProps } from './types'; -type Props = { - icon: IconKindValue; - disabled?: boolean; - ref?: Ref; - onClick?: () => void; -}; - -export const IconButton = ({ icon, ref, disabled = false, onClick }: Props) => { +export const IconButton = ({ icon, ref, disabled = false, onClick }: IconButtonProps) => { return (
{ + onClick={(e) => { if (!disabled) { - onClick?.(); + onClick?.(e); } }} > diff --git a/webnext/src/shared/defguard-ui/components/IconButton/style.scss b/webnext/src/shared/defguard-ui/components/IconButton/style.scss index d2197d8b..56d3fbd5 100644 --- a/webnext/src/shared/defguard-ui/components/IconButton/style.scss +++ b/webnext/src/shared/defguard-ui/components/IconButton/style.scss @@ -1,5 +1,5 @@ .icon-button { - --icon-button-icon-color: var(--fg-neutral); + --icon-color: var(--fg-muted); background-color: transparent; border-radius: var(--button-border-radius-sm); @@ -16,17 +16,19 @@ cursor: pointer; &:hover { + --icon-color: var(--fg-neutral); + background-color: var(--bg-emphasis); } } &.disabled { cursor: not-allowed; - --icon-button-icon-color: var(--fg-disabled); + --icon-color: var(--fg-disabled); } & > .icon svg path { - fill: var(--icon-button-icon-color); + fill: var(--icon-color); @include animate(fill); } diff --git a/webnext/src/shared/defguard-ui/components/IconButton/types.ts b/webnext/src/shared/defguard-ui/components/IconButton/types.ts new file mode 100644 index 00000000..55501dca --- /dev/null +++ b/webnext/src/shared/defguard-ui/components/IconButton/types.ts @@ -0,0 +1,9 @@ +import type { MouseEventHandler, Ref } from 'react'; +import type { IconKindValue } from '../Icon/icon-types'; + +export type IconButtonProps = { + icon: IconKindValue; + disabled?: boolean; + ref?: Ref; + onClick?: MouseEventHandler; +}; diff --git a/webnext/src/shared/defguard-ui/components/IconButtonMenu/IconButtonMenu.tsx b/webnext/src/shared/defguard-ui/components/IconButtonMenu/IconButtonMenu.tsx new file mode 100644 index 00000000..92c56b5c --- /dev/null +++ b/webnext/src/shared/defguard-ui/components/IconButtonMenu/IconButtonMenu.tsx @@ -0,0 +1,78 @@ +import { + autoUpdate, + FloatingPortal, + offset, + shift, + size, + useClick, + useDismiss, + useFloating, + useInteractions, +} from '@floating-ui/react'; +import { useState } from 'react'; +import { mergeRefs } from '../../utils/mergeRefs'; +import { IconButton } from '../IconButton/IconButton'; +import type { IconButtonProps } from '../IconButton/types'; +import { Menu } from '../Menu/Menu'; +import type { MenuItemsGroup } from '../Menu/types'; + +export const IconButtonMenu = ({ + menuItems, + ref, + ...buttonProps +}: IconButtonProps & { + menuItems: MenuItemsGroup[]; +}) => { + const [isOpen, setOpen] = useState(false); + const { refs, context, floatingStyles } = useFloating({ + placement: 'bottom-end', + whileElementsMounted: autoUpdate, + onOpenChange: setOpen, + open: isOpen, + middleware: [ + offset(4), + shift(), + size({ + apply({ rects, elements, availableHeight }) { + const refWidth = `${rects.reference.width}px`; + elements.floating.style.minWidth = refWidth; + elements.floating.style.maxHeight = `${availableHeight - 10}px`; + }, + }), + ], + }); + const click = useClick(context, { + toggle: true, + }); + + const dismiss = useDismiss(context, { + ancestorScroll: true, + escapeKey: true, + outsidePress: (event) => !(event.target as HTMLElement).closest('.menu'), + }); + + const { getFloatingProps, getReferenceProps } = useInteractions([click, dismiss]); + + return ( + <> + + {isOpen && ( + + { + setOpen(false); + }} + {...getFloatingProps()} + /> + + )} + + ); +}; diff --git a/webnext/src/shared/defguard-ui/components/Menu/style.scss b/webnext/src/shared/defguard-ui/components/Menu/style.scss index 963e563f..59646557 100644 --- a/webnext/src/shared/defguard-ui/components/Menu/style.scss +++ b/webnext/src/shared/defguard-ui/components/Menu/style.scss @@ -7,11 +7,12 @@ border-radius: var(--radius-lg); border: 1px solid var(--border-disabled); background-color: var(--bg-default); + overflow: hidden auto; z-index: 3; .menu-spacer { user-select: none; - padding: var(--spacing-sm); + padding: var(--spacing-sm) 0; & > .line { display: block; @@ -27,6 +28,7 @@ flex-flow: row nowrap; column-gap: var(--spacing-md); justify-content: space-between; + flex: none; p { font: var(--t-menu-title); @@ -57,6 +59,7 @@ display: flex; flex-flow: row nowrap; + flex: none; align-items: center; border-radius: var(--radius-md); padding: 0 var(--spacing-sm); From b7f84dda16dec0ebcff9211b2e2a24d1d1e9a354 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20=C5=9Al=C4=99zak?= Date: Wed, 8 Oct 2025 13:04:06 +0200 Subject: [PATCH 4/6] fix step final style --- webnext/src/shared/components/Step/style.scss | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/webnext/src/shared/components/Step/style.scss b/webnext/src/shared/components/Step/style.scss index 3df0dd59..5272bae1 100644 --- a/webnext/src/shared/components/Step/style.scss +++ b/webnext/src/shared/components/Step/style.scss @@ -14,7 +14,8 @@ @include animate(background-color); &.final { - --color: var(--bg-success); + --color: var(--fg-white); + --bg-color: var(--bg-success); } span { From 6a4d1a4b5e74390cde1b35780a2ac05722c506b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20=C5=9Al=C4=99zak?= Date: Wed, 8 Oct 2025 13:05:05 +0200 Subject: [PATCH 5/6] correct step counter config --- webnext/src/pages/ClientDownload/ClientDownloadPage.tsx | 2 +- .../pages/enrollment/EnrollmentStart/EnrollmentStartPage.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/webnext/src/pages/ClientDownload/ClientDownloadPage.tsx b/webnext/src/pages/ClientDownload/ClientDownloadPage.tsx index 31294f87..3e26b832 100644 --- a/webnext/src/pages/ClientDownload/ClientDownloadPage.tsx +++ b/webnext/src/pages/ClientDownload/ClientDownloadPage.tsx @@ -127,7 +127,7 @@ export const ClientDownloadPage = () => { return ( - +

{m.client_download_title()}

{m.client_download_subtitle()}

diff --git a/webnext/src/pages/enrollment/EnrollmentStart/EnrollmentStartPage.tsx b/webnext/src/pages/enrollment/EnrollmentStart/EnrollmentStartPage.tsx index 6b857ab7..36c73659 100644 --- a/webnext/src/pages/enrollment/EnrollmentStart/EnrollmentStartPage.tsx +++ b/webnext/src/pages/enrollment/EnrollmentStart/EnrollmentStartPage.tsx @@ -81,7 +81,7 @@ export const EnrollmentStartPage = () => { return ( - +

{m.enrollment_start_title()}

{m.enrollment_start_subtitle()}

From 942f72eb556283aab572d05158a807bc6dd64211 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20=C5=9Al=C4=99zak?= Date: Wed, 8 Oct 2025 13:06:40 +0200 Subject: [PATCH 6/6] Update ConfigureClientPage.tsx --- .../pages/enrollment/ConfigureClient/ConfigureClientPage.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/webnext/src/pages/enrollment/ConfigureClient/ConfigureClientPage.tsx b/webnext/src/pages/enrollment/ConfigureClient/ConfigureClientPage.tsx index 7211c4c9..8bae4dbf 100644 --- a/webnext/src/pages/enrollment/ConfigureClient/ConfigureClientPage.tsx +++ b/webnext/src/pages/enrollment/ConfigureClient/ConfigureClientPage.tsx @@ -132,8 +132,6 @@ export const ConfigureClientPage = () => { ); }; - console.log(pageData); - return (