diff --git a/packages/examples/boilerplate/index.js b/packages/examples/boilerplate/index.js index d413fc9c5..b6e6f5998 100644 --- a/packages/examples/boilerplate/index.js +++ b/packages/examples/boilerplate/index.js @@ -8,7 +8,7 @@ const signerAlice = ethers.Wallet.createRandom(); const userAlice = await PushAPI.initialize(signerAlice, { env: CONSTANTS.ENV.PROD, }); -const userBobAddress = '0x60cD05eb31cc16cC37163D514bEF162406d482e1'; +const userBobAddress = '0x0149C2723496fEF62e6e7fa79A31E5ea22bA70C7'; const generateRandomWordsWithTimestamp = () => { return `${Math.random() @@ -17,7 +17,13 @@ const generateRandomWordsWithTimestamp = () => { }; userAlice.chat.send(userBobAddress, { - content: "Gm gm! It's a me... Alice! - " + generateRandomWordsWithTimestamp(), + type: 'Reaction', + content: '👍', + reference: 'bafyreia2okco5ocdxmoxon72erviypaht74u3dqunf3vydu237ybju4kw4', }); console.log('Message sent from Alice to ', userBobAddress); +// const groupPermissions = await userAlice.chat.group.info( +// 'a7d0581affdaea7b80be836ea5f8a982c0dfd56fb30ee2b01c64980afb152af7' +// ); +// console.log('info', groupPermissions); diff --git a/packages/examples/sdk-frontend-react/src/app/ChatSupportTest.tsx b/packages/examples/sdk-frontend-react/src/app/ChatSupportTest.tsx index 6f7d9a89c..2399aedc4 100644 --- a/packages/examples/sdk-frontend-react/src/app/ChatSupportTest.tsx +++ b/packages/examples/sdk-frontend-react/src/app/ChatSupportTest.tsx @@ -34,7 +34,7 @@ export const ChatSupportTest = () => { //works as Chat as well as Support Chat { + return ( +
+ + + +
+ ); +}; - return ( -
- - - -
- ) -} - -export default ChatPreviewTest; \ No newline at end of file +export default ChatPreviewTest; diff --git a/packages/examples/sdk-frontend-react/src/app/ChatUITest/ChatPreviewList.tsx b/packages/examples/sdk-frontend-react/src/app/ChatUITest/ChatPreviewList.tsx index 66aab03ef..832ae2461 100644 --- a/packages/examples/sdk-frontend-react/src/app/ChatUITest/ChatPreviewList.tsx +++ b/packages/examples/sdk-frontend-react/src/app/ChatUITest/ChatPreviewList.tsx @@ -191,9 +191,6 @@ const ChatPreviewListTest = () => { onPreload={(chats) => { console.log('preload chats are: ', chats); }} - // prefillChatPreviewList={prefill} - // listType='SEARCH' - // searchParamter='c2d544ad9d1efd5c5a593b143bf8232875c926cf28015564e70ad078b95f807e' /> diff --git a/packages/examples/sdk-frontend-react/src/app/ChatUITest/ChatPreviewSearchList.tsx b/packages/examples/sdk-frontend-react/src/app/ChatUITest/ChatPreviewSearchList.tsx new file mode 100644 index 000000000..ccad6a2e4 --- /dev/null +++ b/packages/examples/sdk-frontend-react/src/app/ChatUITest/ChatPreviewSearchList.tsx @@ -0,0 +1,21 @@ +import { ChatPreviewSearchList } from '@pushprotocol/uiweb'; +import styled from 'styled-components'; + +const ChatPreviewSearchListTest = () => { + const walletAddress = "0xFA3F8E79fb9B03e7a04295594785b91588Aa4DC8"; + return ( + <> + + + + + ); +}; + +export default ChatPreviewSearchListTest; + +const Conatiner = styled.div` +background: '#ffeded', +border: '1px solid rgb(226,8,128)', +height: '28.5vh', +`; diff --git a/packages/examples/sdk-frontend-react/src/app/ChatUITest/ChatProfile.tsx b/packages/examples/sdk-frontend-react/src/app/ChatUITest/ChatProfile.tsx index b39abfe8f..c51afedc0 100644 --- a/packages/examples/sdk-frontend-react/src/app/ChatUITest/ChatProfile.tsx +++ b/packages/examples/sdk-frontend-react/src/app/ChatUITest/ChatProfile.tsx @@ -1,4 +1,5 @@ import { ChatProfile } from '@pushprotocol/uiweb'; +import { CHAT_ID } from '../constants'; export const ChatProfileTest = () => { return ( @@ -6,8 +7,7 @@ export const ChatProfileTest = () => { left component} chatProfileRightHelperComponent={
right component
} - chatId="monalisha.wallet" - // chatId='36baf37e441fdd94e23406c6c716fc4e91a93a9ee68e070cd5b054534dbe09a6' + chatId={CHAT_ID} /> ); 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 d76a77baa..20b6d4e48 100644 --- a/packages/examples/sdk-frontend-react/src/app/ChatUITest/ChatUITest.tsx +++ b/packages/examples/sdk-frontend-react/src/app/ChatUITest/ChatUITest.tsx @@ -33,7 +33,7 @@ const ChatUITest = () => { CHAT BUBBLE - CHAT VIEW LIST + CHAT VIEW LIST CHAT VIEW @@ -44,6 +44,9 @@ const ChatUITest = () => { CHAT PREVIEW LIST + + CHAT PREVIEW SEARCH LIST + USER PROFILE COMPONENT diff --git a/packages/examples/sdk-frontend-react/src/app/ChatUITest/ChatViewComponent.tsx b/packages/examples/sdk-frontend-react/src/app/ChatUITest/ChatViewComponent.tsx index e5c68d7cf..66beffbd1 100644 --- a/packages/examples/sdk-frontend-react/src/app/ChatUITest/ChatViewComponent.tsx +++ b/packages/examples/sdk-frontend-react/src/app/ChatUITest/ChatViewComponent.tsx @@ -1,14 +1,9 @@ import styled from 'styled-components'; -import { - ChatView, - CreateGroupModal, - MODAL_BACKGROUND_TYPE, - MODAL_POSITION_TYPE, - UserProfile, -} from '@pushprotocol/uiweb'; +import { ChatView, MODAL_BACKGROUND_TYPE } from '@pushprotocol/uiweb'; import { Section } from '../components/StyledComponents'; import Img from '../../assets/epnsLogo.png'; +import { CHAT_ID } from '../constants'; const ChatViewComponentTest = () => { const chatFilterList = [ @@ -20,16 +15,12 @@ const ChatViewComponentTest = () => {

Chat View Test page

- {/* { - console.log('in close'); - }} - /> */} + {/* uncomment the below code to test create group modal */} {/* {console.log('in close')}} modalBackground={MODAL_BACKGROUND_TYPE.OVERLAY} modalPositionType={MODAL_POSITION_TYPE.RELATIVE}/> */} console.log('Verification Failed')} - chatId="34c44214589cecc176a136ee1daf0f0231ecc6d6574b920b5ae39eb971fa3cb4" + chatId={CHAT_ID} chatProfileLeftHelperComponent={ console.debug('clicked')} /> } @@ -37,19 +28,6 @@ const ChatViewComponentTest = () => { limit={10} isConnected={true} groupInfoModalBackground={MODAL_BACKGROUND_TYPE.OVERLAY} - // groupInfoModalPositionType={MODAL_POSITION_TYPE.RELATIVE} - // verificationFailModalPosition={MODAL_POSITION_TYPE.RELATIVE} - - // welcomeComponent={
- //

Welcome

- //

new chat

- //

Welcome

- //

new chat

- //

Welcome

- //

new chat

- //

Welcome

- - //
} />
@@ -62,5 +40,3 @@ const ChatViewComponentCard = styled(Section)` height: 80vh; position: relative; `; -//c2d544ad9d1efd5c5a593b143bf8232875c926cf28015564e70ad078b95f807e -//4ac5ab85c9c3d57adbdf2dba79357e56b2f9ef0256befe750d9f93af78d2ca68 diff --git a/packages/examples/sdk-frontend-react/src/app/ChatUITest/ChatViewListTest.tsx b/packages/examples/sdk-frontend-react/src/app/ChatUITest/ChatViewListTest.tsx index 400228016..2e18a4816 100644 --- a/packages/examples/sdk-frontend-react/src/app/ChatUITest/ChatViewListTest.tsx +++ b/packages/examples/sdk-frontend-react/src/app/ChatUITest/ChatViewListTest.tsx @@ -1,44 +1,25 @@ -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'; +import { ChatViewList } from '@pushprotocol/uiweb'; +import { CHAT_ID } from '../constants'; const ChatViewListTest = () => { - // const { account, pgpPrivateKey } = useContext(Web3Context) - - // const { env } = useContext(EnvContext); - - - // usePushChatSocket(); - - return (

Chat UI Test page

- {/* */} - - - + - {/* */}
); }; export default ChatViewListTest; - const ChatViewListCard = styled.div` -height:40vh; -background:black; -overflow: auto; -overflow-x: hidden; -`; \ No newline at end of file + height: 40vh; + background: black; + overflow: auto; + overflow-x: hidden; +`; diff --git a/packages/examples/sdk-frontend-react/src/app/app.tsx b/packages/examples/sdk-frontend-react/src/app/app.tsx index bb55e117c..8aeada002 100644 --- a/packages/examples/sdk-frontend-react/src/app/app.tsx +++ b/packages/examples/sdk-frontend-react/src/app/app.tsx @@ -106,6 +106,7 @@ import { SubscriptionManagerTest } from './widget/SubscriptionManagerTest'; import Widget from './widget/Widget'; // import { SubscriptionManagerTest } from './widget/SubscriptionManagerTest'; import { UserProfileTest } from './ChatUITest/UserProfileTest'; +import ChatPreviewSearchListTest from './ChatUITest/ChatPreviewSearchList'; window.Buffer = window.Buffer || Buffer; @@ -340,13 +341,7 @@ export function App() { - + } /> + } + /> } diff --git a/packages/examples/sdk-frontend-react/src/app/constants.ts b/packages/examples/sdk-frontend-react/src/app/constants.ts new file mode 100644 index 000000000..f7acc7974 --- /dev/null +++ b/packages/examples/sdk-frontend-react/src/app/constants.ts @@ -0,0 +1,3 @@ +// the unique id for a chat or the receivers's wallet ddress or domain name +export const CHAT_ID = + '34c44214589cecc176a136ee1daf0f0231ecc6d6574b920b5ae39eb971fa3cb4'; diff --git a/packages/uiweb/.babelrc b/packages/uiweb/.babelrc index e513ea41c..404251f62 100644 --- a/packages/uiweb/.babelrc +++ b/packages/uiweb/.babelrc @@ -8,5 +8,18 @@ } ] ], - "plugins": [["styled-components", { "pure": true, "ssr": true }]] + "plugins": [ + ["styled-components", { "pure": true, "ssr": true }], + [ + "module-resolver", + { + "root": ["./src"], + "alias": { + "@components": "./src/lib/components", + "@icons": "./src/lib/icons" + // Add more aliases as needed + } + } + ] + ] } diff --git a/packages/uiweb/package.json b/packages/uiweb/package.json index 7aa9bc513..5036e390a 100644 --- a/packages/uiweb/package.json +++ b/packages/uiweb/package.json @@ -37,9 +37,12 @@ "uuid": "^9.0.1" }, "peerDependencies": { - "@pushprotocol/restapi": "1.7.17", + "@pushprotocol/restapi": "1.7.19", "@pushprotocol/socket": "^0.5.0", "react": ">=16.8.0", "styled-components": "^6.0.8" + }, + "devDependencies": { + "babel-plugin-module-resolver": "^5.0.2" } } diff --git a/packages/uiweb/src/lib/components/chat/ChatPreviewList/ChatPreviewList.tsx b/packages/uiweb/src/lib/components/chat/ChatPreviewList/ChatPreviewList.tsx index 0042dfc3e..5d16295de 100644 --- a/packages/uiweb/src/lib/components/chat/ChatPreviewList/ChatPreviewList.tsx +++ b/packages/uiweb/src/lib/components/chat/ChatPreviewList/ChatPreviewList.tsx @@ -174,7 +174,9 @@ export const ChatPreviewList: React.FC = (options: IChatP overrideAccount, }); - console.debug('UIWeb::ChatPreviewList::loadMoreChats:: Fetched', type, nextpage, currentNonce, chatList); + console.debug( + `UIWeb::ChatPreviewList::loadMoreChats:: Fetched type - ${type} - nextpage - ${nextpage} - currentNonce - ${currentNonce} - chatList - ${chatList}` + ); if (chatList) { // get and transform chats @@ -489,12 +491,25 @@ export const ChatPreviewList: React.FC = (options: IChatP useEffect(() => { if ( + chatPreviewList.page !== 0 && listInnerRef && listInnerRef?.current && listInnerRef?.current?.parentElement && - !chatPreviewList.loading && - !chatPreviewList.loaded + !chatPreviewList.loading ) { + console.debug( + 'UIWeb::ChatPreviewList::useEffect[chatPreviewList.items]::Checking if we need to load more chats::', + chatPreviewList, + listInnerRef.current.clientHeight, + SCROLL_LIMIT, + listInnerRef.current.parentElement.clientHeight, + listInnerRef.current.clientHeight + SCROLL_LIMIT < listInnerRef.current.parentElement.clientHeight + ); + + if (chatPreviewList.loaded) { + return; + } + if (listInnerRef.current.clientHeight + SCROLL_LIMIT < listInnerRef.current.parentElement.clientHeight) { // set loading to true setChatPreviewList((prev) => ({ @@ -504,7 +519,7 @@ export const ChatPreviewList: React.FC = (options: IChatP })); } } - }, [chatPreviewList.page]); + }, [chatPreviewList.items]); // If badges count change useEffect(() => { @@ -613,7 +628,6 @@ export const ChatPreviewList: React.FC = (options: IChatP if (!error) { const chatInfo = await fetchChat({ chatId: formattedChatId }); - if (chatInfo && chatInfo?.meta?.group) groupProfile = await getGroupByIDnew({ groupId: formattedChatId, @@ -622,7 +636,6 @@ export const ChatPreviewList: React.FC = (options: IChatP formattedChatId = pCAIP10ToWallet( chatInfo?.participants.find((address) => address != walletToPCAIP10(user?.account)) || formattedChatId ); - //fetch profile if (!groupProfile) { userProfile = await getNewChatUser({ diff --git a/packages/uiweb/src/lib/components/chat/ChatProfile/ChatProfile.tsx b/packages/uiweb/src/lib/components/chat/ChatProfile/ChatProfile.tsx index 0281249b6..277e2e20d 100644 --- a/packages/uiweb/src/lib/components/chat/ChatProfile/ChatProfile.tsx +++ b/packages/uiweb/src/lib/components/chat/ChatProfile/ChatProfile.tsx @@ -219,10 +219,11 @@ export const ChatProfile: React.FC = ({ return ( {/* For showing Chat Profile */} -
+ {chatProfileLeftHelperComponent && (
= ({ }} loading={initialized.loading || initialized.profile.recipient === '' || initialized.profile.icon === ''} /> -
+
{/* For showing group related icons and menu */} -
= ({ cursor="pointer" maxHeight="1.75rem" overflow="hidden" + flex="none" > {chatProfileRightHelperComponent}
@@ -322,7 +324,7 @@ export const ChatProfile: React.FC = ({ )} )} -
+ {/* For showing chat info modal | modal && */} {modal && @@ -352,6 +354,7 @@ export const ChatProfile: React.FC = ({ const Container = styled(Section)` width: auto; + max-width: 100%; background: ${(props) => props.theme.backgroundColor.chatProfileBackground}; border: ${(props) => props.theme.border?.chatProfile}; border-radius: ${(props) => props.theme.borderRadius?.chatProfile}; @@ -362,7 +365,14 @@ const Container = styled(Section)` padding: 6px; box-sizing: border-box; align-self: stretch; - box-sizing: border-box; +`; + +const AddonComponentSection = styled(Section)` + gap: 10px; + + @media ${device.mobileL} { + gap: 5px; + } ; `; const ImageItem = styled.div` diff --git a/packages/uiweb/src/lib/components/chat/ChatView/ChatViewComponent.tsx b/packages/uiweb/src/lib/components/chat/ChatView/ChatViewComponent.tsx index 87fa3d8d6..43a59fc24 100644 --- a/packages/uiweb/src/lib/components/chat/ChatView/ChatViewComponent.tsx +++ b/packages/uiweb/src/lib/components/chat/ChatView/ChatViewComponent.tsx @@ -119,7 +119,8 @@ export const ChatViewComponent: React.FC = (options: IC )} {/* Load ChatViewList if in options */} -
= (options: IC chatId={initialized.derivedChatId} /> )} -
- - {/* Check if user is not in read mode */} - {/* This will probably be not needed anymore */} - {/* {!signer && !(!!account && !!pgpPrivateKey) && !isConnected && ( -
- -
- )} */} + {/* Load MessageInput if in options and user is present */} {messageInput && user && (
` border: ${(props) => props.theme.border?.chatViewComponent}; box-sizing: border-box; `; + +const ChatViewSection = styled(Section)` + @media (${device.mobileL}) { + margin: 0; + } +`; diff --git a/packages/uiweb/src/lib/components/chat/ChatViewBubble/ChatViewBubble.tsx b/packages/uiweb/src/lib/components/chat/ChatViewBubble/ChatViewBubble.tsx index 64ce72f93..c3770fde1 100644 --- a/packages/uiweb/src/lib/components/chat/ChatViewBubble/ChatViewBubble.tsx +++ b/packages/uiweb/src/lib/components/chat/ChatViewBubble/ChatViewBubble.tsx @@ -17,9 +17,17 @@ import { BsLightning } from 'react-icons/bs'; import { FaBell, FaLink, FaRegThumbsUp } from 'react-icons/fa'; import { MdError, MdOpenInNew } from 'react-icons/md'; import { FILE_ICON, allowedNetworks, device } from '../../../config'; -import { formatFileSize, getPfp, pCAIP10ToWallet, shortenText, sign, toSerialisedHexString } from '../../../helpers'; +import { + formatFileSize, + getPfp, + pCAIP10ToWallet, + shortenText, + sign, + toSerialisedHexString, + isMessageEncrypted, +} from '../../../helpers'; import { createBlockie } from '../../../helpers/blockies'; -import { FileMessageContent, FrameDetails, IFrame, IFrameButton } from '../../../types'; +import { FileMessageContent, FrameDetails, IFrame, IFrameButton, IReactionsForChatMessages } from '../../../types'; import { extractWebLink, getFormattedMetadata, hasWebLink } from '../../../utilities'; import { IMessagePayload, TwitterFeedReturnType } from '../exportedTypes'; import { Button, TextInput } from '../reusables'; @@ -30,6 +38,9 @@ import { ImageCard } from './cards/image/ImageCard'; import { MessageCard } from './cards/message/MessageCard'; import { TwitterCard } from './cards/twitter/TwitterCard'; +import { Reactions } from './reactions/Reactions'; +import { ReactionPicker } from './reactions/ReactionPicker'; + const SenderMessageAddress = ({ chat }: { chat: IMessagePayload }) => { const { user } = useContext(ChatDataContext); const theme = useContext(ThemeContext); @@ -136,13 +147,13 @@ const SenderMessageProfilePicture = ({ chat }: { chat: IMessagePayload }) => { }; const MessageWrapper = ({ - chat, + chatPayload, + showChatMeta, children, - isGroup, }: { - chat: IMessagePayload; + chatPayload: IMessagePayload; + showChatMeta: boolean; children: ReactNode; - isGroup: boolean; }) => { const { user } = useChatData(); const theme = useContext(ThemeContext); @@ -152,18 +163,20 @@ const MessageWrapper = ({ flexDirection="row" justifyContent="start" gap="6px" + width="100%" maxWidth="100%" > - {isGroup && pCAIP10ToWallet(chat?.fromCAIP10) !== pCAIP10ToWallet(user?.account ?? '') && ( - + {showChatMeta && pCAIP10ToWallet(chatPayload?.fromCAIP10) !== pCAIP10ToWallet(user?.account ?? '') && ( + )}
- {isGroup && pCAIP10ToWallet(chat?.fromCAIP10) !== pCAIP10ToWallet(user?.account ?? '') && ( - + {showChatMeta && pCAIP10ToWallet(chatPayload?.fromCAIP10) !== pCAIP10ToWallet(user?.account ?? '') && ( + )} {children}
@@ -173,94 +186,183 @@ const MessageWrapper = ({ export const ChatViewBubble = ({ decryptedMessagePayload, - isGroup, + chatPayload: payload, + chatReactions, + showChatMeta = false, + chatId, + actionId, + singularActionId, + setSingularActionId, }: { decryptedMessagePayload: IMessagePayload; - isGroup: boolean; + chatPayload?: IMessagePayload; + chatReactions?: any; + showChatMeta?: boolean; + chatId?: string; + actionId?: string | null | undefined; + singularActionId?: string | null | undefined; + setSingularActionId?: (singularActionId: string | null | undefined) => void; }) => { + // get theme + const theme = useContext(ThemeContext); + + // TODO: Remove decryptedMessagePayload in v2 component + const chatPayload = payload ?? decryptedMessagePayload; + + // setup reactions picker visibility + const [showReactionPicker, setShowReactionPicker] = useState(false); + const [userSelectingReaction, setUserSelectingReaction] = useState(false); + + // get user const { user } = useChatData(); - const position = - pCAIP10ToWallet(decryptedMessagePayload.fromDID).toLowerCase() !== - pCAIP10ToWallet(user?.account ?? '')?.toLowerCase() - ? 0 - : 1; + + // get chat position + const chatPosition = + pCAIP10ToWallet(chatPayload.fromDID).toLowerCase() !== pCAIP10ToWallet(user?.account ?? '')?.toLowerCase() ? 0 : 1; + + // derive message + const message = + typeof chatPayload.messageObj === 'object' + ? (chatPayload.messageObj?.content as string) ?? '' + : (chatPayload.messageObj as string); + + // check and render tweets const { tweetId, messageType }: TwitterFeedReturnType = checkTwitterUrl({ - message: decryptedMessagePayload?.messageContent, + message: message, }); + if (messageType === 'TwitterFeedLink') { - decryptedMessagePayload.messageType = 'TwitterFeedLink'; + chatPayload.messageType = 'TwitterFeedLink'; + } + + // test if the payload is encrypted, if so convert it to text + if (isMessageEncrypted(message)) { + chatPayload.messageType = 'Text'; } + // attach a ref to chat sidebar + const chatSidebarRef = useRef(null); + return ( - {/* Message Card */} - {decryptedMessagePayload.messageType === 'Text' && ( - - )} + {/* Chat Card + Reaction Container */} + setShowReactionPicker(true)} + onMouseLeave={() => setShowReactionPicker(false)} + > + + {/* hide overflow for chat cards and border them */} +
+ {/* Message Card */} + {chatPayload.messageType === 'Text' && ( + + )} - {/* Image Card */} - {decryptedMessagePayload.messageType === 'Image' && ( - - )} + {/* Image Card */} + {chatPayload.messageType === 'Image' && } - {/* File Card */} - {decryptedMessagePayload.messageType === 'File' && ( - - )} + {/* File Card */} + {chatPayload.messageType === 'File' && } - {/* Gif Card */} - {decryptedMessagePayload.messageType === 'GIF' && ( - - )} + {/* Gif Card */} + {chatPayload.messageType === 'GIF' && } - {/* Twitter Card */} - {decryptedMessagePayload.messageType === 'TwitterFeedLink' && ( - - )} + {/* Twitter Card */} + {chatPayload.messageType === 'TwitterFeedLink' && ( + + )} - {/* Default Message Card */} - {decryptedMessagePayload.messageType !== 'Text' && - decryptedMessagePayload.messageType !== 'Image' && - decryptedMessagePayload.messageType !== 'File' && - decryptedMessagePayload.messageType !== 'GIF' && - decryptedMessagePayload.messageType !== 'TwitterFeedLink' && ( - - )} + {/* Default Message Card */} + {chatPayload.messageType !== 'Text' && + chatPayload.messageType !== 'Image' && + chatPayload.messageType !== 'File' && + chatPayload.messageType !== 'GIF' && + chatPayload.messageType !== 'TwitterFeedLink' && ( + + )} +
+ + {/* render if reactions are present */} + {chatReactions && !!chatReactions.length && ( +
+ +
+ )} +
+ + + {/* Only render if user and user readmode is false} + {/* For reaction - additional condition - only render if chatId is passed and setSelectedChatMsgId is passed */} + {user && !user.readmode() && chatId && ( + + )} + +
); }; -const MessageSection = styled(Section)` +const MessageSection = styled(Section)``; + +const ChatWrapperSection = styled(Section)``; + +const ChatBubbleSection = styled(Section)` max-width: 70%; @media ${device.tablet} { @@ -271,3 +373,8 @@ const MessageSection = styled(Section)` max-width: 90%; } `; + +const ChatBubbleSidebarSection = styled(Section)` + width: auto; + position: relative; +`; diff --git a/packages/uiweb/src/lib/components/chat/ChatViewBubble/cards/file/FileCard.tsx b/packages/uiweb/src/lib/components/chat/ChatViewBubble/cards/file/FileCard.tsx index 4a452cb80..b64a3f281 100644 --- a/packages/uiweb/src/lib/components/chat/ChatViewBubble/cards/file/FileCard.tsx +++ b/packages/uiweb/src/lib/components/chat/ChatViewBubble/cards/file/FileCard.tsx @@ -29,18 +29,32 @@ import { IMessagePayload } from '../../../exportedTypes'; // Exported Interfaces & Types // Exported Functions -export const FileCard = ({ chat, isGroup }: { chat: IMessagePayload; position: number; isGroup: boolean }) => { - const fileContent: FileMessageContent = JSON.parse(chat?.messageContent); - const name = fileContent.name; - const content = fileContent.content as string; - const size = fileContent.size; +const getParsedMessage = (message: string): FileMessageContent => { + try { + return JSON.parse(message); + } catch (error) { + console.error('UIWeb::components::ChatViewBubble::FileCard::error while parsing image', error); + return { + name: 'Unable to load file', + content: '', + size: 0, + type: '', + }; + } +}; + +export const FileCard = ({ chat }: { chat: IMessagePayload }) => { + // derive message + const message = + typeof chat.messageObj === 'object' ? (chat.messageObj?.content as string) ?? '' : (chat.messageObj as string); + + const parsedMessage = getParsedMessage(message); return (
extension icon - {shortenText(name, 11)} + {shortenText(parsedMessage.name, 11)} - {formatFileSize(size)} + {formatFileSize(parsedMessage.size)}
{ +export const GIFCard = ({ chat }: { chat: IMessagePayload }) => { + // derive message + const message = + typeof chat.messageObj === 'object' ? (chat.messageObj?.content as string) ?? '' : (chat.messageObj as string); + return (
); diff --git a/packages/uiweb/src/lib/components/chat/ChatViewBubble/cards/image/ImageCard.tsx b/packages/uiweb/src/lib/components/chat/ChatViewBubble/cards/image/ImageCard.tsx index 12068eead..ad63c299f 100644 --- a/packages/uiweb/src/lib/components/chat/ChatViewBubble/cards/image/ImageCard.tsx +++ b/packages/uiweb/src/lib/components/chat/ChatViewBubble/cards/image/ImageCard.tsx @@ -17,28 +17,31 @@ import { IMessagePayload } from '../../../exportedTypes'; // Exported Interfaces & Types // Exported Functions +const getParsedMessage = (message: string) => { + try { + return JSON.parse(message); + } catch (error) { + console.error('UIWeb::components::ChatViewBubble::ImageCard::error while parsing image', error); + return null; + } +}; + +const getImageContent = (message: string) => getParsedMessage(message)?.content ?? ''; + +export const ImageCard = ({ chat }: { chat: IMessagePayload }) => { + // derive message + const message = + typeof chat.messageObj === 'object' ? (chat.messageObj?.content as string) ?? '' : (chat.messageObj as string); -export const ImageCard = ({ - chat, - position, - isGroup, -}: { - chat: IMessagePayload; - position: number; - isGroup: boolean; -}) => { return (
); diff --git a/packages/uiweb/src/lib/components/chat/ChatViewBubble/cards/message/FrameRenderer.tsx b/packages/uiweb/src/lib/components/chat/ChatViewBubble/cards/message/FrameRenderer.tsx index a73d28ee3..ae086669f 100644 --- a/packages/uiweb/src/lib/components/chat/ChatViewBubble/cards/message/FrameRenderer.tsx +++ b/packages/uiweb/src/lib/components/chat/ChatViewBubble/cards/message/FrameRenderer.tsx @@ -523,4 +523,5 @@ const FrameInput = styled.input` const PreviewAnchor = styled(Anchor)` text-decoration: none; + align-self: flex-end; `; diff --git a/packages/uiweb/src/lib/components/chat/ChatViewBubble/cards/message/MessageCard.tsx b/packages/uiweb/src/lib/components/chat/ChatViewBubble/cards/message/MessageCard.tsx index 7aef928b0..b740e8a75 100644 --- a/packages/uiweb/src/lib/components/chat/ChatViewBubble/cards/message/MessageCard.tsx +++ b/packages/uiweb/src/lib/components/chat/ChatViewBubble/cards/message/MessageCard.tsx @@ -33,12 +33,10 @@ interface IMsgFragments { export const MessageCard = ({ chat, position, - isGroup, account, }: { chat: IMessagePayload; position: number; - isGroup: boolean; account: string; }) => { // get theme @@ -195,12 +193,9 @@ export const MessageCard = ({ {/* Preview Renderer - Start with assuming preview is there, callback handles no preview */}
{fragments.map((fragment, fragmentIndex) => { @@ -246,7 +239,6 @@ export const MessageCard = ({ {/* Timestamp rendering */} {time} @@ -287,7 +280,7 @@ const MessageCardSection = styled(Section)` &.video, &.frame { max-width: 512px; - min-width: 240px; + min-width: 200px; & > ${MessageSection} { width: 100%; diff --git a/packages/uiweb/src/lib/components/chat/ChatViewBubble/cards/twitter/TwitterCard.tsx b/packages/uiweb/src/lib/components/chat/ChatViewBubble/cards/twitter/TwitterCard.tsx index 38702d238..a7cb9a85c 100644 --- a/packages/uiweb/src/lib/components/chat/ChatViewBubble/cards/twitter/TwitterCard.tsx +++ b/packages/uiweb/src/lib/components/chat/ChatViewBubble/cards/twitter/TwitterCard.tsx @@ -19,20 +19,9 @@ import { IMessagePayload } from '../../../exportedTypes'; // Exported Functions -export const TwitterCard = ({ - chat, - tweetId, - isGroup, - position, -}: { - chat: IMessagePayload; - tweetId: string; - isGroup: boolean; - position: number; -}) => { +export const TwitterCard = ({ chat, tweetId }: { chat: IMessagePayload; tweetId: string }) => { return (
void; + actionId?: string | null | undefined; + singularActionId?: string | null | undefined; + setSingularActionId?: (singularActionId: string | null | undefined) => void; + chatSidebarRef: RefObject; +}) => { + // get theme + const theme = useContext(ThemeContext); + + // to adjust reaction selection position + const reactionSelectionRef: RefObject = useRef(null); + + // adjust position of reaction selection + const adjustPosition = () => { + // TODO: Resize should adjust reaction picker + // const element = reactionSelectionRef.current; + // if (element) { + // const rect = element.getBoundingClientRect(); + // const elementWidth = rect.width; + // const overflowRight = rect.left + elementWidth + 50 - window.innerWidth; + // if (overflowRight > 0) { + // element.style.left = `${overflowRight - rect.left}px`; // Adjust left so the element is 30px inside the window + // } + // } + }; + + useEffect(() => { + adjustPosition(); + window.addEventListener('resize', adjustPosition); + return () => window.removeEventListener('resize', adjustPosition); + }, []); + + // get user + const { user } = useChatData(); + + // when sending reaction + const [sendingReaction, setSendingReaction] = useState(null); + + // prepare to send reaction + const processSendReaction = (reaction: string) => { + // reset user selecting reaction + setUserSelectingReaction(!userSelectingReaction); + + // set sending reaction + setSendingReaction(reaction); + }; + + useEffect(() => { + // to send reaction + const sendReaction = async (reaction: string) => { + // try to send reaction + user?.chat + .send(chatId, { + type: 'Reaction', + content: reaction, + reference: (chat as any).cid, + }) + .then((response) => { + console.debug( + 'UIWeb::components::ChatViewBubble::ReactionPicker::sendReaction success with response:', + response + ); + }) + .catch((error) => { + console.error('UIWeb::components::ChatViewBubble::ReactionPicker::sendReaction error:', error); + }) + .finally(() => { + setSendingReaction(''); + }); + }; + + if (sendingReaction) { + sendReaction(sendingReaction); + } + }, [sendingReaction]); + + return ( + <> + {/* To display emoji picker */} + + + {/* To pick emoji from emoji picker render and if actionId matches singularActionId */} + {userSelectingReaction && actionId === singularActionId && ( +
+ {/* To display processing if sending reaction is not null */} + {sendingReaction && ( +
+ +
+ )} + + {/* To display reaction only when sending reaction is null */} + {!sendingReaction && ( + <> + + + + + + + + + + + + + )} +
+ )} + + ); +}; diff --git a/packages/uiweb/src/lib/components/chat/ChatViewBubble/reactions/Reactions.tsx b/packages/uiweb/src/lib/components/chat/ChatViewBubble/reactions/Reactions.tsx new file mode 100644 index 000000000..36b883948 --- /dev/null +++ b/packages/uiweb/src/lib/components/chat/ChatViewBubble/reactions/Reactions.tsx @@ -0,0 +1,103 @@ +// React + Web3 Essentials +import { useContext, useRef, useState, useEffect, RefObject } from 'react'; + +// External Packages + +// Internal Compoonents +import { Image, Section, Button, Spinner, Span } from '../../../reusables'; +import { ThemeContext } from '../../theme/ThemeProvider'; + +// Internal Configs + +// Assets + +// Interfaces & Types +import { IReactionsForChatMessages } from '../../../../types'; + +interface IReactions { + [key: string]: string[]; +} + +// Constants + +// Exported Interfaces & Types + +// Exported Functions +export const Reactions = ({ chatReactions }: { chatReactions: IReactionsForChatMessages[] }) => { + // get theme + const theme = useContext(ThemeContext); + + // transform to IReactions + const uniqueReactions = chatReactions.reduce((acc, reaction) => { + const contentKey = (reaction as any).messageObj?.content || ''; + if (!acc[contentKey]) { + acc[contentKey] = []; + } + + // eliminate duplicate + if (!acc[contentKey].includes((reaction as any).fromCAIP10)) { + acc[contentKey].push((reaction as any).fromCAIP10); + } + + return acc; + }, {} as IReactions); + + console.debug('UIWeb::components::ChatViewBubble::Reactions::uniqueReactions', uniqueReactions); + + // render reactions + return ( + // eslint-disable-next-line react/jsx-no-useless-fragment + <> + {Object.keys(uniqueReactions).length > 2 ? ( +
+ + {Object.keys(uniqueReactions).join(' ')} + + + {Object.values(uniqueReactions).reduce((total, reactions) => total + reactions.length, 0)} + +
+ ) : ( + Object.entries(uniqueReactions).map(([content, reactions]) => ( +
+ + {content} + + + {reactions.length} + +
+ )) + )} + + ); +}; diff --git a/packages/uiweb/src/lib/components/chat/ChatViewList/ChatViewList.tsx b/packages/uiweb/src/lib/components/chat/ChatViewList/ChatViewList.tsx index 985d357e3..f51098d0b 100644 --- a/packages/uiweb/src/lib/components/chat/ChatViewList/ChatViewList.tsx +++ b/packages/uiweb/src/lib/components/chat/ChatViewList/ChatViewList.tsx @@ -25,6 +25,7 @@ import { ThemeContext } from '../theme/ThemeProvider'; // Assets // Interfaces & Types +import { IReactionsForChatMessages } from '../../../types'; import { Group, IChatViewListProps } from '../exportedTypes'; import { IChatTheme } from '../theme'; import { IChatInfoResponse } from '../types'; @@ -51,6 +52,8 @@ const CHAT_STATUS = { INVALID_CHAT: 'Invalid chatId', }; +const SCROLL_LIMIT = 25; + // Exported Interfaces & Types // Exported Functions @@ -68,11 +71,16 @@ export const ChatViewList: React.FC = (options: IChatViewLis // const [chatStatusText, setChatStatusText] = useState(''); const [messages, setMessages] = useState([]); + const [reactions, setReactions] = useState({}); + const { historyMessages, historyLoading: messageLoading } = useFetchMessageUtilities(); - const listInnerRef = useRef(null); + const scrollRef = useRef(null); const [stopPagination, setStopPagination] = useState(false); const { fetchChat } = useFetchChat(); + // keep tab on singular action id, useful to ensure only one action takes place + const [singularActionId, setSingularActionId] = useState(null); + // for stream const { chatStream, @@ -87,7 +95,7 @@ export const ChatViewList: React.FC = (options: IChatViewLis const dates = new Set(); // Primary Hook that fetches and sets ChatInfo which then fetches and sets UserInfo - // Which then calls await getMessagesCall(); to fetch messages + // Which then calls await fetchChatMessages(); to fetch messages useEffect(() => { (async () => { if (!user) return; @@ -133,15 +141,143 @@ export const ChatViewList: React.FC = (options: IChatViewLis }; }, [chatId, user]); - // When loading is done + // When loading is done - fetch chat messages useEffect(() => { if (initialized.loading) return; (async function () { - await getMessagesCall(); + await fetchChatMessages(); })(); }, [initialized.loading]); + // when chat messages are changed or chat reactions are changed + useEffect(() => { + const checkForScrollAndFetchMessages = async () => { + if ( + !initialized.loading && + scrollRef && + scrollRef?.current && + scrollRef?.current?.parentElement && + !messageLoading && + !stopPagination + ) { + console.debug( + 'UIWeb::ChatViewList::useEffect[messages, reactions]::Checking if we need to load more chats::', + messages, + reactions, + scrollRef.current.clientHeight, + SCROLL_LIMIT, + scrollRef.current.parentElement.clientHeight, + scrollRef.current.clientHeight + SCROLL_LIMIT < scrollRef.current.parentElement.clientHeight + ); + + if (scrollRef.current.clientHeight + SCROLL_LIMIT < scrollRef.current.parentElement.clientHeight) { + await fetchChatMessages(); + } + } + }; + + // new messages are loaded, calculate new top and adjust since render is done + if (scrollRef.current) { + const content = scrollRef.current; + + const oldScrollHeight = parseInt(content.getAttribute('data-old-scroll-height') || '0', 10); // Old scroll height before messages are added + const newScrollHeight = content.scrollHeight; // New scroll height after messages are added + const scrollHeightDifference = newScrollHeight - oldScrollHeight; // Calculate how much the scroll height has increased plus some variance for spinner + + // Adjust the scroll position by the difference in scroll height to maintain the same view + content.scrollTop += scrollHeightDifference; + } + + // check and fetch messages + checkForScrollAndFetchMessages(); + }, [messages]); + + // Smart Scrolling + // Scroll to bottom if user hasn't scrolled or if scroll is at bottom + // Else leave the scroll as it is + // to get scroll lock + const onScroll = async () => { + if (scrollRef.current) { + const { scrollTop, scrollHeight, clientHeight } = scrollRef.current; + + let scrollLocked = scrollRef.current.getAttribute('data-scroll-locked') === 'true' ? true : false; + const programmableScroll = scrollRef.current.getAttribute('data-programmable-scroll') === 'true' ? true : false; + const programmableScrollTop = scrollRef.current.getAttribute('data-programmable-scroll-top') || 0; + + // user has scrolled away so scroll should not be locked + if (programmableScroll === false) { + scrollLocked = false; + } + + // lock scroll if user is at bottom + if (scrollTop + clientHeight >= scrollHeight - 10) { + // add 10 for variability + scrollLocked = true; + } + + console.debug( + `UIWeb::ChatViewList::onScroll::scrollLocked ${new Date().toISOString()}`, + scrollRef.current.scrollTop, + scrollRef.current.clientHeight, + scrollRef.current.scrollHeight, + scrollLocked + ); + + // update scroll-locked attribute + scrollRef.current.setAttribute('data-scroll-locked', scrollLocked.toString()); + + if (scrollTop === 0) { + const content = scrollRef.current; + const oldScrollHeight = content.scrollHeight; // Capture the old scroll height before new messages are added + scrollRef.current.setAttribute('data-old-scroll-height', oldScrollHeight.toString()); + + await fetchChatMessages(); + } + } + }; + + // To enable smart scrolling when content height gets adjsuted + const chatContainerRef = useRef(null); + useEffect(() => { + const resizeObserver = new ResizeObserver((entries) => { + for (const entry of entries) { + const { height } = entry.contentRect; + + if (scrollRef.current && height !== 0) { + const scrollLocked = scrollRef.current.getAttribute('data-scroll-locked') === 'true' ? true : false; + + console.debug( + `UIWeb::ChatViewList::onScroll::scrollLocked Observer ${new Date().toISOString()}`, + scrollRef.current.scrollTop, + scrollRef.current.clientHeight, + scrollRef.current.scrollHeight, + scrollLocked + ); + + if (height !== 0 && scrollLocked) { + // update programmable-scroll attribute + scrollRef.current.setAttribute('data-programmable-scroll', 'true'); + scrollRef.current?.scrollTo(0, scrollRef.current?.scrollHeight); + + // update programmable-scroll attribute after timeout of 1000ms for previews to render + setTimeout(() => { + if (scrollRef.current) { + scrollRef.current.setAttribute('data-programmable-scroll', 'false'); + } + }, 1000); + } + } + } + }); + + if (chatContainerRef.current) { + resizeObserver.observe(chatContainerRef.current); + } + + return () => resizeObserver.disconnect(); // clean up + }, [chatContainerRef.current]); + // Change listtype to 'CHATS' and hidden to false when chatAcceptStream is received useEffect(() => { if (Object.keys(chatAcceptStream || {}).length > 0 && chatAcceptStream.constructor === Object) { @@ -174,16 +310,12 @@ export const ChatViewList: React.FC = (options: IChatViewLis useEffect(() => { if (Object.keys(chatStream || {}).length > 0 && chatStream.constructor === Object) { transformSteamMessage(chatStream); - // setChatStatusText(''); - scrollToBottom(); } }, [chatStream]); useEffect(() => { if (Object.keys(chatRequestStream || {}).length > 0 && chatRequestStream.constructor === Object) { transformSteamMessage(chatRequestStream); - // setChatStatusText(''); - scrollToBottom(); } }, [chatRequestStream]); @@ -196,97 +328,112 @@ export const ChatViewList: React.FC = (options: IChatViewLis const transformedMessage = transformStreamToIMessageIPFSWithCID(item); if (messages && messages.length) { const newChatViewList = appendUniqueMessages(messages, [transformedMessage], false); - setFilteredMessages(newChatViewList); + filterChatMessages(newChatViewList); } else { - setFilteredMessages([transformedMessage]); - } - } - }; - - useEffect(() => { - if (messages && messages?.length && messages?.length <= limit) { - // setChatStatusText(''); - scrollToBottom(); - } - }, [messages]); - - //methods - const scrollToBottom = () => { - requestAnimationFrame(() => { - if (listInnerRef.current) { - listInnerRef.current.scrollTop = listInnerRef.current.scrollHeight; - } - }); - }; - - 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 = newScroll - oldScroll; + filterChatMessages([transformedMessage]); } } }; - const getMessagesCall = async () => { - let reference = null; - let stopFetchingChats = false; - if (messages && messages?.length) { - reference = messages[0].link; - if (!reference) { - stopFetchingChats = true; - setStopPagination(stopFetchingChats); - } - } - - if (user && !stopFetchingChats) { + const fetchChatMessages = async () => { + if (user && !stopPagination && !messageLoading) { + const reference = messages && messages?.length ? messages[0].link : null; const chatHistory = await historyMessages({ limit: limit, chatId: chatId, reference, }); - if (chatHistory?.length) { + if (chatHistory && chatHistory?.length) { const reversedChatHistory = chatHistory?.reverse(); if (messages && messages?.length) { const newChatViewList = appendUniqueMessages(messages, reversedChatHistory, true); - setFilteredMessages(newChatViewList as IMessageIPFSWithCID[]); + filterChatMessages(newChatViewList as IMessageIPFSWithCID[]); } else { - setFilteredMessages(reversedChatHistory as IMessageIPFSWithCID[]); + filterChatMessages(reversedChatHistory as IMessageIPFSWithCID[]); + } + } + + // check and stop pagination if user is readmode and chatInfo visibility is false + if ( + (user && user.readmode() && initialized.chatInfo?.meta?.visibility === false) || + initialized.chatInfo?.meta?.group === false + ) { + // not a public group + setStopPagination(true); + } + + // check and stop pagination if all chats are fetched + if (!chatHistory || chatHistory?.length < limit) { + setStopPagination(true); + } + } + }; + + const processChatReactions = (messageList: Array) => { + const reactionMessages = reactions; + + for (const message of messageList) { + if (message.messageType === 'Reaction') { + const reaction = message as IMessageIPFSWithCID; + + // TODO: This should be present as an interface in the restapi package + const reference = (reaction as any).messageObj?.reference ?? ''; + + if (!reactionMessages[reference]) { + reactionMessages[reference] = []; } + // Push the reaction directly into the array + reactionMessages[reference].push(reaction); } } + + return reactionMessages; }; - const setFilteredMessages = (messageList: Array) => { - const updatedMessageList = messageList.filter((msg) => !chatFilterList.includes(msg.cid)); + const filterChatMessages = (messageList: Array) => { + // filter duplicates + const uniqueMessagesList = messageList.filter((msg) => !chatFilterList.includes(msg.cid)); - if (updatedMessageList && updatedMessageList.length) { - setMessages([...updatedMessageList]); + // remove reactions into reactions + const reactionMessages = processChatReactions(uniqueMessagesList); + + console.debug( + `UIWeb::ChatViewList::filterChatMessages::uniqueMessageList::${new Date().toISOString()}`, + uniqueMessagesList + ); + console.debug( + `UIWeb::ChatViewList::filterChatMessages::reactionMessages::${new Date().toISOString()}`, + reactionMessages + ); + + if (uniqueMessagesList && uniqueMessagesList.length) { + setMessages([...uniqueMessagesList]); + } + + if (reactionMessages && reactionMessages.length) { + // deep copy to update + setReactions(JSON.parse(JSON.stringify(reactionMessages))); } }; type RenderDataType = { chat: IMessageIPFS; dateNum: string; + uid: string; }; - const renderDate = ({ chat, dateNum }: RenderDataType) => { + const renderDate = ({ chat, dateNum, uid }: RenderDataType) => { const timestampDate = dateToFromNowDaily(chat.timestamp as number); dates.add(dateNum); return ( {timestampDate} @@ -295,12 +442,15 @@ export const ChatViewList: React.FC = (options: IChatViewLis return ( = (options: IChatViewLis e.stopPropagation(); if (!stopPagination) onScroll(); }} + onClick={() => { + // cancel any singular action + setSingularActionId(null); + }} >
= (options: IChatViewLis flexDirection="column" justifyContent="start" width="100%" + ref={chatContainerRef} blur={initialized.isHidden} > {messages && messages?.map((chat: IMessageIPFS, index: number) => { + // If message is a reaction, then skip it + if (chat?.messageType === 'Reaction') return null; + const dateNum = moment(chat.timestamp).format('L'); // TODO: This is a hack as chat.fromDID is converted with eip to match with user.account creating a bug for omnichain const position = pCAIP10ToWallet(chat.fromDID)?.toLowerCase() !== pCAIP10ToWallet(user?.account ?? '')?.toLowerCase() ? 0 : 1; + + // define zIndex, really big number minus 1 + const uid = `${999999999 - index}`; + return ( <> - {dates.has(dateNum) ? null : renderDate({ chat, dateNum })} + {dates.has(dateNum) ? null : renderDate({ chat, dateNum, uid: uid })}
+ {/* TODO: Remove decryptedMessagePayload in v2 component */}
@@ -412,7 +582,6 @@ const ChatViewListCard = styled(Section)` } overscroll-behavior: contain; - scroll-behavior: smooth; `; const ChatViewListCardInner = styled(Section)` diff --git a/packages/uiweb/src/lib/components/chat/ConnectButton/ConnectButton.tsx b/packages/uiweb/src/lib/components/chat/ConnectButton/ConnectButton.tsx index 6922db0de..e854bf5e1 100644 --- a/packages/uiweb/src/lib/components/chat/ConnectButton/ConnectButton.tsx +++ b/packages/uiweb/src/lib/components/chat/ConnectButton/ConnectButton.tsx @@ -19,7 +19,7 @@ interface IThemeProps { theme?: IChatTheme; } -export const ConnectButtonSub = ({ autoConnect = false }) => { +export const ConnectButton = ({ autoConnect = false }) => { const { user, preInitializeUser } = useChatData(); const { wallet, connecting, connect, disconnect } = useAccount({ env: user ? user.env : CONSTANTS.ENV.PROD }); diff --git a/packages/uiweb/src/lib/components/chat/ConnectButton/index.tsx b/packages/uiweb/src/lib/components/chat/ConnectButton/index.tsx index a339765e3..d5457ba77 100644 --- a/packages/uiweb/src/lib/components/chat/ConnectButton/index.tsx +++ b/packages/uiweb/src/lib/components/chat/ConnectButton/index.tsx @@ -1,100 +1 @@ -import { IChatTheme } from '../theme'; - -import coinbaseWalletModule from '@web3-onboard/coinbase'; -import { ConnectButtonSub } from './ConnectButton'; -import { BLOCKNATIVE_PROJECT_ID, InfuraAPIKey } from '../../../config'; -import { Web3OnboardProvider } from '@web3-onboard/react'; -import injectedModule, { ProviderLabel } from '@web3-onboard/injected-wallets'; -import walletConnectModule from '@web3-onboard/walletconnect'; -import init from '@web3-onboard/core'; -import PushIcon from '../../../icons/Bell.svg'; - -const APP_META_DATA = { - name: 'Push Protocol', - logo: PushIcon, - icon: PushIcon, - description: 'Example showcasing how to connect a wallet.', - - recommendedInjectedWallets: [ - { name: 'MetaMask', url: 'https://metamask.io' }, - ], -}; - -const wcv2InitOptions = { - projectId: BLOCKNATIVE_PROJECT_ID, - requiredChains: [1, 56], -}; - -const walletConnect = walletConnectModule(wcv2InitOptions); -const coinbaseWalletSdk = coinbaseWalletModule({ darkMode: true }); -const CHAINS = [ - { - id: '0x1', - token: 'ETH', - label: 'Ethereum Mainnet', - rpcUrl: `https://mainnet.infura.io/v3/${InfuraAPIKey}`, - }, - { - id: '0xAA36A7', - token: 'ETH', - label: 'Sepolia', - rpcUrl: `https://sepolia.infura.io/v3/${InfuraAPIKey}`, - }, - { - id: '0x13882', - token: 'MATIC', - label: 'Polygon - Amoy', - rpcUrl: 'https://rpc-amoy.polygon.technology', - }, - { - id: '0x38', - token: 'BNB', - label: 'Binance', - rpcUrl: 'https://bsc-dataseed.binance.org/', - }, - { - id: '0xA', - token: 'OETH', - label: 'Optimism', - rpcUrl: 'https://mainnet.optimism.io', - }, - { - id: '0xA4B1', - token: 'ARB-ETH', - label: 'Arbitrum', - rpcUrl: 'https://rpc.ankr.com/arbitrum', - }, -]; - -const wallets = [injectedModule(), walletConnect, coinbaseWalletSdk]; - -const web3OnBoard = init({ - wallets, - chains: CHAINS, - appMetadata: APP_META_DATA, - accountCenter: { - desktop: { - enabled: false, - }, - mobile: { - enabled: false, - }, - }, - connect: { - autoConnectLastWallet: true, - }, -}); - -interface IConnectButtonCompProps { - autoConnect?: boolean; -} - -export const ConnectButtonComp: React.FC = ({ - autoConnect, -}) => { - return ( - - - - ); -}; +export { ConnectButton } from './ConnectButton'; diff --git a/packages/uiweb/src/lib/components/chat/CreateGroup/CreateGroupType.tsx b/packages/uiweb/src/lib/components/chat/CreateGroup/CreateGroupType.tsx index 9a6f8691f..360243f0f 100644 --- a/packages/uiweb/src/lib/components/chat/CreateGroup/CreateGroupType.tsx +++ b/packages/uiweb/src/lib/components/chat/CreateGroup/CreateGroupType.tsx @@ -10,10 +10,7 @@ import { Button } from '../reusables'; import { ModalHeaderProps } from './CreateGroupModal'; import { GroupTypeState } from './CreateGroupModal'; import { ThemeContext } from '../theme/ThemeProvider'; -import { - ConditionType, - CriteriaStateType, -} from '../types/tokenGatedGroupCreationType'; +import { ConditionType, CriteriaStateType } from '../types/tokenGatedGroupCreationType'; import ConditionsComponent from './ConditionsComponent'; import { OperatorContainer } from './OperatorContainer'; import { SelectedCriteria } from '../../../hooks/chat/useCriteriaState'; @@ -41,12 +38,7 @@ interface AddConditionProps { criteriaState: CriteriaStateType; } -const AddConditionSection = ({ - heading, - subHeading, - handleNext, - criteriaState, -}: AddConditionProps) => { +const AddConditionSection = ({ heading, subHeading, handleNext, criteriaState }: AddConditionProps) => { const theme = useContext(ThemeContext); const generateMapping = () => { @@ -57,8 +49,17 @@ const AddConditionSection = ({ }; return ( -
-
+
+
{criteriaState.entryOptionsDataArray.length > 1 && ( -
+
{ @@ -87,10 +88,7 @@ const AddConditionSection = ({ )} { criteriaState.deleteEntryOptionsDataArray(idx); }} @@ -135,11 +133,13 @@ export const CreateGroupType = ({ setGroupInputDetails, groupInputDetails, }: ModalHeaderProps & GroupTypeState) => { - const theme = useContext(ThemeContext); return ( -
+
-
+
({ ...prevState, groupEncryptionType: newEl, - })) + })); } console.debug(newEl); }} @@ -169,20 +173,21 @@ export const CreateGroupType = ({ setChecked ? setChecked(!checked) : null} + onToggle={() => (setChecked ? setChecked(!checked) : null)} /> {checked && ( -
+
{ if (handleNext) { - criteriaStateManager.setSelectedCriteria( - SelectedCriteria.ENTRY - ); + criteriaStateManager.setSelectedCriteria(SelectedCriteria.ENTRY); handleNext(); } }} @@ -191,9 +196,7 @@ export const CreateGroupType = ({ { if (handleNext) { - criteriaStateManager.setSelectedCriteria( - SelectedCriteria.CHAT - ); + criteriaStateManager.setSelectedCriteria(SelectedCriteria.CHAT); handleNext(); } }} @@ -204,18 +207,27 @@ export const CreateGroupType = ({ )}
-
- - +
); }; //styles -const ScrollSection = styled(Section) <{ theme: IChatTheme }>` +const ScrollSection = styled(Section)<{ theme: IChatTheme }>` &::-webkit-scrollbar-thumb { background: ${(props) => props.theme.scrollbarColor}; border-radius: 10px; @@ -227,4 +239,4 @@ const ScrollSection = styled(Section) <{ theme: IChatTheme }>` &::-webkit-scrollbar { width: 4px; } -`; \ No newline at end of file +`; diff --git a/packages/uiweb/src/lib/components/chat/CreateGroup/DefineCondition.tsx b/packages/uiweb/src/lib/components/chat/CreateGroup/DefineCondition.tsx index 422b89913..010b0f467 100644 --- a/packages/uiweb/src/lib/components/chat/CreateGroup/DefineCondition.tsx +++ b/packages/uiweb/src/lib/components/chat/CreateGroup/DefineCondition.tsx @@ -16,12 +16,7 @@ import { IChatTheme } from '../theme'; import { device } from '../../../config'; import { OPERATOR_OPTIONS_INFO } from '../constants'; -export const DefineCondtion = ({ - onClose, - handlePrevious, - handleNext, - criteriaStateManager, -}: ModalHeaderProps) => { +export const DefineCondtion = ({ onClose, handlePrevious, handleNext, criteriaStateManager }: ModalHeaderProps) => { const theme = useContext(ThemeContext); const isMobile = useMediaQuery(device.mobileL); @@ -32,10 +27,7 @@ export const DefineCondtion = ({ criteriaState.selectedRules.length < 1 ? theme.backgroundColor?.buttonDisableBackground : theme.backgroundColor?.buttonBackground, - color: - criteriaState.selectedRules.length < 1 - ? theme.textColor?.buttonDisableText - : theme.textColor?.buttonText, + color: criteriaState.selectedRules.length < 1 ? theme.textColor?.buttonDisableText : theme.textColor?.buttonText, }; const verifyAndDoNext = () => { @@ -43,26 +35,19 @@ export const DefineCondtion = ({ }; const getRules = () => { - return [ - [{ operator: criteriaState.entryRuleTypeCondition }], - ...criteriaState.selectedRules.map((el) => [el]), - ]; + return [[{ operator: criteriaState.entryRuleTypeCondition }], ...criteriaState.selectedRules.map((el) => [el])]; }; // set state for edit condition useEffect(() => { if (criteriaState.isCondtionUpdateEnabled()) { criteriaState.setEntryRuleTypeCondition( - criteriaState.entryOptionTypeArray[ - criteriaState.entryOptionsDataArrayUpdate - ] + criteriaState.entryOptionTypeArray[criteriaState.entryOptionsDataArrayUpdate] ); if (criteriaState.selectedRules.length === 0) { criteriaState.setSelectedRule([ - ...criteriaState.entryOptionsDataArray[ - criteriaState.entryOptionsDataArrayUpdate - ], + ...criteriaState.entryOptionsDataArray[criteriaState.entryOptionsDataArrayUpdate], ]); } } else { @@ -77,29 +62,23 @@ export const DefineCondtion = ({ width={isMobile ? '300px' : '400px'} > -
- - {criteriaState.selectedRules.length > 1 && ( -
- { - criteriaState.setEntryRuleTypeCondition( - newEl as keyof typeof OPERATOR_OPTIONS_INFO - ); - }} - /> -
- )} - {criteriaState.selectedRules.length > 0 && + {criteriaState.selectedRules.length > 1 && ( +
+ { + criteriaState.setEntryRuleTypeCondition(newEl as keyof typeof OPERATOR_OPTIONS_INFO); + }} + /> +
+ )} + {criteriaState.selectedRules.length > 0 && ( + - } - - - -
- {!criteriaState.selectedRules.length && + )} + + +
+ {!criteriaState.selectedRules.length && ( + You must add at least 1 criteria to enable gating - } - - + */} - +
); }; @@ -157,4 +145,4 @@ const ConditionSection = styled(Section)<{ theme: IChatTheme }>` &::-webkit-scrollbar { width: 4px; } -`; \ No newline at end of file +`; diff --git a/packages/uiweb/src/lib/components/chat/MessageInput/MessageInput.tsx b/packages/uiweb/src/lib/components/chat/MessageInput/MessageInput.tsx index 9aaa33add..2c6a75eb6 100644 --- a/packages/uiweb/src/lib/components/chat/MessageInput/MessageInput.tsx +++ b/packages/uiweb/src/lib/components/chat/MessageInput/MessageInput.tsx @@ -14,13 +14,13 @@ import useGroupMemberUtilities from '../../../hooks/chat/useGroupMemberUtilities import usePushSendMessage from '../../../hooks/chat/usePushSendMessage'; import useVerifyAccessControl from '../../../hooks/chat/useVerifyAccessControl'; import { AttachmentIcon } from '../../../icons/Attachment'; -import { EmojiIcon } from '../../../icons/Emoji'; +import { EmojiCircleIcon } from '../../../icons/PushIcons'; import { GifIcon } from '../../../icons/Gif'; import OpenLink from '../../../icons/OpenLink'; import { SendCompIcon } from '../../../icons/SendCompIcon'; import { Div, Section, Span, Spinner } from '../../reusables'; import { ConditionsInformation } from '../ChatProfile/ChatProfileInfoModal'; -import { ConnectButtonComp } from '../ConnectButton'; +import { ConnectButton } from '../ConnectButton'; import { Modal, ModalHeader } from '../reusables/Modal'; import { ThemeContext } from '../theme/ThemeProvider'; @@ -60,7 +60,7 @@ const ConnectButtonSection = ({ autoConnect }: { autoConnect: boolean }) => { You need to connect your wallet to get started )} - +
); }; @@ -436,7 +436,6 @@ export const MessageInput: React.FC = ({ > = ({ ) : null} {user && !user?.readmode() && (((isRules ? verified : true) && isMember) || (chatInfo && !groupInfo)) && ( - <> -
- {emoji && ( -
setShowEmojis(!showEmojis)} - > - -
- )} - {showEmojis && ( -
- + {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 && ( + <> +
+ +
+ uploadFile(e)} /> -
+ )} - { - 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 && ( - <> -
- -
- uploadFile(e)} - /> - - )} + {!(loading || fileUploading) && ( +
sendTextMsg()} + > +
- {!(loading || fileUploading) && ( -
sendTextMsg()} - > - -
- )} + )} - {(loading || fileUploading) && ( -
- -
- )} - - + {(loading || fileUploading) && ( +
+ +
+ )} + )} @@ -696,9 +691,9 @@ const MessageInputContainer = styled(Section)` `; const SendSection = styled(Section)` - gap: 11.5px; + gap: 12px; @media ${device.mobileL} { - gap: 7.5px; + gap: 8px; } `; const MultiLineInput = styled.textarea` @@ -717,7 +712,8 @@ const MultiLineInput = styled.textarea` padding-right: 5px; align-self: end; @media ${device.mobileL} { - font-size: 14px; + font-size: 16px; + width: 100%; } &&::-webkit-scrollbar { width: 4px; diff --git a/packages/uiweb/src/lib/components/chat/constants/index.ts b/packages/uiweb/src/lib/components/chat/constants/index.ts index 9cd5984da..e271a0bb8 100644 --- a/packages/uiweb/src/lib/components/chat/constants/index.ts +++ b/packages/uiweb/src/lib/components/chat/constants/index.ts @@ -1,57 +1,55 @@ -import { OptionDescription } from "../reusables"; +import { OptionDescription } from '../reusables'; export const INVITE_CHECKBOX_LABEL: { owner: string; admin: string } = { - owner: 'Only Owner can invite', - admin: 'Only Admin can invite', - }; - - export const GUILD_COMPARISON_OPTIONS: Array = [ - { - heading: 'ALL', - value: 'all', - }, - { - heading: 'ANY', - value: 'any', - }, - { - heading: 'SPECIFIC', - value: 'specific', - }, - ]; + owner: 'Only Owner can invite', + admin: 'Only Admin can invite', +}; - export const OPERATOR_OPTIONS = [ - { - heading: 'Any', - value: 'any', - }, - { - heading: 'All', - value: 'all', - } -] +export const GUILD_COMPARISON_OPTIONS: Array = [ + { + heading: 'ALL', + value: 'all', + }, + { + heading: 'ANY', + value: 'any', + }, + { + heading: 'SPECIFIC', + value: 'specific', + }, +]; +export const OPERATOR_OPTIONS = [ + { + heading: 'Any', + value: 'any', + }, + { + heading: 'All', + value: 'all', + }, +]; export const OPERATOR_OPTIONS_INFO = { - any:{ - head:'Any one', - tail:'of the following criteria must be true' - }, - all:{ - head:'All', - tail:'of the following criteria must be true' - } -} ; - + any: { + head: 'Any one', + tail: 'of the following criteria must be true', + }, + all: { + head: 'All', + tail: 'of the following criteria must be true', + }, +}; export const ACCESS_TYPE_TITLE = { ENTRY: { heading: 'Conditions to Join', - subHeading: 'Add a condition to join or leave it open for everyone', + subHeading: 'Add a condition to join or remove all conditions for no rules', }, CHAT: { heading: 'Conditions to Chat', - subHeading: 'Add a condition to join or leave it open for everyone', + subHeading: 'Add a condition to chat or leave it empty for no rules', }, }; diff --git a/packages/uiweb/src/lib/components/chat/helpers/helper.ts b/packages/uiweb/src/lib/components/chat/helpers/helper.ts index b31f978d3..f5e7460ce 100644 --- a/packages/uiweb/src/lib/components/chat/helpers/helper.ts +++ b/packages/uiweb/src/lib/components/chat/helpers/helper.ts @@ -215,7 +215,7 @@ export const transformStreamToIMessageIPFSWithCID: (item: any) => IMessageIPFSWi fromDID: item?.from, toDID: item?.to[0], messageType: item?.message?.type, - messageObj: { content: item?.message?.content }, + messageObj: { content: item?.message?.content, reference: item?.message?.reference }, sigType: item?.raw?.sigType || '', link: `previous:v2${item?.reference}`, timestamp: parseInt(item?.timestamp), diff --git a/packages/uiweb/src/lib/components/chat/reusables/ProfileContainer.tsx b/packages/uiweb/src/lib/components/chat/reusables/ProfileContainer.tsx index 34ebf5db5..795bc74fa 100644 --- a/packages/uiweb/src/lib/components/chat/reusables/ProfileContainer.tsx +++ b/packages/uiweb/src/lib/components/chat/reusables/ProfileContainer.tsx @@ -2,11 +2,13 @@ import { useEffect, useRef, useState } from 'react'; // External Packages +import styled from 'styled-components'; // Internal Compoonents import { copyToClipboard, pCAIP10ToWallet } from '../../../helpers'; import { createBlockie } from '../../../helpers/blockies'; import { Div, Image, Section, Span, Tooltip } from '../../reusables'; +import { device } from '../../../config'; // Internal Configs @@ -65,10 +67,11 @@ export const ProfileContainer = ({ theme, member, copy, customStyle, loading }:
{member?.icon && ( @@ -105,7 +108,7 @@ export const ProfileContainer = ({ theme, member, copy, customStyle, loading }: fontWeight={customStyle?.fontWeight ?? '400'} color={customStyle?.textColor ?? theme.textColor?.modalSubHeadingText} position="relative" - cursor="pointer" + textAlign="left" > {member.name && member.web3Name ? member.name : member.name || member.web3Name} @@ -117,7 +120,7 @@ export const ProfileContainer = ({ theme, member, copy, customStyle, loading }: gap="5px" cursor="pointer" minHeight="22px" - minWidth="180px" + minWidth="140px" onMouseEnter={() => { const text = member.chatId === member.recipient ? 'Copy Chat ID' : 'Copy Wallet'; setCopyText(text); @@ -129,7 +132,7 @@ export const ProfileContainer = ({ theme, member, copy, customStyle, loading }: }} className={loading ? 'skeleton' : ''} > - {member?.name && member?.web3Name ? `${member?.web3Name} | ${member.abbrRecipient}` : member.abbrRecipient} - + {copy && copyText && (
); }; + +const RecipientSpan = styled(Span)` + text-wrap: nowrap; + + @media ${device.mobileL} { + text-wrap: pretty; + } +`; diff --git a/packages/uiweb/src/lib/components/chat/theme/index.ts b/packages/uiweb/src/lib/components/chat/theme/index.ts index e2c114375..a45dbaef3 100644 --- a/packages/uiweb/src/lib/components/chat/theme/index.ts +++ b/packages/uiweb/src/lib/components/chat/theme/index.ts @@ -24,6 +24,8 @@ interface IBorder { chatWidget?: string; chatSentBubble?: string; chatReceivedBubble?: string; + reactionsBorder?: string; + reactionsHoverBorder?: string; } interface IBorderRadius { chatViewComponent?: string; @@ -35,6 +37,9 @@ interface IBorderRadius { chatPreview?: string; userProfile?: string; chatWidget?: string; + chatBubbleBorderRadius?: string; + reactionsPickerBorderRadius?: string; + reactionsBorderRadius?: string; } interface IPadding { @@ -46,6 +51,8 @@ interface IPadding { messageInputPadding?: string; chatBubbleSenderPadding?: string; chatBubbleReceiverPadding?: string; + reactionsPickerPadding?: string; + reactionsPadding?: string; } interface IMargin { @@ -200,6 +207,9 @@ export const lightChatTheme: IChatTheme = { chatPreview: '24px', userProfile: '0px', chatWidget: '24px', + chatBubbleBorderRadius: '12px', + reactionsPickerBorderRadius: '12px', + reactionsBorderRadius: '24px', }, padding: { @@ -211,6 +221,8 @@ export const lightChatTheme: IChatTheme = { messageInputPadding: '0px', chatBubbleSenderPadding: '0px', chatBubbleReceiverPadding: '0px', + reactionsPickerPadding: '4px', + reactionsPadding: '4px 8px', }, margin: { @@ -220,8 +232,8 @@ export const lightChatTheme: IChatTheme = { chatViewMargin: '0px', chatViewListMargin: '0px 0px 0px 10px', messageInputMargin: '2px 10px 10px 10px', - chatBubbleSenderMargin: '8px 8px 8px 0px', - chatBubbleReceiverMargin: '8px 0px 8px 8px', + chatBubbleSenderMargin: '16px 8px', + chatBubbleReceiverMargin: '16px 8px', }, backgroundColor: { @@ -311,6 +323,8 @@ export const lightChatTheme: IChatTheme = { chatWidget: '1px solid #E4E8EF', chatReceivedBubble: 'none', chatSentBubble: 'none', + reactionsBorder: '1px solid transparent', + reactionsHoverBorder: '1px solid #DFDFDF', }, iconColor: { @@ -367,6 +381,9 @@ export const darkChatTheme: IChatTheme = { chatPreview: '24px', userProfile: '0px', chatWidget: '24px', + chatBubbleBorderRadius: '12px', + reactionsPickerBorderRadius: '12px', + reactionsBorderRadius: '24px', }, padding: { @@ -378,6 +395,8 @@ export const darkChatTheme: IChatTheme = { messageInputPadding: '0px', chatBubbleSenderPadding: '0px', chatBubbleReceiverPadding: '0px', + reactionsPickerPadding: '4px', + reactionsPadding: '4px 8px', }, margin: { @@ -387,8 +406,8 @@ export const darkChatTheme: IChatTheme = { chatViewMargin: '0px', chatViewListMargin: '0px 0px 0px 10px', messageInputMargin: '2px 10px 10px 10px', - chatBubbleSenderMargin: '8px 8px 8px 0px', - chatBubbleReceiverMargin: '8px 0px 8px 8px', + chatBubbleSenderMargin: '16px 8px', + chatBubbleReceiverMargin: '16px 8px', }, backgroundColor: { @@ -477,6 +496,8 @@ export const darkChatTheme: IChatTheme = { userProfile: 'none', chatReceivedBubble: 'none', chatSentBubble: 'none', + reactionsBorder: '1px solid transparent', + reactionsHoverBorder: '1px solid #282A2E', }, iconColor: { diff --git a/packages/uiweb/src/lib/components/chatAndNotification/modal/messageBox/typebar/Typebar.tsx b/packages/uiweb/src/lib/components/chatAndNotification/modal/messageBox/typebar/Typebar.tsx index fac3c793b..e33c70af5 100644 --- a/packages/uiweb/src/lib/components/chatAndNotification/modal/messageBox/typebar/Typebar.tsx +++ b/packages/uiweb/src/lib/components/chatAndNotification/modal/messageBox/typebar/Typebar.tsx @@ -2,15 +2,13 @@ import type { ChangeEvent } from 'react'; import React, { useState, useContext, useRef, useEffect } from 'react'; import styled from 'styled-components'; import { Div, Section } from '../../../../reusables/sharedStyling'; -import { EmojiIcon } from '../../../../../icons/Emoji'; +import { EmojiCircleIcon } from '../../../../../icons/PushIcons'; +import { ThemeContext } from '../../../../chat/theme/ThemeProvider'; import { SendIcon } from '../../../../../icons/Send'; import { GifIcon } from '../../../../../icons/Gif'; import { AttachmentIcon } from '../../../../../icons/Attachment'; import usePushSendMessage from '../../../../../hooks/chatAndNotification/chat/usePushSendMessage'; -import { - ChatAndNotificationMainContext, - ChatMainStateContext, -} from '../../../../../context'; +import { ChatAndNotificationMainContext, ChatMainStateContext } from '../../../../../context'; import useFetchRequests from '../../../../../hooks/chatAndNotification/chat/useFetchRequests'; import { Spinner } from '../../../../reusables/Spinner'; import type { EmojiClickData } from 'emoji-picker-react'; @@ -34,6 +32,9 @@ type TypebarPropType = { const requestLimit = 30; const page = 1; export const Typebar: React.FC = ({ scrollToBottom }) => { + // get theme + const theme = useContext(ThemeContext); + const [typedMessage, setTypedMessage] = useState(''); const [showEmojis, setShowEmojis] = useState(false); const [gifOpen, setGifOpen] = useState(false); @@ -41,10 +42,7 @@ export const Typebar: React.FC = ({ scrollToBottom }) => { const fileUploadInputRef = React.useRef(null); const { selectedChatId, chatsFeed, setSearchedChats, requestsFeed } = useContext(ChatMainStateContext); - const { newChat, setNewChat } = - useContext( - ChatAndNotificationMainContext - ); + const { newChat, setNewChat } = useContext(ChatAndNotificationMainContext); const { sendMessage, loading } = usePushSendMessage(); const [filesUploading, setFileUploading] = useState(false); const { fetchRequests } = useFetchRequests(); @@ -66,14 +64,9 @@ export const Typebar: React.FC = ({ scrollToBottom }) => { }); scrollToBottom(); - if ( - chatsFeed[selectedChatId as string] || - requestsFeed[selectedChatId as string] - ) - setSearchedChats(null); + if (chatsFeed[selectedChatId as string] || requestsFeed[selectedChatId as string]) setSearchedChats(null); if (newChat) setNewChat(false); - if (!chatsFeed[selectedChatId as string]) - fetchRequests({ page, requestLimit }); + if (!chatsFeed[selectedChatId as string]) fetchRequests({ page, requestLimit }); } catch (error) { console.log(error); //handle error @@ -102,20 +95,14 @@ export const Typebar: React.FC = ({ scrollToBottom }) => { } }; - const uploadFile = async ( - e: ChangeEvent - ): Promise => { + 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 - ) { + 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 { @@ -170,7 +157,10 @@ export const Typebar: React.FC = ({ scrollToBottom }) => { alignItems="center" justifyContent="space-between" > -
+
= ({ scrollToBottom }) => { alignSelf="end" onClick={() => setShowEmojis(!showEmojis)} > - +
{showEmojis && ( @@ -209,7 +202,7 @@ export const Typebar: React.FC = ({ scrollToBottom }) => { rows={1} />
- +
= ({ scrollToBottom }) => { )} {(loading || filesUploading) && ( -
+
)} diff --git a/packages/uiweb/src/lib/components/chatWidget/Modal.tsx b/packages/uiweb/src/lib/components/chatWidget/Modal.tsx index e53d62e18..cb47b9e2d 100644 --- a/packages/uiweb/src/lib/components/chatWidget/Modal.tsx +++ b/packages/uiweb/src/lib/components/chatWidget/Modal.tsx @@ -1,4 +1,4 @@ -import React, { useContext } from 'react'; +import React, { useContext, useEffect, useState } from 'react'; import styled from 'styled-components'; import { Div, Section, Span } from '../reusables/sharedStyling'; @@ -7,6 +7,7 @@ import { useChatData } from '../../hooks'; import { SponserPushIcon } from '../../icons/SponserPush'; import { ThemeContext } from '../chat/theme/ThemeProvider'; import { MinimizeIcon } from '../../icons/Minimize'; +import { deriveChatId } from '../../helpers'; /** * @interface IThemeProps @@ -26,6 +27,26 @@ type ModalProps = { export const Modal: React.FC = ({ chatId, isModalOpen, setIsModalOpen, modalTitle, welcomeComponent }) => { const { user } = useChatData(); const theme = useContext(ThemeContext); + // set loading state + const [initialized, setInitialized] = useState({ + loading: true, + derivedChatId: '', + }); + + useEffect(() => { + const fetchDerivedChatId = async () => { + setInitialized((currentState) => ({ ...currentState, loading: true })); + + if (chatId) { + const id = await deriveChatId(chatId, user); + setInitialized({ loading: false, derivedChatId: id }); + } else { + setInitialized({ loading: false, derivedChatId: '' }); + } + }; + + fetchDerivedChatId(); + }, [chatId, user]); // Re-run this effect if chatId or env changes return ( {/* check other inputs for the components */} @@ -49,31 +70,39 @@ export const Modal: React.FC = ({ chatId, isModalOpen, setIsModalOpe
-
- + {!initialized.loading && chatId ? (
- {!user || (user && user?.readmode()) ? <>{welcomeComponent} : } -
+
+ +
+
+ {!user || (user && user?.readmode()) ? ( + <>{welcomeComponent} + ) : ( + + )} +
-
- +
+ +
-
+ ) : null}
` @@ -79,6 +80,7 @@ export const Section = styled.div` z-index: ${(props) => props.zIndex || '0'}; white-space: ${(props) => props.whiteSpace || 'normal'}; border: ${(props) => props.border || 'initial'}; + font-size: ${(props) => props.fontSize || 'initial'}; &.skeleton { > * { @@ -167,6 +169,7 @@ type SpanStyleProps = { cursor?: string; whiteSpace?: string; visibility?: string; + textWrap?: string; }; export const Span = styled.span` @@ -194,6 +197,7 @@ export const Span = styled.span` z-index: ${(props) => props.zIndex || 'auto'}; max-width: ${(props) => props.maxWidth || 'initial'}; white-space: ${(props) => props.whiteSpace || 'normal'}; + text-wrap: ${(props) => props.textWrap || 'normal'}; &.skeleton { > * { @@ -332,8 +336,8 @@ type ButtonStyleProps = { }; export const Button = styled.button` - display: ${(props) => props.display || 'initial'}; - line-height: ${(props) => props.lineHeight || '26px'}; + display: ${(props) => props.display || 'flex'}; + line-height: ${(props) => props.lineHeight || 'normal'}; flex: ${(props) => props.flex || 'initial'}; flex-direction: ${(props) => props.flexDirection || 'row'}; align-self: ${(props) => props.alignSelf || 'auto'}; @@ -383,7 +387,7 @@ export const Button = styled.button` } &:hover { - border: ${(props) => props.hoverBorder || 'inherit'}; + border: ${(props) => props.hoverBorder || 'none'}; & svg > path { stroke: ${(props) => props.hoverSVGPathStroke || 'auto'}; @@ -395,10 +399,11 @@ export const Button = styled.button` } &:hover:after { - opacity: 0.08; + opacity: ${(props) => (props.hoverBackground ? 1 : 0.08)}; } + &:active:after { - opacity: 0.15; + opacity: ${(props) => (props.hoverBackground ? 1 : 0.15)}; } & > div { diff --git a/packages/uiweb/src/lib/components/widget/ConnectButton/ConnectButton.tsx b/packages/uiweb/src/lib/components/widget/ConnectButton/ConnectButton.tsx index 634783b39..0abbca46c 100644 --- a/packages/uiweb/src/lib/components/widget/ConnectButton/ConnectButton.tsx +++ b/packages/uiweb/src/lib/components/widget/ConnectButton/ConnectButton.tsx @@ -1,4 +1,4 @@ -import { useContext, useEffect, useState, } from 'react'; +import { useContext, useEffect, useState } from 'react'; import styled from 'styled-components'; @@ -25,16 +25,15 @@ interface IConnectButtonSubProps { setSigner: React.Dispatch>; } -export const ConnectButtonSub: React.FC = ({ +export const ConnectButton: React.FC = ({ autoconnect = false, setAccount, setSigner, signer, }) => { const { env } = useWidgetData(); - const { wallet, connecting, connect, disconnect, provider, account } = - useAccount({ env }); - const [clickedConnect,setClickedConnect] = useState(false); + const { wallet, connecting, connect, disconnect, provider, account } = useAccount({ env }); + const [clickedConnect, setClickedConnect] = useState(false); const theme = useContext(ThemeContext); @@ -42,20 +41,19 @@ export const ConnectButtonSub: React.FC = ({ if (wallet) { (async () => { const librarySigner = provider?.getSigner(account); - const newAdd = await getAddressFromSigner(librarySigner) - + const newAdd = await getAddressFromSigner(librarySigner); + setAccount(account || newAdd); setSigner(librarySigner); })(); - } else if (!wallet ) { + } else if (!wallet) { setAccount(GUEST_MODE_ACCOUNT); setSigner(undefined); } changeModalStyle('zIndex', '2000'); }; useEffect(() => { - if (wallet && !autoconnect ) { - + if (wallet && !autoconnect) { disconnect(wallet); } setUserData(); @@ -90,8 +88,7 @@ const ConnectButtonDiv = styled.div` width: 100%; button { - background: ${(props) => - `${props.theme.backgroundColor?.buttonBackground}!important`}; + background: ${(props) => `${props.theme.backgroundColor?.buttonBackground}!important`}; color: ${(props) => `${props.theme.textColor?.buttonText}!important`}; text-align: center; font-size: 1em; diff --git a/packages/uiweb/src/lib/components/widget/ConnectButton/index.tsx b/packages/uiweb/src/lib/components/widget/ConnectButton/index.tsx index 9e50b9aca..d5457ba77 100644 --- a/packages/uiweb/src/lib/components/widget/ConnectButton/index.tsx +++ b/packages/uiweb/src/lib/components/widget/ConnectButton/index.tsx @@ -1,110 +1 @@ -import coinbaseWalletModule from '@web3-onboard/coinbase'; -import { ConnectButtonSub } from './ConnectButton'; -import { BLOCKNATIVE_PROJECT_ID, InfuraAPIKey } from '../../../config'; -import { Web3OnboardProvider } from '@web3-onboard/react'; -import injectedModule from '@web3-onboard/injected-wallets'; -import walletConnectModule from '@web3-onboard/walletconnect'; -import init from '@web3-onboard/core'; -import PushIcon from '../../../icons/Bell.svg'; -import { SignerType } from '@pushprotocol/restapi'; - -const APP_META_DATA = { - name: 'Push Protocol', - logo: PushIcon, - icon: PushIcon, - description: 'Example showcasing how to connect a wallet.', - - recommendedInjectedWallets: [ - { name: 'MetaMask', url: 'https://metamask.io' }, - ], -}; - -const wcv2InitOptions = { - projectId: BLOCKNATIVE_PROJECT_ID, - requiredChains: [1, 56], -}; - -const walletConnect = walletConnectModule(wcv2InitOptions); -const coinbaseWalletSdk = coinbaseWalletModule({ darkMode: true }); -const CHAINS = [ - { - id: '0x1', - token: 'ETH', - label: 'Ethereum Mainnet', - rpcUrl: `https://mainnet.infura.io/v3/${InfuraAPIKey}`, - }, - { - id: '0xAA36A7', - token: 'ETH', - label: 'Sepolia', - rpcUrl: `https://sepolia.infura.io/v3/${InfuraAPIKey}`, - }, - { - id: '0x13881', - token: 'MATIC', - label: 'Polygon - Amoy', - rpcUrl: 'https://rpc-amoy.polygon.technology', - }, - { - id: '0x38', - token: 'BNB', - label: 'Binance', - rpcUrl: 'https://bsc-dataseed.binance.org/', - }, - { - id: '0xA', - token: 'OETH', - label: 'Optimism', - rpcUrl: 'https://mainnet.optimism.io', - }, - { - id: '0xA4B1', - token: 'ARB-ETH', - label: 'Arbitrum', - rpcUrl: 'https://rpc.ankr.com/arbitrum', - }, -]; - -const wallets = [injectedModule(), walletConnect, coinbaseWalletSdk]; - -const web3OnBoard = init({ - wallets, - chains: CHAINS, - appMetadata: APP_META_DATA, - accountCenter: { - desktop: { - enabled: false, - }, - mobile: { - enabled: false, - }, - }, - connect: { - autoConnectLastWallet: true, - }, -}); - -interface IConnectButtonCompProps { - autoconnect?: boolean; - setAccount: React.Dispatch>; - signer: SignerType | undefined; - setSigner: React.Dispatch>; -} - -export const ConnectButtonComp: React.FC = ({ - autoconnect, - setAccount, - setSigner, - signer, -}) => { - return ( - - - - ); -}; +export { ConnectButton } from './ConnectButton'; diff --git a/packages/uiweb/src/lib/components/widget/helpers/notifications.ts b/packages/uiweb/src/lib/components/widget/helpers/notifications.ts index cc139590a..af441f4a9 100644 --- a/packages/uiweb/src/lib/components/widget/helpers/notifications.ts +++ b/packages/uiweb/src/lib/components/widget/helpers/notifications.ts @@ -2,13 +2,10 @@ import { NotificationSettingType, UserSetting } from '@pushprotocol/restapi'; const isSettingType1 = (setting: NotificationSettingType) => setting.type === 1; -export const notifUserSettingFormatString = ({ - settings, -}: { - settings: NotificationSettingType[]; -}) => { +export const notifUserSettingFormatString = ({ settings }: { settings: NotificationSettingType[] }) => { const _notifSettings: UserSetting[] = []; settings && + settings.length && settings.forEach((setting) => isSettingType1(setting) ? _notifSettings.push({ @@ -21,38 +18,32 @@ export const notifUserSettingFormatString = ({ ); return _notifSettings; }; -export const notifStrictUserSettingFromat = ({ - settings, -}: { - settings: NotificationSettingType[]; -}) => { +export const notifStrictUserSettingFromat = ({ settings }: { settings: NotificationSettingType[] }) => { const _notifSettings: NotificationSettingType[] = []; settings && - settings.forEach((setting) => - isSettingType1(setting) - ? _notifSettings.push({ - ...setting, - userPreferance: setting?.userPreferance?? {value:0,enabled:false}, - }) - : _notifSettings.push({ - ...setting, - userPreferance: setting?.userPreferance?? { - value: setting.default ||0, - enabled: false, - } - }) - ); + settings.length && + settings.forEach((setting) => + isSettingType1(setting) + ? _notifSettings.push({ + ...setting, + userPreferance: setting?.userPreferance ?? { value: 0, enabled: false }, + }) + : _notifSettings.push({ + ...setting, + userPreferance: setting?.userPreferance ?? { + value: setting.default || 0, + enabled: false, + }, + }) + ); return _notifSettings; }; -export const notifUserSettingFromChannelSetting = ({ - settings -}: { - settings: any[]; -}) => { +export const notifUserSettingFromChannelSetting = ({ settings }: { settings: any[] }) => { const _userSettings: NotificationSettingType[] = []; settings && + settings.length && settings.forEach((setting) => isSettingType1(setting) ? _userSettings.push({ diff --git a/packages/uiweb/src/lib/components/widget/subscriptionManager/ManageNotificationComponent.tsx b/packages/uiweb/src/lib/components/widget/subscriptionManager/ManageNotificationComponent.tsx index 3f99c99cd..d54ed4d3d 100644 --- a/packages/uiweb/src/lib/components/widget/subscriptionManager/ManageNotificationComponent.tsx +++ b/packages/uiweb/src/lib/components/widget/subscriptionManager/ManageNotificationComponent.tsx @@ -2,10 +2,7 @@ import React, { useContext, useState } from 'react'; import { Button, PoweredByPush } from '../reusables'; import { Anchor, Section, Span, Spinner } from '../../reusables'; import styled from 'styled-components'; -import { - useManageSubscriptionsUtilities, - useWidgetData, -} from '../../../hooks'; +import { useManageSubscriptionsUtilities, useWidgetData } from '../../../hooks'; import useToast from '../reusables/NewToast'; import { MdCheckCircle, MdError } from 'react-icons/md'; import { ThemeContext } from '../theme/ThemeProvider'; @@ -21,7 +18,7 @@ import { device } from '../../../config'; import * as PushAPI from '@pushprotocol/restapi'; import { notifStrictUserSettingFromat, notifUserSettingFormatString } from '../helpers'; import { ChannelDetailsComponent } from './ChannelDetailsComponent'; -import { ConnectButtonComp } from '../ConnectButton'; +import { ConnectButton } from '../ConnectButton'; /** * @interface IThemeProps @@ -38,19 +35,11 @@ interface IManageNotificationComponentProps { userSettings: any; handleNext: () => void; } -export const ManageNotficationsComponent: React.FC< - IManageNotificationComponentProps -> = (options: IManageNotificationComponentProps) => { - const { - userSettings, - channelAddress, - channelInfo, - handleNext, - autoconnect = false, - } = options || {}; - const [modifiedSettings, setModifiedSettings] = useState< - PushAPI.NotificationSettingType[] | null - >([...userSettings]); +export const ManageNotficationsComponent: React.FC = ( + options: IManageNotificationComponentProps +) => { + const { userSettings, channelAddress, channelInfo, handleNext, autoconnect = false } = options || {}; + const [modifiedSettings, setModifiedSettings] = useState([...userSettings]); const { unsubscribeError, unsubscribeLoading, @@ -71,7 +60,12 @@ export const ManageNotficationsComponent: React.FC< toastTitle: title, toastMessage: subTitle, toastType: 'ERROR', - getToastIcon: (size) => , + getToastIcon: (size) => ( + + ), }); }; @@ -80,7 +74,12 @@ export const ManageNotficationsComponent: React.FC< toastTitle: title, toastMessage: subTitle, toastType: 'SUCCESS', - getToastIcon: (size) => , + getToastIcon: (size) => ( + + ), }); }; const handleUnsubscribe = async () => { @@ -90,10 +89,7 @@ export const ManageNotficationsComponent: React.FC< }); if (response && response?.status === 204) { //show toast - showSuccess( - 'Notification Disabled', - `You have successfully disabled notifications from ${channelInfo?.name}` - ); + showSuccess('Notification Disabled', `You have successfully disabled notifications from ${channelInfo?.name}`); handleNext(); } } catch (e) { @@ -108,7 +104,6 @@ export const ManageNotficationsComponent: React.FC< } }; - const getFlexDirection = () => { if (userSettings && userSettings.length) { if (isMobile) return 'column'; @@ -125,10 +120,7 @@ export const ManageNotficationsComponent: React.FC< }); if (response && response?.status === 204) { //show toast - showSuccess( - 'Notification Preferences Updated', - 'Your notification settings were updated successfully.' - ); + showSuccess('Notification Preferences Updated', 'Your notification settings were updated successfully.'); } } catch (e) { console.debug(e); @@ -142,7 +134,10 @@ export const ManageNotficationsComponent: React.FC< } }; return ( -
+
- {(user?.readmode()) && ( - )} - {!(user?.readmode()) && ( - )} @@ -206,10 +205,16 @@ export const ManageNotficationsComponent: React.FC< fontSize="12px" fontWeight="400" > - + {' '} {unsubscribeLoading ? ( - + ) : ( 'Unsubscribe' )}{' '} @@ -238,7 +243,11 @@ const GettingStarted = ({ divider }: { divider: string }) => { theme={theme} divider={divider} > -
+
- >; + setSettings: React.Dispatch>; } -export const SettingsComponent: React.FC = ( - options: ISettingsComponentProps -) => { +export const SettingsComponent: React.FC = (options: ISettingsComponentProps) => { const theme = useContext(ThemeContext); const { settings = [], setSettings } = options || {}; - const handleSliderChange = ( - index: number, - value: number | { lower: number; upper: number } - ) => { + const handleSliderChange = (index: number, value: number | { lower: number; upper: number }) => { const updatedSettings = [...settings]; updatedSettings[index].userPreferance!.value = value; @@ -41,9 +34,8 @@ export const SettingsComponent: React.FC = ( const handleToggleChange = (index: number) => { const updatedSettings = [...settings]; - if(updatedSettings[index]?.userPreferance) - updatedSettings[index].userPreferance!.enabled = - !updatedSettings[index].userPreferance!.enabled; + if (updatedSettings[index]?.userPreferance) + updatedSettings[index].userPreferance!.enabled = !updatedSettings[index].userPreferance!.enabled; setSettings(updatedSettings); }; return ( @@ -53,11 +45,12 @@ export const SettingsComponent: React.FC = ( gap="15px" width="100%" maxHeight="200px" - justifyContent='start' + justifyContent="start" overflow="hidden scroll" > - {settings.map( - (setting: PushAPI.NotificationSettingType, index: number) => ( + {settings && + settings?.length && + settings?.map((setting: PushAPI.NotificationSettingType, index: number) => ( = ( {(setting.type == 2 || setting.type == 1) && ( { handleToggleChange(index); }} /> )} - {setting.type == 2 && setting?.userPreferance?.enabled && ( = ( onChange={({ x }) => handleSliderChange(index, x)} /> )} + {/* uncomment this when we need this to be included in the settings feature + not needed as of now */} {/* {setting.type == 3 && setting?.userPreferance?.enabled && ( = ( /> )} */} - ) - )} + ))} ); }; const SettingsSection = styled(Section)` - border-bottom: ${(props) => - props.divider ? props.theme.border?.divider : 'none'}; + border-bottom: ${(props) => (props.divider ? props.theme.border?.divider : 'none')}; padding-bottom: 15px; `; const ScrollSection = styled(Section)<{ theme: IWidgetTheme }>` diff --git a/packages/uiweb/src/lib/components/widget/subscriptionManager/SubscribeComponent.tsx b/packages/uiweb/src/lib/components/widget/subscriptionManager/SubscribeComponent.tsx index c5e86e2e1..dff1838d6 100644 --- a/packages/uiweb/src/lib/components/widget/subscriptionManager/SubscribeComponent.tsx +++ b/packages/uiweb/src/lib/components/widget/subscriptionManager/SubscribeComponent.tsx @@ -7,7 +7,7 @@ import { useManageSubscriptionsUtilities, useWidgetData } from '../../../hooks'; import useToast from '../reusables/NewToast'; import { MdCheckCircle, MdError } from 'react-icons/md'; import { ThemeContext } from '../theme/ThemeProvider'; -import { ConnectButtonComp } from '../ConnectButton'; +import { ConnectButton } from '../ConnectButton'; import { WidgetErrorCodes } from './types'; import { SettingsComponent } from './SettingsComponents'; import { notifUserSettingFormatString, notifUserSettingFromChannelSetting } from '../helpers'; @@ -22,10 +22,8 @@ import { ChannelDetailsComponent } from './ChannelDetailsComponent'; // } const SubHeadingText = { - withSettings: - 'Select which setting list you would like to receive notifications from.', - withoutSettings: - 'Subscribe and receive notifications from your favorite protocol.', + withSettings: 'Select which setting list you would like to receive notifications from.', + withoutSettings: 'Subscribe and receive notifications from your favorite protocol.', }; interface ISubscribeComponentProps { autoconnect?: boolean; @@ -33,37 +31,28 @@ interface ISubscribeComponentProps { channelAddress: string; handleNext: () => void; } -export const SubscribeComponent: React.FC = ( - options: ISubscribeComponentProps -) => { - const { - channelInfo, - handleNext, - channelAddress, - autoconnect = false, - } = options || {}; - const { subscribeToChannel, subscribeError, subscribeLoading,setSubscribeError } = - useManageSubscriptionsUtilities(); +export const SubscribeComponent: React.FC = (options: ISubscribeComponentProps) => { + const { channelInfo, handleNext, channelAddress, autoconnect = false } = options || {}; + const { subscribeToChannel, subscribeError, subscribeLoading, setSubscribeError } = useManageSubscriptionsUtilities(); const theme = useContext(ThemeContext); - const { signer, setAccount, setSigner,user,account } = useWidgetData(); + const { signer, setAccount, setSigner, user, account } = useWidgetData(); const subscribeToast = useToast(); - const [modifiedSettings, setModifiedSettings] = useState(useMemo(() => { - if (channelInfo && channelInfo?.channel_settings) { - return notifUserSettingFromChannelSetting({settings:( JSON.parse(channelInfo?.channel_settings))}); - } - return null; - }, [channelInfo])); - - + const [modifiedSettings, setModifiedSettings] = useState( + useMemo(() => { + if (channelInfo && channelInfo?.channel_settings) { + return notifUserSettingFromChannelSetting({ settings: JSON.parse(channelInfo?.channel_settings) }); + } + return []; + }, [channelInfo]) + ); const handleSubscribe = async () => { - try { const response = await subscribeToChannel({ channelAddress: channelAddress, - channelSettings:notifUserSettingFormatString({ - settings: modifiedSettings!, - }), + channelSettings: notifUserSettingFormatString({ + settings: modifiedSettings, + }), }); if (response && response?.status === 204) { //show toast @@ -72,70 +61,100 @@ export const SubscribeComponent: React.FC = ( toastTitle: 'Notifications Enabled', toastMessage: `You have successfully enabled notifications from ${channelInfo?.name}`, toastType: 'SUCCESS', - getToastIcon: (size) => , + getToastIcon: (size) => ( + + ), }); - } } catch (e) { console.debug(e); - setSubscribeError( - WidgetErrorCodes.NOTIFICATION_WIDGET_SUBSCRIBE_ERROR - ); + setSubscribeError(WidgetErrorCodes.NOTIFICATION_WIDGET_SUBSCRIBE_ERROR); } if (subscribeError) { subscribeToast.showMessageToast({ toastTitle: 'Error while Enabling Notifications', - toastMessage: - 'We encountered an error while enabling notifications. Please try again.', + toastMessage: 'We encountered an error while enabling notifications. Please try again.', toastType: 'ERROR', - getToastIcon: (size) => , + getToastIcon: (size) => ( + + ), }); } }; return ( -
+
+
+ + Subscribe to get Notified + + + {SubHeadingText.withoutSettings} + +
+ + {channelInfo && channelInfo?.channel_settings && (
- - Subscribe to get Notified - - - {SubHeadingText.withoutSettings} - +
- - {channelInfo && channelInfo?.channel_settings &&
- -
} - - {(user && !!user?.readmode)? - <> - {user?.readmode() && ( - )} - - {!user?.readmode() && - } - - :null - } - + )} + + {user && !!user?.readmode ? ( + <> + {user?.readmode() && ( + + )} + + {!user?.readmode() && ( + + )} + + ) : null} +
); diff --git a/packages/uiweb/src/lib/dataProviders/ChatDataProvider.tsx b/packages/uiweb/src/lib/dataProviders/ChatDataProvider.tsx index 477a255bd..78f0ae73d 100644 --- a/packages/uiweb/src/lib/dataProviders/ChatDataProvider.tsx +++ b/packages/uiweb/src/lib/dataProviders/ChatDataProvider.tsx @@ -3,16 +3,13 @@ import { ReactNode, useEffect, useRef, useState } from 'react'; // External Packages import { CONSTANTS, PushAPI, SignerType } from '@pushprotocol/restapi'; -import { ThemeProvider } from 'styled-components'; // Internal Compoonents import { ChatDataContext, IChatDataContextValues } from '../context/chatContext'; -import { getAddressFromSigner, pCAIP10ToWallet, traceStackCalls } from '../helpers'; -import useChatProfile from '../hooks/chat/useChatProfile'; +import { pCAIP10ToWallet } from '../helpers'; + import usePushUserInfoUtilities from '../hooks/chat/useUserInfoUtilities'; -import useCreateChatProfile from '../hooks/useCreateChatProfile'; -import useDecryptPGPKey from '../hooks/useDecryptPGPKey'; -import useGetChatProfile from '../hooks/useGetChatProfile'; + import usePushUser from '../hooks/usePushUser'; import useToast from '../components/chat/reusables/NewToast'; // Re-write this later @@ -27,6 +24,7 @@ import { Constants, ENV, GUEST_MODE_ACCOUNT } from '../config'; import { IUser } from '@pushprotocol/restapi'; import { IChatTheme } from '../components/chat/theme'; import { GlobalStyle } from '../components/reusables'; +import { Web3OnboardDataProvider } from './Web3OnboardDataProvider'; // Constants // Save original console methods @@ -115,7 +113,7 @@ export const ChatUIProvider = ({ console.debug(`UIWeb::ChatDataProvider::user changed - ${new Date().toISOString()}`, user); - if (!user.readmode()) { + if (!user?.readmode()) { await initStream(user); } @@ -223,10 +221,10 @@ export const ChatUIProvider = ({ // To setup debug parameters useEffect(() => { if (debug) { - console.debug('UIWeb::ChatDataProvider::Debug mode enabled'); + console.debug('UIWeb::ChatDataProvider::Debug mode enabled, console logs are enabled'); enableConsole(); } else { - console.warn('UIWeb::ChatDataProvider::Debug mode disabled'); + console.warn('UIWeb::ChatDataProvider::Debug mode is turned off, console logs are suppressed'); disableConsole(); } }, [debug]); @@ -471,8 +469,10 @@ export const ChatUIProvider = ({ const PROVIDER_THEME = Object.assign({}, lightChatTheme, theme); return ( - - {children} + + + {children} + ); }; diff --git a/packages/uiweb/src/lib/dataProviders/Web3OnboardDataProvider.tsx b/packages/uiweb/src/lib/dataProviders/Web3OnboardDataProvider.tsx new file mode 100644 index 000000000..0e1997857 --- /dev/null +++ b/packages/uiweb/src/lib/dataProviders/Web3OnboardDataProvider.tsx @@ -0,0 +1,90 @@ +import coinbaseWalletModule from '@web3-onboard/coinbase'; + +import { BLOCKNATIVE_PROJECT_ID, InfuraAPIKey } from '../config'; +import { Web3OnboardProvider } from '@web3-onboard/react'; +import injectedModule, { ProviderLabel } from '@web3-onboard/injected-wallets'; +import walletConnectModule from '@web3-onboard/walletconnect'; +import init from '@web3-onboard/core'; +import PushIcon from '../icons/Bell.svg'; +import { ReactNode } from 'react'; +import { AppMetaDataType, ChainType } from '../types'; + +const APP_META_DATA: AppMetaDataType = { + name: 'Push Protocol', + logo: PushIcon, + icon: PushIcon, + description: 'Example showcasing how to connect a wallet.', + + recommendedInjectedWallets: [{ name: 'MetaMask', url: 'https://metamask.io' }], +}; + +const wcv2InitOptions = { + projectId: BLOCKNATIVE_PROJECT_ID, + requiredChains: [1, 56], +}; + +const walletConnect = walletConnectModule(wcv2InitOptions); +const coinbaseWalletSdk = coinbaseWalletModule({ darkMode: true }); +const CHAINS: ChainType[] = [ + { + id: '0x1', + token: 'ETH', + label: 'Ethereum Mainnet', + rpcUrl: `https://mainnet.infura.io/v3/${InfuraAPIKey}`, + }, + { + id: '0xAA36A7', + token: 'ETH', + label: 'Sepolia', + rpcUrl: `https://sepolia.infura.io/v3/${InfuraAPIKey}`, + }, + { + id: '0x13882', + token: 'MATIC', + label: 'Polygon - Amoy', + rpcUrl: 'https://rpc-amoy.polygon.technology', + }, + { + id: '0x38', + token: 'BNB', + label: 'Binance', + rpcUrl: 'https://bsc-dataseed.binance.org/', + }, + { + id: '0xA', + token: 'OETH', + label: 'Optimism', + rpcUrl: 'https://mainnet.optimism.io', + }, + { + id: '0xA4B1', + token: 'ARB-ETH', + label: 'Arbitrum', + rpcUrl: 'https://rpc.ankr.com/arbitrum', + }, +]; + +const wallets = [injectedModule(), walletConnect, coinbaseWalletSdk]; +const web3OnBoard = init({ + wallets, + chains: CHAINS, + appMetadata: APP_META_DATA, + accountCenter: { + desktop: { + enabled: false, + }, + mobile: { + enabled: false, + }, + }, + connect: { + autoConnectLastWallet: true, + }, +}); + +interface IWeb3OnboardDataProviderProps { + children: ReactNode; +} +export const Web3OnboardDataProvider: React.FC = ({ children }) => { + return {children}; +}; diff --git a/packages/uiweb/src/lib/dataProviders/WidgetProvider.tsx b/packages/uiweb/src/lib/dataProviders/WidgetProvider.tsx index d926c66fc..4bda08fe3 100644 --- a/packages/uiweb/src/lib/dataProviders/WidgetProvider.tsx +++ b/packages/uiweb/src/lib/dataProviders/WidgetProvider.tsx @@ -4,12 +4,10 @@ import { IWidgetTheme, lightWidgetTheme } from '../components/widget/theme'; import { ThemeContext } from '../components/widget/theme/ThemeProvider'; import { Constants, ENV } from '../config'; import { GUEST_MODE_ACCOUNT } from '../config/constants'; -import { - IWidgetDataContextValues, - WidgetDataContext, -} from '../context/widgetContext'; +import { IWidgetDataContextValues, WidgetDataContext } from '../context/widgetContext'; import { getAddressFromSigner, pCAIP10ToWallet } from '../helpers'; import usePushUser from '../hooks/usePushUser'; +import { Web3OnboardDataProvider } from './Web3OnboardDataProvider'; export interface IWidgetUIProviderProps { children: ReactNode; @@ -28,9 +26,7 @@ export const WidgetUIProvider = ({ signer = undefined, env = Constants.ENV.PROD, }: IWidgetUIProviderProps) => { - const [accountVal, setAccountVal] = useState( - pCAIP10ToWallet(account!) - ); + const [accountVal, setAccountVal] = useState(pCAIP10ToWallet(account!)); const [signerVal, setSignerVal] = useState(signer); const [userVal, setUserVal] = useState(user); const [envVal, setEnvVal] = useState(env); @@ -39,24 +35,23 @@ export const WidgetUIProvider = ({ useEffect(() => { (async () => { // resetStates(); - + setEnvVal(env); let address = null; - if (Object.keys(signer || {}).length && !user) { - address = await getAddressFromSigner(signer!); - } else if (!signer && user) { - const profile = await fetchUserProfile({user}); - if(profile) - address = (pCAIP10ToWallet(profile?.wallets)); - } - console.debug(account) - setAccountVal(address || GUEST_MODE_ACCOUNT); - setSignerVal(signer); - - setUserVal(user); + if (Object.keys(signer || {}).length && !user) { + address = await getAddressFromSigner(signer!); + } else if (!signer && user) { + const profile = await fetchUserProfile({ user }); + if (profile) address = pCAIP10ToWallet(profile?.wallets); + } + console.debug(account); + setAccountVal(address || GUEST_MODE_ACCOUNT); + setSignerVal(signer); + + setUserVal(user); })(); }, [env, account, signer, user]); - console.debug(accountVal,envVal,signerVal) + console.debug(accountVal, envVal, signerVal); useEffect(() => { (async () => { @@ -78,7 +73,6 @@ export const WidgetUIProvider = ({ // setIsPushChatStreamConnected(false); // }; - const value: IWidgetDataContextValues = { account: accountVal, signer: signerVal, @@ -93,11 +87,9 @@ export const WidgetUIProvider = ({ const PROVIDER_THEME = Object.assign({}, lightWidgetTheme, theme); return ( - - {children} - + + {children} + ); }; - - diff --git a/packages/uiweb/src/lib/helpers/address.ts b/packages/uiweb/src/lib/helpers/address.ts index 86554855f..cc44ce5df 100644 --- a/packages/uiweb/src/lib/helpers/address.ts +++ b/packages/uiweb/src/lib/helpers/address.ts @@ -96,6 +96,10 @@ export const resolveWeb3Name = async (address: string, user: PushAPI | undefined } else { try { const udResolver = getUdResolver(user ? user.env : CONSTANTS.ENV.PROD); + if (!udResolver) { + throw new Error('UIWeb::helpers::address::resolveWeb3Name::Error in UD resolver'); + } + // attempt reverse resolution on provided address const udName = await udResolver.reverse(checksumWallet); if (udName) { @@ -111,7 +115,7 @@ export const resolveWeb3Name = async (address: string, user: PushAPI | undefined console.error('UIWeb::helpers::address::resolveWeb3Name::Error in resolving via ENS', err); } - console.debug(`UIWeb::helpers::address::resolveWeb3Name::Wallet: ${checksumWallet} resolved to ${result}`); + // console.debug(`UIWeb::helpers ::address::resolveWeb3Name::Wallet: ${checksumWallet} resolved to ${result}`); return result; }; diff --git a/packages/uiweb/src/lib/helpers/chat/search.ts b/packages/uiweb/src/lib/helpers/chat/search.ts index ffce2eb9b..ef8461e60 100644 --- a/packages/uiweb/src/lib/helpers/chat/search.ts +++ b/packages/uiweb/src/lib/helpers/chat/search.ts @@ -51,11 +51,6 @@ export const getNewChatUser = async ({ let chatProfile: IUser | undefined; let address: string | null = null; address = await getAddress(searchText, env); - // const provider = new ethers.providers.InfuraProvider(); - // address = await provider.resolveName(searchText); - // if (!address) { - // address = await getAddress(searchText, env); - // } if (address) { chatProfile = await fetchChatProfile({ profileId: address, env, user }); if (!chatProfile) chatProfile = displayDefaultUser({ caip10: walletToPCAIP10(address) }); @@ -70,7 +65,8 @@ export const getAddress = async (searchText: string, env: Env) => { let address: string | null = null; if (searchText.includes('.')) { try { - address = await udResolver.owner(searchText); + if (!udResolver) throw new Error('No udResolver available for the network'); + address = await udResolver?.owner(searchText); } catch (err) { try { address = await provider.resolveName(searchText); diff --git a/packages/uiweb/src/lib/helpers/chat/user.ts b/packages/uiweb/src/lib/helpers/chat/user.ts index 7c3a694e6..4414fd60f 100644 --- a/packages/uiweb/src/lib/helpers/chat/user.ts +++ b/packages/uiweb/src/lib/helpers/chat/user.ts @@ -1,12 +1,8 @@ import type { Env, IUser } from '@pushprotocol/restapi'; -import { - - ProfilePicture, -} from '../../config'; +import { ProfilePicture } from '../../config'; import { ethers } from 'ethers'; import { getUdResolver } from '../udResolver'; - export const displayDefaultUser = ({ caip10 }: { caip10: string }): IUser => { const userCreated: IUser = { did: caip10, @@ -61,6 +57,9 @@ export const getUnstoppableName = async ( ) => { // Unstoppable Domains resolution library const udResolver = getUdResolver(env); + if (!udResolver) { + return null; + } // attempt reverse resolution on provided address let udName = await udResolver.reverse(checksumWallet); @@ -71,5 +70,3 @@ export const getUnstoppableName = async ( } return udName; }; - - diff --git a/packages/uiweb/src/lib/helpers/udResolver.ts b/packages/uiweb/src/lib/helpers/udResolver.ts index f14085dcd..9670443fd 100644 --- a/packages/uiweb/src/lib/helpers/udResolver.ts +++ b/packages/uiweb/src/lib/helpers/udResolver.ts @@ -3,23 +3,28 @@ import Resolution from '@unstoppabledomains/resolution'; import { ethers } from 'ethers'; import { allowedNetworks, InfuraAPIKey, NETWORK_DETAILS } from '../config'; -export const getUdResolver = (env:Env): Resolution => { - const l1ChainId = allowedNetworks[env].includes(1) ? 1 : 5; - const l2ChainId = allowedNetworks[env].includes(137) ? 137 : 80002; - // ToDo: Enable for sepolia chainId once UD supports it - // const l1ChainId = appConfig.allowedNetworks.includes(1) ? 1 : 11155111; - return Resolution.fromEthersProvider({ - uns: { - locations: { - Layer1: { - network: "mainnet", // add config for sepolia once it's supported by UD - provider: new ethers.providers.InfuraProvider(l1ChainId, InfuraAPIKey), - }, - Layer2: { - network: NETWORK_DETAILS[l2ChainId].network, - provider: new ethers.providers.InfuraProvider(l2ChainId, InfuraAPIKey), +export const getUdResolver = (env: Env): Resolution | undefined => { + try { + const l1ChainId = allowedNetworks[env].includes(1) ? 1 : 5; + const l2ChainId = allowedNetworks[env].includes(137) ? 137 : 80002; + // ToDo: Enable for sepolia chainId once UD supports it + // const l1ChainId = appConfig.allowedNetworks.includes(1) ? 1 : 11155111; + return Resolution.fromEthersProvider({ + uns: { + locations: { + Layer1: { + network: 'mainnet', // add config for sepolia once it's supported by UD + provider: new ethers.providers.InfuraProvider(l1ChainId, InfuraAPIKey), + }, + Layer2: { + network: NETWORK_DETAILS[l2ChainId].network, + provider: new ethers.providers.InfuraProvider(l2ChainId, InfuraAPIKey), + }, }, }, - }, - }); + }); + } catch (e) { + console.debug(`Errored:UIWeb::helpers::getUdResolver::UD doesnot provide support for the network`); + return undefined; + } }; diff --git a/packages/uiweb/src/lib/helpers/utils.ts b/packages/uiweb/src/lib/helpers/utils.ts index 2adbb5e36..8cd145e13 100644 --- a/packages/uiweb/src/lib/helpers/utils.ts +++ b/packages/uiweb/src/lib/helpers/utils.ts @@ -19,6 +19,7 @@ import { getAddress } from './'; // Exported Functions +// Derive Chat Id export const deriveChatId = async (chatId: string, user: PushAPI | undefined): Promise => { // check if chatid: is appened, if so remove it if (chatId?.startsWith('chatid:')) { @@ -33,3 +34,9 @@ export const deriveChatId = async (chatId: string, user: PushAPI | undefined): P return chatId; }; + +export const isMessageEncrypted = (message: string) => { + if (!message) return false; + + return message.startsWith('U2FsdGVkX1'); +}; diff --git a/packages/uiweb/src/lib/hooks/chat/useFetchMessageUtilities.ts b/packages/uiweb/src/lib/hooks/chat/useFetchMessageUtilities.ts index dd2af7c26..0919c61aa 100644 --- a/packages/uiweb/src/lib/hooks/chat/useFetchMessageUtilities.ts +++ b/packages/uiweb/src/lib/hooks/chat/useFetchMessageUtilities.ts @@ -35,7 +35,6 @@ const useFetchMessageUtilities = () => { page: page, limit: limit, }); - console.debug(chats, 'chats from hook'); return chats; } catch (error: Error | any) { setChatListLoading(false); diff --git a/packages/uiweb/src/lib/hooks/chatAndNotification/chat/useFetchHistoryMessages.ts b/packages/uiweb/src/lib/hooks/chatAndNotification/chat/useFetchHistoryMessages.ts index 48f23c7d0..54c2d8e99 100644 --- a/packages/uiweb/src/lib/hooks/chatAndNotification/chat/useFetchHistoryMessages.ts +++ b/packages/uiweb/src/lib/hooks/chatAndNotification/chat/useFetchHistoryMessages.ts @@ -1,4 +1,3 @@ - import * as PushAPI from '@pushprotocol/restapi'; import type { IMessageIPFS } from '@pushprotocol/restapi'; import { Env } from '@pushprotocol/restapi'; @@ -7,65 +6,60 @@ import { Constants } from '../../../config'; import { ChatMainStateContext, ChatAndNotificationPropsContext } from '../../../context'; import type { ChatMainStateContextType } from '../../../context/chatAndNotification/chat/chatMainStateContext'; +interface HistoryMessagesParams { + threadHash: string; + limit?: number; +} - - interface HistoryMessagesParams { - threadHash: string; - limit?: number; - } - - -const useFetchHistoryMessages - = () => { +const useFetchHistoryMessages = () => { const [error, setError] = useState(); const [loading, setLoading] = useState(false); - const { chats,setChat,selectedChatId} = - useContext(ChatMainStateContext); - const { account, env,decryptedPgpPvtKey } = - useContext(ChatAndNotificationPropsContext); + const { chats, setChat, selectedChatId } = useContext(ChatMainStateContext); + const { account, env, decryptedPgpPvtKey } = useContext(ChatAndNotificationPropsContext); - const historyMessages = useCallback(async ({threadHash,limit = 10,}:HistoryMessagesParams) => { + const historyMessages = useCallback( + async ({ threadHash, limit = 10 }: HistoryMessagesParams) => { + setLoading(true); + try { + const chatHistory: IMessageIPFS[] = await PushAPI.chat.history({ + threadhash: threadHash, + account: account, + toDecrypt: decryptedPgpPvtKey ? true : false, + pgpPrivateKey: String(decryptedPgpPvtKey), + limit: limit, + env: env, + }); - setLoading(true); - try { - const chatHistory:IMessageIPFS[] = await PushAPI.chat.history({ - threadhash: threadHash, - account: account, - toDecrypt: decryptedPgpPvtKey ? true : false, - pgpPrivateKey: String(decryptedPgpPvtKey), - limit: limit, - env: env + chatHistory.reverse(); + if (chats.get(selectedChatId as string)) { + const uniqueMap: { [timestamp: number]: IMessageIPFS } = {}; + const messages = Object.values( + [...chatHistory, ...chats.get(selectedChatId as string)!.messages].reduce((uniqueMap, message) => { + if (message.timestamp && !uniqueMap[message.timestamp]) { + uniqueMap[message.timestamp] = message; + } + return uniqueMap; + }, uniqueMap) + ); + setChat(selectedChatId as string, { + messages: messages, + lastThreadHash: chatHistory[0].link, }); - console.log(chatHistory) - chatHistory.reverse(); - if (chats.get(selectedChatId as string)) { - const uniqueMap: { [timestamp: number]: IMessageIPFS } = {}; - const messages = Object.values( - [...chatHistory, ...chats.get(selectedChatId as string)!.messages].reduce((uniqueMap, message) => { - if (message.timestamp && !uniqueMap[message.timestamp]) { - uniqueMap[message.timestamp] = message; - } - return uniqueMap; - }, uniqueMap) - ); - setChat(selectedChatId as string, { - messages: messages, - lastThreadHash: chatHistory[0].link - }); - } else { - setChat(selectedChatId as string, { messages: chatHistory, lastThreadHash: chatHistory[0].link }); - } - } catch (error: Error | any) { - setLoading(false); - setError(error.message); - console.log(error); - } finally { - setLoading(false); - } - }, [chats]); + } else { + setChat(selectedChatId as string, { messages: chatHistory, lastThreadHash: chatHistory[0].link }); + } + } catch (error: Error | any) { + setLoading(false); + setError(error.message); + console.log(error); + } finally { + setLoading(false); + } + }, + [chats] + ); return { historyMessages, error, loading }; }; -export default useFetchHistoryMessages -; +export default useFetchHistoryMessages; diff --git a/packages/uiweb/src/lib/icons/Emoji.tsx b/packages/uiweb/src/lib/icons/Emoji.tsx deleted file mode 100644 index 748bceb64..000000000 --- a/packages/uiweb/src/lib/icons/Emoji.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import React from 'react'; - -type EmojiIconsProps = { - color?: string; -} - -export const EmojiIcon: React.FC = ({color="#494D5F"}) => { - return ( - - - - - - - - ); -}; \ No newline at end of file diff --git a/packages/uiweb/src/lib/icons/PushIcons.tsx b/packages/uiweb/src/lib/icons/PushIcons.tsx index d12c93de7..a9ffea5e4 100644 --- a/packages/uiweb/src/lib/icons/PushIcons.tsx +++ b/packages/uiweb/src/lib/icons/PushIcons.tsx @@ -182,3 +182,69 @@ export const CancelCircleIcon: React.FC = ({ size, color }) => { ); }; + +// ------ +// CATEGORY - REACTION & EMOJI +// ------ +// Emoji Icon +export const EmojiCircleIcon: React.FC = ({ size, color }) => { + return ( + + + + + + + ); +}; + +// Reply Icon +export const ReplyIcon: React.FC = ({ size, color }) => { + return ( + + + + + + + + ); +}; diff --git a/packages/uiweb/src/lib/types/index.ts b/packages/uiweb/src/lib/types/index.ts index d46024476..42e51afb3 100644 --- a/packages/uiweb/src/lib/types/index.ts +++ b/packages/uiweb/src/lib/types/index.ts @@ -1,4 +1,4 @@ -import type { IFeeds, ParsedResponseType, PushAPI, Rules } from '@pushprotocol/restapi'; +import type { IFeeds, IMessageIPFSWithCID, ParsedResponseType, PushAPI, Rules } from '@pushprotocol/restapi'; import { Bytes, TypedDataDomain, TypedDataField, providers } from 'ethers'; import type { ReactElement } from 'react'; import type { ENV } from '../config'; @@ -215,3 +215,26 @@ export interface IFrame { frameDetails?: FrameDetails; message?: string; } + +export interface IReactionsForChatMessages { + [key: string]: IMessageIPFSWithCID[]; // key is the message CID, value is an array of reactions +} + +export type WalletType = { + name: string; + url: string; +}; + +export type AppMetaDataType = { + name: string; + logo: string; + icon: string; + description: string; + recommendedInjectedWallets: WalletType[]; +}; +export type ChainType = { + id: string; // Assuming all IDs are in hexadecimal string format + token: string; + label: string; + rpcUrl: string; +}; diff --git a/packages/uiweb/tsconfig.json b/packages/uiweb/tsconfig.json index 961fa8169..36346f9e0 100644 --- a/packages/uiweb/tsconfig.json +++ b/packages/uiweb/tsconfig.json @@ -12,7 +12,11 @@ "noPropertyAccessFromIndexSignature": false, // "isolatedModules": true, "noImplicitReturns": true, - "noFallthroughCasesInSwitch": true + "noFallthroughCasesInSwitch": true, + "paths": { + "@components/*": ["src/lib/components/*"], + "@icons/*": ["src/lib/icons/*"] + } }, "files": [], "include": [], diff --git a/packages/uiweb/yarn.lock b/packages/uiweb/yarn.lock index 733887673..b5bec417a 100644 --- a/packages/uiweb/yarn.lock +++ b/packages/uiweb/yarn.lock @@ -1791,6 +1791,7 @@ __metadata: "@web3-onboard/walletconnect": "npm:^2.4.6" "@web3-react/injected-connector": "npm:^6.0.7" animejs: "npm:^2.2.0" + babel-plugin-module-resolver: "npm:^5.0.2" classnames: "npm:^2.2.5" date-fns: "npm:^2.28.0" emoji-picker-react: "npm:^4.4.9" @@ -3357,6 +3358,19 @@ __metadata: languageName: node linkType: hard +"babel-plugin-module-resolver@npm:^5.0.2": + version: 5.0.2 + resolution: "babel-plugin-module-resolver@npm:5.0.2" + dependencies: + find-babel-config: "npm:^2.1.1" + glob: "npm:^9.3.3" + pkg-up: "npm:^3.1.0" + reselect: "npm:^4.1.7" + resolve: "npm:^1.22.8" + checksum: 10c0/ccbb9e673c4219f68937349267521becb72be292cf30bf70b861c3e709d24fbfa589da0bf6c100a0def799d38199299171cb6eac3fb00b1ea740373e2c1fe54c + languageName: node + linkType: hard + "balanced-match@npm:^1.0.0": version: 1.0.2 resolution: "balanced-match@npm:1.0.2" @@ -4483,6 +4497,25 @@ __metadata: languageName: node linkType: hard +"find-babel-config@npm:^2.1.1": + version: 2.1.1 + resolution: "find-babel-config@npm:2.1.1" + dependencies: + json5: "npm:^2.2.3" + path-exists: "npm:^4.0.0" + checksum: 10c0/fb1f348027b83e9fe3b4163ec9c45719adab1205b2807708d660fe2a7beb5f812e0fabb6b21746cf0ef9c9fbf67bcc8e37f6cddb9d43229f0524767522d6110b + languageName: node + linkType: hard + +"find-up@npm:^3.0.0": + version: 3.0.0 + resolution: "find-up@npm:3.0.0" + dependencies: + locate-path: "npm:^3.0.0" + checksum: 10c0/2c2e7d0a26db858e2f624f39038c74739e38306dee42b45f404f770db357947be9d0d587f1cac72d20c114deb38aa57316e879eb0a78b17b46da7dab0a3bd6e3 + languageName: node + linkType: hard + "find-up@npm:^4.1.0": version: 4.1.0 resolution: "find-up@npm:4.1.0" @@ -4535,6 +4568,13 @@ __metadata: languageName: node linkType: hard +"fs.realpath@npm:^1.0.0": + version: 1.0.0 + resolution: "fs.realpath@npm:1.0.0" + checksum: 10c0/444cf1291d997165dfd4c0d58b69f0e4782bfd9149fd72faa4fe299e68e0e93d6db941660b37dd29153bf7186672ececa3b50b7e7249477b03fdf850f287c948 + languageName: node + linkType: hard + "fsevents@npm:~2.3.2": version: 2.3.3 resolution: "fsevents@npm:2.3.3" @@ -4554,6 +4594,13 @@ __metadata: languageName: node linkType: hard +"function-bind@npm:^1.1.2": + version: 1.1.2 + resolution: "function-bind@npm:1.1.2" + checksum: 10c0/d8680ee1e5fcd4c197e4ac33b2b4dce03c71f4d91717292785703db200f5c21f977c568d28061226f9b5900cbcd2c84463646134fd5337e7925e0942bc3f46d5 + languageName: node + linkType: hard + "get-caller-file@npm:^2.0.1": version: 2.0.5 resolution: "get-caller-file@npm:2.0.5" @@ -4616,6 +4663,18 @@ __metadata: languageName: node linkType: hard +"glob@npm:^9.3.3": + version: 9.3.5 + resolution: "glob@npm:9.3.5" + dependencies: + fs.realpath: "npm:^1.0.0" + minimatch: "npm:^8.0.2" + minipass: "npm:^4.2.4" + path-scurry: "npm:^1.6.1" + checksum: 10c0/2f6c2b9ee019ee21dc258ae97a88719614591e4c979cb4580b1b9df6f0f778a3cb38b4bdaf18dfa584637ea10f89a3c5f2533a5e449cf8741514ad18b0951f2e + languageName: node + linkType: hard + "globalyzer@npm:0.1.0": version: 0.1.0 resolution: "globalyzer@npm:0.1.0" @@ -4665,6 +4724,15 @@ __metadata: languageName: node linkType: hard +"hasown@npm:^2.0.0": + version: 2.0.2 + resolution: "hasown@npm:2.0.2" + dependencies: + function-bind: "npm:^1.1.2" + checksum: 10c0/3769d434703b8ac66b209a4cca0737519925bbdb61dd887f93a16372b14694c63ff4e797686d87c90f08168e81082248b9b028bad60d4da9e0d1148766f56eb9 + languageName: node + linkType: hard + "hast-util-parse-selector@npm:^2.0.0": version: 2.2.5 resolution: "hast-util-parse-selector@npm:2.2.5" @@ -4909,6 +4977,15 @@ __metadata: languageName: node linkType: hard +"is-core-module@npm:^2.13.0": + version: 2.13.1 + resolution: "is-core-module@npm:2.13.1" + dependencies: + hasown: "npm:^2.0.0" + checksum: 10c0/2cba9903aaa52718f11c4896dabc189bab980870aae86a62dc0d5cedb546896770ee946fb14c84b7adf0735f5eaea4277243f1b95f5cefa90054f92fbcac2518 + languageName: node + linkType: hard + "is-decimal@npm:^1.0.0": version: 1.0.4 resolution: "is-decimal@npm:1.0.4" @@ -5123,6 +5200,15 @@ __metadata: languageName: node linkType: hard +"json5@npm:^2.2.3": + version: 2.2.3 + resolution: "json5@npm:2.2.3" + bin: + json5: lib/cli.js + checksum: 10c0/5a04eed94810fa55c5ea138b2f7a5c12b97c3750bc63d11e511dcecbfef758003861522a070c2272764ee0f4e3e323862f386945aeb5b85b87ee43f084ba586c + languageName: node + linkType: hard + "keccak@npm:^3.0.3": version: 3.0.4 resolution: "keccak@npm:3.0.4" @@ -5246,6 +5332,16 @@ __metadata: languageName: node linkType: hard +"locate-path@npm:^3.0.0": + version: 3.0.0 + resolution: "locate-path@npm:3.0.0" + dependencies: + p-locate: "npm:^3.0.0" + path-exists: "npm:^3.0.0" + checksum: 10c0/3db394b7829a7fe2f4fbdd25d3c4689b85f003c318c5da4052c7e56eed697da8f1bce5294f685c69ff76e32cba7a33629d94396976f6d05fb7f4c755c5e2ae8b + languageName: node + linkType: hard + "locate-path@npm:^5.0.0": version: 5.0.0 resolution: "locate-path@npm:5.0.0" @@ -5520,6 +5616,15 @@ __metadata: languageName: node linkType: hard +"minimatch@npm:^8.0.2": + version: 8.0.4 + resolution: "minimatch@npm:8.0.4" + dependencies: + brace-expansion: "npm:^2.0.1" + checksum: 10c0/a0a394c356dd5b4cb7f821720841a82fa6f07c9c562c5b716909d1b6ec5e56a7e4c4b5029da26dd256b7d2b3a3f38cbf9ddd8680e887b9b5282b09c05501c1ca + languageName: node + linkType: hard + "minimatch@npm:^9.0.1": version: 9.0.4 resolution: "minimatch@npm:9.0.4" @@ -5589,6 +5694,13 @@ __metadata: languageName: node linkType: hard +"minipass@npm:^4.2.4": + version: 4.2.8 + resolution: "minipass@npm:4.2.8" + checksum: 10c0/4ea76b030d97079f4429d6e8a8affd90baf1b6a1898977c8ccce4701c5a2ba2792e033abc6709373f25c2c4d4d95440d9d5e9464b46b7b76ca44d2ce26d939ce + languageName: node + linkType: hard + "minipass@npm:^5.0.0": version: 5.0.0 resolution: "minipass@npm:5.0.0" @@ -5883,7 +5995,7 @@ __metadata: languageName: node linkType: hard -"p-limit@npm:^2.2.0": +"p-limit@npm:^2.0.0, p-limit@npm:^2.2.0": version: 2.3.0 resolution: "p-limit@npm:2.3.0" dependencies: @@ -5892,6 +6004,15 @@ __metadata: languageName: node linkType: hard +"p-locate@npm:^3.0.0": + version: 3.0.0 + resolution: "p-locate@npm:3.0.0" + dependencies: + p-limit: "npm:^2.0.0" + checksum: 10c0/7b7f06f718f19e989ce6280ed4396fb3c34dabdee0df948376483032f9d5ec22fdf7077ec942143a75827bb85b11da72016497fc10dac1106c837ed593969ee8 + languageName: node + linkType: hard + "p-locate@npm:^4.1.0": version: 4.1.0 resolution: "p-locate@npm:4.1.0" @@ -5931,6 +6052,13 @@ __metadata: languageName: node linkType: hard +"path-exists@npm:^3.0.0": + version: 3.0.0 + resolution: "path-exists@npm:3.0.0" + checksum: 10c0/17d6a5664bc0a11d48e2b2127d28a0e58822c6740bde30403f08013da599182289c56518bec89407e3f31d3c2b6b296a4220bc3f867f0911fee6952208b04167 + languageName: node + linkType: hard + "path-exists@npm:^4.0.0": version: 4.0.0 resolution: "path-exists@npm:4.0.0" @@ -5952,6 +6080,13 @@ __metadata: languageName: node linkType: hard +"path-parse@npm:^1.0.7": + version: 1.0.7 + resolution: "path-parse@npm:1.0.7" + checksum: 10c0/11ce261f9d294cc7a58d6a574b7f1b935842355ec66fba3c3fd79e0f036462eaf07d0aa95bb74ff432f9afef97ce1926c720988c6a7451d8a584930ae7de86e1 + languageName: node + linkType: hard + "path-scurry@npm:^1.10.2": version: 1.10.2 resolution: "path-scurry@npm:1.10.2" @@ -5962,6 +6097,16 @@ __metadata: languageName: node linkType: hard +"path-scurry@npm:^1.6.1": + version: 1.11.1 + resolution: "path-scurry@npm:1.11.1" + dependencies: + lru-cache: "npm:^10.2.0" + minipass: "npm:^5.0.0 || ^6.0.2 || ^7.0.0" + checksum: 10c0/32a13711a2a505616ae1cc1b5076801e453e7aae6ac40ab55b388bb91b9d0547a52f5aaceff710ea400205f18691120d4431e520afbe4266b836fadede15872d + languageName: node + linkType: hard + "pathe@npm:^1.1.1, pathe@npm:^1.1.2": version: 1.1.2 resolution: "pathe@npm:1.1.2" @@ -6053,6 +6198,15 @@ __metadata: languageName: node linkType: hard +"pkg-up@npm:^3.1.0": + version: 3.1.0 + resolution: "pkg-up@npm:3.1.0" + dependencies: + find-up: "npm:^3.0.0" + checksum: 10c0/ecb60e1f8e1f611c0bdf1a0b6a474d6dfb51185567dc6f29cdef37c8d480ecba5362e006606bb290519bbb6f49526c403fabea93c3090c20368d98bb90c999ab + languageName: node + linkType: hard + "pngjs@npm:^5.0.0": version: 5.0.0 resolution: "pngjs@npm:5.0.0" @@ -6487,6 +6641,39 @@ __metadata: languageName: node linkType: hard +"reselect@npm:^4.1.7": + version: 4.1.8 + resolution: "reselect@npm:4.1.8" + checksum: 10c0/06a305a504affcbb67dd0561ddc8306b35796199c7e15b38934c80606938a021eadcf68cfd58e7bb5e17786601c37602a3362a4665c7bf0a96c1041ceee9d0b7 + languageName: node + linkType: hard + +"resolve@npm:^1.22.8": + version: 1.22.8 + resolution: "resolve@npm:1.22.8" + dependencies: + is-core-module: "npm:^2.13.0" + path-parse: "npm:^1.0.7" + supports-preserve-symlinks-flag: "npm:^1.0.0" + bin: + resolve: bin/resolve + checksum: 10c0/07e179f4375e1fd072cfb72ad66d78547f86e6196c4014b31cb0b8bb1db5f7ca871f922d08da0fbc05b94e9fd42206f819648fa3b5b873ebbc8e1dc68fec433a + languageName: node + linkType: hard + +"resolve@patch:resolve@npm%3A^1.22.8#optional!builtin": + version: 1.22.8 + resolution: "resolve@patch:resolve@npm%3A1.22.8#optional!builtin::version=1.22.8&hash=c3c19d" + dependencies: + is-core-module: "npm:^2.13.0" + path-parse: "npm:^1.0.7" + supports-preserve-symlinks-flag: "npm:^1.0.0" + bin: + resolve: bin/resolve + checksum: 10c0/0446f024439cd2e50c6c8fa8ba77eaa8370b4180f401a96abf3d1ebc770ac51c1955e12764cde449fde3fff480a61f84388e3505ecdbab778f4bef5f8212c729 + languageName: node + linkType: hard + "retry@npm:^0.12.0": version: 0.12.0 resolution: "retry@npm:0.12.0" @@ -6880,6 +7067,13 @@ __metadata: languageName: node linkType: hard +"supports-preserve-symlinks-flag@npm:^1.0.0": + version: 1.0.0 + resolution: "supports-preserve-symlinks-flag@npm:1.0.0" + checksum: 10c0/6c4032340701a9950865f7ae8ef38578d8d7053f5e10518076e6554a9381fa91bd9c6850193695c141f32b21f979c985db07265a758867bac95de05f7d8aeb39 + languageName: node + linkType: hard + "svelte-i18n@npm:^3.3.13": version: 3.7.4 resolution: "svelte-i18n@npm:3.7.4"