Improve profile modal (#132)

This commit is contained in:
Szymon Szlachtowicz 2021-11-18 16:34:26 +01:00 committed by GitHub
parent 7fb0bfbdc7
commit 81a14fcb9a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 261 additions and 233 deletions

View File

@ -76,7 +76,7 @@ function DragDiv() {
<ReactChat
theme={theme ? lightTheme : darkTheme}
communityKey={
"0x02f04996fd22b82e7b02a2d3bf07fa986c5181cd23190a992187d9c887011d4a8e"
"0x0229d71fb62c14ec0370d2507ef7e13f1d53ddf1170db3babb5710fd113d0d94f9"
}
fetchMetadata={fetchMetadata}
/>

View File

@ -10,6 +10,7 @@ import { Community } from "./Community";
import { Members } from "./Members/Members";
import { CommunityModal } from "./Modals/CommunityModal";
import { EditModal } from "./Modals/EditModal";
import { ProfileModal } from "./Modals/ProfileModal";
export function Chat() {
const [showMembers, setShowMembers] = useState(true);
@ -55,6 +56,7 @@ export function Chat() {
)}
<CommunityModal subtitle="Public Community" />
<EditModal />
<ProfileModal />
</ChatWrapper>
);
}

View File

@ -123,7 +123,7 @@ export function ChatCreation({
<Contacts>
<ContactsHeading>Contacts</ContactsHeading>
<ContactsList>
{contacts
{Object.values(contacts)
.filter(
(e) =>
e.id != bufToHex(identity.publicKey) &&
@ -132,7 +132,7 @@ export function ChatCreation({
.map((contact) => (
<Member
key={contact.id}
member={contact.id}
contact={contact}
isOnline={contact.online}
onClick={() => addMember(contact.id)}
/>

View File

@ -3,10 +3,29 @@ import React, { useEffect, useMemo, useState } from "react";
import styled from "styled-components";
import { useFetchMetadata } from "../../contexts/fetchMetadataProvider";
import { useMessengerContext } from "../../contexts/messengerProvider";
import { ChatMessage } from "../../models/ChatMessage";
import { Metadata } from "../../models/Metadata";
import { ContactMenu } from "../Form/ContactMenu";
import { ImageMenu } from "../Form/ImageMenu";
interface MentionProps {
id: string;
}
function Mention({ id }: MentionProps) {
const { contacts } = useMessengerContext();
const contact = useMemo(() => contacts[id.slice(1)], [id, contacts]);
const [showMenu, setShowMenu] = useState(false);
if (!contact) return <>{id}</>;
return (
<MentionSpan onClick={() => setShowMenu(!showMenu)}>
{`@${contact.customName ?? contact.id}`}
{showMenu && <ContactMenu id={id.slice(1)} setShowMenu={setShowMenu} />}
</MentionSpan>
);
}
type ChatMessageContentProps = {
message: ChatMessage;
setImage: (image: string) => void;
@ -40,7 +59,7 @@ export function ChatMessageContent({
];
}
if (element.startsWith("@")) {
return [<Mention key={idx}>{element}</Mention>, " "];
return [<Mention key={idx} id={element} />, " "];
}
return [element, " "];
});
@ -158,9 +177,10 @@ const ContentWrapper = styled.div`
flex-direction: column;
`;
const Mention = styled.span`
const MentionSpan = styled.span`
color: blue;
font-weight: 500;
position: relative;
`;
const Link = styled.a`

View File

@ -1,7 +1,6 @@
import React, { useEffect, useMemo, useRef, useState } from "react";
import styled from "styled-components";
import { useBlockedUsers } from "../../contexts/blockedUsersProvider";
import { useMessengerContext } from "../../contexts/messengerProvider";
import { useModal } from "../../contexts/modalProvider";
import { useChatScrollHandle } from "../../hooks/useChatScrollHandle";
@ -14,7 +13,6 @@ import { UntrustworthIcon } from "../Icons/UntrustworthIcon";
import { UserIcon } from "../Icons/UserIcon";
import { LinkModal, LinkModalName } from "../Modals/LinkModal";
import { PictureModal, PictureModalName } from "../Modals/PictureModal";
import { ProfileModal } from "../Modals/ProfileModal";
import { textMediumStyles, textSmallStyles } from "../Text";
import { ChatMessageContent } from "./ChatMessageContent";
@ -27,10 +25,6 @@ type ChatUiMessageProps = {
prevMessage: ChatMessage;
setImage: (img: string) => void;
setLink: (link: string) => void;
setUser: (user: string) => void;
customName?: string;
trueName?: string;
setRenaming: (val: boolean) => void;
};
function ChatUiMessage({
@ -39,13 +33,13 @@ function ChatUiMessage({
prevMessage,
setImage,
setLink,
setUser,
customName,
trueName,
setRenaming,
}: ChatUiMessageProps) {
const { contacts } = useMessengerContext();
const contact = useMemo(
() => contacts[message.sender],
[message.sender, contacts]
);
const [showMenu, setShowMenu] = useState(false);
const [isUntrustworthy, setIsUntrustworthy] = useState(false);
return (
<MessageOuterWrapper>
@ -60,19 +54,10 @@ function ChatUiMessage({
<Icon
onClick={() => {
setShowMenu((e) => !e);
setUser(message.sender);
}}
>
{showMenu && (
<ContactMenu
message={message}
setShowMenu={setShowMenu}
isUntrustworthy={isUntrustworthy}
setIsUntrustworthy={setIsUntrustworthy}
customName={customName}
trueName={trueName}
setRenaming={setRenaming}
/>
<ContactMenu id={message.sender} setShowMenu={setShowMenu} />
)}
<UserIcon />
</Icon>
@ -82,14 +67,14 @@ function ChatUiMessage({
<UserNameWrapper>
<UserName>
{" "}
{customName ? customName : message.sender.slice(0, 10)}
{contact.customName ?? message.sender.slice(0, 10)}
</UserName>
{customName && (
{contact.customName && (
<UserAddress>
{message.sender.slice(0, 5)}...{message.sender.slice(-3)}
</UserAddress>
)}
{isUntrustworthy && <UntrustworthIcon />}
{contact.isUntrustworthy && <UntrustworthIcon />}
</UserNameWrapper>
<TimeWrapper>{message.date.toLocaleString()}</TimeWrapper>
</MessageHeaderWrapper>
@ -107,20 +92,20 @@ function ChatUiMessage({
}
export function ChatMessages() {
const { messages, activeChannel } = useMessengerContext();
const { messages, activeChannel, contacts } = useMessengerContext();
const ref = useRef<HTMLHeadingElement>(null);
const loadingMessages = useChatScrollHandle(messages, ref, activeChannel.id);
const { blockedUsers } = useBlockedUsers();
const shownMessages = useMemo(
() => messages.filter((message) => !blockedUsers.includes(message.sender)),
[blockedUsers, messages, messages.length]
() =>
messages.filter(
(message) => !contacts?.[message.sender]?.blocked ?? true
),
[contacts, messages, messages.length]
);
const [image, setImage] = useState("");
const [link, setLink] = useState("");
const [user, setUser] = useState("");
const { setModal: setPictureModal, isVisible: showPictureModal } =
useModal(PictureModalName);
@ -136,23 +121,10 @@ export function ChatMessages() {
);
useEffect(() => (!showLinkModal ? setLink("") : undefined), [showLinkModal]);
const [renaming, setRenaming] = useState(false);
const [customName, setCustomName] = useState("");
const [trueName, setTrueName] = useState("");
return (
<MessagesWrapper ref={ref}>
<PictureModal image={image} />
<LinkModal link={link} />
<ProfileModal
user={user}
renaming={renaming}
setRenaming={setRenaming}
customName={customName}
setCustomName={setCustomName}
trueName={trueName}
setTrueName={setTrueName}
/>
<EmptyChannel channel={activeChannel} />
{loadingMessages && (
<LoadingWrapper>
@ -167,10 +139,6 @@ export function ChatMessages() {
prevMessage={shownMessages[idx - 1]}
setLink={setLink}
setImage={setImage}
setUser={setUser}
customName={customName}
trueName={trueName}
setRenaming={setRenaming}
/>
))}
</MessagesWrapper>

View File

@ -1,11 +1,12 @@
import React, { useMemo } from "react";
import { bufToHex } from "status-communities/dist/cjs/utils";
import styled from "styled-components";
import { useBlockedUsers } from "../../contexts/blockedUsersProvider";
import { useFriends } from "../../contexts/friendsProvider";
import { useIdentity } from "../../contexts/identityProvider";
import { useModal } from "../../contexts/modalProvider";
import { ChatMessage } from "../../models/ChatMessage";
import { Icon, UserAddress } from "../Chat/ChatMessages";
import { useManageContact } from "../../hooks/useManageContact";
import { UserAddress } from "../Chat/ChatMessages";
import { AddContactSvg } from "../Icons/AddContactIcon";
import { BlockSvg } from "../Icons/BlockIcon";
import { ChatSvg } from "../Icons/ChatIcon";
@ -20,63 +21,45 @@ import { textMediumStyles } from "../Text";
import { DropdownMenu, MenuItem, MenuText } from "./DropdownMenu";
type ContactMenuProps = {
message: ChatMessage;
id: string;
setShowMenu: (val: boolean) => void;
isUntrustworthy: boolean;
setIsUntrustworthy: (val: boolean) => void;
customName?: string;
trueName?: string;
setRenaming: (val: boolean) => void;
};
export function ContactMenu({
message,
setShowMenu,
isUntrustworthy,
setIsUntrustworthy,
customName,
trueName,
setRenaming,
}: ContactMenuProps) {
const id = message.sender;
const { blockedUsers, setBlockedUsers } = useBlockedUsers();
const userInBlocked = useMemo(
() => blockedUsers.includes(id),
[blockedUsers, id]
export function ContactMenu({ id, setShowMenu }: ContactMenuProps) {
const identity = useIdentity();
const isUser = useMemo(
() => id === bufToHex(identity.publicKey),
[id, identity]
);
const { setModal } = useModal(ProfileModalName);
const { friends, setFriends } = useFriends();
const userIsFriend = useMemo(() => friends.includes(id), [friends, id]);
const { contact, setBlocked, setIsUntrustworthy } = useManageContact(id);
const { setModal } = useModal(ProfileModalName);
if (!contact) return null;
return (
<ContactDropdown>
<ContactInfo>
{message.image ? (
<Icon
style={{
backgroundImage: `url(${message.image}`,
}}
/>
) : (
<UserIcon />
)}
<UserIcon />
<UserNameWrapper>
<UserName>
{customName ? customName : message.sender.slice(0, 10)}
</UserName>
{isUntrustworthy && <UntrustworthIcon />}
<UserName>{contact.customName ?? id.slice(0, 10)}</UserName>
{contact.isUntrustworthy && <UntrustworthIcon />}
</UserNameWrapper>
{trueName && <UserTrueName>({trueName})</UserTrueName>}
{contact.customName && (
<UserTrueName>({contact.trueName})</UserTrueName>
)}
<UserAddress>
{message.sender.slice(0, 10)}...{message.sender.slice(-3)}
{id.slice(0, 10)}...{id.slice(-3)}
</UserAddress>
</ContactInfo>
<MenuSection>
<MenuItem onClick={() => setModal(true)}>
<MenuItem
onClick={() => {
setModal({ id, renamingState: false });
}}
>
<ProfileSvg width={16} height={16} />
<MenuText>View Profile</MenuText>
</MenuItem>
@ -94,8 +77,7 @@ export function ContactMenu({
)}
<MenuItem
onClick={() => {
setModal(true);
setRenaming(true);
setModal({ id, renamingState: true });
}}
>
<EditSvg width={16} height={16} />
@ -103,30 +85,30 @@ export function ContactMenu({
</MenuItem>
</MenuSection>
<MenuSection>
<MenuItem onClick={() => setIsUntrustworthy(!isUntrustworthy)}>
<MenuItem onClick={() => setIsUntrustworthy(!contact.isUntrustworthy)}>
<WarningSvg
width={16}
height={16}
className={isUntrustworthy ? "" : "red"}
className={contact.isUntrustworthy ? "" : "red"}
/>
<MenuText className={isUntrustworthy ? "" : "red"}>
{isUntrustworthy
<MenuText className={contact.isUntrustworthy ? "" : "red"}>
{contact.isUntrustworthy
? "Remove Untrustworthy Mark"
: "Mark as Untrustworthy"}
</MenuText>
</MenuItem>
{!userIsFriend && (
{!userIsFriend && !isUser && (
<MenuItem
onClick={() => {
userInBlocked
? setBlockedUsers((prev) => prev.filter((e) => e != id))
: setBlockedUsers((prev) => [...prev, id]);
setBlocked(!contact.blocked);
setShowMenu(false);
}}
>
<BlockSvg width={16} height={16} className="red" />
<MenuText className="red">Block User</MenuText>
<MenuText className="red">
{contact.blocked ? "Unblock User" : "Block User"}
</MenuText>
</MenuItem>
)}
</MenuSection>

View File

@ -1,12 +1,13 @@
import React from "react";
import React, { useState } from "react";
import styled from "styled-components";
import { Contact } from "../../models/Contact";
import { Icon } from "../Chat/ChatMessages";
// import { ContactMenu } from '../Form/ContactMenu';
import { ContactMenu } from "../Form/ContactMenu";
import { UserIcon } from "../Icons/UserIcon";
interface MemberProps {
member: string;
contact: Contact;
isOnline?: boolean;
switchShowMembers?: () => void;
setMembersList?: any;
@ -14,7 +15,7 @@ interface MemberProps {
}
export function Member({
member,
contact,
isOnline,
switchShowMembers,
setMembersList,
@ -30,11 +31,11 @@ export function Member({
});
};
// const [showMenu, setShowMenu] = useState(false);
const [showMenu, setShowMenu] = useState(false);
const onMemberClick = () => {
switchShowMembers?.();
startDialog(member);
startDialog(contact.id);
};
return (
@ -44,12 +45,12 @@ export function Member({
backgroundImage: "unset",
}}
className={isOnline ? "online" : "offline"}
// onClick={() => setShowMenu(e => !e)}
onClick={() => setShowMenu((e) => !e)}
>
{/* {showMenu && <ContactMenu id={member} setShowMenu={setShowMenu} />} */}
{showMenu && <ContactMenu id={contact.id} setShowMenu={setShowMenu} />}
<UserIcon memberView={true} />
</MemberIcon>
<MemberName>{member}</MemberName>
<MemberName>{contact.customName ?? contact.id}</MemberName>
</MemberData>
);
}

View File

@ -34,13 +34,13 @@ export function MembersList({
</MemberCategory>
<MemberCategory>
<MemberCategoryName>Online</MemberCategoryName>
{contacts
{Object.values(contacts)
.filter((e) => e.id != bufToHex(identity.publicKey))
.filter((e) => e.online)
.map((contact) => (
<Member
key={contact.id}
member={contact.id}
contact={contact}
isOnline={contact.online}
switchShowMembers={switchShowMembers}
setMembersList={setMembersList}
@ -49,13 +49,13 @@ export function MembersList({
</MemberCategory>
<MemberCategory>
<MemberCategoryName>Offline</MemberCategoryName>
{contacts
{Object.values(contacts)
.filter((e) => e.id != bufToHex(identity.publicKey))
.filter((e) => !e.online)
.map((contact) => (
<Member
key={contact.id}
member={contact.id}
contact={contact}
isOnline={contact.online}
switchShowMembers={switchShowMembers}
setMembersList={setMembersList}

View File

@ -3,9 +3,9 @@ import styled from "styled-components";
import { Modal } from "./Modal";
export const PictureModalName = "PictureModal";
export const PictureModalName = "PictureModal" as const;
interface PictureModalProps {
export interface PictureModalProps {
image: string;
}

View File

@ -1,8 +1,11 @@
import React, { useMemo, useState } from "react";
import React, { useEffect, useMemo, useState } from "react";
import { bufToHex } from "status-communities/dist/cjs/utils";
import styled from "styled-components";
import { useBlockedUsers } from "../../contexts/blockedUsersProvider";
import { useFriends } from "../../contexts/friendsProvider";
import { useIdentity } from "../../contexts/identityProvider";
import { useModal } from "../../contexts/modalProvider";
import { useManageContact } from "../../hooks/useManageContact";
import { copy } from "../../utils";
import { buttonStyles } from "../Buttons/buttonStyle";
import { ClearSvg } from "../Icons/ClearIcon";
@ -16,46 +19,45 @@ import { textMediumStyles } from "../Text";
import { Modal } from "./Modal";
import { ButtonSection, Heading, Section } from "./ModalStyle";
export const ProfileModalName = "profileModal";
export const ProfileModalName = "profileModal" as const;
interface ProfileModalProps {
user: string;
export type ProfileModalProps = {
id: string;
image?: string;
renaming: boolean;
customName?: string;
trueName?: string;
setRenaming: (val: boolean) => void;
setTrueName: (val: string) => void;
setCustomName: (val: string) => void;
}
renamingState?: boolean;
};
export const ProfileModal = ({
user,
image,
renaming,
customName,
trueName,
setRenaming,
setTrueName,
setCustomName,
}: ProfileModalProps) => {
const [isUntrustworthy, setIsUntrustworthy] = useState(false);
export const ProfileModal = () => {
const { props } = useModal(ProfileModalName);
const { id, image, renamingState } = useMemo(
() => (props ? props : { id: "" }),
[props]
);
const { blockedUsers, setBlockedUsers } = useBlockedUsers();
const userInBlocked = useMemo(
() => blockedUsers.includes(user),
[blockedUsers, user]
const identity = useIdentity();
const isUser = useMemo(
() => id === bufToHex(identity.publicKey),
[id, identity]
);
const { friends, setFriends } = useFriends();
const userIsFriend = useMemo(() => friends.includes(user), [friends, user]);
const userIsFriend = useMemo(() => friends.includes(id), [friends, id]);
const [renaming, setRenaming] = useState(renamingState ?? false);
useEffect(() => {
setRenaming(renamingState ?? false);
}, [renamingState]);
const { contact, setBlocked, setCustomName, setIsUntrustworthy } =
useManageContact(id);
const [customNameInput, setCustomNameInput] = useState("");
if (!contact) return null;
return (
<Modal name={ProfileModalName} className="profile">
<Section>
<Heading>{user.slice(0, 10)}s Profile</Heading>
<Heading>{id.slice(0, 10)}s Profile</Heading>
</Section>
<ProfileSection>
@ -70,8 +72,8 @@ export const ProfileModal = ({
<UserIcon />
)}
<UserNameWrapper>
<UserName>{customName ? customName : user.slice(0, 10)}</UserName>
{isUntrustworthy && <UntrustworthIcon />}
<UserName>{contact.customName ?? id.slice(0, 10)}</UserName>
{contact.isUntrustworthy && <UntrustworthIcon />}
{!renaming && (
<button onClick={() => setRenaming(true)}>
{" "}
@ -79,21 +81,22 @@ export const ProfileModal = ({
</button>
)}
</UserNameWrapper>
{trueName && <UserTrueName>{trueName}</UserTrueName>}
{trueName && <button onClick={() => setTrueName("")}></button>}
{contact.customName && (
<UserTrueName>{contact.trueName}</UserTrueName>
)}
</NameSection>
{renaming ? (
<NameInputWrapper>
<NameInput
placeholder="Only you will see this nickname"
value={customName}
onChange={(e) => setCustomName(e.currentTarget.value)}
value={contact.customName}
onChange={(e) => setCustomNameInput(e.currentTarget.value)}
/>
{customName && (
{contact.customName && (
<ClearBtn
onClick={() => {
setCustomName("");
setTrueName("");
setCustomName(undefined);
setCustomNameInput("");
}}
>
<ClearSvg width={16} height={16} className="profile" />
@ -103,9 +106,9 @@ export const ProfileModal = ({
) : (
<>
<UserAddressWrapper>
<UserAddress>Chatkey: {user.slice(0, 30)}</UserAddress>
<UserAddress>Chatkey: {id.slice(0, 30)}</UserAddress>
<CopyButton onClick={() => copy(user)}>
<CopyButton onClick={() => copy(id)}>
<CopySvg width={24} height={24} />
</CopyButton>
</UserAddressWrapper>
@ -120,9 +123,9 @@ export const ProfileModal = ({
<LeftIconSvg width={28} height={28} />
</BackBtn>
<Btn
disabled={!customName}
disabled={!customNameInput}
onClick={() => {
setTrueName(user.slice(0, 10));
setCustomName(customNameInput);
setRenaming(false);
}}
>
@ -131,38 +134,36 @@ export const ProfileModal = ({
</>
) : (
<>
{!userIsFriend && (
{!userIsFriend && !isUser && (
<ProfileBtn
className={userInBlocked ? "" : "red"}
className={contact.blocked ? "" : "red"}
onClick={() => {
userInBlocked
? setBlockedUsers((prev) => prev.filter((e) => e != user))
: setBlockedUsers((prev) => [...prev, user]);
setBlocked(!contact.blocked);
}}
>
{userInBlocked ? "Unblock" : "Block"}
{contact.blocked ? "Unblock" : "Block"}
</ProfileBtn>
)}
{userIsFriend && (
<ProfileBtn
className="red"
onClick={() =>
setFriends((prev) => prev.filter((e) => e != user))
setFriends((prev) => prev.filter((e) => e != id))
}
>
Remove Contact
</ProfileBtn>
)}
<ProfileBtn
className={isUntrustworthy ? "" : "red"}
onClick={() => setIsUntrustworthy(!isUntrustworthy)}
className={contact.isUntrustworthy ? "" : "red"}
onClick={() => setIsUntrustworthy(!contact.isUntrustworthy)}
>
{isUntrustworthy
{contact.isUntrustworthy
? "Remove Untrustworthy Mark"
: "Mark as Untrustworthy"}
</ProfileBtn>
{!userIsFriend && (
<Btn onClick={() => setFriends((prev) => [...prev, user])}>
<Btn onClick={() => setFriends((prev) => [...prev, id])}>
Send Contact Request
</Btn>
)}

View File

@ -2,7 +2,6 @@ import React, { useRef } from "react";
import { ThemeProvider } from "styled-components";
import styled from "styled-components";
import { BlockedUsersProvider } from "../contexts/blockedUsersProvider";
import { FetchMetadataProvider } from "../contexts/fetchMetadataProvider";
import { FriendsProvider } from "../contexts/friendsProvider";
import { ModalProvider } from "../contexts/modalProvider";
@ -29,17 +28,15 @@ export function ReactChat({
<ThemeProvider theme={theme}>
<NarrowProvider myRef={ref}>
<FetchMetadataProvider fetchMetadata={fetchMetadata}>
<BlockedUsersProvider>
<FriendsProvider>
<ModalProvider>
<Wrapper ref={ref}>
<GlobalStyle />
<ChatLoader communityKey={communityKey} />
<div id="modal-root" />
</Wrapper>
</ModalProvider>
</FriendsProvider>
</BlockedUsersProvider>
<FriendsProvider>
<ModalProvider>
<Wrapper ref={ref}>
<GlobalStyle />
<ChatLoader communityKey={communityKey} />
<div id="modal-root" />
</Wrapper>
</ModalProvider>
</FriendsProvider>
</FetchMetadataProvider>
</NarrowProvider>
</ThemeProvider>

View File

@ -22,7 +22,7 @@ export const SearchBlock = ({
const { contacts } = useMessengerContext();
const searchList = useMemo(() => {
return contacts
return Object.values(contacts)
.filter((member) => member.id.includes(query))
.filter((member) => !dsicludeList.includes(member.id));
}, [query, dsicludeList, contacts]);
@ -35,7 +35,7 @@ export const SearchBlock = ({
style={{ [onBotttom ? "bottom" : "top"]: "calc(100% + 24px)" }}
>
<ContactsList>
{contacts
{Object.values(contacts)
.filter((member) => member.id.includes(query))
.filter((member) => !dsicludeList.includes(member.id))
.map((member) => (

View File

@ -1,27 +0,0 @@
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}
/>
);
}

View File

@ -14,7 +14,8 @@ const MessengerContext = createContext<MessengerType>({
loadPrevDay: async () => undefined,
loadingMessages: false,
communityData: undefined,
contacts: [],
contacts: {},
setContacts: () => undefined,
activeChannel: {
id: "",
name: "",

View File

@ -6,8 +6,17 @@ import React, {
useState,
} from "react";
type ModalsState = {
[name: string]: boolean;
import {
ProfileModalName,
ProfileModalProps,
} from "../components/Modals/ProfileModal";
type TypeMap = {
[ProfileModalName]?: ProfileModalProps;
};
type ModalsState = TypeMap & {
[name: string]: boolean | undefined;
};
type ModalContextType = [
@ -17,11 +26,18 @@ type ModalContextType = [
const ModalContext = createContext<ModalContextType>([{}, () => undefined]);
export function useModal(name: string) {
export function useModal<T extends string>(name: T) {
const [modals, setModals] = useContext(ModalContext);
const setModal = useCallback(
(state: boolean) => {
(state: T extends keyof TypeMap ? TypeMap[T] | false : boolean) => {
setModals((prev) => {
if (!state) {
return {
...prev,
[name]: undefined,
};
}
return {
...prev,
[name]: state,
@ -30,8 +46,11 @@ export function useModal(name: string) {
},
[name, modals]
);
const isVisible = useMemo(() => modals?.[name] ?? false, [modals, name]);
return { isVisible, setModal };
const isVisible = useMemo(() => !!modals?.[name], [modals, name]);
const props = useMemo(() => modals?.[name], [modals, name]);
return { isVisible, setModal, props };
}
interface IdentityProviderProps {

View File

@ -2,7 +2,7 @@
import { useCallback, useEffect, useMemo, useState } from "react";
import {
Community,
Contacts,
Contacts as ContactsClass,
Identity,
Messenger,
} from "status-communities/dist/cjs";
@ -10,7 +10,7 @@ import {
import { ChannelData } from "../../models/ChannelData";
import { ChatMessage } from "../../models/ChatMessage";
import { CommunityData } from "../../models/CommunityData";
import { Contact } from "../../models/Contact";
import { Contacts } from "../../models/Contact";
import { createCommunity } from "../../utils/createCommunity";
import { createMessenger } from "../../utils/createMessenger";
import { uintToImgUrl } from "../../utils/uintToImgUrl";
@ -32,7 +32,8 @@ export type MessengerType = {
loadPrevDay: (id: string) => Promise<void>;
loadingMessages: boolean;
communityData: CommunityData | undefined;
contacts: Contact[];
contacts: Contacts;
setContacts: React.Dispatch<React.SetStateAction<Contacts>>;
channels: ChannelData[];
activeChannel: ChannelData;
setActiveChannel: (channel: ChannelData) => void;
@ -58,7 +59,7 @@ export function useMessenger(
const contactsClass = useMemo(() => {
if (messenger && identity) {
const newContacts = new Contacts(
const newContacts = new ContactsClass(
identity,
messenger.waku,
(id, clock) => {
@ -71,13 +72,25 @@ export function useMessenger(
}
}, [messenger, identity]);
const contacts = useMemo<Contact[]>(() => {
const [contacts, setContacts] = useState<Contacts>({});
useEffect(() => {
const now = Date.now();
return Object.entries(internalContacts).map(([id, clock]) => {
return {
id,
online: clock > now - 301000,
};
setContacts((prev) => {
const newContacts: Contacts = {};
Object.entries(internalContacts).forEach(([id, clock]) => {
newContacts[id] = {
id,
online: clock > now - 301000,
trueName: id.slice(0, 10),
isUntrustworthy: false,
blocked: false,
};
if (prev[id]) {
newContacts[id] = { ...prev[id], ...newContacts[id] };
}
});
return newContacts;
});
}, [internalContacts]);
@ -185,6 +198,7 @@ export function useMessenger(
loadingMessages,
communityData,
contacts,
setContacts,
channels,
activeChannel,
setActiveChannel,

View File

@ -0,0 +1,42 @@
import { useCallback, useMemo } from "react";
import { useMessengerContext } from "../contexts/messengerProvider";
export function useManageContact(id: string) {
const { contacts, setContacts } = useMessengerContext();
const contact = useMemo(() => contacts[id], [id, contacts]);
const setCustomName = useCallback(
(customName: string | undefined) => {
setContacts((prev) => {
const prevUser = prev[id];
if (!prevUser) return prev;
return { ...prev, [id]: { ...prevUser, customName } };
});
},
[id]
);
const setBlocked = useCallback(
(blocked: boolean) => {
setContacts((prev) => {
const prevUser = prev[id];
if (!prevUser) return prev;
return { ...prev, [id]: { ...prevUser, blocked } };
});
},
[id]
);
const setIsUntrustworthy = useCallback(
(isUntrustworthy: boolean) => {
setContacts((prev) => {
const prevUser = prev[id];
if (!prevUser) return prev;
return { ...prev, [id]: { ...prevUser, isUntrustworthy } };
});
},
[id]
);
return { contact, setCustomName, setBlocked, setIsUntrustworthy };
}

View File

@ -1,4 +1,12 @@
export type Contact = {
id: string;
online: boolean;
trueName: string;
customName?: string;
isUntrustworthy: boolean;
blocked: boolean;
};
export type Contacts = {
[id: string]: Contact;
};

View File

@ -6,7 +6,7 @@ import { StatusUpdate_StatusType } from "./proto/communities/v1/status_update";
import { bufToHex } from "./utils";
import { StatusUpdate } from "./wire/status_update";
const STATUS_BROADCAST_INTERVAL = 300000;
const STATUS_BROADCAST_INTERVAL = 30000;
export class Contacts {
waku: Waku;