diff --git a/packages/examples/sdk-frontend-react/src/app/ChatUITest/ChatProfile.tsx b/packages/examples/sdk-frontend-react/src/app/ChatUITest/ChatProfile.tsx new file mode 100644 index 000000000..b889f7daf --- /dev/null +++ b/packages/examples/sdk-frontend-react/src/app/ChatUITest/ChatProfile.tsx @@ -0,0 +1,15 @@ +import { ChatProfile } from "@pushprotocol/uiweb"; + +export const ChatProfileTest = () => { + + return ( +
+ +
+ ) +} diff --git a/packages/examples/sdk-frontend-react/src/app/ChatUITest/ChatUITest.tsx b/packages/examples/sdk-frontend-react/src/app/ChatUITest/ChatUITest.tsx index 936f67a76..7dd92f1ff 100644 --- a/packages/examples/sdk-frontend-react/src/app/ChatUITest/ChatUITest.tsx +++ b/packages/examples/sdk-frontend-react/src/app/ChatUITest/ChatUITest.tsx @@ -26,11 +26,17 @@ const ChatUITest = () => {
+ + CHAT PROFILE + CHAT BUBBLE - MESSAGE LIST + CHAT VIEW LIST + + + CHAT VIEW COMPONENT
diff --git a/packages/examples/sdk-frontend-react/src/app/ChatUITest/MessageBubbles.tsx b/packages/examples/sdk-frontend-react/src/app/ChatUITest/ChatViewBubble.tsx similarity index 58% rename from packages/examples/sdk-frontend-react/src/app/ChatUITest/MessageBubbles.tsx rename to packages/examples/sdk-frontend-react/src/app/ChatUITest/ChatViewBubble.tsx index d3f70a954..d9d184845 100644 --- a/packages/examples/sdk-frontend-react/src/app/ChatUITest/MessageBubbles.tsx +++ b/packages/examples/sdk-frontend-react/src/app/ChatUITest/ChatViewBubble.tsx @@ -1,16 +1,16 @@ -import { MessageBubble } from "@pushprotocol/uiweb"; +import { ChatViewBubble } from "@pushprotocol/uiweb"; import { useEffect, useContext, useState } from "react"; import { EnvContext, Web3Context } from "../context"; import * as PUSHAPI from "@pushprotocol/restapi" import { ENV } from "@pushprotocol/uiweb"; import { IMessagePayload } from "@pushprotocol/uiweb"; -export const MessageBubbles = () => { +export const ChatViewBubbles = () => { const { env } = useContext(EnvContext); const { library, account } = useContext(Web3Context) const [message, setMessage] = useState([]) - const [ conversationHash , setConversationHash] = useState(''); + const [conversationHash, setConversationHash] = useState(''); const librarySigner = library.getSigner() @@ -26,22 +26,22 @@ export const MessageBubbles = () => { const ConversationHash = await PUSHAPI.chat.conversationHash({ account: `eip155:${account}`, - conversationId: '24b029b8e07e60291bf9d8c0c48ff993fa1e0a99105459f7404c425c92e91bac', + conversationId: '831b1d93f36fa2fce6c3d8c7c41c53335c82ad13cbe05478579af235f10716dc', env: env }); setConversationHash(ConversationHash.threadHash); - if(ConversationHash?.threadHash){ - const chatHistory = await PUSHAPI.chat.history({ - threadhash: conversationHash, - account: account, - limit: 10, - toDecrypt: true, - pgpPrivateKey: pgpPrivateKey ? pgpPrivateKey : undefined, - env: env - }) - setMessage(chatHistory) - console.log(chatHistory) - } + if (ConversationHash?.threadHash) { + const chatHistory = await PUSHAPI.chat.history({ + threadhash: conversationHash, + account: account, + limit: 10, + toDecrypt: true, + pgpPrivateKey: pgpPrivateKey ? pgpPrivateKey : undefined, + env: env + }) + setMessage(chatHistory) + console.log(chatHistory) + } } useEffect(() => { @@ -49,9 +49,9 @@ export const MessageBubbles = () => { }, []) return ( -
+
{message.map((msg) => ( - + ))}
) diff --git a/packages/examples/sdk-frontend-react/src/app/ChatUITest/ChatViewComponent.tsx b/packages/examples/sdk-frontend-react/src/app/ChatUITest/ChatViewComponent.tsx new file mode 100644 index 000000000..bf1273fa6 --- /dev/null +++ b/packages/examples/sdk-frontend-react/src/app/ChatUITest/ChatViewComponent.tsx @@ -0,0 +1,30 @@ +import styled from 'styled-components'; + +import { Section } from '../components/StyledComponents'; + +import { ChatViewComponent } from '@pushprotocol/uiweb'; + + +const ChatViewComponentTest = () => { + + + + return ( +
+

Chat UI Test page

+ + {/* */} + + + + +
+ ); +}; + +export default ChatViewComponentTest; + + +const ChatViewComponentCard = styled(Section)` +height:60vh; +`; \ No newline at end of file diff --git a/packages/examples/sdk-frontend-react/src/app/ChatUITest/ChatViewListTest.tsx b/packages/examples/sdk-frontend-react/src/app/ChatUITest/ChatViewListTest.tsx new file mode 100644 index 000000000..28eec588d --- /dev/null +++ b/packages/examples/sdk-frontend-react/src/app/ChatUITest/ChatViewListTest.tsx @@ -0,0 +1,39 @@ +import { useContext, useEffect, useState } from 'react'; +import styled from 'styled-components'; +import * as PUSHAPI from '@pushprotocol/restapi'; +import { Link } from 'react-router-dom'; +import { Section } from '../components/StyledComponents'; +import { ChatViewList } from '@pushprotocol/uiweb'; +import { EnvContext, Web3Context } from '../context'; +import { usePushChatSocket } from '@pushprotocol/uiweb'; +import { MessageInput } from '@pushprotocol/uiweb'; + +const ChatViewListTest = () => { + const { account, pgpPrivateKey } = useContext(Web3Context) + + const { env } = useContext(EnvContext); + + + usePushChatSocket(); + return ( +
+

Chat UI Test page

+ + {/* */} + + + + + + +
+ ); +}; + +export default ChatViewListTest; + + +const ChatViewListCard = styled(Section)` +height:40vh; +background:black; +`; \ No newline at end of file diff --git a/packages/examples/sdk-frontend-react/src/app/ChatUITest/MessageListTest.tsx b/packages/examples/sdk-frontend-react/src/app/ChatUITest/MessageListTest.tsx deleted file mode 100644 index 27cf0844c..000000000 --- a/packages/examples/sdk-frontend-react/src/app/ChatUITest/MessageListTest.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import { useContext, useEffect, useState } from 'react'; -import styled from 'styled-components'; -import * as PUSHAPI from '@pushprotocol/restapi'; -import { Link } from 'react-router-dom'; -import { Section } from '../components/StyledComponents'; -import { MessageList } from '@pushprotocol/uiweb'; -import { EnvContext, Web3Context } from '../context'; -import { usePushChatSocket } from '@pushprotocol/uiweb'; - -const MessageListTest = () => { - const { account } = useContext(Web3Context) - - const { env } = useContext(EnvContext); - const [ conversationHash , setConversationHash] = useState(''); - - const fetchConversationHash = async() =>{ - const ConversationHash = await PUSHAPI.chat.conversationHash({ - account: `eip155:${account}`, - conversationId: '24b029b8e07e60291bf9d8c0c48ff993fa1e0a99105459f7404c425c92e91bac', - env: env - }); - setConversationHash(ConversationHash.threadHash); - } -console.log(conversationHash) - useEffect(()=>{ - fetchConversationHash(); - }) - - usePushChatSocket(); - return ( -
-

Chat UI Test page

- - {/* */} - - - - - - -
- ); -}; - -export default MessageListTest; - - -const MessageListCard = styled(Section)` -height:40vh; -`; \ No newline at end of file diff --git a/packages/examples/sdk-frontend-react/src/app/app.tsx b/packages/examples/sdk-frontend-react/src/app/app.tsx index 0acb0d4f5..a84f521dd 100644 --- a/packages/examples/sdk-frontend-react/src/app/app.tsx +++ b/packages/examples/sdk-frontend-react/src/app/app.tsx @@ -1,4 +1,4 @@ -import { useEffect, useMemo, useState } from 'react'; +import { useContext, useEffect, useMemo, useState } from 'react'; import styled from 'styled-components'; import { Route, Routes, Link } from 'react-router-dom'; import { useWeb3React } from '@web3-react/core'; @@ -74,10 +74,13 @@ import { ChatUIProvider, SpacesUI, SpacesUIProvider, + darkChatTheme, } from '@pushprotocol/uiweb'; import ChatUITest from './ChatUITest/ChatUITest'; -import MessageListTest from './ChatUITest/MessageListTest'; -import { MessageBubbles } from './ChatUITest/MessageBubbles'; +import { ChatProfileTest } from './ChatUITest/ChatProfile'; +import ChatViewListTest from './ChatUITest/ChatViewListTest'; +import { ChatViewBubbles } from './ChatUITest/ChatViewBubble'; +import ChatViewComponentTest from './ChatUITest/ChatViewComponent'; import { lightChatTheme } from '@pushprotocol/uiweb'; window.Buffer = window.Buffer || Buffer; @@ -209,8 +212,7 @@ const checkForWeb3Data = ({ export function App() { const { account, library, active, chainId } = useWeb3React(); - - const [env, setEnv] = useState(ENV.STAGING); + const [env, setEnv] = useState(ENV.PROD); const [isCAIP, setIsCAIP] = useState(false); const { SpaceWidgetComponent } = useSpaceComponents(); @@ -303,60 +305,60 @@ export function App() { - - - - - - NOTIFICATIONS - - - SECRET NOTIFICATION - - - CHANNELS - - - ALIAS - - - DELEGATIONS - - - PAYLOADS - - - SOCKET - - - EMBED - - - CHAT - - - CHAT UI - - - SPACE - - - SPACE UI - - - } - /> - } - /> - } - /> + + + + + + NOTIFICATIONS + + + SECRET NOTIFICATION + + + CHANNELS + + + ALIAS + + + DELEGATIONS + + + PAYLOADS + + + SOCKET + + + EMBED + + + CHAT + + + CHAT UI + + + SPACE + + + SPACE UI + + + } + /> + } + /> + } + /> } /> @@ -505,27 +507,35 @@ export function App() { /> - {/* spaces ui components routes */} - } /> - } /> - } /> - } - /> - } - /> + {/* spaces ui components routes */} + } /> + } /> + } /> + } + /> + } + /> - {/* chat ui components routes */} - } + {/* chat ui components routes */} + } /> - } + element={} + /> + } + /> + } /> {/* */} @@ -536,7 +546,17 @@ export function App() { - ) : null} + ) : + + + } /> + } + /> + + + } ); diff --git a/packages/restapi/CHANGELOG.md b/packages/restapi/CHANGELOG.md index d1fc77947..31e752569 100644 --- a/packages/restapi/CHANGELOG.md +++ b/packages/restapi/CHANGELOG.md @@ -2,6 +2,7 @@ This file was generated using [@jscutlery/semver](https://github.com/jscutlery/semver). +## [1.4.9](https://github.com/ethereum-push-notification-service/push-sdk/compare/restapi-1.4.8...restapi-1.4.9) (2023-08-11) ## [0.0.1-alpha.29](https://github.com/ethereum-push-notification-service/push-sdk/compare/restapi-0.0.1-alpha.28...restapi-0.0.1-alpha.29) (2023-08-02) diff --git a/packages/uiweb/CHANGELOG.md b/packages/uiweb/CHANGELOG.md index 59cb82a9b..4589c675c 100644 --- a/packages/uiweb/CHANGELOG.md +++ b/packages/uiweb/CHANGELOG.md @@ -2,11 +2,49 @@ This file was generated using [@jscutlery/semver](https://github.com/jscutlery/semver). +## [1.1.10](https://github.com/ethereum-push-notification-service/push-sdk/compare/uiweb-1.1.9...uiweb-1.1.10) (2023-08-10) + + + +## [1.1.9](https://github.com/ethereum-push-notification-service/push-sdk/compare/uiweb-1.1.8...uiweb-1.1.9) (2023-08-10) ## [0.0.1-alpha.0](https://github.com/ethereum-push-notification-service/push-sdk/compare/uiweb-1.0.1...uiweb-0.0.1-alpha.0) (2023-08-09) ### Bug Fixes +* hide few spaces features ([#622](https://github.com/ethereum-push-notification-service/push-sdk/issues/622)) ([0e2556c](https://github.com/ethereum-push-notification-service/push-sdk/commit/0e2556c6bbe3438cd30851ffdd9764b027f42f6e)) +* Merge branch 'main' into deployment ([843cd01](https://github.com/ethereum-push-notification-service/push-sdk/commit/843cd0169a270bbab69922021edf312616de3802)) + + + +## [1.1.8](https://github.com/ethereum-push-notification-service/push-sdk/compare/uiweb-1.1.7...uiweb-1.1.8) (2023-08-04) + + +### Bug Fixes + +* merge main ([b9e4440](https://github.com/ethereum-push-notification-service/push-sdk/commit/b9e44408fa4c97720b12217486e8d13ef3caeb00)) + + + +## [1.1.7](https://github.com/ethereum-push-notification-service/push-sdk/compare/uiweb-1.1.6...uiweb-1.1.7) (2023-07-31) + + +### Bug Fixes + +* Merge branch 'main' into deployment ([9755baf](https://github.com/ethereum-push-notification-service/push-sdk/commit/9755baf3d4bcd3ab3fd365fad9d8fb7623fda58f)) + + + +## [1.1.6](https://github.com/ethereum-push-notification-service/push-sdk/compare/uiweb-1.1.5...uiweb-1.1.6) (2023-07-28) + + +### Bug Fixes + +* Merge branch 'main' into deployment ([e33017a](https://github.com/ethereum-push-notification-service/push-sdk/commit/e33017afb2d4e9361d5df47e0f7e726ecdffbc32)) + + + +## [1.1.5](https://github.com/ethereum-push-notification-service/push-sdk/compare/uiweb-1.1.4...uiweb-1.1.5) (2023-07-27) * add alpha support to UI web ([8ecf5d9](https://github.com/ethereum-push-notification-service/push-sdk/commit/8ecf5d92cc1b5c25562ba0d6d1ae1137877a5be7)) * fixed bugs ([#566](https://github.com/ethereum-push-notification-service/push-sdk/issues/566)) ([481d8fc](https://github.com/ethereum-push-notification-service/push-sdk/commit/481d8fcd7c40325654ba490640daabc38ee2f96e)) * fixed build issues ([#550](https://github.com/ethereum-push-notification-service/push-sdk/issues/550)) ([0ce6e18](https://github.com/ethereum-push-notification-service/push-sdk/commit/0ce6e18a82901478fe3157788c716e5224f14bdb)) diff --git a/packages/uiweb/package-lock.json b/packages/uiweb/package-lock.json index a204cad1a..898a92ce8 100644 --- a/packages/uiweb/package-lock.json +++ b/packages/uiweb/package-lock.json @@ -1,12 +1,12 @@ { "name": "@pushprotocol/uiweb", - "version": "1.1.4", + "version": "0.0.1-alpha.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@pushprotocol/uiweb", - "version": "1.1.4", + "version": "0.0.1-alpha.0", "dependencies": { "@livepeer/react": "^2.6.0", "@pushprotocol/socket": "^0.5.0", @@ -17,6 +17,7 @@ "gif-picker-react": "^1.1.0", "html-react-parser": "^1.4.13", "moment": "^2.29.4", + "react-toastify": "^9.1.3", "react-twitter-embed": "^4.0.4" }, "peerDependencies": { @@ -3380,6 +3381,18 @@ } } }, + "node_modules/react-toastify": { + "version": "9.1.3", + "resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-9.1.3.tgz", + "integrity": "sha512-fPfb8ghtn/XMxw3LkxQBk3IyagNpF/LIKjOBflbexr2AWxAH1MJgvnESwEwBn9liLFXgTKWgBSdZpw9m4OTHTg==", + "dependencies": { + "clsx": "^1.1.1" + }, + "peerDependencies": { + "react": ">=16", + "react-dom": ">=16" + } + }, "node_modules/react-twitter-embed": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/react-twitter-embed/-/react-twitter-embed-4.0.4.tgz", diff --git a/packages/uiweb/package.json b/packages/uiweb/package.json index 9e52107ad..7e3eea05b 100644 --- a/packages/uiweb/package.json +++ b/packages/uiweb/package.json @@ -8,12 +8,15 @@ "@livepeer/react": "^2.6.0", "@pushprotocol/socket": "^0.5.0", "@unstoppabledomains/resolution": "^8.5.0", + "@web3-react/injected-connector": "^6.0.7", "date-fns": "^2.28.0", "emoji-picker-react": "^4.4.9", "font-awesome": "^4.7.0", "gif-picker-react": "^1.1.0", "html-react-parser": "^1.4.13", "moment": "^2.29.4", + "react-icons": "^4.10.1", + "react-toastify": "^9.1.3", "react-twitter-embed": "^4.0.4" }, "peerDependencies": { diff --git a/packages/uiweb/src/lib/components/chat/ChatProfile/AddWalletContent.tsx b/packages/uiweb/src/lib/components/chat/ChatProfile/AddWalletContent.tsx new file mode 100644 index 000000000..0ba4639b9 --- /dev/null +++ b/packages/uiweb/src/lib/components/chat/ChatProfile/AddWalletContent.tsx @@ -0,0 +1,380 @@ +import { useContext, useEffect, useState } from "react"; +import styled from "styled-components"; +import { ThemeContext } from "../theme/ThemeProvider"; +import { useChatData } from "../../../hooks"; +import { displayDefaultUser, getAddress, walletToPCAIP10 } from "../../../helpers"; +import { IToast, ModalButtonProps, User } from "../exportedTypes"; +import * as PushAPI from '@pushprotocol/restapi'; +import { ethers } from "ethers"; +import { addWalletValidation } from "../helpers/helper"; +import ArrowGreyIcon from '../../../icons/CaretDownGrey.svg' +import ArrowLeftIcon from '../../../icons/ArrowLeft.svg'; +import CloseIcon from '../../../icons/close.svg'; +import { Spinner } from "../../supportChat/spinner/Spinner"; +import { MoreLightIcon } from '../../../icons/MoreLight'; +import { MoreDarkIcon } from '../../../icons/MoreDark'; +import { SearchIcon } from '../../../icons/SearchIcon'; +import { Section, Span, Image } from "../../reusables/sharedStyling"; +import { AddUserDarkIcon } from '../../../icons/Adddark'; +import { device } from "../../../config"; +import { MemberListContainer } from "./MemberListContainer"; +import useMediaQuery from "../helpers/useMediaQuery"; +import useToast from "../helpers/NewToast"; +import { MdCheckCircle, MdError } from "react-icons/md"; +import { Modal } from "../helpers/Modal"; + + + +export const AddWalletContent = ({ onSubmit, handlePrevious, onClose, memberList, handleMemberList, title, groupMembers, isLoading }: {onSubmit: ()=> void ,onClose: ()=> void, handlePrevious: ()=> void, memberList: any, handleMemberList: any, title: string, groupMembers: any, isLoading?: boolean }) => { + const theme = useContext(ThemeContext); + + const [searchedUser, setSearchedUser] = useState(''); + const [filteredUserData, setFilteredUserData] = useState(null); + const [isInValidAddress, setIsInvalidAddress] = useState(false); + const [isLoadingSearch, setIsLoadingSearch] = useState(false); + const { account, env } = useChatData(); + const isMobile = useMediaQuery(device.mobileL); + const groupInfoToast = useToast(); + + + useEffect(() => { + if (isInValidAddress) { + groupInfoToast.showMessageToast({ + toastTitle: 'Error', + toastMessage: 'Invalid Address', + toastType: 'ERROR', + getToastIcon: (size) => ( + + ), + }); + } + }, [isInValidAddress]); + + const onChangeSearchBox = (e: any) => { + setSearchedUser(e.target.value); + }; + + const handleUserSearch = async (userSearchData: string): Promise => { + try{ + const caip10 = walletToPCAIP10(userSearchData); + let filteredData: User; + + if (userSearchData.length) { + filteredData = await PushAPI.user.get({ + account: caip10, + env: env + }); + + if (filteredData !== null) { + setFilteredUserData(filteredData); + } + // User is not in the protocol. Create new user + else { + if (ethers.utils.isAddress(userSearchData)) { + const displayUser = displayDefaultUser({ caip10 }); + setFilteredUserData(displayUser); + } else { + setIsInvalidAddress(true); + setFilteredUserData(null); + } + } + } else { + setFilteredUserData(null); + } + setIsLoadingSearch(false); + } + catch(error){ + groupInfoToast.showMessageToast({ + toastTitle: 'Error', + toastMessage: 'Unsuccessful search, Try again', + toastType: 'ERROR', + getToastIcon: (size) => ( + + ), + }); + } + }; + + const handleSearch = async (e: any): Promise => { + setIsLoadingSearch(true); + setIsInvalidAddress(false); + e.preventDefault(); + if (!ethers.utils.isAddress(searchedUser)) { + let address: string; + try { + address = await getAddress(searchedUser, env) as string; + // if (!address) { + // address = await library.resolveName(searchedUser); + // } + // this ensures address are checksummed + address = ethers.utils.getAddress(address?.toLowerCase()); + if (address) { + handleUserSearch(address); + } else { + setIsInvalidAddress(true); + setFilteredUserData(null); + } + } catch (err) { + setIsInvalidAddress(true); + setFilteredUserData(null); + } finally { + setIsLoadingSearch(false); + } + } else { + handleUserSearch(searchedUser); + } + }; + + const clearInput = () => { + setSearchedUser(''); + setFilteredUserData(null); + setIsLoadingSearch(false); + }; + + const addMemberToList = (member: User) => { + let errorMessage = ''; + + errorMessage = addWalletValidation(member, memberList, groupMembers, account); + + if (errorMessage) { + groupInfoToast.showMessageToast({ + toastTitle: 'Error', + toastMessage: errorMessage, + toastType: 'ERROR', + getToastIcon: (size) => ( + + ), + }); + } else { + handleMemberList((prev: any) => [...prev, { ...member, isAdmin: false }]); + } + + setFilteredUserData(''); + clearInput(); + }; + + const removeMemberFromList = (member: User) => { + const filteredMembers = memberList?.filter((user: any) => user.wallets !== member.wallets); + handleMemberList(filteredMembers); + }; + + return ( +
+
+ + handlePrevious()} cursor='pointer' /> + + Add Wallets + + onClose()} cursor='pointer' /> +
+ +
+ Add Wallets + + + {groupMembers + ? `0${memberList?.length + groupMembers?.length} / 09 Members` + : `0${memberList?.length} / 09 Members`} + +
+ +
+ + +
+ {searchedUser.length > 0 && ( + clearInput()} cursor='pointer' /> + )} + {searchedUser.length == 0 && !filteredUserData && +
+ +
+ } +
+
+
+ + {filteredUserData ? ( + + } + darkIcon={} + /> + + ) : isLoadingSearch ? ( +
+ +
+ ) : null} + + + {memberList?.map((member: any, index: any) => ( + } + darkIcon={} + /> + ))} + + +
+ onSubmit()} + isLoading={isLoading} + memberListCount={memberList?.length > 0} + theme={theme} + > + {!isLoading && groupMembers ? 'Add To Group' : ''} + {isLoading && } + +
+ +
+ ) +} + +const SearchBarContent = styled.form` + position: relative; + display: flex; + flex: 1; +`; + +const Input = styled.input` + box-sizing: border-box; + display: flex; + flex: 1; +// min-width: 445px; + height: 48px; + padding: 0px 50px 0px 16px; + margin: 10px 0px 0px; + border-radius: 99px; + border: 1px solid; + border-color: ${(props) => props.theme.modalSearchBarBorderColor}; + background: ${(props) => props.theme.modalSearchBarBackground}; + color: ${(props) => props.color || '#000'}; + &:focus { + outline: none; + background-image: linear-gradient( + ${(props) => props.theme.snapFocusBg}, + ${(props) => props.theme.snapFocusBg} + ), + linear-gradient( + to right, + rgba(182, 160, 245, 1), + rgba(244, 110, 246, 1), + rgba(255, 222, 211, 1), + rgba(255, 207, 197, 1) + ); + background-origin: border; + border: 1px solid transparent !important; + background-clip: padding-box, border-box; + } + &::placeholder { + color: #657795; + } + @media ${device.mobileL} { + min-width: 100%; + } +`; + +const MemberList = styled.div` + // justify-content: flex-start; + // padding: 0px 2px; + // margin: 0 0 34px 0; + flex: 1; + // background: red; + width: 100%; +`; + +const MultipleMemberList = styled.div` + // overflow-y: auto; + height: fit-content; + max-height: 216px; + padding: 0px 2px; + // overflow-x: hidden; + width: 100%; + + &::-webkit-scrollbar-track { + background-color: ${(props) => props.theme.scrollBg}; + } + + &::-webkit-scrollbar { + background-color: ${(props) => props.theme.scrollBg}; + width: 6px; + } + + @media (max-width: 768px) { + padding: 0px 0px 0px 0px; + max-height: 35vh; + + &::-webkit-scrollbar-track { + background-color: none; + border-radius: 9px; + } + + &::-webkit-scrollbar { + background-color: none; + width: 4px; + } + } + + &::-webkit-scrollbar-thumb { + border-radius: 10px; + background-image: -webkit-gradient( + linear, + left top, + left bottom, + color-stop(0.44, #cf1c84), + color-stop(0.72, #cf1c84), + color-stop(0.86, #cf1c84) + ); + } +`; + +const ModalConfirmButton = styled.button` + margin: 60px 0 0 0; + background: ${(props) => props.memberListCount ? '#CF1C84' : props.theme.groupButtonBackgroundColor}; + color: ${(props) => props.memberListCount ? '#fff' : props.theme.groupButtonTextColor}; + border: ${(props) => props.memberListCount ? 'none' : props.theme.modalConfirmButtonBorder}; + min-width: 50%; + box-sizing: border-box; + cursor: pointer; + border-radius: 15px; + padding: 16px; + font-size: 1.125rem; + font-weight: 500; + display: flex; + align-items: center; + justify-content: center; + box-shadow: none; +`; \ No newline at end of file diff --git a/packages/uiweb/src/lib/components/chat/ChatProfile/ChatProfile.tsx b/packages/uiweb/src/lib/components/chat/ChatProfile/ChatProfile.tsx new file mode 100644 index 000000000..4949a44b6 --- /dev/null +++ b/packages/uiweb/src/lib/components/chat/ChatProfile/ChatProfile.tsx @@ -0,0 +1,241 @@ +// @typescript-eslint/no-non-null-asserted-optional-chain + +import { useContext, useEffect, useRef, useState } from "react"; +import { Image, Section, Span } from "../../reusables"; +import styled from "styled-components"; +import TokenGatedIcon from '../../../icons/Token-Gated.svg'; +import PublicChatIcon from '../../../icons/Public-Chat.svg'; +import VideoChatIcon from '../../../icons/VideoCallIcon.svg'; +import GreyImage from '../../../icons/greyImage.png'; +import InfoIcon from '../../../icons/infodark.svg'; +import VerticalEllipsisIcon from '../../../icons/VerticalEllipsis.svg'; +import type { IUser } from '@pushprotocol/restapi'; +import { useChatData, useClickAway, useDeviceWidthCheck } from "../../../hooks"; +import { ThemeContext } from "../theme/ThemeProvider"; +import { IChatTheme } from "../theme"; +import { pCAIP10ToWallet, resolveEns, resolveNewEns, shortenText } from "../../../helpers"; +import useGetGroupByID from "../../../hooks/chat/useGetGroupByID"; +import useChatProfile from "../../../hooks/chat/useChatProfile"; +import { IGroup } from "../../../types"; +import { GroupInfoModal } from "./GroupInfoModal"; +import { isValidETHAddress } from "../helpers/helper"; +import { ethers } from "ethers"; +import { IChatProfile, IToast, OptionProps } from "../exportedTypes"; +import { InfuraAPIKey, allowedNetworks, device } from "../../../config"; +import Toast from "../helpers/Toast"; +import useMediaQuery from "../helpers/useMediaQuery"; +import { createBlockie } from "../../space/helpers/blockies"; +// import { NewToast } from "../helpers/NewToast"; +import { ToastContainer, toast } from 'react-toastify'; +import 'react-toastify/dist/ReactToastify.min.css'; + + + +const Options = ({ options, setOptions, isGroup, chatInfo, groupInfo, setGroupInfo,theme }: OptionProps) => { + const DropdownRef = useRef(null); + const [modal, setModal] = useState(false); + + useClickAway(DropdownRef, () => { + setOptions(false); + }); + + const ShowModal = () => { + setModal(true); + } + + if (groupInfo && isGroup){ + return ( +
+ + + {groupInfo?.isPublic && + ()} + + setOptions(true)}> + + + {options && + ( + + + + + Group Info + + + )} + + {modal && + ()} + +
+ ) + } else { + return null } + }; + + + + + +export const ChatProfile: React.FC = ({ chatId, style }: {chatId: string, style: "Info" | "Preview"}) => { + const theme = useContext(ThemeContext); + const { account, env } = useChatData(); + const { getGroupByID } = useGetGroupByID(); + const { fetchUserChatProfile } = useChatProfile(); + + const [isGroup, setIsGroup] = useState(false); + const [options, setOptions] = useState(false); + const [chatInfo, setChatInfo ] = useState(); + const [groupInfo, setGroupInfo ] = useState(); + const [ensName, setEnsName ] = useState(''); + const isMobile = useMediaQuery(device.tablet); + const l1ChainId = allowedNetworks[env].includes(1) ? 1 : 5; + const provider = new ethers.providers.InfuraProvider(l1ChainId, InfuraAPIKey); + + + + const fetchProfileData = async () => { + if(isValidETHAddress(chatId)){ + const ChatProfile = await fetchUserChatProfile({ profileId: chatId }); + setChatInfo(ChatProfile); + setGroupInfo(null); + setIsGroup(false); + } else { + const GroupProfile = await getGroupByID({ groupId : chatId}) + setGroupInfo(GroupProfile); + setChatInfo(null); + setIsGroup(true); + } + } + + const getName = async (chatId: string) => { + if(isValidETHAddress(chatId)){ + const result = await resolveNewEns(chatId, provider); + // if(result) + console.log(result); + setEnsName(result); + } + } + + + useEffect(()=> { + if(!chatId) return; + fetchProfileData(); + getName(chatId); + },[chatId, account, env]) + + if (chatId && style === 'Info') { + return ( + + {chatInfo || groupInfo ? ( + + ) : ()} + + + + {isGroup ? groupInfo?.groupName : ensName ? `${ensName} (${isMobile ? shortenText(chatInfo?.did?.split(':')[1] ?? '', 4, true) : chatId})`: chatInfo ? shortenText(chatInfo.did?.split(':')[1] ?? '', 6, true) : shortenText(chatId,6, true)} + + + + + + {/* {!isGroup && + + + + } */} + + + + + + ) + } else { + return null; + } +} + + +const Container = styled.div` + width: 100%; + background: ${(props) => props.theme.bgColorPrimary}; + border-radius: 32px; + display: flex; + flex-direction: row; + align-items: center; + padding: 6px; + box-sizing: border-box; + position: relative; +`; + +const ImageItem = styled.div` + position: relative; +`; + +const DummyImage = styled.div` + height: 48px; + width: 48px; + border-radius: 100%; + background: #ccc; +`; + +const DropDownBar = styled.div` + position: absolute; + top: 30px; + left: -130px; + display: block; + min-width: 140px; + color: rgb(101, 119, 149); + border: ${(props) => `1px solid ${props.theme.defaultBorder}`}; + background: ${(props) => props.theme.bgColorPrimary}; + z-index: 10; + border-radius: 16px; +`; + +const VideoChatSection = styled.div` + margin: 0 25px 0 auto; +`; + +const DropDownItem = styled(Span)` + display: flex; + flex-direction: row; + align-items: center; + gap: 8px; + padding: 10px 16px; + border-radius: 16px; + z-index: 3000000; + width: 100%; +`; + +const TextItem = styled(Span)` + white-space: nowrap; + overflow: hidden; +`; + + + + + + + + + + + diff --git a/packages/uiweb/src/lib/components/chat/ChatProfile/DropDown.tsx b/packages/uiweb/src/lib/components/chat/ChatProfile/DropDown.tsx new file mode 100644 index 000000000..991c1e6c8 --- /dev/null +++ b/packages/uiweb/src/lib/components/chat/ChatProfile/DropDown.tsx @@ -0,0 +1,205 @@ +// React + Web3 Essentials +import { useContext } from 'react'; + +// External Packages +import styled from 'styled-components'; +import { shortenText } from "../../../helpers"; + +// Internal Components +import { Image, Section, Span } from "../../reusables"; +import { ThemeContext } from "../theme/ThemeProvider"; + + +export type DropdownValueType = { + id: number|string, + value?: string, + title: string, + icon: string, + textColor?: string, + function: () => void, +} + +type DropdownProps = { + dropdownValues: any[]; + textColor?: string; + iconFilter?: string; + hoverBGColor?: string; +}; + + +// Create Dropdown +function Dropdown({ dropdownValues, textColor, iconFilter, hoverBGColor }: DropdownProps) { + const theme = useContext(ThemeContext); + + + const getTextColor = (dropdownValue:DropdownValueType) => { + return dropdownValue.textColor ? dropdownValue.textColor : textColor ? textColor : theme.snackbarBorderText; + } + + + const copyToClipboard = (address:string) => { + if (navigator && navigator.clipboard) { + navigator.clipboard.writeText(address); + } else { + const el = document.createElement('textarea'); + el.value = address; + document.body.appendChild(el); + el.select(); + document.execCommand('copy'); + document.body.removeChild(el); + } + }; + return ( + <> + {dropdownValues.map((dropdownValue) => + dropdownValue?.id === 'walletAddress' ? ( +
dropdownValue?.function()} + > + + {dropdownValue?.title} + + {shortenText(dropdownValue?.title,6)} + + + {dropdownValue?.invertedIcon && ( + icon { + copyToClipboard(dropdownValue?.value); + }} + /> + )} + {dropdownValue?.icon && ( + icon { + copyToClipboard(dropdownValue?.value); + }} + /> + )} +
+ ) : ( + dropdownValue?.function()}> + {dropdownValue?.invertedIcon && ( + icon + )} + {dropdownValue?.icon && ( + icon + )} + {!dropdownValue?.link && dropdownValue?.function && ( + + {dropdownValue.title} + + )} + {dropdownValue?.link && ( + + {dropdownValue.title} + + )} + + ) + )} + + ); +} + +// css styles +const SpanAddress = styled(Span)` + margin: 11px 22px 11px 2px; + font-weight: 400; + size: 14px; + text-transform: uppercase; + color: #fff; + spacing: 1px; + width: max-content; +`; + +const MobileAddress = styled(SpanAddress)` + @media (min-width: 993px) { + display: none; + } +`; + +const DesktopAddress = styled(SpanAddress)` + @media (max-width: 992px) { + display: none; + } +`; + +const DropdownItemContainer = styled(Section)<{hoverBGColor?: string}>` + width: 12.5rem; + justify-content: flex-start; + flex-wrap: nowrap; + margin: 1px 0; + padding: 2px 8px; + border-radius: 12px; + cursor: pointer; + text-align: left; + + &:hover { + background-color: ${(props) => props.hoverBGColor || 'none'}; + } +`; + + +const A = styled.a` + margin: 8px 10px; + font-weight: 400; + font-size: 16px; + width: max-content; + + background: ${(props) => props.color}; + z-index: 11; + &. hover { + background: transparent !important; + } +`; + +export default Dropdown; diff --git a/packages/uiweb/src/lib/components/chat/ChatProfile/GroupInfoModal.tsx b/packages/uiweb/src/lib/components/chat/ChatProfile/GroupInfoModal.tsx new file mode 100644 index 000000000..c5a03ca8b --- /dev/null +++ b/packages/uiweb/src/lib/components/chat/ChatProfile/GroupInfoModal.tsx @@ -0,0 +1,635 @@ +import { useRef, useState } from "react"; +import styled from "styled-components"; +import { useChatData, useClickAway } from "../../../hooks"; +import { IGroup } from "../../../types"; +import { IChatTheme } from "../theme"; +import * as PushAPI from '@pushprotocol/restapi'; +import { IToast, ShadowedProps, UpdateGroupType } from "../exportedTypes"; +import { convertToWalletAddressList, getAdminList, getUpdatedAdminList, getUpdatedMemberList } from "../helpers/helper"; +import { DropdownValueType } from "./DropDown"; +import DismissAdmin from '../../../icons/dismissadmin.svg'; +import AddAdmin from '../../../icons/addadmin.svg'; +import Remove from '../../../icons/remove.svg'; +import { Section, Span, Image } from "../../reusables/sharedStyling"; +import CloseIcon from '../../../icons/close.svg'; +import { ProfileCard } from "./ProfileCard"; +import addIcon from '../../../icons/addicon.svg'; +import { pCAIP10ToWallet, shortenText } from "../../../helpers"; +import LockIcon from '../../../icons/Lock.png' +import LockSlashIcon from '../../../icons/LockSlash.png' +import { AddWalletContent } from './AddWalletContent' +import ArrowIcon from '../../../icons/CaretDown.svg' +import { Modal } from "../helpers/Modal"; +import { device } from "../../../config"; +import useMediaQuery from "../helpers/useMediaQuery"; +import useToast from "../helpers/NewToast"; +import { MdCheckCircle, MdError } from "react-icons/md"; + + + +const PendingMembers = ({ groupInfo, setShowPendingRequests, showPendingRequests, theme }: {groupInfo?: IGroup | null, setShowPendingRequests: React.Dispatch>, showPendingRequests: boolean, theme: IChatTheme }) => { + if(groupInfo){ + return ( + + setShowPendingRequests(!showPendingRequests)}> + Pending Requests + {groupInfo?.pendingMembers?.length} + + + {/* */} + + + {showPendingRequests && ( +
+ {groupInfo?.pendingMembers && groupInfo?.pendingMembers?.length > 0 && groupInfo?.pendingMembers.map((item) => ( + + + + + {shortenText(item?.wallet?.split(':')[1] ?? '', 6, true)} + + + + ))} +
+ )} +
+ ) + } else {return null } +} + +export const GroupInfoModal = ({ theme, modal, setModal, groupInfo, setGroupInfo }: { theme: IChatTheme, modal: boolean, setModal: React.Dispatch>, groupInfo: IGroup, setGroupInfo: React.Dispatch> }) => { + const { account, env, pgpPrivateKey } = useChatData(); + const [showAddMoreWalletModal, setShowAddMoreWalletModal] = useState(false); + const [showPendingRequests, setShowPendingRequests] = useState(false); + const [memberList, setMemberList] = useState([]); + const [isLoading, setIsLoading] = useState(false); + const [selectedMemberAddress, setSelectedMemberAddress] = useState(null); + + const handleClose = () => onClose(); + const dropdownRef = useRef(null); + useClickAway(dropdownRef, () => setSelectedMemberAddress(null)); + const groupInfoToast = useToast(); + + + const groupCreator = groupInfo?.groupCreator; + const membersExceptGroupCreator = groupInfo?.members?.filter((x) => x.wallet?.toLowerCase() !== groupCreator?.toLowerCase()); + const groupMembers = [...membersExceptGroupCreator, ...groupInfo.pendingMembers]; + + + const updateGroup = async (options:UpdateGroupType) => { + const { groupInfo, connectedUser,adminList,memberList } = options; + const updateResponse = await PushAPI.chat.updateGroup({ + chatId: groupInfo?.chatId, + groupName: groupInfo?.groupName, + groupDescription: groupInfo?.groupDescription ?? '', + groupImage: groupInfo?.groupImage, + members: memberList, + admins: adminList, + account: connectedUser?.wallets, + pgpPrivateKey: pgpPrivateKey, + env: env, + }); + let updatedCurrentChat = null; + if(typeof updateResponse !== 'string') + { + updatedCurrentChat = groupInfo; + updatedCurrentChat = updateResponse; + } + return {updateResponse,updatedCurrentChat}; + } + + const addMembers = async () => { + //Already Present Members and PendingMembers + const groupMemberList = convertToWalletAddressList([ + ...groupInfo.members, + ...groupInfo.pendingMembers, + ]); + + //Newly Added Members and alreadyPresent Members in the groupchat + const newMembersToAdd = memberList.map((member: any) => member.wallets); + const members = [...groupMemberList, ...newMembersToAdd]; + + //Admins wallet address from both members and pendingMembers + const adminList = getAdminList?.(groupInfo); + + + try { + setIsLoading(true); + const connectedUser = await PushAPI.user.get({ account: account as string, env }); + const { updateResponse, updatedCurrentChat } = await updateGroup({ + groupInfo, + connectedUser, + adminList, + memberList: members, + }); + + if (typeof updateResponse !== 'string') { + setSelectedMemberAddress(null); + setGroupInfo(updateResponse); + } else { + groupInfoToast.showMessageToast({ + toastTitle: 'Error', + toastMessage: updateResponse, + toastType: 'ERROR', + getToastIcon: (size) => ( + + ), + }); + setSelectedMemberAddress(null); + } + setIsLoading(false); + groupInfoToast.showMessageToast({ + toastTitle: 'Success', + toastMessage: 'Group Invitation sent', + toastType: 'SUCCESS', + getToastIcon: (size) => ( + + ), + }); + handleClose(); + } catch (error) { + setIsLoading(false); + console.log('Error', error); + groupInfoToast.showMessageToast({ + toastTitle: 'Error', + toastMessage: 'Please, try again', + toastType: 'ERROR', + getToastIcon: (size) => ( + + ), + }); + } + }; + + + const makeGroupAdmin = async () => { + const groupMemberList = convertToWalletAddressList([ + ...groupInfo.members, + ...groupInfo.pendingMembers, + ]); + const newAdminList = getUpdatedAdminList(groupInfo, selectedMemberAddress, false); + try { + const connectedUser = await PushAPI.user.get({ account: account as string, env }); + const { updateResponse, updatedCurrentChat } = await updateGroup({ + groupInfo, + connectedUser, + adminList: newAdminList, + memberList: groupMemberList, + }); + if (typeof updateResponse !== 'string') { + setSelectedMemberAddress(null); + setGroupInfo(updateResponse); + + groupInfoToast.showMessageToast({ + toastTitle: 'Success', + toastMessage: 'Admin added successfully', + toastType: 'SUCCESS', + getToastIcon: (size) => ( + ), + }); + } else { + groupInfoToast.showMessageToast({ + toastTitle: 'Error', + toastMessage: updateResponse, + toastType: 'ERROR', + getToastIcon: (size) => ( + + ), + }); + setSelectedMemberAddress(null); + } + } catch (e) { + console.error('Error while adding admin', e); + groupInfoToast.showMessageToast({ + toastTitle: 'Error', + toastMessage: 'Error', + toastType: 'ERROR', + getToastIcon: (size) => ( + + ), + }); + } + setSelectedMemberAddress(null); + }; + + const dismissGroupAdmin = async () => { + const groupMemberList = convertToWalletAddressList([ + ...groupInfo.members, + ...groupInfo.pendingMembers, + ]); + const newAdminList = getUpdatedAdminList(groupInfo, selectedMemberAddress, true); + try { + const connectedUser = await PushAPI.user.get({ account: account as string, env }); + const { updateResponse, updatedCurrentChat } = await updateGroup({ + groupInfo, + connectedUser, + adminList: newAdminList, + memberList: groupMemberList, + }); + if (typeof updateResponse !== 'string') { + setSelectedMemberAddress(null); + setGroupInfo(updateResponse); + + groupInfoToast.showMessageToast({ + toastTitle: 'Success', + toastMessage: 'Admin removed successfully', + toastType: 'SUCCESS', + getToastIcon: (size) => ( + ), + }); + + } else { + groupInfoToast.showMessageToast({ + toastTitle: 'Error', + toastMessage: updateResponse, + toastType: 'ERROR', + getToastIcon: (size) => ( + + ), + }); + setSelectedMemberAddress(null); + } + } catch (e) { + console.error('Error while dismissing admin', e); + groupInfoToast.showMessageToast({ + toastTitle: 'Error', + toastMessage: 'Please, try again', + toastType: 'ERROR', + getToastIcon: (size) => ( + + ), + }); + } + setSelectedMemberAddress(null); + }; + + const removeMember = async () => { + const updatedMemberList = getUpdatedMemberList(groupInfo, selectedMemberAddress!); + const adminList = getUpdatedAdminList(groupInfo, selectedMemberAddress, true); + try { + const connectedUser = await PushAPI.user.get({ account: account as string, env }); + const { updateResponse, updatedCurrentChat } = await updateGroup({ + groupInfo, + connectedUser, + adminList, + memberList: updatedMemberList, + }); + + if (typeof updateResponse !== 'string') { + setSelectedMemberAddress(null); + setGroupInfo(updateResponse); + + groupInfoToast.showMessageToast({ + toastTitle: 'Success', + toastMessage: 'Removed Member successfully', + toastType: 'SUCCESS', + getToastIcon: (size) => ( + + ), + }); + } else { + groupInfoToast.showMessageToast({ + toastTitle: 'Error', + toastMessage: updateResponse, + toastType: 'ERROR', + getToastIcon: (size) => ( + + ), + }); + setSelectedMemberAddress(null); + } + } catch (error) { + console.error('Error in removing member', error); + groupInfoToast.showMessageToast({ + toastTitle: 'Error', + toastMessage: 'Please, try again', + toastType: 'ERROR', + getToastIcon: (size) => ( + + ), + }); + } + setSelectedMemberAddress(null); + }; + + // const messageUserDropdown: DropdownValueType = { + // id: 'message_user', + // title: 'Message user', + // icon: Message, + // function: () => messageUser(), + // }; + const removeAdminDropdown: DropdownValueType = { + id: 'dismiss_admin', + title: 'Dismiss as admin', + icon: DismissAdmin, + function: () => dismissGroupAdmin(), + }; + const addAdminDropdown: DropdownValueType = { + id: 'add_admin', + title: 'Make group admin', + icon: AddAdmin, + function: () => makeGroupAdmin(), + }; + const removeMemberDropdown: DropdownValueType = { + id: 'remove_member', + title: 'Remove', + icon: Remove, + function: () => removeMember(), + textColor: '#ED5858', + }; + + + + const isAccountOwnerAdmin = groupInfo?.members?.some( + (member) => pCAIP10ToWallet(member?.wallet)?.toLowerCase() === account?.toLowerCase() && member?.isAdmin + ); + + const handlePrevious = () => { + setShowAddMoreWalletModal(false); + }; + + const onClose = () => { + setModal(false); + } + + const isMobile = useMediaQuery(device.mobileL); + if(groupInfo){ + return( + + {!showAddMoreWalletModal && (
+
+ +
+ + Group Info + + onClose()} cursor='pointer' /> +
+ + + + +
+ {groupInfo?.groupName} + {groupInfo?.members?.length} Members +
+
+ + + Group Description + {groupInfo?.groupDescription} + + + + + +
+ {groupInfo?.isPublic ? 'Public' : 'Private'} + {groupInfo?.isPublic ? 'Chats are not encrypted' : 'Chats are encrypted'} +
+
+ + {isAccountOwnerAdmin && groupInfo?.members && groupInfo?.members?.length < 10 && ( + setShowAddMoreWalletModal(true)} + > + + + + Add more wallets + + )} + +
+ {groupInfo?.pendingMembers?.length > 0 && ( + + )} +
+ +
+ {groupInfo?.members && groupInfo?.members?.length > 0 && groupInfo?.members.map((item, index) => ( + + ))} +
+ +
)} + + + + {showAddMoreWalletModal && ( + + )} +
+ ) +} else { return null } + +} + +const ProfileDiv = styled.div<{minHeight?: number}>` + display: flex; + flex-direction: column; + justify-content: flex-start; + padding-right: 3px; + align-items: center; + min-width: 445px; + min-height: 72px; + max-height: 216px; + min-height: ${(props) => `${props.minHeight}px`}; + overflow-y: auto; + overflow-x: hidden; + &&::-webkit-scrollbar { + width: 4px; + } + &&::-webkit-scrollbar-thumb { + background: #cf1c84; + border-radius: 10px; + } + @media (max-width: 480px) { + min-width: 300px; + } +`; + +const GroupHeader = styled.div` + margin-top: 34px; + display: flex; + flex-direction: row; + width: 100%; + gap: 19px; +`; + +const GroupDescription = styled.div` + margin-top: 34px; + display: flex; + flex-direction: column; + width: 100%; + align-items: flex-start; + gap: 5px; +`; + + +const PublicEncrypted = styled.div` + margin-top: 20px; + display: flex; + flex-direction: row; + width: 100%; + gap: 19px; + align-items: center; + border: ${(props) => `1px solid ${props.theme.defaultBorder}`}; + border-radius: 16px; + padding: 16px; + box-sizing: border-box; +`; + +const GroupMembers = styled.div` + margin-top: 20px; + display: flex; + flex-direction: row; + width: 100%; + align-items: center; +`; + +const AdminItem = styled.div` + background: rgb(244, 220, 234); + color: rgb(213, 58, 148); + margin-left: auto; + font-size: 10px; + padding: 6px; + border-radius: 8px; +`; + +const AddWalletContainer = styled.div` + margin-top: 20px; + border: ${(props) => `1px solid ${props.theme.defaultBorder}`}; + border-radius: 16px; + width: 100%; + padding: 20px 16px; + box-sizing: border-box; + display: flex; + flex-direction: row; + justify-content: center; + cursor: pointer; + align-items: center; +`; + +const GroupPendingMembers = styled.div` + margin-top: 3px; + display: flex; + flex-direction: row; + width: 100%; + align-items: center; + background: ${(props) => props.theme.pendingCardBackground}; + padding: 10px 15px; + box-sizing: border-box; + + &:last-child { + border-radius: 0px 0px 16px 16px; + } +`; + + +const PendingRequestWrapper = styled.div` + width: 100%; + margin-top: 20px; + border: ${(props) => `1px solid ${props.theme.defaultBorder}`}; + border-radius: 16px; + padding: 0px 0px; + box-sizing: border-box; +`; + +const PendingSection = styled.div` + width: 100%; + display: flex; + flex-direction: row; + align-items: center; + flex: 1; + cursor: pointer; + padding: 15px 20px; + box-sizing: border-box; +`; + +const ArrowImage = styled(Image)` + margin-left: auto; + transform: ${(props) => props?.setPosition ? 'rotate(0)' : 'rotate(180deg)'}; +`; + + +const Badge = styled.div` + margin: 0 0 0 5px; + font-size: 13px; + background: rgb(207, 28, 132); + padding: 4px 8px; + border-radius: 7px; + color: white; + font-weight: 700; +`; diff --git a/packages/uiweb/src/lib/components/chat/ChatProfile/MemberListContainer.tsx b/packages/uiweb/src/lib/components/chat/ChatProfile/MemberListContainer.tsx new file mode 100644 index 000000000..c92529fef --- /dev/null +++ b/packages/uiweb/src/lib/components/chat/ChatProfile/MemberListContainer.tsx @@ -0,0 +1,191 @@ +import { useContext, useRef, useState } from "react"; +import { ThemeContext } from "../theme/ThemeProvider"; +import { useClickAway } from "../../../hooks"; +import Dropdown, { DropdownValueType } from "./DropDown"; +import DismissAdmin from '../../../icons/dismissadmin.svg'; +import AddAdmin from '../../../icons/addadmin.svg'; +import Remove from '../../../icons/remove.svg'; +import styled from "styled-components"; +import { Section, Image, Span } from "../../reusables/sharedStyling"; +import { MemberListContainerType, WalletProfileContainerProps } from "../exportedTypes"; +import { findObject } from "../helpers/helper"; +import { device } from "../../../config"; +import { shortenText } from "../../../helpers"; + +export const MemberListContainer = ({ key, memberData, handleMembers, handleMemberList, lightIcon, darkIcon, memberList }: MemberListContainerType) => { + const theme = useContext(ThemeContext); + const [selectedWallet, setSelectedWallet] = useState(null); + const [dropdownHeight, setDropdownHeight] = useState(0); + const dropdownRef = useRef(null); + + + useClickAway(dropdownRef, () => setSelectedWallet(null)); + + const removeAdminDropdown: DropdownValueType = + { id: 'dismiss_admin', title: 'Dismiss as admin', icon: DismissAdmin, function: () => dismissGroupAdmin() } + + const addAdminDropdown: DropdownValueType = + { id: 'dismiss_admin', title: 'Make group admin', icon: AddAdmin, function: () => makeGroupAdmin() } + + const removeUserDropdown: DropdownValueType = + { id: 'remove_user', title: 'Remove', icon: Remove, function: () => removeUser() } + + const dismissGroupAdmin = () => { + const updatedMembers = memberList.map((member:any) => member?.wallets?.toLowerCase() == memberData?.wallets?.toLowerCase() ? ({ ...member, isAdmin: false }) : member) + handleMembers?.(updatedMembers) + setSelectedWallet(null) + } + + const makeGroupAdmin = () => { + const updatedMembers = memberList.map((member: any) => member?.wallets?.toLowerCase() == memberData?.wallets?.toLowerCase() ? ({ ...member, isAdmin: true }) : member) + handleMembers?.(updatedMembers) + setSelectedWallet(null) + } + + const removeUser = () => { + handleMemberList(memberData) + setSelectedWallet(null) + } + + + const handleHeight = (id: any) => { + const containerHeight = document.getElementById(id)?.getBoundingClientRect(); + setDropdownHeight(containerHeight?.top); + }; + + + return ( + + +
+ +
+ + {shortenText(memberData?.wallets?.split(':')[1], 8, true)} +
+ +
+ {memberData?.isAdmin && ( + + Admin + + )} +
{ + handleHeight(memberData?.wallets); + setSelectedWallet(null) + memberList + ? findObject(memberData, memberList, 'wallets') + ? setSelectedWallet(memberData?.wallets) + : handleMemberList(memberData) + : handleMemberList(memberData) + }} + > + {/* {theme === 'light' ? lightIcon : darkIcon} */} + {darkIcon} +
+
+ + {selectedWallet?.toLowerCase() == memberData?.wallets?.toLowerCase() && ( + 500 ? '30%' : "45%" }} ref={dropdownRef} theme={theme}> + + + )} + + + ) +} + + +const WalletProfileContainer = styled(Section)` + // position: relative; + // padding: 5px 16px; + // margin: 8px 0px; + // justify-content: space-between; + // // min-width: 450px; + // min-width: 100%; + // box-sizing: border-box; + // align-items: center; + // border-radius: 16px; + + // @media (max-width: 480px) { + // // min-width: 300px; + // } + + justify-content: space-between; + padding: 8px 16px; + border-radius: 16px; + position: relative; + box-sizing: border-box; + width: 100%; + // background-color: ${(props) => props.theme.snapFocusBg}; + max-height: 64px; + align-self: stretch; + display: flex; + height: auto; + z-index: auto; + flex: 1; + @media (max-width: 480px) { + max-width: 100%; + } + +`; + +const WalletProfile = styled(Section)` + justify-content: flex-start; +`; + +const DropdownContainer = styled.div` + // position: absolute; + // left: 48%; + // border-radius: 16px; + // padding: 14px 8px; + // background: ${(props) => props.theme.modalContentBackground}; + // border: 1px solid ${(props) => props.theme.modalBorderColor}; + // z-index: 400; + // @media ${device.mobileL} { + // left: 27%; + // } + // @media (min-width: 426px) and (max-width: 1150px) { + // left: 47%; + // } + position: absolute; + left: 48%; + top: 69%; + border-radius: 16px; + padding: 14px 8px; + z-index: 999999999999 !important; + display: flex; + flex-direction: column !important; + background: ${(props) => props.theme.modalContentBackground}; + border: 1px solid ${(props) => props.theme.modalBorderColor}; + + @media ${device.mobileL} { + left: 27%; + } + @media (min-width: 426px) and (max-width: 1150px) { + left: 48%; + } + @media (max-width: 480px){ + left: 25%; + } +`; \ No newline at end of file diff --git a/packages/uiweb/src/lib/components/chat/ChatProfile/ProfileCard.tsx b/packages/uiweb/src/lib/components/chat/ChatProfile/ProfileCard.tsx new file mode 100644 index 000000000..2ee09770f --- /dev/null +++ b/packages/uiweb/src/lib/components/chat/ChatProfile/ProfileCard.tsx @@ -0,0 +1,158 @@ +// React + Web3 Essentials +import React, { useContext ,useState } from 'react'; + +// External Packages +import styled from 'styled-components'; +import { ethers } from 'ethers'; + +// Internal Components +import { MoreLightIcon } from '../../../icons/MoreLight'; +import { MoreDarkIcon } from '../../../icons/MoreDark'; +import { shortenText } from "../../../helpers"; +import { ThemeContext } from "../theme/ThemeProvider"; +import { useChatData, useClickAway} from "../../../hooks"; +import { Image, Section, Span } from "../../reusables"; +import Dropdown from './DropDown'; +import { pCAIP10ToWallet } from '../../../helpers'; +import { device } from "../../../config"; + + +type ProfileCardProps = { + key?: number | string, + member?: any, + dropdownValues?: any; + selectedMemberAddress?: any; + setSelectedMemberAddress?: any; + dropdownRef?: any; +} + +export const ProfileCard = ({ + key, + member, + dropdownValues, + selectedMemberAddress, + setSelectedMemberAddress, + dropdownRef, +}: ProfileCardProps) => { + const theme = useContext(ThemeContext); + const { account } = useChatData(); + + const [dropdownHeight, setDropdownHeight] = useState(0); + + const handleHeight = (id: any) => { + const containerHeight = document.getElementById(id)?.getBoundingClientRect(); + setDropdownHeight(containerHeight?.top); + }; + + return ( + +
+
+ +
+ + {shortenText(member?.wallet?.split(':')[1], 6, true)} + +
+
+ {member?.isAdmin && ( + + Admin + + )} + {pCAIP10ToWallet(member?.wallet)?.toLowerCase() !== account?.toLowerCase() && dropdownValues.length > 0 && ( +
{ + handleHeight(member.wallet); + setSelectedMemberAddress(member?.wallet) + }} + style={{ cursor: 'pointer' }} + > + {theme ? : } +
+ )} +
+ {selectedMemberAddress?.toLowerCase() == member?.wallet?.toLowerCase() && ( + 570 ? '30%' : '40%' }} + theme={theme} + ref={dropdownRef}> + + + )} +
+ ); +}; + +const ProfileCardItem = styled(Section)<{id: any, key: any, background: any}>` + justify-content: space-between; + padding: 8px 16px; + border-radius: 16px; + position: relative; + box-sizing: border-box; + width: 100%; + // background-color: ${(props) => props.theme.snapFocusBg}; + max-height: 64px; + align-self: stretch; + display: flex; + height: auto; + z-index: auto; + flex: 1; + @media (max-width: 480px) { + max-width: 100%; + } +`; + +const DropdownContainer = styled(Section)` + position: absolute; + left: 48%; + top: 69%; + border-radius: 16px; + padding: 14px 8px; + z-index: 999999999999 !important; + display: flex; + flex-direction: column !important; + background: ${(props) => props.theme.modalContentBackground}; + border: 1px solid ${(props) => props.theme.modalBorderColor}; + + @media ${device.mobileL} { + left: 27%; + } + @media (min-width: 426px) and (max-width: 1150px) { + left: 48%; + } + @media (max-width: 480px){ + left: 25%; + } +`; diff --git a/packages/uiweb/src/lib/components/chat/ChatProfile/index.tsx b/packages/uiweb/src/lib/components/chat/ChatProfile/index.tsx new file mode 100644 index 000000000..c84eff807 --- /dev/null +++ b/packages/uiweb/src/lib/components/chat/ChatProfile/index.tsx @@ -0,0 +1 @@ +export { ChatProfile } from './ChatProfile' \ No newline at end of file diff --git a/packages/uiweb/src/lib/components/chat/MessageBubble/MessageBubble.tsx b/packages/uiweb/src/lib/components/chat/ChatViewBubble/ChatViewBubble.tsx similarity index 97% rename from packages/uiweb/src/lib/components/chat/MessageBubble/MessageBubble.tsx rename to packages/uiweb/src/lib/components/chat/ChatViewBubble/ChatViewBubble.tsx index 625957a10..9ad6ce257 100644 --- a/packages/uiweb/src/lib/components/chat/MessageBubble/MessageBubble.tsx +++ b/packages/uiweb/src/lib/components/chat/ChatViewBubble/ChatViewBubble.tsx @@ -273,8 +273,8 @@ const TwitterCard = ({ chat, tweetId, isGroup, position }: { chat: IMessagePaylo ) } -export const MessageBubble = ({ chat }: { chat: IMessagePayload }) => { - const { account } = useChatData(); +export const ChatViewBubble = ({ chat }: { chat: IMessagePayload }) => { + const { account, setAccount, pgpPrivateKey, setPgpPrivateKey, env, setEnv } = useChatData(); const position = pCAIP10ToWallet(chat.fromDID).toLowerCase() !== account?.toLowerCase() ? 0 : 1; const { tweetId, messageType }: TwitterFeedReturnType = checkTwitterUrl({ message: chat?.messageContent }); const [isGroup, setIsGroup] = useState(false); @@ -290,6 +290,12 @@ export const MessageBubble = ({ chat }: { chat: IMessagePayload }) => { } }, [chat.toDID, isGroup]) + // useEffect(() => { + // setAccount(""); + // setPgpPrivateKey(""); + // setEnv(env); + // }, [account, env, pgpPrivateKey]) + if (messageType === 'TwitterFeedLink') { chat.messageType = 'TwitterFeedLink'; } diff --git a/packages/uiweb/src/lib/components/chat/ChatViewBubble/index.ts b/packages/uiweb/src/lib/components/chat/ChatViewBubble/index.ts new file mode 100644 index 000000000..25dc326f2 --- /dev/null +++ b/packages/uiweb/src/lib/components/chat/ChatViewBubble/index.ts @@ -0,0 +1 @@ +export { ChatViewBubble } from './ChatViewBubble'; diff --git a/packages/uiweb/src/lib/components/chat/ChatViewComponent/ChatViewComponent.tsx b/packages/uiweb/src/lib/components/chat/ChatViewComponent/ChatViewComponent.tsx new file mode 100644 index 000000000..846112317 --- /dev/null +++ b/packages/uiweb/src/lib/components/chat/ChatViewComponent/ChatViewComponent.tsx @@ -0,0 +1,86 @@ +import React, { useContext} from 'react'; +import { IChatViewComponentProps } from '../exportedTypes'; + +import { Section, } from '../../reusables'; +import { ChatViewList } from '../ChatViewList'; +import { chatLimit } from '../../../config'; + +import { ThemeContext } from '../theme/ThemeProvider'; +import { useChatData } from '../../../hooks/chat/useChatData'; +import { MessageInput } from '../MessageInput'; +import { ChatProfile } from '../ChatProfile'; + + + +export const ChatViewComponent: React.FC = ( + options: IChatViewComponentProps +) => { + const { + chatId, + messageInput = true, + chatViewList = true, + chatProfile = true, + limit = chatLimit, + emoji = true, + file = true, + gif = true, + isConnected = true, + } = options || {}; + + const {env } = useChatData(); + + console.log(env); + + // const [conversationHash, setConversationHash] = useState(); + + const theme = useContext(ThemeContext); + + + + + + + + + + + return ( +
+ + {chatProfile && } +
+ + + {chatId && chatViewList && } + +
+ + {/* )} */} + + {messageInput && ( +
+ +
+ )} +
+ ); +}; + +//styles + diff --git a/packages/uiweb/src/lib/components/chat/ChatViewComponent/index.ts b/packages/uiweb/src/lib/components/chat/ChatViewComponent/index.ts new file mode 100644 index 000000000..25a649344 --- /dev/null +++ b/packages/uiweb/src/lib/components/chat/ChatViewComponent/index.ts @@ -0,0 +1 @@ +export {ChatViewComponent} from './ChatViewComponent'; \ No newline at end of file diff --git a/packages/uiweb/src/lib/components/chat/ChatViewList/ApproveRequestBubble.tsx b/packages/uiweb/src/lib/components/chat/ChatViewList/ApproveRequestBubble.tsx new file mode 100644 index 000000000..afdc1724a --- /dev/null +++ b/packages/uiweb/src/lib/components/chat/ChatViewList/ApproveRequestBubble.tsx @@ -0,0 +1,119 @@ +import { IFeeds } from '@pushprotocol/restapi'; +import { ThemeContext } from '../theme/ThemeProvider'; +import { Dispatch, useContext } from 'react'; +import { Div, Section, Span, Spinner } from '../../reusables'; +import useApproveChatRequest from '../../../hooks/chat/useApproveChatRequest'; +import { useChatData } from '../../../hooks'; +import { TickSvg } from '../../../icons/Tick'; +import styled from 'styled-components'; +import { IChatTheme } from '../theme'; + +/** + * @interface IThemeProps + * this interface is used for defining the props for styled components + */ +interface IThemeProps { + theme?: IChatTheme; +} +export interface IApproveRequestBubbleProps { + chatId: string; + chatFeed: IFeeds; + setChatFeed: Dispatch; +} + +export const ApproveRequestBubble = ({ + chatFeed, + chatId, + setChatFeed, +}: IApproveRequestBubbleProps) => { + const { account, pgpPrivateKey, env } = useChatData(); + + const ApproveRequestText = { + GROUP: `You were invited to the group ${chatFeed?.groupInformation?.groupName}. Please accept to continue messaging in this group.`, + W2W: ` Please accept to enable push chat from this wallet`, + }; + const theme = useContext(ThemeContext); + const { approveChatRequest, loading: approveLoading } = + useApproveChatRequest(); + + const handleApproveChatRequest = async () => { + try { + if (!pgpPrivateKey) { + return; + } + const response = await approveChatRequest({ + chatId, + }); + if (response) { + const updatedChatFeed = { ...(chatFeed as IFeeds) }; + updatedChatFeed.intent = response; + + setChatFeed(updatedChatFeed); + } + } catch (error_: Error | any) { + console.log(error_.message); + } + }; + return ( +
+ + {chatFeed?.groupInformation + ? ApproveRequestText.GROUP + : ApproveRequestText.W2W} + + + {/*
(!approveLoading ? handleApproveChatRequest() : null)} + > + {approveLoading ? : } +
*/} +
+ ); +}; + +//styles +const Button = styled.button` + border: none; + cursor: pointer; + border-radius: 8px; + background: ${(props) => props.theme.accentBgColor}; + border: none; + color: white; + width: 100%; + font-size: 16px; + font-weight: 600; + line-height: 24px; + max-height: 48px; + min-height: 48px; + padding: 0px 24px; + display: flex; + justify-content: center; + align-items: center; +`; diff --git a/packages/uiweb/src/lib/components/chat/ChatViewList/ChatViewList.tsx b/packages/uiweb/src/lib/components/chat/ChatViewList/ChatViewList.tsx new file mode 100644 index 000000000..220aef905 --- /dev/null +++ b/packages/uiweb/src/lib/components/chat/ChatViewList/ChatViewList.tsx @@ -0,0 +1,351 @@ +import React, { useContext, useEffect, useRef, useState } from 'react'; +import { ChatDataContext } from '../../../context'; +import { IChatViewListProps } from '../exportedTypes'; +import { chatLimit } from '../../../config'; +import { IFeeds, IMessageIPFS } from '@pushprotocol/restapi'; +import useFetchHistoryMessages from '../../../hooks/chat/useFetchHistoryMessages'; +import styled from 'styled-components'; +import { Div, Section, Span, Spinner } from '../../reusables'; +import moment from 'moment'; +import { ChatViewBubble } from '../ChatViewBubble'; +import { + appendUniqueMessages, + checkIfIntent, + checkIfSameChat, + dateToFromNowDaily, + getDefaultFeedObject, + getNewChatUser, + pCAIP10ToWallet, +} from '../../../helpers'; +import { useChatData, usePushChatSocket } from '../../../hooks'; +import { Messagetype } from '../../../types'; +import { ThemeContext } from '../theme/ThemeProvider'; +import { IChatTheme } from '../theme'; +import useFetchConversationHash from '../../../hooks/chat/useFetchConversationHash'; + +import { EncryptionMessage } from './MessageEncryption'; +import useGetGroup from '../../../hooks/chat/useGetGroup'; +import useGetChatProfile from '../../../hooks/useGetChatProfile'; +import useFetchChat from '../../../hooks/chat/useFetchChat'; +import { ApproveRequestBubble } from './ApproveRequestBubble'; + +/** + * @interface IThemeProps + * this interface is used for defining the props for styled components + */ +interface IThemeProps { + theme?: IChatTheme; +} +const ChatStatus = { + FIRST_CHAT: `This is your first conversation with recipient.\n Start the conversation by sending a message.`, + INVALID_CHAT: 'Invalid chatId', +}; + +export const ChatViewList: React.FC = ( + options: IChatViewListProps +) => { + const { chatId, limit = chatLimit } = options || {}; + const { pgpPrivateKey, account } = useChatData(); + const [chatFeed, setChatFeed] = useState({} as IFeeds); + const [chatStatusText, setChatStatusText] = useState(''); + const [messages, setMessages] = useState(); + const [ loading,setLoading] = useState(true); + const [conversationHash, setConversationHash] = useState(); + const { historyMessages, loading:messageLoading } = useFetchHistoryMessages(); + const listInnerRef = useRef(null); + const bottomRef = useRef(null); + const { fetchChat } = useFetchChat(); + const { fetchChatProfile } = useGetChatProfile(); + const { getGroup } = useGetGroup(); + + const { messagesSinceLastConnection, groupInformationSinceLastConnection } = + usePushChatSocket(); + const { fetchConversationHash } = useFetchConversationHash(); + const theme = useContext(ThemeContext); + const dates = new Set(); + const { env } = useChatData(); + + useEffect(() => { + setChatStatusText(''); + }, [chatId, account, env]); + + useEffect(() => { + setMessages(undefined); + setConversationHash(undefined); + }, [chatId, account, pgpPrivateKey, env]); + + useEffect(() => { + (async () => { + const chat = await fetchChat({ chatId }); + if (Object.keys(chat || {}).length) setChatFeed(chat as IFeeds); + else { + let newChatFeed; + let group; + const result = await getNewChatUser({ + searchText: chatId, + fetchChatProfile, + env, + }); + if (result) { + newChatFeed = getDefaultFeedObject({ user: result }); + } else { + group = await getGroup({ searchText: chatId }); + if (group) { + newChatFeed = getDefaultFeedObject({ groupInformation: group }); + } + } + if (newChatFeed) { + if (!newChatFeed?.groupInformation) { + setChatStatusText(ChatStatus.FIRST_CHAT); + } + console.log(chatFeed) + setChatFeed(newChatFeed); + } else { + setChatStatusText(ChatStatus.INVALID_CHAT); + } + + } + setLoading(false); + })(); + }, [chatId, pgpPrivateKey, account, env]); + + useEffect(() => { + if (checkIfSameChat(messagesSinceLastConnection, account!, chatId)) { + if (!Object.keys(messages || {}).length) { + setMessages({ + messages: [messagesSinceLastConnection], + lastThreadHash: messagesSinceLastConnection.cid, + }); + setConversationHash(messagesSinceLastConnection.cid); + } else { + const newChatViewList = appendUniqueMessages( + messages as Messagetype, + [messagesSinceLastConnection], + false + ); + setMessages({ + messages: newChatViewList, + lastThreadHash: messages!.lastThreadHash, + }); + } + scrollToBottom(null); + } + }, [messagesSinceLastConnection]); + + useEffect(() => { + (async function () { + const hash = await fetchConversationHash({ conversationId: chatId }); + setConversationHash(hash?.threadHash); + })(); + }, [chatId, account, env, pgpPrivateKey]); + + useEffect(() => { + if (conversationHash) { + (async function () { + await getMessagesCall(); + })(); + } + }, [conversationHash, pgpPrivateKey, account, env]); + + useEffect(() => { + scrollToBottom(null); + }, [conversationHash]); + + useEffect(() => { + if ( + conversationHash && + Object.keys(messages || {}).length && + messages?.messages.length && + messages?.messages.length <= limit + ) { + scrollToBottom(null); + } + }, [messages]); + + useEffect(() => { + if ( + Object.keys(messagesSinceLastConnection || {}).length && + Object.keys(chatFeed || {}).length && + checkIfSameChat(messagesSinceLastConnection, account!, chatId) + ) { + const updatedChatFeed = chatFeed; + updatedChatFeed.msg = messagesSinceLastConnection; + + setChatStatusText(''); + setChatFeed(updatedChatFeed); + } + }, [messagesSinceLastConnection]); + + const scrollToBottom = (behavior?: string | null) => { + bottomRef?.current?.scrollIntoView( + !behavior ? true : { behavior: 'smooth' } + ); + }; + + useEffect(() => { + if (Object.keys(groupInformationSinceLastConnection || {}).length) { + if ( + chatFeed?.groupInformation?.chatId.toLowerCase() === + groupInformationSinceLastConnection.chatId.toLowerCase() + ) { + const updateChatFeed = chatFeed; + updateChatFeed.groupInformation = groupInformationSinceLastConnection; + setChatFeed(updateChatFeed); + } + } + }, [groupInformationSinceLastConnection]); + + const onScroll = async () => { + if (listInnerRef.current) { + const { scrollTop } = listInnerRef.current; + if (scrollTop === 0) { + const content = listInnerRef.current; + const curScrollPos = content.scrollTop; + const oldScroll = content.scrollHeight - content.clientHeight; + + await getMessagesCall(); + + const newScroll = content.scrollHeight - content.clientHeight; + content.scrollTop = curScrollPos + (newScroll - oldScroll); + } + } + }; + + const getMessagesCall = async () => { + let threadHash = null; + if (!messages) { + threadHash = conversationHash; + } else { + threadHash = messages?.lastThreadHash; + } + if (threadHash && account) { + const chatHistory = await historyMessages({ + limit: limit, + threadHash, + }); + if (chatHistory?.length) { + if (Object.keys(messages || {}) && messages?.messages.length) { + const newChatViewList = appendUniqueMessages( + messages, + chatHistory, + true + ); + setMessages({ + messages: newChatViewList, + lastThreadHash: chatHistory[0].link, + }); + } else { + setMessages({ + messages: chatHistory, + lastThreadHash: chatHistory[0].link, + }); + } + } + } + }; + + type RenderDataType = { + chat: IMessageIPFS; + dateNum: string; + }; + + const renderDate = ({ chat, dateNum }: RenderDataType) => { + const timestampDate = dateToFromNowDaily(chat.timestamp as number); + dates.add(dateNum); + return ( + + {timestampDate} + + ); + }; + return ( + onScroll()} + > + {loading ? : ''} + {!loading && + <> + {chatFeed && + (chatFeed.publicKey || + (chatFeed?.groupInformation && + !chatFeed?.groupInformation?.isPublic)) ? ( + + ) : ( + + )} + + {chatStatusText && ( +
+ + {chatStatusText} + +
+ )} + {messageLoading ? : ''} + + { + !messageLoading && + <> +
+ {messages?.messages && + messages?.messages?.map((chat: IMessageIPFS, index: number) => { + const dateNum = moment(chat.timestamp).format('L'); + const position = + pCAIP10ToWallet(chat.fromDID).toLowerCase() !== + account?.toLowerCase() + ? 0 + : 1; + return ( + <> + {dates.has(dateNum) ? null : renderDate({ chat, dateNum })} +
+ +
+ + ); + })} +
+
+ {chatFeed && checkIfIntent({ chat: chatFeed as IFeeds, account: account! }) && ( + + )} + + } + + } +
+ ); +}; + +//styles +const ChatViewListCard = styled(Section)` + &::-webkit-scrollbar-thumb { + background: ${(props) => props.theme.accentBgColor}; + border-radius: 10px; + } + + &::-webkit-scrollbar { + width: 5px; + } +`; diff --git a/packages/uiweb/src/lib/components/chat/ChatViewList/MessageEncryption.tsx b/packages/uiweb/src/lib/components/chat/ChatViewList/MessageEncryption.tsx new file mode 100644 index 000000000..d2519e6ed --- /dev/null +++ b/packages/uiweb/src/lib/components/chat/ChatViewList/MessageEncryption.tsx @@ -0,0 +1,55 @@ +import styled from "styled-components"; +import { Div, Section, Span } from "../../reusables"; +import { useDeviceWidthCheck } from "../../../hooks"; +import { useContext } from "react"; +import { ThemeContext } from "../theme/ThemeProvider"; +import { NoEncryptionIcon } from "../../../icons/NoEncryption"; +import { EncryptionIcon } from "../../../icons/Encryption"; + +const EncryptionMessageContent = { + ENCRYPTED: { + IconComponent: , + text: 'Messages are end-to-end encrypted. Only users in this chat can view or listen to them. Click to learn more.', + }, + NO_ENCRYPTED: { + IconComponent: , + text: `Messages are not encrypted`, + }, + }; + export const EncryptionMessage = ({ id }: { id: 'ENCRYPTED' | 'NO_ENCRYPTED' }) => { + console.log(id) + const theme = useContext(ThemeContext); + const isMobile = useDeviceWidthCheck(771); + return ( +
+ + {EncryptionMessageContent[id].IconComponent} + + + {EncryptionMessageContent[id].text} + + +
+ ); + }; + + //styles + const EncryptionMessageDiv = styled(Div)` + text-align: center; + svg { + vertical-align: middle; + } +`; \ No newline at end of file diff --git a/packages/uiweb/src/lib/components/chat/ChatViewList/index.ts b/packages/uiweb/src/lib/components/chat/ChatViewList/index.ts new file mode 100644 index 000000000..d91caff1c --- /dev/null +++ b/packages/uiweb/src/lib/components/chat/ChatViewList/index.ts @@ -0,0 +1 @@ +export {ChatViewList} from './ChatViewList'; \ No newline at end of file diff --git a/packages/uiweb/src/lib/components/chat/ConnectButton/ConnectButton.tsx b/packages/uiweb/src/lib/components/chat/ConnectButton/ConnectButton.tsx new file mode 100644 index 000000000..d196b00b7 --- /dev/null +++ b/packages/uiweb/src/lib/components/chat/ConnectButton/ConnectButton.tsx @@ -0,0 +1,174 @@ +import React, { useContext, useEffect } from "react"; +import styled from "styled-components"; +import { InjectedConnector } from "@web3-react/injected-connector"; +import { useWeb3React } from "@web3-react/core"; +import { useChatData } from "../../../hooks"; +import * as PUSHAPI from "@pushprotocol/restapi" +import { Spinner } from "../../reusables"; +import { ThemeContext } from "../theme/ThemeProvider"; + +interface NwMappingType { + [key: number]: string; +} + + +const NETWORK_MAPPING: NwMappingType = { + 1: 'ETH_MAIN_NET', + 5: 'ETH_GOERLI', + 3: 'ETH_ROPSTEN', + 137: 'POLYGON_MAINNET', + 80001: 'POLYGON_MUMBAI', + 56: 'BSC_MAINNET', + 97: 'BSC_TESTNET', + 420: 'OPTIMISM_TESTNET', + 10: 'OPTIMISM_MAINNET', + 1442: 'POLYGON_ZK_EVM_TESTNET', + 1101: 'POLYGON_ZK_EVM_MAINNET', +}; + +const injected = new InjectedConnector({ + supportedChainIds: [1, 3, 4, 5, 42, 137, 80001, 56, 97, 10, 420, 1442, 1101], +}); + +const ConnectWrapper = styled.div` + display: flex; + align-items: center; + flex-direction: column; + margin: 20; + + & .account { + font-size: 1.2rem; + border: 1px solid green; + border-radius: 3px; + padding: 4px 7px; + font-weight: 500; + font-family: monospace; + } + + & .network { + margin: 5px 0; + } + `; + +const StyledButton = styled.button` + border: 0px; + outline: 0px; + padding: 24px 9px; + font-weight: 500; + margin: 10px; + border-radius: 12px; + font-size: 17px; + cursor: pointer; + width: 165px; + height: 44px; + text-align: start; + align-items: center; + display: flex; + justify-content: center; + `; + +const Connect = styled(StyledButton)` + color: rgb(255, 255, 255); + background: #D53A94; + `; + +const Disconnect = styled(StyledButton)` +display: flex; +padding: 9px 24px; +justify-content: center; +align-items: center; +gap: 10px; +background: var(--general-use-creamy-pink, #D53A94); +color: var(--general-use-white, #ffffff); + `; + +export const ConnectButton = () => { + const { active, activate, library } = useWeb3React(); + const { pgpPrivateKey, account, env, setPgpPrivateKey } = useChatData(); + const theme = useContext(ThemeContext); + + useEffect(() => { + if (active && account && env && library) { + const librarySigner = library.getSigner(); + + const connectBtn = async () => { + const user = await PUSHAPI.user.get({ account: account, env: env }); + if (!user) { + await createProfile(); + } + if (user?.encryptedPrivateKey && !pgpPrivateKey) { + const decryptPgpKey = await PUSHAPI.chat.decryptPGPKey({ + encryptedPGPPrivateKey: user.encryptedPrivateKey, + account: account, + signer: librarySigner, + env: env, + }); + setPgpPrivateKey(decryptPgpKey); + } + }; + + connectBtn(); + } + }, [active, account, env, library]); + + const createProfile = async () => { + if (!account || !env || !library) return; + + const librarySigner = library.getSigner(); + + const user = await PUSHAPI.user.create({ + signer: librarySigner, + env: env, + }); + + const createdUser = await PUSHAPI.user.get({ + account: account, + env: env, + }); + + const pvtKey = await PUSHAPI.chat.decryptPGPKey({ + encryptedPGPPrivateKey: createdUser.encryptedPrivateKey ? createdUser.encryptedPrivateKey : "", + signer: librarySigner, + env: env, + toUpgrade: true, + }); + + setPgpPrivateKey(pvtKey); + }; + + async function connect() { + try { + await activate(injected); + } catch (ex) { + console.log(ex); + } + } + + const connectWalletOnPageLoad = async () => { + if (!pgpPrivateKey && !account) { + try { + await activate(injected); + } catch (ex) { + console.log(ex); + } + } + }; + + useEffect(() => { + connectWalletOnPageLoad(); + }, [activate]); + + return ( + + {active ? ( + <> + + + + + ) : ( + Connect Wallet + )} + + ); +}; \ No newline at end of file diff --git a/packages/uiweb/src/lib/components/chat/ConnectButton/index.ts b/packages/uiweb/src/lib/components/chat/ConnectButton/index.ts new file mode 100644 index 000000000..d5457ba77 --- /dev/null +++ b/packages/uiweb/src/lib/components/chat/ConnectButton/index.ts @@ -0,0 +1 @@ +export { ConnectButton } from './ConnectButton'; diff --git a/packages/uiweb/src/lib/components/chat/MessageBubble/index.ts b/packages/uiweb/src/lib/components/chat/MessageBubble/index.ts deleted file mode 100644 index fd86462dc..000000000 --- a/packages/uiweb/src/lib/components/chat/MessageBubble/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { MessageBubble } from './MessageBubble'; diff --git a/packages/uiweb/src/lib/components/chat/MessageInput/MessageInput.tsx b/packages/uiweb/src/lib/components/chat/MessageInput/MessageInput.tsx new file mode 100644 index 000000000..a1305be48 --- /dev/null +++ b/packages/uiweb/src/lib/components/chat/MessageInput/MessageInput.tsx @@ -0,0 +1,340 @@ +import { useChatData, useClickAway, useDeviceWidthCheck } from "../../../hooks"; +import type { FileMessageContent } from "../../../types"; +import type { ChatMainStateContextType } from "../../../context/chatAndNotification/chat/chatMainStateContext"; +import { ChangeEvent, useContext, useEffect, useRef, useState } from "react"; +import { GIFType, IChatTheme, MessageInputProps } from "../exportedTypes"; +import styled from "styled-components"; +import { PUBLIC_GOOGLE_TOKEN, device } from "../../../config"; +import { Section, Div, Span } from "../../reusables"; +import { EmojiIcon } from "../../../icons/Emoji"; +import EmojiPicker, { EmojiClickData } from "emoji-picker-react"; +import * as PUSHAPI from "@pushprotocol/restapi"; +import { GifIcon } from "../../../icons/Gif"; +import GifPicker from "gif-picker-react"; +import { AttachmentIcon } from "../../../icons/Attachment"; +import usePushSendMessage from "../../../hooks/chat/usePushSendMessage"; +import { SendCompIcon } from "../../../icons/SendCompIcon"; +import { Spinner } from "../../reusables"; +import { ThemeContext } from "../theme/ThemeProvider"; +import { ConnectButton } from "../ConnectButton"; + + +/** + * @interface IThemeProps + * this interface is used for defining the props for styled components + */ +interface IThemeProps { + theme?: IChatTheme; +} + +export const MessageInput: React.FC = ({ chatId, Emoji = true, GIF = true, File = true, isConnected = true }) => { + const [typedMessage, setTypedMessage] = useState(""); + const [showEmojis, setShowEmojis] = useState(false); + const [gifOpen, setGifOpen] = useState(false); + const [newChat, setNewChat] = useState(false); + const modalRef = useRef(null); + const fileUploadInputRef = useRef(null); + const [fileUploading, setFileUploading] = useState(false); + const onChangeTypedMessage = (val: string) => { + setTypedMessage(val.trim()); + }; + const theme = useContext(ThemeContext); + const isMobile = useDeviceWidthCheck(425); + const { sendMessage, loading } = usePushSendMessage(); + const { pgpPrivateKey, setPgpPrivateKey } = useChatData(); + + useClickAway(modalRef, () => { + setShowEmojis(false); + setGifOpen(false); + }); + const textAreaRef = useRef(null); + useEffect(() => { + if (textAreaRef?.current?.style) { + textAreaRef.current.style.height = 25 + 'px'; + const scrollHeight = textAreaRef.current?.scrollHeight; + textAreaRef.current.style.height = scrollHeight + 'px'; + } + }, [textAreaRef, typedMessage]) + + const addEmoji = (emojiData: EmojiClickData, event: MouseEvent): void => { + setTypedMessage(typedMessage + emojiData.emoji); + setShowEmojis(false); + } + + const handleUploadFile = () => { + if (fileUploadInputRef.current) { + fileUploadInputRef.current.click(); + } + } + + const uploadFile = async ( + e: ChangeEvent + ): Promise => { + if (!(e.target instanceof HTMLInputElement)) { + return; + } + if (!e.target.files) { + return; + } + if ( + e.target && + (e.target as HTMLInputElement).files && + ((e.target as HTMLInputElement).files as FileList).length + ) { + const file: File = e.target.files[0]; + if (file) { + try { + const TWO_MB = 1024 * 1024 * 2; + if (file.size > TWO_MB) { + console.log('Files larger than 2mb is now allowed'); + throw new Error('Files larger than 2mb is now allowed'); + } + setFileUploading(true); + const messageType = file.type.startsWith('image') ? 'Image' : 'File'; + const reader = new FileReader(); + let fileMessageContent: FileMessageContent; + reader.readAsDataURL(file); + reader.onloadend = async (e): Promise => { + fileMessageContent = { + content: e.target!.result as string, + name: file.name, + type: file.type, + size: file.size, + }; + + sendPushMessage(JSON.stringify(fileMessageContent), messageType); + }; + } catch (err) { + console.log(err); + } finally { + setFileUploading(false); + } + } + } + }; + + const sendPushMessage = async (content: string, type: string) => { + try { + await sendMessage({ + message: content, + chatId, + messageType: type as any, + }); + } catch (error) { + console.log(error); + } + } + + const sendTextMsg = async () => { + if (typedMessage.trim() !== '') { + await sendPushMessage(typedMessage as string, 'Text'); + setTypedMessage(''); + } + } + + // useEffect(() => { + // setPgpPrivateKey + // }, [pgpPrivateKey]) + + const sendGIF = async (emojiObject: GIFType) => { + sendPushMessage(emojiObject.url as string, 'GIF'); + setGifOpen(false); + } + + return ( + + {/* {isConnected && ( + + )} */} + + {!pgpPrivateKey && isConnected && ( + // align this button in right corner + +
+ + You need to connect your wallet to get started + + +
+ ) + } + {pgpPrivateKey && + <> +
+ {Emoji && +
setShowEmojis(!showEmojis)} + > + +
+ } + {showEmojis && ( +
+
+ )} + { + if (event.key === 'Enter' && !event.shiftKey) { + event.preventDefault(); + sendTextMsg(); + } + }} + placeholder="Type your message..." + onChange={(e) => onChangeTypedMessage(e.target.value)} + value={typedMessage} + ref={textAreaRef} + rows={1} + /> +
+ + {GIF && +
setGifOpen(!gifOpen)}> + +
+ } + {gifOpen && ( +
+ +
+ )} +
+ {!fileUploading && File && ( + <> +
setNewChat(true)} + > + +
+ uploadFile(e)} + /> + + )} +
+ {!(loading || fileUploading) && ( +
sendTextMsg()} + > + +
+ )} + + {(loading || fileUploading) && ( +
+ +
+ )} +
+ + } +
+
+ ) +} + +const Container = styled.div` + width: 100%; + overflow: hidden; +`; +const TypebarSection = styled(Section)` + gap: 10px; + @media ${device.mobileL} { + gap: 0px; + } +`; +const SendSection = styled(Section)` + gap: 11.5px; + @media ${device.mobileL} { + gap: 7.5px; + } +`; +const MultiLineInput = styled.textarea` + font-family: inherit; + font-weight: 400; + transform: translateY(3px); + font-size: 16px; + outline: none; + overflow-y: auto; + box-sizing: border-box; + background:${(props) => props.theme.bgColorPrimary}; + border: none; + color: ${(props) => props.theme.textColorSecondary}; + resize: none; + flex: 1; + padding-right: 5px; + align-self: end; + @media ${device.mobileL} { + font-size: 14px; + } + &&::-webkit-scrollbar { + width: 4px; + padding-right: 0px; + } + ::-webkit-scrollbar-thumb { + background: rgb(181 181 186); + border-radius: 10px; + height: 50px; + } + ::placeholder { + color: ${(props) => props.theme.textColorSecondary}; + transform: translateY(1px); + @media ${device.mobileL} { + font-size: 14px; + } + } + + min-height: 25px; + max-height: 80px; + word-break: break-word; +`; +const FileInput = styled.input` + display: none; +`; \ No newline at end of file diff --git a/packages/uiweb/src/lib/components/chat/MessageInput/index.ts b/packages/uiweb/src/lib/components/chat/MessageInput/index.ts new file mode 100644 index 000000000..b0ef7c589 --- /dev/null +++ b/packages/uiweb/src/lib/components/chat/MessageInput/index.ts @@ -0,0 +1 @@ +export { MessageInput } from './MessageInput'; diff --git a/packages/uiweb/src/lib/components/chat/MessageList/MessageList.tsx b/packages/uiweb/src/lib/components/chat/MessageList/MessageList.tsx deleted file mode 100644 index d85ba01da..000000000 --- a/packages/uiweb/src/lib/components/chat/MessageList/MessageList.tsx +++ /dev/null @@ -1,210 +0,0 @@ -import React, { useContext, useEffect, useRef, useState } from 'react'; -import { ChatDataContext } from '../../../context'; -import { IMessageListProps } from '../exportedTypes'; -import { chatLimit } from '../../../config'; -import { IMessageIPFS } from '@pushprotocol/restapi'; -import useFetchHistoryMessages from '../../../hooks/chat/useFetchHistoryMessages'; -import styled from 'styled-components'; -import { Section, Span, Spinner } from '../../reusables'; -import moment from 'moment'; -import { MessageBubble } from '../MessageBubble'; -import { appendUniqueMessages, dateToFromNowDaily, pCAIP10ToWallet } from '../../../helpers'; -import { useChatData, usePushChatSocket } from '../../../hooks'; -import { Messagetype } from '../../../types'; -import { ThemeContext } from '../theme/ThemeProvider'; -import { IChatTheme } from '../theme'; - - - -/** - * @interface IThemeProps - * this interface is used for defining the props for styled components - */ -interface IThemeProps { - theme?: IChatTheme; - - } - -export const MessageList: React.FC = ( - options: IMessageListProps -) => { - const { conversationHash, limit = chatLimit } = options || {}; - const { pgpPrivateKey, account } = useChatData(); - const [messages, setMessages] = useState(); - const { historyMessages, loading } = useFetchHistoryMessages(); - const listInnerRef = useRef(null); - const bottomRef = useRef(null); - const { messagesSinceLastConnection } = usePushChatSocket(); - const theme = useContext(ThemeContext); - const dates = new Set(); - - useEffect(() => { - if ( - Object.keys(messagesSinceLastConnection || {}).length - ) { - if (!Object.keys(messages || {}).length) { - setMessages({ - messages: messagesSinceLastConnection, - lastThreadHash: messagesSinceLastConnection.lastThreadHash, - }); - } else { - const newMessageList = appendUniqueMessages(messages as Messagetype,[messagesSinceLastConnection],false); - setMessages( { - - messages: newMessageList, - lastThreadHash: messages!.lastThreadHash, - - }); - } - scrollToBottom(null); - } - }, [messagesSinceLastConnection]); - - useEffect(() => { - if (conversationHash) { - (async function () { - await getMessagesCall(); - })(); - } - }, [conversationHash, pgpPrivateKey, account]); - - useEffect(() => { - scrollToBottom(null); - }, [conversationHash]); - - useEffect(() => { - if ( - conversationHash && - Object.keys(messages || {}).length && - messages?.messages.length && - messages?.messages.length <= limit - ) { - scrollToBottom(null); - } - }, [messages]); - - const scrollToBottom = (behavior?: string | null) => { - bottomRef?.current?.scrollIntoView( - !behavior ? true : { behavior: 'smooth' } - ); - }; - - const onScroll = async () => { - if (listInnerRef.current) { - const { scrollTop } = listInnerRef.current; - if (scrollTop === 0) { - const content = listInnerRef.current; - const curScrollPos = content.scrollTop; - const oldScroll = content.scrollHeight - content.clientHeight; - - await getMessagesCall(); - - const newScroll = content.scrollHeight - content.clientHeight; - content.scrollTop = curScrollPos + (newScroll - oldScroll); - } - } - }; - - const getMessagesCall = async () => { - let threadHash = null; - if (!messages) { - threadHash = conversationHash; - } else { - threadHash = messages?.lastThreadHash; - } - if (threadHash) { - const chatHistory = await historyMessages({ - limit: limit, - threadHash, - }); - if (chatHistory?.length) { - if (Object.keys(messages || {}) && messages?.messages.length) { - const newMessageList = appendUniqueMessages(messages,chatHistory,true); - setMessages({ - messages: newMessageList, - lastThreadHash: chatHistory[0].link, - }); - } else { - setMessages({ - messages: chatHistory, - lastThreadHash: chatHistory[0].link, - }); - } - } - } - }; - - - type RenderDataType = { - chat: IMessageIPFS; - dateNum: string; - }; - - const renderDate = ({ chat, dateNum }: RenderDataType) => { - const timestampDate = dateToFromNowDaily(chat.timestamp as number); - dates.add(dateNum); - return ( - - {timestampDate} - - ); - }; - return ( - onScroll()} - > - {loading ? : ''} - -
- {messages?.messages.map((chat: IMessageIPFS, index: number) => { - const dateNum = moment(chat.timestamp).format('L'); - const position = - pCAIP10ToWallet(chat.fromDID).toLowerCase() !== - account?.toLowerCase() - ? 0 - : 1; - return ( - <> - {dates.has(dateNum) ? null : renderDate({ chat, dateNum })} -
- -
- - ); - })} -
-
-
- ); -}; - -//styles -const MessageListCard = styled(Section)` -&::-webkit-scrollbar-thumb { - background: ${(props) => props.theme.accentBgColor}; - border-radius: 10px; - } - - &::-webkit-scrollbar { - width: 5px; - } - -`; diff --git a/packages/uiweb/src/lib/components/chat/MessageList/index.ts b/packages/uiweb/src/lib/components/chat/MessageList/index.ts deleted file mode 100644 index 80a558955..000000000 --- a/packages/uiweb/src/lib/components/chat/MessageList/index.ts +++ /dev/null @@ -1 +0,0 @@ -export {MessageList} from './MessageList'; \ No newline at end of file diff --git a/packages/uiweb/src/lib/components/chat/exportedTypes.ts b/packages/uiweb/src/lib/components/chat/exportedTypes.ts index a0a0dbcf1..4da3d5691 100644 --- a/packages/uiweb/src/lib/components/chat/exportedTypes.ts +++ b/packages/uiweb/src/lib/components/chat/exportedTypes.ts @@ -1,22 +1,152 @@ -import type { IMessageIPFS } from '@pushprotocol/restapi'; +import type { IMessageIPFS } from '@pushprotocol/restapi'; +import { IChatTheme } from "./theme"; +import { IGroup } from '../../types' -export interface IMessageListProps { - conversationHash: string; - limit?: number; - } +export interface IChatViewListProps { + chatId: string; + limit?: number; +} + +export interface IChatViewComponentProps { + messageInput?: boolean; + chatViewList?: boolean; + chatProfile?: boolean; //name needs to change + chatId: string; //need confirmation on this + limit?: number; + emoji?: boolean; + gif?: boolean; + file?: boolean; + isConnected?: boolean; +} + +export interface IChatProfile { + chatId: string; + style: "Info" | "Preview"; +} export interface TwitterFeedReturnType { - tweetId: string; - messageType: string; + tweetId: string; + messageType: string; +} + +export interface IToast { + message: string; + status: string; } +export type OptionProps = { + options: boolean; + setOptions: React.Dispatch>; + isGroup: boolean; + chatInfo: any; + groupInfo: IGroup | null | undefined , + setGroupInfo: React.Dispatch>; + theme: IChatTheme; +} export type IMessagePayload = IMessageIPFS; export const CHAT_THEME_OPTIONS = { LIGHT: 'light', DARK: 'dark', - } as const; -export type ChatThemeOptions = (typeof CHAT_THEME_OPTIONS)[keyof typeof CHAT_THEME_OPTIONS]; +export type GIFType = { + url: string; + width: number; + height: number; +}; + +export interface MessageInputProps { + chatId: string; + Emoji?: boolean; + GIF?: boolean; + File?: boolean; + Image?: boolean; + isConnected?: boolean; +} + +export type UpdateGroupType = { + groupInfo: IGroup, + connectedUser: User, + adminList: Array, + memberList: Array, +} + +export type MemberListContainerType = { + key?: number; + memberData: User; + handleMemberList: (member: User) => void; + handleMembers?: (value: User[]) => void; + lightIcon: any; + darkIcon: any; + memberList?: any; +}; + +export interface WalletProfileContainerProps { + id?: any; + background?: any; + border?: any; + +}; + +export interface MessageIPFS { + fromCAIP10: string + toCAIP10: string + fromDID: string + toDID: string + messageType: string + messageContent: string + signature: string + sigType: string + link: string | null + timestamp?: number + encType: string + encryptedSecret: string +} + +export interface Feeds { + chatId?: string; + msg: MessageIPFS; + did: string; + wallets: string; + profilePicture: string | null; + publicKey: string | null; + about: string | null; + threadhash: string | null; + intent: string | null; + intentSentBy: string | null; + intentTimestamp: Date; + combinedDID: string; + cid?: string; + groupInformation?: IGroup +} + +export interface User { + did: string; + wallets: string; + profilePicture: string | null; + publicKey: string; + encryptedPrivateKey: string; + encryptionType: string; + signature: string; + sigType: string; + about: string | null; + name: string | null; + numMsg: number; + allowedNumMsg: number; + linkedListHash?: string | null; + isAdmin?:boolean; +} + +export interface ShadowedProps { + setPosition: boolean; +}; + +export interface ModalButtonProps { + memberListCount?: boolean; + theme?: IChatTheme; + isLoading?: boolean; +}; + + export {IChatTheme} from './theme'; diff --git a/packages/uiweb/src/lib/components/chat/helpers/Modal.tsx b/packages/uiweb/src/lib/components/chat/helpers/Modal.tsx new file mode 100644 index 000000000..c7cbef9b4 --- /dev/null +++ b/packages/uiweb/src/lib/components/chat/helpers/Modal.tsx @@ -0,0 +1,99 @@ +/** + * @file Modal + * generic modal component for spaces UI + * does not handle any business logic, acts only as a container + */ +import { useRef, useContext } from 'react'; +import styled from 'styled-components' +import { ThemeContext } from '../theme/ThemeProvider'; +import { useClickAway } from '../../../hooks'; + +// import { ThemeContext } from '../theme/ThemeProvider'; + +// import { useClickAway } from '../../../hooks'; + +interface IModalProps { + width?: string; + clickawayClose?: () => void; + children: any; +} + +const ClickawayCloseModal = ({ children, clickawayClose, width }: IModalProps) => { + const modalRef = useRef(null); + const theme = useContext(ThemeContext) + + useClickAway(modalRef, () => { + if (clickawayClose) { + clickawayClose(); + } + }); + + return ( + + {children} + + ); +}; + +export const Modal = ({ clickawayClose, children, width }: IModalProps) => { + const theme = useContext(ThemeContext) + return ( + + {clickawayClose ? ( + {children} + ) : ( + + { children } + + )} + + ); +}; + +/* styling */ + +const ModalOverlay = styled.div` + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + width: 100%; + height: 100%; + background-color: rgba(0, 0, 0, 0.4); /* Black with 40% opacity */ + display: flex; + color: ${props => props.theme.textColorPrimary ?? '#000'}; + justify-content: center; + align-items: center; + z-index: 2000; + + max-height: 100vh; + overflow-y: auto; + margin: auto !important; +`; + +const ModalParent = styled.div` + // position: absolute; + // top: 50%; + // left: 50%; + // transform: translate(-50%, -50%); + + display: flex; + flex-direction: column; + align-items: center; + padding: 24px 20px; + + background: ${(props => props.theme.bgColorPrimary)}; + border-radius: 12px; + + width: ${(props => props.width ? props.width : 'auto')}; + margin: auto !important; + + @media (max-width: 425px) { + min-width: 300px; + max-width: 300px; + } +`; diff --git a/packages/uiweb/src/lib/components/chat/helpers/NewToast.tsx b/packages/uiweb/src/lib/components/chat/helpers/NewToast.tsx new file mode 100644 index 000000000..3009b3054 --- /dev/null +++ b/packages/uiweb/src/lib/components/chat/helpers/NewToast.tsx @@ -0,0 +1,228 @@ +/* eslint-disable @typescript-eslint/no-inferrable-types */ +// // React + Web3 Essentials + +// // External Packages +import { Spinner } from '../../supportChat/spinner/Spinner'; +import { toast } from 'react-toastify'; +import styled, { ThemeProvider } from 'styled-components'; +import CloseIcon from '../../../icons/close.svg'; +import useMediaQuery from './useMediaQuery'; +import { useContext, useRef } from 'react'; +import { Image } from '../../reusables'; +import { device } from '../../../config'; +import { ThemeContext } from '../theme/ThemeProvider'; +import { MdOutlineClose } from 'react-icons/md'; +// import useMediaQuery from './useMediaQuery'; + + +// Types +type LoaderToastType = { msg: string; loaderColor: string; textColor: string }; + +const override: React.CSSProperties = { + // width: "fit-content", + height: '45px', +}; + + +const LoaderToast = ({ msg, loaderColor, textColor }: LoaderToastType) => ( + + + + {msg} + + +); + +const CloseButton = ({ closeToast }:{ closeToast: any }) => ( + +); + +export type ShowLoaderToastType = ({ loaderMessage }: { loaderMessage: string }) => React.ReactText; + +export type ShowMessageToastType = ({ + toastTitle, + toastMessage, + toastType, + getToastIcon, +}: { + toastTitle: string; + toastMessage: string; + toastType: 'SUCCESS' | 'ERROR'; + getToastIcon?: (size: number) => JSX.Element; +}) => void; + +const useToast = ( + autoClose: number = 3000, + position: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right' = 'top-right' +) => { + const toastId = useRef(null); + const theme = useContext(ThemeContext); + const isMobile = useMediaQuery(device.tablet); + + let isLoaderToastShown = false; + + const showLoaderToast: ShowLoaderToastType = ({ loaderMessage }) => { + isLoaderToastShown = true; + return (toastId.current = toast( + + + , + { + position, + autoClose: false, + hideProgressBar: true, + closeOnClick: true, + pauseOnHover: true, + draggable: true, + progress: undefined, + closeButton: false, + style: { + background: theme.mainBg, + border: `1px solid ${theme.toastBorderColor}`, + boxShadow: `8px 8px 8px ${theme.toastShadowColor}`, + borderRadius: '20px', + }, + } + )); + }; + + const showMessageToast: ShowMessageToastType = ({ toastTitle, toastMessage, toastType, getToastIcon }) => { + + const toastUI = ( + + {getToastIcon ? getToastIcon(30) : ''} + + + {toastTitle} + + + {toastMessage} + + + + ); + + const toastRenderParams = { + position, + hideProgressBar: true, + closeOnClick: true, + pauseOnHover: true, + draggable: true, + progress: undefined, + type: toast.TYPE.DEFAULT, + closeButton: CloseButton, + autoClose: autoClose, + style: { + background: toastType === 'SUCCESS' ? theme.toastSuccessBackground : theme.toastErrorBackground, + boxShadow: `10px 10px 10px ${theme.toastShadowColor}`, + borderRadius: '20px', + margin: isMobile ? '20px' : '0px', + }, + }; + + if (!isLoaderToastShown) { + // render a new toast + toastId.current = toast(toastUI, { + ...toastRenderParams, + }); + } + + // update the old toast + toast.update(toastId.current, { + render: toastUI, + ...toastRenderParams, + }); + }; + + return { + showLoaderToast, + showMessageToast, + }; +}; + +const LoaderNotification = styled.div` + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; + margin: 1% 3%; +`; +const LoaderMessage = styled.div` + margin-left: 3%; + font-size: 1rem; + font-weight: 600; + line-height: 1.3rem; + letter-spacing: 0em; + text-align: left; +`; + +const Toast = styled.div` + display: flex; + flex-direction: row; + align-items: flex-start; + margin: 1.5% 1%; +`; +const ToastIcon = styled.div` + width: 15%; + margin-right: 4%; +`; +const ToastContent = styled.div` + display: flex; + flex-direction: column; + align-items: flex-start; +`; +const ToastTitle = styled.div` + font-weight: 500; + font-size: 1.125rem; + letter-spacing: -0.019em; + line-height: 1.4rem; + letter-spacing: 0em; + text-align: left; + margin-bottom: 1%; +`; +const ToastMessage = styled.div` + font-weight: 400; + font-size: 0.9375rem; + line-height: 1.3rem; + text-align: left; +`; + +const Button = styled.button` + cursor: pointer; + background: none; + margin: 0; + padding: 0; + width: 1.3rem; + height: 1.3rem; + border: none; +`; + +export default useToast; diff --git a/packages/uiweb/src/lib/components/chat/helpers/Toast.tsx b/packages/uiweb/src/lib/components/chat/helpers/Toast.tsx new file mode 100644 index 000000000..0411a0c6a --- /dev/null +++ b/packages/uiweb/src/lib/components/chat/helpers/Toast.tsx @@ -0,0 +1,190 @@ +import { useState, useEffect, Fragment, useContext } from 'react'; +import styled, { keyframes } from 'styled-components'; +import { Image, Section } from '../../reusables'; +import CloseIcon from '../../../icons/close.svg'; +import { CheckCircleIcon } from '../../../icons/CheckCircle'; +import { ThemeContext } from '../theme/ThemeProvider'; +import { IChatTheme } from '../theme'; +import InfoIcon from '../../../icons/infodark.svg'; + + +type toastProps = { + toastMessage: string; + position?: string; + status: string; +} + +const Toast = ({ toastMessage, position, status }: toastProps) => { + const [message, setMessage] = useState(''); + const theme = useContext(ThemeContext); + + useEffect(() => { + setMessage(toastMessage); + + setTimeout(() => { + setMessage(''); + }, 5000); + }, [toastMessage]); + + const closeToast = () => { + setMessage(''); + } + if (message !== '') { + return ( + + + + + {status === 'success' ? : } + + +
+ {status === 'success' ? 'Success' : 'Error'} + + {toastMessage} + +
+ + +
+
+ ); + } else return null +} + + +const Container = styled.div` + position: fixed !important; + z-index: 999999; + width: 100vw; + top: 0; + left: 0; + height: 100vh; +`; + +const toastInRight = keyframes` + from { + transform: translateX(100%); + } + to { + transform: translateX(0); + } +`; + +const toastInLeft = keyframes` + from { + transform: translateX(-100%); + } + to { + transform: translateX(0); + } +`; + +const Notification = styled.div<{theme?: IChatTheme, status?: string}>` + background: ${(props) => props.status === 'success' ? props.theme.toastSuccessBackground : props.theme.toastErrorBackground}; + transition: .3s ease; + position: fixed; + pointer-events: auto; + overflow: hidden; + margin: 0 0 6px; + margin-bottom: 15px; + // width: 300px; + max-height: 100px; + border-radius: 16px; + box-shadow: 0 0 10px #999; + box-shadow: 10px 10px 10px ${(props) => props.theme.toastShadowColor}; + opacity: 1; + display: flex; + flex-direction: row; + gap: 10px; + + + &:hover { + box-shadow: 0 0 12px #fff; + opacity: 1; + cursor: pointer; + } + + &.toast { + height: fit-content; + width: fit-content; + padding: 20px 15px; + } + + &.top-right { + top: 20px; + right: 20px; + animation: ${toastInRight} .3s ease-in-out .3s both; + } + + &.bottom-right { + bottom: 20px; + right: 20px; + transition: transform .6s ease-in-out; + animation: ${toastInRight} .7s; + } + + &.top-left { + top: 20px; + left: 20px; + transition: transform .6s ease-in; + animation: ${toastInLeft} .7s; + } + + &.bottom-left { + bottom: 20px; + left: 20px; + transition: transform .6s ease-in; + animation: ${toastInLeft} .7s; + } +`; + + + +const NotificationImage = styled.div` +`; + +const NotificationTitle = styled.div` + font-weight: 700; + font-size: 16px; + text-align: left; + margin-top: 0; + margin-bottom: 5px; + min-width: 250px; + color: ${(props) => props.theme.textColorPrimary}; + +`; + +const NotificationMessage = styled.div` + margin: 0; + text-align: left; + white-space: nowrap; + color: ${(props) => props.theme.snackbarBorderText}; + + font-weight: 400; + font-size: 14px; +`; + +const Button = styled.div` + position: relative; + font-weight: 700; + color: #fff; + outline: none; + border: none; + text-shadow: 0 1px 0 #fff; + opacity: 1; + line-height: 1; + font-size: 16px; + padding: 0; + cursor: pointer; + background: 0 0; + border: 0; +`; + +export default Toast; \ No newline at end of file diff --git a/packages/uiweb/src/lib/components/chat/helpers/helper.ts b/packages/uiweb/src/lib/components/chat/helpers/helper.ts index 2f6aff6a6..d4db16867 100644 --- a/packages/uiweb/src/lib/components/chat/helpers/helper.ts +++ b/packages/uiweb/src/lib/components/chat/helpers/helper.ts @@ -1,2 +1,106 @@ -import { IMessagePayload } from "../exportedTypes"; +import { IMessagePayload, User } from "../exportedTypes"; +import { ethers } from "ethers"; +import { IGroup } from "../../../types"; +import { walletToPCAIP10 } from "../../../helpers"; +export const getAdminList = (groupInformation: IGroup): Array => { + const adminsFromMembers = convertToWalletAddressList(groupInformation?.members.filter((admin) => admin.isAdmin == true)); + const adminsFromPendingMembers = convertToWalletAddressList(groupInformation?.pendingMembers.filter((admin) => admin.isAdmin == true)); + const adminList = [...adminsFromMembers,...adminsFromPendingMembers]; + return adminList + }; + +export const convertToWalletAddressList = ( + memberList: { wallet: string }[] + ): string[] => { + return memberList ? memberList.map((member) => member.wallet) : []; + } + +export const getUpdatedMemberList = (groupInfo: IGroup ,walletAddress:string): Array =>{ + const members = groupInfo?.members?.filter((i) => i.wallet?.toLowerCase() !== walletAddress?.toLowerCase()); + return convertToWalletAddressList([...members,...groupInfo.pendingMembers]); +} + +export const getUpdatedAdminList = (groupInfo: IGroup, walletAddress: string | null, toRemove: boolean): Array => { + const groupAdminList: any = getAdminList(groupInfo); + if (!toRemove) { + return [...groupAdminList, walletAddress]; + } else { + const newAdminList = groupAdminList.filter((wallet: any) => wallet !== walletAddress); + return newAdminList; + } + }; + +export const profilePicture = `data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAvklEQVR4AcXBsW2FMBiF0Y8r3GQb6jeBxRauYRpo4yGQkMd4A7kg7Z/GUfSKe8703fKDkTATZsJsrr0RlZSJ9r4RLayMvLmJjnQS1d6IhJkwE2bT13U/DBzp5BN73xgRZsJMmM1HOolqb/yWiWpvjJSUiRZWopIykTATZsJs5g+1N6KSMiO1N/5DmAkzYTa9Lh6MhJkwE2ZzSZlo7xvRwson3txERzqJhJkwE2bT6+JhoKTMJ2pvjAgzYSbMfgDlXixqjH6gRgAAAABJRU5ErkJggg==`; + + +export const displayDefaultUser = ({ caip10 }: { caip10: string }): User => { + const userCreated: User = { + did: caip10, + wallets: caip10, + publicKey: 'temp', + profilePicture: profilePicture, + encryptedPrivateKey: 'temp', + encryptionType: 'temp', + signature: 'temp', + sigType: 'temp', + about: null, + name: null, + numMsg: 1, + allowedNumMsg: 100, + linkedListHash: null, + }; + return userCreated; + }; + +export const findObject = (data: any,parentArray: any[],property: string ): boolean => { + let isPresent = false; + if(data) { + parentArray.map((value) => { + if (value[property] == data[property]) { + isPresent = true; + } + }); + } + return isPresent; + } + +export const MemberAlreadyPresent = (member: any, groupMembers: any )=>{ + const memberCheck = groupMembers?.find((x: any)=>x.wallet?.toLowerCase() == member.wallets?.toLowerCase()); + if(memberCheck){ + return true; + } + return false; + } + +export const addWalletValidation = (member:User,memberList:any,groupMembers:any,account: any) =>{ + const checkIfMemberisAlreadyPresent = MemberAlreadyPresent(member, groupMembers); + + let errorMessage = ''; + + if (checkIfMemberisAlreadyPresent) { + errorMessage = "This Member is Already present in the group" + } + + if (memberList?.length + groupMembers?.length >= 9) { + errorMessage = 'No More Addresses can be added' + } + + if (memberList?.length >= 9) { + errorMessage = 'No More Addresses can be added' + } + + if (findObject(member, memberList, 'wallets')) { + errorMessage = 'Address is already added' + } + + if (member?.wallets?.toLowerCase() === walletToPCAIP10(account)?.toLowerCase()) { + errorMessage = 'Group Creator cannot be added as Member' + } + + return errorMessage; + } + +export function isValidETHAddress(address: string) { + return ethers.utils.isAddress(address); + } \ No newline at end of file diff --git a/packages/uiweb/src/lib/components/chat/helpers/index.ts b/packages/uiweb/src/lib/components/chat/helpers/index.ts new file mode 100644 index 000000000..eb150f0e9 --- /dev/null +++ b/packages/uiweb/src/lib/components/chat/helpers/index.ts @@ -0,0 +1 @@ +export * from './twitter'; \ No newline at end of file diff --git a/packages/uiweb/src/lib/components/chat/helpers/useMediaQuery.ts b/packages/uiweb/src/lib/components/chat/helpers/useMediaQuery.ts new file mode 100644 index 000000000..f5fa9d7d8 --- /dev/null +++ b/packages/uiweb/src/lib/components/chat/helpers/useMediaQuery.ts @@ -0,0 +1,43 @@ +import { useEffect, useState } from 'react'; + +function useMediaQuery(query: string): boolean { + const getMatches = (query: string): boolean => { + // Prevents SSR issues + if (typeof window !== 'undefined') { + return window.matchMedia(query).matches; + } + return false; + }; + + const [matches, setMatches] = useState(getMatches(query)); + + function handleChange() { + setMatches(getMatches(query)); + } + + useEffect(() => { + const matchMedia = window.matchMedia(query); + + // Triggered at the first client-side load and if query changes + handleChange(); + + // Listen matchMedia + if (matchMedia.addListener) { + matchMedia.addListener(handleChange); + } else { + matchMedia.addEventListener('change', handleChange); + } + + return () => { + if (matchMedia.removeListener) { + matchMedia.removeListener(handleChange); + } else { + matchMedia.removeEventListener('change', handleChange); + } + }; + }, [query]); + + return matches; +} + +export default useMediaQuery; \ No newline at end of file diff --git a/packages/uiweb/src/lib/components/chat/index.ts b/packages/uiweb/src/lib/components/chat/index.ts index 95973691a..10531f89e 100644 --- a/packages/uiweb/src/lib/components/chat/index.ts +++ b/packages/uiweb/src/lib/components/chat/index.ts @@ -1,4 +1,7 @@ -export { MessageBubble } from './MessageBubble'; -export * from './MessageList'; +export { ChatViewBubble } from './ChatViewBubble'; +export * from './ChatViewList'; export * from './exportedTypes'; +export * from "./ChatProfile" +export * from './ChatViewComponent' +export * from "./MessageInput" export * from './theme'; diff --git a/packages/uiweb/src/lib/components/chat/theme/index.ts b/packages/uiweb/src/lib/components/chat/theme/index.ts index 9a198030a..34947cdb4 100644 --- a/packages/uiweb/src/lib/components/chat/theme/index.ts +++ b/packages/uiweb/src/lib/components/chat/theme/index.ts @@ -17,6 +17,32 @@ export interface IChatTheme { iconColorPrimary?: string; fontFamily?: string; chatBubblePrimaryBgColor?: string; + fileIconColor?: string; + dropdownBorderColor?: string; + modalPrimaryTextColor?: string; + modalSearchBarBorderColor?: string; + modalSearchBarBackground?: string; + snapFocusBg?: string; + groupButtonBackgroundColor?: string; + groupButtonTextColor?: string; + modalConfirmButtonBorder?: string; + groupSearchProfilBackground?: string, + modalInputBorderColor?: string, + snackbarBorderText?: string, + snackbarBorderIcon?: string, + modalContentBackground?: string, + modalProfileTextColor?: string, + toastSuccessBackground?: string; + toastErrorBackground?: string; + toastShadowColor?: string; + toastBorderColor?: string; + mainBg?: string; + modalBorderColor?: string; + modalDescriptionTextColor?: string; + modalIconColor?: string; + pendingCardBackground?: string, + modalHeadingColor?: string; + defaultBorder?: string; } export const lightChatTheme: IChatTheme = { @@ -31,12 +57,38 @@ export const lightChatTheme: IChatTheme = { accentTextColor: '#fff', btnColorPrimary: 'rgb(202, 89, 155)', border: 'none', - borderRadius: '32px', + borderRadius: '24px', iconColorPrimary: 'none', + fileIconColor: '#000', + dropdownBorderColor: '1px solid rgb(229, 232, 246)', + modalPrimaryTextColor: '#1E1E1E', + modalSearchBarBorderColor: '#BAC4D6', + modalSearchBarBackground: '#FFF', + snapFocusBg: '#F4F5FA', + groupButtonBackgroundColor: '#ADB0BE', + groupButtonTextColor: '#FFF', + modalConfirmButtonBorder: '1px solid #F4DCEA', + groupSearchProfilBackground: '#F4F5FA', + modalInputBorderColor: '#C2CBDB', + snackbarBorderText: '#000', + snackbarBorderIcon: 'none', + modalContentBackground: '#FFFFFF', + modalProfileTextColor: '#1E1E1E', + toastSuccessBackground: 'linear-gradient(90.15deg, #30CC8B -125.65%, #30CC8B -125.63%, #F3FFF9 42.81%)', + toastErrorBackground: 'linear-gradient(90.15deg, #FF2070 -125.65%, #FF2D79 -125.63%, #FFF9FB 42.81%)', + toastShadowColor: '#ccc', + toastBorderColor: '#F4F3FF', + mainBg: '#fff', + modalBorderColor: '#E5E8F6', + modalDescriptionTextColor: '#575D73', + modalIconColor: '#657795', + pendingCardBackground: 'rgba(173, 176, 190, 0.12)', + modalHeadingColor: '#333333', + defaultBorder: '#E5E8F6', }; export const darkChatTheme: IChatTheme = { - chatBubblePrimaryBgColor: 'fff', + chatBubblePrimaryBgColor: '#fff', bgColorPrimary: 'rgb(47, 49, 55)', bgColorSecondary: 'rgb(40, 42, 46)', textColorPrimary: '#fff', @@ -46,8 +98,35 @@ export const darkChatTheme: IChatTheme = { accentTextColor: '#fff', btnColorPrimary: 'rgb(202, 89, 155)', border: 'none', - borderRadius: '32px', + borderRadius: '24px', iconColorPrimary: 'brightness(0) saturate(100%) invert(89%) sepia(8%) saturate(1567%) hue-rotate(191deg) brightness(86%) contrast(93%)', + dropdownBorderColor: '1px solid rgb(74, 79, 103)', + fileIconColor: '#fff', + modalPrimaryTextColor: '#B6BCD6', + modalSearchBarBorderColor: '#4A4F67', + modalSearchBarBackground: '#282A2E', + snapFocusBg: '#404650', + groupButtonBackgroundColor: '#2F3137', + groupButtonTextColor: '#787E99', + modalConfirmButtonBorder: '1px solid #787E99', + groupSearchProfilBackground: '#404650', + modalInputBorderColor: '#4A4F67', + snackbarBorderText: '#B6BCD6', + snackbarBorderIcon: + 'brightness(0) saturate(100%) invert(89%) sepia(8%) saturate(1567%) hue-rotate(191deg) brightness(86%) contrast(93%)', + modalContentBackground: '#2F3137', + modalProfileTextColor: '#B6BCD6', + toastSuccessBackground: 'linear-gradient(90.15deg, #30CC8B -125.65%, #30CC8B -125.63%, #2F3137 42.81%)', + toastErrorBackground: 'linear-gradient(89.96deg, #FF2070 -101.85%, #2F3137 51.33%)', + toastShadowColor: '#00000010', + toastBorderColor: '#4A4F67', + mainBg: '#000', + modalBorderColor: '#4A4F67', + modalDescriptionTextColor: '#787E99', + modalIconColor: '#787E99', + pendingCardBackground: 'rgba(173, 176, 190, 0.08)', + modalHeadingColor: '#B6BCD6', + defaultBorder: '#4A4F67' }; diff --git a/packages/uiweb/src/lib/components/chatAndNotification/ChatAndNotification.tsx b/packages/uiweb/src/lib/components/chatAndNotification/ChatAndNotification.tsx index afef6def9..b65c46478 100644 --- a/packages/uiweb/src/lib/components/chatAndNotification/ChatAndNotification.tsx +++ b/packages/uiweb/src/lib/components/chatAndNotification/ChatAndNotification.tsx @@ -128,7 +128,7 @@ export const ChatAndNotification = () => { (async () => { let user; if (account) { - user = await fetchChatProfile({ profileId: account }); + user = await fetchChatProfile({ profileId: account ,env}); if (user) setConnectedProfile(user); } diff --git a/packages/uiweb/src/lib/components/reusables/sharedStyling.tsx b/packages/uiweb/src/lib/components/reusables/sharedStyling.tsx index 8fa096b69..6ce67adbf 100644 --- a/packages/uiweb/src/lib/components/reusables/sharedStyling.tsx +++ b/packages/uiweb/src/lib/components/reusables/sharedStyling.tsx @@ -41,7 +41,7 @@ export const Span = styled.span` cursor: ${(props) => props.cursor || 'default'}; margin: ${(props) => props.margin || '0px'}; padding: ${(props) => props.padding || '0px'}; - position: ${(props) => props.position || 'static'}; + position: ${(props) => props.position || 'relative'}; right: ${(props) => props.right || 'auto'}; text-align: ${(props) => props.textAlign || 'center'}; text-transform: ${(props) => props.textTransform || 'inherit'}; @@ -100,7 +100,7 @@ export const Section = styled.div` width: ${(props) => props.width || 'auto'}; overflow: ${(props) => props.overflow || 'default'}; padding: ${(props) => props.padding || '0px'}; - position: ${(props) => props.position || 'static'}; + position: ${(props) => props.position || 'relative'}; background: ${(props) => props.gradient ? props.gradient @@ -155,6 +155,7 @@ type DivStyleProps = { cursor?: string; alignSelf?:string; margin?:string; + textAlign?:string; }; export const Div = styled.div` height: ${(props) => props.height || 'auto'}; @@ -162,4 +163,5 @@ export const Div = styled.div` margin: ${(props) => props.margin || '0px'}; cursor: ${(props) => props.cursor || 'default'}; align-self: ${(props) => props.alignSelf || 'center'}; + text-align: ${(props) => props.textAlign || 'default'}; `; diff --git a/packages/uiweb/src/lib/components/supportChat/spinner/Spinner.tsx b/packages/uiweb/src/lib/components/supportChat/spinner/Spinner.tsx index 83627c35b..9aa0086c7 100644 --- a/packages/uiweb/src/lib/components/supportChat/spinner/Spinner.tsx +++ b/packages/uiweb/src/lib/components/supportChat/spinner/Spinner.tsx @@ -5,17 +5,18 @@ import { SpinnerSvg } from '../../../icons/SpinnerSvg'; type SpinnerPropType = { size?: string; + color?: string }; type SpinLoaderPropType = { width?: string; }; -export const Spinner: React.FC = ({ size = 42 }) => { +export const Spinner: React.FC = ({ size = 42, color }) => { const { theme } = useContext(SupportChatPropsContext); return ( - + ); }; diff --git a/packages/uiweb/src/lib/context/chatContext.ts b/packages/uiweb/src/lib/context/chatContext.ts index 330936e67..c075f3377 100644 --- a/packages/uiweb/src/lib/context/chatContext.ts +++ b/packages/uiweb/src/lib/context/chatContext.ts @@ -27,7 +27,7 @@ export const initialChatDataContextValues: IChatDataContextValues = { setPgpPrivateKey: () => { /**/ }, - env: Constants.ENV.DEV, + env: Constants.ENV.PROD, setEnv: () => { /**/ }, diff --git a/packages/uiweb/src/lib/dataProviders/ChatDataProvider.tsx b/packages/uiweb/src/lib/dataProviders/ChatDataProvider.tsx index e099818fe..278f9f5ca 100644 --- a/packages/uiweb/src/lib/dataProviders/ChatDataProvider.tsx +++ b/packages/uiweb/src/lib/dataProviders/ChatDataProvider.tsx @@ -8,7 +8,6 @@ import { ThemeContext } from '../components/chat/theme/ThemeProvider'; import useGetChatProfile from '../hooks/useGetChatProfile'; import { IUser } from '@pushprotocol/restapi'; import { IChatTheme, lightChatTheme } from '../components/chat/theme'; -import { ChatThemeOptions } from '../components'; export interface IChatUIProviderProps { children: ReactNode; @@ -35,22 +34,37 @@ export const ChatUIProvider = ({ const [isPushChatSocketConnected, setIsPushChatSocketConnected] = useState(false); + -useEffect(()=>{ + useEffect(() => { + resetStates(); + setEnvVal(env); + setAccountVal(account); + setPgpPrivateKeyVal(pgpPrivateKey); + }, [env,account,pgpPrivateKey]) + + useEffect(() => { setAccountVal(account) - setPgpPrivateKeyVal(pgpPrivateKey) -},[pgpPrivateKey]) + }, [account]) + + + +const resetStates = () => { + setPushChatSocket(null); + setIsPushChatSocketConnected(false); + +}; useEffect(() => { (async () => { let user; if (account) { - user = await fetchChatProfile({ profileId: account }); + user = await fetchChatProfile({ profileId: account,env }); if (user) setConnectedProfile(user); } })(); - }, [account]); + }, [account,env]); const value: IChatDataContextValues = { account: accountVal, @@ -69,7 +83,7 @@ useEffect(() => { const PROVIDER_THEME = Object.assign({}, lightChatTheme, theme); - +console.log(PROVIDER_THEME) return ( diff --git a/packages/uiweb/src/lib/helpers/address.ts b/packages/uiweb/src/lib/helpers/address.ts index 5c1912572..5af01ad2c 100644 --- a/packages/uiweb/src/lib/helpers/address.ts +++ b/packages/uiweb/src/lib/helpers/address.ts @@ -1,5 +1,5 @@ import { ethers } from 'ethers'; -import type { Web3Provider } from '@ethersproject/providers'; +import type { Web3Provider, InfuraProvider } from '@ethersproject/providers'; /** * @@ -64,6 +64,36 @@ export const resolveEns = (address: string, provider: Web3Provider) => { }); }; +export const resolveNewEns = async (address: string, provider: InfuraProvider) => { + const walletLowercase = pCAIP10ToWallet(address).toLowerCase(); + const checksumWallet = ethers.utils.getAddress(walletLowercase); + let result; + // let provider = ethers.getDefaultProvider('mainnet'); + // if ( + // window.location.hostname == 'app.push.org' || + // window.location.hostname == 'staging.push.org' || + // window.location.hostname == 'dev.push.org' || + // window.location.hostname == 'alpha.push.org' || + // window.location.hostname == 'w2w.push.org' + // ) { + // provider = new ethers.providers.InfuraProvider( + // 'mainnet', + // appConfig.infuraAPIKey + // ); + // } + + await provider.lookupAddress(checksumWallet).then((ens) => { + if (ens) { + result = ens; + return ens; + } else { + result = null; + return null; + } + }); + return result; +}; + export const isPCAIP = (id: string) => { const prefix = `eip155:`; return id?.startsWith(prefix); diff --git a/packages/uiweb/src/lib/helpers/chat/chat.ts b/packages/uiweb/src/lib/helpers/chat/chat.ts index 8990916c9..357dce16e 100644 --- a/packages/uiweb/src/lib/helpers/chat/chat.ts +++ b/packages/uiweb/src/lib/helpers/chat/chat.ts @@ -1,11 +1,12 @@ import * as PushAPI from '@pushprotocol/restapi'; import type { ENV } from '../../config'; import { Constants } from '../../config'; -import type { AccountEnvOptionsType, IMessageIPFS, Messagetype } from '../../types'; +import type { AccountEnvOptionsType, IGroup, IMessageIPFS, Messagetype } from '../../types'; import { ChatFeedsType } from '../../types'; import type { Env, IConnectedUser, IFeeds, IUser } from '@pushprotocol/restapi'; import { isPCAIP, pCAIP10ToWallet, walletToPCAIP10 } from '../address'; import { getData } from './localStorage'; +import { ethers } from 'ethers'; type HandleOnChatIconClickProps = { isModalOpen: boolean; @@ -127,7 +128,7 @@ export const copyToClipboard = (address: string): void => { }; -export const getDefaultFeedObject = ({user}:{user:IUser}):IFeeds => { +export const getDefaultFeedObject = ({user,groupInformation}:{user?:IUser,groupInformation?:IGroup}) :IFeeds => { const feed = { msg: { messageContent: '', @@ -143,21 +144,21 @@ export const getDefaultFeedObject = ({user}:{user:IUser}):IFeeds => { toDID: '', toCAIP10: '', }, - wallets: user.wallets, - did: user.did, + wallets: groupInformation?null: user!.wallets, + did: groupInformation?null: user!.did, threadhash: null, - profilePicture: user?.profile?.picture, + profilePicture: groupInformation?groupInformation.groupImage:user?.profile.picture, name: null, - about: user.about, + about: groupInformation?null:user!.about, intent: null, intentSentBy: null, intentTimestamp: new Date(), - publicKey: user.publicKey, + publicKey: groupInformation?null: user!.publicKey, combinedDID: '', cid: '', groupInformation: undefined, }; - return feed; + return feed as IFeeds; } type CheckIfIntentType = { @@ -165,6 +166,8 @@ type CheckIfIntentType = { account:string, } export const checkIfIntent = ({chat,account}:CheckIfIntentType):boolean => { + console.log(chat) + console.log(account) if(Object.keys(chat || {}).length && (chat.combinedDID.toLowerCase()).includes(walletToPCAIP10(account).toLowerCase())) { if( chat.intent && (chat.intent.toLowerCase()).includes(walletToPCAIP10(account).toLowerCase())) @@ -211,4 +214,28 @@ export const appendUniqueMessages = (parentList:Messagetype,newlist:IMessageIPFS ) ); return newMessageList -} \ No newline at end of file +} + +export const checkIfSameChat = ( + msg: IMessageIPFS, + account: string, + chatId: string +) => { + if (ethers.utils.isAddress(chatId)) { + chatId = walletToPCAIP10(chatId); + } + if ( + Object.keys(msg || {}).length && + ((chatId.toLowerCase() === + msg.fromCAIP10?.toLowerCase() && + walletToPCAIP10(account!).toLowerCase() === + msg.toCAIP10?.toLowerCase()) || + (chatId.toLowerCase() === + msg.toCAIP10?.toLowerCase() && + walletToPCAIP10(account!).toLowerCase() === + msg.fromCAIP10?.toLowerCase())) + ) { + return true; + } + return false; +}; \ No newline at end of file diff --git a/packages/uiweb/src/lib/helpers/chat/search.ts b/packages/uiweb/src/lib/helpers/chat/search.ts index 9c0bd1546..cad3ae4c4 100644 --- a/packages/uiweb/src/lib/helpers/chat/search.ts +++ b/packages/uiweb/src/lib/helpers/chat/search.ts @@ -59,7 +59,7 @@ export const getNewChatUser = async ({ address = await getAddress(searchText, env); if (address) { - chatProfile = await fetchChatProfile({ profileId: address }); + chatProfile = await fetchChatProfile({ profileId: address ,env}); if (!chatProfile) chatProfile = displayDefaultUser({ caip10: walletToPCAIP10(address) }); return chatProfile; diff --git a/packages/uiweb/src/lib/helpers/chat/time.ts b/packages/uiweb/src/lib/helpers/chat/time.ts index 30b211377..496105bff 100644 --- a/packages/uiweb/src/lib/helpers/chat/time.ts +++ b/packages/uiweb/src/lib/helpers/chat/time.ts @@ -6,7 +6,7 @@ export const dateToFromNowDaily = (timestamp: number): string => { lastDay: '[Yesterday]', sameDay: '[Today]', nextWeek: 'dddd', - sameElse: 'L' + sameElse: 'LL' }); return timestampDate; }; \ No newline at end of file diff --git a/packages/uiweb/src/lib/hooks/chat/index.ts b/packages/uiweb/src/lib/hooks/chat/index.ts index 93b5dafbe..8aa6ea6bb 100644 --- a/packages/uiweb/src/lib/hooks/chat/index.ts +++ b/packages/uiweb/src/lib/hooks/chat/index.ts @@ -1,4 +1,11 @@ export * from './useFetchHistoryMessages'; export * from './useChatData'; -export * from './usePushChatSocket'; \ No newline at end of file +export * from './useChatProfile'; +export * from './usePushChatSocket'; +export * from './usePushSendMessage'; +export * from './useGetGroupByID'; +export * from './useFetchChat'; +export * from './useFetchConversationHash'; +export * from './usePushSendMessage'; +export * from './useGetGroupByID'; diff --git a/packages/uiweb/src/lib/hooks/chat/useApproveChatRequest.ts b/packages/uiweb/src/lib/hooks/chat/useApproveChatRequest.ts new file mode 100644 index 000000000..474e64735 --- /dev/null +++ b/packages/uiweb/src/lib/hooks/chat/useApproveChatRequest.ts @@ -0,0 +1,41 @@ +import * as PushAPI from '@pushprotocol/restapi'; +import { useCallback, useContext, useState } from 'react'; +import { useChatData } from './useChatData'; + +interface ApproveChatParams { + chatId: string; +} + +const useApproveChatRequest = () => { + const [error, setError] = useState(); + const [loading, setLoading] = useState(false); + const { account, env,pgpPrivateKey } =useChatData(); + const approveChatRequest = useCallback(async (options:ApproveChatParams) => { + const { + + chatId, + } = options || {}; + setLoading(true); + try { + const response = await PushAPI.chat.approve({ + status: 'Approved', + account: account, + senderAddress: chatId, // receiver's address or chatId of a group + pgpPrivateKey:pgpPrivateKey, + env: env, + }); + return response; + } catch (error: Error | any) { + setLoading(false); + setError(error.message); + console.log(error); + return; + } + }, + [] + ); + + return { approveChatRequest, error, loading }; +}; + +export default useApproveChatRequest; diff --git a/packages/uiweb/src/lib/hooks/chat/useChatProfile.ts b/packages/uiweb/src/lib/hooks/chat/useChatProfile.ts new file mode 100644 index 000000000..8a6d71411 --- /dev/null +++ b/packages/uiweb/src/lib/hooks/chat/useChatProfile.ts @@ -0,0 +1,32 @@ +import * as PushAPI from '@pushprotocol/restapi'; +import { useCallback, useContext } from 'react'; +import { useChatData } from './useChatData'; + +export interface ProfileParams { + profileId: string; +} + +const useChatProfile = () => { + const { env } = useChatData(); + const fetchUserChatProfile = useCallback( + async ({ + profileId + }: ProfileParams): Promise => { + try { + const profile = await PushAPI.user.get({ + env: env, + account: profileId, + }); + return profile; + } catch (error) { + console.log(error); + return; + } + }, + [env] + ); + + return { fetchUserChatProfile }; +}; + +export default useChatProfile; diff --git a/packages/uiweb/src/lib/hooks/chat/useFetchChat.ts b/packages/uiweb/src/lib/hooks/chat/useFetchChat.ts new file mode 100644 index 000000000..4a0887956 --- /dev/null +++ b/packages/uiweb/src/lib/hooks/chat/useFetchChat.ts @@ -0,0 +1,46 @@ +import * as PushAPI from '@pushprotocol/restapi'; +import { Env } from '@pushprotocol/restapi'; +import { useCallback, useContext, useState } from 'react'; +import { useChatData } from './useChatData'; + + +interface fetchChat { + chatId: string; +} + +const useFetchChat = () => { + const [error, setError] = useState(); + const [loading, setLoading] = useState(false); + const { account, env,pgpPrivateKey } = useChatData(); + + + const fetchChat = useCallback( + async ({ chatId}: fetchChat) => { + console.log(env); + setLoading(true); + try { + const chat = await PushAPI.chat.chat({ + account: account!, + toDecrypt: pgpPrivateKey ? true : false, + pgpPrivateKey: String(pgpPrivateKey), + recipient: chatId, + env: env + }); + return chat; + } catch (error: Error | any) { + setLoading(false); + setError(error.message); + console.log(error); + return; + } + finally { + setLoading(false); + } + }, + [pgpPrivateKey,env] + ); + + return { fetchChat, error, loading }; +}; + +export default useFetchChat; diff --git a/packages/uiweb/src/lib/hooks/chat/useFetchConversationHash.ts b/packages/uiweb/src/lib/hooks/chat/useFetchConversationHash.ts new file mode 100644 index 000000000..407e34576 --- /dev/null +++ b/packages/uiweb/src/lib/hooks/chat/useFetchConversationHash.ts @@ -0,0 +1,38 @@ +import * as PushAPI from '@pushprotocol/restapi'; +import { Env } from '@pushprotocol/restapi'; +import { useCallback, useContext, useState } from 'react'; +import { useChatData } from './useChatData'; + +interface conversationHashParams { + conversationId: string; +} + +const useFetchConversationHash = () => { + const [error, setError] = useState(); + const [loading, setLoading] = useState(false); + const { account, env } = useChatData(); + + const fetchConversationHash = useCallback( + async ({ conversationId }: conversationHashParams) => { + setLoading(true); + try { + const response = await PushAPI.chat.conversationHash({ + conversationId, + account: account!, + env: env, + }); + setLoading(false); + return response; + } catch (error: Error | any) { + setLoading(false); + setError(error.message); + console.log(error); + return; + } + }, + [env, account] + ); + return { fetchConversationHash, error, loading }; +}; + +export default useFetchConversationHash; diff --git a/packages/uiweb/src/lib/hooks/chat/useFetchHistoryMessages.ts b/packages/uiweb/src/lib/hooks/chat/useFetchHistoryMessages.ts index bddf1e64c..cdd2ee84c 100644 --- a/packages/uiweb/src/lib/hooks/chat/useFetchHistoryMessages.ts +++ b/packages/uiweb/src/lib/hooks/chat/useFetchHistoryMessages.ts @@ -3,6 +3,7 @@ import * as PushAPI from '@pushprotocol/restapi'; import type { IMessageIPFS } from '@pushprotocol/restapi'; import { useCallback, useContext, useState } from 'react'; import { ChatDataContext } from '../../context'; +import { useChatData } from './useChatData'; @@ -18,8 +19,7 @@ const useFetchHistoryMessages const [error, setError] = useState(); const [loading, setLoading] = useState(false); - const { account, env,pgpPrivateKey } = - useContext(ChatDataContext); + const { account, env,pgpPrivateKey } = useChatData(); const historyMessages = useCallback(async ({threadHash,limit = 10,}:HistoryMessagesParams) => { @@ -27,7 +27,7 @@ const useFetchHistoryMessages try { const chatHistory:IMessageIPFS[] = await PushAPI.chat.history({ threadhash: threadHash, - account: account, + account: account!, toDecrypt: pgpPrivateKey ? true : false, pgpPrivateKey: String(pgpPrivateKey), limit: limit, diff --git a/packages/uiweb/src/lib/hooks/chat/useGetGroup.ts b/packages/uiweb/src/lib/hooks/chat/useGetGroup.ts new file mode 100644 index 000000000..d98059dca --- /dev/null +++ b/packages/uiweb/src/lib/hooks/chat/useGetGroup.ts @@ -0,0 +1,51 @@ +import * as PushAPI from '@pushprotocol/restapi'; +import { Env } from '@pushprotocol/restapi'; +import { useCallback, useContext, useState } from 'react'; +import { useChatData } from './useChatData'; +import { IGroup } from '../../types'; + +interface getGroup { + searchText: string; +} + +const useGetGroup = () => { + const [error, setError] = useState(); + const [loading, setLoading] = useState(false); + const { env } = useChatData(); + + const getGroup = useCallback( + async ({ searchText }: getGroup) => { + setLoading(true); + let group: IGroup; + try { + group = await PushAPI.chat.getGroup({ chatId: searchText, env }); + } catch (error: Error | any) { + if ((error.message as string).includes('No group with chatId')) { + try { + group = await PushAPI.chat.getGroupByName({ + groupName: searchText, + env, + }); + } catch (err) { + setLoading(false); + setError(error.message); + console.log(error); + return; + } + } else { + setLoading(false); + setError(error.message); + console.log(error); + return; + } + } + return group; + + }, + [ env] + ); + + return { getGroup, error, loading }; +}; + +export default useGetGroup; diff --git a/packages/uiweb/src/lib/hooks/chat/useGetGroupByID.ts b/packages/uiweb/src/lib/hooks/chat/useGetGroupByID.ts new file mode 100644 index 000000000..93881577f --- /dev/null +++ b/packages/uiweb/src/lib/hooks/chat/useGetGroupByID.ts @@ -0,0 +1,37 @@ +import * as PushAPI from '@pushprotocol/restapi'; +import { Env } from '@pushprotocol/restapi'; +import { useCallback, useContext, useState } from 'react'; +import { useChatData } from './useChatData'; +import { IGroup } from '../../types'; + +interface getGroup { + groupId: string; +} + +const useGetGroupByID = () => { + const [error, setError] = useState(); + const [loading, setLoading] = useState(false); + const { env } = useChatData(); + + const getGroupByID = useCallback( + async ({ groupId }: getGroup) => { + setLoading(true); + let group: IGroup; + try { + group = await PushAPI.chat.getGroup({ + chatId: groupId, + env: env, + }); + } catch (error) { + console.log(error); + return; + } + return group; + }, + [env] + ); + + return { getGroupByID, error, loading }; +}; + +export default useGetGroupByID; \ No newline at end of file diff --git a/packages/uiweb/src/lib/hooks/chat/usePushChatSocket.ts b/packages/uiweb/src/lib/hooks/chat/usePushChatSocket.ts index 81e173358..7d5484a7c 100644 --- a/packages/uiweb/src/lib/hooks/chat/usePushChatSocket.ts +++ b/packages/uiweb/src/lib/hooks/chat/usePushChatSocket.ts @@ -8,7 +8,6 @@ import { useChatData } from './useChatData'; import { SOCKET_TYPE } from '../../types'; import { getChatId } from '../../helpers'; - export type PushChatSocketHookOptions = { account?: string | null; env?: ENV; @@ -23,10 +22,21 @@ export const usePushChatSocket = () => { setIsPushChatSocketConnected, isPushChatSocketConnected, connectedProfile, - env + env, } = useChatData(); - const [messagesSinceLastConnection,setMessagesSinceLastConnection] = useState({}); + const [messagesSinceLastConnection, setMessagesSinceLastConnection] = + useState({}); + const [ + groupInformationSinceLastConnection, + setGroupInformationSinceLastConnection, + ] = useState({}); + + // useEffect(() => { + // // console.log(pgpPrivateKey, "pgppppppppp") + // // console.log(connectedProfile, "connectedProfileeeeeeeee") + // }, [pgpPrivateKey, connectedProfile]) + const addSocketEvents = useCallback(() => { console.log('addSocketEvents'); pushChatSocket?.on(EVENTS.CONNECT, () => { @@ -37,35 +47,41 @@ export const usePushChatSocket = () => { setIsPushChatSocketConnected(false); }); + pushChatSocket?.on(EVENTS.CHAT_RECEIVED_MESSAGE, async (chat: any) => { + console.log(chat) + console.log(connectedProfile) + console.log(pgpPrivateKey) + if (!connectedProfile || !pgpPrivateKey) { + return; + } + console.log(chat) + if ( + ( chat.messageCategory === 'Request') && + (chat.messageContent === null) && + (chat.messageType === null) + ) { + return; + } + console.log(chat) + const response = await PushAPI.chat.decryptConversation({ + messages: [chat], + connectedUser: connectedProfile, + pgpPrivateKey: pgpPrivateKey, + env: env, + }); + console.log(chat) + + if (response && response.length) { + setMessagesSinceLastConnection(response[0]); + } + }); + pushChatSocket?.on(EVENTS.CHAT_GROUPS, (groupInfo: any) => { + /** + * We receive a group creation or updated event. + */ + setGroupInformationSinceLastConnection(groupInfo); + }); - pushChatSocket?.on( - EVENTS.CHAT_RECEIVED_MESSAGE, - async (chat: any) => { - if (!connectedProfile || !pgpPrivateKey) { - return; - } - // const chatId = getChatId({ msg: chat, account:account! }).toLowerCase(); - if ( - chat.messageCategory === 'Request' && - chat.messageContent === null && - chat.messageType === null - ) { - return; - } - - const response = await PushAPI.chat.decryptConversation({ - messages: [chat], - connectedUser: connectedProfile, - pgpPrivateKey: pgpPrivateKey, - env: env, - }); - if (response && response.length) { - setMessagesSinceLastConnection(response[0]); - } - } - ); - - // eslint-disable-next-line react-hooks/exhaustive-deps }, [ pushChatSocket, @@ -73,7 +89,7 @@ export const usePushChatSocket = () => { pgpPrivateKey, messagesSinceLastConnection, env, - + connectedProfile, ]); const removeSocketEvents = useCallback(() => { @@ -93,7 +109,7 @@ export const usePushChatSocket = () => { removeSocketEvents(); } }; - }, [pushChatSocket]); + }, [pushChatSocket, pgpPrivateKey, connectedProfile]); /** * Whenever the required params to create a connection object change @@ -103,24 +119,21 @@ export const usePushChatSocket = () => { useEffect(() => { if (account) { if (pushChatSocket && pushChatSocket.connected) { - // console.log('=================>>> disconnection in the hook'); // pushChatSocket?.disconnect(); - } - else { + } else { const main = async () => { - const connectionObject = createSocketConnection({ - user: account, - env, - socketType: SOCKET_TYPE.CHAT, - socketOptions: { autoConnect: true, reconnectionAttempts: 3 }, - }); - console.warn('new connection object: ', connectionObject); - - setPushChatSocket(connectionObject); - }; - main().catch((err) => console.error(err)); + const connectionObject = createSocketConnection({ + user: account, + env, + socketType: SOCKET_TYPE.CHAT, + socketOptions: { autoConnect: true, reconnectionAttempts: 3 }, + }); + console.warn('new connection object: ', connectionObject); + + setPushChatSocket(connectionObject); + }; + main().catch((err) => console.error(err)); } - } // eslint-disable-next-line react-hooks/exhaustive-deps @@ -129,6 +142,7 @@ export const usePushChatSocket = () => { return { pushChatSocket, isPushChatSocketConnected, - messagesSinceLastConnection + messagesSinceLastConnection, + groupInformationSinceLastConnection, }; }; diff --git a/packages/uiweb/src/lib/hooks/chat/usePushSendMessage.ts b/packages/uiweb/src/lib/hooks/chat/usePushSendMessage.ts new file mode 100644 index 000000000..dbba7517d --- /dev/null +++ b/packages/uiweb/src/lib/hooks/chat/usePushSendMessage.ts @@ -0,0 +1,50 @@ +import * as PushAPI from '@pushprotocol/restapi'; +import { useCallback, useContext, useState } from 'react'; + +import { useChatData } from '..'; +import { ENV } from '../../config'; + +interface SendMessageParams { + message: string; + chatId: string; + messageType?: 'Text' | 'Image' | 'File' | 'GIF' | 'MediaEmbed'; +} + +const usePushSendMessage = () => { + const [error, setError] = useState(); + const [loading, setLoading] = useState(false); + + const { pgpPrivateKey, env, account } = useChatData(); + + const sendMessage = useCallback( + async (options: SendMessageParams) => { + const { chatId, message, messageType } = options || {}; + setLoading(true); + try { + const response = await PushAPI.chat.send({ + messageContent: message, + messageType: messageType, + receiverAddress: chatId, + account: account ? account : undefined, + pgpPrivateKey: pgpPrivateKey ? pgpPrivateKey : undefined, + env: env, + }); + setLoading(false); + if (!response) { + return false; + } + return; + } catch (error: Error | any) { + setLoading(false); + setError(error.message); + console.log(error); + return; + } + }, + [pgpPrivateKey, account] + ); + + return { sendMessage, error, loading }; +}; + +export default usePushSendMessage; diff --git a/packages/uiweb/src/lib/hooks/chatAndNotification/chat/useFetchHistoryMessages.ts b/packages/uiweb/src/lib/hooks/chatAndNotification/chat/useFetchHistoryMessages.ts index a5394d73d..48f23c7d0 100644 --- a/packages/uiweb/src/lib/hooks/chatAndNotification/chat/useFetchHistoryMessages.ts +++ b/packages/uiweb/src/lib/hooks/chatAndNotification/chat/useFetchHistoryMessages.ts @@ -36,6 +36,7 @@ const useFetchHistoryMessages limit: limit, env: env }); + console.log(chatHistory) chatHistory.reverse(); if (chats.get(selectedChatId as string)) { const uniqueMap: { [timestamp: number]: IMessageIPFS } = {}; diff --git a/packages/uiweb/src/lib/hooks/chatAndNotification/useChatNotificationSocket.ts b/packages/uiweb/src/lib/hooks/chatAndNotification/useChatNotificationSocket.ts index 921043f0d..927c85e9f 100644 --- a/packages/uiweb/src/lib/hooks/chatAndNotification/useChatNotificationSocket.ts +++ b/packages/uiweb/src/lib/hooks/chatAndNotification/useChatNotificationSocket.ts @@ -111,7 +111,7 @@ const useChatNotificationSocket = ({ chat.messageType === null ) { if (chat.messageOrigin === 'other') { - const user = await fetchChatProfile({ profileId: chatId }); + const user = await fetchChatProfile({ profileId: chatId,env }); if (user || Object.keys(user || {}).length) { let newOne: IFeeds = {} as IFeeds; diff --git a/packages/uiweb/src/lib/hooks/useGetChatProfile.ts b/packages/uiweb/src/lib/hooks/useGetChatProfile.ts index 6bfb1e5e5..8937a7026 100644 --- a/packages/uiweb/src/lib/hooks/useGetChatProfile.ts +++ b/packages/uiweb/src/lib/hooks/useGetChatProfile.ts @@ -4,13 +4,14 @@ import { ChatAndNotificationPropsContext } from '../context'; export interface GetProfileParams { profileId: string; + env:PushAPI.Env } const useGetChatProfile = () => { - const { env } = useContext(ChatAndNotificationPropsContext); const fetchChatProfile = useCallback( async ({ - profileId + profileId, + env }: GetProfileParams): Promise => { try { const profile = await PushAPI.user.get({ @@ -23,7 +24,7 @@ const useGetChatProfile = () => { return; } }, - [env] + [] ); return { fetchChatProfile }; diff --git a/packages/uiweb/src/lib/icons/Accept.svg b/packages/uiweb/src/lib/icons/Accept.svg deleted file mode 100644 index 183d2cbb7..000000000 --- a/packages/uiweb/src/lib/icons/Accept.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/packages/uiweb/src/lib/icons/Adddark.tsx b/packages/uiweb/src/lib/icons/Adddark.tsx new file mode 100644 index 000000000..b53d89497 --- /dev/null +++ b/packages/uiweb/src/lib/icons/Adddark.tsx @@ -0,0 +1,12 @@ +import React from 'react'; + +export const AddUserDarkIcon = () => { + return ( + + + + + + + ); +}; diff --git a/packages/uiweb/src/lib/icons/Attachment.tsx b/packages/uiweb/src/lib/icons/Attachment.tsx index 2e4f8a8d5..c8979b221 100644 --- a/packages/uiweb/src/lib/icons/Attachment.tsx +++ b/packages/uiweb/src/lib/icons/Attachment.tsx @@ -1,6 +1,10 @@ import React from 'react'; -export const AttachmentIcon = () => { +interface IAttachmentIconProps { + color?: string; +} + +export const AttachmentIcon: React.FC = ({ color = "#494D5F" }) => { return ( { > ); -}; +}; \ No newline at end of file diff --git a/packages/uiweb/src/lib/icons/BRBIcon.svg b/packages/uiweb/src/lib/icons/BRBIcon.svg new file mode 100644 index 000000000..1b4183b04 --- /dev/null +++ b/packages/uiweb/src/lib/icons/BRBIcon.svg @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/uiweb/src/lib/icons/CaretDownGrey.tsx b/packages/uiweb/src/lib/icons/CaretDownGrey.tsx new file mode 100644 index 000000000..1e2fe2b7f --- /dev/null +++ b/packages/uiweb/src/lib/icons/CaretDownGrey.tsx @@ -0,0 +1,9 @@ +import React from 'react'; + +export const CaretDownIcon = () => { + return ( + + + + ); +}; diff --git a/packages/uiweb/src/lib/icons/CheckCircle.tsx b/packages/uiweb/src/lib/icons/CheckCircle.tsx index b45fceb11..f537b2fcc 100644 --- a/packages/uiweb/src/lib/icons/CheckCircle.tsx +++ b/packages/uiweb/src/lib/icons/CheckCircle.tsx @@ -1,9 +1,9 @@ import React from 'react'; -export const CheckCircleIcon = () => { +export const CheckCircleIcon = ({ stroke, height, width }: { stroke?: string, height?: string, width?: string }) => { return ( - - + + ); diff --git a/packages/uiweb/src/lib/icons/Emoji.tsx b/packages/uiweb/src/lib/icons/Emoji.tsx index a3737a75a..cb9a9dbfc 100644 --- a/packages/uiweb/src/lib/icons/Emoji.tsx +++ b/packages/uiweb/src/lib/icons/Emoji.tsx @@ -1,15 +1,17 @@ import React from 'react'; -export const EmojiIcon = () => { +type EmojiIconsProps = { + color?: string; +} + +export const EmojiIcon: React.FC = ({color="none"}) => { return ( - - - - - + + + + + - - - + ); }; \ No newline at end of file diff --git a/packages/uiweb/src/lib/icons/Encryption.tsx b/packages/uiweb/src/lib/icons/Encryption.tsx index 896bb983b..2ca9faf8a 100644 --- a/packages/uiweb/src/lib/icons/Encryption.tsx +++ b/packages/uiweb/src/lib/icons/Encryption.tsx @@ -1,8 +1,12 @@ import React from 'react'; -export const EncryptionIcon = () => { +export const EncryptionIcon = ({ + size, +}: { + size?: string; +}) => { return ( - + diff --git a/packages/uiweb/src/lib/icons/Gif.tsx b/packages/uiweb/src/lib/icons/Gif.tsx index 37e14cfad..8011a992f 100644 --- a/packages/uiweb/src/lib/icons/Gif.tsx +++ b/packages/uiweb/src/lib/icons/Gif.tsx @@ -3,10 +3,10 @@ import React from 'react'; export const GifIcon = () => { return ( - - + + - - + + ); }; \ No newline at end of file diff --git a/packages/uiweb/src/lib/icons/Lock.png b/packages/uiweb/src/lib/icons/Lock.png new file mode 100644 index 000000000..e7713df82 Binary files /dev/null and b/packages/uiweb/src/lib/icons/Lock.png differ diff --git a/packages/uiweb/src/lib/icons/LockSlash.png b/packages/uiweb/src/lib/icons/LockSlash.png new file mode 100644 index 000000000..aa43a7d48 Binary files /dev/null and b/packages/uiweb/src/lib/icons/LockSlash.png differ diff --git a/packages/uiweb/src/lib/icons/MoreDark.tsx b/packages/uiweb/src/lib/icons/MoreDark.tsx new file mode 100644 index 000000000..3f20f22fd --- /dev/null +++ b/packages/uiweb/src/lib/icons/MoreDark.tsx @@ -0,0 +1,12 @@ +import React from 'react'; + +export const MoreDarkIcon = () => { + return ( + + + + + + ); +}; + diff --git a/packages/uiweb/src/lib/icons/MoreLight.tsx b/packages/uiweb/src/lib/icons/MoreLight.tsx new file mode 100644 index 000000000..4aa3f66ea --- /dev/null +++ b/packages/uiweb/src/lib/icons/MoreLight.tsx @@ -0,0 +1,11 @@ +import React from 'react'; + +export const MoreLightIcon = () => { + return ( + + + + + + ); +}; diff --git a/packages/uiweb/src/lib/icons/NoEncryption.tsx b/packages/uiweb/src/lib/icons/NoEncryption.tsx index 8cfb043dc..fecdb5961 100644 --- a/packages/uiweb/src/lib/icons/NoEncryption.tsx +++ b/packages/uiweb/src/lib/icons/NoEncryption.tsx @@ -1,8 +1,12 @@ import React from 'react'; -export const NoEncryptionIcon = () => { +export const NoEncryptionIcon = ({ + size, +}: { + size?: string; +}) => { return ( - + diff --git a/packages/uiweb/src/lib/icons/Public-Chat.svg b/packages/uiweb/src/lib/icons/Public-Chat.svg new file mode 100644 index 000000000..60eedda28 --- /dev/null +++ b/packages/uiweb/src/lib/icons/Public-Chat.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/uiweb/src/lib/icons/SearchIcon.tsx b/packages/uiweb/src/lib/icons/SearchIcon.tsx new file mode 100644 index 000000000..54680b230 --- /dev/null +++ b/packages/uiweb/src/lib/icons/SearchIcon.tsx @@ -0,0 +1,10 @@ +import React from 'react'; + +export const SearchIcon = () => { + return ( + + + + + ); +}; \ No newline at end of file diff --git a/packages/uiweb/src/lib/icons/SendCompIcon.tsx b/packages/uiweb/src/lib/icons/SendCompIcon.tsx new file mode 100644 index 000000000..e157b36ff --- /dev/null +++ b/packages/uiweb/src/lib/icons/SendCompIcon.tsx @@ -0,0 +1,13 @@ +import React from 'react'; + +type sendIconProps = { + color?: string; +}; + +export const SendCompIcon: React.FC = () => { + return ( + + + + ); +}; \ No newline at end of file diff --git a/packages/uiweb/src/lib/icons/Tick.tsx b/packages/uiweb/src/lib/icons/Tick.tsx new file mode 100644 index 000000000..4aa03d6ed --- /dev/null +++ b/packages/uiweb/src/lib/icons/Tick.tsx @@ -0,0 +1,15 @@ +import React from 'react'; + +export const TickSvg = () => { + return ( + + + + + + + + + + ); +}; \ No newline at end of file diff --git a/packages/uiweb/src/lib/icons/Token-Gated.svg b/packages/uiweb/src/lib/icons/Token-Gated.svg new file mode 100644 index 000000000..8983ac268 --- /dev/null +++ b/packages/uiweb/src/lib/icons/Token-Gated.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/uiweb/src/lib/icons/VerticalEllipsis.svg b/packages/uiweb/src/lib/icons/VerticalEllipsis.svg new file mode 100644 index 000000000..f03943378 --- /dev/null +++ b/packages/uiweb/src/lib/icons/VerticalEllipsis.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/uiweb/src/lib/icons/addadmin.svg b/packages/uiweb/src/lib/icons/addadmin.svg new file mode 100644 index 000000000..6219bb4be --- /dev/null +++ b/packages/uiweb/src/lib/icons/addadmin.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/uiweb/src/lib/icons/adddark.svg b/packages/uiweb/src/lib/icons/adddark.svg new file mode 100644 index 000000000..d6c0cf2ea --- /dev/null +++ b/packages/uiweb/src/lib/icons/adddark.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/packages/uiweb/src/lib/icons/addicon.svg b/packages/uiweb/src/lib/icons/addicon.svg new file mode 100644 index 000000000..8d18e64a5 --- /dev/null +++ b/packages/uiweb/src/lib/icons/addicon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/packages/uiweb/src/lib/icons/addlight.svg b/packages/uiweb/src/lib/icons/addlight.svg new file mode 100644 index 000000000..a20336863 --- /dev/null +++ b/packages/uiweb/src/lib/icons/addlight.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/packages/uiweb/src/lib/icons/close.svg b/packages/uiweb/src/lib/icons/close.svg new file mode 100644 index 000000000..9c8aa06e8 --- /dev/null +++ b/packages/uiweb/src/lib/icons/close.svg @@ -0,0 +1,4 @@ + + + + diff --git a/packages/uiweb/src/lib/icons/dismissadmin.svg b/packages/uiweb/src/lib/icons/dismissadmin.svg new file mode 100644 index 000000000..422831dd3 --- /dev/null +++ b/packages/uiweb/src/lib/icons/dismissadmin.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/packages/uiweb/src/lib/icons/greyImage.png b/packages/uiweb/src/lib/icons/greyImage.png new file mode 100644 index 000000000..85ab7e29d Binary files /dev/null and b/packages/uiweb/src/lib/icons/greyImage.png differ diff --git a/packages/uiweb/src/lib/icons/info.svg b/packages/uiweb/src/lib/icons/info.svg new file mode 100644 index 000000000..4aa6cdd7d --- /dev/null +++ b/packages/uiweb/src/lib/icons/info.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/packages/uiweb/src/lib/icons/infodark.svg b/packages/uiweb/src/lib/icons/infodark.svg new file mode 100644 index 000000000..c13ef750c --- /dev/null +++ b/packages/uiweb/src/lib/icons/infodark.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/packages/uiweb/src/lib/icons/more.svg b/packages/uiweb/src/lib/icons/more.svg new file mode 100644 index 000000000..b1450aaed --- /dev/null +++ b/packages/uiweb/src/lib/icons/more.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/packages/uiweb/src/lib/icons/moredark.svg b/packages/uiweb/src/lib/icons/moredark.svg new file mode 100644 index 000000000..cf68a170d --- /dev/null +++ b/packages/uiweb/src/lib/icons/moredark.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/packages/uiweb/src/lib/icons/remove.svg b/packages/uiweb/src/lib/icons/remove.svg new file mode 100644 index 000000000..84c460dd0 --- /dev/null +++ b/packages/uiweb/src/lib/icons/remove.svg @@ -0,0 +1,4 @@ + + + + diff --git a/packages/uiweb/src/lib/icons/search.svg b/packages/uiweb/src/lib/icons/search.svg new file mode 100644 index 000000000..146f4dd14 --- /dev/null +++ b/packages/uiweb/src/lib/icons/search.svg @@ -0,0 +1,4 @@ + + + + diff --git a/packages/uiweb/src/lib/icons/tick.svg b/packages/uiweb/src/lib/icons/tick.svg new file mode 100644 index 000000000..9b1fe7bdf --- /dev/null +++ b/packages/uiweb/src/lib/icons/tick.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/uiweb/src/lib/icons/videoCallIcon.svg b/packages/uiweb/src/lib/icons/videoCallIcon.svg new file mode 100644 index 000000000..8ec0d28a4 --- /dev/null +++ b/packages/uiweb/src/lib/icons/videoCallIcon.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/uiweb/src/lib/types/index.ts b/packages/uiweb/src/lib/types/index.ts index 51a5d97e9..4efb3e5ed 100644 --- a/packages/uiweb/src/lib/types/index.ts +++ b/packages/uiweb/src/lib/types/index.ts @@ -119,3 +119,21 @@ export interface FileMessageContent { size: number } export type Messagetype = { messages: IMessageIPFS[]; lastThreadHash: string | null }; + +export interface IGroup { + members: { wallet: string, publicKey: string, isAdmin: boolean, image: string }[], + pendingMembers: { wallet: string, publicKey: string, isAdmin: boolean, image: string }[], + contractAddressERC20: string | null, + numberOfERC20: number, + contractAddressNFT: string | null, + numberOfNFTTokens: number, + verificationProof: string, + groupImage: string | null, + groupName: string, + isPublic: boolean, + groupDescription: string | null, + groupCreator: string, + chatId: string, + groupType?:string | undefined +} + diff --git a/packages/uiweb/yarn.lock b/packages/uiweb/yarn.lock index 0e38365e7..80650f3c8 100644 --- a/packages/uiweb/yarn.lock +++ b/packages/uiweb/yarn.lock @@ -626,7 +626,7 @@ buffer-from@^1.1.2: resolved "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz" integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== -clsx@^1.2.1: +clsx@^1.1.1, clsx@^1.2.1: version "1.2.1" resolved "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz" integrity sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg== @@ -992,6 +992,11 @@ querystringify@^2.1.1: resolved "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz" integrity sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ== +react-icons@^4.10.1: + version "4.10.1" + resolved "https://registry.yarnpkg.com/react-icons/-/react-icons-4.10.1.tgz#3f3b5eec1f63c1796f6a26174a1091ca6437a500" + integrity sha512-/ngzDP/77tlCfqthiiGNZeYFACw85fUjZtLbedmJ5DTlNDIwETxhwBzdOJ21zj4iJdvc0J3y7yOsX3PpxAJzrw== + react-property@2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/react-property/-/react-property-2.0.0.tgz" @@ -1025,6 +1030,13 @@ react-style-singleton@^2.2.1: invariant "^2.2.4" tslib "^2.0.0" +react-toastify@^9.1.3: + version "9.1.3" + resolved "https://registry.yarnpkg.com/react-toastify/-/react-toastify-9.1.3.tgz#1e798d260d606f50e0fab5ee31daaae1d628c5ff" + integrity sha512-fPfb8ghtn/XMxw3LkxQBk3IyagNpF/LIKjOBflbexr2AWxAH1MJgvnESwEwBn9liLFXgTKWgBSdZpw9m4OTHTg== + dependencies: + clsx "^1.1.1" + react-twitter-embed@^4.0.4: version "4.0.4" resolved "https://registry.npmjs.org/react-twitter-embed/-/react-twitter-embed-4.0.4.tgz"