mirror of
https://github.com/fromchat-messenger/web.git
synced 2026-09-22 19:15:08 +03:00
Refactor state
This commit is contained in:
@@ -4,7 +4,8 @@ import useDownloadAppScreen from "@/core/hooks/useDownloadAppScreen";
|
||||
import { CallWindow } from "./right/calls/CallWindow";
|
||||
import { useEffect, useRef } from "react";
|
||||
import { useLocation, useNavigate } from "react-router-dom";
|
||||
import { useAppState } from "@/pages/chat/state";
|
||||
import { useUserStore } from "@/state/user";
|
||||
import { useProfileStore } from "@/state/profile";
|
||||
import { fetchUserProfileById, fetchUserProfile } from "@/core/api/account/profile";
|
||||
import styles from "@/pages/chat/css/layout.module.scss";
|
||||
|
||||
@@ -12,7 +13,8 @@ export default function ChatPage() {
|
||||
const { navigate: navigateDownloadApp } = useDownloadAppScreen();
|
||||
const location = useLocation();
|
||||
const navigate = useNavigate();
|
||||
const { user, setProfileDialog } = useAppState();
|
||||
const { user } = useUserStore();
|
||||
const { setProfileDialog } = useProfileStore();
|
||||
const processedProfile = useRef<string | null>(null);
|
||||
|
||||
// Handle profile links ONLY from navigation state (from SmartCatchAll)
|
||||
@@ -64,7 +66,7 @@ export default function ChatPage() {
|
||||
}
|
||||
|
||||
handleProfileLink();
|
||||
}, [location.state, user.authToken, user.currentUser?.id, setProfileDialog, navigate, location.pathname]);
|
||||
}, [location.state, user.authToken, user.currentUser?.id, navigate, location.pathname]);
|
||||
|
||||
if (navigateDownloadApp) return navigateDownloadApp;
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useState, useEffect, useRef, useMemo, type ReactNode } from "react";
|
||||
import { useAppState } from "@/pages/chat/state";
|
||||
import type { ProfileDialogData } from "@/pages/chat/state";
|
||||
import { useProfileStore } from "@/state/profile";
|
||||
import { useUserStore } from "@/state/user";
|
||||
import type { ProfileDialogData } from "@/state/types";
|
||||
import defaultAvatar from "@/images/default-avatar.png";
|
||||
import { confirm } from "mdui/functions/confirm";
|
||||
import { prompt } from "mdui/functions/prompt";
|
||||
@@ -70,7 +71,8 @@ function Section({ type, icon, label, error, value, onChange, readOnly, placehol
|
||||
}
|
||||
|
||||
export function ProfileDialog() {
|
||||
const { chat, user, closeProfileDialog, setUser } = useAppState();
|
||||
const { profileDialog, closeProfileDialog } = useProfileStore();
|
||||
const { user, setUser } = useUserStore();
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [originalData, setOriginalData] = useState<ProfileDialogData | null>(null);
|
||||
const [currentData, setCurrentData] = useState<ProfileDialogData | null>(null);
|
||||
@@ -80,13 +82,13 @@ export function ProfileDialog() {
|
||||
|
||||
// Handle dialog open/close based on state
|
||||
useEffect(() => {
|
||||
if (chat.profileDialog && !isOpen) {
|
||||
if (profileDialog && !isOpen) {
|
||||
// Fetch fresh data when opening dialog
|
||||
fetchFreshProfileData(chat.profileDialog);
|
||||
} else if (!chat.profileDialog && isOpen) {
|
||||
fetchFreshProfileData(profileDialog);
|
||||
} else if (!profileDialog && isOpen) {
|
||||
setIsOpen(false);
|
||||
}
|
||||
}, [chat.profileDialog, isOpen]);
|
||||
}, [profileDialog, isOpen]);
|
||||
|
||||
async function fetchFreshProfileData(profileData: ProfileDialogData) {
|
||||
if (!user.authToken) return;
|
||||
|
||||
@@ -2,14 +2,16 @@ import { PRODUCT_NAME } from "@/core/config";
|
||||
import useProfile from "@/pages/chat/hooks/useProfile";
|
||||
import defaultAvatar from "@/images/default-avatar.png";
|
||||
import { useState } from "react";
|
||||
import { useAppState } from "@/pages/chat/state";
|
||||
import { useUserStore } from "@/state/user";
|
||||
import { useProfileStore } from "@/state/profile";
|
||||
import { MinimizedCallBar } from "@/pages/chat/ui/right/calls/MinimizedCallBar";
|
||||
import styles from "@/pages/chat/css/left-panel.module.scss";
|
||||
import logoIcon from "@/images/logo.svg";
|
||||
|
||||
export function ChatHeader({ headerRef }: { headerRef?: React.RefObject<HTMLElement | null> }) {
|
||||
const { profileData } = useProfile();
|
||||
const { setProfileDialog, user } = useAppState();
|
||||
const { user } = useUserStore();
|
||||
const { setProfileDialog } = useProfileStore();
|
||||
const [profilePictureUrl, setProfilePictureUrl] = useState(profileData?.profile_picture || defaultAvatar);
|
||||
|
||||
function handleProfileClick() {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useAppState } from "@/pages/chat/state";
|
||||
import { useUserStore } from "@/state/user";
|
||||
import { useRef, useState } from "react";
|
||||
import { SettingsDialog } from "./settings/SettingsDialog";
|
||||
import { UsernameSearch } from "./UsernameSearch";
|
||||
@@ -9,7 +9,7 @@ import styles from "@/pages/chat/css/left-panel.module.scss";
|
||||
|
||||
function BottomAppBar({ bottomAppBarRef }: { bottomAppBarRef?: React.RefObject<MDUIBottomAppBar | null> }) {
|
||||
const [settingsOpen, onSettingsOpenChange] = useState(false);
|
||||
const { logout } = useAppState();
|
||||
const { logout } = useUserStore();
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useState, useEffect, useCallback, useMemo } from "react";
|
||||
import { useAppState } from "@/pages/chat/state";
|
||||
import { useUserStore } from "@/state/user";
|
||||
import { useChatStore } from "@/state/chat";
|
||||
import { useDM, type DMUser } from "@/pages/chat/hooks/useDM";
|
||||
import { fetchMessages } from "@/core/api/messaging";
|
||||
import { fetchUserPublicKey } from "@/core/api/dm";
|
||||
@@ -42,7 +43,8 @@ const PUBLIC_CHAT: PublicChat = {
|
||||
};
|
||||
|
||||
export function UnifiedChatsList() {
|
||||
const { user, switchToPublicChat, switchToDM, chat } = useAppState();
|
||||
const { user } = useUserStore();
|
||||
const { switchToPublicChat, switchToDM, activeTab } = useChatStore();
|
||||
const { dmUsers, isLoadingUsers, loadUsers } = useDM();
|
||||
const [lastMessages, setLastMessages] = useState<Record<string, Message | undefined>>({});
|
||||
|
||||
@@ -61,11 +63,11 @@ export function UnifiedChatsList() {
|
||||
}, [user.authToken]);
|
||||
|
||||
useEffect(() => {
|
||||
if (chat.activeTab === "chats") {
|
||||
if (activeTab === "chats") {
|
||||
loadUsers();
|
||||
loadLastMessages();
|
||||
}
|
||||
}, [chat.activeTab, loadUsers, loadLastMessages]);
|
||||
}, [activeTab, loadUsers, loadLastMessages]);
|
||||
|
||||
const allChats = useMemo<ChatItem[]>(() => {
|
||||
return [
|
||||
@@ -155,7 +157,7 @@ export function UnifiedChatsList() {
|
||||
|
||||
async function handleDMClick(dmConversation: DMConversation) {
|
||||
if (!dmConversation.publicKey) {
|
||||
const authToken = useAppState.getState().user.authToken;
|
||||
const authToken = useUserStore.getState().user.authToken;
|
||||
if (!authToken) return;
|
||||
|
||||
const publicKey = await fetchUserPublicKey(dmConversation.id, authToken);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useState, useEffect, useRef } from "react";
|
||||
import { useAppState } from "@/pages/chat/state";
|
||||
import { useUserStore } from "@/state/user";
|
||||
import { useChatStore } from "@/state/chat";
|
||||
import { searchUsers, fetchUserPublicKey } from "@/core/api/dm";
|
||||
import { StatusBadge } from "@/core/components/StatusBadge";
|
||||
import type { User } from "@/core/types";
|
||||
@@ -23,7 +24,8 @@ export interface UsernameSearchProps {
|
||||
}
|
||||
|
||||
export function UsernameSearch({ containerRef, headerRef, bottomAppBarRef }: UsernameSearchProps) {
|
||||
const { user, switchToDM, chat } = useAppState();
|
||||
const { user } = useUserStore();
|
||||
const { switchToDM, activeDm } = useChatStore();
|
||||
const [searchQuery, setSearchQuery] = useState("");
|
||||
const [searchResults, setSearchResults] = useState<SearchUser[]>([]);
|
||||
const [isSearching, setIsSearching] = useState(false);
|
||||
@@ -68,7 +70,7 @@ export function UsernameSearch({ containerRef, headerRef, bottomAppBarRef }: Use
|
||||
|
||||
// Subscribe to online status for all search results
|
||||
useEffect(() => {
|
||||
const activeDmUserId = chat.activeDm?.userId;
|
||||
const activeDmUserId = activeDm?.userId;
|
||||
const switchingToUserId = switchingToUserIdRef.current;
|
||||
const currentSearchResultIds = new Set(searchResults.map(u => u.id));
|
||||
const previousSearchResultIds = new Set(previousSearchResultIdsRef.current);
|
||||
@@ -101,12 +103,12 @@ export function UsernameSearch({ containerRef, headerRef, bottomAppBarRef }: Use
|
||||
|
||||
// Clear the ref if the user is now the active DM (state has updated)
|
||||
const finalSwitchingToUserId = switchingToUserIdRef.current;
|
||||
const finalActiveDmUserId = chat.activeDm?.userId;
|
||||
const finalActiveDmUserId = activeDm?.userId;
|
||||
if (finalSwitchingToUserId && finalSwitchingToUserId === finalActiveDmUserId) {
|
||||
switchingToUserIdRef.current = null;
|
||||
}
|
||||
};
|
||||
}, [searchResults, chat.activeDm?.userId]);
|
||||
}, [searchResults, activeDm?.userId]);
|
||||
|
||||
|
||||
async function handleUserClick(searchUser: SearchUser) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { MaterialList, MaterialListItem } from "@/utils/material";
|
||||
import { useAppState } from "@/pages/chat/state";
|
||||
import { useUserStore } from "@/state/user";
|
||||
import { deleteAccount } from "@/core/api/account";
|
||||
import { confirm } from "mdui/functions/confirm";
|
||||
import styles from "@/pages/chat/css/settings-dialog.module.scss";
|
||||
@@ -9,7 +9,7 @@ interface AccountPanelProps {
|
||||
}
|
||||
|
||||
export function AccountPanel({ onClose }: AccountPanelProps) {
|
||||
const { user, logout } = useAppState();
|
||||
const { user, logout } = useUserStore();
|
||||
const authToken = user?.authToken;
|
||||
|
||||
async function handleDeleteAccount() {
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { useState } from "react";
|
||||
import { StyledDialog } from "@/core/components/StyledDialog";
|
||||
import type { DialogProps } from "@/core/types";
|
||||
import { useAppState } from "@/pages/chat/state";
|
||||
import { useUserStore } from "@/state/user";
|
||||
import { changePassword } from "@/core/api/account";
|
||||
import { MaterialButton, MaterialIconButton, MaterialSwitch, MaterialTextField } from "@/utils/material";
|
||||
import styles from "@/pages/chat/css/changePasswordDialog.module.scss";
|
||||
|
||||
export default function ChangePasswordDialog({ isOpen, onOpenChange }: DialogProps) {
|
||||
const { user } = useAppState();
|
||||
const { user } = useUserStore();
|
||||
|
||||
const [current, setCurrent] = useState("");
|
||||
const [next, setNext] = useState("");
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import { useImmer } from "use-immer";
|
||||
import { MaterialList, MaterialListItem, MaterialButton, MaterialCircularProgress } from "@/utils/material";
|
||||
import { useAppState } from "@/pages/chat/state";
|
||||
import { useUserStore } from "@/state/user";
|
||||
import { listDevices, revokeDevice, logoutAllOtherDevices, type DeviceInfo } from "@/core/api/account/devices";
|
||||
import { confirm } from "mdui/functions/confirm";
|
||||
import styles from "@/pages/chat/css/settings-dialog.module.scss";
|
||||
|
||||
export function DevicesPanel() {
|
||||
const { user } = useAppState();
|
||||
const { user } = useUserStore();
|
||||
const authToken = user?.authToken ?? null;
|
||||
const [devices, updateDevices] = useImmer<DeviceInfo[]>([]);
|
||||
const [devicesLoading, setDevicesLoading] = useState(false);
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { useState, useRef } from "react";
|
||||
import { MaterialList, MaterialListItem, MaterialSwitch, type MDUISwitch } from "@/utils/material";
|
||||
import { useAppState } from "@/pages/chat/state";
|
||||
import { useUserStore } from "@/state/user";
|
||||
import { initialize, subscribe, unsubscribe, isSupported } from "@/core/push-notifications/push-notifications";
|
||||
import { isElectron } from "@/core/electron/electron";
|
||||
import { unsubscribeFromPush } from "@/core/api/push";
|
||||
import styles from "@/pages/chat/css/settings-dialog.module.scss";
|
||||
|
||||
export function NotificationsPanel() {
|
||||
const { user } = useAppState();
|
||||
const { user } = useUserStore();
|
||||
const authToken = user?.authToken ?? null;
|
||||
const [pushEnabled, setPushEnabled] = useState(false);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { useAppState } from "@/pages/chat/state";
|
||||
import { useChatStore } from "@/state/chat";
|
||||
import defaultAvatar from "@/images/default-avatar.png";
|
||||
|
||||
export function ChatMainHeader() {
|
||||
const { currentChat } = useAppState().chat;
|
||||
const { currentChat } = useChatStore();
|
||||
|
||||
return (
|
||||
<div className="chat-header">
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Message } from "./Message";
|
||||
import { useAppState } from "@/pages/chat/state";
|
||||
import { useUserStore } from "@/state/user";
|
||||
import type { Message as MessageType } from "@/core/types";
|
||||
import { MessageContextMenu, type ContextMenuState } from "./MessageContextMenu";
|
||||
import { useState, type ReactNode } from "react";
|
||||
@@ -20,7 +20,7 @@ interface ChatMessagesProps {
|
||||
}
|
||||
|
||||
export function ChatMessages({ messages = [], children, isDm = false, onReplySelect, onEditSelect, onDelete, onRetryMessage, dmRecipientPublicKey }: ChatMessagesProps) {
|
||||
const { user } = useAppState();
|
||||
const { user } = useUserStore();
|
||||
|
||||
// Context menu state
|
||||
const [contextMenu, setContextMenu] = useState<ContextMenuState>({
|
||||
|
||||
@@ -8,7 +8,8 @@ import { useEffect, useState, useRef, useMemo } from "react";
|
||||
import { getCurrentKeys, getAuthHeaders } from "@/core/api/account";
|
||||
import { ecdhSharedSecret, deriveWrappingKey } from "@/utils/crypto/asymmetric";
|
||||
import { importAesGcmKey, aesGcmDecrypt } from "@/utils/crypto/symmetric";
|
||||
import { useAppState } from "@/pages/chat/state";
|
||||
import { useUserStore } from "@/state/user";
|
||||
import { useProfileStore } from "@/state/profile";
|
||||
import { fetchUserProfileById, fetchUserProfile } from "@/core/api/account/profile";
|
||||
import { StatusBadge } from "@/core/components/StatusBadge";
|
||||
import { ub64 } from "@/utils/utils";
|
||||
@@ -25,7 +26,7 @@ interface MessageReactionsProps {
|
||||
}
|
||||
|
||||
function Reactions({ reactions, onReactionClick, messageId }: MessageReactionsProps) {
|
||||
const { user } = useAppState();
|
||||
const { user } = useUserStore();
|
||||
const [visibleReactions, setVisibleReactions] = useState<Reaction[]>([]);
|
||||
const [animatingReactions, setAnimatingReactions] = useState<Set<string>>(new Set());
|
||||
const [isVisible, setIsVisible] = useState(false);
|
||||
@@ -162,7 +163,8 @@ export function Message({ message, isAuthor, onContextMenu, onReactionClick, isD
|
||||
endRect: Rect;
|
||||
} | null>(null);
|
||||
const [isAnimatingOpen, setIsAnimatingOpen] = useState(false);
|
||||
const { user, setProfileDialog } = useAppState();
|
||||
const { user } = useUserStore();
|
||||
const { setProfileDialog } = useProfileStore();
|
||||
const imageRefs = useRef<Map<string, HTMLImageElement>>(new Map());
|
||||
const dmEnvelope = message.runtimeData?.dmEnvelope;
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useState, useEffect, useRef } from "react";
|
||||
import type { Message, Size2D } from "@/core/types";
|
||||
import { EmojiMenu } from "./EmojiMenu";
|
||||
import { useAppState } from "@/pages/chat/state";
|
||||
import { useUserStore } from "@/state/user";
|
||||
import styles from "@/pages/chat/css/MessageContextMenu.module.scss";
|
||||
|
||||
interface MessageContextMenuProps {
|
||||
@@ -35,7 +35,7 @@ export function MessageContextMenu({
|
||||
isOpen,
|
||||
onOpenChange
|
||||
}: MessageContextMenuProps) {
|
||||
const { user } = useAppState();
|
||||
const { user } = useUserStore();
|
||||
// Internal state for closing animation
|
||||
const [isClosing, setIsClosing] = useState(false);
|
||||
const [reactionBarPosition, setReactionBarPosition] = useState<Size2D>({ x: 0, y: 0 });
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import { useState, useEffect, useRef, useMemo, type ReactNode } from "react";
|
||||
import { motion, AnimatePresence } from "motion/react";
|
||||
import { useAppState } from "@/pages/chat/state";
|
||||
import { useChatStore } from "@/state/chat";
|
||||
import { useUserStore } from "@/state/user";
|
||||
import { usePresenceStore } from "@/state/presence";
|
||||
import { useProfileStore } from "@/state/profile";
|
||||
import { MessagePanel, type MessagePanelState } from "./panels/MessagePanel";
|
||||
import { ChatMessages } from "./ChatMessages";
|
||||
import { ChatInputWrapper } from "./ChatInputWrapper";
|
||||
@@ -23,19 +26,20 @@ interface MessagePanelRendererProps {
|
||||
}
|
||||
|
||||
function ChatHeaderText({ panel }: { panel: MessagePanel | null }) {
|
||||
const { chat, user } = useAppState();
|
||||
const { typingUsers, dmTypingUsers } = usePresenceStore();
|
||||
const { user } = useUserStore();
|
||||
const otherTypingUsers = useMemo(() => {
|
||||
return Array
|
||||
.from(chat.typingUsers.entries())
|
||||
.from(typingUsers.entries())
|
||||
.filter(([userId, username]) => userId !== user.currentUser?.id && username)
|
||||
.map(([, username]) => username!);
|
||||
}, [chat.typingUsers, user.currentUser?.id]);
|
||||
}, [typingUsers, user.currentUser?.id]);
|
||||
|
||||
let content: ReactNode;
|
||||
|
||||
if (panel instanceof DMPanel) {
|
||||
const recipientId = panel.getRecipientId()!;
|
||||
const isTyping = chat.dmTypingUsers.get(recipientId);
|
||||
const isTyping = dmTypingUsers.get(recipientId);
|
||||
|
||||
content = isTyping ? <TypingIndicator typingUsers={[]} /> : <OnlineStatus userId={recipientId} />;
|
||||
} else if (panel instanceof PublicChatPanel && otherTypingUsers.length > 0) {
|
||||
@@ -48,7 +52,8 @@ function ChatHeaderText({ panel }: { panel: MessagePanel | null }) {
|
||||
}
|
||||
|
||||
export function MessagePanelRenderer({ panel }: MessagePanelRendererProps) {
|
||||
const { applyPendingPanel, chat, setProfileDialog } = useAppState();
|
||||
const { applyPendingPanel, isSwitching, pendingPanel, activePanel, setIsSwitching } = useChatStore();
|
||||
const { setProfileDialog } = useProfileStore();
|
||||
const messagePanelRef = useRef<HTMLDivElement>(null);
|
||||
const [panelState, setPanelState] = useState<MessagePanelState | null>(null);
|
||||
const messagesEndRef = useRef<HTMLDivElement>(null);
|
||||
@@ -121,30 +126,30 @@ export function MessagePanelRenderer({ panel }: MessagePanelRendererProps) {
|
||||
|
||||
// Handle chat switching animation
|
||||
useEffect(() => {
|
||||
if (chat.isSwitching && chat.pendingPanel) {
|
||||
if (isSwitching && pendingPanel) {
|
||||
// Apply pending panel when animation starts
|
||||
applyPendingPanel();
|
||||
// End switching state after a brief delay to allow animation
|
||||
setTimeout(() => {
|
||||
chat.setIsSwitching(false);
|
||||
setIsSwitching(false);
|
||||
}, 200);
|
||||
}
|
||||
}, [chat.isSwitching, chat.pendingPanel, applyPendingPanel]);
|
||||
}, [isSwitching, pendingPanel, applyPendingPanel, setIsSwitching]);
|
||||
|
||||
// Load messages when panel changes and animation is not running
|
||||
useEffect(() => {
|
||||
if (!chat.activePanel || chat.isSwitching) return;
|
||||
if (!activePanel || isSwitching) return;
|
||||
|
||||
const panelState = chat.activePanel.getState();
|
||||
const panelState = activePanel.getState();
|
||||
|
||||
if (panelState.messages.length === 0 && !panelState.isLoading) {
|
||||
chat.activePanel.loadMessages();
|
||||
activePanel.loadMessages();
|
||||
}
|
||||
}, [chat.activePanel, chat.isSwitching]);
|
||||
}, [activePanel, isSwitching]);
|
||||
|
||||
// Scroll to bottom only when new messages are added
|
||||
useEffect(() => {
|
||||
if (!panelState || chat.isSwitching) return;
|
||||
if (!panelState || isSwitching) return;
|
||||
|
||||
const currentMessageCount = panelState.messages.length;
|
||||
const previousMessageCount = previousMessageCountRef.current;
|
||||
@@ -168,7 +173,7 @@ export function MessagePanelRenderer({ panel }: MessagePanelRendererProps) {
|
||||
|
||||
// Update the previous message count
|
||||
previousMessageCountRef.current = currentMessageCount;
|
||||
}, [panelState?.messages, panelState?.isLoading, chat.isSwitching]);
|
||||
}, [panelState?.messages, panelState?.isLoading, isSwitching]);
|
||||
|
||||
function handleCallClick() {
|
||||
if (panel && panelState && panel.isDm()) {
|
||||
@@ -195,7 +200,7 @@ export function MessagePanelRenderer({ panel }: MessagePanelRendererProps) {
|
||||
}
|
||||
}
|
||||
|
||||
const panelKey = chat.activePanel?.getState().title || "empty";
|
||||
const panelKey = activePanel?.getState().title || "empty";
|
||||
|
||||
return (
|
||||
<div className={styles.chatContainer}>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
* @version 1.0.0
|
||||
*/
|
||||
|
||||
import { useAppState } from "@/pages/chat/state";
|
||||
import { usePresenceStore } from "@/state/presence";
|
||||
import styles from "@/pages/chat/css/TypingIndicators.module.scss";
|
||||
|
||||
interface OnlineIndicatorProps {
|
||||
@@ -14,8 +14,8 @@ interface OnlineIndicatorProps {
|
||||
}
|
||||
|
||||
export function OnlineIndicator({ userId, className = "" }: OnlineIndicatorProps) {
|
||||
const { chat } = useAppState();
|
||||
const status = chat.onlineStatuses.get(userId);
|
||||
const { onlineStatuses } = usePresenceStore();
|
||||
const status = onlineStatuses.get(userId);
|
||||
|
||||
// Only show indicator when user is online
|
||||
if (!status || !status.online) {
|
||||
|
||||
@@ -5,7 +5,8 @@
|
||||
* @version 1.0.0
|
||||
*/
|
||||
|
||||
import { useAppState } from "@/pages/chat/state";
|
||||
import { usePresenceStore } from "@/state/presence";
|
||||
import { useUserStore } from "@/state/user";
|
||||
import styles from "@/pages/chat/css/TypingIndicators.module.scss";
|
||||
|
||||
interface OnlineStatusProps {
|
||||
@@ -14,8 +15,9 @@ interface OnlineStatusProps {
|
||||
}
|
||||
|
||||
export function OnlineStatus({ userId, showLastSeen = false }: OnlineStatusProps) {
|
||||
const { chat, user } = useAppState();
|
||||
const status = userId === user.currentUser?.id ? { online: true, lastSeen: new Date().toISOString() } : chat.onlineStatuses.get(userId);
|
||||
const { onlineStatuses } = usePresenceStore();
|
||||
const { user } = useUserStore();
|
||||
const status = userId === user.currentUser?.id ? { online: true, lastSeen: new Date().toISOString() } : onlineStatuses.get(userId);
|
||||
|
||||
function formatLastSeen(lastSeen: string): string {
|
||||
const date = new Date(lastSeen);
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { useAppState } from "@/pages/chat/state";
|
||||
import { useChatStore } from "@/state/chat";
|
||||
import { MessagePanelRenderer } from "./MessagePanelRenderer";
|
||||
|
||||
export function RightPanel() {
|
||||
const { chat } = useAppState();
|
||||
const { activePanel } = useChatStore();
|
||||
|
||||
return <MessagePanelRenderer panel={chat.activePanel} />
|
||||
return <MessagePanelRenderer panel={activePanel} />
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import { useAppState } from "@/pages/chat/state";
|
||||
import { useCallStore } from "@/state/call";
|
||||
import { useUserStore } from "@/state/user";
|
||||
import useCall from "@/pages/chat/hooks/useCall";
|
||||
import defaultAvatar from "@/images/default-avatar.png";
|
||||
import { createPortal } from "react-dom";
|
||||
@@ -9,8 +10,8 @@ import { motion, AnimatePresence } from "motion/react";
|
||||
import styles from "@/pages/chat/css/callWindow.module.scss";
|
||||
|
||||
export function CallWindow() {
|
||||
const { chat, toggleCallMinimize, user } = useAppState();
|
||||
const { call } = chat;
|
||||
const { call, toggleCallMinimized } = useCallStore();
|
||||
const { user } = useUserStore();
|
||||
const {
|
||||
acceptCall,
|
||||
rejectCall,
|
||||
@@ -158,7 +159,7 @@ export function CallWindow() {
|
||||
<div className={styles.callHeader}>
|
||||
<div className={styles.windowControls}>
|
||||
<MaterialIconButton
|
||||
onClick={toggleCallMinimize}
|
||||
onClick={toggleCallMinimized}
|
||||
icon={call.isMinimized ? "open_in_full" : "close_fullscreen"}
|
||||
className={styles.windowControlBtn}
|
||||
/>
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import { useAppState } from "@/pages/chat/state";
|
||||
import { useCallStore } from "@/state/call";
|
||||
import useCall from "@/pages/chat/hooks/useCall";
|
||||
import defaultAvatar from "@/images/default-avatar.png";
|
||||
import { MaterialIconButton } from "@/utils/material";
|
||||
|
||||
export function MinimizedCallBar() {
|
||||
const { chat, toggleCallMinimize } = useAppState();
|
||||
const { call } = chat;
|
||||
const { call, toggleCallMinimized } = useCallStore();
|
||||
const { endCall, toggleMute } = useCall();
|
||||
|
||||
function getGradientClass() {
|
||||
@@ -39,7 +38,7 @@ export function MinimizedCallBar() {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={`minimized-call-bar ${getGradientClass()}`} onClick={toggleCallMinimize}>
|
||||
<div className={`minimized-call-bar ${getGradientClass()}`} onClick={toggleCallMinimized}>
|
||||
<div className="call-info">
|
||||
<img src={defaultAvatar} alt="Avatar" className="avatar" />
|
||||
<div className="user-details">
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
} from "@/core/api/dm";
|
||||
import { fetchUserProfileById } from "@/core/api/account/profile";
|
||||
import type { DmEncryptedJSON, DmEnvelope, DMWebSocketMessage, EncryptedMessageJson, Message } from "@/core/types";
|
||||
import type { UserState, ProfileDialogData } from "@/pages/chat/state";
|
||||
import type { UserState, ProfileDialogData } from "@/state/types";
|
||||
import { formatDMUsername } from "@/pages/chat/hooks/useDM";
|
||||
import { onlineStatusManager } from "@/core/onlineStatusManager";
|
||||
import { typingManager } from "@/core/typingManager";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { Message, WebSocketMessage } from "@/core/types";
|
||||
import type { UserState, ProfileDialogData } from "@/pages/chat/state";
|
||||
import type { UserState, ProfileDialogData } from "@/state/types";
|
||||
|
||||
export interface MessagePanelState {
|
||||
id: string;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { MessagePanel } from "./MessagePanel";
|
||||
import { request } from "@/core/websocket";
|
||||
import type { ChatWebSocketMessage, Message, ReactionUpdateWebSocketMessage } from "@/core/types";
|
||||
import type { UserState, ProfileDialogData } from "@/pages/chat/state";
|
||||
import type { UserState, ProfileDialogData } from "@/state/types";
|
||||
import { fetchMessages, sendMessage, sendMessageWithFiles } from "@/core/api/messaging";
|
||||
|
||||
export class PublicChatPanel extends MessagePanel {
|
||||
|
||||
Reference in New Issue
Block a user