feat: auto delete msg

This commit is contained in:
Tristan Yang
2022-12-19 14:36:47 +08:00
parent a75f9c59b4
commit e297411d00
13 changed files with 404 additions and 5 deletions
+34 -2
View File
@@ -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 -1
View File
@@ -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
+2
View File
@@ -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;
+8 -2
View File
@@ -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;
+10
View File
@@ -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>
);
}
+61
View File
@@ -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} />}
</>
);
}
+39
View File
@@ -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;