fix and update

This commit is contained in:
lendry
2026-06-30 10:36:21 +03:00
parent a76997986a
commit 7f10b18336
6 changed files with 73 additions and 29 deletions

View File

@@ -40,6 +40,8 @@ interface AuthContextValue {
token: string | null;
isLoading: boolean;
isApiReady: boolean;
/** false первые ~600 ms после bootstrap — блокирует шторм API сразу после логина */
isContentReady: boolean;
isPinLocked: boolean;
hasStoredSession: boolean;
login: (login: string, password: string) => Promise<AuthTokens>;
@@ -109,6 +111,7 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
const [user, setUser] = React.useState<PublicUser | null>(null);
const [isLoading, setIsLoading] = React.useState(true);
const [isApiReady, setIsApiReady] = React.useState(false);
const [isContentReady, setIsContentReady] = React.useState(false);
const [isPinLocked, setIsPinLocked] = React.useState(false);
const [hasStoredSession, setHasStoredSession] = React.useState(false);
const [lockedSessionId, setLockedSessionId] = React.useState<string | null>(null);
@@ -177,8 +180,9 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
initialBootstrapDoneRef.current = true;
setIsApiReady(true);
setIsLoading(false);
setIsContentReady(false);
if (auth.pinVerified !== false) {
scheduleFedcmSessionSync(auth.accessToken, 2000);
scheduleFedcmSessionSync(auth.accessToken, 2500);
}
},
[clearPinLock]
@@ -546,6 +550,21 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
Boolean(hasStoredSession || token || user) &&
(isLoading || !isApiReady);
React.useEffect(() => {
if (isBootstrapping) {
setIsContentReady(false);
return;
}
let cancelled = false;
const timer = window.setTimeout(() => {
if (!cancelled) setIsContentReady(true);
}, 650);
return () => {
cancelled = true;
window.clearTimeout(timer);
};
}, [isBootstrapping]);
return (
<AuthContext.Provider
value={{
@@ -553,6 +572,7 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
token,
isLoading,
isApiReady,
isContentReady,
isPinLocked,
hasStoredSession,
login,
@@ -570,7 +590,7 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
logout
}}
>
{isBootstrapping ? <AppBootstrapScreen /> : children}
{isBootstrapping || !isContentReady ? <AppBootstrapScreen /> : children}
<PinLockModal open={isPinLocked} isSubmitting={pinSubmitting} error={pinError} onSubmit={handlePinUnlock} />
</AuthContext.Provider>
);

View File

@@ -10,7 +10,6 @@ import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover
import {
AppNotification,
fetchNotifications,
fetchUnreadNotificationCount,
getApiErrorMessage,
markAllNotificationsRead,
markNotificationRead,
@@ -27,7 +26,7 @@ function formatTime(value: string) {
export function NotificationBell() {
const router = useRouter();
const { token, isPinLocked, isLoading, isApiReady } = useAuth();
const { token, isPinLocked, isLoading, isContentReady } = useAuth();
const { showToast } = useToast();
const { unreadCount, setUnreadCount, subscribe } = useRealtime();
const [open, setOpen] = useState(false);
@@ -36,23 +35,23 @@ export function NotificationBell() {
const [respondingId, setRespondingId] = useState<string | null>(null);
const load = useCallback(async () => {
if (!token || isPinLocked || isLoading || !isApiReady) return;
if (!token || isPinLocked || isLoading || !isContentReady) return;
setLoading(true);
try {
const [list, count] = await Promise.all([fetchNotifications(token), fetchUnreadNotificationCount(token)]);
const list = await fetchNotifications(token);
setItems(list.notifications ?? []);
setUnreadCount(count.count ?? 0);
} catch (error) {
const message = getApiErrorMessage(error, 'Не удалось загрузить уведомления');
if (message) showToast(message);
} finally {
setLoading(false);
}
}, [isApiReady, isLoading, isPinLocked, setUnreadCount, showToast, token]);
}, [isContentReady, isLoading, isPinLocked, showToast, token]);
useEffect(() => {
if (!open) return;
void load();
}, [load]);
}, [load, open]);
useEffect(() => {
return subscribe((event) => {

View File

@@ -2,7 +2,7 @@
import * as React from 'react';
import { useAuth } from '@/components/id/auth-provider';
import { ensureApiGatewayReady, getWsUrl, type ChatMessage } from '@/lib/api';
import { ensureApiGatewayReady, fetchUnreadNotificationCount, getWsUrl, type ChatMessage } from '@/lib/api';
import { dispatchAvatarUpdated, dispatchChatRoomAvatarUpdated } from '@/lib/avatar-events';
export interface RealtimeEvent {
@@ -31,7 +31,7 @@ interface RealtimeContextValue {
const RealtimeContext = React.createContext<RealtimeContextValue | null>(null);
export function RealtimeProvider({ children }: { children: React.ReactNode }) {
const { user, token, isPinLocked, isLoading, isApiReady } = useAuth();
const { user, token, isPinLocked, isLoading, isContentReady } = useAuth();
const [unreadCount, setUnreadCount] = React.useState(0);
const [connected, setConnected] = React.useState(false);
const listenersRef = React.useRef(new Set<Listener>());
@@ -48,7 +48,26 @@ export function RealtimeProvider({ children }: { children: React.ReactNode }) {
}, []);
React.useEffect(() => {
if (!user || !token || isPinLocked || isLoading || !isApiReady) {
if (!user || !token || isPinLocked || isLoading || !isContentReady) {
setUnreadCount(0);
return;
}
let cancelled = false;
const timer = window.setTimeout(() => {
void fetchUnreadNotificationCount(token)
.then((result) => {
if (!cancelled) setUnreadCount(result.count ?? 0);
})
.catch(() => undefined);
}, 400);
return () => {
cancelled = true;
window.clearTimeout(timer);
};
}, [isContentReady, isLoading, isPinLocked, token, user]);
React.useEffect(() => {
if (!user || !token || isPinLocked || isLoading || !isContentReady) {
socketRef.current?.close();
socketRef.current = null;
setConnected(false);
@@ -121,7 +140,7 @@ export function RealtimeProvider({ children }: { children: React.ReactNode }) {
socketRef.current = null;
setConnected(false);
};
}, [emit, isApiReady, isLoading, isPinLocked, token, user]);
}, [emit, isContentReady, isLoading, isPinLocked, token, user]);
return (
<RealtimeContext.Provider value={{ unreadCount, setUnreadCount, subscribe, connected }}>