From d4572e609e65b1298b73b75013de9728317c465d Mon Sep 17 00:00:00 2001
From: HadiKhai
Date: Fri, 6 Dec 2024 15:02:26 +0200
Subject: [PATCH 1/2] feat: xmtp refactor and unread count
---
.../NotificationBadge.module.css | 27 +
.../ui/src/lib/ui/NotificationBadge/index.tsx | 30 +
packages/@justweb3/ui/src/lib/ui/index.ts | 1 +
.../stories/ui/notification-badge.stories.tsx | 57 ++
.../lib/components/JustWeb3Button/index.tsx | 35 +-
.../lib/components/AllMessageSheet/index.tsx | 56 --
.../src/lib/components/Chat/index.tsx | 618 -----------------
.../{ChatButton => ChatMenuButton}/index.tsx | 48 +-
.../ChatSheet/Chat/ChatHeader/index.tsx | 157 +++++
.../ChatMessagesList/DateDivider/index.tsx | 43 ++
.../ChatMessagesList/EmojiSelector/index.tsx | 85 +++
.../ChatMessagesList}/MessageCard/index.tsx | 50 +-
.../ChatSheet/Chat/ChatMessagesList/index.tsx | 102 +++
.../Chat/ChatReactionOverlay/index.tsx | 29 +
.../Chat/ChatRequestControl/index.tsx | 47 ++
.../ChatTextField/AttachmentButtons/index.tsx | 45 ++
.../ChatTextField/AttachmentPreview/index.tsx | 220 ++++++
.../Chat/ChatTextField/MessageInput/index.tsx | 72 ++
.../Chat/ChatTextField/ReplyPreview/index.tsx | 184 +++++
.../VoiceNoteRecording/index.tsx | 115 ++++
.../ChatSheet/Chat/ChatTextField/index.tsx | 225 ++++++
.../MessageSkeletonCard/index.tsx | 0
.../Chat/LoadingMessagesList/index.tsx | 25 +
.../Chat/VideoPlayerPreview/index.tsx | 119 ++++
.../Chat/VoiceNotePreview}/index.tsx | 48 +-
.../lib/components/ChatSheet/Chat/index.tsx | 257 +++++++
.../NewChat/NewChatTextField/index.tsx | 68 ++
.../NewChat}/index.tsx | 15 +-
.../src/lib/components/ChatSheet/index.tsx | 213 ++----
.../src/lib/components/CustomPlayer/index.tsx | 106 ---
.../components/CustomVoicePreview/index.tsx | 86 ---
.../lib/components/EmojiSelector/index.tsx | 82 ---
.../ChatList}/MessageItem/index.tsx | 87 ++-
.../{ => InboxSheet}/ChatList/index.tsx | 24 +-
.../src/lib/components/InboxSheet/index.tsx | 274 ++++++++
.../src/lib/components/MessageSheet/index.tsx | 29 -
.../lib/components/MessageTextField/index.tsx | 651 ------------------
.../lib/components/NewMessageSheet/index.tsx | 37 -
.../index.tsx | 4 +-
.../src/lib/content-types/readReceipt.ts | 120 ++++
.../lib/hooks/useGetAudioDuration/index.tsx | 71 +-
.../src/lib/hooks/useReadReceipt/index.tsx | 14 +
.../src/lib/hooks/useSendMessage/index.ts | 52 +-
.../xmtp-plugin/src/lib/plugins/index.tsx | 8 +-
.../providers/JustWeb3XMTPProvider/index.tsx | 279 +++++++-
45 files changed, 2900 insertions(+), 2015 deletions(-)
create mode 100644 packages/@justweb3/ui/src/lib/ui/NotificationBadge/NotificationBadge.module.css
create mode 100644 packages/@justweb3/ui/src/lib/ui/NotificationBadge/index.tsx
create mode 100644 packages/@justweb3/ui/src/stories/ui/notification-badge.stories.tsx
delete mode 100644 packages/@justweb3/xmtp-plugin/src/lib/components/AllMessageSheet/index.tsx
delete mode 100644 packages/@justweb3/xmtp-plugin/src/lib/components/Chat/index.tsx
rename packages/@justweb3/xmtp-plugin/src/lib/components/{ChatButton => ChatMenuButton}/index.tsx (64%)
create mode 100644 packages/@justweb3/xmtp-plugin/src/lib/components/ChatSheet/Chat/ChatHeader/index.tsx
create mode 100644 packages/@justweb3/xmtp-plugin/src/lib/components/ChatSheet/Chat/ChatMessagesList/DateDivider/index.tsx
create mode 100644 packages/@justweb3/xmtp-plugin/src/lib/components/ChatSheet/Chat/ChatMessagesList/EmojiSelector/index.tsx
rename packages/@justweb3/xmtp-plugin/src/lib/components/{ => ChatSheet/Chat/ChatMessagesList}/MessageCard/index.tsx (93%)
create mode 100644 packages/@justweb3/xmtp-plugin/src/lib/components/ChatSheet/Chat/ChatMessagesList/index.tsx
create mode 100644 packages/@justweb3/xmtp-plugin/src/lib/components/ChatSheet/Chat/ChatReactionOverlay/index.tsx
create mode 100644 packages/@justweb3/xmtp-plugin/src/lib/components/ChatSheet/Chat/ChatRequestControl/index.tsx
create mode 100644 packages/@justweb3/xmtp-plugin/src/lib/components/ChatSheet/Chat/ChatTextField/AttachmentButtons/index.tsx
create mode 100644 packages/@justweb3/xmtp-plugin/src/lib/components/ChatSheet/Chat/ChatTextField/AttachmentPreview/index.tsx
create mode 100644 packages/@justweb3/xmtp-plugin/src/lib/components/ChatSheet/Chat/ChatTextField/MessageInput/index.tsx
create mode 100644 packages/@justweb3/xmtp-plugin/src/lib/components/ChatSheet/Chat/ChatTextField/ReplyPreview/index.tsx
create mode 100644 packages/@justweb3/xmtp-plugin/src/lib/components/ChatSheet/Chat/ChatTextField/VoiceNoteRecording/index.tsx
create mode 100644 packages/@justweb3/xmtp-plugin/src/lib/components/ChatSheet/Chat/ChatTextField/index.tsx
rename packages/@justweb3/xmtp-plugin/src/lib/components/{ => ChatSheet/Chat/LoadingMessagesList}/MessageSkeletonCard/index.tsx (100%)
create mode 100644 packages/@justweb3/xmtp-plugin/src/lib/components/ChatSheet/Chat/LoadingMessagesList/index.tsx
create mode 100644 packages/@justweb3/xmtp-plugin/src/lib/components/ChatSheet/Chat/VideoPlayerPreview/index.tsx
rename packages/@justweb3/xmtp-plugin/src/lib/components/{VoiceMessageCard => ChatSheet/Chat/VoiceNotePreview}/index.tsx (81%)
create mode 100644 packages/@justweb3/xmtp-plugin/src/lib/components/ChatSheet/Chat/index.tsx
create mode 100644 packages/@justweb3/xmtp-plugin/src/lib/components/ChatSheet/NewChat/NewChatTextField/index.tsx
rename packages/@justweb3/xmtp-plugin/src/lib/components/{NewConversation => ChatSheet/NewChat}/index.tsx (96%)
delete mode 100644 packages/@justweb3/xmtp-plugin/src/lib/components/CustomPlayer/index.tsx
delete mode 100644 packages/@justweb3/xmtp-plugin/src/lib/components/CustomVoicePreview/index.tsx
delete mode 100644 packages/@justweb3/xmtp-plugin/src/lib/components/EmojiSelector/index.tsx
rename packages/@justweb3/xmtp-plugin/src/lib/components/{ => InboxSheet/ChatList}/MessageItem/index.tsx (56%)
rename packages/@justweb3/xmtp-plugin/src/lib/components/{ => InboxSheet}/ChatList/index.tsx (60%)
create mode 100644 packages/@justweb3/xmtp-plugin/src/lib/components/InboxSheet/index.tsx
delete mode 100644 packages/@justweb3/xmtp-plugin/src/lib/components/MessageSheet/index.tsx
delete mode 100644 packages/@justweb3/xmtp-plugin/src/lib/components/MessageTextField/index.tsx
delete mode 100644 packages/@justweb3/xmtp-plugin/src/lib/components/NewMessageSheet/index.tsx
rename packages/@justweb3/xmtp-plugin/src/lib/components/{ChatWithProfileButton => ProfileChatButton}/index.tsx (96%)
create mode 100644 packages/@justweb3/xmtp-plugin/src/lib/content-types/readReceipt.ts
create mode 100644 packages/@justweb3/xmtp-plugin/src/lib/hooks/useReadReceipt/index.tsx
diff --git a/packages/@justweb3/ui/src/lib/ui/NotificationBadge/NotificationBadge.module.css b/packages/@justweb3/ui/src/lib/ui/NotificationBadge/NotificationBadge.module.css
new file mode 100644
index 00000000..d5c0f5a4
--- /dev/null
+++ b/packages/@justweb3/ui/src/lib/ui/NotificationBadge/NotificationBadge.module.css
@@ -0,0 +1,27 @@
+.notificationIcon {
+ position: relative;
+ display: inline-block;
+
+}
+
+.icon {
+ font-size: 24px; /* Adjust icon size */
+ line-height: 40px; /* Center the icon vertically */
+ text-align: center;
+}
+
+.badge {
+ position: absolute;
+ top: 0;
+ right: 4px;
+ background-color: red;
+ color: white;
+ font-size: 8px;
+ font-weight: bold;
+ line-height: 1;
+ border-radius: 100px;
+ padding: 2px 4px;
+ transform: translate(50%, -50%);
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
+ white-space: nowrap;
+}
diff --git a/packages/@justweb3/ui/src/lib/ui/NotificationBadge/index.tsx b/packages/@justweb3/ui/src/lib/ui/NotificationBadge/index.tsx
new file mode 100644
index 00000000..b83fe06d
--- /dev/null
+++ b/packages/@justweb3/ui/src/lib/ui/NotificationBadge/index.tsx
@@ -0,0 +1,30 @@
+import React from 'react';
+import styles from './NotificationBadge.module.css';
+import { SPAN } from '../Text';
+
+interface NotificationBadgeProps {
+ count: number;
+ icon?: React.ReactNode;
+ maxCount?: number;
+}
+
+export const NotificationBadge: React.FC = ({
+ count,
+ icon,
+ maxCount = 99,
+}) => {
+ return (
+
+ {/*
{icon}
*/}
+ {icon}
+ {/*{count > 0 &&
{count}}*/}
+ {count > 0 && (
+
+ {count > maxCount ? `${maxCount}+` : count}
+
+ )}
+
+ );
+};
+
+export default NotificationBadge;
diff --git a/packages/@justweb3/ui/src/lib/ui/index.ts b/packages/@justweb3/ui/src/lib/ui/index.ts
index 3cd6c64c..53e68816 100644
--- a/packages/@justweb3/ui/src/lib/ui/index.ts
+++ b/packages/@justweb3/ui/src/lib/ui/index.ts
@@ -18,3 +18,4 @@ export * from './Sheet';
export * from './Label';
export * from './Checkbox';
export * from './Skeleton';
+export * from './NotificationBadge';
diff --git a/packages/@justweb3/ui/src/stories/ui/notification-badge.stories.tsx b/packages/@justweb3/ui/src/stories/ui/notification-badge.stories.tsx
new file mode 100644
index 00000000..7477aa44
--- /dev/null
+++ b/packages/@justweb3/ui/src/stories/ui/notification-badge.stories.tsx
@@ -0,0 +1,57 @@
+import { Meta, StoryObj } from '@storybook/react';
+import { NotificationBadge } from '../../lib/ui';
+import React from 'react';
+
+const meta: Meta = {
+ component: NotificationBadge,
+ title: 'Design System/UI/NotificationBadge',
+ tags: ['autodocs'],
+};
+
+export default meta;
+type Story = StoryObj;
+
+const ChatIcon = () => {
+ return (
+
+ );
+};
+
+export const Normal: Story = {
+ args: {
+ count: 1,
+ icon: ,
+ },
+};
+
+export const Zero: Story = {
+ args: {
+ count: 0,
+ icon: ,
+ },
+};
+
+export const Many: Story = {
+ args: {
+ count: 50,
+ icon: ,
+ },
+};
+
+export const ManyWithMax: Story = {
+ args: {
+ count: 1000,
+ icon: ,
+ },
+};
diff --git a/packages/@justweb3/widget/src/lib/components/JustWeb3Button/index.tsx b/packages/@justweb3/widget/src/lib/components/JustWeb3Button/index.tsx
index c16067a0..2c10fd20 100644
--- a/packages/@justweb3/widget/src/lib/components/JustWeb3Button/index.tsx
+++ b/packages/@justweb3/widget/src/lib/components/JustWeb3Button/index.tsx
@@ -35,12 +35,14 @@ import styles from './JustWeb3Button.module.css';
export interface JustWeb3Buttonrops {
children: ReactNode;
+ style?: React.CSSProperties;
logout?: () => void;
}
export const JustWeb3Button: FC = ({
children,
logout,
+ style,
}) => {
const [openMApps, setOpenMApps] = useState(false);
const { plugins, mApps, config } = useContext(JustWeb3Context);
@@ -89,6 +91,9 @@ export const JustWeb3Button: FC = ({
= ({
onClick={() => {
handleOpenSignInDialog(true);
}}
+ style={{
+ ...style,
+ }}
left={}
right={
= ({
style={{
backgroundColor: 'var(--justweb3-background-color)',
color: 'var(--justweb3-primary-color)',
+ ...style,
}}
contentStyle={{
alignItems: 'start',
@@ -187,32 +196,6 @@ export const JustWeb3Button: FC = ({
const connectedEnsProfileContent = (
- {/**/}
- {/* */}
- {/* Profile Overview*/}
- {/*
*/}
- {/* */}
- {/* }*/}
- {/* onClick={() => {*/}
- {/* openEnsProfile(connectedEns?.ens, connectedEns?.chainId);*/}
- {/* }}*/}
- {/* >*/}
- {/* View Full Profile*/}
- {/* */}
- {/**/}
- {/* Profile */}
| string | null;
- openChat: boolean;
- closeChat: () => void;
- onChangePeer: (
- peer: CachedConversation | string
- ) => void;
-}
-
-export const AllMessageSheet: React.FC = ({
- peer,
- closeChat,
- openChat,
- onChangePeer,
-}) => {
- const isPeerConversation = useMemo(() => {
- return typeof peer !== 'string';
- }, [peer]);
-
- return (
- !open && closeChat()}>
-
-
- {isPeerConversation ? 'Messages' : 'New Conversation'}
-
-
- {peer !== null &&
- (isPeerConversation ? (
- }
- onBack={closeChat}
- />
- ) : (
- {
- onChangePeer(conversation);
- }}
- />
- ))}
-
-
- );
-};
diff --git a/packages/@justweb3/xmtp-plugin/src/lib/components/Chat/index.tsx b/packages/@justweb3/xmtp-plugin/src/lib/components/Chat/index.tsx
deleted file mode 100644
index 3f454e35..00000000
--- a/packages/@justweb3/xmtp-plugin/src/lib/components/Chat/index.tsx
+++ /dev/null
@@ -1,618 +0,0 @@
-import {
- useEnsAvatar,
- useMountedAccount,
- usePrimaryName,
- useRecords,
-} from '@justaname.id/react';
-import {
- ArrowIcon,
- Avatar,
- BlockedAccountIcon,
- Button,
- Flex,
- LoadingSpinner,
- P,
- Popover,
- PopoverContent,
- PopoverTrigger,
- TuneIcon,
-} from '@justweb3/ui';
-import {
- CachedConversation,
- ContentTypeMetadata,
- useCanMessage,
- useConsent,
- useMessages,
- useStreamMessages,
-} from '@xmtp/react-sdk';
-import React, { useEffect, useMemo } from 'react';
-import { useSendReactionMessage } from '../../hooks';
-import { typeLookup } from '../../utils/attachments';
-import {
- filterReactionsMessages,
- MessageWithReaction,
-} from '../../utils/filterReactionsMessages';
-import { formatAddress } from '../../utils/formatAddress';
-import { groupMessagesByDate } from '../../utils/groupMessageByDate';
-import EmojiSelector from '../EmojiSelector';
-import MessageCard from '../MessageCard';
-import { MessageSkeletonCard } from '../MessageSkeletonCard';
-import MessageTextField from '../MessageTextField';
-import { useJustWeb3 } from '@justweb3/widget';
-
-export interface ChatProps {
- conversation: CachedConversation;
- onBack: () => void;
-}
-
-export const Chat: React.FC = ({ conversation, onBack }) => {
- const { openEnsProfile } = useJustWeb3();
- const [replyMessage, setReplyMessage] =
- React.useState(null);
- const [reactionMessage, setReactionMessage] =
- React.useState(null);
- const [isRequest, setIsRequest] = React.useState(false);
- const [isRequestChangeLoading, setIsRequestChangeLoading] =
- React.useState(false);
- const { entries, allow, refreshConsentList, deny } = useConsent();
- const { mutateAsync: sendReaction } = useSendReactionMessage(conversation);
-
- const { primaryName } = usePrimaryName({
- address: conversation.peerAddress as `0x${string}`,
- });
- const { records } = useRecords({
- ens: primaryName,
- });
- const { sanitizeEnsImage } = useEnsAvatar();
-
- const { address } = useMountedAccount();
-
- const [canMessage, setCanMessage] = React.useState(true);
-
- const { messages, isLoading } = useMessages(conversation);
-
- // Queries
-
- const blockAddress = async (peerAddress: string) => {
- setIsRequestChangeLoading(true);
- await refreshConsentList();
- await deny([peerAddress]);
- await refreshConsentList();
- setIsRequestChangeLoading(false);
- onBack();
- };
-
- const { canMessage: canMessageFn, isLoading: isCanMessageLoading } =
- useCanMessage();
-
- useEffect(() => {
- if (isCanMessageLoading) return;
- canMessageFn(conversation.peerAddress).then((result) => {
- setCanMessage(result);
- });
- }, [isCanMessageLoading, conversation, canMessageFn]);
-
- useStreamMessages(conversation);
-
- // Memo
- const filteredMessages = useMemo(() => {
- const messagesWithoutRead = messages.filter(
- (message) => !(message.contentType === 'xmtp.org/readReceipt:1.0')
- );
- const res = filterReactionsMessages(messagesWithoutRead);
- return res;
- }, [messages]);
-
- const groupedMessages = useMemo(() => {
- return groupMessagesByDate(filteredMessages ?? []);
- }, [filteredMessages]);
-
- useEffect(() => {
- const convoConsentState = entries[conversation.peerAddress]?.permissionType;
- if (convoConsentState === 'unknown' || convoConsentState === undefined) {
- setIsRequest(true);
- } else {
- setIsRequest(false);
- }
- }, [entries, conversation.peerAddress]);
-
- const isMessagesSenderOnly = useMemo(() => {
- return filteredMessages.every(
- (message) => message.senderAddress === address
- );
- }, [filteredMessages, address]);
-
- const handleAllowAddress = async () => {
- setIsRequestChangeLoading(true);
- await refreshConsentList();
- await allow([conversation.peerAddress]);
- void refreshConsentList();
- setIsRequest(false);
- setIsRequestChangeLoading(false);
- };
-
- const handleEmojiSelect = (emoji: string) => {
- if (!reactionMessage) return;
- sendReaction({
- action: 'added',
- content: emoji,
- referenceId: reactionMessage.id,
- });
- };
-
- useEffect(() => {
- if (messages.length == 0) return;
- setTimeout(() => {
- const lastMessageId = messages[messages.length - 1]?.id;
- const element = document.getElementById(lastMessageId);
- if (element) {
- element.scrollIntoView({ behavior: 'smooth' });
- }
- }, 500);
-
- // await checkMessageIfRead();
- }, [messages, conversation]);
-
- const isStringContent =
- typeof replyMessage?.content === 'string' ||
- typeof replyMessage?.content?.content === 'string';
-
- const mimeType = replyMessage?.content?.mimeType;
- const type = mimeType ? typeLookup[mimeType.split('/')?.[1]] : null;
-
- const computeHeight = useMemo(() => {
- const additionalHeight = [];
- const height = '100vh - 50px - 3rem - 1.5rem - 73px - 15px';
- if (isRequest) {
- additionalHeight.push('-40px');
- }
- if (replyMessage) {
- if (isStringContent) {
- additionalHeight.push('46px');
- } else if (mimeType === 'audio/wav') {
- additionalHeight.push('61px');
- } else if (type === 'video' || type === 'image') {
- additionalHeight.push('116px');
- } else {
- additionalHeight.push('47px');
- }
- }
-
- if (isMessagesSenderOnly) {
- additionalHeight.push('59px');
- }
-
- return `calc( ${height} ${
- additionalHeight.length > 0 ? ' - ' + additionalHeight.join(' - ') : ''
- } )`;
- }, [
- replyMessage,
- isMessagesSenderOnly,
- isStringContent,
- mimeType,
- type,
- isRequest,
- ]);
-
- return (
-
- {
- setReactionMessage(null);
- const replica = document.getElementById(
- `${reactionMessage?.id}-replica`
- );
- replica?.remove();
- }}
- >
-
-
-
-
-
- {
- if (primaryName) {
- openEnsProfile(primaryName);
- }
- }}
- >
-
-
-
- {primaryName
- ? primaryName
- : formatAddress(conversation.peerAddress)}
-
- {primaryName && (
-
- {formatAddress(conversation.peerAddress)}
-
- )}
-
-
-
-
-
-
-
-
-
-
- blockAddress(conversation.peerAddress)}
- >
-
-
- Block
-
-
-
-
-
-
-
-
- {isCanMessageLoading || isLoading ? (
-
- {[...Array(8)].map((_, index) => (
-
- ))}
-
- ) : (
-
- {canMessage ? (
-
- {groupedMessages &&
- Object.keys(groupedMessages).map((date, index) => (
-
-
-
-
- {date}
-
-
-
- {groupedMessages[date].map((message) => (
- setReplyMessage(msg)}
- message={message}
- peerAddress={conversation.peerAddress}
- key={`message-${message.id}`}
- onReaction={(message) => {
- setReactionMessage(message);
-
- const element = document.getElementById(
- message.id.toString()
- );
- if (!element) return;
- const replica = element?.cloneNode(
- true
- ) as HTMLElement;
- replica.id = `${message.id}-replica`;
- replica.style.position = 'absolute';
- replica.style.bottom = '310px';
- replica.style.minHeight = '20px';
- replica.style.left = '4.2vw';
- replica.style.zIndex = '90';
- element?.parentElement?.appendChild(replica);
- replica.classList.add('replica-animate');
- }}
- />
- ))}
-
- ))}
- {reactionMessage && (
-
- {
- handleEmojiSelect(emoji);
- setReactionMessage(null);
- const replica = document.getElementById(
- `${reactionMessage?.id}-replica`
- );
- replica?.remove();
- }}
- />
-
- )}
-
- ) : (
-
-
Cannot message {conversation.peerAddress}
-
- )}
-
- )}
-
- {isRequest ? (
- isRequestChangeLoading ? (
-
-
-
- ) : (
-
-
-
-
- )
- ) : (
-
- {isMessagesSenderOnly && (
-
-
- Message in user’s Requests
-
-
- This user has not accepted your message request yet
-
-
- )}
- setReplyMessage(null)}
- conversation={conversation}
- replyMessage={replyMessage}
- peerAddress={conversation.peerAddress}
- />
-
- )}
-
-
- );
-};
diff --git a/packages/@justweb3/xmtp-plugin/src/lib/components/ChatButton/index.tsx b/packages/@justweb3/xmtp-plugin/src/lib/components/ChatMenuButton/index.tsx
similarity index 64%
rename from packages/@justweb3/xmtp-plugin/src/lib/components/ChatButton/index.tsx
rename to packages/@justweb3/xmtp-plugin/src/lib/components/ChatMenuButton/index.tsx
index 4c39d872..6e209868 100644
--- a/packages/@justweb3/xmtp-plugin/src/lib/components/ChatButton/index.tsx
+++ b/packages/@justweb3/xmtp-plugin/src/lib/components/ChatMenuButton/index.tsx
@@ -1,15 +1,26 @@
import { useMountedAccount } from '@justaname.id/react';
-import { ArrowIcon, ClickableItem } from '@justweb3/ui';
+import { ArrowIcon, ClickableItem, SPAN } from '@justweb3/ui';
import { Client, ClientOptions, useClient } from '@xmtp/react-sdk';
import { useEthersSigner } from '../../hooks';
import { XmtpEnvironment } from '../../plugins';
import { loadKeys, storeKeys, wipeKeys } from '../../utils/xmtp';
+import { useJustWeb3XMTP } from '../../providers/JustWeb3XMTPProvider';
+import { useMemo } from 'react';
-export interface ChatButtonProps {
+export interface ChatMenuButtonProps {
handleOpen: (open: boolean) => void;
env: XmtpEnvironment;
}
-export const ChatButton: React.FC = ({ handleOpen, env }) => {
+export const ChatMenuButton: React.FC = ({
+ handleOpen,
+ env,
+}) => {
+ const { conversationsInfo } = useJustWeb3XMTP();
+ const totalUnreadCount = useMemo(() => {
+ return conversationsInfo
+ .filter((conversation) => conversation.consent === 'allowed')
+ .reduce((acc, curr) => acc + curr.unreadCount, 0);
+ }, [conversationsInfo]);
const { initialize } = useClient();
const { client } = useClient();
const walletClient = useEthersSigner();
@@ -74,7 +85,36 @@ export const ChatButton: React.FC = ({ handleOpen, env }) => {
width: '100%',
}}
onClick={handleChat}
- right={}
+ right={
+ <>
+ {totalUnreadCount > 0 && (
+
+
+ {totalUnreadCount}
+
+
+ )}
+
+ >
+ }
/>
);
};
diff --git a/packages/@justweb3/xmtp-plugin/src/lib/components/ChatSheet/Chat/ChatHeader/index.tsx b/packages/@justweb3/xmtp-plugin/src/lib/components/ChatSheet/Chat/ChatHeader/index.tsx
new file mode 100644
index 00000000..2d90fed0
--- /dev/null
+++ b/packages/@justweb3/xmtp-plugin/src/lib/components/ChatSheet/Chat/ChatHeader/index.tsx
@@ -0,0 +1,157 @@
+import {
+ ArrowIcon,
+ Avatar,
+ BlockedAccountIcon,
+ Flex,
+ P,
+ Popover,
+ PopoverContent,
+ PopoverTrigger,
+ TuneIcon,
+} from '@justweb3/ui';
+import { formatAddress } from '../../../../utils/formatAddress';
+import { useEnsAvatar } from '@justaname.id/react';
+
+export interface ChatHeaderProps {
+ primaryName: string | undefined;
+ peerAddress: string;
+ onBack: () => void;
+ openEnsProfile: (ens: string) => void;
+ records: any;
+ blockAddressHandler: (peerAddress: string) => void;
+}
+
+export const ChatHeader: React.FC = ({
+ primaryName,
+ peerAddress,
+ onBack,
+ openEnsProfile,
+ records,
+ blockAddressHandler,
+}) => {
+ const { sanitizeEnsImage } = useEnsAvatar();
+
+ return (
+
+
+
+
+
+ {
+ if (primaryName) openEnsProfile(primaryName);
+ }}
+ >
+
+
+
+ {primaryName || formatAddress(peerAddress)}
+
+ {primaryName && (
+
+ {formatAddress(peerAddress)}
+
+ )}
+
+
+
+
+
+
+
+
+
+
+
+ blockAddressHandler(peerAddress)}
+ >
+
+
+ Block
+
+
+
+
+
+
+
+ );
+};
diff --git a/packages/@justweb3/xmtp-plugin/src/lib/components/ChatSheet/Chat/ChatMessagesList/DateDivider/index.tsx b/packages/@justweb3/xmtp-plugin/src/lib/components/ChatSheet/Chat/ChatMessagesList/DateDivider/index.tsx
new file mode 100644
index 00000000..6b0b7307
--- /dev/null
+++ b/packages/@justweb3/xmtp-plugin/src/lib/components/ChatSheet/Chat/ChatMessagesList/DateDivider/index.tsx
@@ -0,0 +1,43 @@
+import { Flex, P } from '@justweb3/ui';
+
+interface DateDividerProps {
+ date: string;
+}
+
+export const DateDivider: React.FC = ({ date }) => (
+
+
+
+ {date}
+
+
+
+);
diff --git a/packages/@justweb3/xmtp-plugin/src/lib/components/ChatSheet/Chat/ChatMessagesList/EmojiSelector/index.tsx b/packages/@justweb3/xmtp-plugin/src/lib/components/ChatSheet/Chat/ChatMessagesList/EmojiSelector/index.tsx
new file mode 100644
index 00000000..4dd8f6ba
--- /dev/null
+++ b/packages/@justweb3/xmtp-plugin/src/lib/components/ChatSheet/Chat/ChatMessagesList/EmojiSelector/index.tsx
@@ -0,0 +1,85 @@
+import { Flex, Input } from '@justweb3/ui';
+import React, { useMemo } from 'react';
+import { useDebounce } from '@justweb3/widget';
+import { EmojiObject, emojis } from '../../../../../utils/emojis';
+
+interface EmojiSelectorProps {
+ onEmojiSelect: (emoji: string) => void;
+}
+
+export const EmojiSelector: React.FC = ({
+ onEmojiSelect,
+}) => {
+ const [searchValue, setSearchValue] = React.useState('');
+
+ const { debouncedValue: debouncedSearch } = useDebounce(
+ searchValue,
+ 50
+ );
+
+ const onEmojiClickHandler = (emoji: EmojiObject) => {
+ onEmojiSelect(emoji.name);
+ };
+
+ const filteredEmojis = useMemo(() => {
+ if (debouncedSearch.length === 0) return emojis;
+ const filteredEmojis = emojis.filter((emoji) =>
+ emoji.name.toLowerCase().includes(debouncedSearch.toLowerCase())
+ );
+ return filteredEmojis;
+ }, [debouncedSearch]);
+
+ return (
+
+ setSearchValue(e.target.value)}
+ />
+
+ {filteredEmojis.map((emoji, index) => (
+
+ ))}
+
+
+ );
+};
+
+export default EmojiSelector;
diff --git a/packages/@justweb3/xmtp-plugin/src/lib/components/MessageCard/index.tsx b/packages/@justweb3/xmtp-plugin/src/lib/components/ChatSheet/Chat/ChatMessagesList/MessageCard/index.tsx
similarity index 93%
rename from packages/@justweb3/xmtp-plugin/src/lib/components/MessageCard/index.tsx
rename to packages/@justweb3/xmtp-plugin/src/lib/components/ChatSheet/Chat/ChatMessagesList/MessageCard/index.tsx
index 0630fb58..8ad5ce6c 100644
--- a/packages/@justweb3/xmtp-plugin/src/lib/components/MessageCard/index.tsx
+++ b/packages/@justweb3/xmtp-plugin/src/lib/components/ChatSheet/Chat/ChatMessagesList/MessageCard/index.tsx
@@ -9,15 +9,15 @@ import {
} from '@justweb3/ui';
import { CachedConversation, DecodedMessage } from '@xmtp/react-sdk';
import React, { useEffect, useMemo, useRef, useState } from 'react';
-import { useSendReactionMessage } from '../../hooks';
-import { typeLookup } from '../../utils/attachments';
-import { calculateFileSize } from '../../utils/calculateFileSize';
-import { findEmojiByName } from '../../utils/emojis';
-import { MessageWithReaction } from '../../utils/filterReactionsMessages';
-import { formatAddress } from '../../utils/formatAddress';
-import { formatMessageSentTime } from '../../utils/messageTimeFormat';
-import { CustomPlayer } from '../CustomPlayer';
-import VoiceMessageCard from '../VoiceMessageCard';
+import { useSendReactionMessage } from '../../../../../hooks';
+import { typeLookup } from '../../../../../utils/attachments';
+import { calculateFileSize } from '../../../../../utils/calculateFileSize';
+import { findEmojiByName } from '../../../../../utils/emojis';
+import { MessageWithReaction } from '../../../../../utils/filterReactionsMessages';
+import { formatAddress } from '../../../../../utils/formatAddress';
+import { formatMessageSentTime } from '../../../../../utils/messageTimeFormat';
+import VoiceNotePreview from '../../VoiceNotePreview';
+import { VideoPlayerPreview } from '../../VideoPlayerPreview';
interface MessageCardProps {
message: MessageWithReaction;
@@ -107,7 +107,7 @@ const MeasureAndHyphenateText: React.FC<{
);
};
-const MessageCard: React.FC = ({
+export const MessageCard: React.FC = ({
message,
peerAddress,
onReply,
@@ -259,17 +259,17 @@ const MessageCard: React.FC = ({
senderAddress: message.senderAddress,
content:
typeLookup[attachmentExtention] === 'image' ||
- typeLookup[attachmentExtention] === 'video'
+ typeLookup[attachmentExtention] === 'video'
? {
- data: message.content.data,
- mimeType: message.content.mimeType,
- filename: message.content.filename,
- url: URL.createObjectURL(
- new Blob([message.content.data], {
- type: message.content.mimeType,
- })
- ),
- }
+ data: message.content.data,
+ mimeType: message.content.mimeType,
+ filename: message.content.filename,
+ url: URL.createObjectURL(
+ new Blob([message.content.data], {
+ type: message.content.mimeType,
+ })
+ ),
+ }
: message.content,
contentType: message.contentType,
})}
@@ -317,7 +317,7 @@ const MessageCard: React.FC = ({
{repliedMessage?.senderAddress === address
? 'YOU'
: primaryName ??
- formatAddress(repliedMessage?.senderAddress ?? '')}
+ formatAddress(repliedMessage?.senderAddress ?? '')}
{isReplyText || isReplyReply ? (
@@ -340,7 +340,7 @@ const MessageCard: React.FC = ({
: repliedMessage.content}
) : isReplyVoice ? (
- = ({
}}
/>
) : typeLookup[replyAttachmentExtention] === 'video' ? (
- = ({
) : (
{isVoice ? (
-
+
) : (
{typeLookup[attachmentExtention] === 'image' ? (
@@ -469,7 +469,7 @@ const MessageCard: React.FC = ({
) : typeLookup[attachmentExtention] === 'video' ? (
- ;
+ setReplyMessage: (msg: MessageWithReaction | null) => void;
+ setReactionMessage: (msg: MessageWithReaction | null) => void;
+ reactionMessage: MessageWithReaction | null;
+ handleEmojiSelect: (emoji: string) => void;
+ computeHeight: string;
+}
+
+export const ChatMessagesList: React.FC = ({
+ canMessage,
+ groupedMessages,
+ conversation,
+ setReplyMessage,
+ setReactionMessage,
+ reactionMessage,
+ handleEmojiSelect,
+ computeHeight,
+}) => {
+ if (!canMessage) {
+ return (
+
+ Cannot message {conversation.peerAddress}
+
+ );
+ }
+
+ return (
+
+
+ {groupedMessages &&
+ Object.keys(groupedMessages).map((date, index) => (
+
+
+ {groupedMessages[date].map((message) => (
+ {
+ setReactionMessage(msg);
+ const element = document.getElementById(msg.id);
+ if (!element) return;
+ const replica = element.cloneNode(true) as HTMLElement;
+ replica.id = `${msg.id}-replica`;
+ replica.style.position = 'absolute';
+ replica.style.bottom = '310px';
+ replica.style.minHeight = '20px';
+ replica.style.left = '4.2vw';
+ replica.style.zIndex = '90';
+ element.parentElement?.appendChild(replica);
+ replica.classList.add('replica-animate');
+ }}
+ />
+ ))}
+
+ ))}
+
+ {reactionMessage && (
+
+
+
+ )}
+
+
+ );
+};
diff --git a/packages/@justweb3/xmtp-plugin/src/lib/components/ChatSheet/Chat/ChatReactionOverlay/index.tsx b/packages/@justweb3/xmtp-plugin/src/lib/components/ChatSheet/Chat/ChatReactionOverlay/index.tsx
new file mode 100644
index 00000000..46ede4df
--- /dev/null
+++ b/packages/@justweb3/xmtp-plugin/src/lib/components/ChatSheet/Chat/ChatReactionOverlay/index.tsx
@@ -0,0 +1,29 @@
+import { MessageWithReaction } from '../../../utils/filterReactionsMessages';
+
+interface ChatReactionOverlayProps {
+ reactionMessage: MessageWithReaction | null;
+ onOverlayClick: () => void;
+}
+
+export const ChatReactionOverlay: React.FC = ({
+ reactionMessage,
+ onOverlayClick,
+}) => (
+
+);
diff --git a/packages/@justweb3/xmtp-plugin/src/lib/components/ChatSheet/Chat/ChatRequestControl/index.tsx b/packages/@justweb3/xmtp-plugin/src/lib/components/ChatSheet/Chat/ChatRequestControl/index.tsx
new file mode 100644
index 00000000..26d1497b
--- /dev/null
+++ b/packages/@justweb3/xmtp-plugin/src/lib/components/ChatSheet/Chat/ChatRequestControl/index.tsx
@@ -0,0 +1,47 @@
+import { Button, Flex, LoadingSpinner } from '@justweb3/ui';
+
+interface ChatRequestControlsProps {
+ isRequestChangeLoading: boolean;
+ blockAddressHandler: (peerAddress: string) => void;
+ peerAddress: string;
+ handleAllowAddress: () => void;
+}
+
+export const ChatRequestControls: React.FC = ({
+ isRequestChangeLoading,
+ blockAddressHandler,
+ peerAddress,
+ handleAllowAddress,
+}) => {
+ if (isRequestChangeLoading) {
+ return (
+
+
+
+ );
+ }
+
+ return (
+
+
+
+
+ );
+};
diff --git a/packages/@justweb3/xmtp-plugin/src/lib/components/ChatSheet/Chat/ChatTextField/AttachmentButtons/index.tsx b/packages/@justweb3/xmtp-plugin/src/lib/components/ChatSheet/Chat/ChatTextField/AttachmentButtons/index.tsx
new file mode 100644
index 00000000..5958e761
--- /dev/null
+++ b/packages/@justweb3/xmtp-plugin/src/lib/components/ChatSheet/Chat/ChatTextField/AttachmentButtons/index.tsx
@@ -0,0 +1,45 @@
+// ChatTextField/AttachmentButtons.tsx
+import React, { RefObject } from 'react';
+import { AddFolderIcon, AddImageIcon, AddVideoIcon, Flex } from '@justweb3/ui';
+
+export interface AttachmentButtonsProps {
+ onButtonClick: (contentType: 'image' | 'video' | 'application') => void;
+ acceptedTypes: string | string[] | undefined;
+ onAttachmentChange: (e: React.ChangeEvent) => void;
+}
+
+export const AttachmentButtons: React.FC<
+ AttachmentButtonsProps & { inputFileRef: RefObject }
+> = ({ onButtonClick, acceptedTypes, onAttachmentChange, inputFileRef }) => (
+
+ onButtonClick('image')}
+ style={{ cursor: 'pointer' }}
+ />
+ onButtonClick('video')}
+ style={{ cursor: 'pointer' }}
+ />
+ onButtonClick('application')}
+ style={{ cursor: 'pointer' }}
+ />
+
+
+);
diff --git a/packages/@justweb3/xmtp-plugin/src/lib/components/ChatSheet/Chat/ChatTextField/AttachmentPreview/index.tsx b/packages/@justweb3/xmtp-plugin/src/lib/components/ChatSheet/Chat/ChatTextField/AttachmentPreview/index.tsx
new file mode 100644
index 00000000..71abf76e
--- /dev/null
+++ b/packages/@justweb3/xmtp-plugin/src/lib/components/ChatSheet/Chat/ChatTextField/AttachmentPreview/index.tsx
@@ -0,0 +1,220 @@
+// ChatTextField/AttachmentPreview.tsx
+import React from 'react';
+import {
+ Button,
+ DocumentIcon,
+ Flex,
+ P,
+ SendIcon,
+ StopIcon,
+} from '@justweb3/ui';
+import { VideoPlayerPreview } from '../../VideoPlayerPreview';
+import { VoiceNoteRecording } from './../VoiceNoteRecording';
+import { typeLookup } from '../../../../../utils/attachments';
+import { Attachment } from '@xmtp/content-type-remote-attachment';
+
+export interface AttachmentPreviewProps {
+ attachment: Attachment | undefined;
+ attachmentPreview: string | undefined;
+ disabled?: boolean;
+ onCancelAttachment: () => void;
+ onSendAttachment: () => void;
+ recording: boolean;
+ recordingValue: string | null;
+ stopRecording: () => void;
+ pause: () => void;
+ reset: () => void;
+}
+
+export const AttachmentPreview: React.FC = ({
+ attachment,
+ attachmentPreview,
+ disabled,
+ onCancelAttachment,
+ onSendAttachment,
+ recording,
+ recordingValue,
+ stopRecording,
+ pause,
+ reset,
+}) => {
+ const attachmentExtention = attachment?.mimeType.split('/')?.[1] || '';
+
+ if (attachmentPreview) {
+ return (
+
+ {attachment?.mimeType !== 'audio/wav' && (
+
+ )}
+ {attachment?.mimeType === 'audio/wav' ? (
+
+ ) : (
+
+ {typeLookup[attachmentExtention] === 'image' ? (
+
+ ) : typeLookup[attachmentExtention] === 'video' ? (
+
+ ) : (
+
+
+
+ {attachment?.filename ?? 'Cannot preview'}
+
+
+ )}
+
+
+
+
+
+ )}
+ {
+ if (!disabled) onSendAttachment();
+ }}
+ />
+
+ );
+ } else if (recording) {
+ return (
+
+
+ {recordingValue}
+
+
+ RECORDING...
+
+ {
+ stopRecording();
+ pause();
+ reset();
+ }}
+ />
+
+ );
+ }
+
+ return null;
+};
diff --git a/packages/@justweb3/xmtp-plugin/src/lib/components/ChatSheet/Chat/ChatTextField/MessageInput/index.tsx b/packages/@justweb3/xmtp-plugin/src/lib/components/ChatSheet/Chat/ChatTextField/MessageInput/index.tsx
new file mode 100644
index 00000000..c9c61261
--- /dev/null
+++ b/packages/@justweb3/xmtp-plugin/src/lib/components/ChatSheet/Chat/ChatTextField/MessageInput/index.tsx
@@ -0,0 +1,72 @@
+import React from 'react';
+import { Input, MicIcon, SendIcon } from '@justweb3/ui';
+
+interface MessageInputProps {
+ replyMessage: boolean;
+ disabled?: boolean;
+ messageValue: string;
+ setMessageValue: (value: string) => void;
+ handleSendMessage: () => void;
+ startRecording: () => void;
+ start: () => void;
+}
+
+export const MessageInput: React.FC = ({
+ replyMessage,
+ disabled,
+ messageValue,
+ setMessageValue,
+ handleSendMessage,
+ startRecording,
+ start,
+}) => {
+ return (
+ {
+ if (disabled) return;
+ startRecording();
+ start();
+ }}
+ />
+ )
+ }
+ right={
+
+ }
+ disabled={disabled}
+ onKeyDown={(e) => {
+ if (e.key === 'Enter') {
+ handleSendMessage();
+ }
+ }}
+ onChange={(e) => setMessageValue(e.target.value)}
+ />
+ );
+};
diff --git a/packages/@justweb3/xmtp-plugin/src/lib/components/ChatSheet/Chat/ChatTextField/ReplyPreview/index.tsx b/packages/@justweb3/xmtp-plugin/src/lib/components/ChatSheet/Chat/ChatTextField/ReplyPreview/index.tsx
new file mode 100644
index 00000000..7445ea5a
--- /dev/null
+++ b/packages/@justweb3/xmtp-plugin/src/lib/components/ChatSheet/Chat/ChatTextField/ReplyPreview/index.tsx
@@ -0,0 +1,184 @@
+// ChatTextField/ReplyPreview.tsx
+import React, { useMemo } from 'react';
+import { CloseIcon, DocumentIcon, Flex, P } from '@justweb3/ui';
+import VoiceNotePreview from '../../VoiceNotePreview';
+import { VideoPlayerPreview } from '../../VideoPlayerPreview';
+import { formatAddress } from '../../../../../utils/formatAddress';
+import { typeLookup } from '../../../../../utils/attachments';
+import { MessageWithReaction } from '../../../../../utils/filterReactionsMessages';
+
+export interface ReplyPreviewProps {
+ replyMessage: MessageWithReaction | null;
+ onCancelReply: () => void;
+ isSender: boolean;
+ primaryName: string | null | undefined;
+ navigateToRepliedMessage: () => void;
+}
+
+export const ReplyPreview: React.FC = ({
+ replyMessage,
+ onCancelReply,
+ isSender,
+ primaryName,
+ navigateToRepliedMessage,
+}) => {
+ const isReplyText = useMemo(() => {
+ if (!replyMessage) return false;
+ return typeof replyMessage.content === 'string';
+ }, [replyMessage]);
+
+ const isReplyReply = useMemo(() => {
+ if (!replyMessage) return false;
+ return !!replyMessage.content.reference;
+ }, [replyMessage]);
+
+ const isReplyVoice = useMemo(
+ () => replyMessage?.content.mimeType === 'audio/wav',
+ [replyMessage]
+ );
+
+ const replyAttachmentExtension = useMemo(() => {
+ if (!isReplyText && replyMessage && !isReplyReply) {
+ return replyMessage.content.mimeType.split('/')?.[1] || '';
+ }
+ }, [isReplyText, replyMessage, isReplyReply]);
+
+ const isReplyVideoOrImage = useMemo(() => {
+ return (
+ typeLookup[replyAttachmentExtension] === 'image' ||
+ typeLookup[replyAttachmentExtension] === 'video'
+ );
+ }, [replyAttachmentExtension]);
+
+ if (!replyMessage) return null;
+
+ return (
+
+
+
+ {isSender
+ ? 'YOU'
+ : primaryName ?? formatAddress(replyMessage.senderAddress)}
+
+ {isReplyText || isReplyReply ? (
+
+ {isReplyReply ? replyMessage.content.content : replyMessage.content}
+
+ ) : isReplyVoice ? (
+
+ ) : typeLookup[replyAttachmentExtension] === 'image' ? (
+
+ ) : typeLookup[replyAttachmentExtension] === 'video' ? (
+
+ ) : (
+
+
+
+ {replyMessage.content.filename}
+
+
+ )}
+
+
+
+ );
+};
diff --git a/packages/@justweb3/xmtp-plugin/src/lib/components/ChatSheet/Chat/ChatTextField/VoiceNoteRecording/index.tsx b/packages/@justweb3/xmtp-plugin/src/lib/components/ChatSheet/Chat/ChatTextField/VoiceNoteRecording/index.tsx
new file mode 100644
index 00000000..33cf8749
--- /dev/null
+++ b/packages/@justweb3/xmtp-plugin/src/lib/components/ChatSheet/Chat/ChatTextField/VoiceNoteRecording/index.tsx
@@ -0,0 +1,115 @@
+import { CloseIcon, Flex, P, PauseIcon, PlayIcon } from '@justweb3/ui';
+import React, { useEffect, useRef, useState } from 'react';
+import { formatTime } from '../../../../../utils/formatVoiceTime';
+import { useGetAudioDuration } from '../../../../../hooks/useGetAudioDuration';
+
+interface VoiceNoteRecordingProps {
+ audioUrl: string;
+ onCancel: () => void;
+}
+
+export const VoiceNoteRecording: React.FC = ({
+ audioUrl,
+ onCancel,
+}) => {
+ const [playing, setPlaying] = useState(false);
+ const [currentTime, setCurrentTime] = useState(0);
+ const audioRef = useRef(new Audio());
+
+ const audioDuration = useGetAudioDuration(audioUrl);
+
+ useEffect(() => {
+ const audio = new Audio();
+ audioRef.current = audio;
+
+ const onTimeUpdate = () => {
+ setCurrentTime(audio.currentTime);
+ };
+
+ const onEnded = () => {
+ setPlaying(false);
+ setCurrentTime(0);
+ };
+
+ audio.src = audioUrl;
+ audio.addEventListener('timeupdate', onTimeUpdate);
+ audio.addEventListener('ended', onEnded);
+
+ return () => {
+ audio.removeEventListener('timeupdate', onTimeUpdate);
+ audio.removeEventListener('ended', onEnded);
+ };
+ }, [audioUrl]);
+
+ const handlePlayPause = () => {
+ const audio = audioRef.current;
+ if (!audio) return;
+
+ if (playing) {
+ audio.pause();
+ } else {
+ audio.play();
+ }
+ setPlaying(!playing);
+ };
+
+ return (
+
+ {playing ? (
+
+ ) : (
+
+ )}
+
+ {playing || currentTime > 0
+ ? formatTime(currentTime)
+ : formatTime(audioDuration ?? 0)}
+
+
+
+ );
+};
+
+export default VoiceNoteRecording;
diff --git a/packages/@justweb3/xmtp-plugin/src/lib/components/ChatSheet/Chat/ChatTextField/index.tsx b/packages/@justweb3/xmtp-plugin/src/lib/components/ChatSheet/Chat/ChatTextField/index.tsx
new file mode 100644
index 00000000..6698b6ab
--- /dev/null
+++ b/packages/@justweb3/xmtp-plugin/src/lib/components/ChatSheet/Chat/ChatTextField/index.tsx
@@ -0,0 +1,225 @@
+// ChatTextField/index.tsx
+import React, {
+ Dispatch,
+ SetStateAction,
+ useEffect,
+ useMemo,
+ useRef,
+ useState,
+} from 'react';
+import {
+ CachedConversation,
+ ContentTypeMetadata,
+ useClient,
+} from '@xmtp/react-sdk';
+import { useMountedAccount, usePrimaryName } from '@justaname.id/react';
+import { Flex, P } from '@justweb3/ui';
+import { AttachmentButtons } from './AttachmentButtons';
+import { ReplyPreview } from './ReplyPreview';
+import { AttachmentPreview } from './AttachmentPreview';
+import { MessageInput } from './MessageInput';
+import {
+ useAttachmentChange,
+ useRecordingTimer,
+ useRecordVoice,
+ useSendAttachment,
+ useSendMessages,
+ useSendReplyMessage,
+} from '../../../../hooks';
+import { AttachmentType, typeLookup } from '../../../../utils/attachments';
+import type { Attachment } from '@xmtp/content-type-remote-attachment';
+import { MessageWithReaction } from '../../../../utils/filterReactionsMessages';
+
+export interface ChatTextFieldProps {
+ isMessagesSenderOnly: boolean;
+ replyMessage: MessageWithReaction | null;
+ conversation: CachedConversation;
+ peerAddress: string;
+ onCancelReply: () => void;
+ disabled?: boolean;
+ style?: React.CSSProperties;
+}
+
+export const ChatTextField: React.FC = ({
+ isMessagesSenderOnly,
+ replyMessage,
+ conversation,
+ peerAddress,
+ onCancelReply,
+ disabled,
+ style,
+}) => {
+ const [messageValue, setMessageValue] = useState('');
+ const [attachment, setAttachment] = useState();
+ const [attachmentPreview, setAttachmentPreview] = useState<
+ string | undefined
+ >();
+ const { client } = useClient();
+ const { address } = useMountedAccount();
+ const { mutateAsync: sendMessage } = useSendMessages(conversation);
+ const { mutateAsync: sendReply } = useSendReplyMessage(conversation);
+ const { mutateAsync: sendAttachment } = useSendAttachment(conversation);
+ const { primaryName } = usePrimaryName({
+ address: peerAddress as `0x${string}`,
+ });
+
+ const [acceptedTypes, setAcceptedTypes]: [
+ string | string[] | undefined,
+ Dispatch>
+ ] = useState();
+ const inputFile = useRef(null);
+
+ const { onAttachmentChange } = useAttachmentChange({
+ setAttachment,
+ setAttachmentPreview,
+ });
+
+ const { recording, startRecording, stopRecording } = useRecordVoice({
+ setAttachment,
+ setAttachmentPreview,
+ });
+ const { start, pause, reset, recordingValue } = useRecordingTimer({
+ stopRecording,
+ status: recording ? 'recording' : 'idle',
+ });
+
+ const handleSendMessage = async () => {
+ if (!client || disabled || messageValue.length === 0) return;
+
+ if (replyMessage) {
+ await sendReply({
+ message: messageValue,
+ referenceId: replyMessage.id,
+ });
+ onCancelReply && onCancelReply();
+ } else {
+ await sendMessage(messageValue);
+ }
+
+ setMessageValue('');
+ };
+
+ const handleSendAttachment = async () => {
+ if (!client || !attachment || disabled) return;
+ await sendAttachment(attachment);
+ setMessageValue('');
+ setAttachment(undefined);
+ setAttachmentPreview(undefined);
+ };
+
+ const handleCancelAttachment = () => {
+ setAttachment(undefined);
+ setAttachmentPreview(undefined);
+ };
+
+ const onButtonClick = (contentType: AttachmentType) => {
+ if (contentType === 'application') {
+ setAcceptedTypes('all');
+ } else {
+ const acceptedFileTypeList = Object.keys(typeLookup).reduce(
+ (acc: string[], key: string) => {
+ if (typeLookup[key] === contentType) acc.push(`.${key}`);
+ return acc;
+ },
+ []
+ );
+ setAcceptedTypes([...acceptedFileTypeList]);
+ }
+ };
+
+ const isSender = useMemo(
+ () => address === replyMessage?.senderAddress,
+ [replyMessage, address]
+ );
+
+ const navigateToRepliedMessage = () => {
+ if (!replyMessage) return;
+ const element = document.getElementById(replyMessage.id.toString());
+ if (element) {
+ element.scrollIntoView({
+ block: 'end',
+ behavior: 'smooth',
+ });
+ }
+ };
+
+ useEffect(() => {
+ if (acceptedTypes) {
+ inputFile?.current?.click();
+ }
+ }, [acceptedTypes]);
+
+ return (
+
+ {isMessagesSenderOnly && (
+
+
+ Message in user’s Requests
+
+
+ This user has not accepted your message request yet
+
+
+ )}
+
+
+
+
+
+
+
+
+
+ {!attachmentPreview && !recording && (
+
+ )}
+
+
+
+ );
+};
diff --git a/packages/@justweb3/xmtp-plugin/src/lib/components/MessageSkeletonCard/index.tsx b/packages/@justweb3/xmtp-plugin/src/lib/components/ChatSheet/Chat/LoadingMessagesList/MessageSkeletonCard/index.tsx
similarity index 100%
rename from packages/@justweb3/xmtp-plugin/src/lib/components/MessageSkeletonCard/index.tsx
rename to packages/@justweb3/xmtp-plugin/src/lib/components/ChatSheet/Chat/LoadingMessagesList/MessageSkeletonCard/index.tsx
diff --git a/packages/@justweb3/xmtp-plugin/src/lib/components/ChatSheet/Chat/LoadingMessagesList/index.tsx b/packages/@justweb3/xmtp-plugin/src/lib/components/ChatSheet/Chat/LoadingMessagesList/index.tsx
new file mode 100644
index 00000000..5a65dcdc
--- /dev/null
+++ b/packages/@justweb3/xmtp-plugin/src/lib/components/ChatSheet/Chat/LoadingMessagesList/index.tsx
@@ -0,0 +1,25 @@
+import { Flex } from '@justweb3/ui';
+import { MessageSkeletonCard } from './MessageSkeletonCard';
+
+interface LoadingMessagesListProps {
+ computeHeight: string;
+}
+
+export const LoadingMessagesList: React.FC = ({
+ computeHeight,
+}) => (
+
+ {Array.from({ length: 8 }).map((_, index) => (
+
+ ))}
+
+);
diff --git a/packages/@justweb3/xmtp-plugin/src/lib/components/ChatSheet/Chat/VideoPlayerPreview/index.tsx b/packages/@justweb3/xmtp-plugin/src/lib/components/ChatSheet/Chat/VideoPlayerPreview/index.tsx
new file mode 100644
index 00000000..dd4de5d0
--- /dev/null
+++ b/packages/@justweb3/xmtp-plugin/src/lib/components/ChatSheet/Chat/VideoPlayerPreview/index.tsx
@@ -0,0 +1,119 @@
+import { DownloadIcon, Flex, PauseIcon, PlayIcon } from '@justweb3/ui';
+import React, { useEffect, useRef, useState } from 'react';
+
+export interface VideoPlayerPreviewProps {
+ style?: React.CSSProperties;
+ url?: string;
+ disabled?: boolean;
+ fileName?: string;
+}
+
+export const VideoPlayerPreview: React.FC = ({
+ style,
+ url = '',
+ disabled,
+ fileName,
+}) => {
+ const [playing, setPlaying] = React.useState(false);
+ const videoRef = useRef(null);
+ const [hovered, setHovered] = useState(false);
+
+ const togglePlay = () => {
+ if (disabled) return;
+ if (videoRef.current) {
+ if (playing) {
+ videoRef.current.pause();
+ } else {
+ videoRef.current.play();
+ }
+ setPlaying(!playing);
+ }
+ };
+
+ useEffect(() => {
+ const handleVisibilityChange = () => {
+ if (document.hidden && playing && videoRef.current) {
+ videoRef.current.pause();
+ }
+ };
+ document.addEventListener('visibilitychange', handleVisibilityChange);
+ return () => {
+ document.removeEventListener('visibilitychange', handleVisibilityChange);
+ if (videoRef.current && playing) {
+ videoRef.current.pause();
+ setPlaying(false);
+ }
+ };
+ }, [playing]);
+
+ return (
+ {
+ setHovered(true);
+ }}
+ onMouseLeave={() => {
+ setHovered(false);
+ }}
+ onClick={togglePlay}
+ style={{
+ aspectRatio: '16/9',
+ position: 'relative',
+ // TODO: check background color
+ background: 'var(--justweb3-background-color)',
+ cursor: 'pointer',
+ borderRadius: '10px',
+ border: '1px solid var(--justweb3-primary-color)',
+ ...style,
+ }}
+ >
+
+ {hovered && playing && (
+
+ )}
+ {!playing && (
+
+
+ {!!fileName && (
+ {
+ e.stopPropagation();
+ }}
+ style={{
+ transform: 'translateY(2px)',
+ }}
+ href={url}
+ download={fileName}
+ >
+
+
+ )}
+
+ )}
+
+ );
+};
diff --git a/packages/@justweb3/xmtp-plugin/src/lib/components/VoiceMessageCard/index.tsx b/packages/@justweb3/xmtp-plugin/src/lib/components/ChatSheet/Chat/VoiceNotePreview/index.tsx
similarity index 81%
rename from packages/@justweb3/xmtp-plugin/src/lib/components/VoiceMessageCard/index.tsx
rename to packages/@justweb3/xmtp-plugin/src/lib/components/ChatSheet/Chat/VoiceNotePreview/index.tsx
index 0664a88b..819df525 100644
--- a/packages/@justweb3/xmtp-plugin/src/lib/components/VoiceMessageCard/index.tsx
+++ b/packages/@justweb3/xmtp-plugin/src/lib/components/ChatSheet/Chat/VoiceNotePreview/index.tsx
@@ -2,18 +2,18 @@ import { Flex, P, PauseIcon, PlayIcon } from '@justweb3/ui';
import * as Slider from '@radix-ui/react-slider';
import { DecodedMessage } from '@xmtp/xmtp-js';
import React, { useEffect, useMemo, useRef, useState } from 'react';
-import useGetAudioDuration from '../../hooks/useGetAudioDuration';
-import { MessageWithReaction } from '../../utils/filterReactionsMessages';
-import { formatTime } from '../../utils/formatVoiceTime';
+import { formatTime } from '../../../../utils/formatVoiceTime';
+import { useGetAudioDuration } from '../../../../hooks/useGetAudioDuration';
+import { MessageWithReaction } from '../../../../utils/filterReactionsMessages';
-interface VoiceMessageCardProps {
+interface VoiceNotePreviewProps {
message: MessageWithReaction | DecodedMessage;
style?: React.CSSProperties;
disabled?: boolean;
isReceiver: boolean;
}
-const VoiceMessageCard: React.FC = ({
+const VoiceNotePreview: React.FC = ({
message,
style,
disabled,
@@ -115,12 +115,11 @@ const VoiceMessageCard: React.FC = ({
width="22"
height="22"
fill={
- disabled ?
- 'var(--justweb3-primary-color)'
- :
- isReceiver
- ? 'var(--justweb3-primary-color)'
- : 'var(--justweb3-foreground-color-4)'
+ disabled
+ ? 'var(--justweb3-primary-color)'
+ : isReceiver
+ ? 'var(--justweb3-primary-color)'
+ : 'var(--justweb3-foreground-color-4)'
}
style={{
cursor: 'pointer',
@@ -193,19 +192,26 @@ const VoiceMessageCard: React.FC = ({
aria-label="Volume"
/>
-
- {playing || currentTime > 0 ? formatTime(currentTime) : formatTime(duration ?? 0)}
-
+
+
+ {playing || currentTime > 0
+ ? formatTime(currentTime)
+ : formatTime(duration ?? 0)}
+
);
};
-export default VoiceMessageCard;
+export default VoiceNotePreview;
diff --git a/packages/@justweb3/xmtp-plugin/src/lib/components/ChatSheet/Chat/index.tsx b/packages/@justweb3/xmtp-plugin/src/lib/components/ChatSheet/Chat/index.tsx
new file mode 100644
index 00000000..de637057
--- /dev/null
+++ b/packages/@justweb3/xmtp-plugin/src/lib/components/ChatSheet/Chat/index.tsx
@@ -0,0 +1,257 @@
+import React, { useEffect, useMemo, useState } from 'react';
+import {
+ useMountedAccount,
+ usePrimaryName,
+ useRecords,
+} from '@justaname.id/react';
+import { Flex } from '@justweb3/ui';
+import {
+ CachedConversation,
+ ContentTypeMetadata,
+ useCanMessage,
+ useConsent,
+ useMessages,
+ useStreamMessages,
+} from '@xmtp/react-sdk';
+import { useJustWeb3 } from '@justweb3/widget';
+import { ChatTextField } from './ChatTextField';
+import { ChatMessagesList } from './ChatMessagesList';
+import { LoadingMessagesList } from './LoadingMessagesList';
+import { ChatRequestControls } from './ChatRequestControl';
+import { ChatHeader } from './ChatHeader';
+import { ChatReactionOverlay } from './ChatReactionOverlay';
+import {
+ filterReactionsMessages,
+ MessageWithReaction,
+} from '../../../utils/filterReactionsMessages';
+import { useSendReactionMessage } from '../../../hooks';
+import { groupMessagesByDate } from '../../../utils/groupMessageByDate';
+import { typeLookup } from '../../../utils/attachments';
+import { ContentTypeReadReceipt } from '@xmtp/content-type-read-receipt';
+import { useReadReceipt } from '../../../hooks/useReadReceipt';
+
+export interface ChatProps {
+ conversation: CachedConversation;
+ onBack: () => void;
+}
+
+export const Chat: React.FC = ({ conversation, onBack }) => {
+ const { openEnsProfile } = useJustWeb3();
+ const [replyMessage, setReplyMessage] = useState(
+ null
+ );
+ const [reactionMessage, setReactionMessage] =
+ useState(null);
+ const [isRequest, setIsRequest] = useState(false);
+ const [isRequestChangeLoading, setIsRequestChangeLoading] =
+ useState(false);
+
+ const { entries, allow, refreshConsentList, deny } = useConsent();
+ const { mutateAsync: sendReaction } = useSendReactionMessage(conversation);
+ const { primaryName } = usePrimaryName({
+ address: conversation.peerAddress as `0x${string}`,
+ });
+ const { records } = useRecords({ ens: primaryName });
+ const { address } = useMountedAccount();
+ const [canMessage, setCanMessage] = useState(true);
+ const { messages, isLoading } = useMessages(conversation);
+ const { canMessage: canMessageFn, isLoading: isCanMessageLoading } =
+ useCanMessage();
+ const { mutateAsync: readReceipt, isPending: isReadReceiptSending } =
+ useReadReceipt(conversation);
+ useStreamMessages(conversation);
+
+ useEffect(() => {
+ const lastMessage = messages[messages.length - 1];
+
+ if (!lastMessage || isReadReceiptSending) return;
+
+ if (lastMessage?.contentType === ContentTypeReadReceipt.toString()) {
+ return;
+ }
+
+ readReceipt();
+ }, [messages, readReceipt, isReadReceiptSending]);
+
+ // Determine if user can message
+ useEffect(() => {
+ if (isCanMessageLoading) return;
+ canMessageFn(conversation.peerAddress).then(setCanMessage);
+ }, [isCanMessageLoading, conversation, canMessageFn]);
+
+ // Check if conversation is in "request" state
+ useEffect(() => {
+ const convoConsentState = entries[conversation.peerAddress]?.permissionType;
+ setIsRequest(
+ convoConsentState === 'unknown' || convoConsentState === undefined
+ );
+ }, [entries, conversation.peerAddress]);
+
+ // Scroll to last message when messages change
+ useEffect(() => {
+ if (messages.length === 0) return;
+ setTimeout(() => {
+ const lastMessageId = messages[messages.length - 1]?.id;
+ const element = document.getElementById(lastMessageId);
+ if (element) {
+ element.scrollIntoView({ behavior: 'smooth' });
+ }
+ }, 500);
+ }, [messages, conversation]);
+
+ // Filter out read receipts and group messages by date
+ const filteredMessages = useMemo(() => {
+ const withoutRead = messages.filter(
+ (message) => message.contentType !== 'xmtp.org/readReceipt:1.0'
+ );
+ return filterReactionsMessages(withoutRead);
+ }, [messages]);
+
+ const groupedMessages = useMemo(() => {
+ return groupMessagesByDate(filteredMessages ?? []);
+ }, [filteredMessages]);
+
+ const isMessagesSenderOnly = useMemo(() => {
+ return filteredMessages.every(
+ (message) => message.senderAddress === address
+ );
+ }, [filteredMessages, address]);
+
+ const isStringContent =
+ typeof replyMessage?.content === 'string' ||
+ typeof replyMessage?.content?.content === 'string';
+
+ const mimeType = replyMessage?.content?.mimeType;
+ const type = mimeType ? typeLookup[mimeType.split('/')?.[1]] : null;
+
+ const computeHeight = useMemo(() => {
+ const baseHeight = 'calc(100vh - 50px - 3rem - 1.5rem - 73px - 15px)';
+ const adjustments: string[] = [];
+
+ if (isRequest) adjustments.push('40px');
+ if (replyMessage) {
+ if (isStringContent) {
+ adjustments.push('46px');
+ } else if (mimeType === 'audio/wav') {
+ adjustments.push('61px');
+ } else if (type === 'video' || type === 'image') {
+ adjustments.push('116px');
+ } else {
+ adjustments.push('47px');
+ }
+ }
+ if (isMessagesSenderOnly) adjustments.push('59px');
+
+ if (adjustments.length === 0) return baseHeight;
+ return `${baseHeight}${adjustments.map((val) => ` - ${val}`).join('')}`;
+ }, [
+ replyMessage,
+ isMessagesSenderOnly,
+ isStringContent,
+ mimeType,
+ type,
+ isRequest,
+ ]);
+
+ // Handlers
+ const blockAddressHandler = async (peerAddress: string) => {
+ setIsRequestChangeLoading(true);
+ await refreshConsentList();
+ await deny([peerAddress]);
+ await refreshConsentList();
+ setIsRequestChangeLoading(false);
+ onBack();
+ };
+
+ const handleAllowAddress = async () => {
+ setIsRequestChangeLoading(true);
+ await refreshConsentList();
+ await allow([conversation.peerAddress]);
+ await refreshConsentList();
+ setIsRequest(false);
+ setIsRequestChangeLoading(false);
+ };
+
+ const handleEmojiSelect = (emoji: string) => {
+ if (!reactionMessage) return;
+ sendReaction({
+ action: 'added',
+ content: emoji,
+ referenceId: reactionMessage.id,
+ });
+ setReactionMessage(null);
+ const replica = document.getElementById(`${reactionMessage?.id}-replica`);
+ replica?.remove();
+ };
+
+ return (
+
+ {/* Overlay for reaction */}
+ {
+ setReactionMessage(null);
+ const replica = document.getElementById(
+ `${reactionMessage?.id}-replica`
+ );
+ replica?.remove();
+ }}
+ />
+
+
+
+
+ {isCanMessageLoading || isLoading ? (
+
+ ) : (
+
+ )}
+
+ {isRequest ? (
+
+ ) : (
+ setReplyMessage(null)}
+ />
+ )}
+
+
+ );
+};
diff --git a/packages/@justweb3/xmtp-plugin/src/lib/components/ChatSheet/NewChat/NewChatTextField/index.tsx b/packages/@justweb3/xmtp-plugin/src/lib/components/ChatSheet/NewChat/NewChatTextField/index.tsx
new file mode 100644
index 00000000..81b5e15d
--- /dev/null
+++ b/packages/@justweb3/xmtp-plugin/src/lib/components/ChatSheet/NewChat/NewChatTextField/index.tsx
@@ -0,0 +1,68 @@
+import React, { useState } from 'react';
+import { Flex, Input, LoadingSpinner, SendIcon } from '@justweb3/ui';
+
+interface NewChatTextFieldProps {
+ disabled?: boolean;
+ onNewConvo: (message: string) => void;
+ style?: React.CSSProperties;
+}
+
+export const NewChatTextField: React.FC = ({
+ disabled,
+ onNewConvo,
+ style,
+}) => {
+ const [messageValue, setMessageValue] = useState('');
+ const [isNewMessageLoading, setIsNewMessageLoading] =
+ useState(false);
+
+ const handleSendMessage = async () => {
+ if (messageValue.trim().length === 0 || disabled) return;
+ setIsNewMessageLoading(true);
+ await onNewConvo(messageValue);
+ setIsNewMessageLoading(false);
+ setMessageValue('');
+ };
+
+ return (
+
+ {isNewMessageLoading ? (
+
+
+
+ ) : (
+
+ }
+ onKeyDown={(e) => {
+ if (e.key === 'Enter') {
+ handleSendMessage();
+ }
+ }}
+ onChange={(e) => setMessageValue(e.target.value)}
+ />
+ )}
+
+ );
+};
diff --git a/packages/@justweb3/xmtp-plugin/src/lib/components/NewConversation/index.tsx b/packages/@justweb3/xmtp-plugin/src/lib/components/ChatSheet/NewChat/index.tsx
similarity index 96%
rename from packages/@justweb3/xmtp-plugin/src/lib/components/NewConversation/index.tsx
rename to packages/@justweb3/xmtp-plugin/src/lib/components/ChatSheet/NewChat/index.tsx
index 8190c568..6d8e2df6 100644
--- a/packages/@justweb3/xmtp-plugin/src/lib/components/NewConversation/index.tsx
+++ b/packages/@justweb3/xmtp-plugin/src/lib/components/ChatSheet/NewChat/index.tsx
@@ -8,7 +8,6 @@ import {
useStartConversation,
} from '@xmtp/react-sdk';
import React, { useEffect, useMemo } from 'react';
-import MessageTextField from '../MessageTextField';
import { useDebounce } from '@justweb3/widget';
import {
useMountedAccount,
@@ -25,14 +24,15 @@ import {
P,
VerificationsIcon,
} from '@justweb3/ui';
+import { NewChatTextField } from './NewChatTextField';
-interface NewConversationProps {
+interface NewChatProps {
onChatStarted: (conversation: CachedConversation) => void;
onBack: () => void;
selectedAddress?: string;
}
-const NewConversation: React.FC = ({
+export const NewChat: React.FC = ({
onChatStarted,
onBack,
selectedAddress,
@@ -102,7 +102,7 @@ const NewConversation: React.FC = ({
}
};
- const handleNewConversation = async (message: string) => {
+ const handleNewChat = async (message: string) => {
if (!client) return;
const peerAddress =
isAddressName && !!resolvedAddress ? resolvedAddress : debouncedAddress;
@@ -303,10 +303,9 @@ const NewConversation: React.FC = ({
padding: '0px 1.5rem',
}}
>
- = ({
);
};
-export default NewConversation;
+export default NewChat;
diff --git a/packages/@justweb3/xmtp-plugin/src/lib/components/ChatSheet/index.tsx b/packages/@justweb3/xmtp-plugin/src/lib/components/ChatSheet/index.tsx
index 98960a88..a4c57e5e 100644
--- a/packages/@justweb3/xmtp-plugin/src/lib/components/ChatSheet/index.tsx
+++ b/packages/@justweb3/xmtp-plugin/src/lib/components/ChatSheet/index.tsx
@@ -1,184 +1,55 @@
-import {
- AddIcon,
- Flex,
- Sheet,
- SheetContent,
- SheetTitle,
- Tabs,
- TabsContent,
- TabsList,
- TabsTrigger,
-} from '@justweb3/ui';
-import {
- CachedConversation,
- ContentTypeMetadata,
- useConsent,
- useConversations,
- useStreamAllMessages,
- useStreamConversations,
-} from '@xmtp/react-sdk';
-import React, { useEffect, useMemo } from 'react';
-import { ChatList } from '../ChatList';
+import { CachedConversation, ContentTypeMetadata } from '@xmtp/react-sdk';
+import { Sheet, SheetContent, SheetTitle } from '@justweb3/ui';
+import { Chat } from './Chat';
+import { useMemo } from 'react';
+import { NewChat } from './NewChat';
export interface ChatSheetProps {
- open?: boolean;
- handleOpen?: (open: boolean) => void;
- handleOpenChat: (
- conversation: CachedConversation
+ // peerAddress?: string | null;
+ peer: CachedConversation | string | null;
+ openChat: boolean;
+ closeChat: () => void;
+ onChangePeer: (
+ peer: CachedConversation | string
) => void;
- handleNewChat: () => void;
}
export const ChatSheet: React.FC = ({
- open,
- handleOpen,
- handleOpenChat,
- handleNewChat,
+ peer,
+ closeChat,
+ openChat,
+ onChangePeer,
}) => {
- const [tab, setTab] = React.useState('Chats');
- const { conversations, isLoading } = useConversations();
- const [isConsentListLoading, setIsConsentListLoading] = React.useState(true);
- const { loadConsentList, entries } = useConsent();
-
- const allowedConversations = useMemo(() => {
- return conversations.filter(
- (convo) =>
- entries &&
- entries[convo.peerAddress] &&
- entries[convo.peerAddress]?.permissionType === 'allowed'
- );
- }, [conversations, entries]);
-
- const blockedConversations = useMemo(() => {
- return conversations.filter(
- (convo) =>
- entries &&
- entries[convo.peerAddress] &&
- entries[convo.peerAddress]?.permissionType === 'denied'
- );
- }, [conversations, entries]);
-
- const requestConversations = useMemo(() => {
- return conversations.filter((convo) => {
- if (!entries[convo.peerAddress]) return true;
- return entries[convo.peerAddress]?.permissionType === 'unknown';
- });
- }, [conversations, entries]);
-
- useEffect(() => {
- loadConsentList().then(() => {
- setIsConsentListLoading(false);
- });
- }, [loadConsentList]);
-
- useStreamConversations();
- useStreamAllMessages();
+ const isPeerConversation = useMemo(() => {
+ return typeof peer !== 'string';
+ }, [peer]);
return (
-
-
- Chats
-
-
-
- setTab(value)}
- style={{
- display: 'flex',
- flexDirection: 'column',
- marginBottom: '0px',
- overflow: 'hidden',
- marginTop: '10px',
- flex: '1',
- }}
- >
-
-
- Chats
-
-
- Requests
- {requestConversations.length > 0 && (
-
- {requestConversations.length}
-
- )}
-
-
- Blocked
-
-
- {isLoading || isConsentListLoading ? (
- Loading...
+ !open && closeChat()}>
+
+
+ {isPeerConversation ? 'Messages' : 'New Conversation'}
+
+
+ {peer !== null &&
+ (isPeerConversation ? (
+ }
+ onBack={closeChat}
+ />
) : (
- <>
-
-
-
-
-
-
-
-
-
- >
- )}
-
+ {
+ onChangePeer(conversation);
+ }}
+ />
+ ))}
);
diff --git a/packages/@justweb3/xmtp-plugin/src/lib/components/CustomPlayer/index.tsx b/packages/@justweb3/xmtp-plugin/src/lib/components/CustomPlayer/index.tsx
deleted file mode 100644
index 1ab77a3c..00000000
--- a/packages/@justweb3/xmtp-plugin/src/lib/components/CustomPlayer/index.tsx
+++ /dev/null
@@ -1,106 +0,0 @@
-import { DownloadIcon, Flex, PauseIcon, PlayIcon } from '@justweb3/ui';
-import React, { useEffect, useRef, useState } from 'react';
-
-
-export interface CustomPlayerProps {
- style?: React.CSSProperties;
- url?: string;
- disabled?: boolean;
- fileName?: string;
-}
-
-export const CustomPlayer: React.FC = ({
- style,
- url = '',
- disabled,
- fileName
-}) => {
- const [playing, setPlaying] = React.useState(false);
- const videoRef = useRef(null);
- const [hovered, setHovered] = useState(false)
-
- const togglePlay = () => {
- if (disabled) return;
- if (videoRef.current) {
- if (playing) {
- videoRef.current.pause();
- } else {
- videoRef.current.play();
- }
- setPlaying(!playing);
- }
- };
-
- useEffect(() => {
- const handleVisibilityChange = () => {
- if (document.hidden && playing && videoRef.current) {
- videoRef.current.pause();
- }
- };
- document.addEventListener('visibilitychange', handleVisibilityChange);
- return () => {
- document.removeEventListener('visibilitychange', handleVisibilityChange);
- if (videoRef.current && playing) {
- videoRef.current.pause();
- setPlaying(false);
- }
- };
- }, [playing]);
-
- return (
- { setHovered(true) }}
- onMouseLeave={() => { setHovered(false) }}
- onClick={togglePlay}
- style={{
- aspectRatio: '16/9',
- position: 'relative',
- // TODO: check background color
- background: 'var(--justweb3-background-color)',
- cursor: 'pointer',
- borderRadius: '10px',
- border: '1px solid var(--justweb3-primary-color)',
- ...style
- }}>
-
- {
- (hovered && playing) &&
-
- }
- {
- !playing &&
-
-
- {!!fileName && (
- {
- e.stopPropagation()
- }} style={{
- transform: 'translateY(2px)'
- }} href={url} download={fileName}>
-
- )}
-
- }
-
-
- );
-}
diff --git a/packages/@justweb3/xmtp-plugin/src/lib/components/CustomVoicePreview/index.tsx b/packages/@justweb3/xmtp-plugin/src/lib/components/CustomVoicePreview/index.tsx
deleted file mode 100644
index f90bd51d..00000000
--- a/packages/@justweb3/xmtp-plugin/src/lib/components/CustomVoicePreview/index.tsx
+++ /dev/null
@@ -1,86 +0,0 @@
-import { CloseIcon, Flex, P, PauseIcon, PlayIcon } from '@justweb3/ui';
-import React, { useEffect, useRef, useState } from 'react';
-import useGetAudioDuration from '../../hooks/useGetAudioDuration';
-import { formatTime } from '../../utils/formatVoiceTime';
-
-
-interface CustomVoicePreviewProps {
- audioUrl: string;
- onCancel: () => void;
-}
-
-const CustomVoicePreview: React.FC = ({
- audioUrl,
- onCancel
-}) => {
- const [playing, setPlaying] = useState(false);
- const [currentTime, setCurrentTime] = useState(0);
- const audioRef = useRef(new Audio());
-
- const audioDuration = useGetAudioDuration(audioUrl);
-
- useEffect(() => {
- const audio = new Audio();
- audioRef.current = audio;
-
- const onTimeUpdate = () => {
- setCurrentTime(audio.currentTime);
- };
-
- const onEnded = () => {
- setPlaying(false);
- setCurrentTime(0);
- };
-
- audio.src = audioUrl;
- audio.addEventListener('timeupdate', onTimeUpdate);
- audio.addEventListener('ended', onEnded);
-
- return () => {
- audio.removeEventListener('timeupdate', onTimeUpdate);
- audio.removeEventListener('ended', onEnded);
- };
- }, [audioUrl]);
-
- const handlePlayPause = () => {
- const audio = audioRef.current;
- if (!audio) return;
-
- if (playing) {
- audio.pause();
- } else {
- audio.play();
- }
- setPlaying(!playing);
- };
-
- return (
-
- {playing ?
-
- :
-
- }
- {playing || currentTime > 0 ? formatTime(currentTime) : formatTime(audioDuration ?? 0)}
-
-
- );
-};
-
-export default CustomVoicePreview;
diff --git a/packages/@justweb3/xmtp-plugin/src/lib/components/EmojiSelector/index.tsx b/packages/@justweb3/xmtp-plugin/src/lib/components/EmojiSelector/index.tsx
deleted file mode 100644
index c57959d1..00000000
--- a/packages/@justweb3/xmtp-plugin/src/lib/components/EmojiSelector/index.tsx
+++ /dev/null
@@ -1,82 +0,0 @@
-import { Flex, Input } from '@justweb3/ui';
-import React, { useMemo } from 'react';
-import { EmojiObject, emojis } from '../../utils/emojis';
-import { useDebounced } from '../../hooks';
-
-
-interface EmojiSelectorProps {
- onEmojiSelect: (emoji: string) => void;
-}
-
-const EmojiSelector: React.FC = ({
- onEmojiSelect,
-}) => {
- const [searchValue, setSearchValue] = React.useState("");
-
- const {
- value: debouncedSearch,
- } = useDebounced(searchValue, 50);
-
- const onEmojiClickHandler = (emoji: EmojiObject) => {
- onEmojiSelect(emoji.name);
-
- }
-
- const filteredEmojis = useMemo(() => {
- if (debouncedSearch.length === 0) return emojis;
- const filteredEmojis = emojis.filter(emoji =>
- emoji.name.toLowerCase().includes(debouncedSearch.toLowerCase())
- );
- return filteredEmojis;
- }, [debouncedSearch]);
-
-
- return (
-
- setSearchValue(e.target.value)}
- />
-
- {filteredEmojis.map((emoji, index) => (
-
- ))}
-
-
-
- );
-};
-
-export default EmojiSelector;
diff --git a/packages/@justweb3/xmtp-plugin/src/lib/components/MessageItem/index.tsx b/packages/@justweb3/xmtp-plugin/src/lib/components/InboxSheet/ChatList/MessageItem/index.tsx
similarity index 56%
rename from packages/@justweb3/xmtp-plugin/src/lib/components/MessageItem/index.tsx
rename to packages/@justweb3/xmtp-plugin/src/lib/components/InboxSheet/ChatList/MessageItem/index.tsx
index 19b1bf23..a994b787 100644
--- a/packages/@justweb3/xmtp-plugin/src/lib/components/MessageItem/index.tsx
+++ b/packages/@justweb3/xmtp-plugin/src/lib/components/InboxSheet/ChatList/MessageItem/index.tsx
@@ -1,41 +1,50 @@
import {
attachmentContentTypeConfig,
CachedConversation,
+ CachedMessage,
ContentTypeMetadata,
reactionContentTypeConfig,
replyContentTypeConfig,
useConsent,
- useLastMessage,
useStreamMessages,
} from '@xmtp/react-sdk';
import { useEnsAvatar, useRecords } from '@justaname.id/react';
import { Avatar, Button, Flex, formatText, P, SPAN } from '@justweb3/ui';
import React, { useMemo } from 'react';
-import { formatChatDate } from '../../utils/formatChatDate';
+import { formatChatDate } from '../../../../utils/formatChatDate';
export interface MessageItemProps {
conversation: CachedConversation;
onClick?: () => void;
blocked?: boolean;
primaryName?: string | null;
+ conversationInfo?: {
+ conversationId: string;
+ unreadCount: number;
+ consent: 'allowed' | 'blocked' | 'requested';
+ lastMessage: CachedMessage;
+ };
}
-export const MessageItem: React.FC = ({
+const MessageItem: React.FC = ({
conversation,
onClick,
blocked,
primaryName,
+ conversationInfo,
}) => {
- const lastMessage = useLastMessage(conversation.topic);
useStreamMessages(conversation);
- // const { primaryName } = usePrimaryName({
- // address: conversation.peerAddress as `0x${string}`,
- // });
+ // const { messages } = useMessages(conversation);
const { records } = useRecords({
ens: primaryName || undefined,
});
const { sanitizeEnsImage } = useEnsAvatar();
+ // const unreadMessages = useMemo(() => {
+ // if (!lastMessage) return false;
+ // return lastMessage.contentType !== ContentTypeReadReceipt.toString();
+ // }, [lastMessage]);
+
const { allow, refreshConsentList } = useConsent();
const allowUser = async () => {
@@ -44,7 +53,10 @@ export const MessageItem: React.FC = ({
await refreshConsentList();
};
+ console.log('conversationInfo', conversationInfo);
+
const lastContent = useMemo(() => {
+ const lastMessage = conversationInfo?.lastMessage;
if (!lastMessage) return '';
if (typeof lastMessage.content === 'string') {
@@ -72,7 +84,9 @@ export const MessageItem: React.FC = ({
}
return lastMessage.contentFallback;
- }, [lastMessage]);
+ }, [conversationInfo, conversationInfo?.lastMessage]);
+
+ console.log(lastContent);
return (
= ({
lineHeight: '12px',
}}
>
- {lastMessage
- ? lastMessage.senderAddress !== conversation.peerAddress
+ {conversationInfo?.lastMessage
+ ? conversationInfo?.lastMessage.senderAddress !==
+ conversation.peerAddress
? 'You: '
: ''
: ''}
- {lastMessage
+ {conversationInfo?.lastMessage
? lastContent
? lastContent
: 'No preview available'
@@ -148,11 +163,55 @@ export const MessageItem: React.FC = ({
Unblock
) : (
-
- {lastMessage?.sentAt ? formatChatDate(lastMessage.sentAt) : ''}
-
+
+
+ {conversationInfo?.lastMessage?.sentAt
+ ? formatChatDate(conversationInfo?.lastMessage.sentAt)
+ : ''}
+
+ {!!conversationInfo?.unreadCount &&
+ conversationInfo?.unreadCount > 0 && (
+
+
+ {conversationInfo?.unreadCount}
+
+
+ )}
+
)}
);
};
+
+const MessageItemMemo = React.memo(MessageItem, (prevProps, nextProps) => {
+ return JSON.stringify(prevProps) === JSON.stringify(nextProps);
+});
+
+export { MessageItemMemo as MessageItem };
diff --git a/packages/@justweb3/xmtp-plugin/src/lib/components/ChatList/index.tsx b/packages/@justweb3/xmtp-plugin/src/lib/components/InboxSheet/ChatList/index.tsx
similarity index 60%
rename from packages/@justweb3/xmtp-plugin/src/lib/components/ChatList/index.tsx
rename to packages/@justweb3/xmtp-plugin/src/lib/components/InboxSheet/ChatList/index.tsx
index 3ff94d5f..c9cd170e 100644
--- a/packages/@justweb3/xmtp-plugin/src/lib/components/ChatList/index.tsx
+++ b/packages/@justweb3/xmtp-plugin/src/lib/components/InboxSheet/ChatList/index.tsx
@@ -1,7 +1,11 @@
import { Flex } from '@justweb3/ui';
-import { CachedConversation, ContentTypeMetadata } from '@xmtp/react-sdk';
+import {
+ CachedConversation,
+ CachedMessage,
+ ContentTypeMetadata,
+} from '@xmtp/react-sdk';
import React from 'react';
-import { MessageItem } from '../MessageItem';
+import { MessageItem } from './MessageItem';
import { usePrimaryNameBatch } from '@justaname.id/react';
export interface ChatListProps {
@@ -10,12 +14,19 @@ export interface ChatListProps {
conversation: CachedConversation
) => void;
blockedList?: boolean;
+ conversationsInfo?: {
+ conversationId: string;
+ unreadCount: number;
+ consent: 'allowed' | 'blocked' | 'requested';
+ lastMessage: CachedMessage;
+ }[];
}
export const ChatList: React.FC = ({
conversations,
handleOpenChat,
blockedList,
+ conversationsInfo,
}) => {
const { allPrimaryNames } = usePrimaryNameBatch({
addresses: conversations.map((conversation) => conversation.peerAddress),
@@ -33,6 +44,15 @@ export const ChatList: React.FC = ({
item.conversationId === conversation.topic
+ // )?.unreadCount
+ // }
+ //
+ conversationInfo={conversationsInfo?.find(
+ (item) => item.conversationId === conversation.topic
+ )}
onClick={() => handleOpenChat(conversation)}
key={conversation.topic}
blocked={blockedList}
diff --git a/packages/@justweb3/xmtp-plugin/src/lib/components/InboxSheet/index.tsx b/packages/@justweb3/xmtp-plugin/src/lib/components/InboxSheet/index.tsx
new file mode 100644
index 00000000..c4496252
--- /dev/null
+++ b/packages/@justweb3/xmtp-plugin/src/lib/components/InboxSheet/index.tsx
@@ -0,0 +1,274 @@
+import {
+ AddIcon,
+ Flex,
+ Sheet,
+ SheetContent,
+ SheetTitle,
+ Tabs,
+ TabsContent,
+ TabsList,
+ TabsTrigger,
+} from '@justweb3/ui';
+import {
+ CachedConversation,
+ CachedMessage,
+ ContentTypeMetadata,
+ useConsent,
+ useConversations,
+ useStreamAllMessages,
+ useStreamConversations,
+} from '@xmtp/react-sdk';
+import React, { useEffect, useMemo } from 'react';
+import { ChatList } from './ChatList';
+
+export interface InboxSheetProps {
+ open?: boolean;
+ handleOpen?: (open: boolean) => void;
+ handleOpenChat: (
+ conversation: CachedConversation
+ ) => void;
+ handleNewChat: () => void;
+ onConversationsUpdated: ({
+ allowed,
+ blocked,
+ requested,
+ }: {
+ allowed: CachedConversation[];
+ blocked: CachedConversation[];
+ requested: CachedConversation[];
+ }) => void;
+ allConversations: {
+ allowed: CachedConversation[];
+ blocked: CachedConversation[];
+ requested: CachedConversation[];
+ };
+ conversationsInfo?: {
+ conversationId: string;
+ unreadCount: number;
+ consent: 'allowed' | 'blocked' | 'requested';
+ lastMessage: CachedMessage;
+ }[];
+}
+
+export const InboxSheet: React.FC = ({
+ open,
+ handleOpen,
+ handleOpenChat,
+ handleNewChat,
+ onConversationsUpdated,
+ allConversations,
+ conversationsInfo,
+}) => {
+ const [tab, setTab] = React.useState('Chats');
+ const { conversations, isLoading } = useConversations();
+
+ const [isConsentListLoading, setIsConsentListLoading] = React.useState(true);
+ const { loadConsentList, entries } = useConsent();
+
+ const allowedConversations = useMemo(() => {
+ return conversations.filter(
+ (convo) =>
+ entries &&
+ entries[convo.peerAddress] &&
+ entries[convo.peerAddress]?.permissionType === 'allowed'
+ );
+ }, [conversations, entries]);
+
+ const blockedConversations = useMemo(() => {
+ return conversations.filter(
+ (convo) =>
+ entries &&
+ entries[convo.peerAddress] &&
+ entries[convo.peerAddress]?.permissionType === 'denied'
+ );
+ }, [conversations, entries]);
+
+ const requestConversations = useMemo(() => {
+ return conversations.filter((convo) => {
+ if (!entries[convo.peerAddress]) return true;
+ return entries[convo.peerAddress]?.permissionType === 'unknown';
+ });
+ }, [conversations, entries]);
+
+ useEffect(() => {
+ let _allowedConversations = [] as CachedConversation[];
+ let _blockedConversations = [] as CachedConversation[];
+ let _requestConversations = [] as CachedConversation[];
+
+ if (
+ allowedConversations.some(
+ (convo) =>
+ !allConversations.allowed.some((c) => c.topic === convo.topic)
+ )
+ ) {
+ _allowedConversations = allowedConversations.filter(
+ (convo) =>
+ !allConversations.allowed.some((c) => c.topic === convo.topic)
+ );
+ // onConversationsUpdated([...allConversations, ...newConversations]);
+ }
+
+ if (
+ blockedConversations.some(
+ (convo) =>
+ !allConversations.blocked.some((c) => c.topic === convo.topic)
+ )
+ ) {
+ _blockedConversations = blockedConversations.filter(
+ (convo) =>
+ !allConversations.blocked.some((c) => c.topic === convo.topic)
+ );
+ }
+
+ if (
+ requestConversations.some(
+ (convo) =>
+ !allConversations.requested.some((c) => c.topic === convo.topic)
+ )
+ ) {
+ _requestConversations = requestConversations.filter(
+ (convo) =>
+ !allConversations.requested.some((c) => c.topic === convo.topic)
+ );
+ }
+
+ if (
+ _allowedConversations.length > 0 ||
+ _blockedConversations.length > 0 ||
+ _requestConversations.length > 0
+ ) {
+ onConversationsUpdated({
+ allowed: [...allConversations.allowed, ..._allowedConversations],
+ blocked: [...allConversations.blocked, ..._blockedConversations],
+ requested: [...allConversations.requested, ..._requestConversations],
+ });
+ }
+ }, [
+ allConversations,
+ allowedConversations,
+ blockedConversations,
+ onConversationsUpdated,
+ requestConversations,
+ ]);
+
+ useEffect(() => {
+ loadConsentList().then(() => {
+ setIsConsentListLoading(false);
+ });
+ }, [loadConsentList]);
+
+ useStreamConversations();
+ useStreamAllMessages();
+
+ return (
+
+
+ Chats
+
+
+
+ setTab(value)}
+ style={{
+ display: 'flex',
+ flexDirection: 'column',
+ marginBottom: '0px',
+ overflow: 'hidden',
+ marginTop: '10px',
+ flex: '1',
+ }}
+ >
+
+
+ Chats
+
+
+ Requests
+ {requestConversations.length > 0 && (
+
+ {requestConversations.length}
+
+ )}
+
+
+ Blocked
+
+
+ {isLoading || isConsentListLoading ? (
+ Loading...
+ ) : (
+ <>
+
+
+
+
+
+
+
+
+
+ >
+ )}
+
+
+
+ );
+};
diff --git a/packages/@justweb3/xmtp-plugin/src/lib/components/MessageSheet/index.tsx b/packages/@justweb3/xmtp-plugin/src/lib/components/MessageSheet/index.tsx
deleted file mode 100644
index f39ebfbf..00000000
--- a/packages/@justweb3/xmtp-plugin/src/lib/components/MessageSheet/index.tsx
+++ /dev/null
@@ -1,29 +0,0 @@
-import { CachedConversation, ContentTypeMetadata } from '@xmtp/react-sdk';
-import { Sheet, SheetContent, SheetTitle } from '@justweb3/ui';
-import { Chat } from '../Chat';
-
-export interface MessageSheetProps {
- conversation: CachedConversation | null;
- openChat: boolean;
- handleOpenChat: (
- conversation: CachedConversation | null
- ) => void;
-}
-
-export const MessageSheet: React.FC = ({
- conversation,
- handleOpenChat,
- openChat,
-}) => {
- return (
- !open && handleOpenChat(null)}
- >
-
- Messages
- {conversation && handleOpenChat(null)} />}
-
-
- );
-};
diff --git a/packages/@justweb3/xmtp-plugin/src/lib/components/MessageTextField/index.tsx b/packages/@justweb3/xmtp-plugin/src/lib/components/MessageTextField/index.tsx
deleted file mode 100644
index 6c6acbfc..00000000
--- a/packages/@justweb3/xmtp-plugin/src/lib/components/MessageTextField/index.tsx
+++ /dev/null
@@ -1,651 +0,0 @@
-import type { Attachment } from '@xmtp/content-type-remote-attachment';
-import { CachedConversation, useClient } from '@xmtp/react-sdk';
-import React, {
- Dispatch,
- SetStateAction,
- useEffect,
- useMemo,
- useRef,
- useState,
-} from 'react';
-import { MessageWithReaction } from '../../utils/filterReactionsMessages';
-import { useMountedAccount, usePrimaryName } from '@justaname.id/react';
-import {
- useAttachmentChange,
- useRecordingTimer,
- useRecordVoice,
- useSendAttachment,
- useSendMessages,
- useSendReplyMessage,
-} from '../../hooks';
-import { AttachmentType, typeLookup } from '../../utils/attachments';
-import {
- AddFolderIcon,
- AddImageIcon,
- AddVideoIcon,
- Button,
- CloseIcon,
- DocumentIcon,
- Flex,
- Input,
- LoadingSpinner,
- MicIcon,
- P,
- SendIcon,
- StopIcon,
-} from '@justweb3/ui';
-import { formatAddress } from '../../utils/formatAddress';
-import VoiceMessageCard from '../VoiceMessageCard';
-import { CustomPlayer } from '../CustomPlayer';
-import CustomVoicePreview from '../CustomVoicePreview';
-
-interface MessageTextFieldProps {
- newConvo?: boolean;
- disabled?: boolean;
- conversation?: CachedConversation;
- replyMessage?: MessageWithReaction | null;
- onCancelReply?: () => void;
- onNewConvo?: (message: string) => void;
- peerAddress?: string;
- style?: React.CSSProperties;
-}
-
-const MessageTextField: React.FC = ({
- newConvo,
- disabled,
- replyMessage,
- onCancelReply,
- conversation,
- onNewConvo,
- peerAddress,
- style,
-}) => {
- const [messageValue, setMessageValue] = React.useState('');
- const [attachment, setAttachment] = React.useState();
- const [attachmentPreview, setAttachmentPreview] = React.useState<
- string | undefined
- >();
- const [isNewMessageLoading, setIsNewMessageLoading] =
- React.useState(false);
- // const [selectingAttachment, setSelectingAttachment] = React.useState(false);
- const { client } = useClient();
- const { address } = useMountedAccount();
- const { mutateAsync: sendMessage } = useSendMessages(conversation);
- const { mutateAsync: sendReply } = useSendReplyMessage(conversation);
- const { mutateAsync: sendAttachment } = useSendAttachment(conversation);
-
- const attachmentExtention = useMemo(() => {
- return attachment?.mimeType.split('/')?.[1] || '';
- }, [attachment]);
-
- const { primaryName } = usePrimaryName({
- address: peerAddress as `0x${string}`,
- });
-
- // Attachments
- const [acceptedTypes, setAcceptedTypes]: [
- string | string[] | undefined,
- Dispatch>
- ] = useState();
- const inputFile = useRef(null);
- const { onAttachmentChange } = useAttachmentChange({
- setAttachment,
- setAttachmentPreview,
- onError: (error) => {
- // showToast("error", error);
- },
- });
-
- // Recording
- const { recording, startRecording, stopRecording } = useRecordVoice({
- setAttachment,
- setAttachmentPreview,
- });
- const { start, pause, reset, recordingValue } = useRecordingTimer({
- stopRecording,
- status: recording ? 'recording' : 'idle',
- });
-
- // Sending text message
- const handleSendMessage = async () => {
- if (messageValue.length === 0) return;
- if (!client) return;
- if (disabled) return;
- if (newConvo) {
- setIsNewMessageLoading(true);
- onNewConvo && onNewConvo(messageValue);
- setIsNewMessageLoading(false);
- } else {
- if (replyMessage) {
- sendReply({
- message: messageValue,
- referenceId: replyMessage.id,
- });
- onCancelReply && onCancelReply();
- } else {
- sendMessage(messageValue);
- }
- }
- setMessageValue('');
- };
-
- // Sending attachment
- const handleSendAttachment = async () => {
- if (!client) return;
- if (!attachment) return;
- if (disabled) return;
- if (newConvo) {
- onNewConvo && onNewConvo(messageValue);
- } else {
- sendAttachment(attachment);
- }
- setMessageValue('');
- setAttachment(undefined);
- setAttachmentPreview(undefined);
- // setSelectingAttachment(false);
- };
-
- const handleCancelAttachment = () => {
- setAttachment(undefined);
- setAttachmentPreview(undefined);
- };
-
- const onButtonClick = (contentType: AttachmentType) => {
- if (contentType === 'application') {
- setAcceptedTypes('all');
- } else {
- const acceptedFileTypeList = Object.keys(typeLookup).reduce(
- (acc: string[], key: string) => {
- if (typeLookup[key] === contentType) acc.push(`.${key}`);
- return acc;
- },
- []
- );
- setAcceptedTypes([...acceptedFileTypeList]);
- }
- };
-
- // Reply message
- const isSender = useMemo(() => {
- return address === replyMessage?.senderAddress;
- }, [replyMessage, address]);
- const isReplyVoice = useMemo(() => {
- return replyMessage?.content.mimeType === 'audio/wav';
- }, [replyMessage]);
-
- const isReplyText = useMemo(() => {
- if (!replyMessage) return false;
- return typeof replyMessage.content === 'string';
- }, [replyMessage]);
-
- const isReplyReply = useMemo(() => {
- if (!replyMessage) return false;
- return !!replyMessage.content.reference;
- }, [replyMessage]);
-
- const replyAttachmentExtention = useMemo(() => {
- if (!isReplyText && !!replyMessage && !isReplyReply)
- return replyMessage.content.mimeType.split('/')?.[1] || '';
- }, [isReplyText, replyMessage, isReplyReply]);
-
- const navigateToRepliedMessage = () => {
- if (!replyMessage) return;
- const element = document.getElementById(replyMessage.id.toString());
- if (element) {
- element.scrollIntoView({
- block: 'end',
- behavior: 'smooth',
- });
- }
- };
-
- useEffect(() => {
- if (acceptedTypes) {
- inputFile?.current?.click();
- }
- }, [acceptedTypes]);
-
- const isReplyVideoOrImage = useMemo(() => {
- return (
- typeLookup[replyAttachmentExtention] === 'image' ||
- typeLookup[replyAttachmentExtention] === 'video'
- );
- }, [replyAttachmentExtention]);
-
- return (
-
- {!replyMessage}
- {!newConvo && (
-
- onButtonClick('image')}
- style={{
- cursor: 'pointer',
- }}
- />
- onButtonClick('video')}
- style={{
- cursor: 'pointer',
- }}
- />
- onButtonClick('application')}
- style={{
- cursor: 'pointer',
- }}
- />
-
-
- )}
-
- {replyMessage && (
-
-
-
- {isSender
- ? 'YOU'
- : primaryName ?? formatAddress(replyMessage.senderAddress)}
-
- {isReplyText || isReplyReply ? (
-
- {isReplyReply
- ? replyMessage.content.content
- : replyMessage.content}
-
- ) : isReplyVoice ? (
-
- ) : typeLookup[replyAttachmentExtention] === 'image' ? (
-
- ) : typeLookup[replyAttachmentExtention] === 'video' ? (
-
- ) : (
-
-
-
- {replyMessage.content.filename}
-
-
- )}
-
-
-
- )}
- {attachmentPreview ? (
-
- {attachment?.mimeType !== 'audio/wav' && (
-
- )}
- {attachment?.mimeType === 'audio/wav' ? (
-
- ) : (
-
- {typeLookup[attachmentExtention] === 'image' ? (
-
- ) : typeLookup[attachmentExtention] === 'video' ? (
-
- ) : (
-
-
-
- {attachment?.filename ?? 'Cannot preview'}
-
-
- )}
-
-
-
-
-
- )}
- {
- if (disabled) return;
- handleSendAttachment();
- }}
- />
-
- ) : recording ? (
-
-
- {recordingValue}
-
-
- RECORDING...
-
- {
- stopRecording();
- pause();
- reset();
- }}
- />
-
- ) : isNewMessageLoading ? (
-
-
-
- ) : (
-
{
- if (disabled) return;
- startRecording();
- start();
- }}
- />
- )
- }
- right={
-
{
- if (disabled) return;
- handleSendMessage();
- }}
- />
- }
- disabled={disabled}
- onKeyDown={(e) => {
- if (e.key === 'Enter') {
- handleSendMessage();
- }
- }}
- onChange={(e) => setMessageValue(e.target.value)}
- />
- )}
-
-
- );
-};
-
-export default MessageTextField;
diff --git a/packages/@justweb3/xmtp-plugin/src/lib/components/NewMessageSheet/index.tsx b/packages/@justweb3/xmtp-plugin/src/lib/components/NewMessageSheet/index.tsx
deleted file mode 100644
index 69a14e41..00000000
--- a/packages/@justweb3/xmtp-plugin/src/lib/components/NewMessageSheet/index.tsx
+++ /dev/null
@@ -1,37 +0,0 @@
-import { Sheet, SheetContent, SheetTitle } from '@justweb3/ui';
-import { CachedConversation } from '@xmtp/react-sdk';
-import NewConversation from '../NewConversation';
-
-export interface MessageSheetProps {
- openNewChat: boolean;
- handleOpenNewChat: (open: boolean) => void;
- onChatStarted: (conversation: CachedConversation) => void;
- addressOrEns?: string;
-}
-
-export const NewMessageSheet: React.FC = ({
- handleOpenNewChat,
- openNewChat,
- onChatStarted,
- addressOrEns,
-}) => {
- return (
- !open && handleOpenNewChat(false)}
- >
-
- New Conversation
- handleOpenNewChat(false)}
- selectedAddress={addressOrEns}
- />
-
-
- );
-};
diff --git a/packages/@justweb3/xmtp-plugin/src/lib/components/ChatWithProfileButton/index.tsx b/packages/@justweb3/xmtp-plugin/src/lib/components/ProfileChatButton/index.tsx
similarity index 96%
rename from packages/@justweb3/xmtp-plugin/src/lib/components/ChatWithProfileButton/index.tsx
rename to packages/@justweb3/xmtp-plugin/src/lib/components/ProfileChatButton/index.tsx
index 0e839180..c0965bde 100644
--- a/packages/@justweb3/xmtp-plugin/src/lib/components/ChatWithProfileButton/index.tsx
+++ b/packages/@justweb3/xmtp-plugin/src/lib/components/ProfileChatButton/index.tsx
@@ -8,13 +8,13 @@ import { useEthersSigner } from '../../hooks';
import { loadKeys, storeKeys, wipeKeys } from '../../utils/xmtp';
import { ChainId } from '@justaname.id/sdk';
-export interface ChatWithProfileButtonProps {
+export interface ProfileChatButtonProps {
ens: string;
env: 'local' | 'production' | 'dev';
chainId: ChainId;
}
-export const ChatWithProfileButton: React.FC = ({
+export const ProfileChatButton: React.FC = ({
ens,
env,
chainId,
diff --git a/packages/@justweb3/xmtp-plugin/src/lib/content-types/readReceipt.ts b/packages/@justweb3/xmtp-plugin/src/lib/content-types/readReceipt.ts
new file mode 100644
index 00000000..12c0a2c2
--- /dev/null
+++ b/packages/@justweb3/xmtp-plugin/src/lib/content-types/readReceipt.ts
@@ -0,0 +1,120 @@
+import {
+ ContentTypeReadReceipt,
+ ReadReceiptCodec,
+} from '@xmtp/content-type-read-receipt';
+import { z } from 'zod';
+import { isAfter, parseISO } from 'date-fns';
+import {
+ CachedConversation,
+ ContentTypeConfiguration,
+ ContentTypeMessageProcessor,
+ ContentTypeMetadataValues,
+ getCachedConversationByTopic,
+} from '@xmtp/react-sdk';
+import { Mutex } from 'async-mutex';
+import { ContentTypeId } from '@xmtp/content-type-primitives';
+
+const NAMESPACE = 'readReceipt';
+export type CachedReadReceiptMetadata = {
+ incoming: string | undefined;
+ outgoing: string | undefined;
+};
+/**
+ * Retrieve the read receipt from a cached conversation for the given type
+ *
+ * @param conversation Cached conversation
+ * @returns The read receipt date, or `undefined` if the conversation
+ * has no read receipt for the given type
+ */
+export const getReadReceipt = (
+ conversation: CachedConversation,
+ type: keyof CachedReadReceiptMetadata
+) => {
+ const metadata = conversation?.metadata?.[NAMESPACE] as
+ | CachedReadReceiptMetadata
+ | undefined;
+ const readReceiptType = metadata?.[type];
+ return readReceiptType ? parseISO(readReceiptType) : undefined;
+};
+/**
+ * Check if a cached conversation has a read receipt for the given type
+ *
+ * @param conversation Cached conversation
+ * @returns `true` if the conversation has a read receipt for the given type,
+ * `false` otherwise
+ */
+export const hasReadReceipt = (
+ conversation: CachedConversation,
+ type: keyof CachedReadReceiptMetadata
+) => getReadReceipt(conversation, type) !== undefined;
+const ReadReceiptContentSchema = z.object({}).strict();
+/**
+ * Validate the content of a read receipt message
+ *
+ * @param content Message content
+ * @returns `true` if the content is valid, `false` otherwise
+ */
+const isValidReadReceiptContent = (content: unknown) => {
+ const { success } = ReadReceiptContentSchema.safeParse(content);
+ return success;
+};
+const processReadReceiptMutex = new Mutex();
+/**
+ * Process a read receipt message
+ *
+ * Updates the metadata of its conversation with the timestamp of the
+ * read receipt.
+ */
+export const processReadReceipt: ContentTypeMessageProcessor = async ({
+ client,
+ db,
+ message,
+ conversation,
+ updateConversationMetadata,
+}) => {
+ // ensure that only 1 read receipt message is processed at a time to preserve order
+ await processReadReceiptMutex.runExclusive(async () => {
+ const contentType = ContentTypeId.fromString(message.contentType);
+ // always use the latest conversation from the cache
+ const updatedConversation = await getCachedConversationByTopic(
+ client.address,
+ conversation.topic,
+ db
+ );
+ if (updatedConversation) {
+ const isIncoming = message.senderAddress !== client.address;
+ const readReceiptType = isIncoming ? 'incoming' : 'outgoing';
+ const readReceiptDate = getReadReceipt(
+ updatedConversation,
+ readReceiptType
+ );
+ if (
+ ContentTypeReadReceipt.sameAs(contentType) &&
+ conversation &&
+ isValidReadReceiptContent(message.content) &&
+ // ignore read receipts that are older than the current one
+ (!readReceiptDate || isAfter(message.sentAt, readReceiptDate))
+ ) {
+ const metadata = updatedConversation.metadata?.[NAMESPACE] as
+ | CachedReadReceiptMetadata
+ | undefined;
+ // update conversation metadata with the appropriate read receipt
+ await updateConversationMetadata({
+ ...(metadata ?? {}),
+ [readReceiptType]: message.sentAt.toISOString(),
+ } as ContentTypeMetadataValues);
+ }
+ }
+ });
+};
+export const readReceiptContentTypeConfig: ContentTypeConfiguration = {
+ codecs: [new ReadReceiptCodec()],
+ contentTypes: [ContentTypeReadReceipt.toString()],
+ namespace: NAMESPACE,
+ processors: {
+ [ContentTypeReadReceipt.toString()]: [processReadReceipt],
+ },
+ validators: {
+ [ContentTypeReadReceipt.toString()]: isValidReadReceiptContent,
+ },
+};
diff --git a/packages/@justweb3/xmtp-plugin/src/lib/hooks/useGetAudioDuration/index.tsx b/packages/@justweb3/xmtp-plugin/src/lib/hooks/useGetAudioDuration/index.tsx
index ce7dc3c6..91b2af54 100644
--- a/packages/@justweb3/xmtp-plugin/src/lib/hooks/useGetAudioDuration/index.tsx
+++ b/packages/@justweb3/xmtp-plugin/src/lib/hooks/useGetAudioDuration/index.tsx
@@ -1,41 +1,44 @@
import { useEffect, useState } from 'react';
-const useGetAudioDuration = (url: string) => {
- const [duration, setDuration] = useState(null);
-
- useEffect(() => {
- if (!url) {
- setDuration(null);
- return;
+export const useGetAudioDuration = (url: string) => {
+ const [duration, setDuration] = useState(null);
+
+ useEffect(() => {
+ if (!url) {
+ setDuration(null);
+ return;
+ }
+
+ const getDuration = (url: string, next: (duration: number) => void) => {
+ const _player = new Audio(url);
+ const durationChangeHandler = function (
+ this: HTMLAudioElement,
+ e: Event
+ ) {
+ if (this.duration !== Infinity) {
+ const duration = this.duration;
+ _player.remove(); // Cleanup
+ next(duration);
}
+ };
+
+ _player.addEventListener('durationchange', durationChangeHandler, false);
+ _player.load();
+ _player.currentTime = 24 * 60 * 60;
+ _player.volume = 0;
+ };
+
+ getDuration(url, (duration: number) => {
+ setDuration(duration);
+ });
+
+ return () => {
+ const _player = new Audio(url);
+ _player.remove();
+ };
+ }, [url]);
- const getDuration = (url: string, next: (duration: number) => void) => {
- const _player = new Audio(url);
- const durationChangeHandler = function (this: HTMLAudioElement, e: Event) {
- if (this.duration !== Infinity) {
- const duration = this.duration;
- _player.remove(); // Cleanup
- next(duration);
- }
- };
-
- _player.addEventListener('durationchange', durationChangeHandler, false);
- _player.load();
- _player.currentTime = 24 * 60 * 60;
- _player.volume = 0;
- };
-
- getDuration(url, (duration: number) => {
- setDuration(duration);
- });
-
- return () => {
- const _player = new Audio(url);
- _player.remove();
- };
- }, [url]);
-
- return duration;
+ return duration;
};
export default useGetAudioDuration;
diff --git a/packages/@justweb3/xmtp-plugin/src/lib/hooks/useReadReceipt/index.tsx b/packages/@justweb3/xmtp-plugin/src/lib/hooks/useReadReceipt/index.tsx
new file mode 100644
index 00000000..eb1695f7
--- /dev/null
+++ b/packages/@justweb3/xmtp-plugin/src/lib/hooks/useReadReceipt/index.tsx
@@ -0,0 +1,14 @@
+import { useMutation } from '@tanstack/react-query';
+import { CachedConversation, useSendMessage } from '@xmtp/react-sdk';
+import { ContentTypeReadReceipt } from '@xmtp/content-type-read-receipt';
+
+export const useReadReceipt = (conversation?: CachedConversation) => {
+ const { sendMessage } = useSendMessage();
+
+ return useMutation({
+ mutationFn: () => {
+ if (!conversation) throw new Error('Conversation not found');
+ return sendMessage(conversation, {}, ContentTypeReadReceipt);
+ },
+ });
+};
diff --git a/packages/@justweb3/xmtp-plugin/src/lib/hooks/useSendMessage/index.ts b/packages/@justweb3/xmtp-plugin/src/lib/hooks/useSendMessage/index.ts
index 4a89f1d9..c7866345 100644
--- a/packages/@justweb3/xmtp-plugin/src/lib/hooks/useSendMessage/index.ts
+++ b/packages/@justweb3/xmtp-plugin/src/lib/hooks/useSendMessage/index.ts
@@ -1,33 +1,33 @@
-import { useMutation } from '@tanstack/react-query'
-import { ContentTypeId } from '@xmtp/content-type-primitives'
+import { useMutation } from '@tanstack/react-query';
+import { ContentTypeId } from '@xmtp/content-type-primitives';
import {
- CachedConversation,
- useSendMessage,
- DecodedMessage,
- SendOptions,
-} from '@xmtp/react-sdk'
+ CachedConversation,
+ DecodedMessage,
+ SendOptions,
+ useSendMessage,
+} from '@xmtp/react-sdk';
-export const sendMessages = async (
+export const _sendMessages = async (
+ conversation: CachedConversation,
+ message: string,
+ sendMessage: (
conversation: CachedConversation,
- message: string,
- sendMessage: (
- conversation: CachedConversation,
- content: T,
- contentType?: ContentTypeId,
- sendOptions?: Omit,
- ) => Promise | undefined>,
- contentType?: SendOptions,
+ content: T,
+ contentType?: ContentTypeId,
+ sendOptions?: Omit
+ ) => Promise | undefined>,
+ contentType?: SendOptions
) => {
- await sendMessage(conversation, message, undefined, contentType)
-}
+ await sendMessage(conversation, message, undefined, contentType);
+};
export const useSendMessages = (conversation?: CachedConversation) => {
- const { sendMessage } = useSendMessage()
+ const { sendMessage } = useSendMessage();
- return useMutation({
- mutationFn: (message: string, contentType?: SendOptions) => {
- if (!conversation) throw new Error('Conversation not found')
- return sendMessages(conversation, message, sendMessage, contentType)
- },
- })
-}
+ return useMutation({
+ mutationFn: (message: string, contentType?: SendOptions) => {
+ if (!conversation) throw new Error('Conversation not found');
+ return _sendMessages(conversation, message, sendMessage, contentType);
+ },
+ });
+};
diff --git a/packages/@justweb3/xmtp-plugin/src/lib/plugins/index.tsx b/packages/@justweb3/xmtp-plugin/src/lib/plugins/index.tsx
index 63c919a9..90d1a0a3 100644
--- a/packages/@justweb3/xmtp-plugin/src/lib/plugins/index.tsx
+++ b/packages/@justweb3/xmtp-plugin/src/lib/plugins/index.tsx
@@ -1,7 +1,7 @@
import { JustaPlugin } from '@justweb3/widget';
import { JustWeb3XMTPProvider } from '../providers/JustWeb3XMTPProvider';
-import { ChatButton } from '../components/ChatButton';
-import { ChatWithProfileButton } from '../components/ChatWithProfileButton';
+import { ChatMenuButton } from '../components/ChatMenuButton';
+import { ProfileChatButton } from '../components/ProfileChatButton';
export type XmtpEnvironment = 'local' | 'production' | 'dev';
@@ -21,11 +21,11 @@ export const XMTPPlugin = (env: XmtpEnvironment): JustaPlugin => {
);
},
ProfileHeader: (pluginApi, ens, chainId, address) => {
- return ;
+ return ;
},
SignInMenu: (pluginApi) => {
return (
- pluginApi.setState('xmtpOpen', open)}
env={env}
/>
diff --git a/packages/@justweb3/xmtp-plugin/src/lib/providers/JustWeb3XMTPProvider/index.tsx b/packages/@justweb3/xmtp-plugin/src/lib/providers/JustWeb3XMTPProvider/index.tsx
index 05d06fc5..1ee5a0a8 100644
--- a/packages/@justweb3/xmtp-plugin/src/lib/providers/JustWeb3XMTPProvider/index.tsx
+++ b/packages/@justweb3/xmtp-plugin/src/lib/providers/JustWeb3XMTPProvider/index.tsx
@@ -1,29 +1,42 @@
-import React, { useEffect } from 'react';
+import React, { useEffect, useMemo } from 'react';
import {
attachmentContentTypeConfig,
CachedConversation,
+ CachedMessage,
Client,
ClientOptions,
+ ContentTypeConfiguration,
ContentTypeMetadata,
reactionContentTypeConfig,
replyContentTypeConfig,
useClient,
+ useMessages,
+ useStreamMessages,
XMTPProvider,
} from '@xmtp/react-sdk';
-import { ChatSheet } from '../../components/ChatSheet';
+import { InboxSheet } from '../../components/InboxSheet';
import { useEthersSigner } from '../../hooks';
import { useMountedAccount } from '@justaname.id/react';
import { loadKeys, storeKeys, wipeKeys } from '../../utils/xmtp';
-import { AllMessageSheet } from '../../components/AllMessageSheet';
+import { readReceiptContentTypeConfig } from '../../content-types/readReceipt';
+import { ContentTypeReadReceipt } from '@xmtp/content-type-read-receipt';
+import { ChatSheet } from '../../components/ChatSheet';
-const contentTypeConfigs = [
+const contentTypeConfigs: ContentTypeConfiguration[] = [
attachmentContentTypeConfig,
reactionContentTypeConfig,
replyContentTypeConfig,
+ readReceiptContentTypeConfig,
];
interface JustWeb3XMTPContextProps {
handleOpenChat: (address: string) => void;
+ conversationsInfo: {
+ conversationId: string;
+ unreadCount: number;
+ consent: 'allowed' | 'blocked' | 'requested';
+ lastMessage: CachedMessage;
+ }[];
}
const JustWeb3XMTPContext = React.createContext<
@@ -46,6 +59,25 @@ export const JustWeb3XMTPProvider: React.FC = ({
const [isXmtpEnabled, setIsXmtpEnabled] = React.useState(false);
const [conversation, setConversation] =
React.useState | null>(null);
+ const [conversations, setConversations] = React.useState<{
+ allowed: CachedConversation[];
+ blocked: CachedConversation[];
+ requested: CachedConversation[];
+ }>({
+ allowed: [],
+ blocked: [],
+ requested: [],
+ });
+ const [conversationsInfo, setConversationsInfo] = React.useState<
+ {
+ conversationId: string;
+ unreadCount: number;
+ consent: 'allowed' | 'blocked' | 'requested';
+ lastMessage: CachedMessage;
+ }[]
+ >([]);
+
+ console.log(conversations);
const handleXmtpEnabled = (enabled: boolean) => {
setIsXmtpEnabled(enabled);
};
@@ -61,24 +93,142 @@ export const JustWeb3XMTPProvider: React.FC = ({
}
};
+ const handleConversationInfo = (
+ conversationId: string,
+ unreadCount: number,
+ lastMessage: CachedMessage,
+ consent: 'allowed' | 'blocked' | 'requested'
+ ) => {
+ setConversationsInfo((prev) => {
+ const index = prev.findIndex(
+ (item) => item.conversationId === conversationId
+ );
+ if (index === -1) {
+ return [
+ ...prev,
+ {
+ conversationId,
+ unreadCount,
+ lastMessage,
+ consent,
+ },
+ ];
+ }
+ prev[index].unreadCount = unreadCount;
+ prev[index].lastMessage = lastMessage;
+ return [...prev];
+ });
+ };
+
+ console.log('Conversations Info:', conversationsInfo);
+
return (
{isXmtpEnabled && (
- handleOpenChat('')}
+ allConversations={conversations}
+ onConversationsUpdated={setConversations}
+ conversationsInfo={conversationsInfo}
/>
)}
- (
+ item.conversationId === conversation.topic
+ )?.unreadCount
+ }
+ lastMessage={
+ conversationsInfo.find(
+ (item) => item.conversationId === conversation.topic
+ )?.lastMessage
+ }
+ handleConversationInfo={(
+ conversationId,
+ unreadCount,
+ lastMessage
+ ) =>
+ handleConversationInfo(
+ conversationId,
+ unreadCount,
+ lastMessage,
+ 'allowed'
+ )
+ }
+ />
+ ))}
+ {conversations.blocked.map((conversation) => (
+ item.conversationId === conversation.topic
+ )?.unreadCount
+ }
+ lastMessage={
+ conversationsInfo.find(
+ (item) => item.conversationId === conversation.topic
+ )?.lastMessage
+ }
+ handleConversationInfo={(
+ conversationId,
+ unreadCount,
+ lastMessage
+ ) =>
+ handleConversationInfo(
+ conversationId,
+ unreadCount,
+ lastMessage,
+ 'blocked'
+ )
+ }
+ />
+ ))}
+ {conversations.requested.map((conversation) => (
+ item.conversationId === conversation.topic
+ )?.unreadCount
+ }
+ lastMessage={
+ conversationsInfo.find(
+ (item) => item.conversationId === conversation.topic
+ )?.lastMessage
+ }
+ handleConversationInfo={(
+ conversationId,
+ unreadCount,
+ lastMessage
+ ) =>
+ handleConversationInfo(
+ conversationId,
+ unreadCount,
+ lastMessage,
+ 'requested'
+ )
+ }
+ />
+ ))}
+
+ {
setPeerAddress(null);
@@ -99,16 +249,122 @@ interface ChecksProps {
env: 'local' | 'production' | 'dev';
}
+interface GetConversationInfoProps {
+ conversation: CachedConversation;
+ handleConversationInfo: (
+ conversationId: string,
+ unreadCount: number,
+ lastMessage: CachedMessage
+ ) => void;
+ unreadCount?: number;
+ lastMessage?: CachedMessage;
+}
+
+export const GetConversationInfo: React.FC = ({
+ conversation,
+ handleConversationInfo,
+ unreadCount,
+ lastMessage,
+}) => {
+ const { messages } = useMessages(conversation);
+
+ useStreamMessages(conversation);
+ const _unreadCount = useMemo(() => {
+ let count = 0;
+ const _messages = [...messages].reverse();
+ for (const message of _messages) {
+ if (message.contentType === ContentTypeReadReceipt.toString()) {
+ break;
+ }
+
+ count++;
+ }
+
+ return count;
+ }, [messages]);
+
+ const _lastMessage = useMemo(() => {
+ const _messages = [...messages];
+ let lastMessage = _messages[_messages.length - 1];
+ if (lastMessage?.contentType === ContentTypeReadReceipt.toString()) {
+ lastMessage = _messages[_messages.length - 2];
+ }
+
+ console.log('Last Message:', lastMessage);
+ return lastMessage;
+ }, [messages]);
+
+ useEffect(() => {
+ if (unreadCount === _unreadCount && _lastMessage?.id === lastMessage?.id) {
+ return;
+ }
+
+ console.log(
+ 'Updating Conversation Info:',
+ conversation.topic,
+ _unreadCount,
+ _lastMessage
+ );
+ handleConversationInfo(conversation.topic, _unreadCount, _lastMessage);
+ }, [
+ conversation.topic,
+ handleConversationInfo,
+ _unreadCount,
+ unreadCount,
+ _lastMessage,
+ lastMessage?.id,
+ ]);
+
+ return null;
+};
+
export const Checks: React.FC = ({
open,
handleXmtpEnabled,
env,
}) => {
- const { client, initialize, isLoading } = useClient();
+ const { client, initialize, isLoading, disconnect } = useClient();
const signer = useEthersSigner();
const { address } = useMountedAccount();
const [isInitializing, setIsInitializing] = React.useState(false);
const [rejected, setRejected] = React.useState(false);
+
+ useEffect(() => {
+ async function reinitializeXmtp() {
+ if (client && address) {
+ if (client?.address?.toLowerCase() !== address.toLowerCase()) {
+ await disconnect();
+
+ if (!signer) {
+ return;
+ }
+ setIsInitializing(true);
+ const clientOptions: Partial> = {
+ appVersion: 'JustWeb3/1.0.0/' + env + '/0',
+ env: env,
+ };
+ let keys = loadKeys(address ?? '', env);
+ console.log('Keys:', keys);
+ if (!keys) {
+ keys = await Client.getKeys(signer, {
+ env: env,
+ skipContactPublishing: false,
+ // persistConversations: false,
+ });
+ storeKeys(address ?? '', keys, env);
+ }
+
+ await initialize({
+ keys,
+ options: clientOptions,
+ signer: signer,
+ });
+ }
+ }
+ }
+ reinitializeXmtp();
+ }, [client, address, signer, env, initialize, disconnect]);
+
useEffect(() => {
async function initializeXmtp() {
if (isInitializing || isLoading || rejected) return;
@@ -134,11 +390,14 @@ export const Checks: React.FC = ({
});
storeKeys(address ?? '', keys, env);
}
+
await initialize({
keys,
options: clientOptions,
signer: signer,
});
+
+ // _client?.registerCodec(new ReadReceiptCodec());
setIsInitializing(false);
} catch (error) {
console.error('Failed to initialize XMTP Client:', error);
@@ -164,12 +423,6 @@ export const Checks: React.FC = ({
handleXmtpEnabled(!!client);
}, [client, handleXmtpEnabled]);
- // useEffect(() => {
- // if (!address) {
- // disconnect();
- // }
- // }, [connectedEns?.ens, disconnect]);
-
return null;
};
From 4a3af5ec741d4e6938b1311bd2fd8303c7e86875 Mon Sep 17 00:00:00 2001
From: HadiKhai
Date: Mon, 9 Dec 2024 09:48:20 +0200
Subject: [PATCH 2/2] feat: chat optimization
---
.gitignore | 4 +-
apps/console/next.config.js | 5 +-
package.json | 1 +
.../lib/hooks/client/useEnsPublicClient.ts | 2 +-
.../lib/hooks/primaryName/usePrimaryName.ts | 30 +-
.../react/src/lib/hooks/records/useRecords.ts | 6 +-
.../src/lib/providers/JustaNameProvider.tsx | 4 +-
.../sdk/src/lib/api/axiosController.ts | 1 -
.../src/lib/components/FollowButton/index.tsx | 3 +
.../NotificationBadge.module.css | 3 +-
.../ui/src/lib/ui/Tabs/Tabs.module.css | 3 +
.../JustEnsCard/JustEnsCard.module.css | 5 +
.../src/lib/components/JustEnsCard/index.tsx | 3 +-
.../lib/components/JustWeb3Button/index.tsx | 29 +
.../Profile/ContentSection/index.tsx | 5 +-
.../src/lib/dialogs/SignInDialog/index.tsx | 1 +
.../@justweb3/widget/src/lib/plugins/index.ts | 1 +
.../lib/providers/JustWeb3Provider/index.tsx | 16 +
.../@justweb3/xmtp-plugin/.storybook/main.ts | 11 +-
.../xmtp-plugin/.storybook/preview.tsx | 14 +-
.../ChatMessagesList/MessageCard/index.tsx | 1 -
.../ChatSheet/Chat/ChatMessagesList/index.tsx | 31 +-
.../Chat/ChatReactionOverlay/index.tsx | 2 +-
.../Chat/ChatTextField/MessageInput/index.tsx | 1 +
.../lib/components/ChatSheet/Chat/index.tsx | 46 +-
.../InboxSheet/ChatList/MessageItem/index.tsx | 51 +-
.../components/InboxSheet/ChatList/index.tsx | 66 +-
.../src/lib/components/InboxSheet/index.tsx | 358 ++++---
.../components/JustWeb3ButtonRight/index.tsx | 84 ++
.../components/ProfileChatButton/index.tsx | 6 +
.../src/lib/content-types/readReceipt.ts | 2 +
.../src/lib/icons/ChatIcon/index.tsx | 17 +
.../xmtp-plugin/src/lib/plugins/index.tsx | 18 +-
.../providers/JustWeb3XMTPProvider/index.tsx | 86 +-
.../xmtp-plugin/src/stories/xmtp.stories.tsx | 99 +-
yarn.lock | 946 +++++++++++++++++-
36 files changed, 1597 insertions(+), 364 deletions(-)
create mode 100644 packages/@justweb3/xmtp-plugin/src/lib/components/JustWeb3ButtonRight/index.tsx
create mode 100644 packages/@justweb3/xmtp-plugin/src/lib/icons/ChatIcon/index.tsx
diff --git a/.gitignore b/.gitignore
index 0c426e1a..1af11c4b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -62,4 +62,6 @@ cache/
.yarn/install-state.gz
-storybook-static
\ No newline at end of file
+storybook-static
+
+.million
\ No newline at end of file
diff --git a/apps/console/next.config.js b/apps/console/next.config.js
index 55fe2990..a4720cf2 100644
--- a/apps/console/next.config.js
+++ b/apps/console/next.config.js
@@ -2,6 +2,7 @@
// eslint-disable-next-line @typescript-eslint/no-var-requires
const { composePlugins, withNx } = require('@nx/next');
+const MillionLint = require('@million/lint');
/**
* @type {import('@nx/next/plugins/with-nx').WithNxOptions}
@@ -23,4 +24,6 @@ const plugins = [
withNx,
];
-module.exports = composePlugins(...plugins)(nextConfig);
+module.exports = MillionLint.next({ rsc: true })(
+ composePlugins(...plugins)(nextConfig)
+);
diff --git a/package.json b/package.json
index 8e77fd0f..e3ed50e1 100644
--- a/package.json
+++ b/package.json
@@ -19,6 +19,7 @@
"@hookform/resolvers": "^3.9.0",
"@inquirer/prompts": "^4.3.0",
"@justaname.id/address-resolution": "^1.1.0",
+ "@million/lint": "^1.0.13",
"@privy-io/react-auth": "^1.82.0",
"@privy-io/wagmi": "^0.2.12",
"@radix-ui/react-accordion": "^1.2.1",
diff --git a/packages/@justaname.id/react/src/lib/hooks/client/useEnsPublicClient.ts b/packages/@justaname.id/react/src/lib/hooks/client/useEnsPublicClient.ts
index 039c6fb5..288087d2 100644
--- a/packages/@justaname.id/react/src/lib/hooks/client/useEnsPublicClient.ts
+++ b/packages/@justaname.id/react/src/lib/hooks/client/useEnsPublicClient.ts
@@ -22,7 +22,7 @@ export const getEnsPublicClient = (
};
export const buildEnsPublicClientKey = (chainId: ChainId | undefined) => [
- 'CLIENT',
+ 'ENS_PUBLIC_CLIENT',
chainId,
];
diff --git a/packages/@justaname.id/react/src/lib/hooks/primaryName/usePrimaryName.ts b/packages/@justaname.id/react/src/lib/hooks/primaryName/usePrimaryName.ts
index 594082b5..2c704966 100644
--- a/packages/@justaname.id/react/src/lib/hooks/primaryName/usePrimaryName.ts
+++ b/packages/@justaname.id/react/src/lib/hooks/primaryName/usePrimaryName.ts
@@ -6,6 +6,7 @@ import { useEnsPublicClient } from '../client/useEnsPublicClient';
import { defaultOptions } from '../../query';
import { getName } from '@ensdomains/ensjs/public';
import { PrimaryNameTaskQueue } from './primary-name-task-queue';
+import { buildPrimaryNameBatchKey } from './usePrimaryNameBatch';
export const buildPrimaryName = (
address: string,
@@ -59,6 +60,16 @@ export const usePrimaryName = (
let name = '';
+ const primaryNames = queryClient.getQueryData(
+ buildPrimaryNameBatchKey(_chainId)
+ ) as Record;
+
+ if (primaryNames && _params?.address) {
+ if (primaryNames[_params?.address]) {
+ return primaryNames[_params.address];
+ }
+ }
+
const primaryNameGetByAddressResponse =
await justaname.subnames.getPrimaryNameByAddress({
address: params?.address,
@@ -82,25 +93,6 @@ export const usePrimaryName = (
name = reverseResolution.name;
}
}
-
- // const reverseResolution = await getName(ensClient, {
- // address: params?.address,
- // });
- //
- // if (reverseResolution && reverseResolution?.name) {
- // name = reverseResolution.name;
- // } else {
- // const primaryNameGetByAddressResponse =
- // await justaname.subnames.getPrimaryNameByAddress({
- // address: params?.address,
- // chainId: _chainId,
- // });
- //
- // if (primaryNameGetByAddressResponse) {
- // name = primaryNameGetByAddressResponse.name;
- // }
- // }
- //
return name;
};
diff --git a/packages/@justaname.id/react/src/lib/hooks/records/useRecords.ts b/packages/@justaname.id/react/src/lib/hooks/records/useRecords.ts
index 2feb6b87..51b421de 100644
--- a/packages/@justaname.id/react/src/lib/hooks/records/useRecords.ts
+++ b/packages/@justaname.id/react/src/lib/hooks/records/useRecords.ts
@@ -260,11 +260,7 @@ export const useRecords = (params?: UseRecordsParams): UseRecordsResult => {
}
return failureCount < 3;
},
- queryKey: buildRecordsBySubnameKey(
- _ens || '',
- _chainId
- // params?.standard
- ),
+ queryKey: buildRecordsBySubnameKey(_ens || '', _chainId),
queryFn: () =>
getRecordsInternal(
{
diff --git a/packages/@justaname.id/react/src/lib/providers/JustaNameProvider.tsx b/packages/@justaname.id/react/src/lib/providers/JustaNameProvider.tsx
index 2b49b9cb..b3a690a3 100644
--- a/packages/@justaname.id/react/src/lib/providers/JustaNameProvider.tsx
+++ b/packages/@justaname.id/react/src/lib/providers/JustaNameProvider.tsx
@@ -68,8 +68,8 @@ export const JustaNameProvider: FC = ({
const { chainId } = useMountedAccount();
const defaultChain = useMemo(() => {
- return !chainId
- ? undefined
+ return !chainId === undefined
+ ? 1
: chainId !== 1 && chainId !== 11155111
? 1
: chainId;
diff --git a/packages/@justaname.id/sdk/src/lib/api/axiosController.ts b/packages/@justaname.id/sdk/src/lib/api/axiosController.ts
index 9a2dad0b..4e84ddc5 100644
--- a/packages/@justaname.id/sdk/src/lib/api/axiosController.ts
+++ b/packages/@justaname.id/sdk/src/lib/api/axiosController.ts
@@ -42,7 +42,6 @@ export const controlledAxiosPromise = >(
return res.data.result.data as T;
})
.catch((err: AxiosError>) => {
- console.error(err);
if (err?.response) {
if (err?.response?.data?.result) {
if (err?.response?.data?.result?.error) {
diff --git a/packages/@justweb3/efp-plugin/src/lib/components/FollowButton/index.tsx b/packages/@justweb3/efp-plugin/src/lib/components/FollowButton/index.tsx
index e334e222..3206dd24 100644
--- a/packages/@justweb3/efp-plugin/src/lib/components/FollowButton/index.tsx
+++ b/packages/@justweb3/efp-plugin/src/lib/components/FollowButton/index.tsx
@@ -15,6 +15,9 @@ export const FollowButton: React.FC = ({ ens, address }) => {
addressOrEns1: address,
addressOrEns2: ownAddress,
});
+ if (ownAddress === address) {
+ return null;
+ }
if (!ownAddress) {
return (
diff --git a/packages/@justweb3/ui/src/lib/ui/NotificationBadge/NotificationBadge.module.css b/packages/@justweb3/ui/src/lib/ui/NotificationBadge/NotificationBadge.module.css
index d5c0f5a4..f0b5f0f9 100644
--- a/packages/@justweb3/ui/src/lib/ui/NotificationBadge/NotificationBadge.module.css
+++ b/packages/@justweb3/ui/src/lib/ui/NotificationBadge/NotificationBadge.module.css
@@ -1,7 +1,6 @@
.notificationIcon {
position: relative;
- display: inline-block;
-
+ display: flex;
}
.icon {
diff --git a/packages/@justweb3/ui/src/lib/ui/Tabs/Tabs.module.css b/packages/@justweb3/ui/src/lib/ui/Tabs/Tabs.module.css
index dac10440..5510c3f7 100644
--- a/packages/@justweb3/ui/src/lib/ui/Tabs/Tabs.module.css
+++ b/packages/@justweb3/ui/src/lib/ui/Tabs/Tabs.module.css
@@ -6,6 +6,8 @@
margin-bottom: 10px;
gap: 10px;
display: flex;
+ overflow-x: auto;
+ overflow-y: visible;
}
/* Underlined variant */
@@ -22,6 +24,7 @@
font-family: var(--justweb3-font-family);
border-bottom: 2px solid transparent;
+
}
.underlinedTabs .tabsTrigger[data-state='active'] {
diff --git a/packages/@justweb3/widget/src/lib/components/JustEnsCard/JustEnsCard.module.css b/packages/@justweb3/widget/src/lib/components/JustEnsCard/JustEnsCard.module.css
index 11751fc2..3b130351 100644
--- a/packages/@justweb3/widget/src/lib/components/JustEnsCard/JustEnsCard.module.css
+++ b/packages/@justweb3/widget/src/lib/components/JustEnsCard/JustEnsCard.module.css
@@ -70,3 +70,8 @@
display: flex;
gap: 5px;
}
+
+.socialIcon {
+ width: 12px;
+ height: 12px;
+}
diff --git a/packages/@justweb3/widget/src/lib/components/JustEnsCard/index.tsx b/packages/@justweb3/widget/src/lib/components/JustEnsCard/index.tsx
index 515471f2..5b24a65d 100644
--- a/packages/@justweb3/widget/src/lib/components/JustEnsCard/index.tsx
+++ b/packages/@justweb3/widget/src/lib/components/JustEnsCard/index.tsx
@@ -214,8 +214,7 @@ export const JustEnsCard: FC = ({
{records.sanitizedRecords.socials.map((social, index) =>
React.cloneElement(getTextRecordIcon(social.key), {
key: `${ens}-${index}-${social.key}`,
- width: 12,
- height: 12,
+ className: styles.socialIcon,
})
)}
diff --git a/packages/@justweb3/widget/src/lib/components/JustWeb3Button/index.tsx b/packages/@justweb3/widget/src/lib/components/JustWeb3Button/index.tsx
index 2c10fd20..2b53c6b3 100644
--- a/packages/@justweb3/widget/src/lib/components/JustWeb3Button/index.tsx
+++ b/packages/@justweb3/widget/src/lib/components/JustWeb3Button/index.tsx
@@ -162,6 +162,24 @@ export const JustWeb3Button: FC = ({
}
const connectedEnsBtn = (withDialog: boolean) => {
+ const right = plugins.map((plugin) => {
+ const component = plugin.components?.JustWeb3ButtonRight;
+ if (!component) {
+ return null;
+ }
+
+ return (
+ {
+ setMobileDialogOpen(false);
+ }}
+ >
+ {component(createPluginApi(plugin.name))}
+
+ );
+ });
+
return (
= ({
color: 'var(--justweb3-primary-color)',
...style,
}}
+ right={
+
+ {right}
+
+ }
contentStyle={{
alignItems: 'start',
}}
diff --git a/packages/@justweb3/widget/src/lib/components/Profile/ContentSection/index.tsx b/packages/@justweb3/widget/src/lib/components/Profile/ContentSection/index.tsx
index de020496..97d576b5 100644
--- a/packages/@justweb3/widget/src/lib/components/Profile/ContentSection/index.tsx
+++ b/packages/@justweb3/widget/src/lib/components/Profile/ContentSection/index.tsx
@@ -51,6 +51,9 @@ export interface ContentProps {
plugins: JustaPlugin[];
}
+const ENS_MAINNET_RESOLVER = '0x4976fb03C32e5B8cfe2b6cCB31c09Ba78EBaBa41';
+const ENS_SEPOLIA_RESOLVER = '0x8FADE66B79cC9f707aB26799354482EB93a5B7dD';
+
const ContentSection: React.FC = ({
fullSubname = '',
chainId = 1,
@@ -65,7 +68,7 @@ const ContentSection: React.FC = ({
const { accountEnsNames } = useAccountEnsNames();
const [tab, setTab] = React.useState('Main');
const { openEnsProfile } = useJustWeb3();
-
+ // const { offchainResolvers } = useOffchainResolvers()
const isProfileSelf = useMemo(() => {
const tempEns = accountEnsNames
?.map((ens) => ens.ens)
diff --git a/packages/@justweb3/widget/src/lib/dialogs/SignInDialog/index.tsx b/packages/@justweb3/widget/src/lib/dialogs/SignInDialog/index.tsx
index eeb51db4..0bad3cb3 100644
--- a/packages/@justweb3/widget/src/lib/dialogs/SignInDialog/index.tsx
+++ b/packages/@justweb3/widget/src/lib/dialogs/SignInDialog/index.tsx
@@ -30,6 +30,7 @@ import styles from './SignInDialog.module.css';
import clsx from 'clsx';
const ENS_MAINNET_RESOLVER = '0x4976fb03C32e5B8cfe2b6cCB31c09Ba78EBaBa41';
+// const BASE_MAINNET_RESOLVER = '0xde9049636F4a1dfE0a64d1bFe3155C0A14C54F31'
const ENS_SEPOLIA_RESOLVER = '0x8FADE66B79cC9f707aB26799354482EB93a5B7dD';
interface TransitionElementProps extends React.HTMLAttributes {
diff --git a/packages/@justweb3/widget/src/lib/plugins/index.ts b/packages/@justweb3/widget/src/lib/plugins/index.ts
index 6337ffd3..5ba874b9 100644
--- a/packages/@justweb3/widget/src/lib/plugins/index.ts
+++ b/packages/@justweb3/widget/src/lib/plugins/index.ts
@@ -50,6 +50,7 @@ interface PluginComponents {
Provider?: PluginProviderComponent;
Global?: PluginComponent;
SignInMenu?: PluginComponent;
+ JustWeb3ButtonRight?: PluginComponent;
ProfileSection?: PluginRichComponent;
ProfileHeader?: PluginRichComponent;
ProfileTab?: ProfileTabPluginComponent;
diff --git a/packages/@justweb3/widget/src/lib/providers/JustWeb3Provider/index.tsx b/packages/@justweb3/widget/src/lib/providers/JustWeb3Provider/index.tsx
index 90058cf0..5e3bf1ad 100644
--- a/packages/@justweb3/widget/src/lib/providers/JustWeb3Provider/index.tsx
+++ b/packages/@justweb3/widget/src/lib/providers/JustWeb3Provider/index.tsx
@@ -403,6 +403,22 @@ const CheckSession: FC<{
);
const isConnectedPrevious = usePreviousState(isConnected, [isConnected]);
+ useEffect(() => {
+ if (isConnecting || isReconnecting || isEnsAuthPending) {
+ return;
+ }
+
+ const timeout = setTimeout(() => {
+ if (!isConnected) {
+ signOut();
+ }
+ }, 1000);
+
+ return () => {
+ clearTimeout(timeout);
+ };
+ }, [isConnected, isConnecting, isEnsAuthPending, isReconnecting, signOut]);
+
useEffect(() => {
if (connectedEns && chainId) {
if (
diff --git a/packages/@justweb3/xmtp-plugin/.storybook/main.ts b/packages/@justweb3/xmtp-plugin/.storybook/main.ts
index 3e575653..73c1c0cc 100644
--- a/packages/@justweb3/xmtp-plugin/.storybook/main.ts
+++ b/packages/@justweb3/xmtp-plugin/.storybook/main.ts
@@ -13,7 +13,16 @@ const config: StorybookConfig = {
viteFinal: async (config) =>
mergeConfig(config, {
- plugins: [react(), nxViteTsPaths()],
+ plugins: [
+ react(),
+ nxViteTsPaths(),
+ // MillionLint.vite({
+ // filter: {
+ // // I want a regex to exclude all the react tsx components with Icon at the end
+ // exclude: /Icon$/,
+ // },
+ // }),
+ ],
define: {
'process.env': process.env,
},
diff --git a/packages/@justweb3/xmtp-plugin/.storybook/preview.tsx b/packages/@justweb3/xmtp-plugin/.storybook/preview.tsx
index 0b60c255..297516f8 100644
--- a/packages/@justweb3/xmtp-plugin/.storybook/preview.tsx
+++ b/packages/@justweb3/xmtp-plugin/.storybook/preview.tsx
@@ -1,11 +1,11 @@
import './polyfills';
-import { scan } from 'react-scan';
+// import { scan } from 'react-scan';
-if (typeof window !== 'undefined') {
- scan({
- enabled: true,
- log: true, // logs render info to console (default: false)
- });
-}
+// if (typeof window !== 'undefined') {
+// scan({
+// enabled: true,
+// log: true, // logs render info to console (default: false)
+// });
+// }
export const decorators = [(Story) => ];
diff --git a/packages/@justweb3/xmtp-plugin/src/lib/components/ChatSheet/Chat/ChatMessagesList/MessageCard/index.tsx b/packages/@justweb3/xmtp-plugin/src/lib/components/ChatSheet/Chat/ChatMessagesList/MessageCard/index.tsx
index 8ad5ce6c..ae0491c4 100644
--- a/packages/@justweb3/xmtp-plugin/src/lib/components/ChatSheet/Chat/ChatMessagesList/MessageCard/index.tsx
+++ b/packages/@justweb3/xmtp-plugin/src/lib/components/ChatSheet/Chat/ChatMessagesList/MessageCard/index.tsx
@@ -243,7 +243,6 @@ export const MessageCard: React.FC = ({
overflowWrap: 'break-word',
maxWidth: !isImage ? '240px' : 'none',
fontSize: '14px',
- lineHeight: '14px',
padding: isReply ? '4px' : '5px',
borderRadius: '14px',
backgroundColor: isReceiver
diff --git a/packages/@justweb3/xmtp-plugin/src/lib/components/ChatSheet/Chat/ChatMessagesList/index.tsx b/packages/@justweb3/xmtp-plugin/src/lib/components/ChatSheet/Chat/ChatMessagesList/index.tsx
index ebbdee51..2aad68e1 100644
--- a/packages/@justweb3/xmtp-plugin/src/lib/components/ChatSheet/Chat/ChatMessagesList/index.tsx
+++ b/packages/@justweb3/xmtp-plugin/src/lib/components/ChatSheet/Chat/ChatMessagesList/index.tsx
@@ -4,6 +4,7 @@ import { DateDivider } from './DateDivider';
import { EmojiSelector } from './EmojiSelector';
import { MessageCard } from './MessageCard';
import { MessageWithReaction } from '../../../../utils/filterReactionsMessages';
+import { useEffect, useState } from 'react';
interface ChatMessagesListProps {
canMessage: boolean;
@@ -26,6 +27,19 @@ export const ChatMessagesList: React.FC = ({
handleEmojiSelect,
computeHeight,
}) => {
+ const [ref, setRef] = useState(null);
+ useEffect(() => {
+ if (ref) {
+ setTimeout(() => {
+ const lastGroupChild = ref.lastElementChild as HTMLElement;
+
+ const lastMessage = lastGroupChild?.lastElementChild as HTMLElement;
+
+ lastMessage?.scrollIntoView({ behavior: 'smooth' });
+ }, 500);
+ }
+ }, [groupedMessages, ref]);
+
if (!canMessage) {
return (
= ({
return (
setRef(el)}
>
{groupedMessages &&
Object.keys(groupedMessages).map((date, index) => (
-
+
{groupedMessages[date].map((message) => (
= ({
borderTopRightRadius: replyMessage ? 0 : '100px',
borderTop: replyMessage ? '0px' : '',
}}
+ autoFocus
placeholder="Send message..."
value={messageValue}
left={
diff --git a/packages/@justweb3/xmtp-plugin/src/lib/components/ChatSheet/Chat/index.tsx b/packages/@justweb3/xmtp-plugin/src/lib/components/ChatSheet/Chat/index.tsx
index de637057..688c55de 100644
--- a/packages/@justweb3/xmtp-plugin/src/lib/components/ChatSheet/Chat/index.tsx
+++ b/packages/@justweb3/xmtp-plugin/src/lib/components/ChatSheet/Chat/index.tsx
@@ -11,7 +11,6 @@ import {
useCanMessage,
useConsent,
useMessages,
- useStreamMessages,
} from '@xmtp/react-sdk';
import { useJustWeb3 } from '@justweb3/widget';
import { ChatTextField } from './ChatTextField';
@@ -46,7 +45,7 @@ export const Chat: React.FC = ({ conversation, onBack }) => {
const [isRequestChangeLoading, setIsRequestChangeLoading] =
useState(false);
- const { entries, allow, refreshConsentList, deny } = useConsent();
+ const { entries, allow, deny } = useConsent();
const { mutateAsync: sendReaction } = useSendReactionMessage(conversation);
const { primaryName } = usePrimaryName({
address: conversation.peerAddress as `0x${string}`,
@@ -59,7 +58,6 @@ export const Chat: React.FC = ({ conversation, onBack }) => {
useCanMessage();
const { mutateAsync: readReceipt, isPending: isReadReceiptSending } =
useReadReceipt(conversation);
- useStreamMessages(conversation);
useEffect(() => {
const lastMessage = messages[messages.length - 1];
@@ -70,8 +68,16 @@ export const Chat: React.FC = ({ conversation, onBack }) => {
return;
}
- readReceipt();
- }, [messages, readReceipt, isReadReceiptSending]);
+ if (entries[conversation.peerAddress]?.permissionType === 'allowed') {
+ readReceipt();
+ }
+ }, [
+ messages,
+ readReceipt,
+ isReadReceiptSending,
+ entries,
+ conversation.peerAddress,
+ ]);
// Determine if user can message
useEffect(() => {
@@ -87,19 +93,6 @@ export const Chat: React.FC = ({ conversation, onBack }) => {
);
}, [entries, conversation.peerAddress]);
- // Scroll to last message when messages change
- useEffect(() => {
- if (messages.length === 0) return;
- setTimeout(() => {
- const lastMessageId = messages[messages.length - 1]?.id;
- const element = document.getElementById(lastMessageId);
- if (element) {
- element.scrollIntoView({ behavior: 'smooth' });
- }
- }, 500);
- }, [messages, conversation]);
-
- // Filter out read receipts and group messages by date
const filteredMessages = useMemo(() => {
const withoutRead = messages.filter(
(message) => message.contentType !== 'xmtp.org/readReceipt:1.0'
@@ -125,10 +118,10 @@ export const Chat: React.FC = ({ conversation, onBack }) => {
const type = mimeType ? typeLookup[mimeType.split('/')?.[1]] : null;
const computeHeight = useMemo(() => {
- const baseHeight = 'calc(100vh - 50px - 3rem - 1.5rem - 73px - 15px)';
+ const baseHeight = '100vh - 50px - 3rem - 1.5rem - 73px - 15px';
const adjustments: string[] = [];
- if (isRequest) adjustments.push('40px');
+ if (isRequest) return 'calc(100vh - 50px - 3rem - 1.5rem - 15px - 34px)';
if (replyMessage) {
if (isStringContent) {
adjustments.push('46px');
@@ -142,8 +135,9 @@ export const Chat: React.FC = ({ conversation, onBack }) => {
}
if (isMessagesSenderOnly) adjustments.push('59px');
- if (adjustments.length === 0) return baseHeight;
- return `${baseHeight}${adjustments.map((val) => ` - ${val}`).join('')}`;
+ return ` calc(${baseHeight}${adjustments
+ .map((val) => ` - ${val}`)
+ .join('')}) `;
}, [
replyMessage,
isMessagesSenderOnly,
@@ -156,18 +150,18 @@ export const Chat: React.FC = ({ conversation, onBack }) => {
// Handlers
const blockAddressHandler = async (peerAddress: string) => {
setIsRequestChangeLoading(true);
- await refreshConsentList();
+ // await refreshConsentList();
await deny([peerAddress]);
- await refreshConsentList();
+ // await refreshConsentList();
setIsRequestChangeLoading(false);
onBack();
};
const handleAllowAddress = async () => {
setIsRequestChangeLoading(true);
- await refreshConsentList();
+ // await refreshConsentList();
await allow([conversation.peerAddress]);
- await refreshConsentList();
+ // await refreshConsentList();
setIsRequest(false);
setIsRequestChangeLoading(false);
};
diff --git a/packages/@justweb3/xmtp-plugin/src/lib/components/InboxSheet/ChatList/MessageItem/index.tsx b/packages/@justweb3/xmtp-plugin/src/lib/components/InboxSheet/ChatList/MessageItem/index.tsx
index a994b787..f3640899 100644
--- a/packages/@justweb3/xmtp-plugin/src/lib/components/InboxSheet/ChatList/MessageItem/index.tsx
+++ b/packages/@justweb3/xmtp-plugin/src/lib/components/InboxSheet/ChatList/MessageItem/index.tsx
@@ -6,7 +6,6 @@ import {
reactionContentTypeConfig,
replyContentTypeConfig,
useConsent,
- useStreamMessages,
} from '@xmtp/react-sdk';
import { useEnsAvatar, useRecords } from '@justaname.id/react';
import { Avatar, Button, Flex, formatText, P, SPAN } from '@justweb3/ui';
@@ -33,8 +32,6 @@ const MessageItem: React.FC = ({
primaryName,
conversationInfo,
}) => {
- useStreamMessages(conversation);
- // const { messages } = useMessages(conversation);
const { records } = useRecords({
ens: primaryName || undefined,
});
@@ -45,15 +42,15 @@ const MessageItem: React.FC = ({
// return lastMessage.contentType !== ContentTypeReadReceipt.toString();
// }, [lastMessage]);
- const { allow, refreshConsentList } = useConsent();
+ const { allow, deny } = useConsent();
const allowUser = async () => {
- await refreshConsentList();
await allow([conversation.peerAddress]);
- await refreshConsentList();
};
- console.log('conversationInfo', conversationInfo);
+ const ignoreUser = async () => {
+ await deny([conversation.peerAddress]);
+ };
const lastContent = useMemo(() => {
const lastMessage = conversationInfo?.lastMessage;
@@ -80,14 +77,12 @@ const MessageItem: React.FC = ({
if (
replyContentTypeConfig.contentTypes.includes(lastMessage?.contentType)
) {
- return lastMessage.contentFallback;
+ return 'replied "' + lastMessage.content.content + '"';
}
return lastMessage.contentFallback;
}, [conversationInfo, conversationInfo?.lastMessage]);
- console.log(lastContent);
-
return (
= ({
direction={'column'}
style={{
marginLeft: '10px',
- maxWidth: blocked
- ? 'calc(100% - 120px)'
- : 'calc(100% - 50px - 32px - 10px)',
+ maxWidth:
+ conversationInfo?.consent === 'requested'
+ ? 'calc(100% - 132px - 44px)'
+ : conversationInfo?.consent === 'blocked'
+ ? 'calc(100% - 120px)'
+ : 'calc(100% - 50px - 32px - 10px)',
justifyContent: 'space-between',
}}
>
@@ -158,10 +156,29 @@ const MessageItem: React.FC = ({
textAlign: 'end',
}}
>
- {blocked ? (
-
+ {conversationInfo?.consent !== 'allowed' ? (
+
+
+ {conversationInfo?.consent === 'requested' && (
+
+ )}
+
) : (
[];
@@ -14,6 +14,7 @@ export interface ChatListProps {
conversation: CachedConversation
) => void;
blockedList?: boolean;
+ primaryNames: PrimaryNameRecord | undefined;
conversationsInfo?: {
conversationId: string;
unreadCount: number;
@@ -27,37 +28,50 @@ export const ChatList: React.FC = ({
handleOpenChat,
blockedList,
conversationsInfo,
+ primaryNames,
}) => {
- const { allPrimaryNames } = usePrimaryNameBatch({
- addresses: conversations.map((conversation) => conversation.peerAddress),
- });
-
return (
- {conversations.map((conversation) => (
- item.conversationId === conversation.topic
- // )?.unreadCount
- // }
- //
- conversationInfo={conversationsInfo?.find(
- (item) => item.conversationId === conversation.topic
- )}
- onClick={() => handleOpenChat(conversation)}
- key={conversation.topic}
- blocked={blockedList}
- />
- ))}
+ {conversationsInfo
+ ?.sort((a, b) => {
+ if (a.lastMessage?.sentAt && b.lastMessage?.sentAt) {
+ // a.lastMessage.sentAt and b.lastMessage.sentA are Date objects
+ return (
+ b.lastMessage.sentAt.getTime() - a.lastMessage.sentAt.getTime()
+ );
+ }
+ return 0;
+ })
+ .map((conv) => {
+ const conversation = conversations.find(
+ (item) => item.topic === conv.conversationId
+ );
+
+ if (!conversation) return null;
+
+ return (
+ item.conversationId === conversation.topic
+ // )?.unreadCount
+ // }
+ //
+ conversationInfo={conv}
+ onClick={() => handleOpenChat(conversation)}
+ key={conversation.topic}
+ blocked={blockedList}
+ />
+ );
+ })}
);
};
diff --git a/packages/@justweb3/xmtp-plugin/src/lib/components/InboxSheet/index.tsx b/packages/@justweb3/xmtp-plugin/src/lib/components/InboxSheet/index.tsx
index c4496252..a8cdbf43 100644
--- a/packages/@justweb3/xmtp-plugin/src/lib/components/InboxSheet/index.tsx
+++ b/packages/@justweb3/xmtp-plugin/src/lib/components/InboxSheet/index.tsx
@@ -4,6 +4,7 @@ import {
Sheet,
SheetContent,
SheetTitle,
+ SPAN,
Tabs,
TabsContent,
TabsList,
@@ -13,13 +14,18 @@ import {
CachedConversation,
CachedMessage,
ContentTypeMetadata,
+ Conversation,
+ useClient,
useConsent,
useConversations,
useStreamAllMessages,
+ useStreamConsentList,
useStreamConversations,
} from '@xmtp/react-sdk';
import React, { useEffect, useMemo } from 'react';
import { ChatList } from './ChatList';
+import { useMountedAccount, usePrimaryNameBatch } from '@justaname.id/react';
+import { isEqual } from 'lodash';
export interface InboxSheetProps {
open?: boolean;
@@ -60,10 +66,43 @@ export const InboxSheet: React.FC = ({
conversationsInfo,
}) => {
const [tab, setTab] = React.useState('Chats');
- const { conversations, isLoading } = useConversations();
-
+ const { conversations: cachedConversations, isLoading } = useConversations();
+ const { address } = useMountedAccount();
+ const conversations = useMemo(() => {
+ return cachedConversations.filter((convo) => convo.peerAddress !== address);
+ }, [cachedConversations, address]);
const [isConsentListLoading, setIsConsentListLoading] = React.useState(true);
- const { loadConsentList, entries } = useConsent();
+ const { entries, loadConsentList } = useConsent();
+ const { client } = useClient();
+
+ const [initialConversations, setInitialConversations] = React.useState<
+ Conversation[] | null
+ >(null);
+ useEffect(() => {
+ if (!client) return;
+
+ const fetchConversations = async () => {
+ const _conversations = await client.conversations.list();
+ setInitialConversations(_conversations);
+ };
+ fetchConversations();
+ }, [client]);
+
+ const primaryNameConversations = useMemo(() => {
+ if (!initialConversations) return;
+ if (conversations?.length > initialConversations?.length) {
+ return conversations;
+ }
+
+ return initialConversations;
+ }, [conversations, initialConversations]);
+ const { allPrimaryNames } = usePrimaryNameBatch({
+ addresses: primaryNameConversations?.map(
+ (conversation) => conversation.peerAddress
+ ),
+
+ enabled: initialConversations !== null,
+ });
const allowedConversations = useMemo(() => {
return conversations.filter(
@@ -91,56 +130,39 @@ export const InboxSheet: React.FC = ({
}, [conversations, entries]);
useEffect(() => {
- let _allowedConversations = [] as CachedConversation[];
- let _blockedConversations = [] as CachedConversation[];
- let _requestConversations = [] as CachedConversation[];
+ const allowedConversationsTopic = allowedConversations.map(
+ (convo) => convo.topic
+ );
- if (
- allowedConversations.some(
- (convo) =>
- !allConversations.allowed.some((c) => c.topic === convo.topic)
- )
- ) {
- _allowedConversations = allowedConversations.filter(
- (convo) =>
- !allConversations.allowed.some((c) => c.topic === convo.topic)
- );
- // onConversationsUpdated([...allConversations, ...newConversations]);
- }
+ const blockedConversationsTopic = blockedConversations.map(
+ (convo) => convo.topic
+ );
- if (
- blockedConversations.some(
- (convo) =>
- !allConversations.blocked.some((c) => c.topic === convo.topic)
- )
- ) {
- _blockedConversations = blockedConversations.filter(
- (convo) =>
- !allConversations.blocked.some((c) => c.topic === convo.topic)
- );
- }
+ const requestConversationsTopic = requestConversations.map(
+ (convo) => convo.topic
+ );
- if (
- requestConversations.some(
- (convo) =>
- !allConversations.requested.some((c) => c.topic === convo.topic)
- )
- ) {
- _requestConversations = requestConversations.filter(
- (convo) =>
- !allConversations.requested.some((c) => c.topic === convo.topic)
- );
- }
+ const allConversationsAllowedTopic = allConversations.allowed.map(
+ (convo) => convo.topic
+ );
+
+ const allConversationsBlockedTopic = allConversations.blocked.map(
+ (convo) => convo.topic
+ );
+
+ const allConversationsRequestedTopic = allConversations.requested.map(
+ (convo) => convo.topic
+ );
if (
- _allowedConversations.length > 0 ||
- _blockedConversations.length > 0 ||
- _requestConversations.length > 0
+ !isEqual(allowedConversationsTopic, allConversationsAllowedTopic) ||
+ !isEqual(blockedConversationsTopic, allConversationsBlockedTopic) ||
+ !isEqual(requestConversationsTopic, allConversationsRequestedTopic)
) {
onConversationsUpdated({
- allowed: [...allConversations.allowed, ..._allowedConversations],
- blocked: [...allConversations.blocked, ..._blockedConversations],
- requested: [...allConversations.requested, ..._requestConversations],
+ allowed: allowedConversations,
+ blocked: blockedConversations,
+ requested: requestConversations,
});
}
}, [
@@ -152,13 +174,15 @@ export const InboxSheet: React.FC = ({
]);
useEffect(() => {
+ if (!isConsentListLoading) return;
loadConsentList().then(() => {
setIsConsentListLoading(false);
});
- }, [loadConsentList]);
+ }, [loadConsentList, isConsentListLoading]);
useStreamConversations();
useStreamAllMessages();
+ useStreamConsentList();
return (
@@ -171,7 +195,7 @@ export const InboxSheet: React.FC = ({
width: 45,
height: 45,
borderRadius: '50%',
- backgroundColor: 'var(--justweb3-primary-color',
+ backgroundColor: 'var(--justweb3-primary-color)',
cursor: 'pointer',
position: 'absolute',
bottom: '2rem',
@@ -186,88 +210,172 @@ export const InboxSheet: React.FC = ({
height={35}
/>
- setTab(value)}
- style={{
- display: 'flex',
- flexDirection: 'column',
- marginBottom: '0px',
- overflow: 'hidden',
- marginTop: '10px',
- flex: '1',
- }}
- >
-
-
- Chats
-
-
+ setTab(value)}
+ style={{
+ display: 'flex',
+ flexDirection: 'column',
+ marginBottom: '0px',
+ // overflow: 'hidden',
+ maxHeight: 'calc(100vh - 72px - 10px - 28px - 10px)',
+ minHeight: 'calc(100vh - 72px - 10px - 28px - 10px)',
+ marginTop: '10px',
+ flex: '1',
+ }}
+ >
+
+
+ Chats
+
+
+ Requests
+ {requestConversations.length > 0 && (
+
+
+ {requestConversations.length}
+
+
+ )}
+
+
+ Blocked
+
+
+ {isLoading || isConsentListLoading ? (
+ // {true ? (
+
+
+ Loading...
+
+
+ ) : (
+ <>
+
+
+
+
+
+
+
+
+
+ >
+ )}
+
+
+
+
- Requests
- {requestConversations.length > 0 && (
-
- {requestConversations.length}
-
- )}
-
-
+
-
- {isLoading || isConsentListLoading ? (
- Loading...
- ) : (
- <>
-
-
+
+
-
-
-
-
-
-
-
- >
- )}
-
+
+
+
+
+
+
+
+
+
+
);
diff --git a/packages/@justweb3/xmtp-plugin/src/lib/components/JustWeb3ButtonRight/index.tsx b/packages/@justweb3/xmtp-plugin/src/lib/components/JustWeb3ButtonRight/index.tsx
new file mode 100644
index 00000000..5358282e
--- /dev/null
+++ b/packages/@justweb3/xmtp-plugin/src/lib/components/JustWeb3ButtonRight/index.tsx
@@ -0,0 +1,84 @@
+import { XmtpEnvironment } from '../../plugins';
+import { NotificationBadge } from '@justweb3/ui';
+import { ChatIcon } from '../../icons/ChatIcon';
+import { useJustWeb3XMTP } from '../../providers/JustWeb3XMTPProvider';
+import { useMemo } from 'react';
+import { Client, ClientOptions, useClient } from '@xmtp/react-sdk';
+import { useEthersSigner } from '../../hooks';
+import { useMountedAccount } from '@justaname.id/react';
+import { loadKeys, storeKeys, wipeKeys } from '../../utils/xmtp';
+
+export interface ChatMenuButtonProps {
+ handleOpen: (open: boolean) => void;
+ env: XmtpEnvironment;
+}
+
+export const JustWeb3ButtonRight: React.FC = ({
+ handleOpen,
+ env,
+}) => {
+ const { conversationsInfo } = useJustWeb3XMTP();
+ const totalUnreadCount = useMemo(() => {
+ return conversationsInfo
+ .filter((conversation) => conversation.consent === 'allowed')
+ .reduce((acc, curr) => acc + curr.unreadCount, 0);
+ }, [conversationsInfo]);
+ const { initialize } = useClient();
+ const { client } = useClient();
+ const walletClient = useEthersSigner();
+ const { address } = useMountedAccount();
+
+ const handleChat = async () => {
+ if (!client) {
+ const signer = walletClient;
+ try {
+ if (!signer) {
+ return;
+ }
+ const clientOptions: Partial> = {
+ appVersion: 'JustWeb3/1.0.0',
+ env: env,
+ };
+ let keys = loadKeys(address ?? '', env);
+ if (!keys) {
+ keys = await Client.getKeys(signer, {
+ env: env,
+ skipContactPublishing: false,
+ // persistConversations: false,
+ });
+ storeKeys(address ?? '', keys, env);
+ }
+ await initialize({
+ keys,
+ options: clientOptions,
+ signer: signer,
+ }).then(() => {
+ handleOpen(true);
+ });
+
+ // handleClient(client)
+ } catch (error) {
+ console.error('Failed to initialize XMTP Client:', error);
+ wipeKeys(address ?? '', env);
+ }
+ } else {
+ handleOpen(true);
+ }
+ };
+
+ return (
+ {
+ e.stopPropagation();
+ handleChat();
+ }}
+ />
+ }
+ />
+ );
+};
diff --git a/packages/@justweb3/xmtp-plugin/src/lib/components/ProfileChatButton/index.tsx b/packages/@justweb3/xmtp-plugin/src/lib/components/ProfileChatButton/index.tsx
index c0965bde..902cb71f 100644
--- a/packages/@justweb3/xmtp-plugin/src/lib/components/ProfileChatButton/index.tsx
+++ b/packages/@justweb3/xmtp-plugin/src/lib/components/ProfileChatButton/index.tsx
@@ -12,12 +12,14 @@ export interface ProfileChatButtonProps {
ens: string;
env: 'local' | 'production' | 'dev';
chainId: ChainId;
+ address: string;
}
export const ProfileChatButton: React.FC = ({
ens,
env,
chainId,
+ address: profileAddress,
}) => {
const { closeEnsProfile } = useJustWeb3();
const { handleOpenChat } = useJustWeb3XMTP();
@@ -92,6 +94,10 @@ export const ProfileChatButton: React.FC = ({
}
}, [canMessage, canMessageAddress, env, records]);
+ if (profileAddress === address) {
+ return null;
+ }
+
return (
) => {
+ return (
+
+ );
+};
diff --git a/packages/@justweb3/xmtp-plugin/src/lib/plugins/index.tsx b/packages/@justweb3/xmtp-plugin/src/lib/plugins/index.tsx
index 90d1a0a3..22758fb3 100644
--- a/packages/@justweb3/xmtp-plugin/src/lib/plugins/index.tsx
+++ b/packages/@justweb3/xmtp-plugin/src/lib/plugins/index.tsx
@@ -2,6 +2,7 @@ import { JustaPlugin } from '@justweb3/widget';
import { JustWeb3XMTPProvider } from '../providers/JustWeb3XMTPProvider';
import { ChatMenuButton } from '../components/ChatMenuButton';
import { ProfileChatButton } from '../components/ProfileChatButton';
+import { JustWeb3ButtonRight } from '../components/JustWeb3ButtonRight';
export type XmtpEnvironment = 'local' | 'production' | 'dev';
@@ -9,6 +10,14 @@ export const XMTPPlugin = (env: XmtpEnvironment): JustaPlugin => {
return {
name: 'XMTPPlugin',
components: {
+ JustWeb3ButtonRight: (pluginApi) => {
+ return (
+
pluginApi.setState('xmtpOpen', open)}
+ env={env}
+ />
+ );
+ },
Provider: (pluginApi, children) => {
return (
{
);
},
ProfileHeader: (pluginApi, ens, chainId, address) => {
- return ;
+ return (
+
+ );
},
SignInMenu: (pluginApi) => {
return (
diff --git a/packages/@justweb3/xmtp-plugin/src/lib/providers/JustWeb3XMTPProvider/index.tsx b/packages/@justweb3/xmtp-plugin/src/lib/providers/JustWeb3XMTPProvider/index.tsx
index 1ee5a0a8..7092f8d0 100644
--- a/packages/@justweb3/xmtp-plugin/src/lib/providers/JustWeb3XMTPProvider/index.tsx
+++ b/packages/@justweb3/xmtp-plugin/src/lib/providers/JustWeb3XMTPProvider/index.tsx
@@ -11,7 +11,6 @@ import {
replyContentTypeConfig,
useClient,
useMessages,
- useStreamMessages,
XMTPProvider,
} from '@xmtp/react-sdk';
import { InboxSheet } from '../../components/InboxSheet';
@@ -56,6 +55,7 @@ export const JustWeb3XMTPProvider: React.FC = ({
handleOpen,
env,
}) => {
+ // const { isConnected } = useMountedAccount()
const [isXmtpEnabled, setIsXmtpEnabled] = React.useState(false);
const [conversation, setConversation] =
React.useState | null>(null);
@@ -77,7 +77,6 @@ export const JustWeb3XMTPProvider: React.FC = ({
}[]
>([]);
- console.log(conversations);
const handleXmtpEnabled = (enabled: boolean) => {
setIsXmtpEnabled(enabled);
};
@@ -116,12 +115,11 @@ export const JustWeb3XMTPProvider: React.FC = ({
}
prev[index].unreadCount = unreadCount;
prev[index].lastMessage = lastMessage;
+ prev[index].consent = consent;
return [...prev];
});
};
- console.log('Conversations Info:', conversationsInfo);
-
return (
= ({
}) => {
const { messages } = useMessages(conversation);
- useStreamMessages(conversation);
const _unreadCount = useMemo(() => {
let count = 0;
const _messages = [...messages].reverse();
@@ -285,12 +282,17 @@ export const GetConversationInfo: React.FC = ({
const _lastMessage = useMemo(() => {
const _messages = [...messages];
- let lastMessage = _messages[_messages.length - 1];
- if (lastMessage?.contentType === ContentTypeReadReceipt.toString()) {
- lastMessage = _messages[_messages.length - 2];
+ // let lastMessage = _messages[_messages.length - 1];
+ let lastMessageIndex = _messages.length - 1;
+ let lastMessage = _messages[lastMessageIndex];
+ while (
+ lastMessage?.contentType === ContentTypeReadReceipt.toString() &&
+ lastMessageIndex > 0
+ ) {
+ lastMessageIndex--;
+ lastMessage = _messages[lastMessageIndex];
}
- console.log('Last Message:', lastMessage);
return lastMessage;
}, [messages]);
@@ -299,12 +301,6 @@ export const GetConversationInfo: React.FC = ({
return;
}
- console.log(
- 'Updating Conversation Info:',
- conversation.topic,
- _unreadCount,
- _lastMessage
- );
handleConversationInfo(conversation.topic, _unreadCount, _lastMessage);
}, [
conversation.topic,
@@ -330,44 +326,42 @@ export const Checks: React.FC = ({
const [rejected, setRejected] = React.useState(false);
useEffect(() => {
+ if (!client || !address || isInitializing) return;
+ if (client.address.toLowerCase() === address.toLowerCase()) return;
+
async function reinitializeXmtp() {
- if (client && address) {
- if (client?.address?.toLowerCase() !== address.toLowerCase()) {
- await disconnect();
-
- if (!signer) {
- return;
- }
- setIsInitializing(true);
- const clientOptions: Partial> = {
- appVersion: 'JustWeb3/1.0.0/' + env + '/0',
- env: env,
- };
- let keys = loadKeys(address ?? '', env);
- console.log('Keys:', keys);
- if (!keys) {
- keys = await Client.getKeys(signer, {
- env: env,
- skipContactPublishing: false,
- // persistConversations: false,
- });
- storeKeys(address ?? '', keys, env);
- }
-
- await initialize({
- keys,
- options: clientOptions,
- signer: signer,
- });
- }
+ await disconnect();
+
+ if (!signer) {
+ return;
}
+ setIsInitializing(true);
+ const clientOptions: Partial> = {
+ appVersion: 'JustWeb3/1.0.0/' + env + '/0',
+ env: env,
+ };
+ let keys = loadKeys(address ?? '', env);
+ if (!keys) {
+ keys = await Client.getKeys(signer, {
+ env: env,
+ skipContactPublishing: false,
+ // persistConversations: false,
+ });
+ storeKeys(address ?? '', keys, env);
+ }
+
+ await initialize({
+ keys,
+ options: clientOptions,
+ signer: signer,
+ });
}
reinitializeXmtp();
- }, [client, address, signer, env, initialize, disconnect]);
+ }, [client, address, signer, env, initialize, disconnect, isInitializing]);
useEffect(() => {
+ if (isInitializing || isLoading || rejected) return;
async function initializeXmtp() {
- if (isInitializing || isLoading || rejected) return;
try {
if (client) {
return;
diff --git a/packages/@justweb3/xmtp-plugin/src/stories/xmtp.stories.tsx b/packages/@justweb3/xmtp-plugin/src/stories/xmtp.stories.tsx
index 03c180ca..6dd6e287 100644
--- a/packages/@justweb3/xmtp-plugin/src/stories/xmtp.stories.tsx
+++ b/packages/@justweb3/xmtp-plugin/src/stories/xmtp.stories.tsx
@@ -1,5 +1,4 @@
import {
- JustEnsCard,
JustWeb3Button,
JustWeb3Provider,
JustWeb3ProviderConfig,
@@ -93,55 +92,55 @@ export const Example = () => {
-
- {/**/}
- {/**/}
- {/**/}
- {/**/}
- {/**/}
- {/**/}
- {/**/}
- {/**/}
- {/**/}
- {/**/}
- {/**/}
- {/**/}
- {/**/}
- {/**/}
- {/**/}
- {/**/}
-
-
-
-
-
-
-
-
-
-
-
-
+ {/**/}
+ {/* */}
+ {/* */}
+ {/* */}
+ {/* */}
+ {/* */}
+ {/* */}
+ {/* */}
+ {/* */}
+ {/* */}
+ {/* */}
+ {/* */}
+ {/* */}
+ {/* */}
+ {/* */}
+ {/* */}
+ {/* */}
+ {/* */}
+ {/* */}
+ {/* */}
+ {/*
*/}
+ {/**/}
+ {/* */}
+ {/* */}
+ {/* */}
+ {/* */}
+ {/* */}
+ {/* */}
+ {/*
*/}
diff --git a/yarn.lock b/yarn.lock
index 902a2f9e..7630baab 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -62,6 +62,31 @@ __metadata:
languageName: node
linkType: hard
+"@antfu/ni@npm:^0.21.12":
+ version: 0.21.12
+ resolution: "@antfu/ni@npm:0.21.12"
+ bin:
+ na: bin/na.mjs
+ nci: bin/nci.mjs
+ ni: bin/ni.mjs
+ nlx: bin/nlx.mjs
+ nr: bin/nr.mjs
+ nu: bin/nu.mjs
+ nun: bin/nun.mjs
+ checksum: 10c0/729e03cdde75087bd1e959357fa69caf5c8ecf80087f19ac07ecceeb0def3eabbe87a8bd88a78444bea321cce8e385a1c90dca4b5146c351e32c2a516c2d9ea2
+ languageName: node
+ linkType: hard
+
+"@axiomhq/js@npm:1.0.0-rc.3":
+ version: 1.0.0-rc.3
+ resolution: "@axiomhq/js@npm:1.0.0-rc.3"
+ dependencies:
+ fetch-retry: "npm:^6.0.0"
+ uuid: "npm:^8.3.2"
+ checksum: 10c0/bcd02c844c19dcfa49e7e75fffbafffea6caa46756e8f194e41b281c807fde26ccdb2198844672ad078c54ba9609949585ffd2c2d0ed9b5db1d74a77dae5f883
+ languageName: node
+ linkType: hard
+
"@babel/code-frame@npm:7.10.4, @babel/code-frame@npm:~7.10.4":
version: 7.10.4
resolution: "@babel/code-frame@npm:7.10.4"
@@ -89,7 +114,7 @@ __metadata:
languageName: node
linkType: hard
-"@babel/core@npm:^7.11.6, @babel/core@npm:^7.12.3, @babel/core@npm:^7.13.16, @babel/core@npm:^7.14.5, @babel/core@npm:^7.18.9, @babel/core@npm:^7.20.0, @babel/core@npm:^7.21.3, @babel/core@npm:^7.22.5, @babel/core@npm:^7.23.0, @babel/core@npm:^7.23.2, @babel/core@npm:^7.23.9, @babel/core@npm:^7.24.4, @babel/core@npm:^7.25.2, @babel/core@npm:^7.7.5":
+"@babel/core@npm:7.26.0, @babel/core@npm:^7.11.6, @babel/core@npm:^7.12.3, @babel/core@npm:^7.13.16, @babel/core@npm:^7.14.5, @babel/core@npm:^7.18.9, @babel/core@npm:^7.20.0, @babel/core@npm:^7.21.3, @babel/core@npm:^7.22.5, @babel/core@npm:^7.23.0, @babel/core@npm:^7.23.2, @babel/core@npm:^7.23.9, @babel/core@npm:^7.24.4, @babel/core@npm:^7.25.2, @babel/core@npm:^7.7.5":
version: 7.26.0
resolution: "@babel/core@npm:7.26.0"
dependencies:
@@ -1707,7 +1732,7 @@ __metadata:
languageName: node
linkType: hard
-"@babel/types@npm:^7.0.0, @babel/types@npm:^7.18.9, @babel/types@npm:^7.20.0, @babel/types@npm:^7.20.7, @babel/types@npm:^7.21.3, @babel/types@npm:^7.22.5, @babel/types@npm:^7.23.0, @babel/types@npm:^7.24.0, @babel/types@npm:^7.24.7, @babel/types@npm:^7.25.4, @babel/types@npm:^7.25.9, @babel/types@npm:^7.26.0, @babel/types@npm:^7.3.3, @babel/types@npm:^7.4.4":
+"@babel/types@npm:7.26.0, @babel/types@npm:^7.0.0, @babel/types@npm:^7.18.9, @babel/types@npm:^7.20.0, @babel/types@npm:^7.20.7, @babel/types@npm:^7.21.3, @babel/types@npm:^7.22.5, @babel/types@npm:^7.23.0, @babel/types@npm:^7.24.0, @babel/types@npm:^7.24.7, @babel/types@npm:^7.25.4, @babel/types@npm:^7.25.9, @babel/types@npm:^7.26.0, @babel/types@npm:^7.3.3, @babel/types@npm:^7.4.4":
version: 7.26.0
resolution: "@babel/types@npm:7.26.0"
dependencies:
@@ -1798,7 +1823,7 @@ __metadata:
languageName: node
linkType: hard
-"@clack/core@npm:0.3.5, @clack/core@npm:^0.3.5":
+"@clack/core@npm:0.3.5, @clack/core@npm:^0.3.3, @clack/core@npm:^0.3.5":
version: 0.3.5
resolution: "@clack/core@npm:0.3.5"
dependencies:
@@ -1808,6 +1833,18 @@ __metadata:
languageName: node
linkType: hard
+"@clack/prompts@npm:^0.7.0":
+ version: 0.7.0
+ resolution: "@clack/prompts@npm:0.7.0"
+ dependencies:
+ "@clack/core": "npm:^0.3.3"
+ is-unicode-supported: "npm:*"
+ picocolors: "npm:^1.0.0"
+ sisteransi: "npm:^1.0.5"
+ checksum: 10c0/fecb3b34308c5cb75807211b28d50caa4b0c5d150d16e733e59bfba3187ac856f050ed44baeca90eb99e047671096ff54402dd2790e9c0e77845a75b04003e2e
+ languageName: node
+ linkType: hard
+
"@clack/prompts@npm:^0.8.2":
version: 0.8.2
resolution: "@clack/prompts@npm:0.8.2"
@@ -2111,6 +2148,13 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/aix-ppc64@npm:0.20.2":
+ version: 0.20.2
+ resolution: "@esbuild/aix-ppc64@npm:0.20.2"
+ conditions: os=aix & cpu=ppc64
+ languageName: node
+ linkType: hard
+
"@esbuild/aix-ppc64@npm:0.21.5":
version: 0.21.5
resolution: "@esbuild/aix-ppc64@npm:0.21.5"
@@ -2132,6 +2176,13 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/android-arm64@npm:0.20.2":
+ version: 0.20.2
+ resolution: "@esbuild/android-arm64@npm:0.20.2"
+ conditions: os=android & cpu=arm64
+ languageName: node
+ linkType: hard
+
"@esbuild/android-arm64@npm:0.21.5":
version: 0.21.5
resolution: "@esbuild/android-arm64@npm:0.21.5"
@@ -2153,6 +2204,13 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/android-arm@npm:0.20.2":
+ version: 0.20.2
+ resolution: "@esbuild/android-arm@npm:0.20.2"
+ conditions: os=android & cpu=arm
+ languageName: node
+ linkType: hard
+
"@esbuild/android-arm@npm:0.21.5":
version: 0.21.5
resolution: "@esbuild/android-arm@npm:0.21.5"
@@ -2174,6 +2232,13 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/android-x64@npm:0.20.2":
+ version: 0.20.2
+ resolution: "@esbuild/android-x64@npm:0.20.2"
+ conditions: os=android & cpu=x64
+ languageName: node
+ linkType: hard
+
"@esbuild/android-x64@npm:0.21.5":
version: 0.21.5
resolution: "@esbuild/android-x64@npm:0.21.5"
@@ -2195,6 +2260,13 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/darwin-arm64@npm:0.20.2":
+ version: 0.20.2
+ resolution: "@esbuild/darwin-arm64@npm:0.20.2"
+ conditions: os=darwin & cpu=arm64
+ languageName: node
+ linkType: hard
+
"@esbuild/darwin-arm64@npm:0.21.5":
version: 0.21.5
resolution: "@esbuild/darwin-arm64@npm:0.21.5"
@@ -2216,6 +2288,13 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/darwin-x64@npm:0.20.2":
+ version: 0.20.2
+ resolution: "@esbuild/darwin-x64@npm:0.20.2"
+ conditions: os=darwin & cpu=x64
+ languageName: node
+ linkType: hard
+
"@esbuild/darwin-x64@npm:0.21.5":
version: 0.21.5
resolution: "@esbuild/darwin-x64@npm:0.21.5"
@@ -2237,6 +2316,13 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/freebsd-arm64@npm:0.20.2":
+ version: 0.20.2
+ resolution: "@esbuild/freebsd-arm64@npm:0.20.2"
+ conditions: os=freebsd & cpu=arm64
+ languageName: node
+ linkType: hard
+
"@esbuild/freebsd-arm64@npm:0.21.5":
version: 0.21.5
resolution: "@esbuild/freebsd-arm64@npm:0.21.5"
@@ -2258,6 +2344,13 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/freebsd-x64@npm:0.20.2":
+ version: 0.20.2
+ resolution: "@esbuild/freebsd-x64@npm:0.20.2"
+ conditions: os=freebsd & cpu=x64
+ languageName: node
+ linkType: hard
+
"@esbuild/freebsd-x64@npm:0.21.5":
version: 0.21.5
resolution: "@esbuild/freebsd-x64@npm:0.21.5"
@@ -2279,6 +2372,13 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/linux-arm64@npm:0.20.2":
+ version: 0.20.2
+ resolution: "@esbuild/linux-arm64@npm:0.20.2"
+ conditions: os=linux & cpu=arm64
+ languageName: node
+ linkType: hard
+
"@esbuild/linux-arm64@npm:0.21.5":
version: 0.21.5
resolution: "@esbuild/linux-arm64@npm:0.21.5"
@@ -2300,6 +2400,13 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/linux-arm@npm:0.20.2":
+ version: 0.20.2
+ resolution: "@esbuild/linux-arm@npm:0.20.2"
+ conditions: os=linux & cpu=arm
+ languageName: node
+ linkType: hard
+
"@esbuild/linux-arm@npm:0.21.5":
version: 0.21.5
resolution: "@esbuild/linux-arm@npm:0.21.5"
@@ -2321,6 +2428,13 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/linux-ia32@npm:0.20.2":
+ version: 0.20.2
+ resolution: "@esbuild/linux-ia32@npm:0.20.2"
+ conditions: os=linux & cpu=ia32
+ languageName: node
+ linkType: hard
+
"@esbuild/linux-ia32@npm:0.21.5":
version: 0.21.5
resolution: "@esbuild/linux-ia32@npm:0.21.5"
@@ -2342,6 +2456,13 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/linux-loong64@npm:0.20.2":
+ version: 0.20.2
+ resolution: "@esbuild/linux-loong64@npm:0.20.2"
+ conditions: os=linux & cpu=loong64
+ languageName: node
+ linkType: hard
+
"@esbuild/linux-loong64@npm:0.21.5":
version: 0.21.5
resolution: "@esbuild/linux-loong64@npm:0.21.5"
@@ -2363,6 +2484,13 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/linux-mips64el@npm:0.20.2":
+ version: 0.20.2
+ resolution: "@esbuild/linux-mips64el@npm:0.20.2"
+ conditions: os=linux & cpu=mips64el
+ languageName: node
+ linkType: hard
+
"@esbuild/linux-mips64el@npm:0.21.5":
version: 0.21.5
resolution: "@esbuild/linux-mips64el@npm:0.21.5"
@@ -2384,6 +2512,13 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/linux-ppc64@npm:0.20.2":
+ version: 0.20.2
+ resolution: "@esbuild/linux-ppc64@npm:0.20.2"
+ conditions: os=linux & cpu=ppc64
+ languageName: node
+ linkType: hard
+
"@esbuild/linux-ppc64@npm:0.21.5":
version: 0.21.5
resolution: "@esbuild/linux-ppc64@npm:0.21.5"
@@ -2405,6 +2540,13 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/linux-riscv64@npm:0.20.2":
+ version: 0.20.2
+ resolution: "@esbuild/linux-riscv64@npm:0.20.2"
+ conditions: os=linux & cpu=riscv64
+ languageName: node
+ linkType: hard
+
"@esbuild/linux-riscv64@npm:0.21.5":
version: 0.21.5
resolution: "@esbuild/linux-riscv64@npm:0.21.5"
@@ -2426,6 +2568,13 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/linux-s390x@npm:0.20.2":
+ version: 0.20.2
+ resolution: "@esbuild/linux-s390x@npm:0.20.2"
+ conditions: os=linux & cpu=s390x
+ languageName: node
+ linkType: hard
+
"@esbuild/linux-s390x@npm:0.21.5":
version: 0.21.5
resolution: "@esbuild/linux-s390x@npm:0.21.5"
@@ -2447,6 +2596,13 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/linux-x64@npm:0.20.2":
+ version: 0.20.2
+ resolution: "@esbuild/linux-x64@npm:0.20.2"
+ conditions: os=linux & cpu=x64
+ languageName: node
+ linkType: hard
+
"@esbuild/linux-x64@npm:0.21.5":
version: 0.21.5
resolution: "@esbuild/linux-x64@npm:0.21.5"
@@ -2468,6 +2624,13 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/netbsd-x64@npm:0.20.2":
+ version: 0.20.2
+ resolution: "@esbuild/netbsd-x64@npm:0.20.2"
+ conditions: os=netbsd & cpu=x64
+ languageName: node
+ linkType: hard
+
"@esbuild/netbsd-x64@npm:0.21.5":
version: 0.21.5
resolution: "@esbuild/netbsd-x64@npm:0.21.5"
@@ -2489,6 +2652,13 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/openbsd-x64@npm:0.20.2":
+ version: 0.20.2
+ resolution: "@esbuild/openbsd-x64@npm:0.20.2"
+ conditions: os=openbsd & cpu=x64
+ languageName: node
+ linkType: hard
+
"@esbuild/openbsd-x64@npm:0.21.5":
version: 0.21.5
resolution: "@esbuild/openbsd-x64@npm:0.21.5"
@@ -2510,6 +2680,13 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/sunos-x64@npm:0.20.2":
+ version: 0.20.2
+ resolution: "@esbuild/sunos-x64@npm:0.20.2"
+ conditions: os=sunos & cpu=x64
+ languageName: node
+ linkType: hard
+
"@esbuild/sunos-x64@npm:0.21.5":
version: 0.21.5
resolution: "@esbuild/sunos-x64@npm:0.21.5"
@@ -2531,6 +2708,13 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/win32-arm64@npm:0.20.2":
+ version: 0.20.2
+ resolution: "@esbuild/win32-arm64@npm:0.20.2"
+ conditions: os=win32 & cpu=arm64
+ languageName: node
+ linkType: hard
+
"@esbuild/win32-arm64@npm:0.21.5":
version: 0.21.5
resolution: "@esbuild/win32-arm64@npm:0.21.5"
@@ -2552,6 +2736,13 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/win32-ia32@npm:0.20.2":
+ version: 0.20.2
+ resolution: "@esbuild/win32-ia32@npm:0.20.2"
+ conditions: os=win32 & cpu=ia32
+ languageName: node
+ linkType: hard
+
"@esbuild/win32-ia32@npm:0.21.5":
version: 0.21.5
resolution: "@esbuild/win32-ia32@npm:0.21.5"
@@ -2573,6 +2764,13 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/win32-x64@npm:0.20.2":
+ version: 0.20.2
+ resolution: "@esbuild/win32-x64@npm:0.20.2"
+ conditions: os=win32 & cpu=x64
+ languageName: node
+ linkType: hard
+
"@esbuild/win32-x64@npm:0.21.5":
version: 0.21.5
resolution: "@esbuild/win32-x64@npm:0.21.5"
@@ -3652,6 +3850,15 @@ __metadata:
languageName: node
linkType: hard
+"@hono/node-server@npm:^1.11.1":
+ version: 1.13.7
+ resolution: "@hono/node-server@npm:1.13.7"
+ peerDependencies:
+ hono: ^4
+ checksum: 10c0/a50da48fd0b5c647db20a4cefdbec10af957753427bb6b66388f2716a9d3910f192f2f51d0a3ddb9e5e0d6f9057451daabbd84fe8f6190e5ab15c35f86ebaafa
+ languageName: node
+ linkType: hard
+
"@hookform/error-message@npm:^2.0.1":
version: 2.0.1
resolution: "@hookform/error-message@npm:2.0.1"
@@ -5074,6 +5281,66 @@ __metadata:
languageName: node
linkType: hard
+"@million/install@npm:latest":
+ version: 1.0.13
+ resolution: "@million/install@npm:1.0.13"
+ dependencies:
+ "@antfu/ni": "npm:^0.21.12"
+ "@axiomhq/js": "npm:1.0.0-rc.3"
+ "@babel/parser": "npm:^7.25.3"
+ "@babel/types": "npm:7.26.0"
+ "@clack/prompts": "npm:^0.7.0"
+ ast-types: "npm:^0.14.2"
+ cli-high: "npm:^0.4.2"
+ diff: "npm:^5.1.0"
+ effect: "npm:^3.8.4"
+ nanoid: "npm:^5.0.7"
+ recast: "npm:^0.23.9"
+ xycolors: "npm:^0.1.2"
+ bin:
+ install: bin/index.js
+ checksum: 10c0/496617a85663b95b196c698cb6b768aef18954d932eaf01760a5f1299fbe91f4010720bc835596a05a2124bf580cb13eb0b04bc6f12d0b4158d3096290b55307
+ languageName: node
+ linkType: hard
+
+"@million/lint@npm:^1.0.13":
+ version: 1.0.13
+ resolution: "@million/lint@npm:1.0.13"
+ dependencies:
+ "@axiomhq/js": "npm:1.0.0-rc.3"
+ "@babel/core": "npm:7.26.0"
+ "@babel/types": "npm:7.26.0"
+ "@hono/node-server": "npm:^1.11.1"
+ "@million/install": "npm:latest"
+ "@rollup/pluginutils": "npm:^5.1.0"
+ "@rrweb/types": "npm:2.0.0-alpha.16"
+ babel-plugin-syntax-hermes-parser: "npm:^0.21.1"
+ ci-info: "npm:^4.0.0"
+ esbuild: "npm:^0.20.1"
+ faster-babel-types: "npm:^0.1.0"
+ hono: "npm:^4.5.9"
+ isomorphic-fetch: "npm:^3.0.0"
+ nanoid: "npm:^5.0.7"
+ ohash: "npm:^1.1.4"
+ pako: "npm:^2.1.0"
+ pathe: "npm:^1.1.2"
+ piscina: "npm:^4.4.0"
+ pretty-ms: "npm:8.0.0"
+ react-scan: "npm:^0.0.31"
+ rrweb: "npm:2.0.0-alpha.4"
+ rrweb-player: "npm:1.0.0-alpha.4"
+ semver: "npm:^7.6.2"
+ socket.io: "npm:^4.8.1"
+ socket.io-client: "npm:^4.7.5"
+ tmp: "npm:^0.2.3"
+ unplugin: "npm:^1.6.0"
+ update-notifier-cjs: "npm:^5.1.6"
+ bin:
+ lint: cli.js
+ checksum: 10c0/bd5d28ed49137edf20fa9c0bf1ecc429d2ef0f5fda63ff4f020861bce9049d9dd3b6d52f9daa5a5816b312486c7919a802e567e3bac7fa2491d582d3a27cdce5
+ languageName: node
+ linkType: hard
+
"@module-federation/bridge-react-webpack-plugin@npm:0.6.14":
version: 0.6.14
resolution: "@module-federation/bridge-react-webpack-plugin@npm:0.6.14"
@@ -8725,6 +8992,29 @@ __metadata:
languageName: node
linkType: hard
+"@rrweb/types@npm:2.0.0-alpha.16":
+ version: 2.0.0-alpha.16
+ resolution: "@rrweb/types@npm:2.0.0-alpha.16"
+ dependencies:
+ rrweb-snapshot: "npm:^2.0.0-alpha.16"
+ checksum: 10c0/d2eb1e755c3bed7fda19ebdaca92df5ec83979326d2597c95fcdd1a59e4f0fbd17b6f3f5bc48e6faaf098a4521f13cf1aeaf9247e026d8c02e0843ea313498d0
+ languageName: node
+ linkType: hard
+
+"@rrweb/types@npm:^2.0.0-alpha.18, @rrweb/types@npm:^2.0.0-alpha.4":
+ version: 2.0.0-alpha.18
+ resolution: "@rrweb/types@npm:2.0.0-alpha.18"
+ checksum: 10c0/a1adb842f59b782e04576a7e88ffc2e43b8b29460555922e6a15f8ffd4840e554e96c1331653b0a322a3a8f25e73a9ae38ff37d3e60f1da71b6f6c0d001a20e7
+ languageName: node
+ linkType: hard
+
+"@rrweb/utils@npm:^2.0.0-alpha.18":
+ version: 2.0.0-alpha.18
+ resolution: "@rrweb/utils@npm:2.0.0-alpha.18"
+ checksum: 10c0/04a24b838de7294254ead08fbdc39daa9e04a86ed519eba3d6a85d485da56da02aa19cd61d82606c538d324060d6a0fc80fc41813d474e2b57ad9b61e74a137c
+ languageName: node
+ linkType: hard
+
"@rtsao/scc@npm:^1.1.0":
version: 1.1.0
resolution: "@rtsao/scc@npm:1.1.0"
@@ -11304,6 +11594,13 @@ __metadata:
languageName: node
linkType: hard
+"@tsconfig/svelte@npm:^1.0.0":
+ version: 1.0.13
+ resolution: "@tsconfig/svelte@npm:1.0.13"
+ checksum: 10c0/701da672b70300a023754eca604788fe96542178cbf362122bb1083a81f3ff4f8922213960abb99aaf194977e0582eb76a51695a9dd9b5fd6a458e17fb0c3074
+ languageName: node
+ linkType: hard
+
"@tufjs/canonical-json@npm:2.0.0":
version: 2.0.0
resolution: "@tufjs/canonical-json@npm:2.0.0"
@@ -11454,7 +11751,14 @@ __metadata:
languageName: node
linkType: hard
-"@types/cors@npm:^2.8.17":
+"@types/cookie@npm:^0.4.1":
+ version: 0.4.1
+ resolution: "@types/cookie@npm:0.4.1"
+ checksum: 10c0/f96afe12bd51be1ec61410b0641243d93fa3a494702407c787a4c872b5c8bcd39b224471452055e44a9ce42af1a636e87d161994226eaf4c2be9c30f60418409
+ languageName: node
+ linkType: hard
+
+"@types/cors@npm:^2.8.12, @types/cors@npm:^2.8.17":
version: 2.8.17
resolution: "@types/cors@npm:2.8.17"
dependencies:
@@ -11472,6 +11776,13 @@ __metadata:
languageName: node
linkType: hard
+"@types/css-font-loading-module@npm:0.0.7":
+ version: 0.0.7
+ resolution: "@types/css-font-loading-module@npm:0.0.7"
+ checksum: 10c0/a74759a14bcc7d60a1a1d863b53b7638d4aa7f88f1d97347426262cc6fe8f9335d8fa80c7e0608cd67e33ff0067608e9b5475a1227a684e1dfad3cac87df1405
+ languageName: node
+ linkType: hard
+
"@types/debug@npm:^4.1.7":
version: 4.1.12
resolution: "@types/debug@npm:4.1.12"
@@ -11844,6 +12155,15 @@ __metadata:
languageName: node
linkType: hard
+"@types/node@npm:>=10.0.0":
+ version: 22.10.1
+ resolution: "@types/node@npm:22.10.1"
+ dependencies:
+ undici-types: "npm:~6.20.0"
+ checksum: 10c0/0fbb6d29fa35d807f0223a4db709c598ac08d66820240a2cd6a8a69b8f0bc921d65b339d850a666b43b4e779f967e6ed6cf6f0fca3575e08241e6b900364c234
+ languageName: node
+ linkType: hard
+
"@types/node@npm:>=13.7.0":
version: 22.9.1
resolution: "@types/node@npm:22.9.1"
@@ -14397,6 +14717,13 @@ __metadata:
languageName: node
linkType: hard
+"@xstate/fsm@npm:^1.4.0":
+ version: 1.6.5
+ resolution: "@xstate/fsm@npm:1.6.5"
+ checksum: 10c0/472fe625b84b9e7102b8774e80c441b8b7dbc9585e700223d9c7a39c583d38ba50636909a5d749d44b361555daa841862f932a29c52b81c8829a74884e715bf4
+ languageName: node
+ linkType: hard
+
"@xtuc/ieee754@npm:^1.2.0":
version: 1.2.0
resolution: "@xtuc/ieee754@npm:1.2.0"
@@ -14741,6 +15068,15 @@ __metadata:
languageName: node
linkType: hard
+"ansi-align@npm:^3.0.0":
+ version: 3.0.1
+ resolution: "ansi-align@npm:3.0.1"
+ dependencies:
+ string-width: "npm:^4.1.0"
+ checksum: 10c0/ad8b755a253a1bc8234eb341e0cec68a857ab18bf97ba2bda529e86f6e30460416523e0ec58c32e5c21f0ca470d779503244892873a5895dbd0c39c788e82467
+ languageName: node
+ linkType: hard
+
"ansi-colors@npm:^4.1.1, ansi-colors@npm:^4.1.3":
version: 4.1.3
resolution: "ansi-colors@npm:4.1.3"
@@ -15209,6 +15545,15 @@ __metadata:
languageName: node
linkType: hard
+"ast-types@npm:^0.14.2":
+ version: 0.14.2
+ resolution: "ast-types@npm:0.14.2"
+ dependencies:
+ tslib: "npm:^2.0.1"
+ checksum: 10c0/5d66d89b6c07fe092087454b6042dbaf81f2882b176db93861e2b986aafe0bce49e1f1ff59aac775d451c1426ad1e967d250e9e3548f5166ea8a3475e66c169d
+ languageName: node
+ linkType: hard
+
"ast-types@npm:^0.16.1":
version: 0.16.1
resolution: "ast-types@npm:0.16.1"
@@ -15567,6 +15912,15 @@ __metadata:
languageName: node
linkType: hard
+"babel-plugin-syntax-hermes-parser@npm:^0.21.1":
+ version: 0.21.1
+ resolution: "babel-plugin-syntax-hermes-parser@npm:0.21.1"
+ dependencies:
+ hermes-parser: "npm:0.21.1"
+ checksum: 10c0/0134b435e194654f5ba96b6f48ea8ac37c31e9f52e29261bddeefb0cb98852a13cf58a569dd1f39479098dbaf54a0152bff995faf58b8206eb64c2ffaa800b8d
+ languageName: node
+ linkType: hard
+
"babel-plugin-syntax-trailing-function-commas@npm:^7.0.0-beta.0":
version: 7.0.0-beta.0
resolution: "babel-plugin-syntax-trailing-function-commas@npm:7.0.0-beta.0"
@@ -15768,6 +16122,13 @@ __metadata:
languageName: node
linkType: hard
+"base64-arraybuffer@npm:^1.0.1":
+ version: 1.0.2
+ resolution: "base64-arraybuffer@npm:1.0.2"
+ checksum: 10c0/3acac95c70f9406e87a41073558ba85b6be9dbffb013a3d2a710e3f2d534d506c911847d5d9be4de458af6362c676de0a5c4c2d7bdf4def502d00b313368e72f
+ languageName: node
+ linkType: hard
+
"base64-js@npm:^1.2.3, base64-js@npm:^1.3.0, base64-js@npm:^1.3.1, base64-js@npm:^1.5.1":
version: 1.5.1
resolution: "base64-js@npm:1.5.1"
@@ -15775,6 +16136,13 @@ __metadata:
languageName: node
linkType: hard
+"base64id@npm:2.0.0, base64id@npm:~2.0.0":
+ version: 2.0.0
+ resolution: "base64id@npm:2.0.0"
+ checksum: 10c0/6919efd237ed44b9988cbfc33eca6f173a10e810ce50292b271a1a421aac7748ef232a64d1e6032b08f19aae48dce6ee8f66c5ae2c9e5066c82b884861d4d453
+ languageName: node
+ linkType: hard
+
"base64url@npm:3.0.1, base64url@npm:^3.0.1":
version: 3.0.1
resolution: "base64url@npm:3.0.1"
@@ -16013,6 +16381,22 @@ __metadata:
languageName: node
linkType: hard
+"boxen@npm:^5.0.0":
+ version: 5.1.2
+ resolution: "boxen@npm:5.1.2"
+ dependencies:
+ ansi-align: "npm:^3.0.0"
+ camelcase: "npm:^6.2.0"
+ chalk: "npm:^4.1.0"
+ cli-boxes: "npm:^2.2.1"
+ string-width: "npm:^4.2.2"
+ type-fest: "npm:^0.20.2"
+ widest-line: "npm:^3.1.0"
+ wrap-ansi: "npm:^7.0.0"
+ checksum: 10c0/71f31c2eb3dcacd5fce524ae509e0cc90421752e0bfbd0281fd3352871d106c462a0f810c85f2fdb02f3a9fab2d7a84e9718b4999384d651b76104ebe5d2c024
+ languageName: node
+ linkType: hard
+
"bplist-creator@npm:0.1.1":
version: 0.1.1
resolution: "bplist-creator@npm:0.1.1"
@@ -16975,6 +17359,13 @@ __metadata:
languageName: node
linkType: hard
+"cli-boxes@npm:^2.2.1":
+ version: 2.2.1
+ resolution: "cli-boxes@npm:2.2.1"
+ checksum: 10c0/6111352edbb2f62dbc7bfd58f2d534de507afed7f189f13fa894ce5a48badd94b2aa502fda28f1d7dd5f1eb456e7d4033d09a76660013ef50c7f66e7a034f050
+ languageName: node
+ linkType: hard
+
"cli-columns@npm:^4.0.0":
version: 4.0.0
resolution: "cli-columns@npm:4.0.0"
@@ -17012,6 +17403,20 @@ __metadata:
languageName: node
linkType: hard
+"cli-high@npm:^0.4.2":
+ version: 0.4.3
+ resolution: "cli-high@npm:0.4.3"
+ dependencies:
+ "@clack/prompts": "npm:^0.7.0"
+ sugar-high: "npm:^0.7.1"
+ xycolors: "npm:^0.1.2"
+ yargs: "npm:^17.7.2"
+ bin:
+ cli-high: bin/index.js
+ checksum: 10c0/0f93a06436d20d7224fd21a815c46dcad10952724ba7dc3d3ac67ff60fa818676238115fac4620547322e92b450cc88f642efb449ad05c71b648e0fe10a7ec17
+ languageName: node
+ linkType: hard
+
"cli-spinners@npm:2.6.1":
version: 2.6.1
resolution: "cli-spinners@npm:2.6.1"
@@ -17469,6 +17874,20 @@ __metadata:
languageName: node
linkType: hard
+"configstore@npm:^5.0.1":
+ version: 5.0.1
+ resolution: "configstore@npm:5.0.1"
+ dependencies:
+ dot-prop: "npm:^5.2.0"
+ graceful-fs: "npm:^4.1.2"
+ make-dir: "npm:^3.0.0"
+ unique-string: "npm:^2.0.0"
+ write-file-atomic: "npm:^3.0.0"
+ xdg-basedir: "npm:^4.0.0"
+ checksum: 10c0/5af23830e78bdc56cbe92a2f81e87f1d3a39e96e51a0ab2a8bc79bbbc5d4440a48d92833b3fd9c6d34b4a9c4c5853c8487b8e6e68593e7ecbc7434822f7aced3
+ languageName: node
+ linkType: hard
+
"confusing-browser-globals@npm:^1.0.9":
version: 1.0.11
resolution: "confusing-browser-globals@npm:1.0.11"
@@ -17627,7 +18046,7 @@ __metadata:
languageName: node
linkType: hard
-"cookie@npm:0.7.2":
+"cookie@npm:0.7.2, cookie@npm:~0.7.2":
version: 0.7.2
resolution: "cookie@npm:0.7.2"
checksum: 10c0/9596e8ccdbf1a3a88ae02cf5ee80c1c50959423e1022e4e60b91dd87c622af1da309253d8abdb258fb5e3eacb4f08e579dc58b4897b8087574eee0fd35dfa5d2
@@ -17706,7 +18125,7 @@ __metadata:
languageName: node
linkType: hard
-"cors@npm:2.8.5, cors@npm:^2.8.5":
+"cors@npm:2.8.5, cors@npm:^2.8.5, cors@npm:~2.8.5":
version: 2.8.5
resolution: "cors@npm:2.8.5"
dependencies:
@@ -18560,7 +18979,7 @@ __metadata:
languageName: node
linkType: hard
-"debug@npm:4, debug@npm:4.3.7, debug@npm:^4.0.0, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.4, debug@npm:^4.3.5, debug@npm:^4.3.6, debug@npm:~4.3.1, debug@npm:~4.3.2":
+"debug@npm:4, debug@npm:4.3.7, debug@npm:^4.0.0, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.4, debug@npm:^4.3.5, debug@npm:^4.3.6, debug@npm:~4.3.1, debug@npm:~4.3.2, debug@npm:~4.3.4":
version: 4.3.7
resolution: "debug@npm:4.3.7"
dependencies:
@@ -19241,7 +19660,7 @@ __metadata:
languageName: node
linkType: hard
-"dot-prop@npm:^5.1.0":
+"dot-prop@npm:^5.1.0, dot-prop@npm:^5.2.0":
version: 5.3.0
resolution: "dot-prop@npm:5.3.0"
dependencies:
@@ -19349,6 +19768,15 @@ __metadata:
languageName: node
linkType: hard
+"effect@npm:^3.8.4":
+ version: 3.11.3
+ resolution: "effect@npm:3.11.3"
+ dependencies:
+ fast-check: "npm:^3.21.0"
+ checksum: 10c0/a343a20d833112e21b27f17f404e97a33240fafe09d638428381d8bb96b4f0d2eccdaebe43453dce4982242134e1c8cfbab1d63e364fea79de02faeb92940c04
+ languageName: node
+ linkType: hard
+
"ejs@npm:^3.1.10, ejs@npm:^3.1.7":
version: 3.1.10
resolution: "ejs@npm:3.1.10"
@@ -19541,6 +19969,24 @@ __metadata:
languageName: node
linkType: hard
+"engine.io@npm:~6.6.0":
+ version: 6.6.2
+ resolution: "engine.io@npm:6.6.2"
+ dependencies:
+ "@types/cookie": "npm:^0.4.1"
+ "@types/cors": "npm:^2.8.12"
+ "@types/node": "npm:>=10.0.0"
+ accepts: "npm:~1.3.4"
+ base64id: "npm:2.0.0"
+ cookie: "npm:~0.7.2"
+ cors: "npm:~2.8.5"
+ debug: "npm:~4.3.1"
+ engine.io-parser: "npm:~5.2.1"
+ ws: "npm:~8.17.1"
+ checksum: 10c0/e9ac3cba49badb6905259df3b019fbcbe53e2a389c930fb9fbc10eebc8839554b189706206bba2509a4a3a7d78a32f7e027f73230f31662c7efd215276432dad
+ languageName: node
+ linkType: hard
+
"enhanced-resolve@npm:^5.0.0, enhanced-resolve@npm:^5.15.0, enhanced-resolve@npm:^5.17.1, enhanced-resolve@npm:^5.7.0":
version: 5.17.1
resolution: "enhanced-resolve@npm:5.17.1"
@@ -20136,6 +20582,86 @@ __metadata:
languageName: node
linkType: hard
+"esbuild@npm:^0.20.1":
+ version: 0.20.2
+ resolution: "esbuild@npm:0.20.2"
+ dependencies:
+ "@esbuild/aix-ppc64": "npm:0.20.2"
+ "@esbuild/android-arm": "npm:0.20.2"
+ "@esbuild/android-arm64": "npm:0.20.2"
+ "@esbuild/android-x64": "npm:0.20.2"
+ "@esbuild/darwin-arm64": "npm:0.20.2"
+ "@esbuild/darwin-x64": "npm:0.20.2"
+ "@esbuild/freebsd-arm64": "npm:0.20.2"
+ "@esbuild/freebsd-x64": "npm:0.20.2"
+ "@esbuild/linux-arm": "npm:0.20.2"
+ "@esbuild/linux-arm64": "npm:0.20.2"
+ "@esbuild/linux-ia32": "npm:0.20.2"
+ "@esbuild/linux-loong64": "npm:0.20.2"
+ "@esbuild/linux-mips64el": "npm:0.20.2"
+ "@esbuild/linux-ppc64": "npm:0.20.2"
+ "@esbuild/linux-riscv64": "npm:0.20.2"
+ "@esbuild/linux-s390x": "npm:0.20.2"
+ "@esbuild/linux-x64": "npm:0.20.2"
+ "@esbuild/netbsd-x64": "npm:0.20.2"
+ "@esbuild/openbsd-x64": "npm:0.20.2"
+ "@esbuild/sunos-x64": "npm:0.20.2"
+ "@esbuild/win32-arm64": "npm:0.20.2"
+ "@esbuild/win32-ia32": "npm:0.20.2"
+ "@esbuild/win32-x64": "npm:0.20.2"
+ dependenciesMeta:
+ "@esbuild/aix-ppc64":
+ optional: true
+ "@esbuild/android-arm":
+ optional: true
+ "@esbuild/android-arm64":
+ optional: true
+ "@esbuild/android-x64":
+ optional: true
+ "@esbuild/darwin-arm64":
+ optional: true
+ "@esbuild/darwin-x64":
+ optional: true
+ "@esbuild/freebsd-arm64":
+ optional: true
+ "@esbuild/freebsd-x64":
+ optional: true
+ "@esbuild/linux-arm":
+ optional: true
+ "@esbuild/linux-arm64":
+ optional: true
+ "@esbuild/linux-ia32":
+ optional: true
+ "@esbuild/linux-loong64":
+ optional: true
+ "@esbuild/linux-mips64el":
+ optional: true
+ "@esbuild/linux-ppc64":
+ optional: true
+ "@esbuild/linux-riscv64":
+ optional: true
+ "@esbuild/linux-s390x":
+ optional: true
+ "@esbuild/linux-x64":
+ optional: true
+ "@esbuild/netbsd-x64":
+ optional: true
+ "@esbuild/openbsd-x64":
+ optional: true
+ "@esbuild/sunos-x64":
+ optional: true
+ "@esbuild/win32-arm64":
+ optional: true
+ "@esbuild/win32-ia32":
+ optional: true
+ "@esbuild/win32-x64":
+ optional: true
+ bin:
+ esbuild: bin/esbuild
+ checksum: 10c0/66398f9fb2c65e456a3e649747b39af8a001e47963b25e86d9c09d2a48d61aa641b27da0ce5cad63df95ad246105e1d83e7fee0e1e22a0663def73b1c5101112
+ languageName: node
+ linkType: hard
+
"escalade@npm:^3.1.1, escalade@npm:^3.2.0":
version: 3.2.0
resolution: "escalade@npm:3.2.0"
@@ -20143,6 +20669,13 @@ __metadata:
languageName: node
linkType: hard
+"escape-goat@npm:^2.0.0":
+ version: 2.1.1
+ resolution: "escape-goat@npm:2.1.1"
+ checksum: 10c0/fc0ad656f89c05e86a9641a21bdc5ea37b258714c057430b68a834854fa3e5770cda7d41756108863fc68b1e36a0946463017b7553ac39eaaf64815be07816fc
+ languageName: node
+ linkType: hard
+
"escape-html@npm:^1.0.3, escape-html@npm:~1.0.3":
version: 1.0.3
resolution: "escape-html@npm:1.0.3"
@@ -21463,6 +21996,15 @@ __metadata:
languageName: node
linkType: hard
+"fast-check@npm:^3.21.0":
+ version: 3.23.1
+ resolution: "fast-check@npm:3.23.1"
+ dependencies:
+ pure-rand: "npm:^6.1.0"
+ checksum: 10c0/d61ee4a7a2e1abc5126bf2f1894413f532f686b3d1fc15c67fefb60dcca66024934b69a6454d3eba92e6568ac1abbb9882080e212d255865c3b3bbe52c5bf702
+ languageName: node
+ linkType: hard
+
"fast-copy@npm:^3.0.0, fast-copy@npm:^3.0.2":
version: 3.0.2
resolution: "fast-copy@npm:3.0.2"
@@ -21587,6 +22129,15 @@ __metadata:
languageName: node
linkType: hard
+"faster-babel-types@npm:^0.1.0":
+ version: 0.1.0
+ resolution: "faster-babel-types@npm:0.1.0"
+ peerDependencies:
+ "@babel/types": ^7
+ checksum: 10c0/e73f27146458d8af39582231e2b65643f1d74a3cc184ed68a684c3c74cb8b8d054773c174fb6a0bb7a85523af91f10a11ae6f542766d5be6d7b570680c2bb256
+ languageName: node
+ linkType: hard
+
"fastest-levenshtein@npm:^1.0.12, fastest-levenshtein@npm:^1.0.16":
version: 1.0.16
resolution: "fastest-levenshtein@npm:1.0.16"
@@ -21693,6 +22244,20 @@ __metadata:
languageName: node
linkType: hard
+"fetch-retry@npm:^6.0.0":
+ version: 6.0.0
+ resolution: "fetch-retry@npm:6.0.0"
+ checksum: 10c0/8e275b042ff98041236d30b71966f24c34ff19f957bb0f00e664754bd63d0dfb5122d091e7d5bca21f6370d88a1713d22421b33471305d7b86d6799427278802
+ languageName: node
+ linkType: hard
+
+"fflate@npm:^0.4.4":
+ version: 0.4.8
+ resolution: "fflate@npm:0.4.8"
+ checksum: 10c0/29d1eddaaa5deab61b1c6b0d21282adacadbc4d2c01e94d8b1ee784398151673b9c563e53f97a801bc410a1ae55e8de5378114a743430e643e7a0644ba8e5a42
+ languageName: node
+ linkType: hard
+
"fflate@npm:^0.8.1":
version: 0.8.2
resolution: "fflate@npm:0.8.2"
@@ -23100,6 +23665,13 @@ __metadata:
languageName: node
linkType: hard
+"has-yarn@npm:^2.1.0":
+ version: 2.1.0
+ resolution: "has-yarn@npm:2.1.0"
+ checksum: 10c0/b5cab61b4129c2fc0474045b59705371b7f5ddf2aab8ba8725011e52269f017e06f75059a2c8a1d8011e9779c2885ad987263cfc6d1280f611c396b45fd5d74a
+ languageName: node
+ linkType: hard
+
"has@npm:^1.0.3":
version: 1.0.4
resolution: "has@npm:1.0.4"
@@ -23255,6 +23827,13 @@ __metadata:
languageName: node
linkType: hard
+"hermes-estree@npm:0.21.1":
+ version: 0.21.1
+ resolution: "hermes-estree@npm:0.21.1"
+ checksum: 10c0/b9bacf41ced8346e4e1d91519530a5facf8cde8662a4ed732b85bb989b11d596832d3fe743e6ccd212eb08ca160ad504d191895cc13b62932f5ba14edb27516f
+ languageName: node
+ linkType: hard
+
"hermes-estree@npm:0.23.1":
version: 0.23.1
resolution: "hermes-estree@npm:0.23.1"
@@ -23271,6 +23850,15 @@ __metadata:
languageName: node
linkType: hard
+"hermes-parser@npm:0.21.1":
+ version: 0.21.1
+ resolution: "hermes-parser@npm:0.21.1"
+ dependencies:
+ hermes-estree: "npm:0.21.1"
+ checksum: 10c0/a3df443bfef835a982865da16a0b0bda5d86efa699791d5007e14b1b2731cb62cb9b1ac9157581602d35ff0ae437a345e701e03dd6330d9c552e8dafa9776436
+ languageName: node
+ linkType: hard
+
"hermes-parser@npm:0.23.1":
version: 0.23.1
resolution: "hermes-parser@npm:0.23.1"
@@ -23330,6 +23918,13 @@ __metadata:
languageName: node
linkType: hard
+"hono@npm:^4.5.9":
+ version: 4.6.13
+ resolution: "hono@npm:4.6.13"
+ checksum: 10c0/8bf6ed856e3204d8dadc74cddc0f5a8e99e78a12df00e5b57f4d6c416ad7f808e6140688a30606c7d4b2a585b0ad085b7ed8be419f13a4b58e29dfdd54cfdac8
+ languageName: node
+ linkType: hard
+
"hosted-git-info@npm:^2.1.4":
version: 2.8.9
resolution: "hosted-git-info@npm:2.8.9"
@@ -23896,6 +24491,13 @@ __metadata:
languageName: node
linkType: hard
+"import-lazy@npm:^2.1.0":
+ version: 2.1.0
+ resolution: "import-lazy@npm:2.1.0"
+ checksum: 10c0/c5e5f507d26ee23c5b2ed64577155810361ac37863b322cae0c17f16b6a8cdd15adf370288384ddd95ef9de05602fb8d87bf76ff835190eb037333c84db8062c
+ languageName: node
+ linkType: hard
+
"import-lazy@npm:~4.0.0":
version: 4.0.0
resolution: "import-lazy@npm:4.0.0"
@@ -24304,6 +24906,17 @@ __metadata:
languageName: node
linkType: hard
+"is-ci@npm:^2.0.0":
+ version: 2.0.0
+ resolution: "is-ci@npm:2.0.0"
+ dependencies:
+ ci-info: "npm:^2.0.0"
+ bin:
+ is-ci: bin.js
+ checksum: 10c0/17de4e2cd8f993c56c86472dd53dd9e2c7f126d0ee55afe610557046cdd64de0e8feadbad476edc9eeff63b060523b8673d9094ed2ab294b59efb5a66dd05a9a
+ languageName: node
+ linkType: hard
+
"is-ci@npm:^3.0.1":
version: 3.0.1
resolution: "is-ci@npm:3.0.1"
@@ -24493,7 +25106,7 @@ __metadata:
languageName: node
linkType: hard
-"is-installed-globally@npm:~0.4.0":
+"is-installed-globally@npm:^0.4.0, is-installed-globally@npm:~0.4.0":
version: 0.4.0
resolution: "is-installed-globally@npm:0.4.0"
dependencies:
@@ -24571,6 +25184,13 @@ __metadata:
languageName: node
linkType: hard
+"is-npm@npm:^5.0.0":
+ version: 5.0.0
+ resolution: "is-npm@npm:5.0.0"
+ checksum: 10c0/8ded3ae1119bbbda22395fe1c64d2d79d3b3baeb2635c90f9a9dca4b8ce19a67b55fda178269b63421b257b361892fd545807fb5ac212f06776f544d9fcc3ab0
+ languageName: node
+ linkType: hard
+
"is-number-object@npm:^1.0.4":
version: 1.0.7
resolution: "is-number-object@npm:1.0.7"
@@ -24772,6 +25392,13 @@ __metadata:
languageName: node
linkType: hard
+"is-unicode-supported@npm:*, is-unicode-supported@npm:^2.0.0":
+ version: 2.1.0
+ resolution: "is-unicode-supported@npm:2.1.0"
+ checksum: 10c0/a0f53e9a7c1fdbcf2d2ef6e40d4736fdffff1c9f8944c75e15425118ff3610172c87bf7bc6c34d3903b04be59790bb2212ddbe21ee65b5a97030fc50370545a5
+ languageName: node
+ linkType: hard
+
"is-unicode-supported@npm:^0.1.0":
version: 0.1.0
resolution: "is-unicode-supported@npm:0.1.0"
@@ -24786,13 +25413,6 @@ __metadata:
languageName: node
linkType: hard
-"is-unicode-supported@npm:^2.0.0":
- version: 2.1.0
- resolution: "is-unicode-supported@npm:2.1.0"
- checksum: 10c0/a0f53e9a7c1fdbcf2d2ef6e40d4736fdffff1c9f8944c75e15425118ff3610172c87bf7bc6c34d3903b04be59790bb2212ddbe21ee65b5a97030fc50370545a5
- languageName: node
- linkType: hard
-
"is-valid-path@npm:^0.1.1":
version: 0.1.1
resolution: "is-valid-path@npm:0.1.1"
@@ -24874,6 +25494,13 @@ __metadata:
languageName: node
linkType: hard
+"is-yarn-global@npm:^0.3.0":
+ version: 0.3.0
+ resolution: "is-yarn-global@npm:0.3.0"
+ checksum: 10c0/9f1ab6f28e6e7961c4b97e564791d1decf2886a0dbe9b92b2176d76156adbb42b4c06c0f33d7107b270c207cbcfe0b2293b7cc4a0ec6774ac6d37af9503d51e1
+ languageName: node
+ linkType: hard
+
"is64bit@npm:^2.0.0":
version: 2.0.0
resolution: "is64bit@npm:2.0.0"
@@ -24918,6 +25545,16 @@ __metadata:
languageName: node
linkType: hard
+"isomorphic-fetch@npm:^3.0.0":
+ version: 3.0.0
+ resolution: "isomorphic-fetch@npm:3.0.0"
+ dependencies:
+ node-fetch: "npm:^2.6.1"
+ whatwg-fetch: "npm:^3.4.1"
+ checksum: 10c0/511b1135c6d18125a07de661091f5e7403b7640060355d2d704ce081e019bc1862da849482d079ce5e2559b8976d3de7709566063aec1b908369c0b98a2b075b
+ languageName: node
+ linkType: hard
+
"isomorphic-rslog@npm:0.0.4":
version: 0.0.4
resolution: "isomorphic-rslog@npm:0.0.4"
@@ -26712,6 +27349,7 @@ __metadata:
"@hookform/resolvers": "npm:^3.9.0"
"@inquirer/prompts": "npm:^4.3.0"
"@justaname.id/address-resolution": "npm:^1.1.0"
+ "@million/lint": "npm:^1.0.13"
"@nx/cypress": "npm:19.7.3"
"@nx/eslint": "npm:19.7.3"
"@nx/eslint-plugin": "npm:19.7.3"
@@ -29029,6 +29667,13 @@ __metadata:
languageName: node
linkType: hard
+"mitt@npm:^3.0.0":
+ version: 3.0.1
+ resolution: "mitt@npm:3.0.1"
+ checksum: 10c0/3ab4fdecf3be8c5255536faa07064d05caa3dd332bd318ff02e04621f7b3069ca1de9106cfe8e7ced675abfc2bec2ce4c4ef321c4a1bb1fb29df8ae090741913
+ languageName: node
+ linkType: hard
+
"mkdirp@npm:1.0.4, mkdirp@npm:^1.0.3, mkdirp@npm:^1.0.4":
version: 1.0.4
resolution: "mkdirp@npm:1.0.4"
@@ -29228,6 +29873,15 @@ __metadata:
languageName: node
linkType: hard
+"nanoid@npm:^5.0.7":
+ version: 5.0.9
+ resolution: "nanoid@npm:5.0.9"
+ bin:
+ nanoid: bin/nanoid.js
+ checksum: 10c0/a2d9710525d4998a8a1610bbe6eb9a92c254ebab7c567c1ab429046fe7eed9c4df3508b59fb44c58ffdc98edb28dd6f953715c14b64ea0a3a2ce37420cdfeefd
+ languageName: node
+ linkType: hard
+
"napi-wasm@npm:^1.1.0":
version: 1.1.3
resolution: "napi-wasm@npm:1.1.3"
@@ -30941,6 +31595,13 @@ __metadata:
languageName: node
linkType: hard
+"parse-ms@npm:^3.0.0":
+ version: 3.0.0
+ resolution: "parse-ms@npm:3.0.0"
+ checksum: 10c0/056b4a32a9d3749f3f4cfffefb45c45540491deaa8e1d8ad43c2ddde7ba04edd076bd1b298f521238bb5fb084a9b2c4a2ebb78aefa651afbc4c2b0af4232fc54
+ languageName: node
+ linkType: hard
+
"parse-ms@npm:^4.0.0":
version: 4.0.0
resolution: "parse-ms@npm:4.0.0"
@@ -31405,6 +32066,18 @@ __metadata:
languageName: node
linkType: hard
+"piscina@npm:^4.4.0":
+ version: 4.8.0
+ resolution: "piscina@npm:4.8.0"
+ dependencies:
+ "@napi-rs/nice": "npm:^1.0.1"
+ dependenciesMeta:
+ "@napi-rs/nice":
+ optional: true
+ checksum: 10c0/963ee0dc0862e936c88357b21b0b4fa32407ab21e9600756504411f368dcfae7478c8a19e13d0dd8afed56a8252a8e5967ee4413aa33dd436751b7ee2804531e
+ languageName: node
+ linkType: hard
+
"pkg-dir@npm:^3.0.0":
version: 3.0.0
resolution: "pkg-dir@npm:3.0.0"
@@ -32528,6 +33201,15 @@ __metadata:
languageName: node
linkType: hard
+"pretty-ms@npm:8.0.0":
+ version: 8.0.0
+ resolution: "pretty-ms@npm:8.0.0"
+ dependencies:
+ parse-ms: "npm:^3.0.0"
+ checksum: 10c0/e960d633ecca45445cf5c6dffc0f5e4bef6744c92449ab0e8c6c704800675ab71e181c5e02ece5265e02137a33e313d3f3e355fbf8ea30b4b5b23de423329f8d
+ languageName: node
+ linkType: hard
+
"pretty-ms@npm:^9.0.0":
version: 9.1.0
resolution: "pretty-ms@npm:9.1.0"
@@ -32889,7 +33571,16 @@ __metadata:
languageName: node
linkType: hard
-"pure-rand@npm:^6.0.0":
+"pupa@npm:^2.1.1":
+ version: 2.1.1
+ resolution: "pupa@npm:2.1.1"
+ dependencies:
+ escape-goat: "npm:^2.0.0"
+ checksum: 10c0/d2346324780ebae4be847cad052b830e004d816851dd4750fc73faa6cd360f443e358f6b1c83641fd4c904c6055dcb545807f55259a20a52ad86d9477746c724
+ languageName: node
+ linkType: hard
+
+"pure-rand@npm:^6.0.0, pure-rand@npm:^6.1.0":
version: 6.1.0
resolution: "pure-rand@npm:6.1.0"
checksum: 10c0/1abe217897bf74dcb3a0c9aba3555fe975023147b48db540aa2faf507aee91c03bf54f6aef0eb2bf59cc259a16d06b28eca37f0dc426d94f4692aeff02fb0e65
@@ -33507,6 +34198,21 @@ __metadata:
languageName: node
linkType: hard
+"react-scan@npm:^0.0.31":
+ version: 0.0.31
+ resolution: "react-scan@npm:0.0.31"
+ dependencies:
+ "@clack/core": "npm:^0.3.5"
+ "@clack/prompts": "npm:^0.8.2"
+ kleur: "npm:^4.1.5"
+ mri: "npm:^1.2.0"
+ playwright: "npm:^1.49.0"
+ bin:
+ react-scan: bin/cli.js
+ checksum: 10c0/4235909e20a3f096382c96e108c293dfd9369e4311a02661adeab366a9964a8cf238ff7bfab5a4ec59cc022e5ecd157acb8e445a4d7120f30ee06421dd82613f
+ languageName: node
+ linkType: hard
+
"react-scan@npm:^0.0.35":
version: 0.0.35
resolution: "react-scan@npm:0.0.35"
@@ -33806,7 +34512,7 @@ __metadata:
languageName: node
linkType: hard
-"recast@npm:^0.23.1, recast@npm:^0.23.3, recast@npm:^0.23.5":
+"recast@npm:^0.23.1, recast@npm:^0.23.3, recast@npm:^0.23.5, recast@npm:^0.23.9":
version: 0.23.9
resolution: "recast@npm:0.23.9"
dependencies:
@@ -33971,6 +34677,24 @@ __metadata:
languageName: node
linkType: hard
+"registry-auth-token@npm:^5.0.1":
+ version: 5.0.3
+ resolution: "registry-auth-token@npm:5.0.3"
+ dependencies:
+ "@pnpm/npm-conf": "npm:^2.1.0"
+ checksum: 10c0/f92313032fae7dca787aa878cc7fa8499ee5da960802777f6b9f168a5d8f24a97fcfa0cf30a604bcf38b050a5db5f034b1e2fec18a3326f41822a6aff9514c85
+ languageName: node
+ linkType: hard
+
+"registry-url@npm:^5.1.0":
+ version: 5.1.0
+ resolution: "registry-url@npm:5.1.0"
+ dependencies:
+ rc: "npm:^1.2.8"
+ checksum: 10c0/c2c455342b5836cbed5162092eba075c7a02c087d9ce0fde8aeb4dc87a8f4a34a542e58bf4d8ec2d4cb73f04408cb3148ceb1f76647f76b978cfec22047dc6d6
+ languageName: node
+ linkType: hard
+
"regjsgen@npm:^0.8.0":
version: 0.8.0
resolution: "regjsgen@npm:0.8.0"
@@ -34572,6 +35296,24 @@ __metadata:
languageName: node
linkType: hard
+"rrdom@npm:^0.1.7":
+ version: 0.1.7
+ resolution: "rrdom@npm:0.1.7"
+ dependencies:
+ rrweb-snapshot: "npm:^2.0.0-alpha.4"
+ checksum: 10c0/2e06c90f1e3d8a329edb9526ceaf25f2ca09b4a8dffd171577751a94fdc0ec0e56ab899be1f8f775fd6bf76a1268507767a2aa336a6c1d8a189022909cde7726
+ languageName: node
+ linkType: hard
+
+"rrdom@npm:^2.0.0-alpha.18":
+ version: 2.0.0-alpha.18
+ resolution: "rrdom@npm:2.0.0-alpha.18"
+ dependencies:
+ rrweb-snapshot: "npm:^2.0.0-alpha.18"
+ checksum: 10c0/13770f81a475eff96d50bb74d313965a4e3d5b904b9a27dc43cb33fb92dac040fa5acd602db3a20de5366997a4cf3f55c635a5359e58bd26187ef074c0704fe5
+ languageName: node
+ linkType: hard
+
"rrweb-cssom@npm:^0.6.0":
version: 0.6.0
resolution: "rrweb-cssom@npm:0.6.0"
@@ -34579,6 +35321,57 @@ __metadata:
languageName: node
linkType: hard
+"rrweb-player@npm:1.0.0-alpha.4":
+ version: 1.0.0-alpha.4
+ resolution: "rrweb-player@npm:1.0.0-alpha.4"
+ dependencies:
+ "@tsconfig/svelte": "npm:^1.0.0"
+ rrweb: "npm:^2.0.0-alpha.4"
+ checksum: 10c0/cde3a7e0505c97312bc8015bba91f7814770d1d08cff2c2cb3e7a9eaf6a8a2bcc40f5fe5a22a46a351f6992df49a4a1c8be2c85ef461c96d5dd2af69f4fbad75
+ languageName: node
+ linkType: hard
+
+"rrweb-snapshot@npm:^2.0.0-alpha.16, rrweb-snapshot@npm:^2.0.0-alpha.18, rrweb-snapshot@npm:^2.0.0-alpha.4":
+ version: 2.0.0-alpha.18
+ resolution: "rrweb-snapshot@npm:2.0.0-alpha.18"
+ dependencies:
+ postcss: "npm:^8.4.38"
+ checksum: 10c0/296996f50f8a9c5f5dad2f92cea8fcc670bf364e094db48592919cab8987cdc19ce2e7b9b20653c63160f924e733a213329e87ff092353d4a5364c0a3a66405c
+ languageName: node
+ linkType: hard
+
+"rrweb@npm:2.0.0-alpha.4":
+ version: 2.0.0-alpha.4
+ resolution: "rrweb@npm:2.0.0-alpha.4"
+ dependencies:
+ "@rrweb/types": "npm:^2.0.0-alpha.4"
+ "@types/css-font-loading-module": "npm:0.0.7"
+ "@xstate/fsm": "npm:^1.4.0"
+ base64-arraybuffer: "npm:^1.0.1"
+ fflate: "npm:^0.4.4"
+ mitt: "npm:^3.0.0"
+ rrdom: "npm:^0.1.7"
+ rrweb-snapshot: "npm:^2.0.0-alpha.4"
+ checksum: 10c0/124de86bc4db3d3254309950a5fef1c0ed2ad283a4db5840c43eba8d4157738f1fd833e15c3201e6754aa0bec2e3f7ed7fdf7a5335262626e15691afc2cf4f24
+ languageName: node
+ linkType: hard
+
+"rrweb@npm:^2.0.0-alpha.4":
+ version: 2.0.0-alpha.18
+ resolution: "rrweb@npm:2.0.0-alpha.18"
+ dependencies:
+ "@rrweb/types": "npm:^2.0.0-alpha.18"
+ "@rrweb/utils": "npm:^2.0.0-alpha.18"
+ "@types/css-font-loading-module": "npm:0.0.7"
+ "@xstate/fsm": "npm:^1.4.0"
+ base64-arraybuffer: "npm:^1.0.1"
+ mitt: "npm:^3.0.0"
+ rrdom: "npm:^2.0.0-alpha.18"
+ rrweb-snapshot: "npm:^2.0.0-alpha.18"
+ checksum: 10c0/8f33a64f4648fbba1621ec4ec383ee909435a0621259cf1b38c88074b8e23c875a7837f7e403d176f8399ad25312f6632c16c0879618a327d4b1797f8c4b37b1
+ languageName: node
+ linkType: hard
+
"run-applescript@npm:^7.0.0":
version: 7.0.0
resolution: "run-applescript@npm:7.0.0"
@@ -34852,6 +35645,15 @@ __metadata:
languageName: node
linkType: hard
+"semver-diff@npm:^3.1.1":
+ version: 3.1.1
+ resolution: "semver-diff@npm:3.1.1"
+ dependencies:
+ semver: "npm:^6.3.0"
+ checksum: 10c0/7d350f1450b9577d538ef866a9bc4cd97bfbf1f1d92070291495a31d0ec3aa808e826c223e5454ea9877cc06eaa886ffd71bb3a1f331b44bc210f9ff525c68d2
+ languageName: node
+ linkType: hard
+
"semver-regex@npm:^4.0.5":
version: 4.0.5
resolution: "semver-regex@npm:4.0.5"
@@ -34897,7 +35699,7 @@ __metadata:
languageName: node
linkType: hard
-"semver@npm:7.6.3, semver@npm:^7.1.1, semver@npm:^7.1.2, semver@npm:^7.3.4, semver@npm:^7.3.5, semver@npm:^7.3.7, semver@npm:^7.3.8, semver@npm:^7.5.2, semver@npm:^7.5.3, semver@npm:^7.5.4, semver@npm:^7.6.0, semver@npm:^7.6.3":
+"semver@npm:7.6.3, semver@npm:^7.1.1, semver@npm:^7.1.2, semver@npm:^7.3.4, semver@npm:^7.3.5, semver@npm:^7.3.7, semver@npm:^7.3.8, semver@npm:^7.5.2, semver@npm:^7.5.3, semver@npm:^7.5.4, semver@npm:^7.6.0, semver@npm:^7.6.2, semver@npm:^7.6.3":
version: 7.6.3
resolution: "semver@npm:7.6.3"
bin:
@@ -35335,7 +36137,17 @@ __metadata:
languageName: node
linkType: hard
-"socket.io-client@npm:^4.5.1":
+"socket.io-adapter@npm:~2.5.2":
+ version: 2.5.5
+ resolution: "socket.io-adapter@npm:2.5.5"
+ dependencies:
+ debug: "npm:~4.3.4"
+ ws: "npm:~8.17.1"
+ checksum: 10c0/04a5a2a9c4399d1b6597c2afc4492ab1e73430cc124ab02b09e948eabf341180b3866e2b61b5084cb899beb68a4db7c328c29bda5efb9207671b5cb0bc6de44e
+ languageName: node
+ linkType: hard
+
+"socket.io-client@npm:^4.5.1, socket.io-client@npm:^4.7.5":
version: 4.8.1
resolution: "socket.io-client@npm:4.8.1"
dependencies:
@@ -35357,6 +36169,21 @@ __metadata:
languageName: node
linkType: hard
+"socket.io@npm:^4.8.1":
+ version: 4.8.1
+ resolution: "socket.io@npm:4.8.1"
+ dependencies:
+ accepts: "npm:~1.3.4"
+ base64id: "npm:~2.0.0"
+ cors: "npm:~2.8.5"
+ debug: "npm:~4.3.2"
+ engine.io: "npm:~6.6.0"
+ socket.io-adapter: "npm:~2.5.2"
+ socket.io-parser: "npm:~4.2.4"
+ checksum: 10c0/acf931a2bb235be96433b71da3d8addc63eeeaa8acabd33dc8d64e12287390a45f1e9f389a73cf7dc336961cd491679741b7a016048325c596835abbcc017ca9
+ languageName: node
+ linkType: hard
+
"sockjs@npm:^0.3.24":
version: 0.3.24
resolution: "sockjs@npm:0.3.24"
@@ -36057,7 +36884,7 @@ __metadata:
languageName: node
linkType: hard
-"string-width-cjs@npm:string-width@^4.2.0, string-width@npm:^4.1.0, string-width@npm:^4.2.0, string-width@npm:^4.2.3":
+"string-width-cjs@npm:string-width@^4.2.0, string-width@npm:^4.0.0, string-width@npm:^4.1.0, string-width@npm:^4.2.0, string-width@npm:^4.2.2, string-width@npm:^4.2.3":
version: 4.2.3
resolution: "string-width@npm:4.2.3"
dependencies:
@@ -36549,6 +37376,13 @@ __metadata:
languageName: node
linkType: hard
+"sugar-high@npm:^0.7.1":
+ version: 0.7.5
+ resolution: "sugar-high@npm:0.7.5"
+ checksum: 10c0/0d898ce842bc4c7362bed5b302186a39bed320c251e193e185be6b6ba10b183321748fa9d30d2a89b242b7d3cbcdb8abc26c02d3279851a649c0532cbeddd582
+ languageName: node
+ linkType: hard
+
"superstruct@npm:^1.0.3":
version: 1.0.4
resolution: "superstruct@npm:1.0.4"
@@ -37151,7 +37985,7 @@ __metadata:
languageName: node
linkType: hard
-"tmp@npm:~0.2.1, tmp@npm:~0.2.3":
+"tmp@npm:^0.2.3, tmp@npm:~0.2.1, tmp@npm:~0.2.3":
version: 0.2.3
resolution: "tmp@npm:0.2.3"
checksum: 10c0/3e809d9c2f46817475b452725c2aaa5d11985cf18d32a7a970ff25b568438e2c076c2e8609224feef3b7923fa9749b74428e3e634f6b8e520c534eef2fd24125
@@ -37963,6 +38797,13 @@ __metadata:
languageName: node
linkType: hard
+"undici-types@npm:~6.20.0":
+ version: 6.20.0
+ resolution: "undici-types@npm:6.20.0"
+ checksum: 10c0/68e659a98898d6a836a9a59e6adf14a5d799707f5ea629433e025ac90d239f75e408e2e5ff086afc3cace26f8b26ee52155293564593fbb4a2f666af57fc59bf
+ languageName: node
+ linkType: hard
+
"undici@npm:^5.8.1":
version: 5.28.4
resolution: "undici@npm:5.28.4"
@@ -38225,6 +39066,16 @@ __metadata:
languageName: node
linkType: hard
+"unplugin@npm:^1.6.0":
+ version: 1.16.0
+ resolution: "unplugin@npm:1.16.0"
+ dependencies:
+ acorn: "npm:^8.14.0"
+ webpack-virtual-modules: "npm:^0.6.2"
+ checksum: 10c0/547f6bd5ec1dd7411533e68e73c60d5e9527e68d52aa326442650d084866ed3307ac68719068abae23ceab09db197cad43b382a7e69c2d8ca338b27802392fed
+ languageName: node
+ linkType: hard
+
"unstorage@npm:^1.9.0":
version: 1.12.0
resolution: "unstorage@npm:1.12.0"
@@ -38325,6 +39176,30 @@ __metadata:
languageName: node
linkType: hard
+"update-notifier-cjs@npm:^5.1.6":
+ version: 5.1.6
+ resolution: "update-notifier-cjs@npm:5.1.6"
+ dependencies:
+ boxen: "npm:^5.0.0"
+ chalk: "npm:^4.1.0"
+ configstore: "npm:^5.0.1"
+ has-yarn: "npm:^2.1.0"
+ import-lazy: "npm:^2.1.0"
+ is-ci: "npm:^2.0.0"
+ is-installed-globally: "npm:^0.4.0"
+ is-npm: "npm:^5.0.0"
+ is-yarn-global: "npm:^0.3.0"
+ isomorphic-fetch: "npm:^3.0.0"
+ pupa: "npm:^2.1.1"
+ registry-auth-token: "npm:^5.0.1"
+ registry-url: "npm:^5.1.0"
+ semver: "npm:^7.3.7"
+ semver-diff: "npm:^3.1.1"
+ xdg-basedir: "npm:^4.0.0"
+ checksum: 10c0/339d9d8c049c2dc45979159b393e6037b9a935e1da3a0b772e1f42437a2fd372e9b5db12c98c2eef4c9a578922b214432085944935816dab06804ca29ab61d99
+ languageName: node
+ linkType: hard
+
"uqr@npm:^0.1.2":
version: 0.1.2
resolution: "uqr@npm:0.1.2"
@@ -39520,7 +40395,7 @@ __metadata:
languageName: node
linkType: hard
-"whatwg-fetch@npm:^3.0.0":
+"whatwg-fetch@npm:^3.0.0, whatwg-fetch@npm:^3.4.1":
version: 3.6.20
resolution: "whatwg-fetch@npm:3.6.20"
checksum: 10c0/fa972dd14091321d38f36a4d062298df58c2248393ef9e8b154493c347c62e2756e25be29c16277396046d6eaa4b11bd174f34e6403fff6aaca9fb30fa1ff46d
@@ -39696,6 +40571,15 @@ __metadata:
languageName: node
linkType: hard
+"widest-line@npm:^3.1.0":
+ version: 3.1.0
+ resolution: "widest-line@npm:3.1.0"
+ dependencies:
+ string-width: "npm:^4.0.0"
+ checksum: 10c0/b1e623adcfb9df35350dd7fc61295d6d4a1eaa65a406ba39c4b8360045b614af95ad10e05abf704936ed022569be438c4bfa02d6d031863c4166a238c301119f
+ languageName: node
+ linkType: hard
+
"wildcard@npm:^2.0.0":
version: 2.0.1
resolution: "wildcard@npm:2.0.1"
@@ -39919,6 +40803,13 @@ __metadata:
languageName: node
linkType: hard
+"xdg-basedir@npm:^4.0.0":
+ version: 4.0.0
+ resolution: "xdg-basedir@npm:4.0.0"
+ checksum: 10c0/1b5d70d58355af90363a4e0a51c992e77fc5a1d8de5822699c7d6e96a6afea9a1e048cb93312be6870f338ca45ebe97f000425028fa149c1e87d1b5b8b212a06
+ languageName: node
+ linkType: hard
+
"xml-name-validator@npm:^4.0.0":
version: 4.0.0
resolution: "xml-name-validator@npm:4.0.0"
@@ -39985,6 +40876,13 @@ __metadata:
languageName: node
linkType: hard
+"xycolors@npm:^0.1.2":
+ version: 0.1.2
+ resolution: "xycolors@npm:0.1.2"
+ checksum: 10c0/a7dd439022f72e7d9c9ec335ccee5583dca63c2bdcc9aa64cd6b1f7de6d6e319fe01aa3c584241fd2f187be3ca30a5527682310493975357333afb6311c7cac1
+ languageName: node
+ linkType: hard
+
"y18n@npm:^4.0.0":
version: 4.0.3
resolution: "y18n@npm:4.0.3"
@@ -40093,7 +40991,7 @@ __metadata:
languageName: node
linkType: hard
-"yargs@npm:^17.3.1, yargs@npm:^17.6.2":
+"yargs@npm:^17.3.1, yargs@npm:^17.6.2, yargs@npm:^17.7.2":
version: 17.7.2
resolution: "yargs@npm:17.7.2"
dependencies: