feat: new msg email notify

This commit is contained in:
Tristan Yang
2024-11-07 11:15:59 +08:00
parent ce058a678e
commit c2c23daf91
19 changed files with 1754 additions and 1570 deletions
+35 -35
View File
@@ -1,13 +1,13 @@
{
"name": "vocechat-web",
"version": "0.7.47",
"version": "0.7.49",
"homepage": "https://voce.chat",
"dependencies": {
"@metamask/onboarding": "^1.0.1",
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.15",
"@radix-ui/react-popover": "^1.1.2",
"@react-oauth/google": "^0.12.1",
"@reduxjs/toolkit": "^2.2.8",
"@reduxjs/toolkit": "^2.3.0",
"@svgr/webpack": "^8.1.0",
"@tippyjs/react": "^4.2.6",
"@toast-ui/editor": "^3.2.2",
@@ -24,21 +24,21 @@
"@udecode/plate-paragraph": "^33.0.0",
"@udecode/plate-trailing-block": "^33.0.0",
"@uiball/loaders": "^1.3.1",
"agora-rtc-sdk-ng": "^4.22.1",
"agora-rtc-sdk-ng": "^4.22.2",
"broadcast-channel": "^7.0.0",
"browserslist": "^4.24.0",
"browserslist": "^4.24.2",
"clsx": "^2.1.1",
"copy-image-clipboard": "^2.1.2",
"css-loader": "^7.1.2",
"css-minimizer-webpack-plugin": "^7.0.0",
"dayjs": "^1.11.13",
"eslint": "^8.57.0",
"firebase": "^10.14.1",
"framer-motion": "11.11.7",
"firebase": "^11.0.1",
"framer-motion": "11.11.11",
"fs-extra": "^11.2.0",
"heic2any": "^0.0.4",
"html-webpack-plugin": "^5.6.0",
"i18next": "^23.15.2",
"html-webpack-plugin": "^5.6.3",
"i18next": "^23.16.4",
"i18next-browser-languagedetector": "^8.0.0",
"i18next-http-backend": "^2.6.2",
"linkify-plugin-mention": "^4.1.3",
@@ -48,47 +48,47 @@
"localforage-setitems": "^1.4.0",
"lodash": "^4.17.21",
"masonry-layout": "^4.2.2",
"mini-css-extract-plugin": "^2.9.1",
"mini-css-extract-plugin": "^2.9.2",
"prismjs": "^1.29.0",
"qrcode.react": "^4.0.1",
"qrcode.react": "^4.1.0",
"react": "^18.3.1",
"react-dev-utils": "^12.0.1",
"react-dnd": "16.0.1",
"react-dnd-html5-backend": "16.0.1",
"react-dom": "^18.3.1",
"react-error-boundary": "^4.0.13",
"react-error-boundary": "^4.1.2",
"react-helmet": "^6.1.0",
"react-hot-toast": "^2.4.1",
"react-i18next": "^15.0.2",
"react-i18next": "^15.1.0",
"react-redux": "^9.1.2",
"react-refresh": "0.14.2",
"react-router-dom": "^6.26.2",
"react-syntax-highlighter": "^15.5.0",
"react-router-dom": "^6.28.0",
"react-syntax-highlighter": "^15.6.1",
"react-textarea-autosize": "^8.5.4",
"react-use-wizard": "^2.3.0",
"react-viewport-list": "^7.1.2",
"react-virtuoso": "^4.10.4",
"react-virtuoso": "^4.12.0",
"rooks": "^7.14.1",
"slate": "^0.103.0",
"slate-history": "^0.109.0",
"slate": "^0.110.2",
"slate-history": "^0.110.3",
"slate-hyperscript": "^0.100.0",
"slate-react": "0.110.1",
"slate-react": "0.111.0",
"source-map-loader": "^5.0.0",
"style-loader": "^4.0.0",
"tailwind-merge": "2.5.3",
"tailwind-merge": "2.5.4",
"terser-webpack-plugin": "^5.3.10",
"tippy.js": "^6.3.7",
"typescript": "^5.6.3",
"wavesurfer.js": "^7.8.6",
"webpack": "^5.95.0",
"wavesurfer.js": "^7.8.8",
"webpack": "^5.96.1",
"webpack-dev-server": "^4.15.1",
"webpack-manifest-plugin": "^5.0.0",
"workbox-core": "^7.1.0",
"workbox-expiration": "^7.1.0",
"workbox-precaching": "^7.1.0",
"workbox-routing": "^7.1.0",
"workbox-strategies": "^7.1.0",
"workbox-webpack-plugin": "^7.1.0"
"workbox-core": "^7.3.0",
"workbox-expiration": "^7.3.0",
"workbox-precaching": "^7.3.0",
"workbox-routing": "^7.3.0",
"workbox-strategies": "^7.3.0",
"workbox-webpack-plugin": "^7.3.0"
},
"scripts": {
"start": "REACT_APP_BUILD_TIME=$(date +%s) node scripts/start.js",
@@ -115,13 +115,13 @@
]
},
"devDependencies": {
"@babel/core": "^7.25.8",
"@ianvs/prettier-plugin-sort-imports": "^4.3.1",
"@types/lodash": "^4.17.10",
"@babel/core": "^7.26.0",
"@ianvs/prettier-plugin-sort-imports": "^4.4.0",
"@types/lodash": "^4.17.13",
"@types/masonry-layout": "^4.2.8",
"@types/node": "^22.7.5",
"@types/react": "18.3.11",
"@types/react-dom": "^18.3.0",
"@types/node": "^22.9.0",
"@types/react": "18.3.12",
"@types/react-dom": "^18.3.1",
"@types/react-helmet": "^6.1.11",
"@types/react-syntax-highlighter": "^15.5.13",
"@typescript-eslint/eslint-plugin": "^7.15.0",
@@ -133,7 +133,7 @@
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-import": "^2.31.0",
"eslint-plugin-prettier": "^5.2.1",
"eslint-plugin-react": "^7.37.1",
"eslint-plugin-react": "^7.37.2",
"eslint-plugin-react-hooks": "^4.6.2",
"gh-pages": "^6.2.0",
"md5-file": "^5.0.0",
@@ -142,7 +142,7 @@
"postcss-loader": "^8.1.1",
"postinstall-postinstall": "^2.1.0",
"prettier": "^3.3.3",
"tailwindcss": "^3.4.13",
"tailwindcss": "^3.4.14",
"webpack-bundle-analyzer": "^4.10.2"
}
}
+1483 -1417
View File
File diff suppressed because it is too large Load Diff
+11
View File
@@ -61,6 +61,17 @@
"everyone": "Everyone",
"invite": "Invitation Link Only"
},
"server_msg_notify": {
"title": "New Message Notification Through Email",
"desc": "users can control new message if send through email, SMTP enabled first.",
"enable": "On",
"disable": "Off",
"delay_5_min": "Every 5 mins",
"delay_15_min": "Every 15 mins",
"delay_1_hour": "Every 1 hour",
"delay_3_hour": "Every 3 hours",
"delay_12_hour": "Every 12 hours"
},
"admin_create_group": {
"title": "Only Admins Can Create Group",
"desc": "If enabled, only admins can create group.",
+11
View File
@@ -58,6 +58,17 @@
"everyone": "每个人",
"invite": "邀请链接"
},
"server_msg_notify": {
"title": "邮件通知新消息",
"desc": "用户可以自定义是否通过邮件通知新消息,前提:SMTP 已配置并开启",
"enable": "开启",
"disable": "关闭",
"delay_5_min": "每隔五分钟",
"delay_15_min": "每隔 15 分钟",
"delay_1_hour": "每隔一小时",
"delay_3_hour": "每隔三小时",
"delay_12_hour": "每隔 12 小时"
},
"admin_create_group": {
"title": "只允许管理员创建频道",
"desc": "如果开启,只有管理员才能创建频道",
+22
View File
@@ -185,6 +185,28 @@ export const getInviteLinkTimesList = () => [
value: 100
}
];
export const getEmailNotifyDelayList = () => [
{
label: i18n.t("setting:overview.server_msg_notify.delay_5_min"),
value: 5 * 60
},
{
label: i18n.t("setting:overview.server_msg_notify.delay_15_min"),
value: 15 * 60
},
{
label: i18n.t("setting:overview.server_msg_notify.delay_1_hour"),
value: 60 * 60
},
{
label: i18n.t("setting:overview.server_msg_notify.delay_3_hour"),
value: 3 * 60 * 60
},
{
label: i18n.t("setting:overview.server_msg_notify.delay_12_hour"),
value: 12 * 60 * 60
}
];
export const KEY_ADMIN_SEE_CHANNEL_MEMBERS = `only_admin_can_see_channel_members`;
export const KEY_MSG_URL_PREVIEW = `enable_msg_url_preview`;
export default BASE_URL;
+10 -4
View File
@@ -3,14 +3,20 @@ import React from "react";
type Props = {
title: string;
desc: string;
children: React.ReactNode;
toggler?: React.ReactNode;
children?: React.ReactNode;
};
const SettingBlock = ({ title, desc, children }: Props) => {
const SettingBlock = ({ toggler, title, desc, children }: Props) => {
return (
<div className="text-sm w-full">
<p className="text-gray-600 dark:text-gray-100 font-semibold">{title}</p>
<p className="flex justify-between w-full text-gray-400 mb-2 text-xs">{desc}</p>
<div className="flex justify-between mb-2">
<div className="">
<p className="text-gray-600 dark:text-gray-100 font-semibold">{title}</p>
<p className="flex justify-between w-full text-gray-400 text-xs">{desc}</p>
</div>
{toggler && <div>{toggler}</div>}
</div>
{children}
</div>
);
+5 -1
View File
@@ -1,6 +1,8 @@
import { cn } from "@/utils";
import { FC, useId, useState } from "react";
type Props = {
disabled?: boolean;
options: string[];
values: (string | number)[];
defaultValue?: string | number;
@@ -12,6 +14,7 @@ const VALUE_NOT_SET = "";
const VALUES_NOT_SET: string[] = [];
const Radio: FC<Props> = ({
disabled = false,
options,
values = VALUES_NOT_SET,
value = VALUE_NOT_SET,
@@ -25,8 +28,9 @@ const Radio: FC<Props> = ({
return (
<form className="w-full flex flex-col gap-2">
{options.map((item, index) => (
<div className="relative bg-transparent" key={index}>
<div className={cn("relative bg-transparent", disabled && "grayscale-[0.8]")} key={index}>
<input
disabled={disabled}
className="absolute top-0 left-0 w-full h-full opacity-0 cursor-pointer peer z-50"
type="radio"
checked={(values !== VALUES_NOT_SET ? values.indexOf(_value) : _value) === index}
@@ -4,13 +4,13 @@ import { toast } from "react-hot-toast";
import { useTranslation } from "react-i18next";
import SettingBlock from "@/components/SettingBlock";
import StyledRadio from "@/components/styled/Radio";
import {
useGetSystemCommonQuery,
useUpdateSystemCommonMutation
} from "../../../app/services/server";
import { useAppSelector } from "../../../app/store";
import { shallowEqual } from "react-redux";
import Toggle from "@/components/styled/Toggle";
// type Props = {}
@@ -34,14 +34,11 @@ const Index = () => {
};
// if (!loadSuccess) return null;
return (
<SettingBlock title={t("title")} desc={t("desc")}>
<StyledRadio
options={[t("enable"), t("disable")]}
values={["true", "false"]}
value={`${currStatus}`}
onChange={handleToggle}
/>
</SettingBlock>
<SettingBlock
title={t("title")}
desc={t("desc")}
toggler={<Toggle onClick={handleToggle} checked={currStatus} />}
></SettingBlock>
);
};
+27
View File
@@ -0,0 +1,27 @@
import SettingBlock from "@/components/SettingBlock";
import { useTranslation } from "react-i18next";
import Toggle from "@/components/styled/Toggle";
import useConfig from "@/hooks/useConfig";
import { LoginConfig } from "@/types/server";
type Props = {};
const GuestMode = ({}: Props) => {
const { values: loginConfig, updateConfig: updateLoginConfig } = useConfig("login");
const { t } = useTranslation("setting");
const handleGuestToggle = (v: boolean) => {
updateLoginConfig({ ...loginConfig, guest: v });
};
if (!loginConfig) return null;
const { guest = false } = loginConfig as LoginConfig;
return (
<SettingBlock
title={t("overview.guest_mode.title")}
desc={t("overview.guest_mode.desc")}
toggler={<Toggle onClick={handleGuestToggle.bind(null, !guest)} checked={guest} />}
></SettingBlock>
);
};
export default GuestMode;
+7 -9
View File
@@ -7,6 +7,7 @@ import StyledRadio from "@/components/styled/Radio";
import { useAppSelector } from "../../../app/store";
import { shallowEqual, useDispatch } from "react-redux";
import { updateMsgSoundSetting } from "@/app/slices/ui";
import Toggle from "@/components/styled/Toggle";
// type Props = {}
@@ -15,18 +16,15 @@ const Index = () => {
const playSound = useAppSelector((store) => !!store.ui.msgSound, shallowEqual);
const { t } = useTranslation("setting", { keyPrefix: "overview.message_sound" });
// const { t: ct } = useTranslation();
const handleToggle = () => {
const toggleEnable = () => {
dispatch(updateMsgSoundSetting(!playSound));
};
return (
<SettingBlock title={t("title")} desc={t("desc")}>
<StyledRadio
options={[t("enable"), t("disable")]}
values={["true", "false"]}
value={`${playSound}`}
onChange={handleToggle}
/>
</SettingBlock>
<SettingBlock
title={t("title")}
desc={t("desc")}
toggler={<Toggle onClick={toggleEnable} checked={playSound} />}
></SettingBlock>
);
};
@@ -0,0 +1,72 @@
import { useAppSelector } from "@/app/store";
import SettingBlock from "@/components/SettingBlock";
import { useTranslation } from "react-i18next";
import StyledRadio from "@/components/styled/Radio";
import { useGetSystemCommonQuery, useUpdateSystemCommonMutation } from "@/app/services/server";
import { useEffect } from "react";
import toast from "react-hot-toast";
import { shallowEqual } from "react-redux";
import ServerVersionChecker from "@/components/ServerVersionChecker";
import Toggle from "@/components/styled/Toggle";
import { getEmailNotifyDelayList } from "@/app/config";
import useConfig from "@/hooks/useConfig";
import { SMTPConfig } from "@/types/server";
type Props = {};
const items = getEmailNotifyDelayList();
const MsgNotify = ({}: Props) => {
const { values } = useConfig("smtp");
const { t } = useTranslation("setting");
const { t: ct } = useTranslation();
const { refetch } = useGetSystemCommonQuery();
const msgNotify = useAppSelector(
(store) => store.server.msg_smtp_notify_enable ?? false,
shallowEqual
);
const delaySeconds = useAppSelector(
(store) => store.server.msg_smtp_notify_delay_seconds ?? 0,
shallowEqual
);
const [updateSetting, { isSuccess }] = useUpdateSystemCommonMutation();
useEffect(() => {
if (isSuccess) {
refetch();
toast.success(ct("tip.update"));
}
}, [isSuccess]);
const toggleEnable = () => {
if (!msgNotify) {
// 检查下 SMTP 开关
if (!(values as SMTPConfig).enabled) {
toast.error("Enable SMTP first!");
return;
}
}
updateSetting({ msg_smtp_notify_enable: !msgNotify });
};
const handleDelayChange = (val: number) => {
updateSetting({ msg_smtp_notify_delay_seconds: val });
};
return (
<ServerVersionChecker empty version="0.4.0">
<SettingBlock
title={t("overview.server_msg_notify.title")}
desc={t("overview.server_msg_notify.desc")}
toggler={<Toggle onClick={toggleEnable} checked={msgNotify}></Toggle>}
>
<StyledRadio
disabled={!msgNotify}
options={items.map((itm) => itm.label)}
values={items.map((itm) => itm.value)}
value={delaySeconds}
onChange={(v) => {
handleDelayChange(+v);
}}
/>
</SettingBlock>
</ServerVersionChecker>
);
};
export default MsgNotify;
+6 -9
View File
@@ -4,13 +4,13 @@ import { toast } from "react-hot-toast";
import { useTranslation } from "react-i18next";
import SettingBlock from "@/components/SettingBlock";
import StyledRadio from "@/components/styled/Radio";
import {
useGetSystemCommonQuery,
useUpdateSystemCommonMutation
} from "../../../app/services/server";
import { useAppSelector } from "../../../app/store";
import { shallowEqual } from "react-redux";
import Toggle from "@/components/styled/Toggle";
// type Props = {}
@@ -34,14 +34,11 @@ const Index = () => {
};
// if (!loadSuccess) return null;
return (
<SettingBlock title={t("title")} desc={t("desc")}>
<StyledRadio
options={[t("enable"), t("disable")]}
values={["true", "false"]}
value={`${currStatus}`}
onChange={handleToggle}
/>
</SettingBlock>
<SettingBlock
title={t("title")}
desc={t("desc")}
toggler={<Toggle onClick={handleToggle} checked={currStatus} />}
></SettingBlock>
);
};
@@ -1,12 +1,12 @@
import { useAppSelector } from "@/app/store";
import SettingBlock from "@/components/SettingBlock";
import { useTranslation } from "react-i18next";
import StyledRadio from "@/components/styled/Radio";
import { useGetSystemCommonQuery, useUpdateSystemCommonMutation } from "@/app/services/server";
import { useEffect } from "react";
import toast from "react-hot-toast";
import { shallowEqual } from "react-redux";
import ServerVersionChecker from "@/components/ServerVersionChecker";
import Toggle from "@/components/styled/Toggle";
type Props = {};
@@ -25,27 +25,16 @@ const OnlyAdminCreateGroup = ({}: Props) => {
toast.success(ct("tip.update"));
}
}, [isSuccess]);
const handleChange = (newVal: boolean) => {
updateSetting({ only_admin_can_create_group: newVal });
const toggleEnable = () => {
updateSetting({ only_admin_can_create_group: !onlyAdminCreateGroup });
};
return (
<ServerVersionChecker empty version="0.3.50">
<SettingBlock
title={t("overview.admin_create_group.title")}
desc={t("overview.admin_create_group.desc")}
>
<StyledRadio
options={[
t("overview.admin_create_group.enable"),
t("overview.admin_create_group.disable")
]}
values={["true", "false"]}
value={`${onlyAdminCreateGroup}`}
onChange={(v) => {
handleChange(v == "true");
}}
/>
</SettingBlock>
toggler={<Toggle onClick={toggleEnable} checked={onlyAdminCreateGroup}></Toggle>}
></SettingBlock>
</ServerVersionChecker>
);
};
@@ -1,37 +1,26 @@
import SettingBlock from "@/components/SettingBlock";
import { useTranslation } from "react-i18next";
import StyledRadio from "@/components/styled/Radio";
import ServerVersionChecker from "@/components/ServerVersionChecker";
import { KEY_ADMIN_SEE_CHANNEL_MEMBERS } from "@/app/config";
import useServerExtSetting from "@/hooks/useServerExtSetting";
import Toggle from "@/components/styled/Toggle";
type Props = {};
const OnlyAdminCanSeeChannelMembers = ({}: Props) => {
const { updateExtSetting, getExtSetting } = useServerExtSetting();
const { t } = useTranslation("setting");
const handleChange = (newVal: boolean) => {
updateExtSetting(KEY_ADMIN_SEE_CHANNEL_MEMBERS, newVal);
};
const onlyAdminSeeChannelMembers = getExtSetting(KEY_ADMIN_SEE_CHANNEL_MEMBERS);
const handleToggle = () => {
updateExtSetting(KEY_ADMIN_SEE_CHANNEL_MEMBERS, !onlyAdminSeeChannelMembers);
};
return (
<ServerVersionChecker empty version="0.3.50">
<SettingBlock
title={t("overview.admin_see_group_members.title")}
desc={t("overview.admin_see_group_members.desc")}
>
<StyledRadio
options={[
t("overview.admin_see_group_members.enable"),
t("overview.admin_see_group_members.disable")
]}
values={["true", "false"]}
value={`${onlyAdminSeeChannelMembers}`}
onChange={(v) => {
handleChange(v == "true");
}}
/>
</SettingBlock>
toggler={<Toggle onClick={handleToggle} checked={onlyAdminSeeChannelMembers} />}
></SettingBlock>
</ServerVersionChecker>
);
};
+6 -17
View File
@@ -1,37 +1,26 @@
import SettingBlock from "@/components/SettingBlock";
import { useTranslation } from "react-i18next";
import StyledRadio from "@/components/styled/Radio";
import ServerVersionChecker from "@/components/ServerVersionChecker";
import { KEY_MSG_URL_PREVIEW } from "@/app/config";
import useServerExtSetting from "@/hooks/useServerExtSetting";
import Toggle from "@/components/styled/Toggle";
type Props = {};
const EnableMsgURLPreview = ({}: Props) => {
const { updateExtSetting, getExtSetting } = useServerExtSetting();
const { t } = useTranslation("setting");
const handleChange = (newVal: boolean) => {
updateExtSetting(KEY_MSG_URL_PREVIEW, newVal);
};
const enable = getExtSetting(KEY_MSG_URL_PREVIEW);
const handleToggle = () => {
updateExtSetting(KEY_MSG_URL_PREVIEW, !enable);
};
return (
<ServerVersionChecker empty version="0.3.50">
<SettingBlock
title={t("overview.enable_msg_url_preview.title")}
desc={t("overview.enable_msg_url_preview.desc")}
>
<StyledRadio
options={[
t("overview.enable_msg_url_preview.enable"),
t("overview.enable_msg_url_preview.disable")
]}
values={["true", "false"]}
value={`${enable}`}
onChange={(v) => {
handleChange(v == "true");
}}
/>
</SettingBlock>
toggler={<Toggle onClick={handleToggle} checked={enable} />}
></SettingBlock>
</ServerVersionChecker>
);
};
@@ -0,0 +1,31 @@
import SettingBlock from "@/components/SettingBlock";
import useConfig from "@/hooks/useConfig";
import { LoginConfig, WhoCanSignUp } from "@/types/server";
import { useTranslation } from "react-i18next";
import StyledRadio from "../../../components/styled/Radio";
type Props = {};
const WhoCanSignUpSetting = (props: Props) => {
const { t } = useTranslation("setting");
const { values: loginConfig, updateConfig: updateLoginConfig } = useConfig("login");
const handleUpdateWhoCanSignUp = (value: WhoCanSignUp) => {
updateLoginConfig({ ...loginConfig, who_can_sign_up: value });
};
if (!loginConfig) return null;
const { who_can_sign_up: whoCanSignUp } = loginConfig as LoginConfig;
return (
<SettingBlock title={t("overview.sign_up.title")} desc={t("overview.sign_up.desc")}>
<StyledRadio
options={[t("overview.sign_up.everyone"), t("overview.sign_up.invite")]}
values={["EveryOne", "InvitationOnly"]}
value={whoCanSignUp}
onChange={(v: WhoCanSignUp) => {
handleUpdateWhoCanSignUp(v);
}}
/>
</SettingBlock>
);
};
export default WhoCanSignUpSetting;
+8 -36
View File
@@ -2,10 +2,6 @@
import { useTranslation } from "react-i18next";
import { useAppSelector } from "@/app/store";
import { LoginConfig, WhoCanSignUp } from "@/types/server";
import SettingBlock from "@/components/SettingBlock";
import StyledRadio from "@/components/styled/Radio";
import useConfig from "@/hooks/useConfig";
import ChatLayout from "./ChatLayout";
import ContactVerification from "./ContactVerification";
import DarkMode from "./DarkMode";
@@ -18,27 +14,21 @@ import { shallowEqual } from "react-redux";
import OnlyAdminCreateGroup from "./OnlyAdminCreateGroup";
import OnlyAdminCanSeeChannelMembers from "./OnlyAdminSeeChannelMembers";
import EnableURLPreviewInMsg from "./URLPreview";
import MsgNotify from "./MsgEmailNotify";
import GuestMode from "./GuestMode";
import WhoCanSignUpSetting from "./WhoCanSignUpSetting";
export default function Overview() {
const { t } = useTranslation("setting");
const isAdmin = useAppSelector((store) => store.authData.user?.is_admin, shallowEqual);
const { values: loginConfig, updateConfig: updateLoginConfig } = useConfig("login");
const handleUpdateWhoCanSignUp = (value: WhoCanSignUp) => {
updateLoginConfig({ ...loginConfig, who_can_sign_up: value });
};
const handleGuestToggle = (v: "true" | "false") => {
const guest = v === "true";
updateLoginConfig({ ...loginConfig, guest });
};
if (!loginConfig) return null;
const { who_can_sign_up: whoCanSignUp, guest = false } = loginConfig as LoginConfig;
return (
<div className="relative w-full md:w-[512px] flex flex-col gap-6">
<Server />
{isAdmin && (
<>
{/* 全局性的邮件消息通知 */}
<MsgNotify />
{/* 设置前端 url */}
<FrontendURL />
<div className="flex flex-col">
@@ -46,31 +36,13 @@ export default function Overview() {
</div>
{/* 注册开放与否 */}
<SettingBlock title={t("overview.sign_up.title")} desc={t("overview.sign_up.desc")}>
<StyledRadio
options={[t("overview.sign_up.everyone"), t("overview.sign_up.invite")]}
values={["EveryOne", "InvitationOnly"]}
value={whoCanSignUp}
onChange={(v: WhoCanSignUp) => {
handleUpdateWhoCanSignUp(v);
}}
/>
</SettingBlock>
<WhoCanSignUpSetting />
{/* 只有 admin 能创建群组 */}
<EnableURLPreviewInMsg />
<OnlyAdminCanSeeChannelMembers />
{/* 访客模式 */}
<SettingBlock title={t("overview.guest_mode.title")} desc={t("overview.guest_mode.desc")}>
<StyledRadio
options={[t("overview.guest_mode.enable"), t("overview.guest_mode.disable")]}
values={["true", "false"]}
value={`${guest}`}
onChange={(v) => {
handleGuestToggle(v);
}}
/>
</SettingBlock>
<GuestMode />
{/* 是否显示在线提示 */}
<OnlineStatus />
@@ -85,7 +57,7 @@ export default function Overview() {
<DarkMode />
{/* 新消息声音 */}
<MessageSound />
<OnlyAdminCreateGroup />
{isAdmin && <OnlyAdminCreateGroup />}
</div>
);
}
+2
View File
@@ -16,6 +16,8 @@ export interface SystemCommon {
max_file_expiry_mode?: MessageExpireMode;
only_admin_can_create_group: boolean;
ext_setting: null | string;
msg_smtp_notify_enable: boolean;
msg_smtp_notify_delay_seconds: number;
}
export interface GithubAuthConfig {
client_id?: string;
+1
View File
@@ -19,6 +19,7 @@ export interface User {
is_bot?: boolean;
log_id?: number;
widget_id?: string;
msg_smtp_notify_enable?: boolean;
}
export type ContactAction = "add" | "remove" | "block" | "unblock";
export type ContactStatus = "added" | "blocked" | "";