Add user blocking (#110)

This commit is contained in:
Szymon Szlachtowicz 2021-11-03 22:18:49 +01:00 committed by GitHub
parent 2714e8e9dd
commit c7626f5deb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 152 additions and 38 deletions

View File

@ -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`

View File

@ -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;
`;

View File

@ -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>

View File

@ -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>

View File

@ -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}
/>
);
}