@lobehub/chat 1.42.6 → 1.43.1
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 +58 -0
- package/changelog/v1.json +21 -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 +83 -4
- package/locales/bg-BG/auth.json +82 -3
- package/locales/de-DE/auth.json +85 -6
- package/locales/en-US/auth.json +85 -6
- package/locales/es-ES/auth.json +82 -3
- package/locales/fa-IR/auth.json +84 -5
- package/locales/fr-FR/auth.json +85 -6
- package/locales/it-IT/auth.json +83 -4
- package/locales/ja-JP/auth.json +83 -4
- package/locales/ko-KR/auth.json +82 -3
- package/locales/nl-NL/auth.json +83 -4
- package/locales/pl-PL/auth.json +83 -4
- package/locales/pt-BR/auth.json +83 -4
- package/locales/ru-RU/auth.json +82 -3
- package/locales/tr-TR/auth.json +82 -3
- package/locales/vi-VN/auth.json +82 -3
- package/locales/zh-CN/auth.json +82 -3
- package/locales/zh-TW/auth.json +82 -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 +104 -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 +76 -4
- package/src/database/server/models/topic.ts +44 -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 +81 -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,9 +1,11 @@
|
|
1
1
|
'use client';
|
2
2
|
|
3
|
+
import Link from 'next/link';
|
3
4
|
import { useRouter } from 'next/navigation';
|
4
5
|
import { memo } from 'react';
|
5
6
|
import { Flexbox } from 'react-layout-kit';
|
6
7
|
|
8
|
+
import { isDeprecatedEdition } from '@/const/version';
|
7
9
|
import DataStatistics from '@/features/User/DataStatistics';
|
8
10
|
import UserInfo from '@/features/User/UserInfo';
|
9
11
|
import UserLoginOrSignup from '@/features/User/UserLoginOrSignup/Community';
|
@@ -21,21 +23,16 @@ const UserBanner = memo(() => {
|
|
21
23
|
|
22
24
|
return (
|
23
25
|
<Flexbox gap={12} paddingBlock={8}>
|
24
|
-
{!enableAuth ? (
|
26
|
+
{!enableAuth || (enableAuth && isLoginWithAuth) ? (
|
25
27
|
<>
|
26
|
-
<
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
if (enabledNextAuth) return;
|
35
|
-
router.push('/me/profile');
|
36
|
-
}}
|
37
|
-
/>
|
38
|
-
<DataStatistics paddingInline={12} />
|
28
|
+
<Link href={'/profile'} style={{ color: 'inherit' }}>
|
29
|
+
<UserInfo />
|
30
|
+
</Link>
|
31
|
+
{!isDeprecatedEdition && (
|
32
|
+
<Link href={'/profile/stats'} style={{ color: 'inherit' }}>
|
33
|
+
<DataStatistics paddingInline={12} />
|
34
|
+
</Link>
|
35
|
+
)}
|
39
36
|
</>
|
40
37
|
) : (
|
41
38
|
<UserLoginOrSignup
|
@@ -6,7 +6,6 @@ import {
|
|
6
6
|
Download,
|
7
7
|
Feather,
|
8
8
|
FileClockIcon,
|
9
|
-
LogOut,
|
10
9
|
Settings2,
|
11
10
|
} from 'lucide-react';
|
12
11
|
import { useRouter } from 'next/navigation';
|
@@ -28,15 +27,11 @@ export const useCategory = () => {
|
|
28
27
|
const { canInstall, install } = usePWAInstall();
|
29
28
|
const { t } = useTranslation(['common', 'setting', 'auth']);
|
30
29
|
const { showCloudPromotion, hideDocs } = useServerConfigStore(featureFlagsSelectors);
|
31
|
-
const [isLogin, isLoginWithAuth,
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
authSelectors.enabledAuth(s),
|
37
|
-
s.logout,
|
38
|
-
authSelectors.isLoginWithNextAuth(s),
|
39
|
-
]);
|
30
|
+
const [isLogin, isLoginWithAuth, enableAuth] = useUserStore((s) => [
|
31
|
+
authSelectors.isLogin(s),
|
32
|
+
authSelectors.isLoginWithAuth(s),
|
33
|
+
authSelectors.enabledAuth(s),
|
34
|
+
]);
|
40
35
|
|
41
36
|
const profile: CellProps[] = [
|
42
37
|
{
|
@@ -121,20 +116,11 @@ export const useCategory = () => {
|
|
121
116
|
},
|
122
117
|
].filter(Boolean) as CellProps[];
|
123
118
|
|
124
|
-
const nextAuthSignOut: CellProps[] = [
|
125
|
-
{
|
126
|
-
icon: LogOut,
|
127
|
-
key: 'nextauthSignout',
|
128
|
-
label: t('auth:signout'),
|
129
|
-
onClick: signOut,
|
130
|
-
},
|
131
|
-
];
|
132
|
-
|
133
119
|
const mainItems = [
|
134
120
|
{
|
135
121
|
type: 'divider',
|
136
122
|
},
|
137
|
-
...(
|
123
|
+
...(!enableAuth || (enableAuth && isLoginWithAuth) ? profile : []),
|
138
124
|
...(enableAuth ? (isLoginWithAuth ? settings : []) : settingsWithoutAuth),
|
139
125
|
/* ↓ cloud slot ↓ */
|
140
126
|
|
@@ -142,7 +128,6 @@ export const useCategory = () => {
|
|
142
128
|
...(canInstall ? pwa : []),
|
143
129
|
...(isLogin && !isServerMode ? data : []),
|
144
130
|
...(!hideDocs ? helps : []),
|
145
|
-
...(enableAuth && isLoginWithNextAuth ? nextAuthSignOut : []),
|
146
131
|
].filter(Boolean) as CellProps[];
|
147
132
|
|
148
133
|
return mainItems;
|
@@ -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:
|
19
|
-
label: t('profile'),
|
26
|
+
key: ProfileTabs.Profile,
|
27
|
+
label: t('tab.profile'),
|
20
28
|
onClick: () => router.push('/profile'),
|
21
29
|
},
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
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('
|
10
|
+
const { t } = await translation('auth');
|
11
11
|
return metadataModule.generate({
|
12
|
-
description: t('
|
13
|
-
title: t('
|
12
|
+
description: t('header.desc'),
|
13
|
+
title: t('header.title'),
|
14
14
|
url: '/me/profile',
|
15
15
|
});
|
16
16
|
};
|
@@ -1,3 +1,3 @@
|
|
1
|
-
import
|
1
|
+
import Loading from '@/components/Loading/BrandTextLoading';
|
2
2
|
|
3
|
-
export default () => <
|
3
|
+
export default () => <Loading />;
|
@@ -1,9 +1,3 @@
|
|
1
|
-
import
|
1
|
+
import Loading from '@/components/Loading/BrandTextLoading';
|
2
2
|
|
3
|
-
|
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
|
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;
|