refactor: invite and add member Modal

This commit is contained in:
zerosoul
2022-04-18 17:51:44 +08:00
parent 4dce50c809
commit 998abd220b
7 changed files with 369 additions and 376 deletions
-141
View File
@@ -1,141 +0,0 @@
// import React from 'react'
import styled from "styled-components";
import useInviteLink from "../hook/useInviteLink";
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: 0;
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;
padding: 16px;
.close {
cursor: pointer;
}
}
.body {
padding: 16px;
box-shadow: 0px -1px 2px rgba(0, 0, 0, 0.06);
.input {
position: relative;
button {
position: absolute;
right: 4px;
top: 50%;
transform: translateY(-50%);
}
input {
padding-right: 80px;
}
}
> .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 Modal from "./Modal";
import Button from "./styled/Button";
import Input from "./styled/Input";
export default function ChannelInviteModal({ cid = null, title, closeModal }) {
const {
enableSMTP,
linkCopied,
link,
copyLink,
generateNewLink,
generating,
} = useInviteLink(cid);
if (!cid) return null;
return (
<Modal>
<Styled>
<h2 className="title">
Invite friends to #{title}{" "}
<CloseIcon className="close" onClick={closeModal} />
</h2>
<div className="body">
<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="ghost small">
Invite
</Button>
</div>
</div>
<div className="link">
<label htmlFor="">Or Send invite link to your friends</label>
<div className="input">
<Input
readOnly
className="higher"
placeholder="Generating"
value={link}
/>
<Button className="small" onClick={copyLink}>
{linkCopied ? `Copied` : `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>
</div>
</Styled>
</Modal>
);
}
@@ -0,0 +1,197 @@
import { useState, useEffect } from "react";
import styled from "styled-components";
import { useSelector } from "react-redux";
import Button from "../styled/Button";
import Input from "../styled/Input";
import Contact from "../Contact";
import StyledCheckbox from "../styled/Checkbox";
import { useAddMembersMutation } from "../../../app/services/channel";
import CloseIcon from "../../../assets/icons/close.svg";
import toast from "react-hot-toast";
import useFilteredUsers from "../../../common/hook/useFilteredUsers";
const Styled = styled.div`
padding-top: 16px;
> .filter {
width: 376px;
min-height: 40px;
padding: 6px 8px;
display: flex;
align-items: center;
margin-bottom: 12px;
background: #ffffff;
border: 1px solid #e5e7eb;
box-shadow: 0px 1px 2px rgba(31, 41, 55, 0.08);
border-radius: 4px;
.selects {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 5px;
width: 100%;
overflow: scroll;
/* white-space: nowrap; */
&::-webkit-scrollbar {
width: 0; /* Remove scrollbar space */
height: 0; /* Remove scrollbar space */
background: transparent; /* Optional: just make scrollbar invisible */
}
.select {
padding: 4px 6px;
background: #52edff;
border-radius: 4px;
font-weight: 600;
font-size: 14px;
line-height: 20px;
color: #ffffff;
display: flex;
justify-content: space-between;
align-items: center;
gap: 5px;
.close {
cursor: pointer;
width: 12px;
height: 12px;
path {
fill: #fff;
fill-opacity: 1;
}
/* filter: invert(1); */
}
}
.input {
width: fit-content;
}
}
}
.users {
display: flex;
flex-direction: column;
/* height: 260px; */
padding-bottom: 20px;
max-height: 364px;
overflow: scroll;
.user {
cursor: pointer;
display: flex;
align-items: center;
padding: 4px 8px;
/* margin: 0 4px; */
width: -webkit-fill-available;
border-radius: 8px;
&:hover {
background: rgba(116, 127, 141, 0.1);
}
> div {
width: 100%;
}
}
}
> .btn {
width: 100%;
margin-top: 16px;
font-weight: 500;
font-size: 14px;
line-height: 20px;
}
`;
export default function AddMembers({ cid = null, closeModal }) {
const [
addMembers,
{ isLoading: isAdding, isSuccess },
] = useAddMembersMutation();
const [selects, setSelects] = useState([]);
const { channel, contactData } = useSelector((store) => {
return {
channel: store.channels.byId[cid],
contactData: store.contacts.byId,
};
});
useEffect(() => {
if (isSuccess) {
toast.success("Add members successfully!");
closeModal();
}
}, [isSuccess]);
const handleAddMembers = () => {
addMembers({ id: cid, members: selects });
};
const { input, updateInput, contacts = [] } = useFilteredUsers();
const toggleCheckMember = ({ currentTarget }) => {
const { uid } = currentTarget.dataset;
if (selects.includes(+uid)) {
setSelects((prevs) => {
return prevs.filter((id) => id != uid);
});
} else {
setSelects([...selects, +uid]);
}
};
const handleFilterInput = (evt) => {
updateInput(evt.target.value);
};
if (!channel) return null;
const { members: uids } = channel;
const contactIds = contacts.map(({ uid }) => uid);
console.log("selects", selects);
return (
<Styled>
<div className="filter">
{/* {selects && selects.length > 0 && ( */}
<ul className="selects">
{selects.map((uid) => {
return (
<li className="select" key={uid}>
{contactData[uid].name}
<CloseIcon
data-uid={uid}
onClick={toggleCheckMember}
className="close"
/>
</li>
);
})}
<Input
autoFocus
type="text"
className="input none"
value={input}
onChange={handleFilterInput}
/>
</ul>
{/* )} */}
</div>
<ul className="users">
{contactIds.map((uid) => {
const added = uids.includes(uid);
return (
<li
key={uid}
data-uid={uid}
className="user"
onClick={added ? null : toggleCheckMember}
>
<StyledCheckbox
disabled={added}
readOnly
checked={added || selects.includes(uid)}
name="cb"
id="cb"
/>
<Contact uid={uid} interactive={false} />
</li>
);
})}
</ul>
<Button
disabled={selects.length == 0 || isAdding}
className="btn"
onClick={handleAddMembers}
>
{isAdding ? `Adding` : "Add"} to #{channel.name}
</Button>
</Styled>
);
}
@@ -0,0 +1,112 @@
// import React from 'react'
import styled from "styled-components";
import useInviteLink from "../../hook/useInviteLink";
const Styled = styled.div`
padding: 16px 0;
.input {
position: relative;
button {
position: absolute;
right: 4px;
top: 50%;
transform: translateY(-50%);
&.copy {
padding-right: 8px;
background: none;
font-weight: 500;
font-size: 14px;
line-height: 20px;
color: #22ccee;
}
}
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({ cid = null }) {
const {
enableSMTP,
linkCopied,
link,
copyLink,
generateNewLink,
generating,
} = useInviteLink(cid);
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="ghost small">
Invite
</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}>
{linkCopied ? `Copied` : `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>
);
}
@@ -0,0 +1,53 @@
// import React from 'react'
import styled from "styled-components";
import { useSelector } from "react-redux";
import InviteByEmail from "./InviteByEmail";
import AddMembers from "./AddMembers";
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 ChannelInviteModal({
cid = null,
title = "",
closeModal,
}) {
const { channel } = useSelector((store) => {
return {
channel: store.channels.byId[cid],
};
});
if (!cid) return null;
return (
<Modal>
<Styled>
<h2 className="title">
Add friends to #{title || channel?.name}{" "}
<CloseIcon className="close" onClick={closeModal} />
</h2>
{!channel?.is_public && <AddMembers cid={cid} />}
<InviteByEmail cid={cid} />
</Styled>
</Modal>
);
}
@@ -1,118 +0,0 @@
import { useState, useEffect } from "react";
import { useSelector } from "react-redux";
import Modal from "../../../common/component/Modal";
import Button from "../../../common/component/styled/Button";
import Input from "../../../common/component/styled/Input";
import Contact from "../../../common/component/Contact";
import StyledCheckbox from "../../../common/component/styled/Checkbox";
import { useAddMembersMutation } from "../../../app/services/channel";
import CloseIcon from "../../../assets/icons/close.svg";
import toast from "react-hot-toast";
import useFilteredUsers from "../../../common/hook/useFilteredUsers";
import Styled from "./add.member.styled";
export default function AddMemberModal({ uids = [], cid = null, closeModal }) {
const [
addMembers,
{ isLoading: isAdding, isSuccess },
] = useAddMembersMutation();
const [selects, setSelects] = useState([]);
const { channel, contactData } = useSelector((store) => {
return {
channel: store.channels.byId[cid],
// contactIds: store.contacts.ids,
contactData: store.contacts.byId,
};
});
useEffect(() => {
if (isSuccess) {
toast.success("Add members successfully!");
closeModal();
}
}, [isSuccess]);
const handleAddMembers = () => {
addMembers({ id: cid, members: selects });
};
const { input, updateInput, contacts = [] } = useFilteredUsers();
const toggleCheckMember = ({ currentTarget }) => {
const { uid } = currentTarget.dataset;
if (selects.includes(+uid)) {
setSelects((prevs) => {
return prevs.filter((id) => id != uid);
});
} else {
setSelects([...selects, +uid]);
}
};
const handleFilterInput = (evt) => {
updateInput(evt.target.value);
};
if (!channel) return null;
const contactIds = contacts.map(({ uid }) => uid);
console.log("selects", selects);
return (
<Modal>
<Styled>
<div className="head">
Add friends to #{channel.name}{" "}
<CloseIcon onClick={closeModal} className="close" />
</div>
<div className="filter">
{/* {selects && selects.length > 0 && ( */}
<ul className="selects">
{selects.map((uid) => {
return (
<li className="select" key={uid}>
{contactData[uid].name}
<CloseIcon
data-uid={uid}
onClick={toggleCheckMember}
className="close"
/>
</li>
);
})}
<Input
autoFocus
type="text"
className="input none"
value={input}
onChange={handleFilterInput}
/>
</ul>
{/* )} */}
</div>
<ul className="users">
{contactIds.map((uid) => {
const added = uids.includes(uid);
return (
<li
key={uid}
data-uid={uid}
className="user"
onClick={added ? null : toggleCheckMember}
>
<StyledCheckbox
disabled={added}
readOnly
checked={added || selects.includes(uid)}
name="cb"
id="cb"
/>
<Contact uid={uid} interactive={false} />
</li>
);
})}
</ul>
<Button
disabled={selects.length == 0 || isAdding}
className="btn"
onClick={handleAddMembers}
>
{isAdding ? `Adding` : "Add"} to #{channel.name}
</Button>
</Styled>
</Modal>
);
}
@@ -1,109 +0,0 @@
import styled from "styled-components";
const Styled = styled.div`
padding: 16px;
filter: drop-shadow(0px 25px 50px rgba(31, 41, 55, 0.25));
border-radius: 8px;
background-color: #fff;
min-width: 410px;
> .head {
font-weight: 600;
font-size: 18px;
line-height: 28px;
color: #374151;
margin-bottom: 16px;
display: flex;
justify-content: space-between;
align-items: center;
.close {
width: 12px;
height: 12px;
cursor: pointer;
}
}
> .filter {
width: 376px;
min-height: 40px;
padding: 6px 8px;
display: flex;
align-items: center;
margin-bottom: 12px;
background: #ffffff;
border: 1px solid #e5e7eb;
box-shadow: 0px 1px 2px rgba(31, 41, 55, 0.08);
border-radius: 4px;
.selects {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 5px;
width: 100%;
overflow: scroll;
/* white-space: nowrap; */
&::-webkit-scrollbar {
width: 0; /* Remove scrollbar space */
height: 0; /* Remove scrollbar space */
background: transparent; /* Optional: just make scrollbar invisible */
}
.select {
padding: 4px 6px;
background: #52edff;
border-radius: 4px;
font-weight: 600;
font-size: 14px;
line-height: 20px;
color: #ffffff;
display: flex;
justify-content: space-between;
align-items: center;
gap: 5px;
.close {
cursor: pointer;
width: 12px;
height: 12px;
path {
fill: #fff;
fill-opacity: 1;
}
/* filter: invert(1); */
}
}
.input {
width: fit-content;
}
}
}
.users {
display: flex;
flex-direction: column;
/* height: 260px; */
padding-bottom: 20px;
max-height: 364px;
overflow: scroll;
.user {
cursor: pointer;
display: flex;
align-items: center;
padding: 4px 8px;
/* margin: 0 4px; */
width: -webkit-fill-available;
border-radius: 8px;
&:hover {
background: rgba(116, 127, 141, 0.1);
}
> div {
width: 100%;
}
}
}
> .btn {
width: 100%;
margin-top: 16px;
font-weight: 500;
font-size: 14px;
line-height: 20px;
}
`;
export default Styled;
+7 -8
View File
@@ -24,7 +24,7 @@ import {
StyledChannelChat,
StyledHeader,
} from "./styled";
import AddMemberModal from "./AddMemberModal";
import ChannelInviteModal from "../../../common/component/ChannelInviteModal";
import { NavLink, useLocation } from "react-router-dom";
export default function ChannelChat({ cid = "", dropFiles = [] }) {
@@ -40,10 +40,12 @@ export default function ChannelChat({ cid = "", dropFiles = [] }) {
data,
messageData,
loginUid,
loginUser,
footprint,
} = useSelector((store) => {
return {
footprint: store.footprint,
loginUser: store.contacts.byId[store.authData.uid],
loginUid: store.authData.uid,
msgIds: store.channelMessage[cid] || [],
userIds: store.contacts.ids,
@@ -62,18 +64,15 @@ export default function ChannelChat({ cid = "", dropFiles = [] }) {
setAddMemberModalVisible((prev) => !prev);
};
const { name, description, is_public, members = [] } = data;
const { name, description, is_public, members = [], owner } = data;
const memberIds = is_public ? userIds : members;
const addVisible = loginUser?.is_admin || owner == loginUid;
console.log("channel message list", msgIds);
const readIndex = footprint.readChannels[cid];
return (
<>
{addMemberModalVisible && (
<AddMemberModal
cid={cid}
uids={members}
closeModal={toggleAddVisible}
/>
<ChannelInviteModal cid={cid} closeModal={toggleAddVisible} />
)}
<Layout
to={cid}
@@ -137,7 +136,7 @@ export default function ChannelChat({ cid = "", dropFiles = [] }) {
membersVisible ? (
<>
<StyledContacts>
{!is_public && (
{addVisible && (
<div className="add" onClick={toggleAddVisible}>
<img className="icon" src={addIcon} />
<div className="txt">Add members</div>