From 23cc809e062aec06cf4e4393b6912cc6793b2dcd Mon Sep 17 00:00:00 2001 From: lizerk Date: Sat, 11 Apr 2026 20:55:18 +0300 Subject: [PATCH] fix(frontend): resolve merge regressions --- frontend/src/app/messages/page.tsx | 8 + .../src/app/notifications/settings/page.tsx | 446 +----------------- 2 files changed, 9 insertions(+), 445 deletions(-) diff --git a/frontend/src/app/messages/page.tsx b/frontend/src/app/messages/page.tsx index 1fe4f13e..476d0010 100644 --- a/frontend/src/app/messages/page.tsx +++ b/frontend/src/app/messages/page.tsx @@ -23,6 +23,14 @@ type CreateChatPayload = { avatar?: File; }; +type RealtimeChatEvent = { + type: "notification" | "list_update" | "message_edited" | string; + chat_id?: number; + notification?: { + verb?: string; + } | null; +}; + const chatTypeOptions = [ { key: "all", label: "Все" }, { key: "global", label: "Глобальный" }, diff --git a/frontend/src/app/notifications/settings/page.tsx b/frontend/src/app/notifications/settings/page.tsx index 06f02014..79917ae5 100644 --- a/frontend/src/app/notifications/settings/page.tsx +++ b/frontend/src/app/notifications/settings/page.tsx @@ -1,449 +1,5 @@ import { redirect } from "next/navigation"; export default function NotificationSettingsPage() { - const [preferences, setPreferences] = useState({ - web_enabled: true, - email_enabled: false, - email_frequency: 'instant', - push_enabled: false, - dnd_enabled: false, - dnd_start_time: null, - dnd_end_time: null, - disabled_verbs: [], - }); - - const [verbTypes, setVerbTypes] = useState([]); - const [loading, setLoading] = useState(true); - const [preferencesLoaded, setPreferencesLoaded] = useState(false); - const [pushSynced, setPushSynced] = useState(false); - const saveTimeoutRef = useRef(null); - - const { isSupported, isSubscribed, subscribe, unsubscribe, permission, isLoading: pushLoading } = useWebPush(); - - useEffect(() => { - loadPreferences(); - loadVerbTypes(); - }, []); - - // Синхронизация с реальной браузерной подпиской при первой загрузке - useEffect(() => { - console.log('[Settings] Sync check:', { - isSupported, - preferencesLoaded, - loading, - pushLoading, - pushSynced, - 'preferences.push_enabled': preferences.push_enabled, - isSubscribed, - }); - - // Если браузер не поддерживает Push, то синхронизация не нужна - if (!isSupported) { - setPushSynced(true); - return; - } - - // Ждем пока загрузятся и preferences и push состояние - // Синхронизируем только один раз - if (preferencesLoaded && !loading && !pushLoading && !pushSynced) { - if (preferences.push_enabled !== isSubscribed) { - console.log(`[Settings] ⚠️ Syncing push state: DB=${preferences.push_enabled}, Browser=${isSubscribed}`); - // Приоритет у браузерной подписки (это источник правды) - setPreferences(prev => ({ ...prev, push_enabled: isSubscribed })); - } else { - console.log('[Settings] ✅ Push state already in sync'); - } - setPushSynced(true); - } - }, [preferencesLoaded, loading, pushLoading, isSupported, pushSynced, isSubscribed, preferences.push_enabled]); - - // Автосохранение при изменении настроек (debounce 1 секунда) - useEffect(() => { - // Пропускаем автосохранение до первой загрузки - if (!preferencesLoaded) { - return; - } - - // Очищаем предыдущий таймер - if (saveTimeoutRef.current) { - clearTimeout(saveTimeoutRef.current); - } - - // Устанавливаем новый таймер для автосохранения - saveTimeoutRef.current = setTimeout(async () => { - console.log('[Settings] Auto-saving preferences...'); - try { - const payload = { - ...preferences, - dnd_start_time: preferences.dnd_start_time || undefined, - dnd_end_time: preferences.dnd_end_time || undefined, - }; - await apiClient.updateNotificationPreferences(payload); - console.log('[Settings] ✅ Auto-saved successfully'); - } catch (error) { - console.error('[Settings] ❌ Auto-save failed:', error); - } - }, 1000); // 1 секунда после последнего изменения - - // Cleanup - return () => { - if (saveTimeoutRef.current) { - clearTimeout(saveTimeoutRef.current); - } - }; - }, [preferences, preferencesLoaded]); - - const loadPreferences = async () => { - try { - setLoading(true); - const data = await apiClient.getNotificationPreferences(); - console.log('[Settings] Preferences loaded from API:', data); - setPreferences(data); - setPreferencesLoaded(true); - } catch (error) { - console.error('Failed to load preferences:', error); - } finally { - setLoading(false); - } - }; - - const loadVerbTypes = async () => { - try { - const data = await apiClient.getVerbTypes(); - setVerbTypes(data.verb_types || []); - } catch (error) { - console.error('Failed to load verb types:', error); - } - }; - - const toggleVerbDisabled = (verb: string) => { - setPreferences((prev) => ({ - ...prev, - disabled_verbs: prev.disabled_verbs.includes(verb) - ? prev.disabled_verbs.filter((v) => v !== verb) - : [...prev.disabled_verbs, verb], - })); - }; - - const handlePushToggle = async (enabled: boolean) => { - // Обновляем состояние - setPreferences({ ...preferences, push_enabled: enabled }); - - // Синхронизируем браузерную подписку - if (enabled && !isSubscribed) { - await subscribe(); - } else if (!enabled && isSubscribed) { - await unsubscribe(); - } - }; - - const handleDndToggle = (enabled: boolean) => { - if (enabled) { - // При включении DND устанавливаем дефолтное время (весь день) - setPreferences({ - ...preferences, - dnd_enabled: true, - dnd_start_time: preferences.dnd_start_time || '00:00', - dnd_end_time: preferences.dnd_end_time || '23:59', - }); - } else { - // При выключении просто отключаем, время оставляем - setPreferences({ ...preferences, dnd_enabled: false }); - } - }; - - const toggleTrackClass = "peer h-6 w-11 rounded-full bg-[var(--surface-tertiary)] after:absolute after:left-[2px] after:top-[2px] after:h-5 after:w-5 after:rounded-full after:border after:border-[var(--border-subtle)] after:bg-[var(--surface-primary)] after:transition-all after:content-[''] peer-checked:bg-[var(--accent-primary)] peer-checked:after:translate-x-full peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-[color:var(--accent-soft)] peer-disabled:cursor-not-allowed peer-disabled:opacity-50"; - - if (loading) { - return ( - -
-
-
-

Загрузка настроек...

-
-
-
- ); - } - - return ( - -
- {/* Заголовок */} -
- - - Вернуться к уведомлениям - -

Настройки уведомлений

-

- Управляйте способами получения уведомлений · Изменения сохраняются автоматически -

-
- -
- {/* Каналы доставки */} -
-

Каналы доставки

- -
- {/* Web уведомления */} -
-
- -
-

Web уведомления

-

- Уведомления в браузере в реальном времени -

-
-
- -
- - {/* Email уведомления */} -
-
-
- -
-

Email уведомления

-

- Получать уведомления на электронную почту -

-
-
- -
- - {preferences.email_enabled && ( -
- - -
- )} -
- - {/* Push уведомления */} -
-
-
- -
-

Push уведомления

-

- Системные уведомления на устройстве -

-
-
- -
- - {!isSupported && ( -
- -

- Ваш браузер не поддерживает Push уведомления -

-
- )} - - {isSupported && permission === 'denied' && ( -
- -

- Вы запретили уведомления. Измените настройки браузера. -

-
- )} - - {isSupported && permission === 'granted' && preferences.push_enabled && ( -
- -

- Push уведомления активны {isSubscribed && '(подписка активна)'} -

-
- )} -
-
-
- - {/* Режим "Не беспокоить" */} -
-
- -

Не беспокоить

-
- -
-
-
-

Включить режим DND

-

- В указанный период Email и Push будут отключены, Web уведомления — без звука -

-
- -
- - {preferences.dnd_enabled && ( - <> -
-
- - - setPreferences({ ...preferences, dnd_start_time: e.target.value }) - } - required - className="app-input w-full" - /> -
- -
- - - setPreferences({ ...preferences, dnd_end_time: e.target.value }) - } - required - className="app-input w-full" - /> -
-
- -
- -
-

Режим активен

-

- С {preferences.dnd_start_time || '00:00'} до {preferences.dnd_end_time || '23:59'} будут приходить только Web уведомления (без звука). - Email и Push отключены. - {(preferences.dnd_start_time || '00:00') > (preferences.dnd_end_time || '23:59') && ( - - Период через полночь: с {preferences.dnd_start_time} до {preferences.dnd_end_time} следующего дня. - - )} -

-
-
- - )} -
-
- - {/* Типы уведомлений */} - {verbTypes.length > 0 && ( -
-

Типы уведомлений

-

- Выберите, какие типы уведомлений вы хотите получать -

- -
- {verbTypes.map((verbType) => ( - - ))} -
-
- )} - - {/* Навигация */} -
- - - Вернуться к уведомлениям - -
-
-
-
- ); + redirect("/settings?section=notifications"); }