From 2714e8e9dd1a86f778defd6504990ece3515d0e0 Mon Sep 17 00:00:00 2001 From: Maria Rushkova <66270386+mrushkova@users.noreply.github.com> Date: Wed, 3 Nov 2021 16:56:27 +0100 Subject: [PATCH] Add group menu (#107) --- .../src/components/Channels/Channel.tsx | 2 +- .../src/components/Channels/Channels.tsx | 26 ++- .../src/components/Channels/EmptyChannel.tsx | 10 +- packages/react-chat/src/components/Chat.tsx | 15 ++ .../src/components/Chat/ChatBody.tsx | 119 ++++++----- .../src/components/Chat/ChatCreation.tsx | 37 +++- .../src/components/Form/ChannelMenu.tsx | 41 +++- .../src/components/Icons/AddIcon.tsx | 22 +++ .../src/components/Icons/AddMemberIcon.tsx | 49 +++++ .../src/components/Icons/EditGroupIcon.tsx | 41 ++++ .../src/components/Icons/LeftIcon.tsx | 37 ++++ .../src/components/Modals/CommunityModal.tsx | 31 +-- .../src/components/Modals/EditModal.tsx | 185 ++++++++++++++++++ .../src/components/Modals/LinkModal.tsx | 18 +- .../src/components/Modals/ModalStyle.ts | 24 +++ .../components/NarrowMode/NarrowChannels.tsx | 3 + 16 files changed, 560 insertions(+), 100 deletions(-) create mode 100644 packages/react-chat/src/components/Icons/AddIcon.tsx create mode 100644 packages/react-chat/src/components/Icons/AddMemberIcon.tsx create mode 100644 packages/react-chat/src/components/Icons/EditGroupIcon.tsx create mode 100644 packages/react-chat/src/components/Icons/LeftIcon.tsx create mode 100644 packages/react-chat/src/components/Modals/EditModal.tsx create mode 100644 packages/react-chat/src/components/Modals/ModalStyle.ts diff --git a/packages/react-chat/src/components/Channels/Channel.tsx b/packages/react-chat/src/components/Channels/Channel.tsx index 114df1b..f312aed 100644 --- a/packages/react-chat/src/components/Channels/Channel.tsx +++ b/packages/react-chat/src/components/Channels/Channel.tsx @@ -66,7 +66,7 @@ export function Channel({ ) : ( "#" )}{" "} - {channel.name} + {channel.name.slice(0, 10)} {activeView && ( {channel.description} diff --git a/packages/react-chat/src/components/Channels/Channels.tsx b/packages/react-chat/src/components/Channels/Channels.tsx index b180200..a4b1c74 100644 --- a/packages/react-chat/src/components/Channels/Channels.tsx +++ b/packages/react-chat/src/components/Channels/Channels.tsx @@ -12,6 +12,7 @@ interface ChannelsProps { activeChannelId: string; channels: ChannelData[]; membersList: string[]; + groupList: string[]; setCreateChat: (val: boolean) => void; } @@ -20,6 +21,7 @@ export function Channels({ activeChannelId, channels, membersList, + groupList, setCreateChat, }: ChannelsProps) { const { clearNotifications, notifications } = useMessengerContext(); @@ -61,6 +63,28 @@ export function Channels({ + {groupList.length > 0 && + groupList.map((group) => ( + { + onCommunityClick({ + id: group, + name: group.slice(0, 10), + description: "Contact", + }); + setCreateChat(false); + }} + /> + ))} {membersList.length > 0 && membersList.map((member) => ( - {channel.name} + + {channel.name.slice(0, 10)} + {channel.type === "dm" ? ( Any messages you send here are encrypted and can only be read by you - and {channel.name}. + and {channel.name.slice(0, 10)}. ) : channel.type === "group" ? ( @@ -70,6 +72,9 @@ const ChannelNameEmpty = styled(ChannelName)` font-size: 22px; line-height: 30px; margin-bottom: 16px; + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; `; const EmptyText = styled.p` @@ -77,6 +82,7 @@ const EmptyText = styled.p` color: ${({ theme }) => theme.secondary}; max-width: 310px; text-align: center; + word-break: break-all; & > span { color: ${({ theme }) => theme.primary}; diff --git a/packages/react-chat/src/components/Chat.tsx b/packages/react-chat/src/components/Chat.tsx index 8f1f1b7..43a35fb 100644 --- a/packages/react-chat/src/components/Chat.tsx +++ b/packages/react-chat/src/components/Chat.tsx @@ -13,6 +13,7 @@ import { ChatCreation } from "./Chat/ChatCreation"; import { Community } from "./Community"; import { Members } from "./Members/Members"; import { CommunityModal } from "./Modals/CommunityModal"; +import { EditModal } from "./Modals/EditModal"; import { CommunitySkeleton } from "./Skeleton/CommunitySkeleton"; interface ChatProps { @@ -31,6 +32,7 @@ export function Chat({ const [showMembers, setShowMembers] = useState(true); const [showChannels, setShowChannels] = useState(true); const [membersList, setMembersList] = useState([]); + const [groupList, setGroupList] = useState([]); const [createChat, setCreateChat] = useState(false); const narrow = useNarrow(); @@ -38,6 +40,9 @@ export function Chat({ const [isModalVisible, setIsModalVisible] = useState(false); const showModal = () => setIsModalVisible(true); + const [isEditVisible, setIsEditVisible] = useState(false); + const showEditModal = () => setIsEditVisible(true); + const { community } = useMessengerContext(); const communityData = useMemo(() => { @@ -90,6 +95,7 @@ export function Chat({ activeChannelId={activeChannel?.id ?? ""} channels={channels} membersList={membersList} + groupList={groupList} setCreateChat={setCreateChat} /> @@ -106,9 +112,12 @@ export function Chat({ community={communityData} showCommunity={!showChannels} onCommunityClick={showModal} + onEditClick={showEditModal} channels={channels} membersList={membersList} + groupList={groupList} setMembersList={setMembersList} + setGroupList={setGroupList} setCreateChat={setCreateChat} /> )} @@ -123,6 +132,7 @@ export function Chat({ @@ -138,6 +148,11 @@ export function Chat({ publicKey={communityKey} /> )} + setIsEditVisible(false)} + channel={activeChannel} + /> ); } diff --git a/packages/react-chat/src/components/Chat/ChatBody.tsx b/packages/react-chat/src/components/Chat/ChatBody.tsx index 470c3e7..e187b43 100644 --- a/packages/react-chat/src/components/Chat/ChatBody.tsx +++ b/packages/react-chat/src/components/Chat/ChatBody.tsx @@ -18,6 +18,7 @@ import { CommunitySkeleton } from "../Skeleton/CommunitySkeleton"; import { Loading } from "../Skeleton/Loading"; import { LoadingSkeleton } from "../Skeleton/LoadingSkeleton"; +import { ChatCreation } from "./ChatCreation"; import { ChatInput } from "./ChatInput"; import { ChatMessages } from "./ChatMessages"; @@ -31,9 +32,12 @@ interface ChatBodyProps { setActiveChannel: (val: ChannelData) => void; activeChannelId: string; onCommunityClick: () => void; + onEditClick: () => void; channels: ChannelData[]; membersList: string[]; + groupList: string[]; setMembersList: any; + setGroupList: any; setCreateChat: (val: boolean) => void; } @@ -47,9 +51,12 @@ export function ChatBody({ setActiveChannel, activeChannelId, onCommunityClick, + onEditClick, channels, membersList, + groupList, setMembersList, + setGroupList, setCreateChat, }: ChatBodyProps) { const { messenger, messages } = useMessengerContext(); @@ -57,6 +64,7 @@ export function ChatBody({ const [showChannelsList, setShowChannelsList] = useState(false); const [showMembersList, setShowMembersList] = useState(false); const [showChannelMenu, setShowChannelMenu] = useState(false); + const [editGroup, setEditGroup] = useState(false); const className = useMemo(() => (narrow ? "narrow" : ""), [narrow]); const switchChannelList = useCallback(() => { @@ -78,55 +86,71 @@ export function ChatBody({ return ( - - - {messenger && community ? ( - <> - {(showCommunity || narrow) && ( - - - - )} - (narrow ? switchChannelList() : undefined)} - /> - - ) : ( - - )} - + {editGroup && community ? ( + + ) : ( + + + {messenger && community ? ( + <> + {(showCommunity || narrow) && ( + + + + )} + (narrow ? switchChannelList() : undefined)} + /> + + ) : ( + + )} + - - {!narrow && ( - - - + + {!narrow && ( + + + + )} + setShowChannelMenu(!showChannelMenu)}> + + + + {!community && } + {showChannelMenu && ( + )} - setShowChannelMenu(!showChannelMenu)}> - - - - {!community && } - {showChannelMenu && ( - - )} - + + )} {messenger && community ? ( <> {!showChannelsList && !showMembersList && ( @@ -155,6 +179,7 @@ export function ChatBody({ setShowChannels={setShowChannelsList} activeChannelId={activeChannelId} membersList={membersList} + groupList={groupList} setCreateChat={setCreateChat} /> )} diff --git a/packages/react-chat/src/components/Chat/ChatCreation.tsx b/packages/react-chat/src/components/Chat/ChatCreation.tsx index f7e5dfe..d0a5e97 100644 --- a/packages/react-chat/src/components/Chat/ChatCreation.tsx +++ b/packages/react-chat/src/components/Chat/ChatCreation.tsx @@ -11,15 +11,19 @@ import { textMediumStyles } from "../Text"; interface ChatCreationProps { community: CommunityData; setMembersList: any; + setGroupList: any; setActiveChannel: (val: ChannelData) => void; setCreateChat: (val: boolean) => void; + editGroup?: boolean; } export function ChatCreation({ community, setMembersList, + setGroupList, setActiveChannel, setCreateChat, + editGroup, }: ChatCreationProps) { const [query, setQuery] = useState(""); const [styledGroup, setStyledGroup] = useState([]); @@ -40,12 +44,27 @@ export function ChatCreation({ }; const createChat = (group: string[]) => { - setMembersList(group); - setActiveChannel({ - id: group.join(""), - name: group.join(", "), - type: "dm", - }); + group.length > 1 + ? (setGroupList((prevGroups: string[]) => { + [...prevGroups, group]; + }), + setActiveChannel({ + id: group.join(""), + name: group.join(", "), + type: "group", + })) + : (setMembersList((prevMembers: string[]) => { + if (prevMembers.find((chat) => chat === group[0])) { + return prevMembers; + } else { + return [...prevMembers, group[0]]; + } + }), + setActiveChannel({ + id: group[0], + name: group[0], + type: "dm", + })); setCreateChat(false); }; @@ -90,12 +109,14 @@ export function ChatCreation({ createChat(styledGroup)} + onClick={() => + editGroup ? setMembersList(styledGroup) : createChat(styledGroup) + } > Confirm - {!query && styledGroup.length === 0 && ( + {!editGroup && !query && styledGroup.length === 0 && ( Contacts diff --git a/packages/react-chat/src/components/Form/ChannelMenu.tsx b/packages/react-chat/src/components/Form/ChannelMenu.tsx index bfd1d3c..b959f2a 100644 --- a/packages/react-chat/src/components/Form/ChannelMenu.tsx +++ b/packages/react-chat/src/components/Form/ChannelMenu.tsx @@ -1,11 +1,15 @@ import React from "react"; +import styled from "styled-components"; import { useMessengerContext } from "../../contexts/messengerProvider"; import { useNarrow } from "../../contexts/narrowProvider"; import { ChannelData } from "../../models/ChannelData"; import { ChatMessage } from "../../models/ChatMessage"; +import { AddMemberIconSvg } from "../Icons/AddMemberIcon"; import { CheckSvg } from "../Icons/CheckIcon"; import { ClearSvg } from "../Icons/ClearIcon"; +import { EgitGroupSvg } from "../Icons/EditGroupIcon"; +import { LeftIconSvg } from "../Icons/LeftIcon"; import { MembersSmallSvg } from "../Icons/MembersSmallIcon"; import { MuteSvg } from "../Icons/MuteIcon"; @@ -16,6 +20,8 @@ interface ChannelMenuProps { messages: ChatMessage[]; switchMemberList: () => void; setShowChannelMenu: (val: boolean) => void; + setEditGroup: (val: boolean) => void; + onEditClick: () => void; } export const ChannelMenu = ({ @@ -23,6 +29,8 @@ export const ChannelMenu = ({ messages, switchMemberList, setShowChannelMenu, + setEditGroup, + onEditClick, }: ChannelMenuProps) => { const narrow = useNarrow(); const { clearNotifications } = useMessengerContext(); @@ -40,23 +48,50 @@ export const ChannelMenu = ({ View Members )} + {channel.type === "group" && ( + <> + setEditGroup(true)}> + + Add / remove from group + + + + Edit name and image + + + )} { channel.isMuted = true; setShowChannelMenu(false); }} > - + Mute Chat clearNotifications(channel.id)}> - + Mark as Read messages.length === 0}> - + Clear History + {channel.type === "group" && ( + + {" "} + channel}> + + Leave Group + + + )} ); }; + +const MenuSection = styled.div` + margin-top: 5px; + padding-top: 5px; + border-top: 1px solid ${({ theme }) => theme.inputColor}; +`; diff --git a/packages/react-chat/src/components/Icons/AddIcon.tsx b/packages/react-chat/src/components/Icons/AddIcon.tsx new file mode 100644 index 0000000..ad16400 --- /dev/null +++ b/packages/react-chat/src/components/Icons/AddIcon.tsx @@ -0,0 +1,22 @@ +import React from "react"; +import styled from "styled-components"; + +export const AddIcon = () => { + return ( + + + + ); +}; + +const Icon = styled.svg` + & > path { + fill: ${({ theme }) => theme.bodyBackgroundColor}; + } +`; diff --git a/packages/react-chat/src/components/Icons/AddMemberIcon.tsx b/packages/react-chat/src/components/Icons/AddMemberIcon.tsx new file mode 100644 index 0000000..09bc54b --- /dev/null +++ b/packages/react-chat/src/components/Icons/AddMemberIcon.tsx @@ -0,0 +1,49 @@ +import React from "react"; +import styled from "styled-components"; + +type AddMemberIconSvgProps = { + width: number; + height: number; + className?: string; +}; + +export function AddMemberIconSvg({ + width, + height, + className, +}: AddMemberIconSvgProps) { + return ( + + + + + + + + ); +} + +export const AddMemberIcon = () => { + return ; +}; + +const Icon = styled(AddMemberIconSvg)` + & > path { + fill: ${({ theme }) => theme.tertiary}; + } + + &:hover > path { + fill: ${({ theme }) => theme.bodyBackgroundColor}; + } +`; diff --git a/packages/react-chat/src/components/Icons/EditGroupIcon.tsx b/packages/react-chat/src/components/Icons/EditGroupIcon.tsx new file mode 100644 index 0000000..012a9da --- /dev/null +++ b/packages/react-chat/src/components/Icons/EditGroupIcon.tsx @@ -0,0 +1,41 @@ +import React from "react"; +import styled from "styled-components"; + +type EgitGroupSvgProps = { + width: number; + height: number; + className?: string; +}; + +export function EgitGroupSvg({ width, height, className }: EgitGroupSvgProps) { + return ( + + + + ); +} + +export const EgitGroupIcon = () => { + return ; +}; + +const Icon = styled(EgitGroupSvg)` + & > path { + fill: ${({ theme }) => theme.tertiary}; + } + + &:hover > path { + fill: ${({ theme }) => theme.bodyBackgroundColor}; + } +`; diff --git a/packages/react-chat/src/components/Icons/LeftIcon.tsx b/packages/react-chat/src/components/Icons/LeftIcon.tsx new file mode 100644 index 0000000..869a26a --- /dev/null +++ b/packages/react-chat/src/components/Icons/LeftIcon.tsx @@ -0,0 +1,37 @@ +import React from "react"; +import styled from "styled-components"; + +type LeftIconSvgProps = { + width: number; + height: number; + className?: string; +}; + +export function LeftIconSvg({ width, height, className }: LeftIconSvgProps) { + return ( + + + + ); +} + +export const LeftIconIcon = () => { + return ; +}; + +const Icon = styled(LeftIconSvg)` + & > path { + fill: ${({ theme }) => theme.tertiary}; + } + + &:hover > path { + fill: ${({ theme }) => theme.bodyBackgroundColor}; + } +`; diff --git a/packages/react-chat/src/components/Modals/CommunityModal.tsx b/packages/react-chat/src/components/Modals/CommunityModal.tsx index a2c359b..9bf1fec 100644 --- a/packages/react-chat/src/components/Modals/CommunityModal.tsx +++ b/packages/react-chat/src/components/Modals/CommunityModal.tsx @@ -9,9 +9,10 @@ import { } from "../CommunityIdentity"; import { CopyInput } from "../Form/CopyInput"; import { StatusLogo } from "../Icons/StatusLogo"; -import { textMediumStyles, textSmallStyles } from "../Text"; +import { textSmallStyles } from "../Text"; import { BasicModalProps, Modal } from "./Modal"; +import { Section, Text } from "./ModalStyle"; interface CommunityModalProps extends BasicModalProps, CommunityIdentityProps { description: string; @@ -55,27 +56,6 @@ export const CommunityModal = ({ ); }; -const Section = styled.div` - padding: 20px 16px; - - & + & { - border-top: 1px solid ${({ theme }) => theme.border}; - } -`; - -const Text = styled.p` - color: ${({ theme }) => theme.primary}; - - ${textMediumStyles} -`; - -const Hint = styled.p` - margin-top: 16px; - color: ${({ theme }) => theme.secondary}; - - ${textSmallStyles} -`; - const BottomSection = styled(Section)` display: flex; flex-direction: column; @@ -92,3 +72,10 @@ const StyledDownloadButton = styled(DownloadButton)` text-decoration: underline; color: ${({ theme }) => theme.secondary}; `; + +const Hint = styled.p` + margin-top: 16px; + color: ${({ theme }) => theme.secondary}; + + ${textSmallStyles} +`; diff --git a/packages/react-chat/src/components/Modals/EditModal.tsx b/packages/react-chat/src/components/Modals/EditModal.tsx new file mode 100644 index 0000000..ac0b7be --- /dev/null +++ b/packages/react-chat/src/components/Modals/EditModal.tsx @@ -0,0 +1,185 @@ +import React, { useState } from "react"; +import styled from "styled-components"; + +import { ChannelData } from "../../models/ChannelData"; +import { uintToImgUrl } from "../../utils/uintToImgUrl"; +import { buttonStyles } from "../Buttons/buttonStyle"; +import { ChannelLogo } from "../Channels/Channel"; +import { AddIcon } from "../Icons/AddIcon"; +import { textMediumStyles } from "../Text"; + +import { BasicModalProps, Modal } from "./Modal"; +import { Heading, Section } from "./ModalStyle"; + +interface EditModalProps extends BasicModalProps { + channel: ChannelData; +} + +export const EditModal = ({ isVisible, onClose, channel }: EditModalProps) => { + const [groupName, setGroupName] = useState(""); + const [imageUint, setImageUint] = useState(undefined); + const [showSizeLimit, setShowSizeLimit] = useState(false); + + return ( + +
+ Edit group name and image +
+ +
+ + + + {groupName.length}/30 + + + setGroupName(e.currentTarget.value)} + /> + + + + + {!channel.icon && channel.name.slice(0, 1).toUpperCase()} + + + { + const fileReader = new FileReader(); + fileReader.onloadend = (s) => { + const arr = new Uint8Array(s.target?.result as ArrayBuffer); + setImageUint(arr); + }; + + if (e?.target?.files?.[0]) { + if (e.target.files[0].size < 1024 * 1024) { + fileReader.readAsArrayBuffer(e.target.files[0]); + } else { + setShowSizeLimit(true); + } + } + }} + /> + + + {showSizeLimit && } + +
+ + Save changes + +
+ ); +}; + +const NameSection = styled.div` + display: flex; + flex-direction: column; + margin-bottom: 16px; +`; + +const LabelGroup = styled.div` + display: flex; + justify-content: space-between; + align-items: center; +`; + +const Label = styled.p` + color: ${({ theme }) => theme.primary}; + padding: 10px 0; + + ${textMediumStyles} +`; + +const Hint = styled.p` + color: ${({ theme }) => theme.secondary}; + font-size: 12px; + line-height: 16px; +`; + +const NameInput = styled.input` + padding: 14px 70px 14px 8px; + background: ${({ theme }) => theme.inputColor}; + border-radius: 8px; + border: 1px solid ${({ theme }) => theme.inputColor}; + color: ${({ theme }) => theme.primary}; + outline: none; + + ${textMediumStyles} + + &:focus { + outline: none; + caret-color: ${({ theme }) => theme.notificationColor}; + } +`; + +const LogoSection = styled.div` + display: flex; + flex-direction: column; + justify-content: center; + margin-bottom: 8px; +`; + +const GroupLogo = styled(ChannelLogo)` + width: 128px; + height: 128px; + font-weight: bold; + font-size: 80px; + position: relative; + align-self: center; + margin-right: 0; +`; + +const AddPictureInputWrapper = styled.div` + display: flex; + justify-content: center; + align-items: center; + position: absolute; + top: 0; + right: 8px; + width: 40px; + height: 40px; + background: ${({ theme }) => theme.tertiary}; + border-radius: 50%; +`; + +const AddPictureInput = styled.input` + position: absolute; + top: 0; + right: 0; + width: 40px; + height: 40px; + opacity: 0; + z-index: 2; + cursor: pointer; +`; + +const ButtonSection = styled(Section)` + display: flex; + justify-content: flex-end; +`; + +const SaveBtn = styled.button` + padding: 11px 24px; + + ${buttonStyles} + + &:hover { + background: ${({ theme }) => theme.buttonBgHover}; + } +`; diff --git a/packages/react-chat/src/components/Modals/LinkModal.tsx b/packages/react-chat/src/components/Modals/LinkModal.tsx index 0b72247..9eb2f42 100644 --- a/packages/react-chat/src/components/Modals/LinkModal.tsx +++ b/packages/react-chat/src/components/Modals/LinkModal.tsx @@ -5,6 +5,7 @@ import { buttonStyles } from "../Buttons/buttonStyle"; import { textMediumStyles } from "../Text"; import { BasicModalProps, Modal } from "./Modal"; +import { Heading, Section } from "./ModalStyle"; interface LinkModalProps extends BasicModalProps { link: string; @@ -14,7 +15,7 @@ export const LinkModal = ({ isVisible, onClose, link }: LinkModalProps) => { return (
- Are you sure you want to visit this website? + Are you sure you want to visit this website?
{link} @@ -34,21 +35,6 @@ export const LinkModal = ({ isVisible, onClose, link }: LinkModalProps) => { ); }; -const Section = styled.div` - padding: 16px; - - & + & { - border-top: 1px solid ${({ theme }) => theme.border}; - } -`; - -const Question = styled.p` - color: ${({ theme }) => theme.primary}; - font-weight: bold; - font-size: 17px; - line-height: 24px; -`; - const Link = styled.a` text-decoration: none; word-break: break-all; diff --git a/packages/react-chat/src/components/Modals/ModalStyle.ts b/packages/react-chat/src/components/Modals/ModalStyle.ts new file mode 100644 index 0000000..421a3ae --- /dev/null +++ b/packages/react-chat/src/components/Modals/ModalStyle.ts @@ -0,0 +1,24 @@ +import styled from "styled-components"; + +import { textMediumStyles } from "../Text"; + +export const Section = styled.div` + padding: 16px; + + & + & { + border-top: 1px solid ${({ theme }) => theme.border}; + } +`; + +export const Heading = styled.p` + color: ${({ theme }) => theme.primary}; + font-weight: bold; + font-size: 17px; + line-height: 24px; +`; + +export const Text = styled.p` + color: ${({ theme }) => theme.primary}; + + ${textMediumStyles} +`; diff --git a/packages/react-chat/src/components/NarrowMode/NarrowChannels.tsx b/packages/react-chat/src/components/NarrowMode/NarrowChannels.tsx index 9c8869d..7341bb0 100644 --- a/packages/react-chat/src/components/NarrowMode/NarrowChannels.tsx +++ b/packages/react-chat/src/components/NarrowMode/NarrowChannels.tsx @@ -13,6 +13,7 @@ interface NarrowChannelsProps { setShowChannels: (val: boolean) => void; channels: ChannelData[]; membersList: string[]; + groupList: string[]; setCreateChat: (val: boolean) => void; } @@ -23,6 +24,7 @@ export function NarrowChannels({ setShowChannels, channels, membersList, + groupList, setCreateChat, }: NarrowChannelsProps) { return ( @@ -36,6 +38,7 @@ export function NarrowChannels({ activeChannelId={activeChannelId} channels={channels} membersList={membersList} + groupList={groupList} setCreateChat={setCreateChat} />