Frontend:
- Установлен Sonner для toast уведомлений
- Создан Service Worker (public/sw.js) для Web Push
- Реализован хук useWebPush для управления подписками
- Добавлены утилиты Web Push (lib/push.ts)
- Создан компонент NotificationCenter с UI
- Интегрирован в AppShell (header desktop + mobile)
- Добавлен Toaster в Providers
API методы:
- getVapidPublicKey() - получение VAPID ключа
- subscribePush() - создание подписки
- unsubscribePush() - удаление подписки
- getPushSubscriptions() - список подписок
Возможности:
- � Web Push (работают даже когда вкладка закрыта)
- � NotificationCenter с badge и историей
- � Toast уведомления (Sonner)
- ⚙️ Настройки push подписок
- � Респонсивный дизайн
Документация: frontend/NOTIFICATIONS_IMPLEMENTATION.md
19 KiB
Реализация системы уведомлений на фронтенде
Дата: 2026-02-27
Технологии: Next.js 16.1.6, React 19, TypeScript 5, Sonner
Статус: ✅ Завершено
Обзор
Реализована полноценная система уведомлений для фронтенда с поддержкой:
- 📱 Web Push - нативные push уведомления (работают даже когда вкладка закрыта)
- 🔔 Toast уведомления - всплывающие уведомления в интерфейсе (Sonner)
- 📋 Notification Center - центр уведомлений с badge и историей
- ⚙️ Настройки - управление подписками на push уведомления
Установленные библиотеки
{
"sonner": "^1.x" // Toast уведомления для Next.js
}
Почему Sonner?
- ✅ Создан специально для Next.js/React
- ✅ Легковесный (2.5KB gzipped)
- ✅ Красивый дизайн из коробки
- ✅ Поддержка promise/loading состояний
- ✅ Используется в shadcn/ui
Структура файлов
frontend/
├── public/
│ └── sw.js # Service Worker для Web Push
├── src/
│ ├── components/
│ │ ├── NotificationCenter.tsx # UI компонент центра уведомлений
│ │ ├── Providers.tsx # Добавлен Toaster от Sonner
│ │ └── AppShell.tsx # Интегрирован NotificationCenter в header
│ ├── hooks/
│ │ ├── useWebPush.ts # React хук для Web Push
│ │ └── useApi.ts # Уже был (useNotifications)
│ └── lib/
│ ├── push.ts # Утилиты для Web Push
│ └── api.ts # Добавлены методы push подписок
Компоненты и функции
1. Service Worker (public/sw.js)
Назначение: Обрабатывает входящие push события от сервера даже когда вкладка закрыта.
Функции:
pushevent - получение и показ нативных уведомленийnotificationclickevent - открытие страницы при кликеnotificationcloseevent - отправка статистики (опционально)
Формат данных от сервера:
{
"title": "Новая заявка",
"body": "Заявка #123 требует вашего внимания",
"icon": "/logo.png",
"tag": "notification-123",
"data": {
"url": "/requests/123",
"notification_id": 123,
"category": "requests"
},
"requireInteraction": false
}
Регистрация: Автоматическая при вызове useWebPush().
2. Web Push хук (hooks/useWebPush.ts)
Назначение: React хук для управления Web Push подписками.
Использование:
import { useWebPush } from '@/hooks/useWebPush';
function MyComponent() {
const {
isSupported, // Поддерживается ли Web Push
isSubscribed, // Есть ли активная подписка
permission, // 'default' | 'granted' | 'denied'
subscribe, // Подписаться
unsubscribe, // Отписаться
isLoading // Загрузка
} = useWebPush();
return (
<button
onClick={subscribe}
disabled={!isSupported || isLoading}
>
{isSubscribed ? 'Отключить' : 'Включить'} уведомления
</button>
);
}
API методы:
subscribe()- запрашивает разрешение и создает подпискуunsubscribe()- удаляет подпискуrequestPermission()- явно запрашивает разрешение
Внутренняя логика:
- Проверяет поддержку браузером (
isPushSupported()) - Получает VAPID ключ с сервера
- Регистрирует Service Worker
- Создает push подписку (
PushManager.subscribe()) - Отправляет данные подписки на сервер
3. NotificationCenter (components/NotificationCenter.tsx)
Назначение: UI компонент центра уведомлений с иконкой bell.
Возможности:
- 🔔 Badge с количеством непрочитанных
- 📋 Список последних 20 уведомлений
- ✅ Отметка как прочитано
- ⚙️ Настройки push подписок
- 📱 Респонсивный дизайн (desktop + mobile)
Структура:
<NotificationCenter>
<button> {/* Bell иконка с badge */} </button>
<dropdown>
<header> {/* Заголовок + кнопки */} </header>
<settings> {/* Включить/выключить push */} </settings>
<list> {/* Список уведомлений */} </list>
<footer> {/* Ссылка на полный список */} </footer>
</dropdown>
</NotificationCenter>
Интеграция:
import { NotificationCenter } from '@/components/NotificationCenter';
<header>
<NotificationCenter />
</header>
4. Toast уведомления (Sonner)
Назначение: Всплывающие уведомления в интерфейсе.
Провайдер: Уже добавлен в Providers.tsx:
import { Toaster } from 'sonner';
<Toaster position="top-right" richColors />
Использование:
import { toast } from 'sonner';
// Простое уведомление
toast('Изменения сохранены');
// С типом
toast.success('Подписка активирована');
toast.error('Не удалось сохранить');
toast.warning('Осталось 5 минут');
toast.info('Новое обновление доступно');
// С действиями
toast('Файл загружен', {
action: {
label: 'Открыть',
onClick: () => window.open('/files/123')
}
});
// Promise (автоматический loading → success/error)
toast.promise(
apiClient.saveData(),
{
loading: 'Сохранение...',
success: 'Сохранено!',
error: 'Ошибка сохранения'
}
);
Преимущества:
- Автоматическое управление очередью
- Swipe to dismiss (свайп для закрытия)
- Поддержка action buttons
- Красивые анимации
5. API методы (lib/api.ts)
Добавлены методы:
// Получить VAPID ключ для подписки
async getVapidPublicKey(): Promise<{ vapid_public_key: string }>
// Создать push подписку
async subscribePush(data: {
endpoint: string;
keys: { p256dh: string; auth: string };
device_name?: string;
}): Promise<{ status: string; message: string; created: boolean }>
// Удалить push подписку
async unsubscribePush(endpoint?: string): Promise<{ status: string }>
// Получить список подписок
async getPushSubscriptions(): Promise<{ subscriptions: any[] }>
Уже существующие:
// Получить список уведомлений
async getNotifications(): Promise<any>
// Отметить уведомление как прочитанное
async markNotificationAsRead(id: number): Promise<void>
// Отметить все как прочитанные
async markAllNotificationsAsRead(): Promise<void>
Workflow (Как это работает)
1. Инициализация при загрузке страницы
User opens page
↓
useWebPush hook initialized
↓
Check browser support (Service Worker + Push API + Notification API)
↓
✅ Supported → Continue
↓
Fetch VAPID public key from server
↓
Register Service Worker (/sw.js)
↓
Check current subscription (PushManager.getSubscription())
↓
isSubscribed = (subscription !== null)
2. Подписка на уведомления (User action)
User clicks "Включить уведомления"
↓
Check permission:
- 'default' → Request permission (Notification.requestPermission())
- 'denied' → Show error toast
- 'granted' → Continue
↓
Create push subscription (PushManager.subscribe())
- userVisibleOnly: true
- applicationServerKey: VAPID key
↓
Serialize subscription (endpoint + keys.p256dh + keys.auth)
↓
Send to backend (POST /api/v1/notifications/push/subscribe/)
↓
Backend saves to WebPushDevice table
↓
✅ Show success toast
3. Получение push уведомления
Backend sends push notification (django-push-notifications)
↓
Browser receives push event (даже если вкладка закрыта)
↓
Service Worker intercepts 'push' event
↓
Parse payload (JSON.parse(event.data.text()))
↓
Show native notification (registration.showNotification())
↓
User clicks notification
↓
Service Worker 'notificationclick' event
↓
Focus or open window with notification URL
4. Обновление списка уведомлений
NotificationCenter component mounts
↓
useNotifications() hook called
↓
Fetch notifications from API (GET /api/v1/notifications/)
↓
Display in dropdown with unread badge
↓
User clicks notification
↓
Mark as read (POST /api/v1/notifications/{id}/mark_read/)
↓
Navigate to action_url
Интеграция в существующие компоненты
AppShell.tsx
Изменения:
- Импортирован
NotificationCenter - Заменены статичные кнопки Bell на
<NotificationCenter /> - Добавлено в desktop версию (header)
- Добавлено в mobile версию (нижняя панель)
До:
<button className="...">
<Bell size={18} />
</button>
После:
<NotificationCenter />
Providers.tsx
Изменения:
- Импортирован
Toasterиз Sonner - Добавлен
<Toaster />в провайдеры
import { Toaster } from 'sonner';
<Toaster position="top-right" richColors />
Тестирование
Тестовый сценарий 1: Подписка на уведомления
- Открыть приложение в браузере
- Кликнуть на иконку Bell в header
- Кликнуть на "Настройки" (⚙️)
- Кликнуть "Включить" в разделе Push уведомления
- Браузер покажет запрос разрешения → Разрешить
- Toast: "Подписка на уведомления активирована"
- В настройках должно быть "Включены" и кнопка "Отключить"
Тестовый сценарий 2: Получение push уведомления
Из Django shell:
from notifications.services import NotificationService
from notifications.models import Notification
from django.contrib.auth import get_user_model
User = get_user_model()
user = User.objects.get(username='testuser')
notification = Notification.objects.filter(recipient=user).first()
# Отправить push
count = NotificationService.send_web_push_notification(notification)
print(f"Отправлено: {count}")
Проверки:
- Нативное уведомление должно появиться (даже если вкладка закрыта)
- При клике должна открыться нужная страница
- В NotificationCenter должно появиться уведомление
- Badge должен увеличиться
Тестовый сценарий 3: Toast уведомления
В любом компоненте:
import { toast } from 'sonner';
<button onClick={() => toast.success('Тестовое уведомление')}>
Показать toast
</button>
Проверки:
- Toast появляется в правом верхнем углу
- Автоматически исчезает через 4 секунды
- Можно закрыть свайпом вправо
- При наведении пауза на таймере
Тестовый сценарий 4: NotificationCenter UI
- Открыть NotificationCenter
- Должен показаться список уведомлений
- Непрочитанные - с синим фоном и точкой
- При клике на уведомление:
- Отмечается как прочитанное
- Badge уменьшается
- Происходит навигация (если есть URL)
- "Прочитать все" - все уведомления становятся прочитанными
Браузерная совместимость
Web Push API
| Браузер | Desktop | Mobile | Примечания |
|---|---|---|---|
| Chrome | ✅ 50+ | ✅ 50+ | Полная поддержка |
| Firefox | ✅ 44+ | ✅ 48+ | Полная поддержка |
| Safari | ✅ 16+ | ❌ | iOS не поддерживает |
| Edge | ✅ 79+ | ✅ | На базе Chromium |
| Opera | ✅ 37+ | ✅ | На базе Chromium |
Важно: iOS Safari НЕ поддерживает Web Push (даже в iOS 16+).
- Для iOS нужно использовать нативное приложение + APNS
Fallback стратегия
const { isSupported, isSubscribed } = useWebPush();
if (!isSupported) {
// Показываем альтернативу
return <div>Push уведомления не поддерживаются вашим браузером</div>;
}
Production checklist
- HTTPS обязателен - Service Worker требует HTTPS
- VAPID ключи - проверить что есть в production settings
- Service Worker scope - проверить что sw.js доступен в корне
- Тестирование на реальных устройствах
- Desktop Chrome/Firefox
- Android Chrome
- Desktop Safari (если поддержка добавится)
- Мониторинг ошибок - логировать ошибки Service Worker
- Rate limiting - защита от спама push уведомлений
Конфигурация
Environment variables (Backend)
# В .env или settings.py
VAPID_PUBLIC_KEY=BMTitZy9r4ygYJBgGdaZuCkb7rwR7iHLJv...
VAPID_PRIVATE_KEY=<private_key>
VAPID_ADMIN_EMAIL=admin@example.com
Next.js конфигурация
next.config.ts - добавить headers для Service Worker (если нужно):
async headers() {
return [
{
source: '/sw.js',
headers: [
{
key: 'Service-Worker-Allowed',
value: '/'
}
]
}
];
}
Troubleshooting
Проблема: Service Worker не регистрируется
Возможные причины:
- Приложение запущено на HTTP (нужен HTTPS или localhost)
- sw.js недоступен (404)
- Браузер не поддерживает Service Worker
Решение:
# Проверить доступность sw.js
curl http://localhost:3000/sw.js
# В консоли браузера
navigator.serviceWorker.getRegistrations().then(console.log)
Проблема: Push подписка не создается
Возможные причины:
- Разрешение denied
- VAPID ключ неверный
- PushManager не поддерживается
Решение:
// В консоли браузера
console.log('Permission:', Notification.permission);
console.log('PushManager supported:', 'PushManager' in window);
// Проверить VAPID ключ
fetch('/api/v1/notifications/push/vapid-key/')
.then(r => r.json())
.then(console.log);
Проблема: Уведомления не приходят
Возможные причины:
- Подписка истекла (endpoint недействителен)
- Backend не может отправить (VAPID ошибка)
- Service Worker не активен
Решение:
# Django shell - проверить активные устройства
from push_notifications.models import WebPushDevice
devices = WebPushDevice.objects.filter(user=user, active=True)
print(devices.count())
# Отправить тестовое уведомление
devices.send_message('{"title": "Test", "body": "Hello"}')
Дальнейшие улучшения
Функциональность
- Фильтры уведомлений - по категориям (заявки, сообщения, календарь)
- Группировка - по дате или типу
- Действия в уведомлении - прямо из push (approve/reject)
- Звук - настраиваемый звук для разных типов
- Badge на иконке приложения - счетчик непрочитанных
UX/UI
- Skeleton loading - вместо "Загрузка..."
- Infinite scroll - для списка уведомлений
- Поиск - по уведомлениям
- Настройки по типам - включить/выключить для каждого типа
- Темная тема - поддержка dark mode
Производительность
- WebSocket real-time - мгновенное обновление списка
- Service Worker cache - кеширование данных уведомлений
- Prefetch - предзагрузка страниц из action_url
Аналитика
- Отслеживание открытий - сколько push уведомлений открыто
- Conversion tracking - какие уведомления ведут к действиям
- A/B тестирование - разные тексты/форматы
Заключение
✅ Система уведомлений полностью реализована:
Backend:
- django-push-notifications 3.1.0
- WebPushDevice модель
- VAPID ключи настроены
- API endpoints готовы
Frontend:
- Service Worker для Web Push
- NotificationCenter UI компонент
- useWebPush React хук
- Sonner для toast уведомлений
- Интеграция в AppShell
Результат:
- Нативные push уведомления (работают даже когда вкладка закрыта)
- Красивый UI с badge и историей
- Настройки подписок
- Респонсивный дизайн
- TypeScript типизация
Готово к использованию! 🎉