feat: add menu to More Button in Profile
This commit is contained in:
@@ -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";
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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,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
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user