diff --git a/src/app/services/base.query.js b/src/app/services/base.query.js index 2633f62c..c575e563 100644 --- a/src/app/services/base.query.js +++ b/src/app/services/base.query.js @@ -10,6 +10,7 @@ const whiteList = [ "checkInviteTokenValid", "getGoogleAuthConfig", "getLoginConfig", + "getServerVersion", "getServer", "getOpenid", "getMetamaskNonce", diff --git a/src/app/services/channel.js b/src/app/services/channel.js index 48073a59..bedd369e 100644 --- a/src/app/services/channel.js +++ b/src/app/services/channel.js @@ -1,177 +1,192 @@ -import { createApi } from '@reduxjs/toolkit/query/react'; +import { createApi } from "@reduxjs/toolkit/query/react"; // import toast from "react-hot-toast"; -import baseQuery from './base.query'; -import BASE_URL, { ContentTypes } from '../config'; -import { updateChannel } from '../slices/channels'; -import { removeMessage } from '../slices/message'; -import { removeChannelSession } from '../slices/message.channel'; -import { removeReactionMessage } from '../slices/message.reaction'; -import { onMessageSendStarted } from './handlers'; +import baseQuery from "./base.query"; +import BASE_URL, { ContentTypes } from "../config"; +import { updateChannel, removeChannel } from "../slices/channels"; +import { removeMessage } from "../slices/message"; +import { removeChannelSession } from "../slices/message.channel"; +import { removeReactionMessage } from "../slices/message.reaction"; +import { onMessageSendStarted } from "./handlers"; export const channelApi = createApi({ - reducerPath: 'channelApi', - baseQuery, - refetchOnFocus: true, - endpoints: (builder) => ({ - getChannels: builder.query({ - query: () => ({ url: `group` }) + reducerPath: "channelApi", + baseQuery, + refetchOnFocus: true, + endpoints: (builder) => ({ + getChannels: builder.query({ + query: () => ({ url: `group` }), + }), + getChannel: builder.query({ + query: (id) => ({ url: `group/${id}` }), + }), + leaveChannel: builder.query({ + query: (id) => ({ url: `group/${id}/leave` }), + async onQueryStarted(gid, { dispatch, queryFulfilled }) { + try { + await queryFulfilled; + dispatch(removeChannel(gid)); + } catch { + console.log("channel update failed"); + } + }, + }), + createChannel: builder.mutation({ + query: (data) => ({ + url: "group", + method: "POST", + body: data, + }), + }), + updateChannel: builder.mutation({ + query: ({ id, ...data }) => ({ + url: `group/${id}`, + method: "PUT", + body: data, + }), + async onQueryStarted( + { id, name, description }, + { dispatch, queryFulfilled } + ) { + // id: who send to ,from_uid: who sent + const patchResult = dispatch(updateChannel({ id, name, description })); + try { + await queryFulfilled; + } catch { + console.log("channel update failed"); + patchResult.undo(); + } + }, + }), + getHistoryMessages: builder.query({ + query: ({ gid, mid = 0, limit = 50 }) => ({ + url: `/group/${gid}/history?before=${mid}&limit=${limit}`, + }), + // async onQueryStarted(id, { dispatch, getState, queryFulfilled }) { + // const { + // ui: { channelSetting }, + // channelMessage, + // } = getState(); + // try { + // await queryFulfilled; + // dispatch(removeChannel(id)); + // if (id == channelSetting) { + // dispatch(toggleChannelSetting()); + // } + // // 删掉该channel下的所有消息&reaction + // const mids = channelMessage[id]; + // if (mids) { + // dispatch(removeChannelSession(id)); + // dispatch(removeMessage(mids)); + // dispatch(removeReactionMessage(mids)); + // } + // } catch { + // console.log("remove channel error"); + // } + // }, + }), + createInviteLink: builder.query({ + query: (gid) => ({ + headers: { + "content-type": "text/plain", + accept: "text/plain", + }, + url: `/group/${gid}/create_invite_link`, + responseHandler: (response) => response.text(), + }), + transformResponse: (link) => { + // 替换掉域名 + const invite = new URL(link); + return `${location.origin}${invite.pathname}${invite.search}${invite.hash}`; + }, + }), + removeChannel: builder.query({ + query: (id) => ({ + url: `group/${id}`, + method: "DELETE", + }), + async onQueryStarted(id, { dispatch, getState, queryFulfilled }) { + const { channelMessage } = getState(); + try { + await queryFulfilled; + // 删掉该channel下的所有消息&reaction + const mids = channelMessage[id]; + if (mids) { + dispatch(removeChannelSession(id)); + dispatch(removeMessage(mids)); + dispatch(removeReactionMessage(mids)); + } + } catch { + console.log("remove channel error"); + } + }, + }), + sendChannelMsg: builder.mutation({ + query: ({ id, content, type = "text", properties = "" }) => ({ + headers: { + "content-type": ContentTypes[type], + "X-Properties": properties + ? btoa(unescape(encodeURIComponent(JSON.stringify(properties)))) + : "", + }, + url: `group/${id}/send`, + method: "POST", + body: type == "file" ? JSON.stringify(content) : content, + }), + async onQueryStarted(param1, param2) { + await onMessageSendStarted.call(this, param1, param2, "channel"); + }, + }), + addMembers: builder.mutation({ + query: ({ id, members }) => ({ + url: `group/${id}/members/add`, + method: "POST", + body: members, + }), + }), + removeMembers: builder.mutation({ + query: ({ id, members }) => ({ + url: `group/${id}/members/remove`, + method: "POST", + body: members, + }), + }), + updateIcon: builder.mutation({ + query: ({ gid, image }) => ({ + headers: { + "content-type": "image/png", + }, + url: `/group/${gid}/avatar`, + method: "POST", + body: image, + }), + async onQueryStarted({ gid }, { dispatch, queryFulfilled }) { + try { + await queryFulfilled; + dispatch( + updateChannel({ + id: gid, + icon: `${BASE_URL}/resource/group_avatar?gid=${gid}&t=${new Date().getTime()}`, + }) + ); + } catch (error) { + console.log("err", error); + } + }, + }), }), - getChannel: builder.query({ - query: (id) => ({ url: `group/${id}` }) - }), - createChannel: builder.mutation({ - query: (data) => ({ - url: 'group', - method: 'POST', - body: data - }) - }), - updateChannel: builder.mutation({ - query: ({ id, ...data }) => ({ - url: `group/${id}`, - method: 'PUT', - body: data - }), - async onQueryStarted({ id, name, description }, { dispatch, queryFulfilled }) { - // id: who send to ,from_uid: who sent - const patchResult = dispatch(updateChannel({ id, name, description })); - try { - await queryFulfilled; - } catch { - console.log('channel update failed'); - patchResult.undo(); - } - } - }), - getHistoryMessages: builder.query({ - query: ({ gid, mid = 0, limit = 50 }) => ({ - url: `/group/${gid}/history?before=${mid}&limit=${limit}` - }) - // async onQueryStarted(id, { dispatch, getState, queryFulfilled }) { - // const { - // ui: { channelSetting }, - // channelMessage, - // } = getState(); - // try { - // await queryFulfilled; - // dispatch(removeChannel(id)); - // if (id == channelSetting) { - // dispatch(toggleChannelSetting()); - // } - // // 删掉该channel下的所有消息&reaction - // const mids = channelMessage[id]; - // if (mids) { - // dispatch(removeChannelSession(id)); - // dispatch(removeMessage(mids)); - // dispatch(removeReactionMessage(mids)); - // } - // } catch { - // console.log("remove channel error"); - // } - // }, - }), - createInviteLink: builder.query({ - query: (gid) => ({ - headers: { - 'content-type': 'text/plain', - accept: 'text/plain' - }, - url: `/group/${gid}/create_invite_link`, - responseHandler: (response) => response.text() - }), - transformResponse: (link) => { - // 替换掉域名 - const invite = new URL(link); - return `${location.origin}${invite.pathname}${invite.search}${invite.hash}`; - } - }), - removeChannel: builder.query({ - query: (id) => ({ - url: `group/${id}`, - method: 'DELETE' - }), - async onQueryStarted(id, { dispatch, getState, queryFulfilled }) { - const { channelMessage } = getState(); - try { - await queryFulfilled; - // 删掉该channel下的所有消息&reaction - const mids = channelMessage[id]; - if (mids) { - dispatch(removeChannelSession(id)); - dispatch(removeMessage(mids)); - dispatch(removeReactionMessage(mids)); - } - } catch { - console.log('remove channel error'); - } - } - }), - sendChannelMsg: builder.mutation({ - query: ({ id, content, type = 'text', properties = '' }) => ({ - headers: { - 'content-type': ContentTypes[type], - 'X-Properties': properties - ? btoa(unescape(encodeURIComponent(JSON.stringify(properties)))) - : '' - }, - url: `group/${id}/send`, - method: 'POST', - body: type == 'file' ? JSON.stringify(content) : content - }), - async onQueryStarted(param1, param2) { - await onMessageSendStarted.call(this, param1, param2, 'channel'); - } - }), - addMembers: builder.mutation({ - query: ({ id, members }) => ({ - url: `group/${id}/members/add`, - method: 'POST', - body: members - }) - }), - removeMembers: builder.mutation({ - query: ({ id, members }) => ({ - url: `group/${id}/members/remove`, - method: 'POST', - body: members - }) - }), - updateIcon: builder.mutation({ - query: ({ gid, image }) => ({ - headers: { - 'content-type': 'image/png' - }, - url: `/group/${gid}/avatar`, - method: 'POST', - body: image - }), - async onQueryStarted({ gid }, { dispatch, queryFulfilled }) { - try { - await queryFulfilled; - dispatch( - updateChannel({ - id: gid, - icon: `${BASE_URL}/resource/group_avatar?gid=${gid}&t=${new Date().getTime()}` - }) - ); - } catch (error) { - console.log('err', error); - } - } - }) - }) }); export const { - useLazyCreateInviteLinkQuery, - useCreateInviteLinkQuery, - useLazyGetHistoryMessagesQuery, - useGetChannelQuery, - useUpdateChannelMutation, - useLazyRemoveChannelQuery, - useGetChannelsQuery, - useCreateChannelMutation, - useSendChannelMsgMutation, - useAddMembersMutation, - useRemoveMembersMutation, - useUpdateIconMutation + useLazyLeaveChannelQuery, + useLazyCreateInviteLinkQuery, + useCreateInviteLinkQuery, + useLazyGetHistoryMessagesQuery, + useGetChannelQuery, + useUpdateChannelMutation, + useLazyRemoveChannelQuery, + useGetChannelsQuery, + useCreateChannelMutation, + useSendChannelMsgMutation, + useAddMembersMutation, + useRemoveMembersMutation, + useUpdateIconMutation, } = channelApi; diff --git a/src/app/slices/channels.js b/src/app/slices/channels.js index 94d94dc0..1af46c25 100644 --- a/src/app/slices/channels.js +++ b/src/app/slices/channels.js @@ -40,7 +40,6 @@ const channelsSlice = createSlice({ }, updateChannel(state, action) { const ignoreInPublic = ["add_member", "remove_member"]; - // console.log("set channels store", action); const { id, operation, members = [], ...rest } = action.payload; const currChannel = state.byId[id]; if ( @@ -48,7 +47,6 @@ const channelsSlice = createSlice({ (currChannel.is_public && ignoreInPublic.includes(operation)) ) return; - switch (operation) { case "remove_member": { @@ -71,9 +69,7 @@ const channelsSlice = createSlice({ console.log("add member to channel", [..._set]); state.byId[id].members = [..._set]; } - break; - default: state.byId[id] = { ...state.byId[id], ...rest }; break; diff --git a/src/common/component/LeaveChannel/LeaveConfirmModal.js b/src/common/component/LeaveChannel/LeaveConfirmModal.js new file mode 100644 index 00000000..cf9f7e0d --- /dev/null +++ b/src/common/component/LeaveChannel/LeaveConfirmModal.js @@ -0,0 +1,53 @@ +import { useEffect } from "react"; +import toast from "react-hot-toast"; +import { useNavigate } from "react-router-dom"; +import useLeaveChannel from "../../hook/useLeaveChannel"; +import Modal from "../Modal"; +import StyledModal from "../styled/Modal"; +import Button from "../styled/Button"; + +export default function LeaveConfirmModal({ id, closeModal, handleNextStep }) { + const navigateTo = useNavigate(); + const { isOwner, leaving, leaveChannel, leaveSuccess } = useLeaveChannel(id); + + useEffect(() => { + if (leaveSuccess) { + toast.success("Leave channel successfully!"); + closeModal(); + navigateTo("/chat"); + } + }, [leaveSuccess]); + if (!id) return null; + return ( + + + + {isOwner ? ( + + ) : ( + + )} + + } + > + + ); +} diff --git a/src/common/component/LeaveChannel/TransferOwnerModal.js b/src/common/component/LeaveChannel/TransferOwnerModal.js new file mode 100644 index 00000000..b12aca30 --- /dev/null +++ b/src/common/component/LeaveChannel/TransferOwnerModal.js @@ -0,0 +1,111 @@ +import { useEffect, useState } from "react"; +import toast from "react-hot-toast"; +import styled from "styled-components"; +import { useNavigate } from "react-router-dom"; +import Modal from "../Modal"; +import useLeaveChannel from "../../hook/useLeaveChannel"; +import StyledModal from "../styled/Modal"; +import Button from "../styled/Button"; +import Contact from "../Contact"; +const UserList = styled.ul` + display: flex; + flex-direction: column; + max-height: 260px; + padding: 16px 0; + overflow-y: scroll; + .user { + cursor: pointer; + display: flex; + align-items: center; + padding: 0 8px; + width: -webkit-fill-available; + &:hover, + &.selected { + background: rgba(116, 127, 141, 0.1); + } + > a { + width: 100%; + } + } +`; +export default function TransferOwnerModal({ + id, + closeModal, + withLeave = true, +}) { + const { + transferOwner, + otherMembers, + leaving, + leaveChannel, + leaveSuccess, + transferSuccess, + transfering, + } = useLeaveChannel(id); + + const [uid, setUid] = useState(null); + const navigateTo = useNavigate(); + + const handleSelectUser = (uid) => { + setUid(uid); + }; + const handleTransferAndLeave = async () => { + if (!uid) return; + await transferOwner(uid); + if (withLeave) { + await leaveChannel(); + } + }; + + useEffect(() => { + if (transferSuccess && leaveSuccess) { + toast.success("Leave channel successfully!"); + closeModal(); + navigateTo("/chat"); + } + }, [leaveSuccess, transferSuccess, withLeave]); + + if (!id) return null; + const operating = leaving || transfering; + return ( + + + + + + } + > + + {otherMembers.map((id) => { + // const { uid } = u; + return ( +
  • + +
  • + ); + })} +
    +
    +
    + ); +} diff --git a/src/common/component/LeaveChannel/index.js b/src/common/component/LeaveChannel/index.js new file mode 100644 index 00000000..d193d62d --- /dev/null +++ b/src/common/component/LeaveChannel/index.js @@ -0,0 +1,23 @@ +import { useState } from "react"; +// import styled from "styled-components"; +import TransferOwnerModal from "./TransferOwnerModal"; +import LeaveConfirmModal from "./LeaveConfirmModal"; +export default function LeaveChannel({ + id = null, + isOwner = false, + closeModal, +}) { + const [transferOwner, setTransferOwner] = useState(isOwner); + const handleNextStep = () => { + setTransferOwner(true); + }; + if (transferOwner) + return ; + return ( + + ); +} diff --git a/src/common/component/StyledSettingContainer.js b/src/common/component/StyledSettingContainer.js index 35a43dab..7b04eee1 100644 --- a/src/common/component/StyledSettingContainer.js +++ b/src/common/component/StyledSettingContainer.js @@ -115,6 +115,7 @@ export default function StyledSettingContainer({ {dangers.length ? (