@lobehub/chat 1.42.6 → 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.
- package/CHANGELOG.md +33 -0
- package/changelog/v1.json +12 -0
- package/docs/.cdn.cache.json +1 -0
- package/docs/changelog/2025-01-03-user-profile.mdx +27 -0
- package/docs/changelog/2025-01-03-user-profile.zh-CN.mdx +26 -0
- package/docs/self-hosting/advanced/auth/next-auth/wechat.mdx +3 -1
- package/docs/self-hosting/advanced/auth/next-auth/wechat.zh-CN.mdx +2 -2
- package/locales/ar/auth.json +76 -4
- package/locales/bg-BG/auth.json +75 -3
- package/locales/de-DE/auth.json +78 -6
- package/locales/en-US/auth.json +78 -6
- package/locales/es-ES/auth.json +75 -3
- package/locales/fa-IR/auth.json +77 -5
- package/locales/fr-FR/auth.json +78 -6
- package/locales/it-IT/auth.json +76 -4
- package/locales/ja-JP/auth.json +76 -4
- package/locales/ko-KR/auth.json +75 -3
- package/locales/nl-NL/auth.json +76 -4
- package/locales/pl-PL/auth.json +76 -4
- package/locales/pt-BR/auth.json +76 -4
- package/locales/ru-RU/auth.json +75 -3
- package/locales/tr-TR/auth.json +74 -3
- package/locales/vi-VN/auth.json +75 -3
- package/locales/zh-CN/auth.json +75 -3
- package/locales/zh-TW/auth.json +75 -3
- package/package.json +4 -3
- package/src/app/(main)/(mobile)/me/(home)/__tests__/UserBanner.test.tsx +4 -0
- package/src/app/(main)/(mobile)/me/(home)/__tests__/useCategory.test.tsx +0 -46
- package/src/app/(main)/(mobile)/me/(home)/features/UserBanner.tsx +11 -14
- package/src/app/(main)/(mobile)/me/(home)/features/useCategory.tsx +6 -21
- package/src/app/(main)/(mobile)/me/profile/features/Category.tsx +38 -21
- package/src/app/(main)/(mobile)/me/profile/layout.tsx +0 -3
- package/src/app/(main)/(mobile)/me/profile/page.tsx +3 -3
- package/src/app/(main)/chat/loading.tsx +2 -2
- package/src/app/(main)/discover/loading.tsx +2 -8
- package/src/app/(main)/files/loading.tsx +2 -2
- package/src/app/(main)/profile/(home)/Client.tsx +53 -0
- package/src/app/(main)/profile/(home)/[[...slugs]]/page.tsx +38 -0
- package/src/app/(main)/profile/@category/default.tsx +9 -0
- package/src/app/(main)/profile/@category/features/CategoryContent.tsx +38 -0
- package/src/app/(main)/profile/_layout/Desktop/Header.tsx +85 -0
- package/src/app/(main)/profile/_layout/Desktop/SideBar.tsx +42 -0
- package/src/app/(main)/profile/_layout/Desktop/index.tsx +48 -0
- package/src/app/(main)/profile/_layout/Mobile/Header.tsx +23 -5
- package/src/app/(main)/profile/_layout/Mobile/index.tsx +12 -5
- package/src/app/(main)/profile/_layout/type.ts +6 -0
- package/src/app/(main)/profile/error.tsx +5 -0
- package/src/app/(main)/profile/features/ClerkProfile.tsx +72 -0
- package/src/app/(main)/profile/hooks/useCategory.tsx +51 -0
- package/src/app/(main)/profile/layout.tsx +7 -17
- package/src/app/(main)/profile/loading.tsx +2 -22
- package/src/app/(main)/profile/not-found.tsx +3 -0
- package/src/app/(main)/profile/security/page.tsx +34 -0
- package/src/app/(main)/profile/stats/Client.tsx +52 -0
- package/src/app/(main)/profile/stats/features/AiHeatmaps.tsx +130 -0
- package/src/app/(main)/profile/stats/features/AssistantsRank.tsx +115 -0
- package/src/app/(main)/profile/stats/features/ModelsRank.tsx +84 -0
- package/src/app/(main)/profile/stats/features/ShareButton/Preview.tsx +159 -0
- package/src/app/(main)/profile/stats/features/ShareButton/ShareModal.tsx +87 -0
- package/src/app/(main)/profile/stats/features/ShareButton/TotalCard.tsx +39 -0
- package/src/app/(main)/profile/stats/features/ShareButton/index.tsx +26 -0
- package/src/app/(main)/profile/stats/features/TimeLabel.tsx +30 -0
- package/src/app/(main)/profile/stats/features/TopicsRank.tsx +103 -0
- package/src/app/(main)/profile/stats/features/TotalAssistants.tsx +56 -0
- package/src/app/(main)/profile/stats/features/TotalMessages.tsx +56 -0
- package/src/app/(main)/profile/stats/features/TotalTopics.tsx +53 -0
- package/src/app/(main)/profile/stats/features/TotalWords.tsx +54 -0
- package/src/app/(main)/profile/stats/features/Welcome.tsx +86 -0
- package/src/app/(main)/profile/{[[...slugs]] → stats}/page.tsx +4 -5
- package/src/app/(main)/repos/[id]/evals/dataset/page.tsx +2 -2
- package/src/app/(main)/repos/[id]/evals/evaluation/page.tsx +2 -2
- package/src/app/(main)/settings/@category/features/CategoryContent.tsx +1 -1
- package/src/app/(main)/settings/_layout/Desktop/index.tsx +1 -1
- package/src/app/(main)/settings/_layout/Mobile/Header.tsx +1 -1
- package/src/app/(main)/settings/_layout/Mobile/index.tsx +2 -0
- package/src/app/(main)/settings/common/features/Theme/index.tsx +2 -17
- package/src/app/(main)/settings/loading.tsx +2 -2
- package/src/components/Loading/BrandTextLoading/index.tsx +2 -2
- package/src/components/Statistic/index.tsx +15 -0
- package/src/components/StatisticCard/TitleWithPercentage.tsx +80 -0
- package/src/components/StatisticCard/growthPercentage.tsx +8 -0
- package/src/components/StatisticCard/index.tsx +209 -0
- package/src/const/url.ts +3 -3
- package/src/database/server/models/__tests__/message.test.ts +346 -35
- package/src/database/server/models/__tests__/session.test.ts +185 -2
- package/src/database/server/models/__tests__/topic.test.ts +136 -0
- package/src/database/server/models/__tests__/user.test.ts +140 -1
- package/src/database/server/models/message.ts +109 -14
- package/src/database/server/models/session.ts +75 -4
- package/src/database/server/models/topic.ts +43 -3
- package/src/database/server/models/user.ts +22 -0
- package/src/database/utils/genWhere.ts +39 -0
- package/src/features/ShareModal/ShareImage/index.tsx +11 -24
- package/src/features/ShareModal/ShareImage/type.ts +1 -6
- package/src/features/User/DataStatistics.tsx +21 -14
- package/src/features/User/UserPanel/PanelContent.tsx +12 -16
- package/src/features/User/UserPanel/useMenu.tsx +4 -6
- package/src/features/User/__tests__/PanelContent.test.tsx +4 -0
- package/src/features/User/__tests__/useMenu.test.tsx +1 -21
- package/src/hooks/useActiveTabKey.ts +34 -1
- package/src/{features/ShareModal/ShareImage → hooks}/useScreenshot.ts +51 -6
- package/src/locales/default/auth.ts +74 -2
- package/src/server/ld.test.ts +1 -1
- package/src/server/modules/AssistantStore/index.ts +3 -2
- package/src/server/routers/lambda/message.ts +35 -6
- package/src/server/routers/lambda/session.ts +17 -3
- package/src/server/routers/lambda/topic.ts +17 -3
- package/src/server/routers/lambda/user.ts +4 -0
- package/src/server/services/changelog/index.ts +1 -1
- package/src/services/message/_deprecated.ts +16 -0
- package/src/services/message/client.test.ts +0 -18
- package/src/services/message/client.ts +12 -9
- package/src/services/message/server.ts +12 -4
- package/src/services/message/type.ts +15 -3
- package/src/services/session/_deprecated.ts +5 -0
- package/src/services/session/client.ts +6 -2
- package/src/services/session/server.ts +6 -2
- package/src/services/session/type.ts +7 -1
- package/src/services/topic/_deprecated.ts +5 -0
- package/src/services/topic/client.ts +6 -2
- package/src/services/topic/server.ts +7 -1
- package/src/services/topic/type.ts +7 -2
- package/src/services/user/_deprecated.ts +4 -0
- package/src/services/user/client.ts +4 -0
- package/src/services/user/server.ts +4 -0
- package/src/services/user/type.ts +5 -0
- package/src/store/global/initialState.ts +6 -0
- package/src/store/user/slices/auth/action.test.ts +1 -33
- package/src/store/user/slices/auth/action.ts +0 -9
- package/src/store/user/slices/common/action.test.ts +2 -2
- package/src/types/message/index.ts +5 -0
- package/src/types/session/index.ts +8 -0
- package/src/types/topic/topic.ts +7 -0
- package/src/utils/format.ts +1 -1
- package/src/utils/time.ts +23 -0
- package/src/app/(main)/profile/[[...slugs]]/Client.tsx +0 -76
- package/src/components/Loading/BrandTextLoading/LobeChatText/SVG.tsx +0 -44
- package/src/components/Loading/BrandTextLoading/LobeChatText/index.tsx +0 -6
- package/src/components/Loading/BrandTextLoading/LobeChatText/style.css +0 -32
- package/src/hooks/useActiveSettingsKey.ts +0 -20
@@ -1,3 +1,3 @@
|
|
1
|
-
import
|
1
|
+
import Loading from '@/components/Loading/BrandTextLoading';
|
2
2
|
|
3
|
-
export default () => <
|
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,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 {
|
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
|
15
|
-
const
|
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={
|
19
|
-
|
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
|
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 }:
|
8
|
+
const Layout = ({ children }: LayoutProps) => {
|
6
9
|
return (
|
7
10
|
<>
|
8
|
-
<Header />
|
9
|
-
|
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 = '
|
21
|
+
Layout.displayName = 'MobileProfileLayout';
|
15
22
|
|
16
23
|
export default Layout;
|
@@ -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;
|
@@ -0,0 +1,51 @@
|
|
1
|
+
import { Icon } from '@lobehub/ui';
|
2
|
+
import { ChartColumnBigIcon, ShieldCheck, UserCircle } from 'lucide-react';
|
3
|
+
import Link from 'next/link';
|
4
|
+
import { useTranslation } from 'react-i18next';
|
5
|
+
|
6
|
+
import type { MenuProps } from '@/components/Menu';
|
7
|
+
import { isDeprecatedEdition } from '@/const/version';
|
8
|
+
import { ProfileTabs } from '@/store/global/initialState';
|
9
|
+
import { useUserStore } from '@/store/user';
|
10
|
+
import { authSelectors } from '@/store/user/slices/auth/selectors';
|
11
|
+
|
12
|
+
export const useCategory = () => {
|
13
|
+
const { t } = useTranslation('auth');
|
14
|
+
const [enableAuth, isLoginWithClerk] = useUserStore((s) => [
|
15
|
+
authSelectors.enabledAuth(s),
|
16
|
+
authSelectors.isLoginWithClerk(s),
|
17
|
+
]);
|
18
|
+
|
19
|
+
const cateItems: MenuProps['items'] = [
|
20
|
+
{
|
21
|
+
icon: <Icon icon={UserCircle} />,
|
22
|
+
key: ProfileTabs.Profile,
|
23
|
+
label: (
|
24
|
+
<Link href={'/profile'} onClick={(e) => e.preventDefault()}>
|
25
|
+
{t('tab.profile')}
|
26
|
+
</Link>
|
27
|
+
),
|
28
|
+
},
|
29
|
+
enableAuth &&
|
30
|
+
isLoginWithClerk && {
|
31
|
+
icon: <Icon icon={ShieldCheck} />,
|
32
|
+
key: ProfileTabs.Security,
|
33
|
+
label: (
|
34
|
+
<Link href={'/profile/security'} onClick={(e) => e.preventDefault()}>
|
35
|
+
{t('tab.security')}
|
36
|
+
</Link>
|
37
|
+
),
|
38
|
+
},
|
39
|
+
!isDeprecatedEdition && {
|
40
|
+
icon: <Icon icon={ChartColumnBigIcon} />,
|
41
|
+
key: ProfileTabs.Stats,
|
42
|
+
label: (
|
43
|
+
<Link href={'/profile/stats'} onClick={(e) => e.preventDefault()}>
|
44
|
+
{t('tab.stats')}
|
45
|
+
</Link>
|
46
|
+
),
|
47
|
+
},
|
48
|
+
].filter(Boolean) as MenuProps['items'];
|
49
|
+
|
50
|
+
return cateItems;
|
51
|
+
};
|
@@ -1,21 +1,11 @@
|
|
1
|
-
import
|
2
|
-
import { PropsWithChildren } from 'react';
|
1
|
+
import ServerLayout from '@/components/server/ServerLayout';
|
3
2
|
|
4
|
-
import
|
5
|
-
import
|
3
|
+
import Desktop from './_layout/Desktop';
|
4
|
+
import Mobile from './_layout/Mobile';
|
5
|
+
import { LayoutProps } from './_layout/type';
|
6
6
|
|
7
|
-
|
7
|
+
const ProfileLayout = ServerLayout<LayoutProps>({ Desktop, Mobile });
|
8
8
|
|
9
|
-
|
10
|
-
if (!enableClerk) return notFound();
|
9
|
+
ProfileLayout.displayName = 'ProfileLayout';
|
11
10
|
|
12
|
-
|
13
|
-
|
14
|
-
if (mobile) return <MobileLayout>{children}</MobileLayout>;
|
15
|
-
|
16
|
-
return children;
|
17
|
-
};
|
18
|
-
|
19
|
-
Layout.displayName = 'ProfileLayout';
|
20
|
-
|
21
|
-
export default Layout;
|
11
|
+
export default ProfileLayout;
|
@@ -1,23 +1,3 @@
|
|
1
|
-
import
|
1
|
+
import Loading from '@/components/Loading/BrandTextLoading';
|
2
2
|
|
3
|
-
|
4
|
-
import { isMobileDevice } from '@/utils/server/responsive';
|
5
|
-
|
6
|
-
const Loading = async () => {
|
7
|
-
const mobile = await 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;
|
3
|
+
export default () => <Loading />;
|
@@ -0,0 +1,34 @@
|
|
1
|
+
import { Skeleton } from 'antd';
|
2
|
+
import dynamic from 'next/dynamic';
|
3
|
+
import { notFound } from 'next/navigation';
|
4
|
+
|
5
|
+
import { enableClerk } from '@/const/auth';
|
6
|
+
import { metadataModule } from '@/server/metadata';
|
7
|
+
import { translation } from '@/server/translation';
|
8
|
+
import { isMobileDevice } from '@/utils/server/responsive';
|
9
|
+
|
10
|
+
const ClerkProfile = dynamic(() => import('../features/ClerkProfile'), {
|
11
|
+
loading: () => (
|
12
|
+
<div style={{ flex: 1 }}>
|
13
|
+
<Skeleton paragraph={{ rows: 8 }} title={false} />
|
14
|
+
</div>
|
15
|
+
),
|
16
|
+
});
|
17
|
+
|
18
|
+
export const generateMetadata = async () => {
|
19
|
+
const { t } = await translation('auth');
|
20
|
+
return metadataModule.generate({
|
21
|
+
description: t('header.desc'),
|
22
|
+
title: t('tab.security'),
|
23
|
+
url: '/profile/security',
|
24
|
+
});
|
25
|
+
};
|
26
|
+
|
27
|
+
const Page = async () => {
|
28
|
+
if (!enableClerk) return notFound();
|
29
|
+
const mobile = await isMobileDevice();
|
30
|
+
|
31
|
+
return <ClerkProfile mobile={mobile} />;
|
32
|
+
};
|
33
|
+
|
34
|
+
export default Page;
|