Add user blocking (#110)
This commit is contained in:
parent
2714e8e9dd
commit
c7626f5deb
|
@ -1,9 +1,11 @@
|
||||||
import React, { useEffect, useMemo, useRef, useState } from "react";
|
import React, { useEffect, useMemo, useRef, useState } from "react";
|
||||||
import styled from "styled-components";
|
import styled from "styled-components";
|
||||||
|
|
||||||
|
import { useBlockedUsers } from "../../contexts/blockedUsersProvider";
|
||||||
import { useMessengerContext } from "../../contexts/messengerProvider";
|
import { useMessengerContext } from "../../contexts/messengerProvider";
|
||||||
import { ChatMessage } from "../../models/ChatMessage";
|
import { ChatMessage } from "../../models/ChatMessage";
|
||||||
import { equalDate } from "../../utils";
|
import { equalDate } from "../../utils";
|
||||||
|
import { ContactMenu } from "../Form/ContactMenu";
|
||||||
import { LoadingIcon } from "../Icons/LoadingIcon";
|
import { LoadingIcon } from "../Icons/LoadingIcon";
|
||||||
import { UserIcon } from "../Icons/UserIcon";
|
import { UserIcon } from "../Icons/UserIcon";
|
||||||
import { LinkModal } from "../Modals/LinkModal";
|
import { LinkModal } from "../Modals/LinkModal";
|
||||||
|
@ -12,6 +14,60 @@ import { textSmallStyles } from "../Text";
|
||||||
|
|
||||||
import { ChatMessageContent } from "./ChatMessageContent";
|
import { ChatMessageContent } from "./ChatMessageContent";
|
||||||
|
|
||||||
|
const today = new Date();
|
||||||
|
|
||||||
|
type ChatUiMessageProps = {
|
||||||
|
idx: number;
|
||||||
|
message: ChatMessage;
|
||||||
|
prevMessage: ChatMessage;
|
||||||
|
setImage: (img: string) => void;
|
||||||
|
setLink: (link: string) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
function ChatUiMessage({
|
||||||
|
message,
|
||||||
|
idx,
|
||||||
|
prevMessage,
|
||||||
|
setImage,
|
||||||
|
setLink,
|
||||||
|
}: ChatUiMessageProps) {
|
||||||
|
const [showMenu, setShowMenu] = useState(false);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<MessageOuterWrapper>
|
||||||
|
{(idx === 0 || !equalDate(prevMessage.date, message.date)) && (
|
||||||
|
<DateSeparator>
|
||||||
|
{equalDate(message.date, today)
|
||||||
|
? "Today"
|
||||||
|
: message.date.toLocaleDateString()}
|
||||||
|
</DateSeparator>
|
||||||
|
)}
|
||||||
|
<MessageWrapper>
|
||||||
|
<Icon onClick={() => setShowMenu((e) => !e)}>
|
||||||
|
{showMenu && (
|
||||||
|
<ContactMenu id={message.sender} setShowMenu={setShowMenu} />
|
||||||
|
)}
|
||||||
|
<UserIcon />
|
||||||
|
</Icon>
|
||||||
|
|
||||||
|
<ContentWrapper>
|
||||||
|
<MessageHeaderWrapper>
|
||||||
|
<UserNameWrapper>{message.sender.slice(0, 10)}</UserNameWrapper>
|
||||||
|
<TimeWrapper>{message.date.toLocaleString()}</TimeWrapper>
|
||||||
|
</MessageHeaderWrapper>
|
||||||
|
<MessageText>
|
||||||
|
<ChatMessageContent
|
||||||
|
message={message}
|
||||||
|
setImage={setImage}
|
||||||
|
setLinkOpen={setLink}
|
||||||
|
/>
|
||||||
|
</MessageText>
|
||||||
|
</ContentWrapper>
|
||||||
|
</MessageWrapper>
|
||||||
|
</MessageOuterWrapper>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
type ChatMessagesProps = {
|
type ChatMessagesProps = {
|
||||||
messages: ChatMessage[];
|
messages: ChatMessage[];
|
||||||
activeChannelId: string;
|
activeChannelId: string;
|
||||||
|
@ -19,9 +75,10 @@ type ChatMessagesProps = {
|
||||||
|
|
||||||
export function ChatMessages({ messages, activeChannelId }: ChatMessagesProps) {
|
export function ChatMessages({ messages, activeChannelId }: ChatMessagesProps) {
|
||||||
const { loadPrevDay, loadingMessages } = useMessengerContext();
|
const { loadPrevDay, loadingMessages } = useMessengerContext();
|
||||||
|
|
||||||
const [scrollOnBot, setScrollOnBot] = useState(true);
|
const [scrollOnBot, setScrollOnBot] = useState(true);
|
||||||
const ref = useRef<HTMLHeadingElement>(null);
|
const ref = useRef<HTMLHeadingElement>(null);
|
||||||
const today = useMemo(() => new Date(), []);
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (ref && ref.current && scrollOnBot) {
|
if (ref && ref.current && scrollOnBot) {
|
||||||
ref.current.scrollTop = ref.current.scrollHeight;
|
ref.current.scrollTop = ref.current.scrollHeight;
|
||||||
|
@ -39,6 +96,12 @@ export function ChatMessages({ messages, activeChannelId }: ChatMessagesProps) {
|
||||||
}
|
}
|
||||||
}, [messages, messages.length, loadingMessages]);
|
}, [messages, messages.length, loadingMessages]);
|
||||||
|
|
||||||
|
const { blockedUsers } = useBlockedUsers();
|
||||||
|
|
||||||
|
const shownMessages = useMemo(() => {
|
||||||
|
return messages.filter((message) => !blockedUsers.includes(message.sender));
|
||||||
|
}, [messages, blockedUsers, messages.length]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const setScroll = () => {
|
const setScroll = () => {
|
||||||
if (ref && ref.current) {
|
if (ref && ref.current) {
|
||||||
|
@ -76,39 +139,16 @@ export function ChatMessages({ messages, activeChannelId }: ChatMessagesProps) {
|
||||||
<LoadingIcon className="message" />
|
<LoadingIcon className="message" />
|
||||||
</LoadingWrapper>
|
</LoadingWrapper>
|
||||||
)}
|
)}
|
||||||
{messages.map((message, idx) => {
|
{shownMessages.map((message, idx) => {
|
||||||
return (
|
return (
|
||||||
<MessageOuterWrapper key={message.date.getTime()}>
|
<ChatUiMessage
|
||||||
{(idx === 0 ||
|
key={message.date.getTime()}
|
||||||
!equalDate(messages[idx - 1].date, message.date)) && (
|
message={message}
|
||||||
<DateSeparator>
|
idx={idx}
|
||||||
{equalDate(message.date, today)
|
prevMessage={shownMessages[idx - 1]}
|
||||||
? "Today"
|
setLink={setLink}
|
||||||
: message.date.toLocaleDateString()}
|
setImage={setImage}
|
||||||
</DateSeparator>
|
/>
|
||||||
)}
|
|
||||||
<MessageWrapper>
|
|
||||||
<Icon>
|
|
||||||
<UserIcon />
|
|
||||||
</Icon>
|
|
||||||
|
|
||||||
<ContentWrapper>
|
|
||||||
<MessageHeaderWrapper>
|
|
||||||
<UserNameWrapper>
|
|
||||||
{message.sender.slice(0, 10)}
|
|
||||||
</UserNameWrapper>
|
|
||||||
<TimeWrapper>{message.date.toLocaleString()}</TimeWrapper>
|
|
||||||
</MessageHeaderWrapper>
|
|
||||||
<MessageText>
|
|
||||||
<ChatMessageContent
|
|
||||||
message={message}
|
|
||||||
setImage={setImage}
|
|
||||||
setLinkOpen={setLink}
|
|
||||||
/>
|
|
||||||
</MessageText>
|
|
||||||
</ContentWrapper>
|
|
||||||
</MessageWrapper>
|
|
||||||
</MessageOuterWrapper>
|
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</MessagesWrapper>
|
</MessagesWrapper>
|
||||||
|
@ -178,6 +218,7 @@ export const Icon = styled.div`
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
background-color: #bcbdff;
|
background-color: #bcbdff;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
|
position: relative;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const UserNameWrapper = styled.div`
|
const UserNameWrapper = styled.div`
|
||||||
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
import React, { useMemo } from "react";
|
||||||
|
import styled from "styled-components";
|
||||||
|
|
||||||
|
import { useBlockedUsers } from "../../contexts/blockedUsersProvider";
|
||||||
|
|
||||||
|
import { DropdownMenu, MenuItem } from "./DropdownMenu";
|
||||||
|
|
||||||
|
type ContactMenuProps = {
|
||||||
|
id: string;
|
||||||
|
setShowMenu: (val: boolean) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function ContactMenu({ id, setShowMenu }: ContactMenuProps) {
|
||||||
|
const { blockedUsers, setBlockedUsers } = useBlockedUsers();
|
||||||
|
const userInBlocked = useMemo(
|
||||||
|
() => blockedUsers.includes(id),
|
||||||
|
[blockedUsers, id]
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<ContactDropdown>
|
||||||
|
<MenuItem
|
||||||
|
onClick={() => {
|
||||||
|
userInBlocked
|
||||||
|
? setBlockedUsers((prev) => prev.filter((e) => e != id))
|
||||||
|
: setBlockedUsers((prev) => [...prev, id]);
|
||||||
|
setShowMenu(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{userInBlocked ? "Unblock user" : "Block user"}
|
||||||
|
</MenuItem>
|
||||||
|
</ContactDropdown>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const ContactDropdown = styled(DropdownMenu)`
|
||||||
|
top: 20px;
|
||||||
|
left: 0px;
|
||||||
|
`;
|
|
@ -1,8 +1,9 @@
|
||||||
import React, { useCallback } from "react";
|
import React, { useCallback, useState } from "react";
|
||||||
import styled from "styled-components";
|
import styled from "styled-components";
|
||||||
|
|
||||||
import { useNarrow } from "../../contexts/narrowProvider";
|
import { useNarrow } from "../../contexts/narrowProvider";
|
||||||
import { Icon } from "../Chat/ChatMessages";
|
import { Icon } from "../Chat/ChatMessages";
|
||||||
|
import { ContactMenu } from "../Form/ContactMenu";
|
||||||
import { UserIcon } from "../Icons/UserIcon";
|
import { UserIcon } from "../Icons/UserIcon";
|
||||||
|
|
||||||
interface MemberProps {
|
interface MemberProps {
|
||||||
|
@ -38,6 +39,8 @@ export function Member({
|
||||||
setShowChannels(true);
|
setShowChannels(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const [showMenu, setShowMenu] = useState(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MemberData
|
<MemberData
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
@ -49,7 +52,9 @@ export function Member({
|
||||||
backgroundImage: "unset",
|
backgroundImage: "unset",
|
||||||
}}
|
}}
|
||||||
className={isOnline ? "online" : "offline"}
|
className={isOnline ? "online" : "offline"}
|
||||||
|
onClick={() => setShowMenu((e) => !e)}
|
||||||
>
|
>
|
||||||
|
{showMenu && <ContactMenu id={member} setShowMenu={setShowMenu} />}
|
||||||
<UserIcon memberView={true} />
|
<UserIcon memberView={true} />
|
||||||
</MemberIcon>
|
</MemberIcon>
|
||||||
<MemberName>{member}</MemberName>
|
<MemberName>{member}</MemberName>
|
||||||
|
|
|
@ -2,6 +2,7 @@ import React, { useRef } from "react";
|
||||||
import { ThemeProvider } from "styled-components";
|
import { ThemeProvider } from "styled-components";
|
||||||
import styled from "styled-components";
|
import styled from "styled-components";
|
||||||
|
|
||||||
|
import { BlockedUsersProvider } from "../contexts/blockedUsersProvider";
|
||||||
import { FetchMetadataProvider } from "../contexts/fetchMetadataProvider";
|
import { FetchMetadataProvider } from "../contexts/fetchMetadataProvider";
|
||||||
import { NarrowProvider } from "../contexts/narrowProvider";
|
import { NarrowProvider } from "../contexts/narrowProvider";
|
||||||
import { Metadata } from "../models/Metadata";
|
import { Metadata } from "../models/Metadata";
|
||||||
|
@ -26,10 +27,12 @@ export function ReactChat({
|
||||||
<ThemeProvider theme={theme}>
|
<ThemeProvider theme={theme}>
|
||||||
<NarrowProvider myRef={ref}>
|
<NarrowProvider myRef={ref}>
|
||||||
<FetchMetadataProvider fetchMetadata={fetchMetadata}>
|
<FetchMetadataProvider fetchMetadata={fetchMetadata}>
|
||||||
<Wrapper ref={ref}>
|
<BlockedUsersProvider>
|
||||||
<GlobalStyle />
|
<Wrapper ref={ref}>
|
||||||
<ChatLoader communityKey={communityKey} />
|
<GlobalStyle />
|
||||||
</Wrapper>
|
<ChatLoader communityKey={communityKey} />
|
||||||
|
</Wrapper>
|
||||||
|
</BlockedUsersProvider>
|
||||||
</FetchMetadataProvider>
|
</FetchMetadataProvider>
|
||||||
</NarrowProvider>
|
</NarrowProvider>
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
import React, { createContext, useContext, useState } from "react";
|
||||||
|
|
||||||
|
const BlockedUsersContext = createContext<{
|
||||||
|
blockedUsers: string[];
|
||||||
|
setBlockedUsers: React.Dispatch<React.SetStateAction<string[]>>;
|
||||||
|
}>({
|
||||||
|
blockedUsers: [],
|
||||||
|
setBlockedUsers: () => undefined,
|
||||||
|
});
|
||||||
|
|
||||||
|
export function useBlockedUsers() {
|
||||||
|
return useContext(BlockedUsersContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface BlockedUsersProviderProps {
|
||||||
|
children: React.ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function BlockedUsersProvider({ children }: BlockedUsersProviderProps) {
|
||||||
|
const [blockedUsers, setBlockedUsers] = useState<string[]>([]);
|
||||||
|
return (
|
||||||
|
<BlockedUsersContext.Provider
|
||||||
|
value={{ blockedUsers, setBlockedUsers }}
|
||||||
|
children={children}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
Loading…
Reference in New Issue