feat: invit link modal from channel

This commit is contained in:
zerosoul
2022-04-13 21:47:29 +08:00
parent d7cd2f6836
commit 9552efc75e
6 changed files with 301 additions and 116 deletions
+3
View File
@@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M9 5C9 5.52592 9.203 6.00443 9.53491 6.36145C10.1452 6.12793 10.8076 6 11.5 6C11.8966 6 12.2833 6.04197 12.6561 6.12171C12.8732 5.80182 13 5.41572 13 5C13 3.89543 12.1046 3 11 3C9.89543 3 9 3.89543 9 5ZM6.06019 13.0008C6.10955 13.0008 6.15849 13.0005 6.20699 12.9998C6.07215 12.5231 6 12.02 6 11.5C6 10.1704 6.47182 8.95094 7.25716 8H3.5C2.67157 8 2 8.67157 2 9.5V10.2746C2 12.0969 3.44552 13.0008 6.06019 13.0008ZM6 2C7.38094 2 8.50041 3.11947 8.50041 4.5004C8.50041 5.88134 7.38094 7.00081 6 7.00081C4.61906 7.00081 3.49959 5.88134 3.49959 4.5004C3.49959 3.11947 4.61906 2 6 2ZM16 11.5C16 13.9853 13.9853 16 11.5 16C9.01472 16 7 13.9853 7 11.5C7 9.01472 9.01472 7 11.5 7C13.9853 7 16 9.01472 16 11.5ZM12 9.5C12 9.22386 11.7761 9 11.5 9C11.2239 9 11 9.22386 11 9.5V11H9.5C9.22386 11 9 11.2239 9 11.5C9 11.7761 9.22386 12 9.5 12H11V13.5C11 13.7761 11.2239 14 11.5 14C11.7761 14 12 13.7761 12 13.5V12H13.5C13.7761 12 14 11.7761 14 11.5C14 11.2239 13.7761 11 13.5 11H12V9.5Z" fill="#344054"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

+15 -40
View File
@@ -1,12 +1,7 @@
import { useEffect } from "react";
// import { useEffect } from "react";
import styled from "styled-components";
import { useSelector } from "react-redux";
import {
useLazyCreateInviteLinkQuery,
useGetSMTPConfigQuery,
} from "../../app/services/server";
import useInviteLink from "../hook/useInviteLink";
import Button from "./styled/Button";
import useCopy from "../hook/useCopy";
const StyledWrapper = styled.div`
display: flex;
flex-direction: column;
@@ -51,51 +46,31 @@ const StyledWrapper = styled.div`
`;
export default function InviteLink() {
const {
data: config,
isSuccess: configQuerySuccess,
} = useGetSMTPConfigQuery();
const [copid, copy] = useCopy();
const {
inviteLink: { link },
loginUser,
} = useSelector((store) => {
return {
inviteLink: store.server.inviteLink,
loginUser: store.contacts.byId[store.authData.uid],
};
});
const [createLink, { isLoading }] = useLazyCreateInviteLinkQuery();
useEffect(() => {
if (!link && loginUser && loginUser.is_admin) {
createLink();
}
}, [link, loginUser]);
generating,
link,
linkCopied,
copyLink,
generateNewLink,
} = useInviteLink();
const handleNewLink = () => {
createLink();
generateNewLink();
};
if (!loginUser || !loginUser.is_admin || !configQuerySuccess) return null;
let finalLink = null;
if (link) {
const tmpURL = new URL(link);
tmpURL.searchParams.set("code", config.enabled);
finalLink = tmpURL.href;
}
return (
<StyledWrapper>
<span className="tip">
Share this link to invite people to this server.
</span>
<div className="link">
<span title={finalLink} className="content">
{finalLink}
<span title={link} className="content">
{link}
</span>
<Button onClick={copy.bind(null, finalLink)} className="main">
{copid ? "Copied" : `Copy`}
<Button onClick={copyLink} className="main">
{linkCopied ? "Copied" : `Copy`}
</Button>
</div>
<span className="sub_tip">Invite link expires in 7 days.</span>
<Button className="ghost" disabled={isLoading} onClick={handleNewLink}>
{isLoading ? `Generating` : `Generate New Link`}
<Button className="ghost" disabled={generating} onClick={handleNewLink}>
{generating ? `Generating` : `Generate New Link`}
</Button>
</StyledWrapper>
);
+134
View File
@@ -0,0 +1,134 @@
// 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 InviteModal({ title, closeModal }) {
const {
enableSMTP,
linkCopied,
link,
copyLink,
generateNewLink,
generating,
} = useInviteLink();
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
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 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>
);
}
+35
View File
@@ -0,0 +1,35 @@
import { useState, useEffect } from "react";
import useCopy from "./useCopy";
import {
useCreateInviteLinkQuery,
useGetSMTPConfigQuery,
} from "../../app/services/server";
export default function useInviteLink() {
const [finalLink, setFinalLink] = useState("");
const {
data: config,
isSuccess: configQuerySuccess,
} = useGetSMTPConfigQuery();
const { data: link, isLoading, refetch } = useCreateInviteLinkQuery();
const [linkCopied, copy] = useCopy();
const copyLink = () => {
copy(finalLink);
};
useEffect(() => {
if (link && config) {
const tmpURL = new URL(link);
tmpURL.searchParams.set("code", config.enabled);
setFinalLink(tmpURL.href);
}
}, [link, config]);
return {
enableSMTP: config?.enabled,
generating: isLoading,
generateNewLink: refetch,
link: finalLink,
linkCopied,
copyLink,
};
}
+96 -68
View File
@@ -1,3 +1,4 @@
import { useState } from "react";
import { useNavigate } from "react-router-dom";
import { useDrop } from "react-dnd";
import { NativeTypes } from "react-dnd-html5-backend";
@@ -5,6 +6,7 @@ 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 { useDebounce} from "rooks";
import { useReadMessageMutation } from "../../../app/services/message";
@@ -15,6 +17,7 @@ import ChannelIcon from "../../../common/component/ChannelIcon";
import { getUnreadCount } from "../utils";
const NavItem = ({ id, setFiles, toggleRemoveConfirm }) => {
const [inviteModalVisible, setInviteModalVisible] = useState(false);
const navigate = useNavigate();
const [muteChannel] = useUpdateMuteSettingMutation();
const [updateReadIndex] = useReadMessageMutation();
@@ -75,6 +78,11 @@ const NavItem = ({ id, setFiles, toggleRemoveConfirm }) => {
updateReadIndex(param);
}
};
const toggleInviteModalVisible = (evt) => {
evt.preventDefault();
evt.stopPropagation();
setInviteModalVisible((prev) => !prev);
};
const handleMute = () => {
const data = muted
? { remove_groups: [id] }
@@ -82,77 +90,97 @@ const NavItem = ({ id, setFiles, toggleRemoveConfirm }) => {
muteChannel(data);
};
const { is_public, name } = channel;
const unreads = getUnreadCount({ mids, messageData, readIndex, loginUid });
const isDot = muted || unreads > 99;
const { unreads, mentions = [] } = getUnreadCount({
mids,
messageData,
readIndex,
loginUid,
});
const isMentions = mentions.length !== 0;
return (
<Tippy
interactive
placement="right-start"
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),
},
]}
/>
}
>
<StyledLink
data-cid={id}
onContextMenu={handleContextMenuEvent}
ref={drop}
<>
<Tippy
interactive
placement="right-start"
offset={[offset.y, offset.x]}
visible={contextMenuVisible}
onClickOutside={hideContextMenu}
key={id}
className={`link ${isActive ? "drop_over" : ""} ${
muted ? "muted" : ""
}`}
to={`/chat/channel/${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),
},
]}
/>
}
>
<div className={`name`} title={name}>
<ChannelIcon personal={!is_public} muted={muted} />
<span className={`txt ${unreads == 0 ? "read" : ""}`}>{name}</span>
</div>
<div className="icons">
<Tooltip placement="bottom" tip="Channel Setting">
<i
className="setting"
data-id={id}
onClick={handleChannelSetting}
></i>
</Tooltip>
{unreads > 0 && (
<i className={`badge ${isDot ? "dot" : ""}`}>
{isDot ? null : unreads}
</i>
)}
</div>
</StyledLink>
</Tippy>
<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">
<Tooltip placement="bottom" tip="Add Member">
<i
className="icon invite"
data-id={id}
onClick={toggleInviteModalVisible}
></i>
</Tooltip>
<Tooltip placement="bottom" tip="Channel Setting">
<i
className="icon setting"
data-id={id}
onClick={handleChannelSetting}
></i>
</Tooltip>
{unreads > 0 && (
<i className={`badge ${isMentions ? "mention" : ""}`}>
{isMentions ? mentions.length : unreads}
</i>
)}
</div>
</StyledLink>
</Tippy>
{inviteModalVisible && (
<InviteModal
title={channel.name}
closeModal={toggleInviteModalVisible}
/>
)}
</>
);
};
+18 -8
View File
@@ -1,6 +1,7 @@
import styled from "styled-components";
import { NavLink } from "react-router-dom";
import settingIcon from "../../../assets/icons/setting.svg?url";
import inviteIcon from "../../../assets/icons/invite.from.channel.svg?url";
const Styled = styled(NavLink)`
position: relative;
display: flex;
@@ -21,7 +22,7 @@ const Styled = styled(NavLink)`
font-weight: 600;
font-size: 14px;
line-height: 20px;
width: 160px;
width: 140px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
@@ -33,14 +34,20 @@ const Styled = styled(NavLink)`
> .icons {
display: flex;
align-items: center;
gap: 4px;
> .setting {
gap: 8px;
> .icon {
visibility: hidden;
display: flex;
width: 16px;
height: 16px;
background-image: url(${settingIcon});
background-size: 16px;
background-size: contain;
&.setting {
background-image: url(${settingIcon});
}
&.invite {
background-image: url(${inviteIcon});
}
}
> .badge {
color: #fff;
@@ -54,6 +61,9 @@ const Styled = styled(NavLink)`
font-weight: 900;
font-size: 10px;
line-height: 10px;
&.mention {
background: #f97066;
}
&.dot {
min-width: unset;
width: 6px;
@@ -70,11 +80,11 @@ const Styled = styled(NavLink)`
background: #bfbfbf;
}
}
&:hover > .icons > {
.badge {
&:hover > .icons {
> .badge {
display: none;
}
.setting {
> .icon {
visibility: visible;
}
}