feat: auto delete msg
This commit is contained in:
@@ -7,9 +7,11 @@
|
||||
"general": "General",
|
||||
"overview": "Overview",
|
||||
"members": "Members",
|
||||
"auto_delete_msg": "Auto-delete Messages",
|
||||
"user": "User",
|
||||
"my_account": "My Account",
|
||||
"config": "Configuration",
|
||||
"bot": "Bot & Webhook",
|
||||
"firebase": "Firebase",
|
||||
"agora": "Agora",
|
||||
"smtp": "SMTP",
|
||||
@@ -36,6 +38,10 @@
|
||||
"public": "Public",
|
||||
"private": "Private"
|
||||
},
|
||||
"dm": {
|
||||
"delete": "Delete User",
|
||||
"delete_desc": "Are you sure want to delete this user?"
|
||||
},
|
||||
"overview": {
|
||||
"name": "Server Name",
|
||||
"desc": "Server Description",
|
||||
@@ -78,6 +84,35 @@
|
||||
"wechat": "Or, add WeChat for more information:"
|
||||
}
|
||||
},
|
||||
"bot": {
|
||||
"bot_tip_title": "Bots can send messages to channels.",
|
||||
"bot_tip_desc": "Please note that we do not display a Bot’s API key again after generation. You can always generate a new API key of a Bot by clicking “Get API key” and this won’t nullify the previous API key(s).",
|
||||
"webhook_tip_title": "Webhooks can receive messages in a channel and should a URL provided by you.",
|
||||
"webhook_tip_desc": "Please provide a webhook URL that will return 200.",
|
||||
"manage": "Manage",
|
||||
"manage_desc": "Disabling your account means you can recover it at any time after taking this action.",
|
||||
"create_title": "Create Bot",
|
||||
"create_desc": "Create a bot to extend your chat",
|
||||
"delete_title": "Delete Bot",
|
||||
"delete_desc": "Are you sure to delete the Bot?",
|
||||
"webhook_title": "Update Webhook URL",
|
||||
"webhook_desc": "Please input a valid URL",
|
||||
"col_avatar": "Avatar",
|
||||
"col_name": "Bot Name",
|
||||
"col_api_key": "Bot API Key",
|
||||
"col_webhook": "Webhook URL",
|
||||
"col_opt": "Operations"
|
||||
},
|
||||
"auto_delete_msg": {
|
||||
"title": "Delete messages sent by myself after certain time period. ",
|
||||
"desc": "This will remove the messages data stored on both the server and client side.",
|
||||
"off": "Off",
|
||||
"30_seconds": "30 seconds",
|
||||
"10_min": "10 minutes",
|
||||
"1_hour": "1 hour",
|
||||
"1_day": "1 day",
|
||||
"1_week": "1 week"
|
||||
},
|
||||
"firebase": {
|
||||
"enable": "Enable",
|
||||
"token_url": "Token Url",
|
||||
|
||||
@@ -7,8 +7,10 @@
|
||||
"general": "通用",
|
||||
"overview": "概况",
|
||||
"members": "成员",
|
||||
"auto_delete_msg": "自动删除消息",
|
||||
"my_account": "我的账号",
|
||||
"config": "配置",
|
||||
"bot": "机器人&Webhook",
|
||||
"firebase": "Firebase",
|
||||
"agora": "Agora",
|
||||
"smtp": "SMTP",
|
||||
@@ -80,6 +82,33 @@
|
||||
"wechat": "或者,加微信了解更多:"
|
||||
}
|
||||
},
|
||||
"bot": {
|
||||
"bot_tip_title": "使用机器人向频道里发送消息",
|
||||
"bot_tip_desc": "Please note that we do not display a Bot’s API key again after generation. You can always generate a new API key of a Bot by clicking “Get API key” and this won’t nullify the previous API key(s).",
|
||||
"webhook_tip_title": "Webhooks can receive messages in a channel and should a URL provided by you.",
|
||||
"webhook_tip_desc": "请提供一个有效的Webhook URL:GET 请求响应 200即为有效。",
|
||||
"manage": "管理",
|
||||
"manage_desc": "Disabling your account means you can recover it at any time after taking this action.",
|
||||
"create_title": "创建机器人",
|
||||
"create_desc": "Create a bot to extend your chat",
|
||||
"webhook_title": "更新Webhook",
|
||||
"webhook_desc": "请输入有效的URL",
|
||||
"col_avatar": "头像",
|
||||
"col_name": "名称",
|
||||
"col_api_key": "API Key",
|
||||
"col_webhook": "Webhook 地址",
|
||||
"col_opt": "操作"
|
||||
},
|
||||
"auto_delete_msg": {
|
||||
"title": "删除我发送的消息",
|
||||
"desc": "消息将在客户端和服务端彻底删除",
|
||||
"off": "关闭",
|
||||
"30_seconds": "30 秒",
|
||||
"10_min": "10 分钟",
|
||||
"1_hour": "1 小时",
|
||||
"1_day": "1 天",
|
||||
"1_week": "1 周"
|
||||
},
|
||||
"firebase": {
|
||||
"enable": "开启",
|
||||
"token_url": "Token 地址",
|
||||
|
||||
@@ -6,7 +6,7 @@ import { fillUsers } from "../slices/users";
|
||||
import BASE_URL, { ContentTypes } from "../config";
|
||||
import { onMessageSendStarted } from "./handlers";
|
||||
import handleChatMessage from "../../common/hook/useStreaming/chat.handler";
|
||||
import { User, UserDTO, UserForAdmin, UserForAdminDTO } from "../../types/user";
|
||||
import { AutoDeleteMsgDTO, User, UserCreateDTO, UserDTO, UserForAdmin, UserForAdminDTO } from "../../types/user";
|
||||
import { ChatMessage, ContentTypeKey, MuteDTO } from "../../types/message";
|
||||
import { RootState } from "../store";
|
||||
|
||||
@@ -39,6 +39,13 @@ export const userApi = createApi({
|
||||
deleteUser: builder.query<void, number>({
|
||||
query: (uid) => ({ url: `/admin/user/${uid}`, method: "DELETE" })
|
||||
}),
|
||||
createUser: builder.mutation<UserForAdmin, UserCreateDTO>({
|
||||
query: (data) => ({
|
||||
url: `/admin/user`,
|
||||
body: data,
|
||||
method: "POST"
|
||||
})
|
||||
}),
|
||||
updateUser: builder.mutation<UserForAdmin, UserForAdminDTO>({
|
||||
query: ({ id, ...rest }) => ({
|
||||
url: `/admin/user/${id}`,
|
||||
@@ -46,6 +53,15 @@ export const userApi = createApi({
|
||||
method: "PUT"
|
||||
})
|
||||
}),
|
||||
|
||||
updateAutoDeleteMsg: builder.mutation<void, AutoDeleteMsgDTO>({
|
||||
query: (data) => ({
|
||||
url: `/user/burn-after-reading`,
|
||||
body: data,
|
||||
method: "POST"
|
||||
})
|
||||
}),
|
||||
|
||||
updateMuteSetting: builder.mutation<void, MuteDTO>({
|
||||
query: (data) => ({
|
||||
url: `/user/mute`,
|
||||
@@ -71,6 +87,19 @@ export const userApi = createApi({
|
||||
body: data
|
||||
})
|
||||
}),
|
||||
updateAvatarByAdmin: builder.mutation<void, { uid: number, file: File }>({
|
||||
query: ({ uid, file }) => ({
|
||||
headers: {
|
||||
"content-type": "image/png"
|
||||
},
|
||||
url: `admin/user/${uid}/avatar`,
|
||||
method: "POST",
|
||||
body: file
|
||||
})
|
||||
}),
|
||||
getUserByAdmin: builder.query<UserForAdmin, number>({
|
||||
query: (uid) => ({ url: `/admin/user/${uid}` })
|
||||
}),
|
||||
updateInfo: builder.mutation<User, UserDTO>({
|
||||
query: (data) => ({
|
||||
url: `user`,
|
||||
@@ -78,7 +107,6 @@ export const userApi = createApi({
|
||||
body: data
|
||||
})
|
||||
}),
|
||||
|
||||
sendMsg: builder.mutation<
|
||||
number,
|
||||
{ id: number; content: string; type: ContentTypeKey; properties?: object }
|
||||
@@ -117,6 +145,10 @@ export const userApi = createApi({
|
||||
});
|
||||
|
||||
export const {
|
||||
useGetUserByAdminQuery,
|
||||
useUpdateAvatarByAdminMutation,
|
||||
useUpdateAutoDeleteMsgMutation,
|
||||
useCreateUserMutation,
|
||||
useLazyGetHistoryMessagesQuery,
|
||||
useUpdateUserMutation,
|
||||
useUpdateMuteSettingMutation,
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { toast } from 'react-hot-toast';
|
||||
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useUpdateAutoDeleteMsgMutation } from "../../app/services/user";
|
||||
import SaveTip from "./SaveTip";
|
||||
import StyledRadio from "./styled/Radio";
|
||||
|
||||
|
||||
type Props = {
|
||||
id: number,
|
||||
type?: "user" | "channel",
|
||||
expires_in?: number
|
||||
}
|
||||
const AutoDeleteMessages = ({ id, type = "channel", expires_in = 0 }: Props) => {
|
||||
const [updateSetting, { isSuccess }] = useUpdateAutoDeleteMsgMutation();
|
||||
const [value, setValue] = useState(expires_in);
|
||||
const { t } = useTranslation("setting", { keyPrefix: "auto_delete_msg" });
|
||||
const options = [
|
||||
{ title: t("off"), value: 0 },
|
||||
{ title: t("30_seconds"), value: 30 },
|
||||
{ title: t("10_min"), value: 10 * 60 },
|
||||
{ title: t("1_hour"), value: 60 * 60 },
|
||||
{ title: t("1_day"), value: 24 * 60 * 60 },
|
||||
{ title: t("1_week"), value: 7 * 24 * 60 * 60 },
|
||||
];
|
||||
const handleSave = () => {
|
||||
const dto = type == "user" ? { users: [{ uid: id, expires_in: value }] } : { groups: [{ gid: id, expires_in: value }] };
|
||||
updateSetting(dto);
|
||||
};
|
||||
const handleReset = () => {
|
||||
setValue(expires_in);
|
||||
};
|
||||
const handleChange = (val: number) => {
|
||||
setValue(val);
|
||||
};
|
||||
useEffect(() => {
|
||||
if (isSuccess) {
|
||||
toast.success("Update Successfully!");
|
||||
}
|
||||
}, [isSuccess]);
|
||||
|
||||
return (
|
||||
<section className="max-w-[512px] h-full relative">
|
||||
<div className="text-sm">
|
||||
<h2 >{t("title")}</h2>
|
||||
<p className="text-gray-500">{t("desc")}</p>
|
||||
</div>
|
||||
<div className="mt-4">
|
||||
<StyledRadio
|
||||
options={options.map(({ title }) => title)}
|
||||
values={options.map(({ value }) => value)}
|
||||
value={value}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
</div>
|
||||
{expires_in !== value && <SaveTip saveHandler={handleSave} resetHandler={handleReset} />}
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export default AutoDeleteMessages;
|
||||
@@ -1,7 +1,7 @@
|
||||
import styled from "styled-components";
|
||||
|
||||
const StyledWrapper = styled.div`
|
||||
z-index: 9999;
|
||||
z-index: 998;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
|
||||
@@ -69,6 +69,9 @@ const SessionContextMenu: FC<Props> = ({
|
||||
const data = channelMuted ? { remove_groups: [id] } : { add_groups: [{ gid: id }] };
|
||||
muteChannel(data);
|
||||
};
|
||||
const handleDMSetting = () => {
|
||||
navigateTo(`/setting/dm/${id}?f=${pathname}`);
|
||||
};
|
||||
|
||||
const items =
|
||||
context == "user"
|
||||
@@ -77,6 +80,10 @@ const SessionContextMenu: FC<Props> = ({
|
||||
title: t("action.mark_read"),
|
||||
handler: handleReadAll
|
||||
},
|
||||
{
|
||||
title: t("setting"),
|
||||
handler: handleDMSetting
|
||||
},
|
||||
canCopyEmail && {
|
||||
title: t("action.copy_email"),
|
||||
handler: copyEmail
|
||||
|
||||
@@ -19,6 +19,7 @@ const FavoritesPage = lazy(() => import("./favs"));
|
||||
const OnboardingPage = lazy(() => import("./onboarding"));
|
||||
const InvitePage = lazy(() => import("./invite"));
|
||||
const SettingChannelPage = lazy(() => import("./settingChannel"));
|
||||
const SettingDMPage = lazy(() => import("./settingDM"));
|
||||
const SettingPage = lazy(() => import("./setting"));
|
||||
const ResourceManagement = lazy(() => import("./resources"));
|
||||
const GuestLogin = lazy(() => import("./guest"));
|
||||
@@ -125,6 +126,7 @@ const PageRoutes = () => {
|
||||
}
|
||||
/>
|
||||
<Route path="channel/:cid" element={<LazyIt><SettingChannelPage /></LazyIt>} />
|
||||
<Route path="dm/:uid" element={<LazyIt><SettingDMPage /></LazyIt>} />
|
||||
</Route>
|
||||
<Route
|
||||
index
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { toast } from 'react-hot-toast';
|
||||
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useUpdateAutoDeleteMsgMutation } from "../../app/services/user";
|
||||
import SaveTip from "../../common/component/SaveTip";
|
||||
import StyledRadio from "../../common/component/styled/Radio";
|
||||
|
||||
|
||||
type Props = {
|
||||
id: number,
|
||||
type?: "user" | "channel",
|
||||
expires_in?: number
|
||||
}
|
||||
const AutoDeleteMessages = ({ id, type = "channel", expires_in = 0 }: Props) => {
|
||||
const [updateSetting, { isSuccess }] = useUpdateAutoDeleteMsgMutation();
|
||||
const [value, setValue] = useState(expires_in);
|
||||
const { t } = useTranslation("setting", { keyPrefix: "auto_delete_msg" });
|
||||
const options = [
|
||||
{ title: t("off"), value: 0 },
|
||||
{ title: t("30_seconds"), value: 30 },
|
||||
{ title: t("10_min"), value: 10 * 60 },
|
||||
{ title: t("1_hour"), value: 60 * 60 },
|
||||
{ title: t("1_day"), value: 24 * 60 * 60 },
|
||||
{ title: t("1_week"), value: 7 * 24 * 60 * 60 },
|
||||
];
|
||||
const handleSave = () => {
|
||||
const dto = type == "user" ? { users: [{ uid: id, expires_in: value }] } : { groups: [{ gid: id, expires_in: value }] };
|
||||
updateSetting(dto);
|
||||
};
|
||||
const handleReset = () => {
|
||||
setValue(expires_in);
|
||||
};
|
||||
const handleChange = (val: number) => {
|
||||
setValue(val);
|
||||
};
|
||||
useEffect(() => {
|
||||
if (isSuccess) {
|
||||
toast.success("Update Successfully!");
|
||||
}
|
||||
}, [isSuccess]);
|
||||
|
||||
return (
|
||||
<section className="max-w-[512px] h-full relative">
|
||||
<div className="text-sm">
|
||||
<h2 >{t("title")}</h2>
|
||||
<p className="text-gray-500">{t("desc")}</p>
|
||||
</div>
|
||||
<div className="mt-4">
|
||||
<StyledRadio
|
||||
options={options.map(({ title }) => title)}
|
||||
values={options.map(({ value }) => value)}
|
||||
value={value}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
</div>
|
||||
{expires_in !== value && <SaveTip saveHandler={handleSave} resetHandler={handleReset} />}
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export default AutoDeleteMessages;
|
||||
@@ -1,7 +1,8 @@
|
||||
import Overview from "./Overview";
|
||||
import ManageMembers from "../../common/component/ManageMembers";
|
||||
import { ReactNode } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import Overview from "./Overview";
|
||||
import AutoDeleteMessages from './AutoDeleteMessages';
|
||||
import ManageMembers from "../../common/component/ManageMembers";
|
||||
|
||||
export interface NavItem {
|
||||
name: string;
|
||||
@@ -26,6 +27,11 @@ const useNavs = (cid: number): Nav[] => {
|
||||
title: t("nav.overview"),
|
||||
component: <Overview id={cid} />
|
||||
},
|
||||
{
|
||||
name: "auto_delete_msg",
|
||||
title: t("nav.auto_delete_msg"),
|
||||
component: <AutoDeleteMessages id={cid} />
|
||||
},
|
||||
{
|
||||
name: "members",
|
||||
title: t("nav.members"),
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
import { useEffect, FC } from "react";
|
||||
import toast from "react-hot-toast";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import Modal from "../../common/component/Modal";
|
||||
import { useLazyDeleteUserQuery } from "../../app/services/user";
|
||||
import StyledModal from "../../common/component/styled/Modal";
|
||||
import Button from "../../common/component/styled/Button";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
interface Props {
|
||||
id: number;
|
||||
closeModal: (cid?: number) => void;
|
||||
}
|
||||
|
||||
const DeleteConfirmModal: FC<Props> = ({ id, closeModal }) => {
|
||||
const { t } = useTranslation("setting");
|
||||
const { t: ct } = useTranslation();
|
||||
const navigateTo = useNavigate();
|
||||
const [deleteUser, { isLoading, isSuccess }] = useLazyDeleteUserQuery();
|
||||
const handleDelete = () => {
|
||||
deleteUser(id);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (isSuccess) {
|
||||
toast.success("delete user successfully!");
|
||||
closeModal();
|
||||
navigateTo("/chat");
|
||||
}
|
||||
}, [isSuccess]);
|
||||
|
||||
if (!id) return null;
|
||||
return (
|
||||
<Modal id="modal-modal">
|
||||
<StyledModal
|
||||
className="compact"
|
||||
title={t("dm.delete")}
|
||||
description={t("dm.delete_desc")}
|
||||
buttons={
|
||||
<>
|
||||
<Button onClick={closeModal.bind(null, undefined)} className="cancel">
|
||||
{ct("action.cancel")}
|
||||
</Button>
|
||||
<Button onClick={handleDelete} className="danger">
|
||||
{isLoading ? "Deleting" : ct("action.remove")}
|
||||
</Button>
|
||||
</>
|
||||
}
|
||||
></StyledModal>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default DeleteConfirmModal;
|
||||
@@ -0,0 +1,10 @@
|
||||
// import { useState, useEffect, ChangeEvent } from "react";
|
||||
import Profile from "../../common/component/Profile";
|
||||
export default function Overview({ id = 0 }) {
|
||||
|
||||
return (
|
||||
<section className="w-full h-full flex justify-center items-start">
|
||||
<Profile uid={id} />
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
import { useState } from "react";
|
||||
import { useParams, useNavigate, useSearchParams } from "react-router-dom";
|
||||
import StyledSettingContainer from "../../common/component/StyledSettingContainer";
|
||||
import DeleteConfirmModal from "./DeleteConfirmModal";
|
||||
import useNavs from "./navs";
|
||||
import { useAppSelector } from "../../app/store";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
let from: string = "";
|
||||
|
||||
export default function DMSetting() {
|
||||
const { t } = useTranslation("setting");
|
||||
const { uid = 0 } = useParams();
|
||||
const { loginUser, user } = useAppSelector((store) => {
|
||||
return {
|
||||
loginUser: store.authData.user,
|
||||
user: uid ? store.users.byId[+uid] : undefined
|
||||
};
|
||||
});
|
||||
const navigate = useNavigate();
|
||||
const [searchParams] = useSearchParams();
|
||||
const navs = useNavs(+uid);
|
||||
const flattenNavs = navs
|
||||
.map(({ items }) => {
|
||||
return items;
|
||||
})
|
||||
.flat();
|
||||
const navKey = searchParams.get("nav");
|
||||
from = from ? from : searchParams.get("f") || "/";
|
||||
const [deleteConfirm, setDeleteConfirm] = useState(false);
|
||||
const close = () => {
|
||||
navigate(from);
|
||||
from = "";
|
||||
};
|
||||
const toggleDeleteConfirm = () => {
|
||||
setDeleteConfirm((prev) => !prev);
|
||||
};
|
||||
if (!uid) return null;
|
||||
const currNav = flattenNavs.find((n) => n.name == navKey) || flattenNavs[0];
|
||||
const canDelete = loginUser?.is_admin;
|
||||
|
||||
return (
|
||||
<>
|
||||
<StyledSettingContainer
|
||||
nav={currNav}
|
||||
closeModal={close}
|
||||
title="DM Setting"
|
||||
navs={navs}
|
||||
dangers={[
|
||||
canDelete && {
|
||||
title: t("action.remove"),
|
||||
handler: toggleDeleteConfirm
|
||||
}
|
||||
]}
|
||||
>
|
||||
{currNav.component}
|
||||
</StyledSettingContainer>
|
||||
{deleteConfirm && <DeleteConfirmModal closeModal={toggleDeleteConfirm} id={+uid} />}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import { ReactNode } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import Overview from "./Overview";
|
||||
import AutoDeleteMessages from '../../common/component/AutoDeleteMessages';
|
||||
|
||||
export interface NavItem {
|
||||
name: string;
|
||||
title: string;
|
||||
component: ReactNode;
|
||||
}
|
||||
|
||||
export interface Nav {
|
||||
name?: string;
|
||||
title: string;
|
||||
items: NavItem[];
|
||||
}
|
||||
|
||||
const useNavs = (uid: number): Nav[] => {
|
||||
const { t } = useTranslation("setting");
|
||||
return [
|
||||
{
|
||||
title: t("nav.general"),
|
||||
items: [
|
||||
{
|
||||
name: "overview",
|
||||
title: t("nav.overview"),
|
||||
component: <Overview id={uid} />
|
||||
},
|
||||
{
|
||||
name: "auto_delete_msg",
|
||||
title: t("nav.auto_delete_msg"),
|
||||
component: <AutoDeleteMessages id={uid} type="user" />
|
||||
},
|
||||
]
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
export default useNavs;
|
||||
Reference in New Issue
Block a user