@lobehub/lobehub 2.0.0-next.316 → 2.0.0-next.317
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +25 -0
- package/changelog/v1.json +5 -0
- package/package.json +1 -1
- package/scripts/generate-oidc-jwk.mjs +3 -1
- package/src/app/[variants]/(desktop)/desktop-onboarding/features/LoginStep.tsx +56 -11
- package/src/app/[variants]/(desktop)/desktop-onboarding/index.tsx +103 -94
- package/src/app/[variants]/(desktop)/desktop-onboarding/navigation.ts +11 -0
- package/src/app/[variants]/(desktop)/desktop-onboarding/storage.ts +15 -17
- package/src/app/[variants]/(desktop)/desktop-onboarding/types.ts +11 -0
- package/src/features/User/UserPanel/PanelContent.tsx +11 -10
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,31 @@
|
|
|
2
2
|
|
|
3
3
|
# Changelog
|
|
4
4
|
|
|
5
|
+
## [Version 2.0.0-next.317](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.316...v2.0.0-next.317)
|
|
6
|
+
|
|
7
|
+
<sup>Released on **2026-01-19**</sup>
|
|
8
|
+
|
|
9
|
+
#### 🐛 Bug Fixes
|
|
10
|
+
|
|
11
|
+
- **desktop**: Resolve onboarding navigation issues after logout.
|
|
12
|
+
|
|
13
|
+
<br/>
|
|
14
|
+
|
|
15
|
+
<details>
|
|
16
|
+
<summary><kbd>Improvements and Fixes</kbd></summary>
|
|
17
|
+
|
|
18
|
+
#### What's fixed
|
|
19
|
+
|
|
20
|
+
- **desktop**: Resolve onboarding navigation issues after logout, closes [#11628](https://github.com/lobehub/lobe-chat/issues/11628) ([05a0873](https://github.com/lobehub/lobe-chat/commit/05a0873))
|
|
21
|
+
|
|
22
|
+
</details>
|
|
23
|
+
|
|
24
|
+
<div align="right">
|
|
25
|
+
|
|
26
|
+
[](#readme-top)
|
|
27
|
+
|
|
28
|
+
</div>
|
|
29
|
+
|
|
5
30
|
## [Version 2.0.0-next.316](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.315...v2.0.0-next.316)
|
|
6
31
|
|
|
7
32
|
<sup>Released on **2026-01-19**</sup>
|
package/changelog/v1.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lobehub/lobehub",
|
|
3
|
-
"version": "2.0.0-next.
|
|
3
|
+
"version": "2.0.0-next.317",
|
|
4
4
|
"description": "LobeHub - an open-source,comprehensive AI Agent framework that supports speech synthesis, multimodal, and extensible Function Call plugin system. Supports one-click free deployment of your private ChatGPT/LLM web application.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"framework",
|
|
@@ -21,7 +21,9 @@ async function generateJwks() {
|
|
|
21
21
|
console.error('正在生成 RSA 密钥对...');
|
|
22
22
|
|
|
23
23
|
// 生成 RS256 密钥对
|
|
24
|
-
const { privateKey } = await generateKeyPair('RS256'
|
|
24
|
+
const { privateKey } = await generateKeyPair('RS256', {
|
|
25
|
+
extractable: true,
|
|
26
|
+
});
|
|
25
27
|
|
|
26
28
|
// 导出为 JWK 格式
|
|
27
29
|
const jwk = await exportJWK(privateKey);
|
|
@@ -199,10 +199,13 @@ const LoginStep = memo<LoginStepProps>(({ onBack, onNext }) => {
|
|
|
199
199
|
});
|
|
200
200
|
|
|
201
201
|
const handleCancelAuth = async () => {
|
|
202
|
-
|
|
202
|
+
setRemoteError(null);
|
|
203
|
+
clearRemoteServerSyncError();
|
|
204
|
+
|
|
203
205
|
setCloudLoginStatus('idle');
|
|
204
206
|
setSelfhostLoginStatus('idle');
|
|
205
207
|
setAuthProgress(null);
|
|
208
|
+
await remoteServerService.cancelAuthorization();
|
|
206
209
|
};
|
|
207
210
|
|
|
208
211
|
// 渲染 Cloud 登录内容
|
|
@@ -238,10 +241,9 @@ const LoginStep = memo<LoginStepProps>(({ onBack, onNext }) => {
|
|
|
238
241
|
|
|
239
242
|
if (cloudLoginStatus === 'error') {
|
|
240
243
|
return (
|
|
241
|
-
|
|
244
|
+
<Flexbox style={{ width: '100%' }}>
|
|
242
245
|
<Alert
|
|
243
246
|
description={remoteError || t('authResult.failed.desc')}
|
|
244
|
-
style={{ width: '100%' }}
|
|
245
247
|
title={t('authResult.failed.title')}
|
|
246
248
|
type={'secondary'}
|
|
247
249
|
/>
|
|
@@ -254,7 +256,7 @@ const LoginStep = memo<LoginStepProps>(({ onBack, onNext }) => {
|
|
|
254
256
|
>
|
|
255
257
|
{t('screen5.actions.tryAgain')}
|
|
256
258
|
</Button>
|
|
257
|
-
|
|
259
|
+
</Flexbox>
|
|
258
260
|
);
|
|
259
261
|
}
|
|
260
262
|
|
|
@@ -340,10 +342,9 @@ const LoginStep = memo<LoginStepProps>(({ onBack, onNext }) => {
|
|
|
340
342
|
|
|
341
343
|
if (selfhostLoginStatus === 'error') {
|
|
342
344
|
return (
|
|
343
|
-
<Flexbox gap={16}>
|
|
345
|
+
<Flexbox gap={16} style={{ width: '100%' }}>
|
|
344
346
|
<Alert
|
|
345
347
|
description={remoteError || t('authResult.failed.desc')}
|
|
346
|
-
style={{ width: '100%' }}
|
|
347
348
|
title={t('authResult.failed.title')}
|
|
348
349
|
type={'secondary'}
|
|
349
350
|
/>
|
|
@@ -354,6 +355,47 @@ const LoginStep = memo<LoginStepProps>(({ onBack, onNext }) => {
|
|
|
354
355
|
);
|
|
355
356
|
}
|
|
356
357
|
|
|
358
|
+
if (selfhostLoginStatus === 'loading') {
|
|
359
|
+
const phaseText = t(authorizationPhaseI18nKeyMap[authProgress?.phase ?? 'browser_opened'], {
|
|
360
|
+
defaultValue: t('screen5.actions.connecting'),
|
|
361
|
+
});
|
|
362
|
+
const remainingSeconds = authProgress
|
|
363
|
+
? Math.max(0, Math.ceil((authProgress.maxPollTime - authProgress.elapsed) / 1000))
|
|
364
|
+
: null;
|
|
365
|
+
|
|
366
|
+
return (
|
|
367
|
+
<Flexbox gap={8} style={{ width: '100%' }}>
|
|
368
|
+
<Button
|
|
369
|
+
block
|
|
370
|
+
disabled={true}
|
|
371
|
+
icon={Server}
|
|
372
|
+
loading={true}
|
|
373
|
+
size={'large'}
|
|
374
|
+
type={'primary'}
|
|
375
|
+
>
|
|
376
|
+
{t('screen5.actions.connecting')}
|
|
377
|
+
</Button>
|
|
378
|
+
<Text style={{ color: cssVar.colorTextDescription }} type={'secondary'}>
|
|
379
|
+
{phaseText}
|
|
380
|
+
</Text>
|
|
381
|
+
<Flexbox align={'center'} horizontal justify={'space-between'}>
|
|
382
|
+
{remainingSeconds !== null ? (
|
|
383
|
+
<Text style={{ color: cssVar.colorTextDescription }} type={'secondary'}>
|
|
384
|
+
{t('screen5.auth.remaining', {
|
|
385
|
+
time: remainingSeconds,
|
|
386
|
+
})}
|
|
387
|
+
</Text>
|
|
388
|
+
) : (
|
|
389
|
+
<div />
|
|
390
|
+
)}
|
|
391
|
+
<Button onClick={handleCancelAuth} size={'small'} type={'text'}>
|
|
392
|
+
{t('screen5.actions.cancel')}
|
|
393
|
+
</Button>
|
|
394
|
+
</Flexbox>
|
|
395
|
+
</Flexbox>
|
|
396
|
+
);
|
|
397
|
+
}
|
|
398
|
+
|
|
357
399
|
return (
|
|
358
400
|
<Flexbox gap={16} style={{ width: '100%' }}>
|
|
359
401
|
<Text color={cssVar.colorTextSecondary}>{t(loginMethodMetas.selfhost.descriptionKey)}</Text>
|
|
@@ -365,6 +407,11 @@ const LoginStep = memo<LoginStepProps>(({ onBack, onNext }) => {
|
|
|
365
407
|
const { electronSystemService } = await import('@/services/electron/system');
|
|
366
408
|
await electronSystemService.showContextMenu('edit');
|
|
367
409
|
}}
|
|
410
|
+
onKeyDown={(e) => {
|
|
411
|
+
if (e.key === 'Enter') {
|
|
412
|
+
handleSelfhostConnect();
|
|
413
|
+
}
|
|
414
|
+
}}
|
|
368
415
|
placeholder={t('screen5.selfhost.endpointPlaceholder')}
|
|
369
416
|
prefix={<Icon icon={Server} style={{ marginRight: 4 }} />}
|
|
370
417
|
size={'large'}
|
|
@@ -372,16 +419,14 @@ const LoginStep = memo<LoginStepProps>(({ onBack, onNext }) => {
|
|
|
372
419
|
value={endpoint}
|
|
373
420
|
/>
|
|
374
421
|
<Button
|
|
375
|
-
disabled={!endpoint.trim() ||
|
|
376
|
-
loading={
|
|
422
|
+
disabled={!endpoint.trim() || isConnectingServer}
|
|
423
|
+
loading={false}
|
|
377
424
|
onClick={handleSelfhostConnect}
|
|
378
425
|
size={'large'}
|
|
379
426
|
style={{ width: '100%' }}
|
|
380
427
|
type={'primary'}
|
|
381
428
|
>
|
|
382
|
-
{
|
|
383
|
-
? t('screen5.actions.connecting')
|
|
384
|
-
: t('screen5.actions.connectToServer')}
|
|
429
|
+
{t('screen5.actions.connectToServer')}
|
|
385
430
|
</Button>
|
|
386
431
|
</Flexbox>
|
|
387
432
|
);
|
|
@@ -14,40 +14,79 @@ import LoginStep from './features/LoginStep';
|
|
|
14
14
|
import PermissionsStep from './features/PermissionsStep';
|
|
15
15
|
import WelcomeStep from './features/WelcomeStep';
|
|
16
16
|
import {
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
clearDesktopOnboardingScreen,
|
|
18
|
+
getDesktopOnboardingScreen,
|
|
19
19
|
setDesktopOnboardingCompleted,
|
|
20
|
-
|
|
20
|
+
setDesktopOnboardingScreen,
|
|
21
21
|
} from './storage';
|
|
22
|
+
import { DesktopOnboardingScreen, isDesktopOnboardingScreen } from './types';
|
|
22
23
|
|
|
23
24
|
const DesktopOnboardingPage = memo(() => {
|
|
24
25
|
const [searchParams, setSearchParams] = useSearchParams();
|
|
25
26
|
const [isMac, setIsMac] = useState(true);
|
|
26
27
|
const [isLoading, setIsLoading] = useState(true);
|
|
27
28
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
29
|
+
const flow = isMac
|
|
30
|
+
? [
|
|
31
|
+
DesktopOnboardingScreen.Welcome,
|
|
32
|
+
DesktopOnboardingScreen.Permissions,
|
|
33
|
+
DesktopOnboardingScreen.DataMode,
|
|
34
|
+
DesktopOnboardingScreen.Login,
|
|
35
|
+
]
|
|
36
|
+
: [
|
|
37
|
+
DesktopOnboardingScreen.Welcome,
|
|
38
|
+
DesktopOnboardingScreen.DataMode,
|
|
39
|
+
DesktopOnboardingScreen.Login,
|
|
40
|
+
];
|
|
41
|
+
|
|
42
|
+
const resolveScreenForPlatform = useCallback(
|
|
43
|
+
(screen: DesktopOnboardingScreen) => {
|
|
44
|
+
if (!isMac && screen === DesktopOnboardingScreen.Permissions)
|
|
45
|
+
return DesktopOnboardingScreen.DataMode;
|
|
46
|
+
return screen;
|
|
47
|
+
},
|
|
48
|
+
[isMac],
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
const getRequestedScreenFromUrl = useCallback((): DesktopOnboardingScreen | null => {
|
|
52
|
+
const screenParam = searchParams.get('screen');
|
|
53
|
+
if (isDesktopOnboardingScreen(screenParam)) return screenParam;
|
|
54
|
+
|
|
55
|
+
return null;
|
|
43
56
|
}, [searchParams]);
|
|
44
57
|
|
|
45
|
-
const [
|
|
58
|
+
const [currentScreen, setCurrentScreen] = useState<DesktopOnboardingScreen>(
|
|
59
|
+
DesktopOnboardingScreen.Welcome,
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
useEffect(() => {
|
|
63
|
+
if (isLoading) return;
|
|
64
|
+
|
|
65
|
+
const saved = getDesktopOnboardingScreen();
|
|
66
|
+
const requested = getRequestedScreenFromUrl();
|
|
46
67
|
|
|
47
|
-
|
|
68
|
+
const initial = resolveScreenForPlatform(requested ?? saved ?? DesktopOnboardingScreen.Welcome);
|
|
69
|
+
|
|
70
|
+
setCurrentScreen(initial);
|
|
71
|
+
|
|
72
|
+
// Canonicalize URL to `?screen=...`
|
|
73
|
+
const currentUrlScreen = searchParams.get('screen');
|
|
74
|
+
if (currentUrlScreen !== initial) {
|
|
75
|
+
setSearchParams({ screen: initial });
|
|
76
|
+
}
|
|
77
|
+
}, [
|
|
78
|
+
getRequestedScreenFromUrl,
|
|
79
|
+
isLoading,
|
|
80
|
+
resolveScreenForPlatform,
|
|
81
|
+
searchParams,
|
|
82
|
+
setSearchParams,
|
|
83
|
+
]);
|
|
84
|
+
|
|
85
|
+
// Persist current screen to localStorage.
|
|
48
86
|
useEffect(() => {
|
|
49
|
-
|
|
50
|
-
|
|
87
|
+
if (isLoading) return;
|
|
88
|
+
setDesktopOnboardingScreen(currentScreen);
|
|
89
|
+
}, [currentScreen, isLoading]);
|
|
51
90
|
|
|
52
91
|
// 设置窗口大小和可调整性
|
|
53
92
|
useEffect(() => {
|
|
@@ -91,79 +130,48 @@ const DesktopOnboardingPage = memo(() => {
|
|
|
91
130
|
};
|
|
92
131
|
}, []);
|
|
93
132
|
|
|
94
|
-
//
|
|
133
|
+
// Listen URL changes: allow deep-linking between screens.
|
|
95
134
|
useEffect(() => {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
}
|
|
103
|
-
}, [searchParams, currentStep]);
|
|
135
|
+
if (isLoading) return;
|
|
136
|
+
const requested = getRequestedScreenFromUrl();
|
|
137
|
+
if (!requested) return;
|
|
138
|
+
const resolved = resolveScreenForPlatform(requested);
|
|
139
|
+
if (resolved !== currentScreen) setCurrentScreen(resolved);
|
|
140
|
+
}, [currentScreen, getRequestedScreenFromUrl, isLoading, resolveScreenForPlatform]);
|
|
104
141
|
|
|
105
142
|
const goToNextStep = useCallback(() => {
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
break;
|
|
126
|
-
}
|
|
127
|
-
case 4: {
|
|
128
|
-
// 如果是第4步(LoginStep),完成 onboarding
|
|
129
|
-
setDesktopOnboardingCompleted();
|
|
130
|
-
clearDesktopOnboardingStep(); // Clear persisted step since onboarding is complete
|
|
131
|
-
// Restore window minimum size before hard reload (cleanup won't run due to hard navigation)
|
|
132
|
-
electronSystemService
|
|
133
|
-
.setWindowMinimumSize(APP_WINDOW_MIN_SIZE)
|
|
134
|
-
.catch(console.error)
|
|
135
|
-
.finally(() => {
|
|
136
|
-
// Use hard reload instead of SPA navigation to ensure the app boots with the new desktop state.
|
|
137
|
-
window.location.replace('/');
|
|
138
|
-
});
|
|
139
|
-
return prev;
|
|
140
|
-
}
|
|
141
|
-
default: {
|
|
142
|
-
nextStep = prev + 1;
|
|
143
|
-
}
|
|
143
|
+
setCurrentScreen((prev) => {
|
|
144
|
+
const idx = flow.indexOf(prev);
|
|
145
|
+
const next = flow[idx + 1];
|
|
146
|
+
|
|
147
|
+
if (!next) {
|
|
148
|
+
// Complete onboarding.
|
|
149
|
+
setDesktopOnboardingCompleted();
|
|
150
|
+
clearDesktopOnboardingScreen();
|
|
151
|
+
|
|
152
|
+
// Restore window minimum size before hard reload (cleanup won't run due to hard navigation)
|
|
153
|
+
electronSystemService
|
|
154
|
+
.setWindowMinimumSize(APP_WINDOW_MIN_SIZE)
|
|
155
|
+
.catch(console.error)
|
|
156
|
+
.finally(() => {
|
|
157
|
+
// Use hard reload instead of SPA navigation to ensure the app boots with the new desktop state.
|
|
158
|
+
window.location.replace('/');
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
return prev;
|
|
144
162
|
}
|
|
145
|
-
|
|
146
|
-
setSearchParams({
|
|
147
|
-
return
|
|
163
|
+
|
|
164
|
+
setSearchParams({ screen: next });
|
|
165
|
+
return next;
|
|
148
166
|
});
|
|
149
167
|
}, [isMac, setSearchParams]);
|
|
150
168
|
|
|
151
169
|
const goToPreviousStep = useCallback(() => {
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
prevStep = isMac ? 2 : 1;
|
|
158
|
-
} else if (prev === 2) {
|
|
159
|
-
// 如果当前是第2步(PermissionsStep),上一步是第1步
|
|
160
|
-
prevStep = 1;
|
|
161
|
-
} else {
|
|
162
|
-
prevStep = prev - 1;
|
|
163
|
-
}
|
|
164
|
-
// 更新 URL query 参数
|
|
165
|
-
setSearchParams({ step: prevStep.toString() });
|
|
166
|
-
return prevStep;
|
|
170
|
+
setCurrentScreen((prev) => {
|
|
171
|
+
const idx = flow.indexOf(prev);
|
|
172
|
+
const prevScreen = flow[Math.max(0, idx - 1)] ?? DesktopOnboardingScreen.Welcome;
|
|
173
|
+
setSearchParams({ screen: prevScreen });
|
|
174
|
+
return prevScreen;
|
|
167
175
|
});
|
|
168
176
|
}, [isMac, setSearchParams]);
|
|
169
177
|
|
|
@@ -172,21 +180,22 @@ const DesktopOnboardingPage = memo(() => {
|
|
|
172
180
|
}
|
|
173
181
|
|
|
174
182
|
const renderStep = () => {
|
|
175
|
-
switch (
|
|
176
|
-
case
|
|
183
|
+
switch (currentScreen) {
|
|
184
|
+
case DesktopOnboardingScreen.Welcome: {
|
|
177
185
|
return <WelcomeStep onNext={goToNextStep} />;
|
|
178
186
|
}
|
|
179
|
-
case
|
|
180
|
-
//
|
|
187
|
+
case DesktopOnboardingScreen.Permissions: {
|
|
188
|
+
// macOS-only screen; fallback to DataMode if platform doesn't support.
|
|
181
189
|
if (!isMac) {
|
|
182
|
-
|
|
190
|
+
setCurrentScreen(DesktopOnboardingScreen.DataMode);
|
|
191
|
+
return null;
|
|
183
192
|
}
|
|
184
193
|
return <PermissionsStep onBack={goToPreviousStep} onNext={goToNextStep} />;
|
|
185
194
|
}
|
|
186
|
-
case
|
|
195
|
+
case DesktopOnboardingScreen.DataMode: {
|
|
187
196
|
return <DataModeStep onBack={goToPreviousStep} onNext={goToNextStep} />;
|
|
188
197
|
}
|
|
189
|
-
case
|
|
198
|
+
case DesktopOnboardingScreen.Login: {
|
|
190
199
|
return <LoginStep onBack={goToPreviousStep} onNext={goToNextStep} />;
|
|
191
200
|
}
|
|
192
201
|
default: {
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { DesktopOnboardingScreen } from './types';
|
|
2
|
+
|
|
3
|
+
const DESKTOP_ONBOARDING_ROUTE = '/desktop-onboarding';
|
|
4
|
+
export const getDesktopOnboardingPath = (screen?: DesktopOnboardingScreen) => {
|
|
5
|
+
if (!screen) return DESKTOP_ONBOARDING_ROUTE;
|
|
6
|
+
return `${DESKTOP_ONBOARDING_ROUTE}?screen=${encodeURIComponent(screen)}`;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export const navigateToDesktopOnboarding = (screen?: DesktopOnboardingScreen) => {
|
|
10
|
+
location.href = getDesktopOnboardingPath(screen);
|
|
11
|
+
};
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import { DesktopOnboardingScreen, isDesktopOnboardingScreen } from './types';
|
|
2
|
+
|
|
1
3
|
export const DESKTOP_ONBOARDING_STORAGE_KEY = 'lobechat:desktop:onboarding:completed:v1';
|
|
2
|
-
export const
|
|
4
|
+
export const DESKTOP_ONBOARDING_SCREEN_KEY = 'lobechat:desktop:onboarding:screen:v1';
|
|
3
5
|
|
|
4
6
|
export const getDesktopOnboardingCompleted = () => {
|
|
5
7
|
if (typeof window === 'undefined') return true;
|
|
@@ -35,33 +37,29 @@ export const clearDesktopOnboardingCompleted = () => {
|
|
|
35
37
|
};
|
|
36
38
|
|
|
37
39
|
/**
|
|
38
|
-
* Get the persisted onboarding
|
|
40
|
+
* Get the persisted onboarding screen (for restoring after app restart)
|
|
39
41
|
*/
|
|
40
|
-
export const
|
|
42
|
+
export const getDesktopOnboardingScreen = () => {
|
|
41
43
|
if (typeof window === 'undefined') return null;
|
|
42
44
|
|
|
43
45
|
try {
|
|
44
|
-
const
|
|
45
|
-
if (
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
return parsedStep;
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
return null;
|
|
46
|
+
const screen = window.localStorage.getItem(DESKTOP_ONBOARDING_SCREEN_KEY);
|
|
47
|
+
if (!screen) return null;
|
|
48
|
+
if (!isDesktopOnboardingScreen(screen)) return null;
|
|
49
|
+
return screen;
|
|
52
50
|
} catch {
|
|
53
51
|
return null;
|
|
54
52
|
}
|
|
55
53
|
};
|
|
56
54
|
|
|
57
55
|
/**
|
|
58
|
-
* Persist the current onboarding
|
|
56
|
+
* Persist the current onboarding screen
|
|
59
57
|
*/
|
|
60
|
-
export const
|
|
58
|
+
export const setDesktopOnboardingScreen = (screen: DesktopOnboardingScreen) => {
|
|
61
59
|
if (typeof window === 'undefined') return false;
|
|
62
60
|
|
|
63
61
|
try {
|
|
64
|
-
window.localStorage.setItem(
|
|
62
|
+
window.localStorage.setItem(DESKTOP_ONBOARDING_SCREEN_KEY, screen);
|
|
65
63
|
return true;
|
|
66
64
|
} catch {
|
|
67
65
|
return false;
|
|
@@ -69,13 +67,13 @@ export const setDesktopOnboardingStep = (step: number) => {
|
|
|
69
67
|
};
|
|
70
68
|
|
|
71
69
|
/**
|
|
72
|
-
* Clear the persisted onboarding
|
|
70
|
+
* Clear the persisted onboarding screen (called when onboarding completes)
|
|
73
71
|
*/
|
|
74
|
-
export const
|
|
72
|
+
export const clearDesktopOnboardingScreen = () => {
|
|
75
73
|
if (typeof window === 'undefined') return false;
|
|
76
74
|
|
|
77
75
|
try {
|
|
78
|
-
window.localStorage.removeItem(
|
|
76
|
+
window.localStorage.removeItem(DESKTOP_ONBOARDING_SCREEN_KEY);
|
|
79
77
|
return true;
|
|
80
78
|
} catch {
|
|
81
79
|
return false;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export enum DesktopOnboardingScreen {
|
|
2
|
+
DataMode = 'data-mode',
|
|
3
|
+
Login = 'login',
|
|
4
|
+
Permissions = 'permissions',
|
|
5
|
+
Welcome = 'welcome',
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export const isDesktopOnboardingScreen = (value: unknown): value is DesktopOnboardingScreen => {
|
|
9
|
+
if (typeof value !== 'string') return false;
|
|
10
|
+
return (Object.values(DesktopOnboardingScreen) as string[]).includes(value);
|
|
11
|
+
};
|
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
import { ENABLE_BUSINESS_FEATURES } from '@lobechat/business-const';
|
|
2
2
|
import { Flexbox } from '@lobehub/ui';
|
|
3
|
-
import { useRouter } from '@/libs/next/navigation';
|
|
4
3
|
import { memo } from 'react';
|
|
5
|
-
import { Link
|
|
4
|
+
import { Link } from 'react-router-dom';
|
|
6
5
|
|
|
6
|
+
import { navigateToDesktopOnboarding } from '@/app/[variants]/(desktop)/desktop-onboarding/navigation';
|
|
7
7
|
import { clearDesktopOnboardingCompleted } from '@/app/[variants]/(desktop)/desktop-onboarding/storage';
|
|
8
|
+
import { DesktopOnboardingScreen } from '@/app/[variants]/(desktop)/desktop-onboarding/types';
|
|
8
9
|
import BusinessPanelContent from '@/business/client/features/User/BusinessPanelContent';
|
|
9
10
|
import BrandWatermark from '@/components/BrandWatermark';
|
|
10
11
|
import Menu from '@/components/Menu';
|
|
11
12
|
import { isDesktop } from '@/const/version';
|
|
12
13
|
import { enableBetterAuth, enableNextAuth } from '@/envs/auth';
|
|
14
|
+
import { useRouter } from '@/libs/next/navigation';
|
|
13
15
|
import { useUserStore } from '@/store/user';
|
|
14
16
|
import { authSelectors } from '@/store/user/selectors';
|
|
15
17
|
|
|
@@ -21,7 +23,7 @@ import { useMenu } from './useMenu';
|
|
|
21
23
|
|
|
22
24
|
const PanelContent = memo<{ closePopover: () => void }>(({ closePopover }) => {
|
|
23
25
|
const router = useRouter();
|
|
24
|
-
|
|
26
|
+
|
|
25
27
|
const isLoginWithAuth = useUserStore(authSelectors.isLoginWithAuth);
|
|
26
28
|
const [openSignIn, signOut] = useUserStore((s) => [s.openLogin, s.logout]);
|
|
27
29
|
const { mainItems, logoutItems } = useMenu();
|
|
@@ -35,17 +37,16 @@ const PanelContent = memo<{ closePopover: () => void }>(({ closePopover }) => {
|
|
|
35
37
|
if (isDesktop) {
|
|
36
38
|
closePopover();
|
|
37
39
|
|
|
38
|
-
// Desktop: clear OIDC tokens (electron main) + re-enter desktop onboarding at Screen5.
|
|
39
40
|
try {
|
|
40
41
|
const { remoteServerService } = await import('@/services/electron/remoteServer');
|
|
41
42
|
await remoteServerService.clearRemoteServerConfig();
|
|
42
|
-
} catch {
|
|
43
|
-
|
|
43
|
+
} catch (error) {
|
|
44
|
+
console.error(error);
|
|
45
|
+
} finally {
|
|
46
|
+
clearDesktopOnboardingCompleted();
|
|
47
|
+
signOut();
|
|
48
|
+
navigateToDesktopOnboarding(DesktopOnboardingScreen.Login);
|
|
44
49
|
}
|
|
45
|
-
|
|
46
|
-
clearDesktopOnboardingCompleted();
|
|
47
|
-
signOut();
|
|
48
|
-
navigate('/desktop-onboarding#5', { replace: true });
|
|
49
50
|
return;
|
|
50
51
|
}
|
|
51
52
|
|