feat(chat): rewrite chat page with chatscope UI and custom hooks
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
db59316dc3
commit
bb0aa5e28e
6 changed files with 361 additions and 69 deletions
|
|
@ -75,6 +75,8 @@
|
|||
"@blocknote/core": "^0.41.1",
|
||||
"@blocknote/mantine": "^0.41.1",
|
||||
"@blocknote/react": "^0.41.1",
|
||||
"@chatscope/chat-ui-kit-react": "^2.1.1",
|
||||
"@chatscope/chat-ui-kit-styles": "^1.4.0",
|
||||
"@datadog/browser-rum": "^6.13.0",
|
||||
"@datadog/browser-rum-react": "^6.13.0",
|
||||
"@hookform/resolvers": "^5.2.2",
|
||||
|
|
|
|||
90
apps/main/src/components/ChatChannelPreview.tsx
Normal file
90
apps/main/src/components/ChatChannelPreview.tsx
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
import { ChannelBadge } from "@ui/components/ChannelBadge";
|
||||
import type { UserTablo } from "@xtablo/shared/types/tablos.types";
|
||||
import { Badge } from "@xtablo/ui/components/badge";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
interface ChatChannelPreviewProps {
|
||||
tablo: UserTablo;
|
||||
isActive: boolean;
|
||||
onClick: () => void;
|
||||
unreadCount: number;
|
||||
lastMessage?: string;
|
||||
lastMessageTime?: string;
|
||||
isOnline: boolean;
|
||||
}
|
||||
|
||||
function formatTimestamp(timestamp: string | Date): string {
|
||||
const date = new Date(timestamp);
|
||||
const now = new Date();
|
||||
const diff = now.getTime() - date.getTime();
|
||||
const minutes = Math.floor(diff / 60000);
|
||||
const hours = Math.floor(diff / 3600000);
|
||||
const days = Math.floor(diff / 86400000);
|
||||
|
||||
if (minutes < 1) return "now";
|
||||
if (minutes < 60) return `${minutes}m`;
|
||||
if (hours < 24) return `${hours}h`;
|
||||
if (days < 7) return `${days}d`;
|
||||
return date.toLocaleDateString();
|
||||
}
|
||||
|
||||
export function ChatChannelPreview({
|
||||
tablo,
|
||||
isActive,
|
||||
onClick,
|
||||
unreadCount,
|
||||
lastMessage,
|
||||
lastMessageTime,
|
||||
isOnline,
|
||||
}: ChatChannelPreviewProps) {
|
||||
return (
|
||||
<div
|
||||
className={twMerge(
|
||||
"group relative flex items-center gap-3 p-3 cursor-pointer transition-all duration-200 hover:bg-gray-50 dark:hover:bg-gray-800/50 border-b border-gray-100 dark:border-gray-800",
|
||||
isActive && "bg-purple-50 dark:bg-purple-900/20 border-purple-200 dark:border-purple-800"
|
||||
)}
|
||||
onClick={onClick}
|
||||
>
|
||||
<ChannelBadge tablo={tablo} displayTitle={tablo.name} isOnline={isOnline} />
|
||||
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-center justify-between mb-1">
|
||||
<h3
|
||||
className={twMerge(
|
||||
"font-medium text-gray-900 dark:text-gray-100 truncate",
|
||||
isActive && "text-[#804EEC] dark:text-purple-400"
|
||||
)}
|
||||
>
|
||||
{tablo.name}
|
||||
</h3>
|
||||
{lastMessageTime && (
|
||||
<span className="text-xs text-gray-500 dark:text-gray-400 ml-2 shrink-0">
|
||||
{formatTimestamp(lastMessageTime)}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between">
|
||||
<p className="text-sm text-gray-600 dark:text-gray-400 line-clamp-2 max-h-10 overflow-hidden">
|
||||
{lastMessage ?? "No messages yet"}
|
||||
</p>
|
||||
|
||||
{unreadCount > 0 && (
|
||||
<div className="ml-2 shrink-0">
|
||||
<Badge
|
||||
color="indigo"
|
||||
className="text-xs min-w-[20px] h-5 px-2 py-0 flex items-center justify-center"
|
||||
>
|
||||
{unreadCount > 99 ? "99+" : unreadCount}
|
||||
</Badge>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{isActive && (
|
||||
<div className="absolute left-0 top-0 bottom-0 w-1 bg-[#804EEC] dark:bg-purple-400 rounded-r-full" />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
54
apps/main/src/components/ChatHeader.tsx
Normal file
54
apps/main/src/components/ChatHeader.tsx
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
import { ChannelBadge } from "@ui/components/ChannelBadge";
|
||||
import type { UserTablo } from "@xtablo/shared/types/tablos.types";
|
||||
|
||||
interface ChatHeaderProps {
|
||||
tablo: UserTablo | null;
|
||||
onToggleChannelList?: () => void;
|
||||
isChannelListExpanded?: boolean;
|
||||
onlineUsers: string[];
|
||||
}
|
||||
|
||||
export function ChatHeader({
|
||||
tablo,
|
||||
onToggleChannelList,
|
||||
isChannelListExpanded = false,
|
||||
onlineUsers,
|
||||
}: ChatHeaderProps) {
|
||||
const memberCount = onlineUsers.length;
|
||||
|
||||
return (
|
||||
<div className="flex items-center px-4 py-3 border-b border-gray-200 dark:border-gray-600/50 bg-white dark:bg-gray-800/40">
|
||||
{onToggleChannelList && (
|
||||
<button
|
||||
onClick={onToggleChannelList}
|
||||
className="mr-2 p-2 rounded-md hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors"
|
||||
aria-label="Toggle channel list"
|
||||
>
|
||||
<svg
|
||||
className={`w-5 h-5 transition-transform duration-200 ${
|
||||
isChannelListExpanded ? "rotate-180" : ""
|
||||
}`}
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
|
||||
</svg>
|
||||
</button>
|
||||
)}
|
||||
{tablo && (
|
||||
<>
|
||||
<ChannelBadge tablo={tablo} displayTitle={tablo.name} isOnline={memberCount > 0} />
|
||||
<div className="ml-3">
|
||||
<h2 className="font-semibold text-gray-900 dark:text-gray-100">{tablo.name}</h2>
|
||||
{memberCount > 0 && (
|
||||
<p className="text-xs text-gray-500 dark:text-gray-400">
|
||||
{memberCount} online
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -28,7 +28,6 @@ import { TabloDetailsPage } from "../pages/tablo-details";
|
|||
import { TablosPage } from "../pages/tablos";
|
||||
import { TasksPage } from "../pages/tasks";
|
||||
import { UpdatePasswordPage } from "../pages/update-password";
|
||||
import ChatProvider from "../providers/ChatProvider";
|
||||
|
||||
export const routes: RouteObject[] = [
|
||||
// Protected routes
|
||||
|
|
@ -75,11 +74,7 @@ export const routes: RouteObject[] = [
|
|||
},
|
||||
{
|
||||
path: "chat",
|
||||
element: (
|
||||
<ChatProvider>
|
||||
<ChatPage />
|
||||
</ChatProvider>
|
||||
),
|
||||
element: <ChatPage />,
|
||||
children: [{ index: true }, { path: ":channelId" }],
|
||||
},
|
||||
// Notes feature temporarily hidden
|
||||
|
|
|
|||
|
|
@ -1,39 +1,58 @@
|
|||
import { ChannelPreview } from "@ui/components/ChannelPreview";
|
||||
import { CustomChannelHeader } from "@ui/components/CustomChannelHeader";
|
||||
import { useEffect, useState } from "react";
|
||||
import "@chatscope/chat-ui-kit-styles/dist/default/styles.min.css";
|
||||
import {
|
||||
Channel,
|
||||
ChannelList,
|
||||
MessageInput,
|
||||
ChatContainer,
|
||||
MessageList,
|
||||
useChatContext,
|
||||
Window,
|
||||
} from "stream-chat-react";
|
||||
import { useChannelFromUrl } from "../hooks/channel";
|
||||
Message,
|
||||
MessageInput,
|
||||
TypingIndicator,
|
||||
} from "@chatscope/chat-ui-kit-react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import { ChatChannelPreview } from "../components/ChatChannelPreview";
|
||||
import { ChatHeader } from "../components/ChatHeader";
|
||||
import { useChat } from "../hooks/useChat";
|
||||
import { useChatUnread } from "../hooks/useChatUnread";
|
||||
import { useTablosList } from "../hooks/tablos";
|
||||
import { useUser } from "../providers/UserStoreProvider";
|
||||
|
||||
export function ChatPage() {
|
||||
const user = useUser();
|
||||
const filters = { members: { $in: [user.id] }, type: "messaging" };
|
||||
|
||||
const { client, channel, setActiveChannel } = useChatContext();
|
||||
const { channel: channelFromUrl, isChannelInUrl } = useChannelFromUrl(client);
|
||||
|
||||
const { channelId } = useParams();
|
||||
const navigate = useNavigate();
|
||||
const { data: tablos } = useTablosList();
|
||||
const [isChannelListExpanded, setIsChannelListExpanded] = useState(false);
|
||||
const { getUnreadCount } = useChatUnread();
|
||||
const [isChannelListExpanded, setIsChannelListExpanded] = useState(!channelId);
|
||||
|
||||
const toggleChannelList = () => {
|
||||
setIsChannelListExpanded(!isChannelListExpanded);
|
||||
const {
|
||||
messages,
|
||||
sendMessage,
|
||||
sendTyping,
|
||||
isConnected,
|
||||
typingUsers,
|
||||
onlineUsers,
|
||||
loadMoreMessages,
|
||||
hasMoreMessages,
|
||||
markAsRead,
|
||||
} = useChat(channelId);
|
||||
|
||||
const activeTablo = tablos?.find((t) => t.id === channelId) ?? null;
|
||||
|
||||
// Mark as read when channel is focused
|
||||
useEffect(() => {
|
||||
if (channelId && messages.length > 0) {
|
||||
markAsRead();
|
||||
}
|
||||
}, [channelId, messages.length, markAsRead]);
|
||||
|
||||
const handleSend = (innerHtml: string, textContent: string) => {
|
||||
const text = textContent.trim();
|
||||
if (!text) return;
|
||||
sendMessage(text);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (channelFromUrl) {
|
||||
setActiveChannel(channelFromUrl);
|
||||
} else {
|
||||
setIsChannelListExpanded(true);
|
||||
}
|
||||
}, [channelFromUrl]);
|
||||
const handleChannelSelect = (tabloId: string) => {
|
||||
navigate(`/chat/${tabloId}`);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col h-[calc(100vh-75px)] bg-gray-50 dark:bg-background">
|
||||
|
|
@ -41,46 +60,72 @@ export function ChatPage() {
|
|||
<h1 className="text-2xl font-bold text-gray-900 dark:text-gray-100">Discussions</h1>
|
||||
</div>
|
||||
<div className="flex flex-1 overflow-hidden">
|
||||
{/* Channel list sidebar */}
|
||||
<div
|
||||
className={`border-r border-gray-200 dark:border-gray-600/50 bg-white dark:bg-gray-700/40 transition-all duration-300 ease-in-out overflow-hidden ${
|
||||
isChannelListExpanded ? "w-80" : "w-0"
|
||||
}`}
|
||||
>
|
||||
<ChannelList
|
||||
filters={filters}
|
||||
setActiveChannelOnMount={isChannelInUrl ? false : true}
|
||||
Preview={({
|
||||
displayTitle,
|
||||
channel,
|
||||
activeChannel,
|
||||
setActiveChannel,
|
||||
unread,
|
||||
latestMessagePreview,
|
||||
}) => (
|
||||
<ChannelPreview
|
||||
displayTitle={displayTitle}
|
||||
channel={channel}
|
||||
tablo={tablos?.find((t) => t.id === channel.id) ?? null}
|
||||
activeChannel={activeChannel}
|
||||
setActiveChannel={setActiveChannel}
|
||||
unreadCount={unread}
|
||||
latestMessagePreview={latestMessagePreview}
|
||||
<div className="overflow-y-auto h-full">
|
||||
{tablos?.map((tablo) => (
|
||||
<ChatChannelPreview
|
||||
key={tablo.id}
|
||||
tablo={tablo}
|
||||
isActive={channelId === tablo.id}
|
||||
onClick={() => handleChannelSelect(tablo.id)}
|
||||
unreadCount={getUnreadCount(tablo.id)}
|
||||
isOnline={onlineUsers.some((uid) => uid !== user.id)}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex-1 bg-white dark:bg-gray-700/40">
|
||||
<Channel channel={channel}>
|
||||
<Window>
|
||||
<CustomChannelHeader
|
||||
tablos={tablos ?? []}
|
||||
onToggleChannelList={toggleChannelList}
|
||||
|
||||
{/* Chat area */}
|
||||
<div className="flex-1 flex flex-col bg-white dark:bg-gray-700/40">
|
||||
{channelId && activeTablo ? (
|
||||
<>
|
||||
<ChatHeader
|
||||
tablo={activeTablo}
|
||||
onToggleChannelList={() => setIsChannelListExpanded(!isChannelListExpanded)}
|
||||
isChannelListExpanded={isChannelListExpanded}
|
||||
onlineUsers={onlineUsers}
|
||||
/>
|
||||
<MessageList />
|
||||
<MessageInput />
|
||||
</Window>
|
||||
</Channel>
|
||||
<div className="flex-1 overflow-hidden">
|
||||
<ChatContainer>
|
||||
<MessageList
|
||||
typingIndicator={
|
||||
typingUsers.length > 0 ? (
|
||||
<TypingIndicator content="typing..." />
|
||||
) : undefined
|
||||
}
|
||||
>
|
||||
{messages.map((msg) => (
|
||||
<Message
|
||||
key={msg.id}
|
||||
model={{
|
||||
message: msg.text,
|
||||
sentTime: msg.createdAt,
|
||||
sender: msg.userId,
|
||||
direction: msg.userId === user.id ? "outgoing" : "incoming",
|
||||
position: "single",
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</MessageList>
|
||||
<MessageInput
|
||||
placeholder="Type a message..."
|
||||
onSend={handleSend}
|
||||
onChange={() => sendTyping()}
|
||||
attachButton={false}
|
||||
/>
|
||||
</ChatContainer>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<div className="flex-1 flex items-center justify-center text-gray-500 dark:text-gray-400">
|
||||
Select a conversation to start chatting
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
120
pnpm-lock.yaml
120
pnpm-lock.yaml
|
|
@ -124,6 +124,25 @@ importers:
|
|||
specifier: ^4.0.8
|
||||
version: 4.0.8(@types/debug@4.1.12)(@types/node@20.19.23)(@vitest/ui@4.0.8)(happy-dom@20.0.7)(jiti@2.6.1)(jsdom@20.0.3)(lightningcss@1.30.2)(terser@5.46.1)(tsx@4.20.6)
|
||||
|
||||
apps/chat-worker:
|
||||
dependencies:
|
||||
hono:
|
||||
specifier: ^4.7.7
|
||||
version: 4.10.4
|
||||
jose:
|
||||
specifier: ^6.0.0
|
||||
version: 6.2.2
|
||||
devDependencies:
|
||||
'@cloudflare/workers-types':
|
||||
specifier: ^4.20250410.0
|
||||
version: 4.20260411.1
|
||||
typescript:
|
||||
specifier: ^5.8.3
|
||||
version: 5.9.3
|
||||
wrangler:
|
||||
specifier: ^4.14.0
|
||||
version: 4.44.0(@cloudflare/workers-types@4.20260411.1)
|
||||
|
||||
apps/external:
|
||||
dependencies:
|
||||
'@tanstack/react-query':
|
||||
|
|
@ -174,7 +193,7 @@ importers:
|
|||
version: 2.2.5
|
||||
'@cloudflare/vite-plugin':
|
||||
specifier: ^1.9.4
|
||||
version: 1.13.14(vite@6.4.1(@types/node@22.18.12)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.1)(tsx@4.20.6))(workerd@1.20251011.0)(wrangler@4.44.0)
|
||||
version: 1.13.14(vite@6.4.1(@types/node@22.18.12)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.1)(tsx@4.20.6))(workerd@1.20251011.0)(wrangler@4.44.0(@cloudflare/workers-types@4.20260411.1))
|
||||
'@tailwindcss/vite':
|
||||
specifier: ^4.0.14
|
||||
version: 4.1.15(vite@6.4.1(@types/node@22.18.12)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.1)(tsx@4.20.6))
|
||||
|
|
@ -204,7 +223,7 @@ importers:
|
|||
version: 5.1.4(typescript@5.9.3)(vite@6.4.1(@types/node@22.18.12)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.1)(tsx@4.20.6))
|
||||
wrangler:
|
||||
specifier: ^4.24.3
|
||||
version: 4.44.0
|
||||
version: 4.44.0(@cloudflare/workers-types@4.20260411.1)
|
||||
|
||||
apps/main:
|
||||
dependencies:
|
||||
|
|
@ -217,6 +236,12 @@ importers:
|
|||
'@blocknote/react':
|
||||
specifier: ^0.41.1
|
||||
version: 0.41.1(@floating-ui/dom@1.7.4)(@tiptap/extensions@3.8.0(@tiptap/core@3.8.0(@tiptap/pm@3.8.0))(@tiptap/pm@3.8.0))(@types/hast@3.0.4)(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
|
||||
'@chatscope/chat-ui-kit-react':
|
||||
specifier: ^2.1.1
|
||||
version: 2.1.1(prop-types@15.8.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
|
||||
'@chatscope/chat-ui-kit-styles':
|
||||
specifier: ^1.4.0
|
||||
version: 1.4.0
|
||||
'@datadog/browser-rum':
|
||||
specifier: ^6.13.0
|
||||
version: 6.22.0
|
||||
|
|
@ -373,7 +398,7 @@ importers:
|
|||
version: 2.2.5
|
||||
'@cloudflare/vite-plugin':
|
||||
specifier: ^1.9.4
|
||||
version: 1.13.14(vite@6.4.1(@types/node@22.18.12)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.1)(tsx@4.20.6))(workerd@1.20251011.0)(wrangler@4.44.0)
|
||||
version: 1.13.14(vite@6.4.1(@types/node@22.18.12)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.1)(tsx@4.20.6))(workerd@1.20251011.0)(wrangler@4.44.0(@cloudflare/workers-types@4.20260411.1))
|
||||
'@esbuild-plugins/node-globals-polyfill':
|
||||
specifier: ^0.2.3
|
||||
version: 0.2.3(esbuild@0.25.11)
|
||||
|
|
@ -511,7 +536,7 @@ importers:
|
|||
version: 7.4.0
|
||||
wrangler:
|
||||
specifier: ^4.24.3
|
||||
version: 4.44.0
|
||||
version: 4.44.0(@cloudflare/workers-types@4.20260411.1)
|
||||
|
||||
packages/shared:
|
||||
dependencies:
|
||||
|
|
@ -1623,6 +1648,16 @@ packages:
|
|||
'@braintree/sanitize-url@6.0.4':
|
||||
resolution: {integrity: sha512-s3jaWicZd0pkP0jf5ysyHUI/RE7MHos6qlToFcGWXVp+ykHOy77OUMrfbgJ9it2C5bow7OIQwYYaHjk9XlBQ2A==}
|
||||
|
||||
'@chatscope/chat-ui-kit-react@2.1.1':
|
||||
resolution: {integrity: sha512-rCtE9abdmAbBDkAAUYBC1TDTBMZHquqFIZhADptAfHcJ8z8W3XH/z/ZuwBSJXtzi6h1mwCNc3tBmm1A2NLGhNg==}
|
||||
peerDependencies:
|
||||
prop-types: ^15.7.2
|
||||
react: ^16.12.0 || ^17.0.0 || ^18.2.0 || ^19.0.0
|
||||
react-dom: ^16.12.0 || ^17.0.0 || ^18.2.0 || ^19.0.0
|
||||
|
||||
'@chatscope/chat-ui-kit-styles@1.4.0':
|
||||
resolution: {integrity: sha512-016mBJD3DESw7Nh+lkKcPd22xG92ghA0VpIXIbjQtmXhC7Ve6wRazTy8z1Ahut+Tbv179+JxrftuMngsj/yV8Q==}
|
||||
|
||||
'@cloudflare/kv-asset-handler@0.4.0':
|
||||
resolution: {integrity: sha512-+tv3z+SPp+gqTIcImN9o0hqE9xyfQjI1XD9pL6NuKjua9B1y7mNYv0S9cP+QEbA4ppVgGZEmKOvHX5G5Ei1CVA==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
|
|
@ -1672,6 +1707,9 @@ packages:
|
|||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
'@cloudflare/workers-types@4.20260411.1':
|
||||
resolution: {integrity: sha512-SsntcTanLz+LmgJC8yB7sGCtpC8HxboVDmwrOH1hp1SHZwuKnhfmhUfeiwy7O/cE3iVN1cxe1E17stxP5DJXDw==}
|
||||
|
||||
'@cspotcode/source-map-support@0.8.1':
|
||||
resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==}
|
||||
engines: {node: '>=12'}
|
||||
|
|
@ -2180,6 +2218,29 @@ packages:
|
|||
'@formatjs/intl-localematcher@0.6.2':
|
||||
resolution: {integrity: sha512-XOMO2Hupl0wdd172Y06h6kLpBz6Dv+J4okPLl4LPtzbr8f66WbIoy4ev98EBuZ6ZK4h5ydTN6XneT4QVpD7cdA==}
|
||||
|
||||
'@fortawesome/fontawesome-common-types@6.7.2':
|
||||
resolution: {integrity: sha512-Zs+YeHUC5fkt7Mg1l6XTniei3k4bwG/yo3iFUtZWd/pMx9g3fdvkSK9E0FOC+++phXOka78uJcYb8JaFkW52Xg==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
'@fortawesome/fontawesome-free@6.7.2':
|
||||
resolution: {integrity: sha512-JUOtgFW6k9u4Y+xeIaEiLr3+cjoUPiAuLXoyKOJSia6Duzb7pq+A76P9ZdPDoAoxHdHzq6gE9/jKBGXlZT8FbA==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
'@fortawesome/fontawesome-svg-core@6.7.2':
|
||||
resolution: {integrity: sha512-yxtOBWDrdi5DD5o1pmVdq3WMCvnobT0LU6R8RyyVXPvFRd2o79/0NCuQoCjNTeZz9EzA9xS3JxNWfv54RIHFEA==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
'@fortawesome/free-solid-svg-icons@6.7.2':
|
||||
resolution: {integrity: sha512-GsBrnOzU8uj0LECDfD5zomZJIjrPhIlWU82AHwa2s40FKH+kcxQaBvBo3Z4TxyZHIyX8XTDxsyA33/Vx9eFuQA==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
'@fortawesome/react-fontawesome@0.2.6':
|
||||
resolution: {integrity: sha512-mtBFIi1UsYQo7rYonYFkjgYKGoL8T+fEH6NGUpvuqtY3ytMsAoDaPo5rk25KuMtKDipY4bGYM/CkmCHA1N3FUg==}
|
||||
deprecated: v0.2.x is no longer supported. Unless you are still using FontAwesome 5, please update to v3.1.1 or greater.
|
||||
peerDependencies:
|
||||
'@fortawesome/fontawesome-svg-core': ~1 || ~6 || ~7
|
||||
react: ^16.3 || ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||
|
||||
'@google-cloud/common@5.0.2':
|
||||
resolution: {integrity: sha512-V7bmBKYQyu0eVG2BFejuUjlBt+zrya6vtsKdY+JxMM/dNntPF41vZ9+LhOshEUH01zOHEqBSvI7Dad7ZS6aUeA==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
|
|
@ -5480,6 +5541,9 @@ packages:
|
|||
class-variance-authority@0.7.1:
|
||||
resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==}
|
||||
|
||||
classnames@2.5.1:
|
||||
resolution: {integrity: sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==}
|
||||
|
||||
cli-cursor@3.1.0:
|
||||
resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==}
|
||||
engines: {node: '>=8'}
|
||||
|
|
@ -6936,6 +7000,9 @@ packages:
|
|||
resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==}
|
||||
hasBin: true
|
||||
|
||||
jose@6.2.2:
|
||||
resolution: {integrity: sha512-d7kPDd34KO/YnzaDOlikGpOurfF0ByC2sEV4cANCtdqLlTfBlw2p14O/5d/zv40gJPbIQxfES3nSx1/oYNyuZQ==}
|
||||
|
||||
js-tokens@4.0.0:
|
||||
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
|
||||
|
||||
|
|
@ -11231,6 +11298,20 @@ snapshots:
|
|||
|
||||
'@braintree/sanitize-url@6.0.4': {}
|
||||
|
||||
'@chatscope/chat-ui-kit-react@2.1.1(prop-types@15.8.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)':
|
||||
dependencies:
|
||||
'@chatscope/chat-ui-kit-styles': 1.4.0
|
||||
'@fortawesome/fontawesome-free': 6.7.2
|
||||
'@fortawesome/fontawesome-svg-core': 6.7.2
|
||||
'@fortawesome/free-solid-svg-icons': 6.7.2
|
||||
'@fortawesome/react-fontawesome': 0.2.6(@fortawesome/fontawesome-svg-core@6.7.2)(react@19.0.0)
|
||||
classnames: 2.5.1
|
||||
prop-types: 15.8.1
|
||||
react: 19.0.0
|
||||
react-dom: 19.0.0(react@19.0.0)
|
||||
|
||||
'@chatscope/chat-ui-kit-styles@1.4.0': {}
|
||||
|
||||
'@cloudflare/kv-asset-handler@0.4.0':
|
||||
dependencies:
|
||||
mime: 3.0.0
|
||||
|
|
@ -11241,7 +11322,7 @@ snapshots:
|
|||
optionalDependencies:
|
||||
workerd: 1.20251011.0
|
||||
|
||||
'@cloudflare/vite-plugin@1.13.14(vite@6.4.1(@types/node@22.18.12)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.1)(tsx@4.20.6))(workerd@1.20251011.0)(wrangler@4.44.0)':
|
||||
'@cloudflare/vite-plugin@1.13.14(vite@6.4.1(@types/node@22.18.12)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.1)(tsx@4.20.6))(workerd@1.20251011.0)(wrangler@4.44.0(@cloudflare/workers-types@4.20260411.1))':
|
||||
dependencies:
|
||||
'@cloudflare/unenv-preset': 2.7.8(unenv@2.0.0-rc.21)(workerd@1.20251011.0)
|
||||
'@remix-run/node-fetch-server': 0.8.1
|
||||
|
|
@ -11251,7 +11332,7 @@ snapshots:
|
|||
tinyglobby: 0.2.15
|
||||
unenv: 2.0.0-rc.21
|
||||
vite: 6.4.1(@types/node@22.18.12)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.1)(tsx@4.20.6)
|
||||
wrangler: 4.44.0
|
||||
wrangler: 4.44.0(@cloudflare/workers-types@4.20260411.1)
|
||||
ws: 8.18.0
|
||||
transitivePeerDependencies:
|
||||
- bufferutil
|
||||
|
|
@ -11273,6 +11354,8 @@ snapshots:
|
|||
'@cloudflare/workerd-windows-64@1.20251011.0':
|
||||
optional: true
|
||||
|
||||
'@cloudflare/workers-types@4.20260411.1': {}
|
||||
|
||||
'@cspotcode/source-map-support@0.8.1':
|
||||
dependencies:
|
||||
'@jridgewell/trace-mapping': 0.3.9
|
||||
|
|
@ -11651,6 +11734,24 @@ snapshots:
|
|||
dependencies:
|
||||
tslib: 2.8.1
|
||||
|
||||
'@fortawesome/fontawesome-common-types@6.7.2': {}
|
||||
|
||||
'@fortawesome/fontawesome-free@6.7.2': {}
|
||||
|
||||
'@fortawesome/fontawesome-svg-core@6.7.2':
|
||||
dependencies:
|
||||
'@fortawesome/fontawesome-common-types': 6.7.2
|
||||
|
||||
'@fortawesome/free-solid-svg-icons@6.7.2':
|
||||
dependencies:
|
||||
'@fortawesome/fontawesome-common-types': 6.7.2
|
||||
|
||||
'@fortawesome/react-fontawesome@0.2.6(@fortawesome/fontawesome-svg-core@6.7.2)(react@19.0.0)':
|
||||
dependencies:
|
||||
'@fortawesome/fontawesome-svg-core': 6.7.2
|
||||
prop-types: 15.8.1
|
||||
react: 19.0.0
|
||||
|
||||
'@google-cloud/common@5.0.2':
|
||||
dependencies:
|
||||
'@google-cloud/projectify': 4.0.0
|
||||
|
|
@ -15697,6 +15798,8 @@ snapshots:
|
|||
dependencies:
|
||||
clsx: 2.1.1
|
||||
|
||||
classnames@2.5.1: {}
|
||||
|
||||
cli-cursor@3.1.0:
|
||||
dependencies:
|
||||
restore-cursor: 3.1.0
|
||||
|
|
@ -17687,6 +17790,8 @@ snapshots:
|
|||
|
||||
jiti@2.6.1: {}
|
||||
|
||||
jose@6.2.2: {}
|
||||
|
||||
js-tokens@4.0.0: {}
|
||||
|
||||
js-tokens@9.0.1: {}
|
||||
|
|
@ -20926,7 +21031,7 @@ snapshots:
|
|||
'@cloudflare/workerd-linux-arm64': 1.20251011.0
|
||||
'@cloudflare/workerd-windows-64': 1.20251011.0
|
||||
|
||||
wrangler@4.44.0:
|
||||
wrangler@4.44.0(@cloudflare/workers-types@4.20260411.1):
|
||||
dependencies:
|
||||
'@cloudflare/kv-asset-handler': 0.4.0
|
||||
'@cloudflare/unenv-preset': 2.7.8(unenv@2.0.0-rc.21)(workerd@1.20251011.0)
|
||||
|
|
@ -20937,6 +21042,7 @@ snapshots:
|
|||
unenv: 2.0.0-rc.21
|
||||
workerd: 1.20251011.0
|
||||
optionalDependencies:
|
||||
'@cloudflare/workers-types': 4.20260411.1
|
||||
fsevents: 2.3.3
|
||||
transitivePeerDependencies:
|
||||
- bufferutil
|
||||
|
|
|
|||
Loading…
Reference in a new issue