@lobehub/chat 1.42.5 → 1.43.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 (141) hide show
  1. package/CHANGELOG.md +58 -0
  2. package/changelog/v1.json +21 -0
  3. package/docs/.cdn.cache.json +1 -0
  4. package/docs/changelog/2025-01-03-user-profile.mdx +27 -0
  5. package/docs/changelog/2025-01-03-user-profile.zh-CN.mdx +26 -0
  6. package/docs/self-hosting/advanced/auth/next-auth/wechat.mdx +3 -1
  7. package/docs/self-hosting/advanced/auth/next-auth/wechat.zh-CN.mdx +2 -2
  8. package/locales/ar/auth.json +76 -4
  9. package/locales/bg-BG/auth.json +75 -3
  10. package/locales/de-DE/auth.json +78 -6
  11. package/locales/en-US/auth.json +78 -6
  12. package/locales/es-ES/auth.json +75 -3
  13. package/locales/fa-IR/auth.json +77 -5
  14. package/locales/fr-FR/auth.json +78 -6
  15. package/locales/it-IT/auth.json +76 -4
  16. package/locales/ja-JP/auth.json +76 -4
  17. package/locales/ko-KR/auth.json +75 -3
  18. package/locales/nl-NL/auth.json +76 -4
  19. package/locales/pl-PL/auth.json +76 -4
  20. package/locales/pt-BR/auth.json +76 -4
  21. package/locales/ru-RU/auth.json +75 -3
  22. package/locales/tr-TR/auth.json +74 -3
  23. package/locales/vi-VN/auth.json +75 -3
  24. package/locales/zh-CN/auth.json +75 -3
  25. package/locales/zh-TW/auth.json +75 -3
  26. package/package.json +13 -3
  27. package/src/app/(main)/(mobile)/me/(home)/__tests__/UserBanner.test.tsx +4 -0
  28. package/src/app/(main)/(mobile)/me/(home)/__tests__/useCategory.test.tsx +0 -46
  29. package/src/app/(main)/(mobile)/me/(home)/features/UserBanner.tsx +11 -14
  30. package/src/app/(main)/(mobile)/me/(home)/features/useCategory.tsx +6 -21
  31. package/src/app/(main)/(mobile)/me/profile/features/Category.tsx +38 -21
  32. package/src/app/(main)/(mobile)/me/profile/layout.tsx +0 -3
  33. package/src/app/(main)/(mobile)/me/profile/page.tsx +3 -3
  34. package/src/app/(main)/chat/(workspace)/@topic/features/TopicListContent/ByTimeMode/index.tsx +1 -1
  35. package/src/app/(main)/chat/loading.tsx +2 -2
  36. package/src/app/(main)/discover/loading.tsx +2 -8
  37. package/src/app/(main)/files/loading.tsx +2 -2
  38. package/src/app/(main)/profile/(home)/Client.tsx +53 -0
  39. package/src/app/(main)/profile/(home)/[[...slugs]]/page.tsx +38 -0
  40. package/src/app/(main)/profile/@category/default.tsx +9 -0
  41. package/src/app/(main)/profile/@category/features/CategoryContent.tsx +38 -0
  42. package/src/app/(main)/profile/_layout/Desktop/Header.tsx +85 -0
  43. package/src/app/(main)/profile/_layout/Desktop/SideBar.tsx +42 -0
  44. package/src/app/(main)/profile/_layout/Desktop/index.tsx +48 -0
  45. package/src/app/(main)/profile/_layout/Mobile/Header.tsx +23 -5
  46. package/src/app/(main)/profile/_layout/Mobile/index.tsx +12 -5
  47. package/src/app/(main)/profile/_layout/type.ts +6 -0
  48. package/src/app/(main)/profile/error.tsx +5 -0
  49. package/src/app/(main)/profile/features/ClerkProfile.tsx +72 -0
  50. package/src/app/(main)/profile/hooks/useCategory.tsx +51 -0
  51. package/src/app/(main)/profile/layout.tsx +7 -17
  52. package/src/app/(main)/profile/loading.tsx +2 -22
  53. package/src/app/(main)/profile/not-found.tsx +3 -0
  54. package/src/app/(main)/profile/security/page.tsx +34 -0
  55. package/src/app/(main)/profile/stats/Client.tsx +52 -0
  56. package/src/app/(main)/profile/stats/features/AiHeatmaps.tsx +130 -0
  57. package/src/app/(main)/profile/stats/features/AssistantsRank.tsx +115 -0
  58. package/src/app/(main)/profile/stats/features/ModelsRank.tsx +84 -0
  59. package/src/app/(main)/profile/stats/features/ShareButton/Preview.tsx +159 -0
  60. package/src/app/(main)/profile/stats/features/ShareButton/ShareModal.tsx +87 -0
  61. package/src/app/(main)/profile/stats/features/ShareButton/TotalCard.tsx +39 -0
  62. package/src/app/(main)/profile/stats/features/ShareButton/index.tsx +26 -0
  63. package/src/app/(main)/profile/stats/features/TimeLabel.tsx +30 -0
  64. package/src/app/(main)/profile/stats/features/TopicsRank.tsx +103 -0
  65. package/src/app/(main)/profile/stats/features/TotalAssistants.tsx +56 -0
  66. package/src/app/(main)/profile/stats/features/TotalMessages.tsx +56 -0
  67. package/src/app/(main)/profile/stats/features/TotalTopics.tsx +53 -0
  68. package/src/app/(main)/profile/stats/features/TotalWords.tsx +54 -0
  69. package/src/app/(main)/profile/stats/features/Welcome.tsx +86 -0
  70. package/src/app/(main)/profile/{[[...slugs]] → stats}/page.tsx +4 -5
  71. package/src/app/(main)/repos/[id]/evals/dataset/page.tsx +2 -2
  72. package/src/app/(main)/repos/[id]/evals/evaluation/page.tsx +2 -2
  73. package/src/app/(main)/settings/@category/features/CategoryContent.tsx +1 -1
  74. package/src/app/(main)/settings/_layout/Desktop/index.tsx +1 -1
  75. package/src/app/(main)/settings/_layout/Mobile/Header.tsx +1 -1
  76. package/src/app/(main)/settings/_layout/Mobile/index.tsx +2 -0
  77. package/src/app/(main)/settings/common/features/Theme/index.tsx +2 -17
  78. package/src/app/(main)/settings/loading.tsx +2 -2
  79. package/src/components/Loading/BrandTextLoading/index.tsx +2 -2
  80. package/src/components/Statistic/index.tsx +15 -0
  81. package/src/components/StatisticCard/TitleWithPercentage.tsx +80 -0
  82. package/src/components/StatisticCard/growthPercentage.tsx +8 -0
  83. package/src/components/StatisticCard/index.tsx +209 -0
  84. package/src/const/url.ts +3 -3
  85. package/src/database/server/models/__tests__/message.test.ts +346 -35
  86. package/src/database/server/models/__tests__/session.test.ts +185 -2
  87. package/src/database/server/models/__tests__/topic.test.ts +136 -0
  88. package/src/database/server/models/__tests__/user.test.ts +140 -1
  89. package/src/database/server/models/message.ts +109 -14
  90. package/src/database/server/models/session.ts +75 -4
  91. package/src/database/server/models/topic.ts +43 -3
  92. package/src/database/server/models/user.ts +22 -0
  93. package/src/database/utils/genWhere.ts +39 -0
  94. package/src/features/ShareModal/ShareImage/index.tsx +11 -24
  95. package/src/features/ShareModal/ShareImage/type.ts +1 -6
  96. package/src/features/User/DataStatistics.tsx +21 -14
  97. package/src/features/User/UserPanel/PanelContent.tsx +12 -16
  98. package/src/features/User/UserPanel/useMenu.tsx +4 -6
  99. package/src/features/User/__tests__/PanelContent.test.tsx +4 -0
  100. package/src/features/User/__tests__/useMenu.test.tsx +1 -21
  101. package/src/hooks/useActiveTabKey.ts +34 -1
  102. package/src/{features/ShareModal/ShareImage → hooks}/useScreenshot.ts +51 -6
  103. package/src/locales/default/auth.ts +74 -2
  104. package/src/server/ld.test.ts +1 -1
  105. package/src/server/modules/AssistantStore/index.ts +3 -2
  106. package/src/server/routers/lambda/message.ts +35 -6
  107. package/src/server/routers/lambda/session.ts +17 -3
  108. package/src/server/routers/lambda/topic.ts +17 -3
  109. package/src/server/routers/lambda/user.ts +4 -0
  110. package/src/server/services/changelog/index.ts +1 -1
  111. package/src/services/message/_deprecated.ts +16 -0
  112. package/src/services/message/client.test.ts +0 -18
  113. package/src/services/message/client.ts +12 -9
  114. package/src/services/message/server.ts +12 -4
  115. package/src/services/message/type.ts +15 -3
  116. package/src/services/session/_deprecated.ts +5 -0
  117. package/src/services/session/client.ts +6 -2
  118. package/src/services/session/server.ts +6 -2
  119. package/src/services/session/type.ts +7 -1
  120. package/src/services/topic/_deprecated.ts +5 -0
  121. package/src/services/topic/client.ts +6 -2
  122. package/src/services/topic/server.ts +7 -1
  123. package/src/services/topic/type.ts +7 -2
  124. package/src/services/user/_deprecated.ts +4 -0
  125. package/src/services/user/client.ts +4 -0
  126. package/src/services/user/server.ts +4 -0
  127. package/src/services/user/type.ts +5 -0
  128. package/src/store/global/initialState.ts +6 -0
  129. package/src/store/user/slices/auth/action.test.ts +1 -33
  130. package/src/store/user/slices/auth/action.ts +0 -9
  131. package/src/store/user/slices/common/action.test.ts +2 -2
  132. package/src/types/message/index.ts +5 -0
  133. package/src/types/session/index.ts +8 -0
  134. package/src/types/topic/topic.ts +7 -0
  135. package/src/utils/format.ts +1 -1
  136. package/src/utils/time.ts +23 -0
  137. package/src/app/(main)/profile/[[...slugs]]/Client.tsx +0 -76
  138. package/src/components/Loading/BrandTextLoading/LobeChatText/SVG.tsx +0 -44
  139. package/src/components/Loading/BrandTextLoading/LobeChatText/index.tsx +0 -6
  140. package/src/components/Loading/BrandTextLoading/LobeChatText/style.css +0 -32
  141. package/src/hooks/useActiveSettingsKey.ts +0 -20
@@ -1,43 +1,60 @@
1
1
  'use client';
2
2
 
3
- import { LogOut, ShieldCheck, UserCircle } from 'lucide-react';
3
+ import { ChartColumnBigIcon, LogOut, ShieldCheck, UserCircle } from 'lucide-react';
4
4
  import { useRouter } from 'next/navigation';
5
5
  import { memo } from 'react';
6
6
  import { useTranslation } from 'react-i18next';
7
7
 
8
8
  import Cell, { CellProps } from '@/components/Cell';
9
+ import { isDeprecatedEdition } from '@/const/version';
10
+ import { ProfileTabs } from '@/store/global/initialState';
9
11
  import { useUserStore } from '@/store/user';
12
+ import { authSelectors } from '@/store/user/slices/auth/selectors';
10
13
 
11
14
  const Category = memo(() => {
15
+ const [isLogin, enableAuth, isLoginWithClerk, signOut] = useUserStore((s) => [
16
+ authSelectors.isLogin(s),
17
+ authSelectors.enabledAuth(s),
18
+ authSelectors.isLoginWithClerk(s),
19
+ s.logout,
20
+ ]);
12
21
  const router = useRouter();
13
22
  const { t } = useTranslation('auth');
14
- const signOut = useUserStore((s) => s.logout);
15
23
  const items: CellProps[] = [
16
24
  {
17
25
  icon: UserCircle,
18
- key: 'profile',
19
- label: t('profile'),
26
+ key: ProfileTabs.Profile,
27
+ label: t('tab.profile'),
20
28
  onClick: () => router.push('/profile'),
21
29
  },
22
- {
23
- icon: ShieldCheck,
24
- key: 'security',
25
- label: t('security'),
26
- onClick: () => router.push('/profile/security'),
27
- },
28
- {
29
- type: 'divider',
30
- },
31
- {
32
- icon: LogOut,
33
- key: 'logout',
34
- label: t('signout', { ns: 'auth' }),
35
- onClick: () => {
36
- signOut();
37
- router.push('/login');
30
+ enableAuth &&
31
+ isLoginWithClerk && {
32
+ icon: ShieldCheck,
33
+ key: ProfileTabs.Security,
34
+ label: t('tab.security'),
35
+ onClick: () => router.push('/profile/security'),
38
36
  },
37
+ !isDeprecatedEdition && {
38
+ icon: ChartColumnBigIcon,
39
+ key: ProfileTabs.Stats,
40
+ label: t('tab.stats'),
41
+ onClick: () => router.push('/profile/stats'),
39
42
  },
40
- ];
43
+ enableAuth &&
44
+ isLogin && {
45
+ type: 'divider',
46
+ },
47
+ enableAuth &&
48
+ isLogin && {
49
+ icon: LogOut,
50
+ key: 'logout',
51
+ label: t('signout', { ns: 'auth' }),
52
+ onClick: () => {
53
+ signOut();
54
+ router.push('/login');
55
+ },
56
+ },
57
+ ].filter(Boolean) as CellProps[];
41
58
 
42
59
  return items?.map((item, index) => <Cell key={item.key || index} {...item} />);
43
60
  });
@@ -1,13 +1,10 @@
1
- import { notFound } from 'next/navigation';
2
1
  import { PropsWithChildren } from 'react';
3
2
 
4
3
  import MobileContentLayout from '@/components/server/MobileNavLayout';
5
- import { enableClerk } from '@/const/auth';
6
4
 
7
5
  import Header from './features/Header';
8
6
 
9
7
  const Layout = ({ children }: PropsWithChildren) => {
10
- if (!enableClerk) return notFound();
11
8
  return <MobileContentLayout header={<Header />}>{children}</MobileContentLayout>;
12
9
  };
13
10
 
@@ -7,10 +7,10 @@ import { isMobileDevice } from '@/utils/server/responsive';
7
7
  import Category from './features/Category';
8
8
 
9
9
  export const generateMetadata = async () => {
10
- const { t } = await translation('clerk');
10
+ const { t } = await translation('auth');
11
11
  return metadataModule.generate({
12
- description: t('userProfile.navbar.title'),
13
- title: t('userProfile.navbar.description'),
12
+ description: t('header.desc'),
13
+ title: t('header.title'),
14
14
  url: '/me/profile',
15
15
  });
16
16
  };
@@ -54,7 +54,7 @@ const ByTimeMode = memo(() => {
54
54
 
55
55
  const groupContent = useCallback(
56
56
  (index: number) => {
57
- if (index === 0) return undefined;
57
+ if (index === 0) return <div style={{ height: 1 }} />;
58
58
 
59
59
  const topicGroup = groups[index];
60
60
  return <TopicGroupItem {...topicGroup} />;
@@ -1,3 +1,3 @@
1
- import CircleLoading from '@/components/Loading/BrandTextLoading';
1
+ import Loading from '@/components/Loading/BrandTextLoading';
2
2
 
3
- export default () => <CircleLoading />;
3
+ export default () => <Loading />;
@@ -1,9 +1,3 @@
1
- import { Center } from 'react-layout-kit';
1
+ import Loading from '@/components/Loading/BrandTextLoading';
2
2
 
3
- import CircleLoading from '@/components/Loading/BrandTextLoading';
4
-
5
- export default () => (
6
- <Center height={'90vh'} width={'100%'}>
7
- <CircleLoading />
8
- </Center>
9
- );
3
+ export default () => <Loading />;
@@ -1,3 +1,3 @@
1
- import CircleLoading from '@/components/Loading/BrandTextLoading';
1
+ import Loading from '@/components/Loading/BrandTextLoading';
2
2
 
3
- export default () => <CircleLoading />;
3
+ export default () => <Loading />;
@@ -0,0 +1,53 @@
1
+ 'use client';
2
+
3
+ import { Form, type ItemGroup } from '@lobehub/ui';
4
+ import { memo } from 'react';
5
+ import { useTranslation } from 'react-i18next';
6
+
7
+ import { FORM_STYLE } from '@/const/layoutTokens';
8
+ import AvatarWithUpload from '@/features/AvatarWithUpload';
9
+ import UserAvatar from '@/features/User/UserAvatar';
10
+ import { useUserStore } from '@/store/user';
11
+ import { authSelectors, userProfileSelectors } from '@/store/user/selectors';
12
+
13
+ type SettingItemGroup = ItemGroup;
14
+
15
+ const Client = memo<{ mobile?: boolean }>(() => {
16
+ const [isLoginWithNextAuth] = useUserStore((s) => [authSelectors.isLoginWithNextAuth(s)]);
17
+ const [enableAuth, nickname, username, userProfile] = useUserStore((s) => [
18
+ s.enableAuth(),
19
+ userProfileSelectors.nickName(s),
20
+ userProfileSelectors.username(s),
21
+ userProfileSelectors.userProfile(s),
22
+ ]);
23
+
24
+ const [form] = Form.useForm();
25
+ const { t } = useTranslation('auth');
26
+
27
+ const profile: SettingItemGroup = {
28
+ children: [
29
+ {
30
+ children: enableAuth && isLoginWithNextAuth ? <UserAvatar /> : <AvatarWithUpload />,
31
+ label: t('profile.avatar'),
32
+ minWidth: undefined,
33
+ },
34
+ {
35
+ children: nickname || username,
36
+ label: t('profile.username'),
37
+ minWidth: undefined,
38
+ },
39
+ {
40
+ children: userProfile?.email || '--',
41
+ hidden: !isLoginWithNextAuth || !userProfile?.email,
42
+ label: t('profile.email'),
43
+ minWidth: undefined,
44
+ },
45
+ ],
46
+ title: t('tab.profile'),
47
+ };
48
+ return (
49
+ <Form form={form} items={[profile]} itemsType={'group'} variant={'pure'} {...FORM_STYLE} />
50
+ );
51
+ });
52
+
53
+ export default Client;
@@ -0,0 +1,38 @@
1
+ import { Skeleton } from 'antd';
2
+ import dynamic from 'next/dynamic';
3
+
4
+ import { enableClerk } from '@/const/auth';
5
+ import { metadataModule } from '@/server/metadata';
6
+ import { translation } from '@/server/translation';
7
+ import { isMobileDevice } from '@/utils/server/responsive';
8
+
9
+ import Client from '../Client';
10
+
11
+ // 为了兼容 ClerkProfile, 需要使用 [[...slug]]
12
+
13
+ const ClerkProfile = dynamic(() => import('../../features/ClerkProfile'), {
14
+ loading: () => (
15
+ <div style={{ flex: 1 }}>
16
+ <Skeleton paragraph={{ rows: 8 }} title={false} />
17
+ </div>
18
+ ),
19
+ });
20
+
21
+ export const generateMetadata = async () => {
22
+ const { t } = await translation('auth');
23
+ return metadataModule.generate({
24
+ description: t('header.desc'),
25
+ title: t('tab.profile'),
26
+ url: '/profile',
27
+ });
28
+ };
29
+
30
+ const Page = async () => {
31
+ const mobile = await isMobileDevice();
32
+
33
+ if (enableClerk) return <ClerkProfile mobile={mobile} />;
34
+
35
+ return <Client mobile={mobile} />;
36
+ };
37
+
38
+ export default Page;
@@ -0,0 +1,9 @@
1
+ import CategoryContent from './features/CategoryContent';
2
+
3
+ const Category = () => {
4
+ return <CategoryContent />;
5
+ };
6
+
7
+ Category.displayName = 'SettingCategory';
8
+
9
+ export default Category;
@@ -0,0 +1,38 @@
1
+ 'use client';
2
+
3
+ import { memo } from 'react';
4
+ import urlJoin from 'url-join';
5
+
6
+ import Menu from '@/components/Menu';
7
+ import { useActiveSettingsKey } from '@/hooks/useActiveTabKey';
8
+ import { useQuery } from '@/hooks/useQuery';
9
+ import { useQueryRoute } from '@/hooks/useQueryRoute';
10
+ import { ProfileTabs, SettingsTabs } from '@/store/global/initialState';
11
+
12
+ import { useCategory } from '../../hooks/useCategory';
13
+
14
+ const CategoryContent = memo<{ modal?: boolean }>(({ modal }) => {
15
+ const activeTab = useActiveSettingsKey();
16
+ const { tab = SettingsTabs.Common } = useQuery();
17
+ const cateItems = useCategory();
18
+ const router = useQueryRoute();
19
+
20
+ return (
21
+ <Menu
22
+ items={cateItems}
23
+ onClick={({ key }) => {
24
+ const activeKey = key === ProfileTabs.Profile ? '/' : key;
25
+ if (modal) {
26
+ router.replace('/profile/modal', { query: { tab: activeKey } });
27
+ } else {
28
+ router.push(urlJoin('/profile', activeKey));
29
+ }
30
+ }}
31
+ selectable
32
+ selectedKeys={[modal ? tab : (activeTab as any)]}
33
+ variant={'compact'}
34
+ />
35
+ );
36
+ });
37
+
38
+ export default CategoryContent;
@@ -0,0 +1,85 @@
1
+ 'use client';
2
+
3
+ import { ActionIcon, ChatHeader, ChatHeaderTitle } from '@lobehub/ui';
4
+ import { Drawer, type DrawerProps } from 'antd';
5
+ import { createStyles } from 'antd-style';
6
+ import { Menu } from 'lucide-react';
7
+ import { ReactNode, memo, useState } from 'react';
8
+ import { Flexbox } from 'react-layout-kit';
9
+
10
+ import BrandWatermark from '@/components/BrandWatermark';
11
+
12
+ const useStyles = createStyles(({ token, css }) => ({
13
+ container: css`
14
+ position: relative;
15
+ flex: none;
16
+ height: 54px;
17
+ background: ${token.colorBgContainer};
18
+ `,
19
+ title: css`
20
+ font-size: 18px;
21
+ font-weight: 700;
22
+ line-height: 1.2;
23
+ `,
24
+ }));
25
+
26
+ interface HeaderProps extends Pick<DrawerProps, 'getContainer'> {
27
+ children: ReactNode;
28
+ title: ReactNode;
29
+ }
30
+
31
+ const Header = memo<HeaderProps>(({ children, getContainer, title }) => {
32
+ const [open, setOpen] = useState(false);
33
+ const { styles, theme } = useStyles();
34
+
35
+ return (
36
+ <>
37
+ <ChatHeader
38
+ className={styles.container}
39
+ left={
40
+ <ChatHeaderTitle
41
+ title={
42
+ <Flexbox align={'center'} className={styles.title} gap={4} horizontal>
43
+ <ActionIcon
44
+ color={theme.colorText}
45
+ icon={Menu}
46
+ onClick={() => setOpen(true)}
47
+ size={{ blockSize: 32, fontSize: 18 }}
48
+ />
49
+ {title}
50
+ </Flexbox>
51
+ }
52
+ />
53
+ }
54
+ />
55
+ <Drawer
56
+ bodyStyle={{
57
+ display: 'flex',
58
+ flexDirection: 'column',
59
+ gap: 20,
60
+ justifyContent: 'space-between',
61
+ padding: 16,
62
+ }}
63
+ getContainer={getContainer}
64
+ headerStyle={{ display: 'none' }}
65
+ maskStyle={{ background: 'transparent' }}
66
+ onClick={() => setOpen(false)}
67
+ onClose={() => setOpen(false)}
68
+ open={open}
69
+ placement={'left'}
70
+ rootStyle={{ position: 'absolute' }}
71
+ style={{
72
+ background: theme.colorBgContainer,
73
+ borderRight: `1px solid ${theme.colorSplit}`,
74
+ }}
75
+ width={260}
76
+ zIndex={10}
77
+ >
78
+ {children}
79
+ <BrandWatermark paddingInline={12} />
80
+ </Drawer>
81
+ </>
82
+ );
83
+ });
84
+
85
+ export default Header;
@@ -0,0 +1,42 @@
1
+ 'use client';
2
+
3
+ import { createStyles } from 'antd-style';
4
+ import { useTranslation } from 'react-i18next';
5
+ import { Flexbox, FlexboxProps } from 'react-layout-kit';
6
+
7
+ import BrandWatermark from '@/components/BrandWatermark';
8
+ import PanelTitle from '@/components/PanelTitle';
9
+
10
+ const useStyles = createStyles(({ token, css }) => ({
11
+ container: css`
12
+ padding-block: 0 16px;
13
+ padding-inline: 12px;
14
+ background: ${token.colorBgContainer};
15
+ border-inline-end: 1px solid ${token.colorBorder};
16
+ `,
17
+ }));
18
+
19
+ interface SidebarLayoutProps extends FlexboxProps {
20
+ desc?: string;
21
+ title?: string;
22
+ }
23
+
24
+ const SidebarLayout = ({ children, className, title, desc, ...rest }: SidebarLayoutProps) => {
25
+ const { cx, styles } = useStyles();
26
+ const { t } = useTranslation('auth');
27
+ return (
28
+ <Flexbox
29
+ className={cx(styles.container, className)}
30
+ flex={'none'}
31
+ gap={20}
32
+ width={280}
33
+ {...rest}
34
+ >
35
+ <PanelTitle desc={desc || t('header.desc')} title={title || t('header.title')} />
36
+ {children}
37
+ <BrandWatermark paddingInline={12} />
38
+ </Flexbox>
39
+ );
40
+ };
41
+
42
+ export default SidebarLayout;
@@ -0,0 +1,48 @@
1
+ 'use client';
2
+
3
+ import { useResponsive } from 'antd-style';
4
+ import { memo, useRef } from 'react';
5
+ import { useTranslation } from 'react-i18next';
6
+ import { Flexbox } from 'react-layout-kit';
7
+
8
+ import InitClientDB from '@/features/InitClientDB';
9
+ import Footer from '@/features/Setting/Footer';
10
+ import SettingContainer from '@/features/Setting/SettingContainer';
11
+ import { useActiveProfileKey } from '@/hooks/useActiveTabKey';
12
+
13
+ import { LayoutProps } from '../type';
14
+ import Header from './Header';
15
+ import SideBar from './SideBar';
16
+
17
+ const Layout = memo<LayoutProps>(({ children, category }) => {
18
+ const ref = useRef<any>(null);
19
+ const { md = true } = useResponsive();
20
+ const { t } = useTranslation('auth');
21
+ const activeKey = useActiveProfileKey();
22
+
23
+ return (
24
+ <>
25
+ <Flexbox
26
+ height={'100%'}
27
+ horizontal={md}
28
+ ref={ref}
29
+ style={{ position: 'relative' }}
30
+ width={'100%'}
31
+ >
32
+ {md ? (
33
+ <SideBar>{category}</SideBar>
34
+ ) : (
35
+ <Header getContainer={() => ref.current} title={<>{t(`tab.${activeKey}`)}</>}>
36
+ {category}
37
+ </Header>
38
+ )}
39
+ <SettingContainer addonAfter={<Footer />}>{children}</SettingContainer>
40
+ </Flexbox>
41
+ <InitClientDB />
42
+ </>
43
+ );
44
+ });
45
+
46
+ Layout.displayName = 'DesktopProfileLayout';
47
+
48
+ export default Layout;
@@ -1,22 +1,40 @@
1
1
  'use client';
2
2
 
3
3
  import { MobileNavBar, MobileNavBarTitle } from '@lobehub/ui';
4
- import { usePathname, useRouter } from 'next/navigation';
4
+ import { useRouter } from 'next/navigation';
5
5
  import { memo } from 'react';
6
6
  import { useTranslation } from 'react-i18next';
7
+ import { Flexbox } from 'react-layout-kit';
7
8
 
9
+ import { useActiveProfileKey } from '@/hooks/useActiveTabKey';
8
10
  import { mobileHeaderSticky } from '@/styles/mobileHeader';
9
11
 
12
+ import ShareButton from '../../stats/features/ShareButton';
13
+
10
14
  const Header = memo(() => {
11
15
  const { t } = useTranslation('auth');
12
16
 
13
17
  const router = useRouter();
14
- const pathname = usePathname();
15
- const isSecurity = pathname.startsWith('/prifile/security');
18
+ const activeSettingsKey = useActiveProfileKey();
19
+ const isStats = activeSettingsKey === 'stats';
20
+
21
+ const handleBackClick = () => {
22
+ router.push('/me/profile');
23
+ };
24
+
16
25
  return (
17
26
  <MobileNavBar
18
- center={<MobileNavBarTitle title={t(isSecurity ? 'security' : 'profile')} />}
19
- onBackClick={() => router.push('/me/profile')}
27
+ center={
28
+ <MobileNavBarTitle
29
+ title={
30
+ <Flexbox align={'center'} gap={8} horizontal>
31
+ <span style={{ lineHeight: 1.2 }}> {t(`tab.${activeSettingsKey}`)}</span>
32
+ </Flexbox>
33
+ }
34
+ />
35
+ }
36
+ onBackClick={handleBackClick}
37
+ right={isStats ? <ShareButton mobile /> : undefined}
20
38
  showBackButton
21
39
  style={mobileHeaderSticky}
22
40
  />
@@ -1,16 +1,23 @@
1
- import { PropsWithChildren } from 'react';
1
+ import MobileContentLayout from '@/components/server/MobileNavLayout';
2
+ import InitClientDB from '@/features/InitClientDB';
3
+ import Footer from '@/features/Setting/Footer';
2
4
 
5
+ import { LayoutProps } from '../type';
3
6
  import Header from './Header';
4
7
 
5
- const Layout = ({ children }: PropsWithChildren) => {
8
+ const Layout = ({ children }: LayoutProps) => {
6
9
  return (
7
10
  <>
8
- <Header />
9
- {children}
11
+ <MobileContentLayout header={<Header />}>
12
+ {children}
13
+ <div style={{ flex: 1 }} />
14
+ <Footer />
15
+ </MobileContentLayout>
16
+ <InitClientDB />
10
17
  </>
11
18
  );
12
19
  };
13
20
 
14
- Layout.displayName = 'ProfileMobileLayout';
21
+ Layout.displayName = 'MobileProfileLayout';
15
22
 
16
23
  export default Layout;
@@ -0,0 +1,6 @@
1
+ import { ReactNode } from 'react';
2
+
3
+ export interface LayoutProps {
4
+ category: ReactNode;
5
+ children: ReactNode;
6
+ }
@@ -0,0 +1,5 @@
1
+ 'use client';
2
+
3
+ import dynamic from 'next/dynamic';
4
+
5
+ export default dynamic(() => import('@/components/Error'));
@@ -0,0 +1,72 @@
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, responsive, token }) =>
10
+ ({
11
+ cardBox: css`
12
+ width: 100%;
13
+ min-width: 100%;
14
+ background: transparent;
15
+ `,
16
+ footer: css`
17
+ display: none !important;
18
+ `,
19
+ headerTitle: css`
20
+ ${responsive.mobile} {
21
+ margin: 0;
22
+ padding: 16px;
23
+
24
+ font-size: 14px;
25
+ font-weight: 400;
26
+ line-height: 24px;
27
+
28
+ opacity: 0.5;
29
+ }
30
+ `,
31
+ navbar: css`
32
+ display: none !important;
33
+ `,
34
+ navbarMobileMenuRow: css`
35
+ display: none !important;
36
+ `,
37
+ pageScrollBox: css`
38
+ padding: 0;
39
+ `,
40
+ profileSection: css`
41
+ ${responsive.mobile} {
42
+ padding-inline: 16px;
43
+ background: ${token.colorBgContainer};
44
+ }
45
+ `,
46
+ rootBox: css`
47
+ width: 100%;
48
+ height: 100%;
49
+ `,
50
+ scrollBox: css`
51
+ background: transparent;
52
+ `,
53
+ }) as Partial<{
54
+ // eslint-disable-next-line unused-imports/no-unused-vars
55
+ [k in keyof ElementsConfig]: any;
56
+ }>,
57
+ );
58
+
59
+ const Client = memo<{ mobile?: boolean }>(({ mobile }) => {
60
+ const { styles } = useStyles(mobile);
61
+
62
+ return (
63
+ <UserProfile
64
+ appearance={{
65
+ elements: styles,
66
+ }}
67
+ path={'/profile'}
68
+ />
69
+ );
70
+ });
71
+
72
+ export default Client;