From 5e30761742308cc4c26741ced1f384a3e53ccc88 Mon Sep 17 00:00:00 2001 From: vantablackTea Date: Mon, 28 Sep 2026 12:51:08 +0300 Subject: [PATCH] =?UTF-8?q?feat:=20web=20push=20=E2=80=94=20VAPID=20build-?= =?UTF-8?q?arg=20+=20enable-notifications=20button?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 2 + deploy/docker-compose.yml | 2 + web/src/components/Sidebar.tsx | 61 ++++++++++++++++++++++- web/src/components/icons.tsx | 14 ++++++ web/src/lib/push.ts | 88 ++++++++++++++++++++++++++-------- 5 files changed, 144 insertions(+), 23 deletions(-) diff --git a/Dockerfile b/Dockerfile index 457bbf6..7c4efed 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,8 @@ # --- Frontend build stage --- FROM node:22-alpine AS web WORKDIR /build/web +ARG VITE_VAPID_PUBLIC_KEY +ENV VITE_VAPID_PUBLIC_KEY=$VITE_VAPID_PUBLIC_KEY COPY web/package.json web/package-lock.json ./ RUN npm ci COPY web/ . diff --git a/deploy/docker-compose.yml b/deploy/docker-compose.yml index 3a470b7..da18c1f 100644 --- a/deploy/docker-compose.yml +++ b/deploy/docker-compose.yml @@ -3,6 +3,8 @@ services: build: context: .. dockerfile: Dockerfile + args: + VITE_VAPID_PUBLIC_KEY: ${MESSENGER_VAPID_PUBLIC_KEY:-} image: messenger/api:latest restart: unless-stopped environment: diff --git a/web/src/components/Sidebar.tsx b/web/src/components/Sidebar.tsx index 868880d..a170435 100644 --- a/web/src/components/Sidebar.tsx +++ b/web/src/components/Sidebar.tsx @@ -1,7 +1,8 @@ -import { useMemo, useState } from 'react' +import { useCallback, useMemo, useState } from 'react' import Avatar, { GroupAvatar } from './Avatar' -import { IconLogout, IconPlus, IconSearch } from './icons' +import { IconBell, IconCheck, IconLogout, IconPlus, IconSearch } from './icons' import { formatListTime, peerForConversation, userTitle } from '../lib/format' +import { isIosInstallRequired, isPushSupported, notificationPermission, subscribeToPush } from '../lib/push' import type { Conversation, User } from '../lib/types' interface SidebarProps { @@ -53,6 +54,17 @@ export default function Sidebar({ onLogout, }: SidebarProps) { const [query, setQuery] = useState('') + const [pushSupported] = useState(() => isPushSupported()) + const [iosInstallRequired] = useState(() => isIosInstallRequired()) + const [permission, setPermission] = useState(() => notificationPermission()) + const [subscribing, setSubscribing] = useState(false) + + const handleEnablePush = useCallback(async () => { + setSubscribing(true) + const granted = await subscribeToPush() + setSubscribing(false) + setPermission(granted ? 'granted' : notificationPermission()) + }, []) const items = useMemo(() => { const term = query.trim().toLowerCase() @@ -233,6 +245,51 @@ export default function Sidebar({ + {pushSupported && ( +
+ {permission === 'granted' ? ( +
+ + + + + + Уведомления включены + +
+ ) : permission === 'denied' ? ( +
+ + + + + Уведомления заблокированы + + Разрешите их в настройках браузера + + +
+ ) : ( + + )} + {iosInstallRequired && permission !== 'granted' && ( +

+ На iOS уведомления работают только после добавления на экран «Домой». +

+ )} +
+ )} +
diff --git a/web/src/components/icons.tsx b/web/src/components/icons.tsx index 44766c0..5fab1c1 100644 --- a/web/src/components/icons.tsx +++ b/web/src/components/icons.tsx @@ -157,6 +157,20 @@ export function IconTrash({ className }: IconProps) { ) } +export function IconBell({ className }: IconProps) { + return ( + + ) +} + export function IconSmile({ className }: IconProps) { return (