Add no community chat (#209)
This commit is contained in:
parent
21e49cb7bf
commit
180b2be276
|
@ -103,11 +103,6 @@ export function ChatCreation({
|
||||||
value={query}
|
value={query}
|
||||||
onInput={(e) => setQuery(e.currentTarget.value)}
|
onInput={(e) => setQuery(e.currentTarget.value)}
|
||||||
/>
|
/>
|
||||||
<SearchBlock
|
|
||||||
query={query}
|
|
||||||
discludeList={styledGroup}
|
|
||||||
onClick={addMember}
|
|
||||||
/>
|
|
||||||
</SearchMembers>
|
</SearchMembers>
|
||||||
)}
|
)}
|
||||||
{!narrow && styledGroup.length === 5 && (
|
{!narrow && styledGroup.length === 5 && (
|
||||||
|
@ -132,12 +127,18 @@ export function ChatCreation({
|
||||||
Confirm
|
Confirm
|
||||||
</CreationBtn>
|
</CreationBtn>
|
||||||
{!narrow && <ActivityButton className="creation" />}
|
{!narrow && <ActivityButton className="creation" />}
|
||||||
|
<SearchBlock
|
||||||
|
query={query}
|
||||||
|
discludeList={styledGroup}
|
||||||
|
onClick={addMember}
|
||||||
|
/>
|
||||||
</CreationBar>
|
</CreationBar>
|
||||||
{!setEditGroup && !query && (
|
{!setEditGroup && (
|
||||||
<Contacts>
|
<Contacts>
|
||||||
<ContactsHeading>Contacts</ContactsHeading>
|
<ContactsHeading>Contacts</ContactsHeading>
|
||||||
<ContactsList>
|
<ContactsList>
|
||||||
{identity &&
|
{identity &&
|
||||||
|
!query &&
|
||||||
Object.values(contacts)
|
Object.values(contacts)
|
||||||
.filter(
|
.filter(
|
||||||
(e) =>
|
(e) =>
|
||||||
|
@ -181,7 +182,7 @@ const CreationBar = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin-bottom: 32px;
|
margin-bottom: 32px;
|
||||||
|
position: relative;
|
||||||
&.limit {
|
&.limit {
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,36 +0,0 @@
|
||||||
import React from "react";
|
|
||||||
|
|
||||||
import { ChatStateProvider } from "../contexts/chatStateProvider";
|
|
||||||
import { IdentityProvider } from "../contexts/identityProvider";
|
|
||||||
import { MessengerProvider } from "../contexts/messengerProvider";
|
|
||||||
import {
|
|
||||||
UserCreationState,
|
|
||||||
useUserCreationState,
|
|
||||||
} from "../contexts/userCreationStateProvider";
|
|
||||||
|
|
||||||
import { Chat } from "./Chat";
|
|
||||||
import { IdentityLoader } from "./Form/IdentityLoader";
|
|
||||||
|
|
||||||
type ChatLoaderProps = {
|
|
||||||
communityKey: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export function ChatLoader({ communityKey }: ChatLoaderProps) {
|
|
||||||
const [userCreationState] = useUserCreationState();
|
|
||||||
|
|
||||||
if (userCreationState === UserCreationState.NotCreating)
|
|
||||||
return (
|
|
||||||
<IdentityProvider>
|
|
||||||
<MessengerProvider communityKey={communityKey}>
|
|
||||||
<ChatStateProvider>
|
|
||||||
<Chat />
|
|
||||||
</ChatStateProvider>
|
|
||||||
</MessengerProvider>
|
|
||||||
</IdentityProvider>
|
|
||||||
);
|
|
||||||
if (userCreationState === UserCreationState.Creating) {
|
|
||||||
return <IdentityLoader />;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
|
@ -4,17 +4,19 @@ import styled from "styled-components";
|
||||||
|
|
||||||
import { ConfigType } from "..";
|
import { ConfigType } from "..";
|
||||||
import { ActivityProvider } from "../contexts/activityProvider";
|
import { ActivityProvider } from "../contexts/activityProvider";
|
||||||
|
import { ChatStateProvider } from "../contexts/chatStateProvider";
|
||||||
import { ConfigProvider } from "../contexts/configProvider";
|
import { ConfigProvider } from "../contexts/configProvider";
|
||||||
import { FetchMetadataProvider } from "../contexts/fetchMetadataProvider";
|
import { FetchMetadataProvider } from "../contexts/fetchMetadataProvider";
|
||||||
|
import { IdentityProvider } from "../contexts/identityProvider";
|
||||||
|
import { MessengerProvider } from "../contexts/messengerProvider";
|
||||||
import { ModalProvider } from "../contexts/modalProvider";
|
import { ModalProvider } from "../contexts/modalProvider";
|
||||||
import { NarrowProvider } from "../contexts/narrowProvider";
|
import { NarrowProvider } from "../contexts/narrowProvider";
|
||||||
import { ToastProvider } from "../contexts/toastProvider";
|
import { ToastProvider } from "../contexts/toastProvider";
|
||||||
import { UserCreationStateProvider } from "../contexts/userCreationStateProvider";
|
|
||||||
import { Metadata } from "../models/Metadata";
|
import { Metadata } from "../models/Metadata";
|
||||||
import { GlobalStyle } from "../styles/GlobalStyle";
|
import { GlobalStyle } from "../styles/GlobalStyle";
|
||||||
import { Theme } from "../styles/themes";
|
import { Theme } from "../styles/themes";
|
||||||
|
|
||||||
import { ChatLoader } from "./ChatLoader";
|
import { Chat } from "./Chat";
|
||||||
|
|
||||||
interface DappConnectCommunityChatProps {
|
interface DappConnectCommunityChatProps {
|
||||||
theme: Theme;
|
theme: Theme;
|
||||||
|
@ -37,15 +39,19 @@ export function DappConnectCommunityChat({
|
||||||
<FetchMetadataProvider fetchMetadata={fetchMetadata}>
|
<FetchMetadataProvider fetchMetadata={fetchMetadata}>
|
||||||
<ModalProvider>
|
<ModalProvider>
|
||||||
<ActivityProvider>
|
<ActivityProvider>
|
||||||
<UserCreationStateProvider>
|
|
||||||
<ToastProvider>
|
<ToastProvider>
|
||||||
<Wrapper ref={ref}>
|
<Wrapper ref={ref}>
|
||||||
<GlobalStyle />
|
<GlobalStyle />
|
||||||
<ChatLoader communityKey={communityKey} />
|
<IdentityProvider>
|
||||||
|
<MessengerProvider communityKey={communityKey}>
|
||||||
|
<ChatStateProvider>
|
||||||
|
<Chat />
|
||||||
<div id="modal-root" />
|
<div id="modal-root" />
|
||||||
|
</ChatStateProvider>
|
||||||
|
</MessengerProvider>
|
||||||
|
</IdentityProvider>
|
||||||
</Wrapper>
|
</Wrapper>
|
||||||
</ToastProvider>
|
</ToastProvider>
|
||||||
</UserCreationStateProvider>
|
|
||||||
</ActivityProvider>
|
</ActivityProvider>
|
||||||
</ModalProvider>
|
</ModalProvider>
|
||||||
</FetchMetadataProvider>
|
</FetchMetadataProvider>
|
||||||
|
|
|
@ -1,133 +0,0 @@
|
||||||
import { Identity } from "@waku/status-communities/dist/cjs";
|
|
||||||
import React, { useCallback, useEffect, useState } from "react";
|
|
||||||
import styled from "styled-components";
|
|
||||||
|
|
||||||
import { useSetIdentity } from "../../contexts/identityProvider";
|
|
||||||
import {
|
|
||||||
UserCreationState,
|
|
||||||
useUserCreationState,
|
|
||||||
} from "../../contexts/userCreationStateProvider";
|
|
||||||
import {
|
|
||||||
decryptIdentity,
|
|
||||||
loadEncryptedIdentity,
|
|
||||||
saveIdentity,
|
|
||||||
} from "../../utils";
|
|
||||||
|
|
||||||
export function IdentityLoader() {
|
|
||||||
const setIdentity = useSetIdentity();
|
|
||||||
const [password, setPassword] = useState("");
|
|
||||||
const state = useUserCreationState();
|
|
||||||
|
|
||||||
const [encryptedIdentity, setEncryptedIdentity] = useState(
|
|
||||||
loadEncryptedIdentity() ?? ""
|
|
||||||
);
|
|
||||||
const [identityInMemory, setIdentityInMemory] = useState(false);
|
|
||||||
const [wrongPassword, setWrongPassword] = useState(false);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (encryptedIdentity) {
|
|
||||||
setIdentityInMemory(true);
|
|
||||||
}
|
|
||||||
setWrongPassword(false);
|
|
||||||
}, [encryptedIdentity]);
|
|
||||||
|
|
||||||
const loadIdentity = useCallback(async () => {
|
|
||||||
const identity = await decryptIdentity(encryptedIdentity, password);
|
|
||||||
if (!identity) {
|
|
||||||
setWrongPassword(true);
|
|
||||||
} else {
|
|
||||||
setIdentity(identity);
|
|
||||||
state[1](UserCreationState.NotCreating);
|
|
||||||
}
|
|
||||||
}, [encryptedIdentity, password]);
|
|
||||||
|
|
||||||
const createIdentity = useCallback(async () => {
|
|
||||||
const identity = Identity.generate();
|
|
||||||
await saveIdentity(identity, password);
|
|
||||||
setIdentity(identity);
|
|
||||||
state[1](UserCreationState.NotCreating);
|
|
||||||
}, [encryptedIdentity, password]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Wrapper>
|
|
||||||
{encryptedIdentity ? (
|
|
||||||
<FormWrappers>
|
|
||||||
<InfoDiv>Please provide password for your identity</InfoDiv>
|
|
||||||
{wrongPassword && <div>Wrong password</div>}
|
|
||||||
<Input
|
|
||||||
value={password}
|
|
||||||
type={"password"}
|
|
||||||
onChange={(e) => setPassword(e.target.value)}
|
|
||||||
onKeyPress={(e) => {
|
|
||||||
if (e.key === "Enter") {
|
|
||||||
loadIdentity();
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Button onClick={loadIdentity}>LOAD</Button>
|
|
||||||
<Button
|
|
||||||
onClick={() => {
|
|
||||||
setEncryptedIdentity("");
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Create new identity
|
|
||||||
</Button>
|
|
||||||
</FormWrappers>
|
|
||||||
) : (
|
|
||||||
<FormWrappers>
|
|
||||||
<InfoDiv>Please provide password for your identity</InfoDiv>
|
|
||||||
<Input
|
|
||||||
value={password}
|
|
||||||
type={"password"}
|
|
||||||
onChange={(e) => setPassword(e.target.value)}
|
|
||||||
onKeyPress={(e) => {
|
|
||||||
if (e.key === "Enter") {
|
|
||||||
createIdentity();
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Button onClick={createIdentity}>Create</Button>
|
|
||||||
{identityInMemory && (
|
|
||||||
<Button
|
|
||||||
onClick={() =>
|
|
||||||
setEncryptedIdentity(loadEncryptedIdentity() ?? "")
|
|
||||||
}
|
|
||||||
>
|
|
||||||
Go back
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</FormWrappers>
|
|
||||||
)}
|
|
||||||
</Wrapper>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const Input = styled.input`
|
|
||||||
border-radius: 5px;
|
|
||||||
padding: 5px;
|
|
||||||
margin: 5px;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const Button = styled.button`
|
|
||||||
padding: 5px;
|
|
||||||
margin: 5px;
|
|
||||||
box-shadow: 5px 5px 10px -4px rgba(197, 197, 255, 1);
|
|
||||||
border-radius: 5px;
|
|
||||||
background-color: ${({ theme }) => theme.buttonBg};
|
|
||||||
`;
|
|
||||||
|
|
||||||
const Wrapper = styled.div`
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
display: flex;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const FormWrappers = styled.div`
|
|
||||||
margin: auto;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const InfoDiv = styled.div`
|
|
||||||
margin: 5px;
|
|
||||||
font-weight: 500;
|
|
||||||
font-size: 16px;
|
|
||||||
`;
|
|
|
@ -1,4 +1,3 @@
|
||||||
import { utils } from "@waku/status-communities/dist/cjs";
|
|
||||||
import { bufToHex } from "@waku/status-communities/dist/cjs/utils";
|
import { bufToHex } from "@waku/status-communities/dist/cjs/utils";
|
||||||
import React, { useMemo } from "react";
|
import React, { useMemo } from "react";
|
||||||
import styled from "styled-components";
|
import styled from "styled-components";
|
||||||
|
@ -6,6 +5,7 @@ import styled from "styled-components";
|
||||||
import { useIdentity } from "../../contexts/identityProvider";
|
import { useIdentity } from "../../contexts/identityProvider";
|
||||||
import { useMessengerContext } from "../../contexts/messengerProvider";
|
import { useMessengerContext } from "../../contexts/messengerProvider";
|
||||||
import { useModal } from "../../contexts/modalProvider";
|
import { useModal } from "../../contexts/modalProvider";
|
||||||
|
import { Contact } from "../../models/Contact";
|
||||||
import { buttonStyles } from "../Buttons/buttonStyle";
|
import { buttonStyles } from "../Buttons/buttonStyle";
|
||||||
import { LogoutIcon } from "../Icons/LogoutIcon";
|
import { LogoutIcon } from "../Icons/LogoutIcon";
|
||||||
import { LogoutModalName } from "../Modals/LogoutModal";
|
import { LogoutModalName } from "../Modals/LogoutModal";
|
||||||
|
@ -15,30 +15,32 @@ import { Member } from "./Member";
|
||||||
export function MembersList() {
|
export function MembersList() {
|
||||||
const { contacts, nickname, activeChannel } = useMessengerContext();
|
const { contacts, nickname, activeChannel } = useMessengerContext();
|
||||||
const identity = useIdentity();
|
const identity = useIdentity();
|
||||||
|
const userPK = useMemo(
|
||||||
|
() => (identity ? bufToHex(identity?.publicKey) : undefined),
|
||||||
|
[identity]
|
||||||
|
);
|
||||||
const { setModal } = useModal(LogoutModalName);
|
const { setModal } = useModal(LogoutModalName);
|
||||||
|
|
||||||
const userContacts = useMemo(() => {
|
const members = useMemo(() => {
|
||||||
|
const contactsArray = Object.values(contacts);
|
||||||
if (identity) {
|
if (identity) {
|
||||||
return Object.values(contacts).filter(
|
if (
|
||||||
(e) => e.id != bufToHex(identity.publicKey)
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return Object.values(contacts);
|
|
||||||
}
|
|
||||||
}, [contacts, identity]);
|
|
||||||
|
|
||||||
const members = useMemo(
|
|
||||||
() =>
|
|
||||||
activeChannel &&
|
activeChannel &&
|
||||||
activeChannel?.type === "group" &&
|
activeChannel.type === "group" &&
|
||||||
activeChannel.members &&
|
activeChannel.members
|
||||||
identity
|
) {
|
||||||
? activeChannel.members.filter(
|
const returnContacts: Contact[] = [];
|
||||||
(e) => e.id !== utils.bufToHex(identity.publicKey)
|
activeChannel.members.forEach((member) => {
|
||||||
)
|
if (contacts[member.id] && member.id != userPK) {
|
||||||
: userContacts,
|
returnContacts.push(contacts[member.id]);
|
||||||
[activeChannel]
|
}
|
||||||
);
|
});
|
||||||
|
return returnContacts;
|
||||||
|
}
|
||||||
|
return contactsArray.filter((e) => e.id !== userPK);
|
||||||
|
}
|
||||||
|
return contactsArray;
|
||||||
|
}, [activeChannel, contacts, identity, userPK]);
|
||||||
|
|
||||||
const onlineContacts = useMemo(
|
const onlineContacts = useMemo(
|
||||||
() => members.filter((e) => e.online),
|
() => members.filter((e) => e.online),
|
||||||
|
@ -57,9 +59,9 @@ export function MembersList() {
|
||||||
<Row>
|
<Row>
|
||||||
<Member
|
<Member
|
||||||
contact={{
|
contact={{
|
||||||
id: utils.bufToHex(identity.publicKey),
|
id: userPK ?? "",
|
||||||
customName: nickname,
|
customName: nickname,
|
||||||
trueName: utils.bufToHex(identity.publicKey),
|
trueName: userPK ?? "",
|
||||||
}}
|
}}
|
||||||
isYou={true}
|
isYou={true}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -18,6 +18,7 @@ const MessengerContext = createContext<MessengerType>({
|
||||||
communityData: undefined,
|
communityData: undefined,
|
||||||
contacts: {},
|
contacts: {},
|
||||||
contactsDispatch: () => undefined,
|
contactsDispatch: () => undefined,
|
||||||
|
addContact: () => undefined,
|
||||||
activeChannel: undefined,
|
activeChannel: undefined,
|
||||||
channels: {},
|
channels: {},
|
||||||
channelsDispatch: () => undefined,
|
channelsDispatch: () => undefined,
|
||||||
|
@ -33,7 +34,7 @@ export function useMessengerContext() {
|
||||||
}
|
}
|
||||||
|
|
||||||
interface MessengerProviderProps {
|
interface MessengerProviderProps {
|
||||||
communityKey: string;
|
communityKey: string | undefined;
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,29 +0,0 @@
|
||||||
import React, { createContext, useContext, useState } from "react";
|
|
||||||
|
|
||||||
export enum UserCreationState {
|
|
||||||
Creating,
|
|
||||||
NotCreating,
|
|
||||||
}
|
|
||||||
|
|
||||||
type UserCreationContextType = [
|
|
||||||
UserCreationState,
|
|
||||||
React.Dispatch<React.SetStateAction<UserCreationState>>
|
|
||||||
];
|
|
||||||
|
|
||||||
const ChatStateContext = createContext<UserCreationContextType>([
|
|
||||||
UserCreationState.NotCreating,
|
|
||||||
() => undefined,
|
|
||||||
]);
|
|
||||||
|
|
||||||
export function useUserCreationState() {
|
|
||||||
return useContext(ChatStateContext);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function UserCreationStateProvider({
|
|
||||||
children,
|
|
||||||
}: {
|
|
||||||
children: React.ReactNode;
|
|
||||||
}) {
|
|
||||||
const state = useState(UserCreationState.NotCreating);
|
|
||||||
return <ChatStateContext.Provider value={state} children={children} />;
|
|
||||||
}
|
|
|
@ -0,0 +1,65 @@
|
||||||
|
import React, { useRef } from "react";
|
||||||
|
import { ThemeProvider } from "styled-components";
|
||||||
|
import styled from "styled-components";
|
||||||
|
|
||||||
|
import { ConfigType } from "..";
|
||||||
|
import { ActivityProvider } from "../contexts/activityProvider";
|
||||||
|
import { ChatStateProvider } from "../contexts/chatStateProvider";
|
||||||
|
import { ConfigProvider } from "../contexts/configProvider";
|
||||||
|
import { FetchMetadataProvider } from "../contexts/fetchMetadataProvider";
|
||||||
|
import { IdentityProvider } from "../contexts/identityProvider";
|
||||||
|
import { MessengerProvider } from "../contexts/messengerProvider";
|
||||||
|
import { ModalProvider } from "../contexts/modalProvider";
|
||||||
|
import { NarrowProvider } from "../contexts/narrowProvider";
|
||||||
|
import { ToastProvider } from "../contexts/toastProvider";
|
||||||
|
import { Metadata } from "../models/Metadata";
|
||||||
|
import { GlobalStyle } from "../styles/GlobalStyle";
|
||||||
|
import { Theme } from "../styles/themes";
|
||||||
|
|
||||||
|
import { GroupChat } from "./GroupChat";
|
||||||
|
|
||||||
|
interface DappConnectGroupChatProps {
|
||||||
|
theme: Theme;
|
||||||
|
config: ConfigType;
|
||||||
|
fetchMetadata?: (url: string) => Promise<Metadata | undefined>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function DappConnectGroupChat({
|
||||||
|
theme,
|
||||||
|
config,
|
||||||
|
fetchMetadata,
|
||||||
|
}: DappConnectGroupChatProps) {
|
||||||
|
const ref = useRef<HTMLHeadingElement>(null);
|
||||||
|
return (
|
||||||
|
<ConfigProvider config={config}>
|
||||||
|
<ThemeProvider theme={theme}>
|
||||||
|
<NarrowProvider myRef={ref}>
|
||||||
|
<FetchMetadataProvider fetchMetadata={fetchMetadata}>
|
||||||
|
<ModalProvider>
|
||||||
|
<ActivityProvider>
|
||||||
|
<ToastProvider>
|
||||||
|
<Wrapper ref={ref}>
|
||||||
|
<GlobalStyle />
|
||||||
|
<IdentityProvider>
|
||||||
|
<MessengerProvider communityKey={undefined}>
|
||||||
|
<ChatStateProvider>
|
||||||
|
<GroupChat />
|
||||||
|
<div id="modal-root" />
|
||||||
|
</ChatStateProvider>
|
||||||
|
</MessengerProvider>
|
||||||
|
</IdentityProvider>
|
||||||
|
</Wrapper>
|
||||||
|
</ToastProvider>
|
||||||
|
</ActivityProvider>
|
||||||
|
</ModalProvider>
|
||||||
|
</FetchMetadataProvider>
|
||||||
|
</NarrowProvider>
|
||||||
|
</ThemeProvider>
|
||||||
|
</ConfigProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const Wrapper = styled.div`
|
||||||
|
height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
`;
|
|
@ -0,0 +1,90 @@
|
||||||
|
import React, { useState } from "react";
|
||||||
|
import styled from "styled-components";
|
||||||
|
|
||||||
|
import { Channels } from "../components/Channels/Channels";
|
||||||
|
import { ChatCreation } from "../components/Chat/ChatCreation";
|
||||||
|
import { AgreementModal } from "../components/Modals/AgreementModal";
|
||||||
|
import { CoinbaseModal } from "../components/Modals/CoinbaseModal";
|
||||||
|
import { EditModal } from "../components/Modals/EditModal";
|
||||||
|
import { LeavingModal } from "../components/Modals/LeavingModal";
|
||||||
|
import { LogoutModal } from "../components/Modals/LogoutModal";
|
||||||
|
import { ProfileFoundModal } from "../components/Modals/ProfileFoundModal";
|
||||||
|
import { ProfileModal } from "../components/Modals/ProfileModal";
|
||||||
|
import { StatusModal } from "../components/Modals/StatusModal";
|
||||||
|
import { UserCreationModal } from "../components/Modals/UserCreationModal";
|
||||||
|
import { UserCreationStartModal } from "../components/Modals/UserCreationStartModal";
|
||||||
|
import { WalletConnectModal } from "../components/Modals/WalletConnectModal";
|
||||||
|
import { WalletModal } from "../components/Modals/WalletModal";
|
||||||
|
import { ToastMessageList } from "../components/ToastMessages/ToastMessageList";
|
||||||
|
import { ChatState, useChatState } from "../contexts/chatStateProvider";
|
||||||
|
import { useNarrow } from "../contexts/narrowProvider";
|
||||||
|
|
||||||
|
import { GroupChatBody } from "./GroupChat/GroupChatBody";
|
||||||
|
import { GroupMembers } from "./GroupMembers/GroupMembers";
|
||||||
|
|
||||||
|
function Modals() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<UserCreationModal />
|
||||||
|
<EditModal />
|
||||||
|
<ProfileModal />
|
||||||
|
<StatusModal />
|
||||||
|
<WalletModal />
|
||||||
|
<WalletConnectModal />
|
||||||
|
<CoinbaseModal />
|
||||||
|
<LogoutModal />
|
||||||
|
<AgreementModal />
|
||||||
|
<ProfileFoundModal />
|
||||||
|
<UserCreationStartModal />
|
||||||
|
<LeavingModal />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function GroupChat() {
|
||||||
|
const [state] = useChatState();
|
||||||
|
const [showMembers, setShowMembers] = useState(false);
|
||||||
|
const [editGroup, setEditGroup] = useState(false);
|
||||||
|
const narrow = useNarrow();
|
||||||
|
return (
|
||||||
|
<ChatWrapper>
|
||||||
|
{!narrow && (
|
||||||
|
<ChannelsWrapper>
|
||||||
|
<Channels setEditGroup={setEditGroup} />
|
||||||
|
</ChannelsWrapper>
|
||||||
|
)}
|
||||||
|
{state === ChatState.ChatBody && (
|
||||||
|
<GroupChatBody
|
||||||
|
onClick={() => setShowMembers(!showMembers)}
|
||||||
|
showMembers={showMembers}
|
||||||
|
permission={true}
|
||||||
|
editGroup={editGroup}
|
||||||
|
setEditGroup={setEditGroup}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{showMembers && !narrow && state === ChatState.ChatBody && (
|
||||||
|
<GroupMembers />
|
||||||
|
)}
|
||||||
|
{state === ChatState.ChatCreation && <ChatCreation />}
|
||||||
|
<Modals />
|
||||||
|
<ToastMessageList />
|
||||||
|
</ChatWrapper>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const ChatWrapper = styled.div`
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
position: relative;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const ChannelsWrapper = styled.div`
|
||||||
|
width: 21%;
|
||||||
|
height: 100%;
|
||||||
|
min-width: 250px;
|
||||||
|
background-color: ${({ theme }) => theme.sectionBackgroundColor};
|
||||||
|
padding: 10px 16px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
`;
|
|
@ -0,0 +1,182 @@
|
||||||
|
import React, { useCallback, useEffect, useMemo, useState } from "react";
|
||||||
|
import styled from "styled-components";
|
||||||
|
|
||||||
|
import { ChatCreation } from "../../components/Chat/ChatCreation";
|
||||||
|
import { ChatInput } from "../../components/Chat/ChatInput";
|
||||||
|
import {
|
||||||
|
ChatTopbar,
|
||||||
|
ChatTopbarLoading,
|
||||||
|
} from "../../components/Chat/ChatTopbar";
|
||||||
|
import { TokenRequirement } from "../../components/Form/TokenRequirement";
|
||||||
|
import { MessagesList } from "../../components/Messages/MessagesList";
|
||||||
|
import { NarrowChannels } from "../../components/NarrowMode/NarrowChannels";
|
||||||
|
import { NarrowMembers } from "../../components/NarrowMode/NarrowMembers";
|
||||||
|
import { LoadingSkeleton } from "../../components/Skeleton/LoadingSkeleton";
|
||||||
|
import { useMessengerContext } from "../../contexts/messengerProvider";
|
||||||
|
import { useNarrow } from "../../contexts/narrowProvider";
|
||||||
|
import { Reply } from "../../hooks/useReply";
|
||||||
|
import { ChannelData } from "../../models/ChannelData";
|
||||||
|
|
||||||
|
export enum ChatBodyState {
|
||||||
|
Chat,
|
||||||
|
Channels,
|
||||||
|
Members,
|
||||||
|
}
|
||||||
|
|
||||||
|
function ChatBodyLoading() {
|
||||||
|
const narrow = useNarrow();
|
||||||
|
return (
|
||||||
|
<Wrapper>
|
||||||
|
<ChatBodyWrapper className={narrow ? "narrow" : ""}>
|
||||||
|
<ChatTopbarLoading />
|
||||||
|
<LoadingSkeleton />
|
||||||
|
<ChatInput reply={undefined} setReply={() => undefined} />
|
||||||
|
</ChatBodyWrapper>
|
||||||
|
</Wrapper>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
type ChatBodyContentProps = {
|
||||||
|
showState: ChatBodyState;
|
||||||
|
switchShowState: (state: ChatBodyState) => void;
|
||||||
|
channel: ChannelData;
|
||||||
|
};
|
||||||
|
|
||||||
|
function ChatBodyContent({
|
||||||
|
showState,
|
||||||
|
switchShowState,
|
||||||
|
channel,
|
||||||
|
}: ChatBodyContentProps) {
|
||||||
|
const [reply, setReply] = useState<Reply | undefined>(undefined);
|
||||||
|
|
||||||
|
switch (showState) {
|
||||||
|
case ChatBodyState.Chat:
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<MessagesList setReply={setReply} channel={channel} />
|
||||||
|
<ChatInput reply={reply} setReply={setReply} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
case ChatBodyState.Channels:
|
||||||
|
return (
|
||||||
|
<NarrowChannels
|
||||||
|
setShowChannels={() => switchShowState(ChatBodyState.Channels)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
case ChatBodyState.Members:
|
||||||
|
return (
|
||||||
|
<NarrowMembers
|
||||||
|
switchShowMembersList={() => switchShowState(ChatBodyState.Members)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface GroupChatBodyProps {
|
||||||
|
onClick: () => void;
|
||||||
|
showMembers: boolean;
|
||||||
|
permission: boolean;
|
||||||
|
editGroup: boolean;
|
||||||
|
setEditGroup: React.Dispatch<React.SetStateAction<boolean>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function GroupChatBody({
|
||||||
|
onClick,
|
||||||
|
showMembers,
|
||||||
|
permission,
|
||||||
|
editGroup,
|
||||||
|
setEditGroup,
|
||||||
|
}: GroupChatBodyProps) {
|
||||||
|
const { activeChannel, loadingMessenger } = useMessengerContext();
|
||||||
|
|
||||||
|
const narrow = useNarrow();
|
||||||
|
const className = useMemo(() => (narrow ? "narrow" : ""), [narrow]);
|
||||||
|
|
||||||
|
const [showState, setShowState] = useState<ChatBodyState>(ChatBodyState.Chat);
|
||||||
|
const switchShowState = useCallback(
|
||||||
|
(state: ChatBodyState) => {
|
||||||
|
if (narrow) {
|
||||||
|
setShowState((prev) => (prev === state ? ChatBodyState.Chat : state));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[narrow]
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!narrow) {
|
||||||
|
setShowState(ChatBodyState.Chat);
|
||||||
|
}
|
||||||
|
}, [narrow]);
|
||||||
|
|
||||||
|
if (!loadingMessenger && activeChannel) {
|
||||||
|
return (
|
||||||
|
<Wrapper>
|
||||||
|
<ChatBodyWrapper className={className}>
|
||||||
|
{editGroup ? (
|
||||||
|
<ChatCreation
|
||||||
|
setEditGroup={setEditGroup}
|
||||||
|
activeChannel={activeChannel}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<ChatTopbar
|
||||||
|
onClick={onClick}
|
||||||
|
setEditGroup={setEditGroup}
|
||||||
|
showMembers={showMembers}
|
||||||
|
showState={showState}
|
||||||
|
switchShowState={switchShowState}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<ChatBodyContent
|
||||||
|
showState={showState}
|
||||||
|
switchShowState={switchShowState}
|
||||||
|
channel={activeChannel}
|
||||||
|
/>
|
||||||
|
</ChatBodyWrapper>
|
||||||
|
{!permission && (
|
||||||
|
<BluredWrapper>
|
||||||
|
<TokenRequirement />
|
||||||
|
</BluredWrapper>
|
||||||
|
)}
|
||||||
|
</Wrapper>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return <ChatBodyLoading />;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Wrapper = styled.div`
|
||||||
|
width: 61%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
flex: 1;
|
||||||
|
height: 100%;
|
||||||
|
background: ${({ theme }) => theme.bodyBackgroundColor};
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&.narrow {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const ChatBodyWrapper = styled.div`
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
flex: 1;
|
||||||
|
background: ${({ theme }) => theme.bodyBackgroundColor};
|
||||||
|
`;
|
||||||
|
|
||||||
|
const BluredWrapper = styled.div`
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-end;
|
||||||
|
justify-content: center;
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
background: ${({ theme }) => theme.bodyBackgroundGradient};
|
||||||
|
backdrop-filter: blur(4px);
|
||||||
|
z-index: 2;
|
||||||
|
`;
|
|
@ -0,0 +1,49 @@
|
||||||
|
import React, { useMemo, useState } from "react";
|
||||||
|
import styled from "styled-components";
|
||||||
|
|
||||||
|
import { MembersList } from "../../components/Members/MembersList";
|
||||||
|
import { useMessengerContext } from "../../contexts/messengerProvider";
|
||||||
|
|
||||||
|
export function GroupMembers() {
|
||||||
|
const { addContact, activeChannel } = useMessengerContext();
|
||||||
|
const heading = useMemo(
|
||||||
|
() =>
|
||||||
|
activeChannel && activeChannel?.type === "group"
|
||||||
|
? "Group members"
|
||||||
|
: "Members",
|
||||||
|
[activeChannel]
|
||||||
|
);
|
||||||
|
const [newUserInput, setNewUserInput] = useState("");
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<MembersWrapper>
|
||||||
|
<MemberHeading>{heading}</MemberHeading>
|
||||||
|
<MembersList />
|
||||||
|
<input
|
||||||
|
value={newUserInput}
|
||||||
|
onChange={(e) => setNewUserInput(e.target.value)}
|
||||||
|
/>
|
||||||
|
<button onClick={() => addContact(newUserInput)}>Add Contact</button>
|
||||||
|
</MembersWrapper>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const MembersWrapper = styled.div`
|
||||||
|
width: 18%;
|
||||||
|
height: 100%;
|
||||||
|
min-width: 164px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
background-color: ${({ theme }) => theme.sectionBackgroundColor};
|
||||||
|
padding: 16px;
|
||||||
|
overflow-y: scroll;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const MemberHeading = styled.h2`
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 15px;
|
||||||
|
line-height: 22px;
|
||||||
|
color: ${({ theme }) => theme.primary};
|
||||||
|
margin-bottom: 16px;
|
||||||
|
`;
|
|
@ -1,4 +1,5 @@
|
||||||
import {
|
import {
|
||||||
|
Contacts as ContactsClass,
|
||||||
GroupChat,
|
GroupChat,
|
||||||
GroupChats,
|
GroupChats,
|
||||||
Identity,
|
Identity,
|
||||||
|
@ -28,10 +29,11 @@ export function useGroupChats(
|
||||||
messenger: Messenger | undefined,
|
messenger: Messenger | undefined,
|
||||||
identity: Identity | undefined,
|
identity: Identity | undefined,
|
||||||
dispatch: (action: ChannelAction) => void,
|
dispatch: (action: ChannelAction) => void,
|
||||||
addChatMessage: (newMessage: ChatMessage | undefined, id: string) => void
|
addChatMessage: (newMessage: ChatMessage | undefined, id: string) => void,
|
||||||
|
contactsClass: ContactsClass | undefined
|
||||||
) {
|
) {
|
||||||
const groupChat = useMemo(() => {
|
const groupChat = useMemo(() => {
|
||||||
if (messenger && identity) {
|
if (messenger && identity && contactsClass) {
|
||||||
const addChat = (chat: GroupChat) => {
|
const addChat = (chat: GroupChat) => {
|
||||||
const members = chat.members
|
const members = chat.members
|
||||||
.map((member) => member.id)
|
.map((member) => member.id)
|
||||||
|
@ -52,6 +54,7 @@ export function useGroupChats(
|
||||||
description: `Chatkey: ${chat.members[0].id}`,
|
description: `Chatkey: ${chat.members[0].id}`,
|
||||||
members,
|
members,
|
||||||
};
|
};
|
||||||
|
chat.members.forEach((member) => contactsClass.addContact(member.id));
|
||||||
dispatch({ type: "AddChannel", payload: channel });
|
dispatch({ type: "AddChannel", payload: channel });
|
||||||
};
|
};
|
||||||
const removeChat = (chat: GroupChat) => {
|
const removeChat = (chat: GroupChat) => {
|
||||||
|
@ -81,7 +84,7 @@ export function useGroupChats(
|
||||||
handleMessage
|
handleMessage
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}, [messenger, identity]);
|
}, [messenger, identity, contactsClass]);
|
||||||
|
|
||||||
const createGroupChat = useCallback(
|
const createGroupChat = useCallback(
|
||||||
(members: string[]) => {
|
(members: string[]) => {
|
||||||
|
|
|
@ -17,12 +17,14 @@ export function useLoadPrevDay(
|
||||||
const [loadingMessages, setLoadingMessages] = useState(false);
|
const [loadingMessages, setLoadingMessages] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (chatId) {
|
||||||
setLoadingMessages(loadingPreviousMessages.current[chatId]);
|
setLoadingMessages(loadingPreviousMessages.current[chatId]);
|
||||||
|
}
|
||||||
}, [chatId]);
|
}, [chatId]);
|
||||||
|
|
||||||
const loadPrevDay = useCallback(
|
const loadPrevDay = useCallback(
|
||||||
async (id: string, groupChat?: boolean) => {
|
async (id: string, groupChat?: boolean) => {
|
||||||
if (messenger) {
|
if (messenger && id) {
|
||||||
const endTime = lastLoadTime.current[id] ?? new Date();
|
const endTime = lastLoadTime.current[id] ?? new Date();
|
||||||
const startTime = new Date(endTime.getTime() - _MS_PER_DAY * 5);
|
const startTime = new Date(endTime.getTime() - _MS_PER_DAY * 5);
|
||||||
const timeDiff = Math.floor(
|
const timeDiff = Math.floor(
|
||||||
|
|
|
@ -41,6 +41,7 @@ export type MessengerType = {
|
||||||
communityData: CommunityData | undefined;
|
communityData: CommunityData | undefined;
|
||||||
contacts: Contacts;
|
contacts: Contacts;
|
||||||
contactsDispatch: (action: ContactsAction) => void;
|
contactsDispatch: (action: ContactsAction) => void;
|
||||||
|
addContact: (publicKey: string) => void;
|
||||||
channels: ChannelsData;
|
channels: ChannelsData;
|
||||||
channelsDispatch: (action: ChannelAction) => void;
|
channelsDispatch: (action: ChannelAction) => void;
|
||||||
removeChannel: (channelId: string) => void;
|
removeChannel: (channelId: string) => void;
|
||||||
|
@ -105,7 +106,7 @@ function useCreateCommunity(
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useMessenger(
|
export function useMessenger(
|
||||||
communityKey: string,
|
communityKey: string | undefined,
|
||||||
identity: Identity | undefined,
|
identity: Identity | undefined,
|
||||||
newNickname: string | undefined
|
newNickname: string | undefined
|
||||||
) {
|
) {
|
||||||
|
@ -117,6 +118,15 @@ export function useMessenger(
|
||||||
newNickname
|
newNickname
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const addContact = useCallback(
|
||||||
|
(publicKey: string) => {
|
||||||
|
if (contactsClass) {
|
||||||
|
contactsClass.addContact(publicKey);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[contactsClass]
|
||||||
|
);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
addChatMessage,
|
addChatMessage,
|
||||||
addMessage,
|
addMessage,
|
||||||
|
@ -176,7 +186,13 @@ export function useMessenger(
|
||||||
createGroupChat,
|
createGroupChat,
|
||||||
changeGroupChatName,
|
changeGroupChatName,
|
||||||
addMembers,
|
addMembers,
|
||||||
} = useGroupChats(messenger, identity, channelsDispatch, addChatMessage);
|
} = useGroupChats(
|
||||||
|
messenger,
|
||||||
|
identity,
|
||||||
|
channelsDispatch,
|
||||||
|
addChatMessage,
|
||||||
|
contactsClass
|
||||||
|
);
|
||||||
|
|
||||||
const { loadPrevDay, loadingMessages } = useLoadPrevDay(
|
const { loadPrevDay, loadingMessages } = useLoadPrevDay(
|
||||||
channelsState.activeChannel.id,
|
channelsState.activeChannel.id,
|
||||||
|
@ -237,9 +253,12 @@ export function useMessenger(
|
||||||
}, [notifications, channelsState]);
|
}, [notifications, channelsState]);
|
||||||
|
|
||||||
const loadingMessenger = useMemo(() => {
|
const loadingMessenger = useMemo(() => {
|
||||||
return !communityData || !messenger || !channelsState.activeChannel.id;
|
return Boolean(
|
||||||
|
(communityKey && !communityData) ||
|
||||||
|
!messenger ||
|
||||||
|
(communityKey && !channelsState.activeChannel.id)
|
||||||
|
);
|
||||||
}, [communityData, messenger, channelsState]);
|
}, [communityData, messenger, channelsState]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
messenger,
|
messenger,
|
||||||
messages,
|
messages,
|
||||||
|
@ -252,6 +271,7 @@ export function useMessenger(
|
||||||
communityData,
|
communityData,
|
||||||
contacts,
|
contacts,
|
||||||
contactsDispatch,
|
contactsDispatch,
|
||||||
|
addContact,
|
||||||
channels: channelsState.channels,
|
channels: channelsState.channels,
|
||||||
channelsDispatch,
|
channelsDispatch,
|
||||||
removeChannel,
|
removeChannel,
|
||||||
|
|
|
@ -22,16 +22,16 @@ export function useChatScrollHandle(
|
||||||
(ref?.current?.clientHeight ?? 0) >= (ref?.current?.scrollHeight ?? 0)
|
(ref?.current?.clientHeight ?? 0) >= (ref?.current?.scrollHeight ?? 0)
|
||||||
) {
|
) {
|
||||||
setScrollOnBot(true);
|
setScrollOnBot(true);
|
||||||
loadPrevDay(activeChannel.id, activeChannel.type === "group");
|
loadPrevDay(activeChannel.id, activeChannel.type !== "channel");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [messages.length, loadingMessages, activeChannel]);
|
}, [messages.length, activeChannel]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const setScroll = () => {
|
const setScroll = () => {
|
||||||
if (ref?.current && activeChannel) {
|
if (ref?.current && activeChannel) {
|
||||||
if (ref.current.scrollTop <= 0) {
|
if (ref.current.scrollTop <= 0) {
|
||||||
loadPrevDay(activeChannel.id, activeChannel.type === "group");
|
loadPrevDay(activeChannel.id, activeChannel.type !== "channel");
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
ref.current.scrollTop + ref.current.clientHeight ==
|
ref.current.scrollTop + ref.current.clientHeight ==
|
||||||
|
|
|
@ -1,4 +1,12 @@
|
||||||
import { DappConnectCommunityChat } from "./components/DappConnectCommunityChat";
|
import { DappConnectCommunityChat } from "./components/DappConnectCommunityChat";
|
||||||
import { ConfigType } from "./contexts/configProvider";
|
import { ConfigType } from "./contexts/configProvider";
|
||||||
|
import { DappConnectGroupChat } from "./groupChatComponents/DappConnectGroupChat";
|
||||||
import { darkTheme, lightTheme } from "./styles/themes";
|
import { darkTheme, lightTheme } from "./styles/themes";
|
||||||
export { DappConnectCommunityChat, lightTheme, darkTheme, ConfigType };
|
|
||||||
|
export {
|
||||||
|
DappConnectCommunityChat,
|
||||||
|
DappConnectGroupChat,
|
||||||
|
lightTheme,
|
||||||
|
darkTheme,
|
||||||
|
ConfigType,
|
||||||
|
};
|
||||||
|
|
|
@ -0,0 +1,41 @@
|
||||||
|
{
|
||||||
|
"root": true,
|
||||||
|
"parser": "@typescript-eslint/parser",
|
||||||
|
"parserOptions": { "project": "./tsconfig.json" },
|
||||||
|
"env": { "es6": true },
|
||||||
|
"ignorePatterns": ["node_modules", "dist", "coverage", "proto"],
|
||||||
|
"plugins": ["import", "eslint-comments", "functional"],
|
||||||
|
"extends": [
|
||||||
|
"eslint:recommended",
|
||||||
|
"plugin:eslint-comments/recommended",
|
||||||
|
"plugin:@typescript-eslint/recommended",
|
||||||
|
"plugin:import/typescript",
|
||||||
|
"prettier"
|
||||||
|
],
|
||||||
|
"globals": { "BigInt": true, "console": true, "WebAssembly": true },
|
||||||
|
"rules": {
|
||||||
|
"@typescript-eslint/explicit-module-boundary-types": "off",
|
||||||
|
"eslint-comments/disable-enable-pair": [
|
||||||
|
"error",
|
||||||
|
{ "allowWholeFile": true }
|
||||||
|
],
|
||||||
|
"eslint-comments/no-unused-disable": "error",
|
||||||
|
"import/order": [
|
||||||
|
"error",
|
||||||
|
{ "newlines-between": "always", "alphabetize": { "order": "asc" } }
|
||||||
|
],
|
||||||
|
"no-constant-condition": ["error", { "checkLoops": false }],
|
||||||
|
"sort-imports": [
|
||||||
|
"error",
|
||||||
|
{ "ignoreDeclarationSort": true, "ignoreCase": true }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"overrides": [
|
||||||
|
{
|
||||||
|
"files": ["*.spec.ts", "**/test_utils/*.ts"],
|
||||||
|
"rules": {
|
||||||
|
"@typescript-eslint/no-non-null-assertion": "off"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"extension": ["ts"],
|
||||||
|
"spec": "src/**/*.spec.ts",
|
||||||
|
"require": "ts-node/register",
|
||||||
|
"exit": true
|
||||||
|
}
|
|
@ -0,0 +1,2 @@
|
||||||
|
# package.json is formatted by package managers, so we ignore it here
|
||||||
|
package.json
|
|
@ -0,0 +1 @@
|
||||||
|
#React chat example
|
|
@ -0,0 +1,71 @@
|
||||||
|
{
|
||||||
|
"name": "@waku/react-group-chat-sdk-example",
|
||||||
|
"main": "index.js",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"repository": "https://github.com/status-im/dappconnect-chat-sdk/",
|
||||||
|
"license": "MIT OR Apache-2.0",
|
||||||
|
"packageManager": "yarn@3.0.1",
|
||||||
|
"scripts": {
|
||||||
|
"clean:all": "yarn clean && rimraf node_modules/",
|
||||||
|
"clean": "rimraf dist/",
|
||||||
|
"build": "rm -rf dist && webpack --mode=production --env ENV=production",
|
||||||
|
"start": "webpack serve --mode=development --env ENV=$ENV COMMUNITY_KEY=$COMMUNITY_KEY --https",
|
||||||
|
"fix": "run-s 'fix:*'",
|
||||||
|
"fix:prettier": "prettier './{src,test}/**/*.{ts,tsx}' \"./*.json\" --write",
|
||||||
|
"fix:lint": "eslint './{src,test}/**/*.{ts,tsx}' --fix",
|
||||||
|
"test": "run-s 'test:*'",
|
||||||
|
"test:lint": "eslint './{src,test}/**/*.{ts,tsx}'",
|
||||||
|
"test:prettier": "prettier './{src,test}/**/*.{ts,tsx}' \"./*.json\" --list-different"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@waku/react-chat-sdk": "^0.1.0",
|
||||||
|
"assert": "^2.0.0",
|
||||||
|
"browserify-zlib": "^0.2.0",
|
||||||
|
"buffer": "^6.0.3",
|
||||||
|
"crypto-browserify": "^3.12.0",
|
||||||
|
"https-browserify": "^1.0.0",
|
||||||
|
"process": "^0.11.10",
|
||||||
|
"react": "^17.0.2",
|
||||||
|
"react-dom": "^17.0.2",
|
||||||
|
"react-router-dom": "^5.2.0",
|
||||||
|
"stream-browserify": "^3.0.0",
|
||||||
|
"stream-http": "^3.2.0",
|
||||||
|
"styled-components": "^5.3.1"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@testing-library/react-hooks": "^7.0.1",
|
||||||
|
"@types/chai": "^4.2.21",
|
||||||
|
"@types/mocha": "^9.0.0",
|
||||||
|
"@types/node": "^16.4.12",
|
||||||
|
"@types/react": "^17.0.15",
|
||||||
|
"@types/react-dom": "^17.0.9",
|
||||||
|
"@types/react-router": "^5.1.16",
|
||||||
|
"@types/react-router-dom": "^5.1.8",
|
||||||
|
"@types/styled-components": "^5.1.12",
|
||||||
|
"@typescript-eslint/eslint-plugin": "^4.29.0",
|
||||||
|
"@typescript-eslint/parser": "^4.29.0",
|
||||||
|
"chai": "^4.3.4",
|
||||||
|
"css-loader": "^6.3.0",
|
||||||
|
"esbuild-loader": "^2.15.1",
|
||||||
|
"eslint": "^7.32.0",
|
||||||
|
"eslint-plugin-hooks": "^0.2.0",
|
||||||
|
"eslint-plugin-react": "^7.24.0",
|
||||||
|
"file-loader": "^6.2.0",
|
||||||
|
"fork-ts-checker-webpack-plugin": "^6.3.1",
|
||||||
|
"html-webpack-plugin": "^5.3.2",
|
||||||
|
"jsdom": "^16.7.0",
|
||||||
|
"jsdom-global": "^3.0.2",
|
||||||
|
"mocha": "^9.0.3",
|
||||||
|
"npm-run-all": "^4.1.5",
|
||||||
|
"prettier": "^2.3.2",
|
||||||
|
"rimraf": "^3.0.2",
|
||||||
|
"source-map-loader": "^3.0.0",
|
||||||
|
"style-loader": "^3.3.0",
|
||||||
|
"ts-loader": "^9.2.5",
|
||||||
|
"ts-node": "^10.1.0",
|
||||||
|
"typescript": "^4.3.5",
|
||||||
|
"webpack": "^5.48.0",
|
||||||
|
"webpack-cli": "^4.7.2",
|
||||||
|
"webpack-dev-server": "^3.11.2"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
/* /index.html 200
|
|
@ -0,0 +1,14 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0,minimum-scale=1" />
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet" />
|
||||||
|
<title>DAppconnect group chat</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="root"></div>
|
||||||
|
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||||
|
<script type="module" src="/index.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,139 @@
|
||||||
|
import {
|
||||||
|
DappConnectGroupChat,
|
||||||
|
darkTheme,
|
||||||
|
lightTheme,
|
||||||
|
} from "@waku/react-chat-sdk";
|
||||||
|
import React, { useRef, useState } from "react";
|
||||||
|
import ReactDOM from "react-dom";
|
||||||
|
import styled from "styled-components";
|
||||||
|
|
||||||
|
const fetchMetadata = async (link: string) => {
|
||||||
|
const response = await fetch("https://localhost:3000", {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ site: link }),
|
||||||
|
});
|
||||||
|
const body = await response.text();
|
||||||
|
const parsedBody = JSON.parse(body);
|
||||||
|
if (
|
||||||
|
"og:image" in parsedBody &&
|
||||||
|
"og:site_name" in parsedBody &&
|
||||||
|
"og:title" in parsedBody
|
||||||
|
) {
|
||||||
|
return JSON.parse(body);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function DragDiv() {
|
||||||
|
const [x, setX] = useState(0);
|
||||||
|
const [y, setY] = useState(0);
|
||||||
|
const [width, setWidth] = useState(window.innerWidth - 50);
|
||||||
|
const [height, setHeight] = useState(window.innerHeight - 50);
|
||||||
|
const [showChat, setShowChat] = useState(true);
|
||||||
|
const ref = useRef<HTMLHeadingElement>(null);
|
||||||
|
const moved = useRef(false);
|
||||||
|
const setting = useRef("");
|
||||||
|
|
||||||
|
const [theme, setTheme] = useState(true);
|
||||||
|
|
||||||
|
const onMouseMove = (e: MouseEvent) => {
|
||||||
|
if (setting.current === "position") {
|
||||||
|
e.preventDefault();
|
||||||
|
setX(e.x - 20);
|
||||||
|
setY(e.y - 20);
|
||||||
|
}
|
||||||
|
if (setting.current === "size") {
|
||||||
|
setWidth(e.x - x);
|
||||||
|
setHeight(e.y - y);
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
|
moved.current = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const onMouseUp = () => {
|
||||||
|
document.removeEventListener("mousemove", onMouseMove);
|
||||||
|
document.removeEventListener("mouseup", onMouseUp);
|
||||||
|
if (!moved.current) [setShowChat((prev) => !prev)];
|
||||||
|
moved.current = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
setTheme(!theme);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Change theme
|
||||||
|
</button>
|
||||||
|
<Drag style={{ left: x, top: y, width: width, height: height }} ref={ref}>
|
||||||
|
<Bubble
|
||||||
|
onMouseDown={() => {
|
||||||
|
setting.current = "position";
|
||||||
|
document.addEventListener("mousemove", onMouseMove);
|
||||||
|
document.addEventListener("mouseup", onMouseUp);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<FloatingDiv className={showChat ? "" : "hide"}>
|
||||||
|
<DappConnectGroupChat
|
||||||
|
theme={theme ? lightTheme : darkTheme}
|
||||||
|
config={{
|
||||||
|
environment: process.env.ENV ?? "",
|
||||||
|
dappUrl: "https://0.0.0.0:8080",
|
||||||
|
}}
|
||||||
|
fetchMetadata={fetchMetadata}
|
||||||
|
/>
|
||||||
|
</FloatingDiv>
|
||||||
|
{showChat && (
|
||||||
|
<SizeSet
|
||||||
|
onMouseDown={() => {
|
||||||
|
setting.current = "size";
|
||||||
|
document.addEventListener("mousemove", onMouseMove);
|
||||||
|
document.addEventListener("mouseup", onMouseUp);
|
||||||
|
}}
|
||||||
|
></SizeSet>
|
||||||
|
)}
|
||||||
|
</Drag>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const FloatingDiv = styled.div`
|
||||||
|
height: calc(100% - 50px);
|
||||||
|
border: 1px solid black;
|
||||||
|
|
||||||
|
&.hide {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const SizeSet = styled.div`
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: 0px;
|
||||||
|
width: 10px;
|
||||||
|
height: 10px;
|
||||||
|
background-color: light-grey;
|
||||||
|
border: 1px solid;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Bubble = styled.div`
|
||||||
|
width: 50px;
|
||||||
|
height: 50px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: lightblue;
|
||||||
|
border: 1px solid;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Drag = styled.div`
|
||||||
|
position: absolute;
|
||||||
|
min-width: 375px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
ReactDOM.render(
|
||||||
|
<div style={{ height: "100%" }}>
|
||||||
|
<DragDiv />
|
||||||
|
</div>,
|
||||||
|
document.getElementById("root")
|
||||||
|
);
|
|
@ -0,0 +1,47 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "es6",
|
||||||
|
"outDir": "dist",
|
||||||
|
"jsx": "react",
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"module": "commonjs",
|
||||||
|
"declaration": true,
|
||||||
|
"sourceMap": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"composite": true,
|
||||||
|
"strict": true /* Enable all strict type-checking options. */,
|
||||||
|
|
||||||
|
/* Strict Type-Checking Options */
|
||||||
|
"noImplicitAny": true,
|
||||||
|
"strictNullChecks": true,
|
||||||
|
"strictFunctionTypes": true,
|
||||||
|
"strictPropertyInitialization": true,
|
||||||
|
"noImplicitThis": true,
|
||||||
|
"alwaysStrict": true,
|
||||||
|
|
||||||
|
/* Additional Checks */
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"noUnusedParameters": true,
|
||||||
|
"noImplicitReturns": false /* to set at a later stage */,
|
||||||
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
|
||||||
|
/* Debugging Options */
|
||||||
|
"traceResolution": false,
|
||||||
|
"listEmittedFiles": false,
|
||||||
|
"listFiles": false,
|
||||||
|
"pretty": true,
|
||||||
|
|
||||||
|
// Due to broken types in indirect dependencies
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"typeRoots": [
|
||||||
|
"./node_modules/@types",
|
||||||
|
"./src/types",
|
||||||
|
"../../node_modules/@types"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"include": ["src"],
|
||||||
|
"types": ["mocha"],
|
||||||
|
"compileOnSave": false
|
||||||
|
}
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,88 @@
|
||||||
|
const path = require('path');
|
||||||
|
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
||||||
|
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
|
||||||
|
const webpack = require('webpack');
|
||||||
|
const { ESBuildMinifyPlugin } = require('esbuild-loader');
|
||||||
|
|
||||||
|
module.exports = env => {
|
||||||
|
const environment = env.ENV || 'development';
|
||||||
|
const communityKey = env.COMMUNITY_KEY || '';
|
||||||
|
|
||||||
|
return {
|
||||||
|
entry: './src/index.tsx',
|
||||||
|
output: {
|
||||||
|
filename: 'index.[fullhash].js',
|
||||||
|
path: path.join(__dirname, 'dist'),
|
||||||
|
publicPath: '/',
|
||||||
|
},
|
||||||
|
devtool: 'source-map',
|
||||||
|
resolve: {
|
||||||
|
extensions: ['.ts', '.tsx', '.js', '.json'],
|
||||||
|
fallback: {
|
||||||
|
buffer: require.resolve('buffer/'),
|
||||||
|
crypto: require.resolve('crypto-browserify'),
|
||||||
|
stream: require.resolve('stream-browserify'),
|
||||||
|
assert: require.resolve('assert/'),
|
||||||
|
http: require.resolve('stream-http'),
|
||||||
|
https: require.resolve('https-browserify'),
|
||||||
|
zlib: require.resolve('browserify-zlib')
|
||||||
|
},
|
||||||
|
},
|
||||||
|
module: {
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
test: /\.tsx?$/,
|
||||||
|
loader: 'esbuild-loader',
|
||||||
|
exclude: /node_modules/,
|
||||||
|
options: {
|
||||||
|
loader: 'tsx',
|
||||||
|
target: 'es2018',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
enforce: 'pre',
|
||||||
|
test: /\.js$/,
|
||||||
|
exclude: /node_modules/,
|
||||||
|
loader: 'source-map-loader',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.(png|svg|jpg|gif|woff|woff2|eot|ttf|otf|ico)$/,
|
||||||
|
use: ['file-loader'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.css$/i,
|
||||||
|
use: ['style-loader', 'css-loader'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
optimization: {
|
||||||
|
minimizer: [
|
||||||
|
new ESBuildMinifyPlugin({
|
||||||
|
target: 'es2018',
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
new ForkTsCheckerWebpackPlugin(),
|
||||||
|
new HtmlWebpackPlugin({
|
||||||
|
template: 'src/index.html',
|
||||||
|
}),
|
||||||
|
new webpack.DefinePlugin({
|
||||||
|
'process.env.ENV': JSON.stringify(environment),
|
||||||
|
'process.env.COMMUNITY_KEY': JSON.stringify(communityKey),
|
||||||
|
}),
|
||||||
|
new webpack.ProvidePlugin({
|
||||||
|
process: 'process/browser.js',
|
||||||
|
Buffer: ['buffer', 'Buffer'],
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
devServer: {
|
||||||
|
historyApiFallback: true,
|
||||||
|
host: '0.0.0.0',
|
||||||
|
stats: 'errors-only',
|
||||||
|
overlay: true,
|
||||||
|
hot: true,
|
||||||
|
},
|
||||||
|
stats: 'minimal',
|
||||||
|
};
|
||||||
|
};
|
54
yarn.lock
54
yarn.lock
|
@ -1343,6 +1343,60 @@ __metadata:
|
||||||
languageName: unknown
|
languageName: unknown
|
||||||
linkType: soft
|
linkType: soft
|
||||||
|
|
||||||
|
"@waku/react-group-chat-sdk-example@workspace:packages/react-group-chat-example":
|
||||||
|
version: 0.0.0-use.local
|
||||||
|
resolution: "@waku/react-group-chat-sdk-example@workspace:packages/react-group-chat-example"
|
||||||
|
dependencies:
|
||||||
|
"@testing-library/react-hooks": ^7.0.1
|
||||||
|
"@types/chai": ^4.2.21
|
||||||
|
"@types/mocha": ^9.0.0
|
||||||
|
"@types/node": ^16.4.12
|
||||||
|
"@types/react": ^17.0.15
|
||||||
|
"@types/react-dom": ^17.0.9
|
||||||
|
"@types/react-router": ^5.1.16
|
||||||
|
"@types/react-router-dom": ^5.1.8
|
||||||
|
"@types/styled-components": ^5.1.12
|
||||||
|
"@typescript-eslint/eslint-plugin": ^4.29.0
|
||||||
|
"@typescript-eslint/parser": ^4.29.0
|
||||||
|
"@waku/react-chat-sdk": ^0.1.0
|
||||||
|
assert: ^2.0.0
|
||||||
|
browserify-zlib: ^0.2.0
|
||||||
|
buffer: ^6.0.3
|
||||||
|
chai: ^4.3.4
|
||||||
|
crypto-browserify: ^3.12.0
|
||||||
|
css-loader: ^6.3.0
|
||||||
|
esbuild-loader: ^2.15.1
|
||||||
|
eslint: ^7.32.0
|
||||||
|
eslint-plugin-hooks: ^0.2.0
|
||||||
|
eslint-plugin-react: ^7.24.0
|
||||||
|
file-loader: ^6.2.0
|
||||||
|
fork-ts-checker-webpack-plugin: ^6.3.1
|
||||||
|
html-webpack-plugin: ^5.3.2
|
||||||
|
https-browserify: ^1.0.0
|
||||||
|
jsdom: ^16.7.0
|
||||||
|
jsdom-global: ^3.0.2
|
||||||
|
mocha: ^9.0.3
|
||||||
|
npm-run-all: ^4.1.5
|
||||||
|
prettier: ^2.3.2
|
||||||
|
process: ^0.11.10
|
||||||
|
react: ^17.0.2
|
||||||
|
react-dom: ^17.0.2
|
||||||
|
react-router-dom: ^5.2.0
|
||||||
|
rimraf: ^3.0.2
|
||||||
|
source-map-loader: ^3.0.0
|
||||||
|
stream-browserify: ^3.0.0
|
||||||
|
stream-http: ^3.2.0
|
||||||
|
style-loader: ^3.3.0
|
||||||
|
styled-components: ^5.3.1
|
||||||
|
ts-loader: ^9.2.5
|
||||||
|
ts-node: ^10.1.0
|
||||||
|
typescript: ^4.3.5
|
||||||
|
webpack: ^5.48.0
|
||||||
|
webpack-cli: ^4.7.2
|
||||||
|
webpack-dev-server: ^3.11.2
|
||||||
|
languageName: unknown
|
||||||
|
linkType: soft
|
||||||
|
|
||||||
"@waku/status-communities@^0.1.0, @waku/status-communities@workspace:packages/status-communities":
|
"@waku/status-communities@^0.1.0, @waku/status-communities@workspace:packages/status-communities":
|
||||||
version: 0.0.0-use.local
|
version: 0.0.0-use.local
|
||||||
resolution: "@waku/status-communities@workspace:packages/status-communities"
|
resolution: "@waku/status-communities@workspace:packages/status-communities"
|
||||||
|
|
Loading…
Reference in New Issue