@lobehub/chat 0.157.2 → 0.158.0

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 (92) hide show
  1. package/.eslintignore +1 -2
  2. package/CHANGELOG.md +25 -0
  3. package/locales/ar/auth.json +2 -0
  4. package/locales/ar/common.json +1 -0
  5. package/locales/bg-BG/auth.json +2 -0
  6. package/locales/bg-BG/common.json +1 -0
  7. package/locales/bg-BG/error.json +3 -3
  8. package/locales/de-DE/auth.json +2 -0
  9. package/locales/de-DE/common.json +1 -0
  10. package/locales/en-US/auth.json +2 -0
  11. package/locales/en-US/common.json +1 -0
  12. package/locales/es-ES/auth.json +2 -0
  13. package/locales/es-ES/common.json +1 -0
  14. package/locales/fr-FR/auth.json +2 -0
  15. package/locales/fr-FR/common.json +1 -0
  16. package/locales/it-IT/auth.json +2 -0
  17. package/locales/it-IT/common.json +1 -0
  18. package/locales/ja-JP/auth.json +2 -0
  19. package/locales/ja-JP/common.json +1 -0
  20. package/locales/ko-KR/auth.json +2 -0
  21. package/locales/ko-KR/common.json +1 -0
  22. package/locales/nl-NL/auth.json +2 -0
  23. package/locales/nl-NL/common.json +50 -49
  24. package/locales/pl-PL/auth.json +2 -0
  25. package/locales/pl-PL/common.json +1 -0
  26. package/locales/pl-PL/error.json +3 -3
  27. package/locales/pt-BR/auth.json +2 -0
  28. package/locales/pt-BR/common.json +1 -0
  29. package/locales/ru-RU/auth.json +2 -0
  30. package/locales/ru-RU/common.json +1 -0
  31. package/locales/ru-RU/error.json +3 -3
  32. package/locales/tr-TR/auth.json +2 -0
  33. package/locales/tr-TR/common.json +1 -0
  34. package/locales/vi-VN/auth.json +2 -0
  35. package/locales/vi-VN/common.json +1 -0
  36. package/locales/zh-CN/auth.json +2 -0
  37. package/locales/zh-CN/common.json +1 -0
  38. package/locales/zh-TW/auth.json +2 -0
  39. package/locales/zh-TW/common.json +1 -0
  40. package/package.json +1 -1
  41. package/src/app/(main)/(mobile)/me/(home)/__tests__/UserBanner.test.tsx +80 -0
  42. package/src/app/(main)/(mobile)/me/(home)/__tests__/useCategory.test.tsx +116 -0
  43. package/src/app/(main)/(mobile)/me/(home)/features/Category.tsx +15 -0
  44. package/src/app/(main)/(mobile)/me/(home)/features/UserBanner.tsx +37 -0
  45. package/src/app/(main)/(mobile)/me/(home)/features/useCategory.tsx +95 -0
  46. package/src/app/(main)/(mobile)/me/{page.tsx → (home)/page.tsx} +6 -10
  47. package/src/app/(main)/(mobile)/me/data/features/Category.tsx +48 -0
  48. package/src/app/(main)/(mobile)/me/data/features/Header.tsx +33 -0
  49. package/src/app/(main)/(mobile)/me/data/layout.tsx +13 -0
  50. package/src/app/(main)/(mobile)/me/data/loading.tsx +5 -0
  51. package/src/app/(main)/(mobile)/me/data/page.tsx +17 -0
  52. package/src/app/(main)/(mobile)/me/profile/features/Category.tsx +45 -0
  53. package/src/app/(main)/(mobile)/me/profile/features/Header.tsx +33 -0
  54. package/src/app/(main)/(mobile)/me/profile/layout.tsx +16 -0
  55. package/src/app/(main)/(mobile)/me/profile/loading.tsx +5 -0
  56. package/src/app/(main)/(mobile)/me/profile/page.tsx +17 -0
  57. package/src/app/(main)/(mobile)/me/settings/features/Category.tsx +15 -0
  58. package/src/app/(main)/(mobile)/me/settings/features/Header.tsx +33 -0
  59. package/src/app/(main)/(mobile)/me/settings/features/useCategory.tsx +57 -0
  60. package/src/app/(main)/(mobile)/me/settings/layout.tsx +13 -0
  61. package/src/app/(main)/(mobile)/me/settings/loading.tsx +5 -0
  62. package/src/app/(main)/(mobile)/me/settings/page.tsx +17 -0
  63. package/src/app/(main)/_layout/Mobile.tsx +5 -4
  64. package/src/app/(main)/profile/[[...slugs]]/Client.tsx +74 -0
  65. package/src/app/(main)/profile/[[...slugs]]/page.tsx +18 -0
  66. package/src/app/(main)/profile/_layout/Mobile/Header.tsx +26 -0
  67. package/src/app/(main)/profile/_layout/Mobile/index.tsx +16 -0
  68. package/src/app/(main)/profile/layout.tsx +20 -0
  69. package/src/app/(main)/profile/loading.tsx +23 -0
  70. package/src/app/(main)/settings/_layout/Mobile/Header.tsx +2 -1
  71. package/src/app/(main)/settings/hooks/useCategory.tsx +7 -13
  72. package/src/app/@modal/layout.tsx +3 -0
  73. package/src/components/Cell/Divider.tsx +3 -2
  74. package/src/components/Cell/index.tsx +28 -18
  75. package/src/features/User/DataStatistics.tsx +3 -1
  76. package/src/features/User/UserLoginOrSignup.tsx +2 -2
  77. package/src/features/User/UserPanel/PanelContent.tsx +9 -3
  78. package/src/features/User/UserPanel/useMenu.tsx +29 -29
  79. package/src/features/User/__tests__/PanelContent.test.tsx +7 -0
  80. package/src/features/User/__tests__/useMenu.test.tsx +142 -0
  81. package/src/layout/AuthProvider/Clerk/useAppearance.ts +5 -4
  82. package/src/locales/default/auth.ts +2 -0
  83. package/src/locales/default/common.ts +1 -0
  84. package/src/store/user/slices/auth/selectors.ts +2 -1
  85. package/src/app/(auth)/profile/[[...slugs]]/PageTitle.tsx +0 -13
  86. package/src/app/(auth)/profile/[[...slugs]]/page.tsx +0 -14
  87. package/src/app/(main)/(mobile)/me/features/Cate.tsx +0 -33
  88. package/src/app/(main)/(mobile)/me/features/ExtraCate.tsx +0 -24
  89. package/src/app/(main)/(mobile)/me/features/useExtraCate.tsx +0 -62
  90. /package/src/app/(main)/(mobile)/me/{features → (home)/features}/Header.tsx +0 -0
  91. /package/src/app/(main)/(mobile)/me/{layout.tsx → (home)/layout.tsx} +0 -0
  92. /package/src/app/(main)/(mobile)/me/{loading.tsx → (home)/loading.tsx} +0 -0
@@ -0,0 +1,17 @@
1
+ import { redirect } from 'next/navigation';
2
+
3
+ import { isMobileDevice } from '@/utils/responsive';
4
+
5
+ import Category from './features/Category';
6
+
7
+ const Page = () => {
8
+ const mobile = isMobileDevice();
9
+
10
+ if (!mobile) return redirect('/settings/common');
11
+
12
+ return <Category />;
13
+ };
14
+
15
+ Page.displayName = 'MeSettings';
16
+
17
+ export default Page;
@@ -1,24 +1,25 @@
1
1
  'use client';
2
2
 
3
3
  import { usePathname } from 'next/navigation';
4
+ import qs from 'query-string';
4
5
  import { memo } from 'react';
5
6
 
6
7
  import { useQuery } from '@/hooks/useQuery';
7
8
 
8
9
  import { LayoutProps } from './type';
9
10
 
10
- const MOBILE_IGNORE_NAV_ROUTES = ['/settings/', '/chat/'];
11
+ const MOBILE_NAV_ROUTES = new Set(['/chat', '/market', '/me']);
11
12
 
12
13
  const Layout = memo(({ children, nav }: LayoutProps) => {
13
14
  const { showMobileWorkspace } = useQuery();
14
15
  const pathname = usePathname();
15
- const hideNav =
16
- showMobileWorkspace || MOBILE_IGNORE_NAV_ROUTES.some((path) => pathname.startsWith(path));
16
+ const { url } = qs.parseUrl(pathname);
17
+ const showNav = !showMobileWorkspace && MOBILE_NAV_ROUTES.has(url);
17
18
 
18
19
  return (
19
20
  <>
20
21
  {children}
21
- {!hideNav && nav}
22
+ {showNav && nav}
22
23
  </>
23
24
  );
24
25
  });
@@ -0,0 +1,74 @@
1
+ 'use client';
2
+
3
+ import { UserProfile } from '@clerk/nextjs';
4
+ import { ElementsConfig } from '@clerk/types';
5
+ import { createStyles } from 'antd-style';
6
+ import { memo } from 'react';
7
+
8
+ export const useStyles = createStyles(
9
+ ({ css, token, cx }, mobile: boolean) =>
10
+ ({
11
+ cardBox: css`
12
+ width: 100%;
13
+ max-width: unset;
14
+ height: 100%;
15
+
16
+ border: unset;
17
+ border-radius: unset;
18
+ box-shadow: unset;
19
+ `,
20
+ footer: cx(
21
+ mobile &&
22
+ css`
23
+ display: none;
24
+ `,
25
+ ),
26
+ navbar: css`
27
+ flex: none;
28
+
29
+ width: 280px;
30
+ max-width: unset;
31
+ margin-right: 0;
32
+ padding: 24px 12px 16px;
33
+
34
+ background: ${token.colorBgContainer};
35
+ border-right: 1px solid ${token.colorSplit};
36
+ `,
37
+ navbarMobileMenuRow: cx(
38
+ mobile &&
39
+ css`
40
+ display: none;
41
+ `,
42
+ ),
43
+ pageScrollBox: css`
44
+ align-self: center;
45
+ width: 100%;
46
+ max-width: 1024px;
47
+ `,
48
+ rootBox: css`
49
+ width: 100%;
50
+ height: 100%;
51
+ `,
52
+ scrollBox: css`
53
+ background: ${token.colorBgLayout};
54
+ border: unset;
55
+ border-radius: unset;
56
+ `,
57
+ }) as Partial<{
58
+ [k in keyof ElementsConfig]: any;
59
+ }>,
60
+ );
61
+
62
+ const Client = memo<{ mobile?: boolean }>(({ mobile }) => {
63
+ const { styles } = useStyles(mobile);
64
+
65
+ return (
66
+ <UserProfile
67
+ appearance={{
68
+ elements: styles,
69
+ }}
70
+ />
71
+ );
72
+ });
73
+
74
+ export default Client;
@@ -0,0 +1,18 @@
1
+ import { translation } from '@/server/translation';
2
+ import { isMobileDevice } from '@/utils/responsive';
3
+
4
+ import Client from './Client';
5
+
6
+ export const generateMetadata = async () => {
7
+ const { t } = await translation('common');
8
+ return {
9
+ title: t('userButton.profile'),
10
+ };
11
+ };
12
+
13
+ const Page = () => {
14
+ const mobile = isMobileDevice();
15
+ return <Client mobile={mobile} />;
16
+ };
17
+
18
+ export default Page;
@@ -0,0 +1,26 @@
1
+ 'use client';
2
+
3
+ import { MobileNavBar, MobileNavBarTitle } from '@lobehub/ui';
4
+ import { usePathname, useRouter } from 'next/navigation';
5
+ import { memo } from 'react';
6
+ import { useTranslation } from 'react-i18next';
7
+
8
+ import { mobileHeaderSticky } from '@/styles/mobileHeader';
9
+
10
+ const Header = memo(() => {
11
+ const { t } = useTranslation('auth');
12
+
13
+ const router = useRouter();
14
+ const pathname = usePathname();
15
+ const isSecurity = pathname.startsWith('/prifile/security');
16
+ return (
17
+ <MobileNavBar
18
+ center={<MobileNavBarTitle title={t(isSecurity ? 'security' : 'profile')} />}
19
+ onBackClick={() => router.push('/me/profile')}
20
+ showBackButton
21
+ style={mobileHeaderSticky}
22
+ />
23
+ );
24
+ });
25
+
26
+ export default Header;
@@ -0,0 +1,16 @@
1
+ import { PropsWithChildren } from 'react';
2
+
3
+ import Header from './Header';
4
+
5
+ const Layout = ({ children }: PropsWithChildren) => {
6
+ return (
7
+ <>
8
+ <Header />
9
+ {children}
10
+ </>
11
+ );
12
+ };
13
+
14
+ Layout.displayName = 'ProfileMobileLayout';
15
+
16
+ export default Layout;
@@ -0,0 +1,20 @@
1
+ import { notFound } from 'next/navigation';
2
+ import { PropsWithChildren } from 'react';
3
+
4
+ import { enableClerk } from '@/const/auth';
5
+ import { isMobileDevice } from '@/utils/responsive';
6
+
7
+ import MobileLayout from './_layout/Mobile';
8
+
9
+ const Layout = ({ children }: PropsWithChildren) => {
10
+ if (!enableClerk) return notFound();
11
+
12
+ const mobile = isMobileDevice();
13
+ if (mobile) return <MobileLayout>{children}</MobileLayout>;
14
+
15
+ return children;
16
+ };
17
+
18
+ Layout.displayName = 'ProfileLayout';
19
+
20
+ export default Layout;
@@ -0,0 +1,23 @@
1
+ import { Flexbox } from 'react-layout-kit';
2
+
3
+ import SkeletonLoading from '@/components/SkeletonLoading';
4
+ import { isMobileDevice } from '@/utils/responsive';
5
+
6
+ const Loading = () => {
7
+ const mobile = isMobileDevice();
8
+ if (mobile) return <SkeletonLoading paragraph={{ rows: 8 }} />;
9
+ return (
10
+ <Flexbox horizontal style={{ position: 'relative' }} width={'100%'}>
11
+ <Flexbox padding={24} width={256}>
12
+ <SkeletonLoading paragraph={{ rows: 8 }} />;
13
+ </Flexbox>
14
+ <Flexbox align={'center'} flex={1}>
15
+ <Flexbox padding={24} style={{ maxWidth: 1024 }} width={'100%'}>
16
+ <SkeletonLoading paragraph={{ rows: 8 }} />;
17
+ </Flexbox>
18
+ </Flexbox>
19
+ </Flexbox>
20
+ );
21
+ };
22
+
23
+ export default Loading;
@@ -7,6 +7,7 @@ import { memo } from 'react';
7
7
  import { useTranslation } from 'react-i18next';
8
8
  import { Flexbox } from 'react-layout-kit';
9
9
 
10
+ import { enableAuth } from '@/const/auth';
10
11
  import { useActiveSettingsKey } from '@/hooks/useActiveSettingsKey';
11
12
  import { SettingsTabs } from '@/store/global/initialState';
12
13
  import { mobileHeaderSticky } from '@/styles/mobileHeader';
@@ -32,7 +33,7 @@ const Header = memo(() => {
32
33
  }
33
34
  />
34
35
  }
35
- onBackClick={() => router.push('/me')}
36
+ onBackClick={() => router.push(enableAuth ? '/me/settings' : '/me')}
36
37
  showBackButton
37
38
  style={mobileHeaderSticky}
38
39
  />
@@ -9,26 +9,20 @@ import type { MenuProps } from '@/components/Menu';
9
9
  import { SettingsTabs } from '@/store/global/initialState';
10
10
  import { featureFlagsSelectors, useServerConfigStore } from '@/store/serverConfig';
11
11
 
12
- interface UseCategoryOptions {
13
- mobile?: boolean;
14
- }
15
-
16
- export const useCategory = ({ mobile }: UseCategoryOptions = {}) => {
12
+ export const useCategory = () => {
17
13
  const { t } = useTranslation('setting');
18
14
  const { enableWebrtc, showLLM } = useServerConfigStore(featureFlagsSelectors);
19
15
 
20
- const iconSize = mobile ? { fontSize: 20 } : undefined;
21
-
22
16
  const cateItems: MenuProps['items'] = useMemo(
23
17
  () =>
24
18
  [
25
19
  {
26
- icon: <Icon icon={Settings2} size={iconSize} />,
20
+ icon: <Icon icon={Settings2} />,
27
21
  key: SettingsTabs.Common,
28
22
  label: t('tab.common'),
29
23
  },
30
24
  enableWebrtc && {
31
- icon: <Icon icon={Cloudy} size={iconSize} />,
25
+ icon: <Icon icon={Cloudy} />,
32
26
  key: SettingsTabs.Sync,
33
27
  label: (
34
28
  <Flexbox align={'center'} gap={8} horizontal>
@@ -40,18 +34,18 @@ export const useCategory = ({ mobile }: UseCategoryOptions = {}) => {
40
34
  ),
41
35
  },
42
36
  showLLM && {
43
- icon: <Icon icon={Brain} size={iconSize} />,
37
+ icon: <Icon icon={Brain} />,
44
38
  key: SettingsTabs.LLM,
45
39
  label: t('tab.llm'),
46
40
  },
47
- { icon: <Icon icon={Mic2} size={iconSize} />, key: SettingsTabs.TTS, label: t('tab.tts') },
41
+ { icon: <Icon icon={Mic2} />, key: SettingsTabs.TTS, label: t('tab.tts') },
48
42
  {
49
- icon: <Icon icon={Bot} size={iconSize} />,
43
+ icon: <Icon icon={Bot} />,
50
44
  key: SettingsTabs.Agent,
51
45
  label: t('tab.agent'),
52
46
  },
53
47
  {
54
- icon: <Icon icon={Info} size={iconSize} />,
48
+ icon: <Icon icon={Info} />,
55
49
  key: SettingsTabs.About,
56
50
  label: t('tab.about'),
57
51
  },
@@ -1,12 +1,14 @@
1
1
  'use client';
2
2
 
3
3
  import { Modal } from '@lobehub/ui';
4
+ import { useTheme } from 'antd-style';
4
5
  import { useRouter } from 'next/navigation';
5
6
  import { PropsWithChildren, memo, useState } from 'react';
6
7
 
7
8
  const SessionSettingsModal = memo<PropsWithChildren>(({ children }) => {
8
9
  const [open, setOpen] = useState(true);
9
10
  const router = useRouter();
11
+ const theme = useTheme();
10
12
 
11
13
  return (
12
14
  <Modal
@@ -18,6 +20,7 @@ const SessionSettingsModal = memo<PropsWithChildren>(({ children }) => {
18
20
  open={open}
19
21
  styles={{
20
22
  body: { display: 'flex', minHeight: 'min(75vh, 750px)', overflow: 'hidden', padding: 0 },
23
+ content: { border: 'none', boxShadow: `0 0 0 1px ${theme.colorBorderSecondary}` },
21
24
  }}
22
25
  title={false}
23
26
  width={1024}
@@ -1,14 +1,15 @@
1
1
  'use client';
2
2
 
3
3
  import { createStyles } from 'antd-style';
4
+ import { rgba } from 'polished';
4
5
  import { memo } from 'react';
5
6
 
6
7
  const useStyles = createStyles(
7
- ({ css, token }) => css`
8
+ ({ css, token, isDarkMode }) => css`
8
9
  flex: none;
9
10
  width: 100%;
10
11
  height: 6px;
11
- background: ${token.colorFillTertiary};
12
+ background: ${isDarkMode ? rgba(token.colorFillTertiary, 0.04) : token.colorFillTertiary};
12
13
  `,
13
14
  );
14
15
 
@@ -1,42 +1,52 @@
1
- import { Icon, List } from '@lobehub/ui';
1
+ import { Icon, IconProps } from '@lobehub/ui';
2
2
  import { createStyles } from 'antd-style';
3
3
  import { ChevronRight } from 'lucide-react';
4
4
  import { ReactNode, memo } from 'react';
5
+ import { Flexbox } from 'react-layout-kit';
5
6
 
6
- const { Item } = List;
7
+ import Divider from './Divider';
7
8
 
8
9
  const useStyles = createStyles(({ css, token }) => ({
9
10
  container: css`
10
11
  position: relative;
11
-
12
- gap: 12px;
13
-
14
- padding: 16px !important;
15
-
16
- background: ${token.colorBgLayout};
12
+ font-size: 15px;
17
13
  border-radius: 0;
14
+
15
+ &:active {
16
+ background: ${token.colorFillTertiary};
17
+ }
18
18
  `,
19
19
  }));
20
20
 
21
21
  export interface CellProps {
22
- icon: ReactNode;
23
- label: string | ReactNode;
22
+ icon?: IconProps['icon'];
23
+ key?: string | number;
24
+ label?: string | ReactNode;
24
25
  onClick?: () => void;
26
+ type?: 'divider';
25
27
  }
26
28
 
27
- const Cell = memo<CellProps>(({ label, icon, onClick }) => {
28
- const { cx, styles } = useStyles();
29
+ const Cell = memo<CellProps>(({ label, icon, onClick, type }) => {
30
+ const { cx, styles, theme } = useStyles();
31
+
32
+ if (type === 'divider') return <Divider />;
29
33
 
30
34
  return (
31
- <Item
32
- active={false}
33
- avatar={icon}
35
+ <Flexbox
36
+ align={'center'}
34
37
  className={cx(styles.container)}
38
+ gap={12}
39
+ horizontal
40
+ justify={'space-between'}
35
41
  onClick={onClick}
36
- title={label as string}
42
+ padding={16}
37
43
  >
38
- <Icon icon={ChevronRight} size={{ fontSize: 16 }} />
39
- </Item>
44
+ <Flexbox align={'center'} gap={12} horizontal>
45
+ {icon && <Icon color={theme.colorPrimaryBorder} icon={icon} size={{ fontSize: 20 }} />}
46
+ {label}
47
+ </Flexbox>
48
+ <Icon color={theme.colorBorder} icon={ChevronRight} size={{ fontSize: 16 }} />
49
+ </Flexbox>
40
50
  );
41
51
  });
42
52
 
@@ -13,6 +13,7 @@ import useSWR from 'swr';
13
13
  import { messageService } from '@/services/message';
14
14
  import { sessionService } from '@/services/session';
15
15
  import { topicService } from '@/services/topic';
16
+ import { useServerConfigStore } from '@/store/serverConfig';
16
17
 
17
18
  const useStyles = createStyles(({ css, token }) => ({
18
19
  card: css`
@@ -53,6 +54,7 @@ const formatNumber = (num: any) => {
53
54
  };
54
55
 
55
56
  const DataStatistics = memo<Omit<FlexboxProps, 'children'>>(({ style, ...rest }) => {
57
+ const mobile = useServerConfigStore((s) => s.isMobile);
56
58
  // sessions
57
59
  const { data: sessions, isLoading: sessionsLoading } = useSWR(
58
60
  'count-sessions',
@@ -111,7 +113,7 @@ const DataStatistics = memo<Omit<FlexboxProps, 'children'>>(({ style, ...rest })
111
113
  <Flexbox
112
114
  align={'center'}
113
115
  className={styles.card}
114
- flex={showBadge ? 2 : 1}
116
+ flex={showBadge && !mobile ? 2 : 1}
115
117
  gap={4}
116
118
  horizontal
117
119
  justify={'space-between'}
@@ -3,7 +3,7 @@ import { memo } from 'react';
3
3
  import { useTranslation } from 'react-i18next';
4
4
  import { Flexbox } from 'react-layout-kit';
5
5
 
6
- import UserInfo from '@/features/User/UserInfo';
6
+ import UserInfo from './UserInfo';
7
7
 
8
8
  const UserLoginOrSignup = memo<{ onClick: () => void }>(({ onClick }) => {
9
9
  const { t } = useTranslation('auth');
@@ -11,7 +11,7 @@ const UserLoginOrSignup = memo<{ onClick: () => void }>(({ onClick }) => {
11
11
  return (
12
12
  <>
13
13
  <UserInfo />
14
- <Flexbox paddingBlock={'0 12px'} paddingInline={16} width={'100%'}>
14
+ <Flexbox paddingBlock={12} paddingInline={16} width={'100%'}>
15
15
  <Button block onClick={onClick} type={'primary'}>
16
16
  {t('loginOrSignup')}
17
17
  </Button>
@@ -45,13 +45,19 @@ const PanelContent = memo<{ closePopover: () => void }>(({ closePopover }) => {
45
45
  return (
46
46
  <Flexbox gap={2} style={{ minWidth: 300 }}>
47
47
  {!enableAuth ? (
48
- <UserInfo />
48
+ <>
49
+ <UserInfo />
50
+ <DataStatistics />
51
+ </>
49
52
  ) : isLoginWithAuth ? (
50
- <UserInfo onClick={handleOpenProfile} />
53
+ <>
54
+ <UserInfo onClick={handleOpenProfile} />
55
+ <DataStatistics />
56
+ </>
51
57
  ) : (
52
58
  <UserLoginOrSignup onClick={handleSignIn} />
53
59
  )}
54
- <DataStatistics />
60
+
55
61
  <Menu items={mainItems} onClick={closePopover} />
56
62
  <Flexbox
57
63
  align={'center'}
@@ -57,7 +57,21 @@ export const useMenu = () => {
57
57
  const hasNewVersion = useNewVersion();
58
58
  const openSettings = useOpenSettings();
59
59
  const { t } = useTranslation(['common', 'setting', 'auth']);
60
- const isSignedIn = useUserStore(authSelectors.isLoginWithAuth);
60
+ const [isLogin, isLoginWithAuth, isLoginWithClerk, openUserProfile] = useUserStore((s) => [
61
+ authSelectors.isLogin(s),
62
+ authSelectors.isLoginWithAuth(s),
63
+ authSelectors.isLoginWithClerk(s),
64
+ s.openUserProfile,
65
+ ]);
66
+
67
+ const profile: MenuProps['items'] = [
68
+ {
69
+ icon: <Icon icon={CircleUserRound} />,
70
+ key: 'profile',
71
+ label: t('userPanel.profile'),
72
+ onClick: () => openUserProfile(),
73
+ },
74
+ ];
61
75
 
62
76
  const settings: MenuProps['items'] = [
63
77
  {
@@ -82,7 +96,7 @@ export const useMenu = () => {
82
96
  },
83
97
  ];
84
98
 
85
- const exports: MenuProps['items'] = [
99
+ const data: MenuProps['items'] = [
86
100
  {
87
101
  icon: <Icon icon={HardDriveUpload} />,
88
102
  key: 'import',
@@ -123,22 +137,6 @@ export const useMenu = () => {
123
137
  },
124
138
  ];
125
139
 
126
- const openUserProfile = useUserStore((s) => s.openUserProfile);
127
-
128
- const planAndBilling: MenuProps['items'] = [
129
- {
130
- icon: <Icon icon={CircleUserRound} />,
131
- key: 'profile',
132
- label: t('userPanel.profile'),
133
- onClick: () => {
134
- openUserProfile();
135
- },
136
- },
137
- {
138
- type: 'divider',
139
- },
140
- ];
141
-
142
140
  const helps: MenuProps['items'] = [
143
141
  {
144
142
  icon: <Icon icon={DiscordIcon} />,
@@ -192,19 +190,21 @@ export const useMenu = () => {
192
190
  {
193
191
  type: 'divider',
194
192
  },
195
- ...settings,
196
- ...(isSignedIn ? planAndBilling : []),
197
- ...exports,
193
+ ...(isLoginWithClerk ? profile : []),
194
+ ...(isLogin ? settings : []),
195
+ ...(isLogin ? data : []),
198
196
  ...helps,
199
197
  ].filter(Boolean) as MenuProps['items'];
200
198
 
201
- const logoutItems: MenuProps['items'] = [
202
- {
203
- icon: <Icon icon={LogOut} />,
204
- key: 'logout',
205
- label: <span>{t('signout', { ns: 'auth' })}</span>,
206
- },
207
- ];
199
+ const logoutItems: MenuProps['items'] = isLoginWithAuth
200
+ ? [
201
+ {
202
+ icon: <Icon icon={LogOut} />,
203
+ key: 'logout',
204
+ label: <span>{t('signout', { ns: 'auth' })}</span>,
205
+ },
206
+ ]
207
+ : [];
208
208
 
209
209
  return { logoutItems, mainItems };
210
- };
210
+ };
@@ -57,6 +57,10 @@ vi.mock('../UserLoginOrSignup', () => ({
57
57
  )),
58
58
  }));
59
59
 
60
+ vi.mock('../DataStatistics', () => ({
61
+ default: vi.fn(() => <div>Mocked DataStatistics</div>),
62
+ }));
63
+
60
64
  // 定义一个变量来存储 enableAuth 的值
61
65
  let enableAuth = true;
62
66
 
@@ -83,6 +87,7 @@ describe('PanelContent', () => {
83
87
  render(<PanelContent closePopover={closePopover} />);
84
88
 
85
89
  expect(screen.getByText('Mocked UserInfo')).toBeInTheDocument();
90
+ expect(screen.getByText('Mocked DataStatistics')).toBeInTheDocument();
86
91
  expect(screen.queryByText('Mocked SignInBlock')).not.toBeInTheDocument();
87
92
  });
88
93
 
@@ -94,6 +99,7 @@ describe('PanelContent', () => {
94
99
  render(<PanelContent closePopover={closePopover} />);
95
100
 
96
101
  expect(screen.getByText('Mocked SignInBlock')).toBeInTheDocument();
102
+ expect(screen.queryByText('Mocked DataStatistics')).not.toBeInTheDocument();
97
103
  expect(screen.queryByText('Mocked UserInfo')).not.toBeInTheDocument();
98
104
  });
99
105
 
@@ -127,6 +133,7 @@ describe('PanelContent', () => {
127
133
  render(<PanelContent closePopover={closePopover} />);
128
134
 
129
135
  expect(screen.getByText('Mocked UserInfo')).toBeInTheDocument();
136
+ expect(screen.getByText('Mocked DataStatistics')).toBeInTheDocument();
130
137
  expect(screen.queryByText('Mocked SignInBlock')).not.toBeInTheDocument();
131
138
  });
132
139