refactor: invite modal

This commit is contained in:
zerosoul
2022-05-07 12:24:01 +08:00
parent 1902c2f891
commit 02d928c4cd
8 changed files with 202 additions and 351 deletions
+2 -2
View File
@@ -1,7 +1,7 @@
import { useState } from "react";
import styled from "styled-components";
import ChannelModal from "../component/ChannelModal";
import ServerInviteModal from "../component/ServerInviteModal";
import InviteModal from "../component/InviteModal";
import IconChat from "../../assets/icons/placeholder.chat.svg";
import IconAsk from "../../assets/icons/placeholder.question.svg";
import IconInvite from "../../assets/icons/placeholder.invite.svg";
@@ -121,7 +121,7 @@ export default function BlankPlaceholder({ type = "chat" }) {
<ContactsModal closeModal={toggleContactListVisible} />
)}
{inviteModalVisible && (
<ServerInviteModal closeModal={toggleInviteModalVisible} />
<InviteModal closeModal={toggleInviteModalVisible} />
)}
</>
);
@@ -27,28 +27,32 @@ const Styled = styled.div`
}
`;
import Modal from "../Modal";
export default function ChannelInviteModal({
// type: server,channel
export default function InviteModal({
type = "server",
cid = null,
title = "",
closeModal,
}) {
const { channel } = useSelector((store) => {
const { channel, server } = useSelector((store) => {
return {
channel: store.channels.byId[cid],
server: store.server,
};
});
if (!cid) return null;
const finalTitle =
type == "server" ? server.name : `#${title || channel?.name}`;
return (
<Modal>
<Styled>
<h2 className="title">
Add friends to #{title || channel?.name}{" "}
Add friends to {finalTitle}
<CloseIcon className="close" onClick={closeModal} />
</h2>
{!channel?.is_public && (
<AddMembers cid={cid} closeModal={closeModal} />
)}
<InviteByEmail cid={cid} />
<InviteByEmail cid={channel?.is_public ? null : cid} />
</Styled>
</Modal>
);
@@ -1,123 +0,0 @@
import { useEffect } from "react";
import toast from "react-hot-toast";
import styled from "styled-components";
import useInviteLink from "../../hook/useInviteLink";
const Styled = styled.div`
padding: 16px 0;
.input {
position: relative;
display: flex;
align-items: center;
gap: 8px;
> .copy {
position: absolute;
right: 4px;
top: 50%;
transform: translateY(-50%);
padding-right: 8px;
background: none;
font-weight: 500;
font-size: 14px;
line-height: 20px;
color: #22ccee;
&:hover {
color: #088ab2;
}
}
input {
padding-right: 80px;
&.invite {
padding-right: 50px;
}
}
}
> .invite {
display: flex;
flex-direction: column;
gap: 16px;
margin-bottom: 16px;
/* position: relative; */
}
> .link {
display: flex;
flex-direction: column;
gap: 8px;
margin-bottom: 12px;
/* position: relative; */
}
label {
color: #6b7280;
font-style: normal;
font-weight: 500;
font-size: 14px;
line-height: 20px;
}
> .tip {
color: #344054;
font-weight: 400;
font-size: 12px;
line-height: 18px;
button {
background: none;
border: none;
color: #22ccee;
}
}
`;
import Button from "../styled/Button";
import Input from "../styled/Input";
export default function InviteByEmail() {
const {
enableSMTP,
linkCopied,
link,
copyLink,
generateNewLink,
generating,
} = useInviteLink();
useEffect(() => {
if (linkCopied) {
toast.success("Invite Link Copied!");
}
}, [linkCopied]);
return (
<Styled>
<div className="invite">
<label htmlFor="">Invite by Email</label>
<div className="input">
<Input
readOnly={true}
disabled={!enableSMTP}
type="email"
className="higher "
placeholder={enableSMTP ? "Enter Email" : "Enable SMTP First"}
/>
<Button disabled={!enableSMTP} className="send">
Send
</Button>
</div>
</div>
<div className="link">
<label htmlFor="">Or Send invite link to your friends</label>
<div className="input">
<Input
readOnly
className="higher invite"
placeholder="Generating"
value={link}
/>
<button className="copy" onClick={copyLink}>
Copy
</button>
</div>
</div>
<div className="tip">
Invite link expires in 7 days.{" "}
<button disabled={generating} className="new" onClick={generateNewLink}>
Generate New Link
</button>
</div>
</Styled>
);
}
@@ -1,44 +0,0 @@
// import React from 'react'
import styled from "styled-components";
import { useSelector } from "react-redux";
import InviteByEmail from "./InviteByEmail";
import CloseIcon from "../../../assets/icons/close.svg";
const Styled = styled.div`
display: flex;
flex-direction: column;
background: #fff;
box-shadow: 0px 25px 50px rgba(31, 41, 55, 0.25);
border-radius: var(--br);
padding: 16px;
min-width: 408px;
> .title {
display: flex;
align-items: center;
justify-content: space-between;
font-style: normal;
font-weight: 600;
font-size: 18px;
line-height: 28px;
color: #374151;
.close {
cursor: pointer;
}
}
`;
import Modal from "../Modal";
export default function ServerInviteModal({ closeModal }) {
const server = useSelector((store) => {
return store.server;
});
return (
<Modal>
<Styled>
<h2 className="title">
Invite friends to {server.name}
<CloseIcon className="close" onClick={closeModal} />
</h2>
<InviteByEmail />
</Styled>
</Modal>
);
}
+2 -2
View File
@@ -26,7 +26,7 @@ import {
StyledChannelChat,
StyledHeader,
} from "./styled";
import ChannelInviteModal from "../../../common/component/ChannelInviteModal";
import InviteModal from "../../../common/component/InviteModal";
import { NavLink, useLocation } from "react-router-dom";
import Tippy from "@tippyjs/react";
@@ -79,7 +79,7 @@ export default function ChannelChat({ cid = "", dropFiles = [] }) {
return (
<>
{addMemberModalVisible && (
<ChannelInviteModal cid={cid} closeModal={toggleAddVisible} />
<InviteModal cid={cid} closeModal={toggleAddVisible} />
)}
<Layout
to={cid}
+189 -175
View File
@@ -1,185 +1,199 @@
import { useState } from 'react';
import { useLocation, useNavigate } from 'react-router-dom';
import { useDrop } from 'react-dnd';
import { NativeTypes } from 'react-dnd-html5-backend';
import { useSelector } from 'react-redux';
import Tippy from '@tippyjs/react';
import useContextMenu from '../../../common/hook/useContextMenu';
import ContextMenu from '../../../common/component/ContextMenu';
import InviteModal from '../../../common/component/ChannelInviteModal';
import Tooltip from '../../../common/component/Tooltip';
import IconSetting from '../../../assets/icons/setting.svg';
import IconInvite from '../../../assets/icons/invite.from.channel.svg';
import { useState } from "react";
import { useLocation, useNavigate } from "react-router-dom";
import { useDrop } from "react-dnd";
import { NativeTypes } from "react-dnd-html5-backend";
import { useSelector } from "react-redux";
import Tippy from "@tippyjs/react";
import useContextMenu from "../../../common/hook/useContextMenu";
import ContextMenu from "../../../common/component/ContextMenu";
import InviteModal from "../../../common/component/InviteModal";
import Tooltip from "../../../common/component/Tooltip";
import IconSetting from "../../../assets/icons/setting.svg";
import IconInvite from "../../../assets/icons/invite.from.channel.svg";
// import { useDebounce} from "rooks";
import { useReadMessageMutation } from '../../../app/services/message';
import { useUpdateMuteSettingMutation } from '../../../app/services/contact';
import { useReadMessageMutation } from "../../../app/services/message";
import { useUpdateMuteSettingMutation } from "../../../app/services/contact";
import StyledLink from './styled';
import ChannelIcon from '../../../common/component/ChannelIcon';
import { getUnreadCount } from '../utils';
import StyledLink from "./styled";
import ChannelIcon from "../../../common/component/ChannelIcon";
import { getUnreadCount } from "../utils";
const NavItem = ({ id, setFiles, toggleRemoveConfirm }) => {
const { pathname } = useLocation();
const [inviteModalVisible, setInviteModalVisible] = useState(false);
const navigate = useNavigate();
const [muteChannel] = useUpdateMuteSettingMutation();
const [updateReadIndex] = useReadMessageMutation();
const { pathname } = useLocation();
const [inviteModalVisible, setInviteModalVisible] = useState(false);
const navigate = useNavigate();
const [muteChannel] = useUpdateMuteSettingMutation();
const [updateReadIndex] = useReadMessageMutation();
const {
visible: contextMenuVisible,
offset,
handleContextMenuEvent,
hideContextMenu
} = useContextMenu();
const { channel, mids, messageData, readIndex, muted, loginUid, loginUser } = useSelector(
(store) => {
return {
loginUser: store.contacts.byId[store.authData.uid],
channel: store.channels.byId[id],
mids: store.channelMessage[id],
messageData: store.message,
loginUid: store.authData.uid,
readIndex: store.footprint.readChannels[id],
muted: store.footprint.muteChannels[id]
};
}
);
const handleChannelSetting = (evt) => {
evt.preventDefault();
evt.stopPropagation();
const { id } = evt.currentTarget.dataset;
if (id) {
navigate(`/setting/channel/${id}?f=${pathname}`);
}
};
const [{ isActive }, drop] = useDrop(() => ({
accept: [NativeTypes.FILE],
drop({ dataTransfer }) {
if (dataTransfer.files.length) {
// console.log(files, rest);
setFiles([...dataTransfer.files]);
navigate(`/chat/channel/${id}`);
// 重置
setTimeout(() => {
setFiles([]);
}, 300);
}
},
collect: (monitor) => ({
isActive: monitor.canDrop() && monitor.isOver()
})
}));
const handleReadAll = () => {
const lastMid = mids[mids.length - 1];
console.log('last mid', mids, lastMid);
if (lastMid) {
const param = { groups: [{ gid: id, mid: lastMid }] };
updateReadIndex(param);
}
};
const toggleInviteModalVisible = (evt) => {
if (evt) {
evt.preventDefault();
evt.stopPropagation();
}
setInviteModalVisible((prev) => !prev);
};
const handleMute = () => {
const data = muted ? { remove_groups: [id] } : { add_groups: [{ gid: id }] };
muteChannel(data);
};
const { is_public, name, owner } = channel;
const { unreads = 0, mentions = [] } = getUnreadCount({
mids,
messageData,
readIndex,
loginUid
});
const isMentions = mentions.length !== 0;
const inviteIconVisible = loginUser?.is_admin || owner == loginUid;
return (
<>
<Tippy
interactive
placement="right-start"
popperOptions={{ strategy: 'fixed' }}
offset={[offset.y, offset.x]}
visible={contextMenuVisible}
onClickOutside={hideContextMenu}
key={id}
content={
<ContextMenu
hideMenu={hideContextMenu}
items={[
{
title: 'Mark As Read',
underline: true,
handler: handleReadAll
},
{
title: muted ? 'Unmute' : 'Mute',
handler: handleMute
},
{
title: 'Notification Settings',
underline: true
},
is_public
? null
: {
title: 'Invite People'
},
{
title: 'Delete Channel',
danger: true,
handler: toggleRemoveConfirm.bind(null, id)
}
]}
/>
const {
visible: contextMenuVisible,
offset,
handleContextMenuEvent,
hideContextMenu,
} = useContextMenu();
const {
channel,
mids,
messageData,
readIndex,
muted,
loginUid,
// loginUser,
} = useSelector((store) => {
return {
// loginUser: store.contacts.byId[store.authData.uid],
channel: store.channels.byId[id],
mids: store.channelMessage[id],
messageData: store.message,
loginUid: store.authData.uid,
readIndex: store.footprint.readChannels[id],
muted: store.footprint.muteChannels[id],
};
});
const handleChannelSetting = (evt) => {
evt.preventDefault();
evt.stopPropagation();
const { id } = evt.currentTarget.dataset;
if (id) {
navigate(`/setting/channel/${id}?f=${pathname}`);
}
>
<StyledLink
data-cid={id}
onContextMenu={handleContextMenuEvent}
ref={drop}
key={id}
className={`link ${isActive ? 'drop_over' : ''} ${muted ? 'muted' : ''}`}
to={`/chat/channel/${id}`}
>
<div className={`name`} title={name}>
<ChannelIcon personal={!is_public} muted={muted} />
<span className={`txt ${unreads == 0 ? 'read' : ''}`}>{name}</span>
</div>
<div className="icons">
{inviteIconVisible && (
<Tooltip placement="bottom" tip="Add Member">
<IconInvite
className="icon invite"
data-id={id}
onClick={toggleInviteModalVisible}
></IconInvite>
</Tooltip>
};
const [{ isActive }, drop] = useDrop(() => ({
accept: [NativeTypes.FILE],
drop({ dataTransfer }) {
if (dataTransfer.files.length) {
// console.log(files, rest);
setFiles([...dataTransfer.files]);
navigate(`/chat/channel/${id}`);
// 重置
setTimeout(() => {
setFiles([]);
}, 300);
}
},
collect: (monitor) => ({
isActive: monitor.canDrop() && monitor.isOver(),
}),
}));
const handleReadAll = () => {
const lastMid = mids[mids.length - 1];
console.log("last mid", mids, lastMid);
if (lastMid) {
const param = { groups: [{ gid: id, mid: lastMid }] };
updateReadIndex(param);
}
};
const toggleInviteModalVisible = (evt) => {
if (evt) {
evt.preventDefault();
evt.stopPropagation();
}
setInviteModalVisible((prev) => !prev);
};
const handleMute = () => {
const data = muted
? { remove_groups: [id] }
: { add_groups: [{ gid: id }] };
muteChannel(data);
};
const { is_public, name, owner } = channel;
const { unreads = 0, mentions = [] } = getUnreadCount({
mids,
messageData,
readIndex,
loginUid,
});
const isMentions = mentions.length !== 0;
const inviteIconVisible = is_public || owner == loginUid;
return (
<>
<Tippy
interactive
placement="right-start"
popperOptions={{ strategy: "fixed" }}
offset={[offset.y, offset.x]}
visible={contextMenuVisible}
onClickOutside={hideContextMenu}
key={id}
content={
<ContextMenu
hideMenu={hideContextMenu}
items={[
{
title: "Mark As Read",
underline: true,
handler: handleReadAll,
},
{
title: muted ? "Unmute" : "Mute",
handler: handleMute,
},
{
title: "Notification Settings",
underline: true,
},
{
title: "Invite People",
handler: toggleInviteModalVisible,
},
{
title: "Delete Channel",
danger: true,
handler: toggleRemoveConfirm.bind(null, id),
},
]}
/>
}
>
<StyledLink
data-cid={id}
onContextMenu={handleContextMenuEvent}
ref={drop}
key={id}
className={`link ${isActive ? "drop_over" : ""} ${
muted ? "muted" : ""
}`}
to={`/chat/channel/${id}`}
>
<div className={`name`} title={name}>
<ChannelIcon personal={!is_public} muted={muted} />
<span className={`txt ${unreads == 0 ? "read" : ""}`}>{name}</span>
</div>
<div className="icons">
{inviteIconVisible && (
<Tooltip placement="bottom" tip="Add Member">
<IconInvite
className="icon invite"
data-id={id}
onClick={toggleInviteModalVisible}
></IconInvite>
</Tooltip>
)}
<Tooltip placement="bottom" tip="Channel Setting">
<IconSetting
className="icon setting"
data-id={id}
onClick={handleChannelSetting}
></IconSetting>
</Tooltip>
{unreads > 0 && (
<i className={`badge ${isMentions ? "mention" : ""}`}>
{isMentions ? mentions.length : unreads}
</i>
)}
</div>
</StyledLink>
</Tippy>
{inviteModalVisible && (
<InviteModal
type="channel"
cid={id}
title={channel.name}
closeModal={toggleInviteModalVisible}
/>
)}
<Tooltip placement="bottom" tip="Channel Setting">
<IconSetting
className="icon setting"
data-id={id}
onClick={handleChannelSetting}
></IconSetting>
</Tooltip>
{unreads > 0 && (
<i className={`badge ${isMentions ? 'mention' : ''}`}>
{isMentions ? mentions.length : unreads}
</i>
)}
</div>
</StyledLink>
</Tippy>
{inviteModalVisible && (
<InviteModal cid={id} title={channel.name} closeModal={toggleInviteModalVisible} />
)}
</>
);
</>
);
};
export default NavItem;