Skip to content

Commit f7742a3

Browse files
committed
Merge branch 'dev' into config-polling
2 parents 430c171 + 925ed2f commit f7742a3

File tree

9 files changed

+100
-30
lines changed

9 files changed

+100
-30
lines changed

web/src/pages/enrollment/components/EnrollmentSideBar/EnrollmentSideBar.tsx

+17-10
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,14 @@ import { TimeLeft } from '../TimeLeft/TimeLeft';
1414
export const EnrollmentSideBar = () => {
1515
const { LL } = useI18nContext();
1616

17-
const vpnOptional = useEnrollmentStore((state) => state.vpnOptional);
17+
const vpnOptional = useEnrollmentStore(
18+
(state) => state.enrollmentSettings?.vpn_setup_optional,
19+
);
1820
const [currentStep, stepsMax] = useEnrollmentStore((state) => [
1921
state.step,
2022
state.stepsMax,
2123
]);
24+
const enrollmentSettings = useEnrollmentStore((state) => state.enrollmentSettings);
2225

2326
// fetch app version
2427
const { getAppInfo } = useApi();
@@ -37,16 +40,20 @@ export const EnrollmentSideBar = () => {
3740
}, []);
3841

3942
const steps = useMemo((): LocalizedString[] => {
40-
const steps = LL.pages.enrollment.sideBar.steps;
41-
const vpnStep = vpnOptional ? `${steps.vpn()}*` : steps.vpn();
42-
return [
43-
steps.welcome(),
44-
steps.verification(),
45-
steps.password(),
46-
vpnStep as LocalizedString,
47-
steps.finish(),
43+
const stepsLL = LL.pages.enrollment.sideBar.steps;
44+
const vpnStep = (
45+
vpnOptional ? `${stepsLL.vpn()}*` : stepsLL.vpn()
46+
) as LocalizedString;
47+
const steps = [
48+
stepsLL.welcome(),
49+
stepsLL.verification(),
50+
stepsLL.password(),
51+
...(!enrollmentSettings?.only_client_activation
52+
? [vpnStep, stepsLL.finish()]
53+
: [stepsLL.finish()]),
4854
];
49-
}, [LL.pages.enrollment.sideBar.steps, vpnOptional]);
55+
return steps;
56+
}, [LL.pages.enrollment.sideBar.steps, vpnOptional, enrollmentSettings]);
5057

5158
return (
5259
<div id="enrollment-side-bar">

web/src/pages/enrollment/hooks/store/useEnrollmentStore.tsx

+3-2
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
AdminInfo,
88
Device,
99
DeviceConfig,
10+
EnrollmentSettings,
1011
UserInfo,
1112
} from '../../../../shared/hooks/api/types';
1213

@@ -30,7 +31,7 @@ const persistKeys: Array<keyof StoreValues> = [
3031
'adminInfo',
3132
'deviceState',
3233
'endContent',
33-
'vpnOptional',
34+
'enrollmentSettings',
3435
];
3536

3637
export const useEnrollmentStore = createWithEqualityFn<Store>()(
@@ -82,7 +83,7 @@ type StoreValues = {
8283
userInfo?: UserInfo;
8384
userPassword?: string;
8485
adminInfo?: AdminInfo;
85-
vpnOptional?: boolean;
86+
enrollmentSettings?: EnrollmentSettings;
8687
// Markdown content for final step card
8788
endContent?: string;
8889
deviceState?: {

web/src/pages/enrollment/steps/DeviceStep/DeviceStep.tsx

+44-14
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,11 @@ import { useEffect } from 'react';
77
import { shallow } from 'zustand/shallow';
88

99
import { useI18nContext } from '../../../../i18n/i18n-react';
10+
import { LoaderSpinner } from '../../../../shared/components/layout/LoaderSpinner/LoaderSpinner';
1011
import { MessageBox } from '../../../../shared/components/layout/MessageBox/MessageBox';
1112
import { MessageBoxType } from '../../../../shared/components/layout/MessageBox/types';
1213
import { useApi } from '../../../../shared/hooks/api/useApi';
14+
import useEffectOnce from '../../../../shared/hooks/api/utils';
1315
import { useEnrollmentStore } from '../../hooks/store/useEnrollmentStore';
1416
import { ConfigureDeviceCard } from './components/ConfigureDeviceCard/ConfigureDeviceCard';
1517
import { QuickGuideCard } from './components/QuickGuideCard/QuickGuideCard';
@@ -21,7 +23,7 @@ export const DeviceStep = () => {
2123
const { LL } = useI18nContext();
2224
const setStore = useEnrollmentStore((state) => state.setState);
2325
const deviceState = useEnrollmentStore((state) => state.deviceState);
24-
const vpnOptional = useEnrollmentStore((state) => state.vpnOptional);
26+
const settings = useEnrollmentStore((state) => state.enrollmentSettings);
2527
const [userPhone, userPassword] = useEnrollmentStore(
2628
(state) => [state.userInfo?.phone_number, state.userPassword],
2729
shallow,
@@ -32,8 +34,8 @@ export const DeviceStep = () => {
3234
);
3335

3436
const cn = classNames({
35-
required: !vpnOptional,
36-
optional: vpnOptional,
37+
required: !settings?.vpn_setup_optional,
38+
optional: settings?.vpn_setup_optional,
3739
});
3840

3941
const { mutate } = useMutation({
@@ -51,7 +53,11 @@ export const DeviceStep = () => {
5153
useEffect(() => {
5254
if (userPassword) {
5355
const sub = nextSubject.subscribe(() => {
54-
if ((deviceState && deviceState.device && deviceState.configs) || vpnOptional) {
56+
if (
57+
(deviceState && deviceState.device && deviceState.configs) ||
58+
settings?.vpn_setup_optional ||
59+
settings?.only_client_activation
60+
) {
5561
setStore({
5662
loading: true,
5763
});
@@ -66,20 +72,44 @@ export const DeviceStep = () => {
6672
sub.unsubscribe();
6773
};
6874
}
69-
}, [deviceState, nextSubject, vpnOptional, setStore, userPhone, userPassword, mutate]);
75+
}, [
76+
deviceState,
77+
nextSubject,
78+
settings?.vpn_setup_optional,
79+
setStore,
80+
userPhone,
81+
userPassword,
82+
mutate,
83+
settings?.only_client_activation,
84+
]);
85+
86+
// If only client activation is enabled, skip manual wireguard setup
87+
useEffectOnce(() => {
88+
if (settings?.only_client_activation) {
89+
nextSubject.next();
90+
}
91+
});
7092

7193
return (
7294
<div id="enrollment-device-step" className={cn}>
73-
{vpnOptional && (
74-
<MessageBox
75-
type={MessageBoxType.WARNING}
76-
message={LL.pages.enrollment.steps.deviceSetup.optionalMessage()}
77-
/>
95+
{!settings?.only_client_activation ? (
96+
<>
97+
{settings?.vpn_setup_optional && (
98+
<MessageBox
99+
type={MessageBoxType.WARNING}
100+
message={LL.pages.enrollment.steps.deviceSetup.optionalMessage()}
101+
/>
102+
)}
103+
<div className="cards">
104+
<ConfigureDeviceCard />
105+
<QuickGuideCard />
106+
</div>
107+
</>
108+
) : (
109+
<div id="loader">
110+
<LoaderSpinner size={80} />
111+
</div>
78112
)}
79-
<div className="cards">
80-
<ConfigureDeviceCard />
81-
<QuickGuideCard />
82-
</div>
83113
</div>
84114
);
85115
};

web/src/pages/enrollment/steps/DeviceStep/style.scss

+7
Original file line numberDiff line numberDiff line change
@@ -45,4 +45,11 @@
4545
}
4646
}
4747
}
48+
49+
#loader {
50+
display: flex;
51+
justify-content: center;
52+
align-items: center;
53+
height: 500px;
54+
}
4855
}

web/src/pages/main/MainPage.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,8 @@ export const MainPage = () => {
4040
adminInfo: res.admin,
4141
sessionStart,
4242
sessionEnd,
43-
vpnOptional: res.vpn_setup_optional,
4443
endContent: res.final_page_content,
44+
enrollmentSettings: res.settings,
4545
});
4646
navigate(routes.enrollment, { replace: true });
4747
})

web/src/pages/token/components/TokenCard.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ export const TokenCard = () => {
7373
adminInfo: res.admin,
7474
sessionStart,
7575
sessionEnd,
76-
vpnOptional: res.vpn_setup_optional,
76+
enrollmentSettings: res.settings,
7777
endContent: res.final_page_content,
7878
});
7979
navigate(routes.enrollment, { replace: true });

web/src/shared/hooks/api/types.ts

+6-1
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,17 @@ export type EnrollmentStartRequest = {
1818
token: string;
1919
};
2020

21+
export type EnrollmentSettings = {
22+
vpn_setup_optional: boolean;
23+
only_client_activation: boolean;
24+
};
25+
2126
export type EnrollmentStartResponse = {
2227
admin: AdminInfo;
2328
user: UserInfo;
2429
deadline_timestamp: number;
2530
final_page_content: string;
26-
vpn_setup_optional: boolean;
31+
settings: EnrollmentSettings;
2732
};
2833

2934
export type ActivateUserRequest = {

web/src/shared/hooks/api/utils.ts

+20
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,27 @@
1+
import { useEffect, useRef } from 'react';
2+
13
// eslint-disable-next-line @typescript-eslint/no-explicit-any
24
export const removeNulls = (obj: any) => {
35
return JSON.parse(JSON.stringify(obj), (_, value) => {
46
if (value == null) return undefined;
57
return value;
68
});
79
};
10+
11+
/**
12+
Under normal circumstances, useEffect should run only once when passed an empty dependency array.
13+
However, in dev mode with react strict mode enabled, everything is rendered twice for debugging purposes.
14+
This also causes useEffect to run twice, which is not always desirable.
15+
This custom hook ensures that the effect runs only once in dev mode as well.
16+
*/
17+
export default function useEffectOnce(fn: () => void) {
18+
const isMounted = useRef(false);
19+
useEffect(() => {
20+
if (isMounted.current) {
21+
return;
22+
}
23+
24+
fn();
25+
isMounted.current = true;
26+
}, [fn]);
27+
}

web/vite.config.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import { defineConfig } from 'vite';
21
import react from '@vitejs/plugin-react-swc';
32
import autoprefixer from 'autoprefixer';
43
import * as path from 'path';
4+
import { defineConfig } from 'vite';
55

66
// https://vitejs.dev/config/
77
export default defineConfig({

0 commit comments

Comments
 (0)