fix and update
This commit is contained in:
@@ -44,6 +44,7 @@ export function AddressQuickSection({
|
||||
isReady?: boolean;
|
||||
}) {
|
||||
const { user, token } = useAuth();
|
||||
const userId = user?.id;
|
||||
const { showToast } = useToast();
|
||||
const [addresses, setAddresses] = useState<UserAddress[]>([]);
|
||||
const [formOpen, setFormOpen] = useState(false);
|
||||
@@ -51,19 +52,19 @@ export function AddressQuickSection({
|
||||
const [editingAddress, setEditingAddress] = useState<UserAddress | null>(null);
|
||||
|
||||
const loadAddresses = useCallback(async () => {
|
||||
if (!user || !token || isPinLocked || !isReady) return;
|
||||
if (!userId || !token || isPinLocked || !isReady) return;
|
||||
try {
|
||||
const response = await fetchUserAddresses(user.id, token);
|
||||
const response = await fetchUserAddresses(userId, token);
|
||||
setAddresses(response.addresses ?? []);
|
||||
} catch (error) {
|
||||
const message = getApiErrorMessage(error, 'Не удалось загрузить адреса');
|
||||
if (message) showToast(message);
|
||||
}
|
||||
}, [isPinLocked, isReady, showToast, token, user]);
|
||||
}, [isPinLocked, isReady, showToast, token, userId]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isReady && user && !isPinLocked) void loadAddresses();
|
||||
}, [isPinLocked, isReady, loadAddresses, user]);
|
||||
if (isReady && userId && !isPinLocked) void loadAddresses();
|
||||
}, [isPinLocked, isReady, loadAddresses, userId]);
|
||||
|
||||
const homeAddress = useMemo(() => getPrimaryAddress(addresses, 'HOME') as UserAddress | null, [addresses]);
|
||||
const workAddress = useMemo(() => getPrimaryAddress(addresses, 'WORK') as UserAddress | null, [addresses]);
|
||||
|
||||
@@ -47,6 +47,7 @@ import {
|
||||
deleteChatRoom,
|
||||
editChatMessage,
|
||||
fetchBotChatMessages,
|
||||
fetchChatMediaUrl,
|
||||
fetchChatMessages,
|
||||
fetchChatRooms,
|
||||
getApiErrorMessage,
|
||||
@@ -67,6 +68,7 @@ import {
|
||||
} from '@/lib/chat-message-utils';
|
||||
import { detectChatMessageType, parseMessageMetadata, resolveChatContentType } from '@/lib/chat-media';
|
||||
import { uploadChatMediaBatch } from '@/lib/chat-media-upload';
|
||||
import { fetchMediaBlobWithRetry } from '@/lib/resilient-media-fetch';
|
||||
import { getSingleEmojiNative, isSingleEmojiMessage } from '@/lib/chat-emoji-display';
|
||||
import { shouldShowChatDateSeparator } from '@/lib/chat-date-utils';
|
||||
import { encryptE2EBlob, e2eKindFromMessageType } from '@/lib/e2e-chat';
|
||||
@@ -85,6 +87,74 @@ function formatMessageTime(value: string) {
|
||||
return new Intl.DateTimeFormat('ru-RU', { hour: '2-digit', minute: '2-digit' }).format(new Date(value));
|
||||
}
|
||||
|
||||
function MiniChatImageMessage({ message, token }: { message: ChatMessage; token: string }) {
|
||||
const [src, setSrc] = useState<string | null>(null);
|
||||
const [failed, setFailed] = useState(false);
|
||||
const metadata = parseMessageMetadata(message.metadataJson) as { fileName?: string; width?: number; height?: number };
|
||||
|
||||
useEffect(() => {
|
||||
if (!message.storageKey) return;
|
||||
let cancelled = false;
|
||||
let objectUrl: string | null = null;
|
||||
|
||||
setSrc(null);
|
||||
setFailed(false);
|
||||
|
||||
async function loadImage() {
|
||||
try {
|
||||
const { accessUrl } = await fetchChatMediaUrl(message.roomId, message.storageKey!, token, metadata.fileName);
|
||||
const blob = await fetchMediaBlobWithRetry(accessUrl, token, {
|
||||
mimeType: message.mimeType,
|
||||
label: 'фото чата'
|
||||
});
|
||||
if (cancelled) return;
|
||||
objectUrl = URL.createObjectURL(blob);
|
||||
setSrc(objectUrl);
|
||||
} catch {
|
||||
if (!cancelled) setFailed(true);
|
||||
}
|
||||
}
|
||||
|
||||
void loadImage();
|
||||
|
||||
return () => {
|
||||
cancelled = true;
|
||||
if (objectUrl) URL.revokeObjectURL(objectUrl);
|
||||
};
|
||||
}, [message.id, message.mimeType, message.roomId, message.storageKey, metadata.fileName, token]);
|
||||
|
||||
const ratio =
|
||||
metadata.width && metadata.height
|
||||
? Math.max(0.65, Math.min(1.35, metadata.height / metadata.width))
|
||||
: 0.72;
|
||||
|
||||
if (failed) {
|
||||
return (
|
||||
<div className="flex min-h-[120px] w-[190px] flex-col items-center justify-center gap-2 rounded-2xl bg-white/55 text-xs text-[#667085]">
|
||||
<ImageIcon className="h-5 w-5" />
|
||||
<span>Фото недоступно</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (!src) {
|
||||
return (
|
||||
<div className="flex w-[190px] items-center justify-center rounded-2xl bg-white/55" style={{ height: Math.round(190 * ratio) }}>
|
||||
<Loader2 className="h-5 w-5 animate-spin text-[#8a94a6]" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<img
|
||||
src={src}
|
||||
alt={message.content?.trim() || metadata.fileName || 'Фото в чате'}
|
||||
className="max-h-[260px] w-[190px] rounded-2xl object-cover shadow-sm"
|
||||
style={{ aspectRatio: metadata.width && metadata.height ? `${metadata.width} / ${metadata.height}` : undefined }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function MiniFamilyChat() {
|
||||
const { user, token, isPinLocked, isContentReady } = useAuth();
|
||||
const { showToast } = useToast();
|
||||
@@ -1103,6 +1173,15 @@ export function MiniFamilyChat() {
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
) : message.storageKey && message.type === 'IMAGE' && token ? (
|
||||
<div className="space-y-1">
|
||||
<MiniChatImageMessage message={message} token={token} />
|
||||
{visibleText?.trim() ? (
|
||||
<div className="whitespace-pre-wrap break-words text-sm leading-relaxed">
|
||||
<ChatEmojiContent content={visibleText} size={18} />
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
) : message.storageKey ? (
|
||||
<p className="text-sm">{getMessagePreviewText(message, visibleText)}</p>
|
||||
) : isLargeEmoji ? (
|
||||
|
||||
@@ -35,6 +35,7 @@ const RealtimeContext = React.createContext<RealtimeContextValue | null>(null);
|
||||
|
||||
export function RealtimeProvider({ children }: { children: React.ReactNode }) {
|
||||
const { user, token, isPinLocked, isLoading, isContentReady } = useAuth();
|
||||
const userId = user?.id;
|
||||
const [unreadCount, setUnreadCount] = React.useState(0);
|
||||
const [connected, setConnected] = React.useState(false);
|
||||
const listenersRef = React.useRef(new Set<Listener>());
|
||||
@@ -52,7 +53,7 @@ export function RealtimeProvider({ children }: { children: React.ReactNode }) {
|
||||
}, []);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!user || !token || isPinLocked || isLoading || !isContentReady) {
|
||||
if (!userId || !token || isPinLocked || isLoading || !isContentReady) {
|
||||
setUnreadCount(0);
|
||||
return;
|
||||
}
|
||||
@@ -68,10 +69,10 @@ export function RealtimeProvider({ children }: { children: React.ReactNode }) {
|
||||
cancelled = true;
|
||||
window.clearTimeout(timer);
|
||||
};
|
||||
}, [isContentReady, isLoading, isPinLocked, token, user]);
|
||||
}, [isContentReady, isLoading, isPinLocked, token, userId]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!user || !token || isPinLocked || isLoading || !isContentReady) {
|
||||
if (!userId || !token || isPinLocked || isLoading || !isContentReady) {
|
||||
socketRef.current?.close();
|
||||
socketRef.current = null;
|
||||
setConnected(false);
|
||||
@@ -149,7 +150,7 @@ export function RealtimeProvider({ children }: { children: React.ReactNode }) {
|
||||
socketRef.current = null;
|
||||
setConnected(false);
|
||||
};
|
||||
}, [emit, isContentReady, isLoading, isPinLocked, token, user]);
|
||||
}, [emit, isContentReady, isLoading, isPinLocked, token, userId]);
|
||||
|
||||
return (
|
||||
<RealtimeContext.Provider value={{ unreadCount, setUnreadCount, subscribe, connected }}>
|
||||
|
||||
Reference in New Issue
Block a user