mirror of
https://github.com/lobehub/lobehub.git
synced 2026-03-27 13:29:15 +07:00
♻️ refactor: move notification settings page to business override slot
This commit is contained in:
@@ -1,6 +1,22 @@
|
||||
import type { NotificationSettings } from '@lobechat/types';
|
||||
|
||||
export const DEFAULT_NOTIFICATION_SETTINGS: NotificationSettings = {
|
||||
email: { enabled: true },
|
||||
inbox: { enabled: true },
|
||||
email: {
|
||||
enabled: true,
|
||||
items: {
|
||||
generation: {
|
||||
image_generation_completed: true,
|
||||
video_generation_completed: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
inbox: {
|
||||
enabled: true,
|
||||
items: {
|
||||
generation: {
|
||||
image_generation_completed: true,
|
||||
video_generation_completed: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
const Notification = () => null;
|
||||
|
||||
export default Notification;
|
||||
@@ -1,5 +1,6 @@
|
||||
import Billing from '@/business/client/BusinessSettingPages/Billing';
|
||||
import Credits from '@/business/client/BusinessSettingPages/Credits';
|
||||
import Notification from '@/business/client/BusinessSettingPages/Notification';
|
||||
import Plans from '@/business/client/BusinessSettingPages/Plans';
|
||||
import Referral from '@/business/client/BusinessSettingPages/Referral';
|
||||
import Usage from '@/business/client/BusinessSettingPages/Usage';
|
||||
@@ -12,7 +13,6 @@ import Appearance from '../appearance';
|
||||
import Creds from '../creds';
|
||||
import Hotkey from '../hotkey';
|
||||
import Memory from '../memory';
|
||||
import Notification from '../notification';
|
||||
import Profile from '../profile';
|
||||
import Provider from '../provider';
|
||||
import Proxy from '../proxy';
|
||||
|
||||
@@ -22,9 +22,12 @@ export const componentMap = {
|
||||
[SettingsTabs.Memory]: dynamic(() => import('../memory'), {
|
||||
loading: loading('Settings > Memory'),
|
||||
}),
|
||||
[SettingsTabs.Notification]: dynamic(() => import('../notification'), {
|
||||
loading: loading('Settings > Notification'),
|
||||
}),
|
||||
[SettingsTabs.Notification]: dynamic(
|
||||
() => import('@/business/client/BusinessSettingPages/Notification'),
|
||||
{
|
||||
loading: loading('Settings > Notification'),
|
||||
},
|
||||
),
|
||||
[SettingsTabs.About]: dynamic(() => import('../about'), {
|
||||
loading: loading('Settings > About'),
|
||||
}),
|
||||
|
||||
@@ -1,114 +0,0 @@
|
||||
'use client';
|
||||
|
||||
import { getDefaultNotificationSettings, SCENARIO_REGISTRY } from '@cloud/const';
|
||||
import { type FormGroupItemType } from '@lobehub/ui';
|
||||
import { Form, Icon, Skeleton } from '@lobehub/ui';
|
||||
import { Switch } from 'antd';
|
||||
import isEqual from 'fast-deep-equal';
|
||||
import { Loader2Icon } from 'lucide-react';
|
||||
import { memo, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { FORM_STYLE } from '@/const/layoutTokens';
|
||||
import { useUserStore } from '@/store/user';
|
||||
import { settingsSelectors } from '@/store/user/selectors';
|
||||
|
||||
const defaults = getDefaultNotificationSettings();
|
||||
|
||||
/** Collect notification types grouped by category for a given channel */
|
||||
const getCategoryTypesForChannel = (channel: string): { category: string; types: string[] }[] => {
|
||||
const result: { category: string; types: string[] }[] = [];
|
||||
for (const [category, categoryTypes] of Object.entries(SCENARIO_REGISTRY)) {
|
||||
const types: string[] = [];
|
||||
for (const [type, config] of Object.entries(categoryTypes)) {
|
||||
if (config.channels.includes(channel)) types.push(type);
|
||||
}
|
||||
if (types.length > 0) result.push({ category, types });
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
const NotificationForm = memo(() => {
|
||||
const { t } = useTranslation('setting');
|
||||
const { t: tNotification } = useTranslation('notification');
|
||||
const [form] = Form.useForm();
|
||||
const { notification } = useUserStore(settingsSelectors.currentSettings, isEqual);
|
||||
const [setSettings, isUserStateInit] = useUserStore((s) => [s.setSettings, s.isUserStateInit]);
|
||||
const [loadingChannel, setLoadingChannel] = useState<string | null>(null);
|
||||
|
||||
const initialValues = useMemo(() => {
|
||||
const result: Record<
|
||||
string,
|
||||
{ enabled: boolean; items: Record<string, Record<string, boolean>> }
|
||||
> = {};
|
||||
for (const [ch, def] of Object.entries(defaults)) {
|
||||
const userChannel = notification?.[ch as keyof typeof notification];
|
||||
const items: Record<string, Record<string, boolean>> = {};
|
||||
for (const [category, typeDefaults] of Object.entries(def.items)) {
|
||||
items[category] = { ...typeDefaults, ...userChannel?.items?.[category] };
|
||||
}
|
||||
result[ch] = {
|
||||
enabled: userChannel?.enabled ?? def.enabled,
|
||||
items,
|
||||
};
|
||||
}
|
||||
return result;
|
||||
}, [notification]);
|
||||
|
||||
if (!isUserStateInit) return <Skeleton active paragraph={{ rows: 3 }} title={false} />;
|
||||
|
||||
const buildChannelGroup = (channel: string): FormGroupItemType => {
|
||||
const categoryTypes = getCategoryTypesForChannel(channel);
|
||||
const isInbox = channel === 'inbox';
|
||||
|
||||
return {
|
||||
children: [
|
||||
{
|
||||
children: <Switch />,
|
||||
desc: isInbox ? t('notification.inbox.desc') : t('notification.email.desc'),
|
||||
label: t('notification.enabled'),
|
||||
layout: 'horizontal',
|
||||
minWidth: undefined,
|
||||
name: [channel, 'enabled'],
|
||||
valuePropName: 'checked',
|
||||
},
|
||||
...categoryTypes.flatMap(({ category, types }) =>
|
||||
types.map((type) => ({
|
||||
children: <Switch />,
|
||||
label: tNotification(`${type}_title`),
|
||||
layout: 'horizontal' as const,
|
||||
minWidth: undefined,
|
||||
name: [channel, 'items', category, type],
|
||||
valuePropName: 'checked',
|
||||
})),
|
||||
),
|
||||
],
|
||||
extra: loadingChannel === channel && (
|
||||
<Icon spin icon={Loader2Icon} size={16} style={{ opacity: 0.5 }} />
|
||||
),
|
||||
title: isInbox ? t('notification.inbox.title') : t('notification.email.title'),
|
||||
};
|
||||
};
|
||||
|
||||
const channelGroups = Object.keys(defaults).map(buildChannelGroup);
|
||||
|
||||
return (
|
||||
<Form
|
||||
collapsible={false}
|
||||
form={form}
|
||||
initialValues={initialValues}
|
||||
items={channelGroups}
|
||||
itemsType={'group'}
|
||||
variant={'filled'}
|
||||
onValuesChange={async (changedValues) => {
|
||||
const channel = Object.keys(changedValues)[0];
|
||||
setLoadingChannel(channel);
|
||||
await setSettings({ notification: changedValues });
|
||||
setLoadingChannel(null);
|
||||
}}
|
||||
{...FORM_STYLE}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
export default NotificationForm;
|
||||
@@ -1,17 +0,0 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import SettingHeader from '@/routes/(main)/settings/features/SettingHeader';
|
||||
|
||||
import NotificationForm from './features/NotificationForm';
|
||||
|
||||
const Page = () => {
|
||||
const { t } = useTranslation('setting');
|
||||
return (
|
||||
<>
|
||||
<SettingHeader title={t('tab.notification')} />
|
||||
<NotificationForm />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Page;
|
||||
Reference in New Issue
Block a user