diff --git a/driver-pwa/frontend/src/components/NotificationsRow.test.tsx b/driver-pwa/frontend/src/components/NotificationsRow.test.tsx
new file mode 100644
index 0000000..ab019f2
--- /dev/null
+++ b/driver-pwa/frontend/src/components/NotificationsRow.test.tsx
@@ -0,0 +1,40 @@
+import { describe, it, expect, vi, beforeEach } from "vitest";
+import { render, screen } from "@testing-library/react";
+import userEvent from "@testing-library/user-event";
+
+vi.mock("@/lib/push", () => ({
+ currentState: vi.fn(async () => "off"),
+ enable: vi.fn(async () => "on"),
+}));
+
+import { NotificationsRow } from "./NotificationsRow";
+import * as push from "@/lib/push";
+
+describe("строка уведомлений", () => {
+ beforeEach(() => {
+ vi.clearAllMocks();
+ vi.mocked(push.currentState).mockResolvedValue("off");
+ });
+
+ it("выключенные уведомления предлагает включить", async () => {
+ render();
+ await userEvent.click(await screen.findByRole("button", { name: /Включить уведомления/ }));
+ expect(push.enable).toHaveBeenCalled();
+ });
+
+ it("включённые — строки нет: отказаться от них мы не предлагаем", async () => {
+ // Отключение остаётся системным (настройки телефона или сайта в браузере) —
+ // рядом с балансом кнопка «выключить» только подталкивала бы к отказу.
+ vi.mocked(push.currentState).mockResolvedValue("on");
+ const { container } = render();
+ await vi.waitFor(() => expect(container.textContent).toBe(""));
+ expect(screen.queryByText(/Выключить/)).not.toBeInTheDocument();
+ });
+
+ it("запрещённые в браузере — объясняем, а не предлагаем кнопку", async () => {
+ vi.mocked(push.currentState).mockResolvedValue("denied");
+ render();
+ expect(await screen.findByText(/запрещены в настройках браузера/)).toBeInTheDocument();
+ expect(screen.queryByRole("button")).not.toBeInTheDocument();
+ });
+});
diff --git a/driver-pwa/frontend/src/components/NotificationsRow.tsx b/driver-pwa/frontend/src/components/NotificationsRow.tsx
index a9161a2..74fc0a6 100644
--- a/driver-pwa/frontend/src/components/NotificationsRow.tsx
+++ b/driver-pwa/frontend/src/components/NotificationsRow.tsx
@@ -1,7 +1,7 @@
import { useEffect, useState } from "react";
-import { Bell, BellOff } from "lucide-react";
+import { Bell } from "lucide-react";
import { toast } from "sonner";
-import { currentState, disable, enable, type PushState } from "@/lib/push";
+import { currentState, enable, type PushState } from "@/lib/push";
/**
* Включение уведомлений — одной строкой на главном экране.
@@ -9,6 +9,13 @@ import { currentState, disable, enable, type PushState } from "@/lib/push";
* Не всплывающим окном на входе: разрешение спрашивается один раз за установку,
* и потраченная впустую попытка закрывает канал навсегда. Здесь водитель жмёт
* сам, увидев, ради чего.
+ *
+ * Выключения здесь НЕТ намеренно (решение владельца 19.08.2026). Включённые
+ * уведомления — это ровно те два события, ради которых приложение и открывают;
+ * кнопка «выключить» рядом с балансом только предлагает от них отказаться.
+ * Способ отключить остаётся системный: настройки уведомлений телефона или
+ * настройки сайта в браузере — там же, где человек управляет всеми остальными
+ * приложениями.
*/
export function NotificationsRow() {
const [state, setState] = useState(null);
@@ -20,14 +27,15 @@ export function NotificationsRow() {
return () => { alive = false; };
}, []);
- if (state === null || state === "unsupported") return null;
+ // Включено — строки нет: предлагать нечего.
+ if (state === null || state === "unsupported" || state === "on") return null;
- async function toggle() {
+ async function turnOn() {
setBusy(true);
try {
- setState(state === "on" ? await disable() : await enable());
+ setState(await enable());
} catch {
- toast.error("Не удалось изменить настройку уведомлений");
+ toast.error("Не удалось включить уведомления");
} finally {
setBusy(false);
}
@@ -53,10 +61,9 @@ export function NotificationsRow() {
);
}