From 15132f56e88b5dd97f4fedf2c6bb6c8e552f6f1c Mon Sep 17 00:00:00 2001 From: Hermes Date: Tue, 11 Aug 2026 13:37:29 +0200 Subject: [PATCH] =?UTF-8?q?feat(client):=20PWA=20install=20=E2=80=94=20cli?= =?UTF-8?q?ent=20route,=20manifest=20start=5Furl=3D/client,=20SW=20app-she?= =?UTF-8?q?ll,=20invitation=20links=20=E2=86=92=20/client/:token?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/manifest.webmanifest | 4 +- public/service-worker.js | 41 ++++++++++++++----- src/router.jsx | 17 +++++++- .../functions/_shared/delivery-invitations.ts | 2 +- 4 files changed, 49 insertions(+), 15 deletions(-) diff --git a/public/manifest.webmanifest b/public/manifest.webmanifest index 82bc8a9..70070c9 100644 --- a/public/manifest.webmanifest +++ b/public/manifest.webmanifest @@ -1,8 +1,8 @@ { "name": "СуперСам Доставка", "short_name": "СуперСам", - "description": "Панель управления доставкой стройматериалов с уведомлениями.", - "start_url": "/delivery/showcase", + "description": "Согласование доставки заказов и уведомления для клиентов СуперСам.", + "start_url": "/client", "scope": "/", "display": "standalone", "background_color": "#f4f7f5", diff --git a/public/service-worker.js b/public/service-worker.js index 7f00b3f..b6e1db3 100644 --- a/public/service-worker.js +++ b/public/service-worker.js @@ -1,9 +1,9 @@ const isLocalhost = self.location.hostname === "localhost" || self.location.hostname === "127.0.0.1"; if (!isLocalhost) { - const STATIC_CACHE = "construction-delivery-static-v106"; - const RUNTIME_CACHE = "construction-delivery-runtime-v104"; - const APP_SHELL_URLS = ["/", "/index.html", "/manifest.webmanifest", "/icons/icon-192.png", "/icons/icon-512.png"]; + const STATIC_CACHE = "construction-delivery-static-v107"; + const RUNTIME_CACHE = "construction-delivery-runtime-v107"; + const APP_SHELL_URLS = ["/", "/client", "/index.html", "/manifest.webmanifest", "/icons/icon-192.png", "/icons/icon-512.png"]; self.addEventListener("install", (event) => { event.waitUntil( @@ -90,13 +90,27 @@ self.addEventListener("push", (event) => { data = { title: "Уведомление", body: "" }; } - const { title, body, url, order_group_id } = data; + const { title, body, url } = data; + const targetUrl = (() => { + if (!url) return "/client"; + try { + const parsed = new URL(url, self.location.origin); + if (parsed.origin !== self.location.origin) return "/client"; + const legacyMatch = parsed.pathname.match(/^\/delivery\/([^/]+)$/); + if (legacyMatch) return `/client/${legacyMatch[1]}${parsed.search}${parsed.hash}`; + return parsed.pathname.startsWith("/client") + ? `${parsed.pathname}${parsed.search}${parsed.hash}` + : "/client"; + } catch { + return "/client"; + } + })(); event.waitUntil( self.registration.showNotification(title || "Уведомление", { body: body || "", icon: "/icons/icon-512.png", - data: { url: url || "/dashboard" }, + data: { url: targetUrl }, }), ); }); @@ -105,14 +119,21 @@ self.addEventListener("push", (event) => { self.addEventListener("notificationclick", (event) => { event.notification.close(); - const targetUrl = (event.notification.data && event.notification.data.url) || "/dashboard"; + const targetPath = (event.notification.data && event.notification.data.url) || "/client"; + const targetUrl = new URL(targetPath, self.location.origin).href; event.waitUntil( - self.clients.matchAll({ type: "window", includeUncontrolled: true }).then((clientList) => { - for (const client of clientList) { - if ("focus" in client) { - return client.focus(); + self.clients.matchAll({ type: "window", includeUncontrolled: true }).then(async (clientList) => { + const sameOriginClient = clientList.find((client) => { + try { + return new URL(client.url).origin === self.location.origin; + } catch { + return false; } + }); + if (sameOriginClient) { + if ("navigate" in sameOriginClient) await sameOriginClient.navigate(targetUrl); + return "focus" in sameOriginClient ? sameOriginClient.focus() : undefined; } return self.clients.openWindow(targetUrl); }), diff --git a/src/router.jsx b/src/router.jsx index 1ad44e4..716f29d 100644 --- a/src/router.jsx +++ b/src/router.jsx @@ -1,5 +1,5 @@ import React from "react"; -import { Navigate, createBrowserRouter, Outlet } from "react-router-dom"; +import { Navigate, createBrowserRouter, useParams } from "react-router-dom"; import App from "./App"; import { ClientDeliveryPage } from "./pages/ClientDeliveryPage"; import { DashboardPage } from "./pages/DashboardPage"; @@ -44,9 +44,17 @@ export const router = createBrowserRouter([ element: , }, { - path: "delivery/:token", + path: "client", element: , }, + { + path: "client/:token", + element: , + }, + { + path: "delivery/:token", + element: , + }, { path: "forbidden", element: , @@ -82,3 +90,8 @@ export const router = createBrowserRouter([ ], }, ]); + +function LegacyDeliveryRedirect() { + const { token } = useParams(); + return ; +} diff --git a/supabase/functions/_shared/delivery-invitations.ts b/supabase/functions/_shared/delivery-invitations.ts index 9458ce3..7372297 100644 --- a/supabase/functions/_shared/delivery-invitations.ts +++ b/supabase/functions/_shared/delivery-invitations.ts @@ -197,7 +197,7 @@ export const resolvePublicAppUrl = ( }; export const buildInvitationUrl = (baseUrl: string, token: string) => - `${baseUrl.replace(/\/$/, "")}/delivery/${token}`; + `${baseUrl.replace(/\/$/, "")}/client/${token}`; export type DeliveryInvitationRecord = { id?: string;