feat: add menu to More Button in Profile

This commit is contained in:
zerosoul
2022-05-25 14:11:08 +08:00
parent 74ea8032d6
commit b67bf93ce0
12 changed files with 232 additions and 160 deletions
+4
View File
@@ -18,6 +18,10 @@ export const firebaseConfig = {
appId: "1:418687074928:web:753286adbf239f5af9eab5",
measurementId: "G-XV476KEC8P",
};
export const ChatPrefixs = {
channel: "#",
user: "@",
};
export const vapidKey = `BGXCn-5YRXSFw38Q9lUKJ5bibL212-yIQn1pCvthGhp6_KwA29FO1Ax_d_7if1vfC2a5wTSVO8AcZrc-Hm1aS0Y`;
// "840319286941-6ds7lbvk55eq8mjortf68cb2ll65lprt.apps.googleusercontent.com";
export const tokenHeader = "X-API-Key";
+2 -1
View File
@@ -63,6 +63,7 @@ const StyledWrapper = styled.div`
}
`;
export default function Contact({
cid = null,
owner = false,
dm = false,
interactive = true,
@@ -84,7 +85,7 @@ export default function Contact({
disabled={!popover}
placement="left"
trigger="click"
content={<Profile uid={uid} type="card" />}
content={<Profile uid={uid} type="card" cid={cid} />}
>
<StyledWrapper
size={avatarSize}
+1 -6
View File
@@ -127,7 +127,7 @@ export default function ManageMembers({ cid = null }) {
loginUser: store.contacts.byId[store.authData.uid],
};
});
const [copied, copy] = useCopy();
const { copy } = useCopy();
const [
updateContact,
{ isSuccess: updateSuccess },
@@ -149,11 +149,6 @@ export default function ManageMembers({ cid = null }) {
toast.success("Delete Successfully");
}
}, [removeSuccess]);
useEffect(() => {
if (copied) {
toast.success("Emial Copied!");
}
}, [copied]);
useEffect(() => {
if (removeMemberSuccess) {
toast.success("Remove Member successfully");
+2 -2
View File
@@ -10,7 +10,7 @@ const Styled = styled.span`
cursor: pointer;
}
`;
export default function Mention({ uid, popover = true }) {
export default function Mention({ uid, popover = true, cid }) {
const contactsData = useSelector((store) => store.contacts.byId);
const user = contactsData[uid];
if (!user) return null;
@@ -20,7 +20,7 @@ export default function Mention({ uid, popover = true }) {
interactive
placement="top"
trigger="click"
content={<Profile uid={uid} type="card" />}
content={<Profile uid={uid} type="card" cid={cid} />}
>
<Styled className={popover ? "clickable" : ""}>{`@${user.name}`}</Styled>
</Tippy>
+1 -1
View File
@@ -99,7 +99,7 @@ function Message({
interactive
placement="left"
trigger="click"
content={<Profile uid={fromUid} type="card" />}
content={<Profile uid={fromUid} type="card" cid={contextId} />}
>
<div className="avatar" data-uid={fromUid} ref={avatarRef}>
<Avatar url={currUser.avatar} name={currUser.name} />
@@ -51,7 +51,7 @@ const renderContent = ({
(match, idx) => {
console.log("match", match);
const uid = match.trim().slice(1);
return <Mention key={idx} uid={uid} />;
return <Mention key={idx} uid={uid} cid={to} />;
}
)}
{/* {content.replace(/\s{1}\@[1-9]+\s{1}/g,)} */}
@@ -9,7 +9,7 @@ import useCopy from "../../hook/useCopy";
import usePinMessage from "../../hook/usePinMessage";
export default function useMessageOperation({ mid, context, contextId }) {
const [copied, copy] = useCopy();
const { copy } = useCopy();
const { content_type, properties, currUid, from_uid, content } = useSelector(
(store) => {
return {
@@ -64,11 +64,6 @@ export default function useMessageOperation({ mid, context, contextId }) {
toast.success("Unpin Message Successfully!");
}
}, [isUnpinSuccess]);
useEffect(() => {
if (copied) {
toast.success("Copied");
}
}, [copied]);
const enablePin = context == "channel" && canPin;
// const enableReply = currUid != from_uid;
const isImage =
@@ -83,7 +78,9 @@ export default function useMessageOperation({ mid, context, contextId }) {
[ContentTypes.text, ContentTypes.markdown].includes(content_type) ||
isImage;
return {
copyContent: isImage ? copyContent.bind(null, true) : copyContent,
copyContent: isImage
? copyContent.bind(null, true)
: copyContent.bind(null, false),
canCopy,
isImage,
isMarkdown: content_type == ContentTypes.markdown,
-134
View File
@@ -1,134 +0,0 @@
// import React from "react";
import { useSelector } from 'react-redux';
import { NavLink } from 'react-router-dom';
import styled from 'styled-components';
import IconMessage from '../../assets/icons/message.svg';
import IconCall from '../../assets/icons/call.svg';
import IconMore from '../../assets/icons/more.svg';
import Avatar from '../../common/component/Avatar';
import toast from 'react-hot-toast';
const StyledWrapper = styled.div`
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
margin-top: 80px;
width: 432px;
gap: 4px;
.avatar {
width: 80px;
height: 80px;
border-radius: 50%;
}
.name {
user-select: text;
font-weight: bold;
font-size: 18px;
line-height: 100%;
color: #1c1c1e;
}
.email {
user-select: text;
font-weight: normal;
font-size: 14px;
line-height: 20px;
color: #98a2b3;
}
.intro {
color: #344054;
font-style: normal;
font-weight: 400;
font-size: 14px;
line-height: 20px;
}
.icons {
margin-top: 24px;
display: flex;
align-items: center;
gap: 8px;
.icon {
cursor: pointer;
font-weight: 500;
font-size: 14px;
line-height: 20px;
color: #22ccee;
display: flex;
align-items: center;
flex-direction: column;
gap: 4px;
background: #f9fafb;
border-radius: 8px;
width: 128px;
padding: 14px 0 12px 0;
&:hover {
background: #f2f4f7;
}
&.call,
&.more {
svg path {
fill: #22ccee;
}
}
}
}
.line {
width: 100%;
height: 1px;
border: none;
background-color: rgba(0, 0, 0, 0.1);
}
&.card {
padding: 16px;
width: 280px;
background: #ffffff;
box-shadow: 0px 20px 25px 20px rgba(31, 41, 55, 0.1), 0px 10px 10px rgba(31, 41, 55, 0.04);
border-radius: 6px;
.icons {
padding-bottom: 2px;
}
}
`;
export default function Profile({ uid = null, type = 'embed' }) {
const data = useSelector((store) => store.contacts.byId[uid]);
if (!data) return null;
// console.log("profile", data);
const {
name,
email,
avatar
// introduction = "This guy has nothing to introduce",
} = data;
const handleClick = () => {
toast.success('cooming soon...');
};
return (
<StyledWrapper className={type}>
<Avatar className="avatar" url={avatar} name={name} />
<h2 className="name">{name}</h2>
<span className="email">{email}</span>
{/* <p className="intro">{introduction}</p> */}
<ul className="icons">
<NavLink to={`/chat/dm/${uid}`}>
<li className="icon chat">
<IconMessage />
<span className="txt">Message</span>
</li>
</NavLink>
{/* <NavLink to={`#`}> */}
{type !== 'card' && (
<li className="icon call" onClick={handleClick}>
<IconCall />
<span className="txt">Call</span>
</li>
)}
{/* </NavLink> */}
<li className="icon more" onClick={handleClick}>
<IconMore />
<span className="txt">More</span>
</li>
</ul>
{/* {type == "embed" && <hr className="line" />} */}
</StyledWrapper>
);
}
+116
View File
@@ -0,0 +1,116 @@
import { useEffect } from "react";
import { useSelector } from "react-redux";
import { NavLink } from "react-router-dom";
import toast from "react-hot-toast";
import Tippy from "@tippyjs/react";
import { hideAll } from "tippy.js";
import IconMessage from "../../../assets/icons/message.svg";
import IconCall from "../../../assets/icons/call.svg";
import IconMore from "../../../assets/icons/more.svg";
import Avatar from "../Avatar";
import StyledWrapper from "./styled";
import StyledMenu from "../styled/Menu";
import useCopy from "../../hook/useCopy";
import { useRemoveMembersMutation } from "../../../app/services/channel";
export default function Profile({ uid = null, type = "embed", cid = null }) {
const [
removeFromChannel,
{ isSuccess: removeSuccess },
] = useRemoveMembersMutation();
const { copy } = useCopy();
const { data, channel, loginUid, isAdmin } = useSelector((store) => {
return {
data: store.contacts.byId[uid],
channel: store.channels.byId[cid],
loginUid: store.authData.uid,
isAdmin: store.contacts.byId[store.authData.uid]?.is_admin,
};
});
useEffect(() => {
if (removeSuccess) {
toast.success("Remove Successfully");
}
}, [removeSuccess]);
if (!data) return null;
// console.log("profile", data);
const {
name,
email,
avatar,
// introduction = "This guy has nothing to introduce",
} = data;
const handleClick = () => {
toast.success("cooming soon...");
};
const handlCopyEmail = () => {
copy(email);
hideAll();
};
const handleRemove = (uid) => {
removeFromChannel({ id: +cid, members: [+uid] });
hideAll();
};
const canCall = type !== "card" && loginUid != uid;
const canRemoveFromChannel =
cid &&
!channel?.is_public &&
(isAdmin || channel?.owner == loginUid) &&
channel?.owner != uid;
const hasMore = canCall || email || canRemoveFromChannel;
return (
<StyledWrapper className={type}>
<Avatar className="avatar" url={avatar} name={name} />
<h2 className="name">{name}</h2>
<span className="email">{email}</span>
{/* <p className="intro">{introduction}</p> */}
<ul className="icons">
<NavLink to={`/chat/dm/${uid}`}>
<li className="icon chat">
<IconMessage />
<span className="txt">Message</span>
</li>
</NavLink>
{/* <NavLink to={`#`}> */}
{canCall && (
<li className="icon call" onClick={handleClick}>
<IconCall />
<span className="txt">Call</span>
</li>
)}
{/* </NavLink> */}
<Tippy
disabled={!hasMore}
interactive
placement="bottom-start"
trigger="click"
hideOnClick={true}
content={
<StyledMenu>
{canCall && <li className="item">Call</li>}
{email && (
<li className="item" onClick={handlCopyEmail}>
Copy Email
</li>
)}
{canRemoveFromChannel && (
<li
className="item danger"
onClick={handleRemove.bind(null, uid)}
>
Remove from Channel
</li>
)}
</StyledMenu>
}
>
<li className={`icon more ${hasMore ? "" : "disabled"}`}>
<IconMore />
<span className="txt">More</span>
</li>
</Tippy>
</ul>
</StyledWrapper>
);
}
+96
View File
@@ -0,0 +1,96 @@
import styled from "styled-components";
const StyledWrapper = styled.div`
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
margin-top: 80px;
width: 432px;
gap: 4px;
.avatar {
width: 80px;
height: 80px;
border-radius: 50%;
}
.name {
user-select: text;
font-weight: bold;
font-size: 18px;
line-height: 100%;
color: #1c1c1e;
}
.email {
user-select: text;
font-weight: normal;
font-size: 14px;
line-height: 20px;
color: #98a2b3;
}
.intro {
color: #344054;
font-style: normal;
font-weight: 400;
font-size: 14px;
line-height: 20px;
}
.icons {
margin-top: 24px;
display: flex;
align-items: center;
gap: 8px;
.icon {
cursor: pointer;
font-weight: 500;
font-size: 14px;
line-height: 20px;
color: #22ccee;
display: flex;
align-items: center;
flex-direction: column;
gap: 4px;
background: #f9fafb;
border-radius: 8px;
width: 128px;
padding: 14px 0 12px 0;
&:not(.disabled):hover {
background: #f2f4f7;
}
&.call,
&.more {
svg path {
fill: #22ccee;
}
}
&.disabled {
color: #ccc;
svg path {
fill: #ccc;
}
}
.txt {
user-select: none;
}
}
}
.line {
width: 100%;
height: 1px;
border: none;
background-color: rgba(0, 0, 0, 0.1);
}
&.card {
padding: 16px;
width: 280px;
background: #ffffff;
box-shadow: 0px 20px 25px 20px rgba(31, 41, 55, 0.1),
0px 10px 10px rgba(31, 41, 55, 0.04);
border-radius: 6px;
.icons {
padding-bottom: 2px;
}
}
`;
export default StyledWrapper;
+3 -6
View File
@@ -3,7 +3,7 @@ import { useDispatch, useSelector } from "react-redux";
import useSendMessage from "../../hook/useSendMessage";
import useAddLocalFileMessage from "../../hook/useAddLocalFileMessage";
import { updateInputMode } from "../../../app/slices/ui";
import { ContentTypes } from "../../../app/config";
import { ContentTypes, ChatPrefixs } from "../../../app/config";
import StyledSend from "./styled";
import UploadFileList from "./UploadFileList";
@@ -15,10 +15,7 @@ import MarkdownEditor from "../MarkdownEditor";
import MixedInput, { useMixedEditor } from "../MixedInput";
import useDraft from "../../hook/useDraft";
import useUploadFile from "../../hook/useUploadFile";
const Types = {
channel: "#",
user: "@",
};
const Modes = {
text: "text",
markdown: "markdown",
@@ -132,7 +129,7 @@ function Send({
};
const name =
context == "channel" ? channelsData[id]?.name : contactsData[id]?.name;
const placeholder = `Send to ${Types[context]}${name} `;
const placeholder = `Send to ${ChatPrefixs[context]}${name} `;
const members =
context == "channel"
? channelsData[id]?.is_public
+2 -2
View File
@@ -19,7 +19,7 @@ export default function useInviteLink(cid = null) {
generateServerInviteLink,
{ data: serverInviteLink, isLoading: generatingServerLink },
] = useCreateServerInviteLinkQuery();
const [linkCopied, copy] = useCopy();
const { copied, copy } = useCopy({ enableToast: false });
const copyLink = () => {
copy(finalLink);
};
@@ -50,7 +50,7 @@ export default function useInviteLink(cid = null) {
? generateChannelInviteLink.bind(null, cid)
: genServerLink,
link: finalLink,
linkCopied,
linkCopied: copied,
copyLink,
};
}