@lobehub/lobehub 2.0.0-next.163 → 2.0.0-next.165

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.
Files changed (124) hide show
  1. package/.cursor/rules/desktop-feature-implementation.mdc +31 -34
  2. package/.cursor/rules/desktop-local-tools-implement.mdc +3 -3
  3. package/.cursor/rules/desktop-window-management.mdc +56 -66
  4. package/CHANGELOG.md +52 -0
  5. package/README.md +6 -6
  6. package/README.zh-CN.md +6 -6
  7. package/apps/desktop/Development.md +42 -46
  8. package/apps/desktop/README.md +37 -1
  9. package/apps/desktop/README.zh-CN.md +26 -1
  10. package/apps/desktop/electron.vite.config.ts +1 -0
  11. package/apps/desktop/src/main/controllers/AuthCtr.ts +4 -3
  12. package/apps/desktop/src/main/controllers/BrowserWindowsCtr.ts +33 -20
  13. package/apps/desktop/src/main/controllers/DevtoolsCtr.ts +4 -2
  14. package/apps/desktop/src/main/controllers/LocalFileCtr.ts +14 -13
  15. package/apps/desktop/src/main/controllers/MenuCtr.ts +5 -4
  16. package/apps/desktop/src/main/controllers/NetworkProxyCtr.ts +18 -19
  17. package/apps/desktop/src/main/controllers/NotificationCtr.ts +4 -3
  18. package/apps/desktop/src/main/controllers/RemoteServerConfigCtr.ts +5 -4
  19. package/apps/desktop/src/main/controllers/RemoteServerSyncCtr.ts +3 -2
  20. package/apps/desktop/src/main/controllers/ShellCommandCtr.ts +5 -4
  21. package/apps/desktop/src/main/controllers/ShortcutCtr.ts +4 -3
  22. package/apps/desktop/src/main/controllers/SystemCtr.ts +7 -37
  23. package/apps/desktop/src/main/controllers/SystemServerCtr.ts +38 -0
  24. package/apps/desktop/src/main/controllers/TrayMenuCtr.ts +5 -4
  25. package/apps/desktop/src/main/controllers/UpdaterCtr.ts +6 -5
  26. package/apps/desktop/src/main/controllers/UploadFileCtr.ts +3 -25
  27. package/apps/desktop/src/main/controllers/UploadFileServerCtr.ts +33 -0
  28. package/apps/desktop/src/main/controllers/__tests__/AuthCtr.test.ts +9 -1
  29. package/apps/desktop/src/main/controllers/__tests__/BrowserWindowsCtr.test.ts +29 -9
  30. package/apps/desktop/src/main/controllers/__tests__/DevtoolsCtr.test.ts +12 -3
  31. package/apps/desktop/src/main/controllers/__tests__/LocalFileCtr.test.ts +7 -0
  32. package/apps/desktop/src/main/controllers/__tests__/MenuCtr.test.ts +10 -0
  33. package/apps/desktop/src/main/controllers/__tests__/NetworkProxyCtr.test.ts +10 -0
  34. package/apps/desktop/src/main/controllers/__tests__/NotificationCtr.test.ts +8 -0
  35. package/apps/desktop/src/main/controllers/__tests__/RemoteServerConfigCtr.test.ts +8 -0
  36. package/apps/desktop/src/main/controllers/__tests__/RemoteServerSyncCtr.test.ts +1 -0
  37. package/apps/desktop/src/main/controllers/__tests__/ShellCommandCtr.test.ts +10 -0
  38. package/apps/desktop/src/main/controllers/__tests__/ShortcutCtr.test.ts +11 -0
  39. package/apps/desktop/src/main/controllers/__tests__/SystemCtr.test.ts +43 -73
  40. package/apps/desktop/src/main/controllers/__tests__/SystemServerCtr.test.ts +75 -0
  41. package/apps/desktop/src/main/controllers/__tests__/TrayMenuCtr.test.ts +24 -13
  42. package/apps/desktop/src/main/controllers/__tests__/UpdaterCtr.test.ts +13 -2
  43. package/apps/desktop/src/main/controllers/__tests__/UploadFileCtr.test.ts +29 -108
  44. package/apps/desktop/src/main/controllers/__tests__/UploadFileServerCtr.test.ts +55 -0
  45. package/apps/desktop/src/main/controllers/_template.ts +2 -2
  46. package/apps/desktop/src/main/controllers/index.ts +5 -29
  47. package/apps/desktop/src/main/controllers/registry.ts +52 -0
  48. package/apps/desktop/src/main/core/App.ts +15 -47
  49. package/apps/desktop/src/main/core/__tests__/App.test.ts +5 -4
  50. package/apps/desktop/src/main/core/infrastructure/IoCContainer.ts +0 -5
  51. package/apps/desktop/src/main/core/infrastructure/__tests__/IoCContainer.test.ts +0 -50
  52. package/apps/desktop/src/main/exports.d.ts +8 -0
  53. package/apps/desktop/src/main/exports.ts +2 -0
  54. package/apps/desktop/src/main/global.d.ts +3 -0
  55. package/apps/desktop/src/main/modules/fileSearch/__tests__/macOS.integration.test.ts +17 -8
  56. package/apps/desktop/src/main/package.json +10 -0
  57. package/apps/desktop/src/main/services/fileSrv.ts +1 -1
  58. package/apps/desktop/src/main/utils/ipc/__tests__/base.test.ts +91 -0
  59. package/apps/desktop/src/main/utils/ipc/base.ts +170 -0
  60. package/apps/desktop/src/main/utils/ipc/index.ts +11 -0
  61. package/apps/desktop/src/main/utils/ipc/utility.ts +20 -0
  62. package/apps/desktop/src/preload/electronApi.ts +4 -1
  63. package/apps/desktop/src/preload/invoke.test.ts +13 -16
  64. package/apps/desktop/src/preload/invoke.ts +2 -5
  65. package/apps/desktop/src/preload/routeInterceptor.test.ts +13 -13
  66. package/apps/desktop/src/preload/routeInterceptor.ts +4 -4
  67. package/apps/desktop/tsconfig.json +15 -5
  68. package/changelog/v1.json +14 -0
  69. package/locales/ar/auth.json +3 -0
  70. package/locales/bg-BG/auth.json +3 -0
  71. package/locales/de-DE/auth.json +3 -0
  72. package/locales/en-US/auth.json +4 -1
  73. package/locales/es-ES/auth.json +3 -0
  74. package/locales/fa-IR/auth.json +3 -0
  75. package/locales/fr-FR/auth.json +3 -0
  76. package/locales/it-IT/auth.json +3 -0
  77. package/locales/ja-JP/auth.json +3 -0
  78. package/locales/ko-KR/auth.json +3 -0
  79. package/locales/nl-NL/auth.json +3 -0
  80. package/locales/pl-PL/auth.json +3 -0
  81. package/locales/pt-BR/auth.json +3 -0
  82. package/locales/ru-RU/auth.json +3 -0
  83. package/locales/tr-TR/auth.json +3 -0
  84. package/locales/vi-VN/auth.json +3 -0
  85. package/locales/zh-CN/auth.json +3 -0
  86. package/locales/zh-TW/auth.json +3 -0
  87. package/package.json +4 -3
  88. package/packages/electron-client-ipc/src/index.ts +1 -1
  89. package/packages/electron-client-ipc/src/ipc.test.ts +62 -0
  90. package/packages/electron-client-ipc/src/ipc.ts +63 -0
  91. package/packages/electron-client-ipc/src/streamInvoke.ts +7 -1
  92. package/packages/electron-client-ipc/src/types/dispatch.ts +1 -10
  93. package/packages/electron-client-ipc/vitest.config.mts +10 -0
  94. package/packages/electron-server-ipc/src/ipcClient.ts +1 -2
  95. package/packages/electron-server-ipc/src/ipcServer.ts +1 -2
  96. package/packages/electron-server-ipc/src/types/index.ts +1 -5
  97. package/pnpm-workspace.yaml +1 -1
  98. package/scripts/i18nWorkflow/const.ts +2 -2
  99. package/scripts/i18nWorkflow/i18nConfig.ts +7 -0
  100. package/scripts/i18nWorkflow/utils.ts +1 -1
  101. package/src/app/[variants]/(auth)/signup/[[...signup]]/BetterAuthSignUpForm.tsx +23 -0
  102. package/src/app/[variants]/(main)/(mobile)/me/(home)/features/UserBanner.tsx +3 -9
  103. package/src/app/[variants]/(main)/discover/(detail)/provider/features/Sidebar/ActionButton/ProviderConfig.tsx +2 -2
  104. package/src/app/[variants]/(main)/profile/(home)/Client.tsx +206 -138
  105. package/src/features/User/PlanTag.tsx +4 -4
  106. package/src/locales/default/auth.ts +3 -0
  107. package/src/locales/default/setting.ts +1 -0
  108. package/src/server/modules/ElectronIPCClient/index.ts +59 -13
  109. package/src/services/electron/__tests__/devtools.test.ts +10 -6
  110. package/src/services/electron/autoUpdate.ts +5 -5
  111. package/src/services/electron/desktopNotification.ts +4 -7
  112. package/src/services/electron/devtools.ts +2 -2
  113. package/src/services/electron/file.ts +3 -2
  114. package/src/services/electron/localFileService.ts +17 -16
  115. package/src/services/electron/remoteServer.ts +7 -6
  116. package/src/services/electron/settings.ts +9 -11
  117. package/src/services/electron/system.ts +8 -6
  118. package/src/store/chat/slices/plugin/actions/pluginTypes.ts +1 -1
  119. package/src/store/global/actions/general.ts +8 -10
  120. package/src/utils/electron/desktopRemoteRPCFetch.ts +3 -2
  121. package/src/utils/electron/ipc.ts +12 -0
  122. package/tsconfig.json +5 -0
  123. package/apps/desktop/src/main/types/ipcClientEvent.ts +0 -3
  124. package/packages/electron-client-ipc/src/dispatch.ts +0 -41
@@ -52,6 +52,7 @@ const useStyles = createStyles(({ css, token }) => ({
52
52
  }));
53
53
 
54
54
  interface SignUpFormValues {
55
+ confirmPassword: string;
55
56
  email: string;
56
57
  password: string;
57
58
  }
@@ -179,6 +180,28 @@ export default function BetterAuthSignUpForm() {
179
180
  />
180
181
  </Form.Item>
181
182
 
183
+ <Form.Item
184
+ dependencies={['password']}
185
+ name="confirmPassword"
186
+ rules={[
187
+ { message: t('betterAuth.errors.confirmPasswordRequired'), required: true },
188
+ ({ getFieldValue }) => ({
189
+ validator(_, value) {
190
+ if (!value || getFieldValue('password') === value) {
191
+ return Promise.resolve();
192
+ }
193
+ return Promise.reject(new Error(t('betterAuth.errors.passwordMismatch')));
194
+ },
195
+ }),
196
+ ]}
197
+ >
198
+ <Input.Password
199
+ placeholder={t('betterAuth.signup.confirmPasswordPlaceholder')}
200
+ prefix={<Lock size={16} />}
201
+ size="large"
202
+ />
203
+ </Form.Item>
204
+
182
205
  <Form.Item>
183
206
  <Button
184
207
  block
@@ -1,10 +1,10 @@
1
1
  'use client';
2
2
 
3
3
  import { memo } from 'react';
4
- import { Link, useNavigate } from 'react-router-dom';
5
4
  import { Flexbox } from 'react-layout-kit';
5
+ import { Link } from 'react-router-dom';
6
6
 
7
- import { enableAuth, enableNextAuth } from '@/const/auth';
7
+ import { enableAuth } from '@/const/auth';
8
8
  import DataStatistics from '@/features/User/DataStatistics';
9
9
  import UserInfo from '@/features/User/UserInfo';
10
10
  import UserLoginOrSignup from '@/features/User/UserLoginOrSignup/Community';
@@ -12,7 +12,6 @@ import { useUserStore } from '@/store/user';
12
12
  import { authSelectors } from '@/store/user/selectors';
13
13
 
14
14
  const UserBanner = memo(() => {
15
- const navigate = useNavigate();
16
15
  const isLoginWithAuth = useUserStore(authSelectors.isLoginWithAuth);
17
16
  const [signIn] = useUserStore((s) => [s.openLogin]);
18
17
 
@@ -30,12 +29,7 @@ const UserBanner = memo(() => {
30
29
  ) : (
31
30
  <UserLoginOrSignup
32
31
  onClick={() => {
33
- // If use NextAuth, call openLogin method directly
34
- if (enableNextAuth) {
35
- signIn();
36
- return;
37
- }
38
- navigate('/login');
32
+ signIn();
39
33
  }}
40
34
  />
41
35
  )}
@@ -30,8 +30,8 @@ const ProviderConfig = memo(() => {
30
30
  const tab = 'provider';
31
31
 
32
32
  if (isDesktop) {
33
- const { dispatch } = await import('@lobechat/electron-client-ipc');
34
- await dispatch('openSettingsWindow', {
33
+ const { ensureElectronIpc } = await import('@/utils/electron/ipc');
34
+ await ensureElectronIpc().windows.openSettingsWindow({
35
35
  searchParams,
36
36
  tab,
37
37
  });
@@ -31,6 +31,7 @@ interface ProfileRowProps {
31
31
  action?: ReactNode;
32
32
  children: ReactNode;
33
33
  label: string;
34
+ mobile?: boolean;
34
35
  }
35
36
 
36
37
  const rowStyle: CSSProperties = {
@@ -43,17 +44,31 @@ const labelStyle: CSSProperties = {
43
44
  width: 160,
44
45
  };
45
46
 
46
- const ProfileRow = memo<ProfileRowProps>(({ label, children, action }) => (
47
- <Flexbox align="center" gap={24} horizontal justify="space-between" style={rowStyle}>
48
- <Flexbox align="center" gap={24} horizontal style={{ flex: 1 }}>
49
- <Typography.Text style={labelStyle}>{label}</Typography.Text>
50
- <Flexbox style={{ flex: 1 }}>{children}</Flexbox>
47
+ const ProfileRow = memo<ProfileRowProps>(({ label, children, action, mobile }) => {
48
+ if (mobile) {
49
+ return (
50
+ <Flexbox gap={12} style={rowStyle}>
51
+ <Flexbox align="center" horizontal justify="space-between">
52
+ <Typography.Text strong>{label}</Typography.Text>
53
+ {action}
54
+ </Flexbox>
55
+ <Flexbox>{children}</Flexbox>
56
+ </Flexbox>
57
+ );
58
+ }
59
+
60
+ return (
61
+ <Flexbox align="center" gap={24} horizontal justify="space-between" style={rowStyle}>
62
+ <Flexbox align="center" gap={24} horizontal style={{ flex: 1 }}>
63
+ <Typography.Text style={labelStyle}>{label}</Typography.Text>
64
+ <Flexbox style={{ flex: 1 }}>{children}</Flexbox>
65
+ </Flexbox>
66
+ {action && <Flexbox>{action}</Flexbox>}
51
67
  </Flexbox>
52
- {action && <Flexbox>{action}</Flexbox>}
53
- </Flexbox>
54
- ));
68
+ );
69
+ });
55
70
 
56
- const AvatarRow = memo(() => {
71
+ const AvatarRow = memo<{ mobile?: boolean }>(({ mobile }) => {
57
72
  const { t } = useTranslation('auth');
58
73
  const isLogin = useUserStore(authSelectors.isLogin);
59
74
  const updateAvatar = useUserStore((s) => s.updateAvatar);
@@ -89,34 +104,48 @@ const AvatarRow = memo(() => {
89
104
 
90
105
  const canUpload = !enableAuth || isLogin;
91
106
 
107
+ const avatarContent = canUpload ? (
108
+ <Spin indicator={<LoadingOutlined spin />} spinning={uploading}>
109
+ <Upload beforeUpload={handleUploadAvatar} itemRender={() => void 0} maxCount={1}>
110
+ <UserAvatar clickable size={40} />
111
+ </Upload>
112
+ </Spin>
113
+ ) : (
114
+ <UserAvatar size={40} />
115
+ );
116
+
117
+ const updateAction = canUpload ? (
118
+ <Upload beforeUpload={handleUploadAvatar} itemRender={() => void 0} maxCount={1}>
119
+ <Typography.Text style={{ cursor: 'pointer', fontSize: 13 }}>
120
+ {t('profile.updateAvatar')}
121
+ </Typography.Text>
122
+ </Upload>
123
+ ) : null;
124
+
125
+ if (mobile) {
126
+ return (
127
+ <Flexbox gap={12} style={rowStyle}>
128
+ <Flexbox align="center" horizontal justify="space-between">
129
+ <Typography.Text strong>{t('profile.avatar')}</Typography.Text>
130
+ {updateAction}
131
+ </Flexbox>
132
+ <Flexbox>{avatarContent}</Flexbox>
133
+ </Flexbox>
134
+ );
135
+ }
136
+
92
137
  return (
93
138
  <Flexbox align="center" gap={24} horizontal justify="space-between" style={rowStyle}>
94
139
  <Flexbox align="center" gap={24} horizontal style={{ flex: 1 }}>
95
140
  <Typography.Text style={labelStyle}>{t('profile.avatar')}</Typography.Text>
96
- <Flexbox style={{ flex: 1 }}>
97
- {canUpload ? (
98
- <Spin indicator={<LoadingOutlined spin />} spinning={uploading}>
99
- <Upload beforeUpload={handleUploadAvatar} itemRender={() => void 0} maxCount={1}>
100
- <UserAvatar clickable size={40} />
101
- </Upload>
102
- </Spin>
103
- ) : (
104
- <UserAvatar size={40} />
105
- )}
106
- </Flexbox>
141
+ <Flexbox style={{ flex: 1 }}>{avatarContent}</Flexbox>
107
142
  </Flexbox>
108
- {canUpload && (
109
- <Upload beforeUpload={handleUploadAvatar} itemRender={() => void 0} maxCount={1}>
110
- <Typography.Text style={{ cursor: 'pointer', fontSize: 13 }}>
111
- {t('profile.updateAvatar')}
112
- </Typography.Text>
113
- </Upload>
114
- )}
143
+ {updateAction}
115
144
  </Flexbox>
116
145
  );
117
146
  });
118
147
 
119
- const FullNameRow = memo(() => {
148
+ const FullNameRow = memo<{ mobile?: boolean }>(({ mobile }) => {
120
149
  const { t } = useTranslation('auth');
121
150
  const fullName = useUserStore(userProfileSelectors.fullName);
122
151
  const updateFullName = useUserStore((s) => s.updateFullName);
@@ -152,64 +181,83 @@ const FullNameRow = memo(() => {
152
181
  }
153
182
  }, [editValue, updateFullName]);
154
183
 
184
+ const editingContent = (
185
+ <motion.div
186
+ animate={{ opacity: 1, y: 0 }}
187
+ exit={{ opacity: 0, y: -10 }}
188
+ initial={{ opacity: 0, y: -10 }}
189
+ key="editing"
190
+ transition={{ duration: 0.2 }}
191
+ >
192
+ <Flexbox gap={12}>
193
+ {!mobile && <Typography.Text strong>{t('profile.fullNameInputHint')}</Typography.Text>}
194
+ <Input
195
+ autoFocus
196
+ onChange={(e) => setEditValue(e.target.value)}
197
+ onPressEnter={handleSave}
198
+ placeholder={t('profile.fullName')}
199
+ value={editValue}
200
+ />
201
+ <Flexbox gap={8} horizontal justify="flex-end">
202
+ <Button disabled={saving} onClick={handleCancel} size="small">
203
+ {t('profile.cancel')}
204
+ </Button>
205
+ <Button loading={saving} onClick={handleSave} size="small" type="primary">
206
+ {t('profile.save')}
207
+ </Button>
208
+ </Flexbox>
209
+ </Flexbox>
210
+ </motion.div>
211
+ );
212
+
213
+ const displayContent = (
214
+ <motion.div
215
+ animate={{ opacity: 1 }}
216
+ exit={{ opacity: 0 }}
217
+ initial={{ opacity: 0 }}
218
+ key="display"
219
+ transition={{ duration: 0.2 }}
220
+ >
221
+ {mobile ? (
222
+ <Typography.Text>{fullName || '--'}</Typography.Text>
223
+ ) : (
224
+ <Flexbox align="center" horizontal justify="space-between">
225
+ <Typography.Text>{fullName || '--'}</Typography.Text>
226
+ <Typography.Text onClick={handleStartEdit} style={{ cursor: 'pointer', fontSize: 13 }}>
227
+ {t('profile.updateFullName')}
228
+ </Typography.Text>
229
+ </Flexbox>
230
+ )}
231
+ </motion.div>
232
+ );
233
+
234
+ if (mobile) {
235
+ return (
236
+ <Flexbox gap={12} style={rowStyle}>
237
+ <Flexbox align="center" horizontal justify="space-between">
238
+ <Typography.Text strong>{t('profile.fullName')}</Typography.Text>
239
+ {!isEditing && (
240
+ <Typography.Text onClick={handleStartEdit} style={{ cursor: 'pointer', fontSize: 13 }}>
241
+ {t('profile.updateFullName')}
242
+ </Typography.Text>
243
+ )}
244
+ </Flexbox>
245
+ <AnimatePresence mode="wait">{isEditing ? editingContent : displayContent}</AnimatePresence>
246
+ </Flexbox>
247
+ );
248
+ }
249
+
155
250
  return (
156
251
  <Flexbox gap={24} horizontal style={rowStyle}>
157
252
  <Typography.Text style={labelStyle}>{t('profile.fullName')}</Typography.Text>
158
253
  <Flexbox style={{ flex: 1 }}>
159
- <AnimatePresence mode="wait">
160
- {isEditing ? (
161
- <motion.div
162
- animate={{ opacity: 1, y: 0 }}
163
- exit={{ opacity: 0, y: -10 }}
164
- initial={{ opacity: 0, y: -10 }}
165
- key="editing"
166
- transition={{ duration: 0.2 }}
167
- >
168
- <Flexbox gap={12}>
169
- <Typography.Text strong>{t('profile.fullNameInputHint')}</Typography.Text>
170
- <Input
171
- autoFocus
172
- onChange={(e) => setEditValue(e.target.value)}
173
- onPressEnter={handleSave}
174
- placeholder={t('profile.fullName')}
175
- value={editValue}
176
- />
177
- <Flexbox gap={8} horizontal justify="flex-end">
178
- <Button disabled={saving} onClick={handleCancel} size="small">
179
- {t('profile.cancel')}
180
- </Button>
181
- <Button loading={saving} onClick={handleSave} size="small" type="primary">
182
- {t('profile.save')}
183
- </Button>
184
- </Flexbox>
185
- </Flexbox>
186
- </motion.div>
187
- ) : (
188
- <motion.div
189
- animate={{ opacity: 1 }}
190
- exit={{ opacity: 0 }}
191
- initial={{ opacity: 0 }}
192
- key="display"
193
- transition={{ duration: 0.2 }}
194
- >
195
- <Flexbox align="center" horizontal justify="space-between">
196
- <Typography.Text>{fullName || '--'}</Typography.Text>
197
- <Typography.Text
198
- onClick={handleStartEdit}
199
- style={{ cursor: 'pointer', fontSize: 13 }}
200
- >
201
- {t('profile.updateFullName')}
202
- </Typography.Text>
203
- </Flexbox>
204
- </motion.div>
205
- )}
206
- </AnimatePresence>
254
+ <AnimatePresence mode="wait">{isEditing ? editingContent : displayContent}</AnimatePresence>
207
255
  </Flexbox>
208
256
  </Flexbox>
209
257
  );
210
258
  });
211
259
 
212
- const UsernameRow = memo(() => {
260
+ const UsernameRow = memo<{ mobile?: boolean }>(({ mobile }) => {
213
261
  const { t } = useTranslation('auth');
214
262
  const username = useUserStore(userProfileSelectors.username);
215
263
  const updateUsername = useUserStore((s) => s.updateUsername);
@@ -281,70 +329,89 @@ const UsernameRow = memo(() => {
281
329
  setError('');
282
330
  };
283
331
 
332
+ const editingContent = (
333
+ <motion.div
334
+ animate={{ opacity: 1, y: 0 }}
335
+ exit={{ opacity: 0, y: -10 }}
336
+ initial={{ opacity: 0, y: -10 }}
337
+ key="editing"
338
+ transition={{ duration: 0.2 }}
339
+ >
340
+ <Flexbox gap={12}>
341
+ {!mobile && <Typography.Text strong>{t('profile.usernameInputHint')}</Typography.Text>}
342
+ <Input
343
+ autoFocus
344
+ onChange={handleInputChange}
345
+ onPressEnter={handleSave}
346
+ placeholder={t('profile.usernamePlaceholder')}
347
+ status={error ? 'error' : undefined}
348
+ value={editValue}
349
+ />
350
+ {error && (
351
+ <Typography.Text style={{ fontSize: 12 }} type="danger">
352
+ {error}
353
+ </Typography.Text>
354
+ )}
355
+ <Flexbox gap={8} horizontal justify="flex-end">
356
+ <Button disabled={saving} onClick={handleCancel} size="small">
357
+ {t('profile.cancel')}
358
+ </Button>
359
+ <Button loading={saving} onClick={handleSave} size="small" type="primary">
360
+ {t('profile.save')}
361
+ </Button>
362
+ </Flexbox>
363
+ </Flexbox>
364
+ </motion.div>
365
+ );
366
+
367
+ const displayContent = (
368
+ <motion.div
369
+ animate={{ opacity: 1 }}
370
+ exit={{ opacity: 0 }}
371
+ initial={{ opacity: 0 }}
372
+ key="display"
373
+ transition={{ duration: 0.2 }}
374
+ >
375
+ {mobile ? (
376
+ <Typography.Text>{username || '--'}</Typography.Text>
377
+ ) : (
378
+ <Flexbox align="center" horizontal justify="space-between">
379
+ <Typography.Text>{username || '--'}</Typography.Text>
380
+ <Typography.Text onClick={handleStartEdit} style={{ cursor: 'pointer', fontSize: 13 }}>
381
+ {t('profile.updateUsername')}
382
+ </Typography.Text>
383
+ </Flexbox>
384
+ )}
385
+ </motion.div>
386
+ );
387
+
388
+ if (mobile) {
389
+ return (
390
+ <Flexbox gap={12} style={rowStyle}>
391
+ <Flexbox align="center" horizontal justify="space-between">
392
+ <Typography.Text strong>{t('profile.username')}</Typography.Text>
393
+ {!isEditing && (
394
+ <Typography.Text onClick={handleStartEdit} style={{ cursor: 'pointer', fontSize: 13 }}>
395
+ {t('profile.updateUsername')}
396
+ </Typography.Text>
397
+ )}
398
+ </Flexbox>
399
+ <AnimatePresence mode="wait">{isEditing ? editingContent : displayContent}</AnimatePresence>
400
+ </Flexbox>
401
+ );
402
+ }
403
+
284
404
  return (
285
405
  <Flexbox gap={24} horizontal style={rowStyle}>
286
406
  <Typography.Text style={labelStyle}>{t('profile.username')}</Typography.Text>
287
407
  <Flexbox style={{ flex: 1 }}>
288
- <AnimatePresence mode="wait">
289
- {isEditing ? (
290
- <motion.div
291
- animate={{ opacity: 1, y: 0 }}
292
- exit={{ opacity: 0, y: -10 }}
293
- initial={{ opacity: 0, y: -10 }}
294
- key="editing"
295
- transition={{ duration: 0.2 }}
296
- >
297
- <Flexbox gap={12}>
298
- <Typography.Text strong>{t('profile.usernameInputHint')}</Typography.Text>
299
- <Input
300
- autoFocus
301
- onChange={handleInputChange}
302
- onPressEnter={handleSave}
303
- placeholder={t('profile.usernamePlaceholder')}
304
- status={error ? 'error' : undefined}
305
- value={editValue}
306
- />
307
- {error && (
308
- <Typography.Text style={{ fontSize: 12 }} type="danger">
309
- {error}
310
- </Typography.Text>
311
- )}
312
- <Flexbox gap={8} horizontal justify="flex-end">
313
- <Button disabled={saving} onClick={handleCancel} size="small">
314
- {t('profile.cancel')}
315
- </Button>
316
- <Button loading={saving} onClick={handleSave} size="small" type="primary">
317
- {t('profile.save')}
318
- </Button>
319
- </Flexbox>
320
- </Flexbox>
321
- </motion.div>
322
- ) : (
323
- <motion.div
324
- animate={{ opacity: 1 }}
325
- exit={{ opacity: 0 }}
326
- initial={{ opacity: 0 }}
327
- key="display"
328
- transition={{ duration: 0.2 }}
329
- >
330
- <Flexbox align="center" horizontal justify="space-between">
331
- <Typography.Text>{username || '--'}</Typography.Text>
332
- <Typography.Text
333
- onClick={handleStartEdit}
334
- style={{ cursor: 'pointer', fontSize: 13 }}
335
- >
336
- {t('profile.updateUsername')}
337
- </Typography.Text>
338
- </Flexbox>
339
- </motion.div>
340
- )}
341
- </AnimatePresence>
408
+ <AnimatePresence mode="wait">{isEditing ? editingContent : displayContent}</AnimatePresence>
342
409
  </Flexbox>
343
410
  </Flexbox>
344
411
  );
345
412
  });
346
413
 
347
- const PasswordRow = memo(() => {
414
+ const PasswordRow = memo<{ mobile?: boolean }>(({ mobile }) => {
348
415
  const { t } = useTranslation('auth');
349
416
  const userProfile = useUserStore(userProfileSelectors.userProfile);
350
417
  const hasPasswordAccount = useUserStore(authSelectors.hasPasswordAccount);
@@ -387,6 +454,7 @@ const PasswordRow = memo(() => {
387
454
  </Typography.Text>
388
455
  }
389
456
  label={t('profile.password')}
457
+ mobile={mobile}
390
458
  >
391
459
  <Typography.Text>{hasPasswordAccount ? '••••••' : '--'}</Typography.Text>
392
460
  </ProfileRow>
@@ -435,24 +503,24 @@ const Client = memo<{ mobile?: boolean }>(({ mobile }) => {
435
503
  <Divider style={{ marginBlock: 0 }} />
436
504
 
437
505
  {/* Avatar Row - Editable */}
438
- <AvatarRow />
506
+ <AvatarRow mobile={mobile} />
439
507
 
440
508
  <Divider style={{ margin: 0 }} />
441
509
 
442
510
  {/* Full Name Row - Editable */}
443
- <FullNameRow />
511
+ <FullNameRow mobile={mobile} />
444
512
 
445
513
  <Divider style={{ margin: 0 }} />
446
514
 
447
515
  {/* Username Row - Editable */}
448
- <UsernameRow />
516
+ <UsernameRow mobile={mobile} />
449
517
 
450
518
  <Divider style={{ margin: 0 }} />
451
519
 
452
520
  {/* Password Row - For Better Auth users to change or set password */}
453
521
  {isLoginWithBetterAuth && (
454
522
  <>
455
- <PasswordRow />
523
+ <PasswordRow mobile={mobile} />
456
524
  <Divider style={{ margin: 0 }} />
457
525
  </>
458
526
  )}
@@ -460,7 +528,7 @@ const Client = memo<{ mobile?: boolean }>(({ mobile }) => {
460
528
  {/* Email Row - Read Only */}
461
529
  {isLoginWithAuth && userProfile?.email && (
462
530
  <>
463
- <ProfileRow label={t('profile.email')}>
531
+ <ProfileRow label={t('profile.email')} mobile={mobile}>
464
532
  <Typography.Text>{userProfile.email}</Typography.Text>
465
533
  </ProfileRow>
466
534
  <Divider style={{ margin: 0 }} />
@@ -469,7 +537,7 @@ const Client = memo<{ mobile?: boolean }>(({ mobile }) => {
469
537
 
470
538
  {/* SSO Providers Row */}
471
539
  {isLoginWithAuth && (
472
- <ProfileRow label={t('profile.sso.providers')}>
540
+ <ProfileRow label={t('profile.sso.providers')} mobile={mobile}>
473
541
  <SSOProvidersList />
474
542
  </ProfileRow>
475
543
  )}
@@ -1,8 +1,8 @@
1
1
  import { Tag } from '@lobehub/ui';
2
2
  import { useTheme } from 'antd-style';
3
- import Link from 'next/link';
4
3
  import { memo } from 'react';
5
4
  import { useTranslation } from 'react-i18next';
5
+ import { Link } from 'react-router-dom';
6
6
  import urlJoin from 'url-join';
7
7
 
8
8
  import { OFFICIAL_URL } from '@/const/url';
@@ -37,12 +37,12 @@ const PlanTag = memo<PlanTagProps>(({ type = PlanType.Preview }) => {
37
37
 
38
38
  return (
39
39
  <Link
40
- href={urlJoin(
40
+ style={{ cursor: 'pointer' }}
41
+ target={isDesktop ? '_blank' : undefined}
42
+ to={urlJoin(
41
43
  isDesktop ? OFFICIAL_URL : '/',
42
44
  isFree ? '/subscription/plans' : '/subscription/usage',
43
45
  )}
44
- style={{ cursor: 'pointer' }}
45
- target={isDesktop ? '_blank' : undefined}
46
46
  >
47
47
  <PlanIcon plan={type} size={22} type={'tag'} />
48
48
  </Link>
@@ -54,6 +54,7 @@ export default {
54
54
  },
55
55
  betterAuth: {
56
56
  errors: {
57
+ confirmPasswordRequired: '请确认密码',
57
58
  emailExists: '该邮箱已注册,请直接登录',
58
59
  emailInvalid: '请输入有效的邮箱地址或用户名',
59
60
  emailNotRegistered: '该邮箱或用户名尚未注册',
@@ -65,6 +66,7 @@ export default {
65
66
  passwordFormat: '密码必须同时包含字母和数字',
66
67
  passwordMaxLength: '密码最多不超过 64 个字符',
67
68
  passwordMinLength: '密码至少需要 8 个字符',
69
+ passwordMismatch: '两次输入的密码不一致',
68
70
  passwordRequired: '请输入密码',
69
71
  usernameNotRegistered: '该用户名尚未注册',
70
72
  usernameRequired: '请输入用户名',
@@ -125,6 +127,7 @@ export default {
125
127
  submit: '登录',
126
128
  },
127
129
  signup: {
130
+ confirmPasswordPlaceholder: '请确认密码',
128
131
  emailPlaceholder: '请输入邮箱地址',
129
132
  error: '注册失败,请重试',
130
133
  firstNamePlaceholder: '名字',
@@ -806,6 +806,7 @@ export default {
806
806
  noEnabled: '暂无启用插件',
807
807
  store: '插件商店',
808
808
  },
809
+
809
810
  tabs: {
810
811
  all: '全部',
811
812
  installed: '已启用',