feat(client): PWA install — client route, manifest start_url=/client, SW app-shell, invitation links → /client/:token
This commit is contained in:
parent
65b42c8482
commit
15132f56e8
|
|
@ -1,8 +1,8 @@
|
||||||
{
|
{
|
||||||
"name": "СуперСам Доставка",
|
"name": "СуперСам Доставка",
|
||||||
"short_name": "СуперСам",
|
"short_name": "СуперСам",
|
||||||
"description": "Панель управления доставкой стройматериалов с уведомлениями.",
|
"description": "Согласование доставки заказов и уведомления для клиентов СуперСам.",
|
||||||
"start_url": "/delivery/showcase",
|
"start_url": "/client",
|
||||||
"scope": "/",
|
"scope": "/",
|
||||||
"display": "standalone",
|
"display": "standalone",
|
||||||
"background_color": "#f4f7f5",
|
"background_color": "#f4f7f5",
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
const isLocalhost = self.location.hostname === "localhost" || self.location.hostname === "127.0.0.1";
|
const isLocalhost = self.location.hostname === "localhost" || self.location.hostname === "127.0.0.1";
|
||||||
|
|
||||||
if (!isLocalhost) {
|
if (!isLocalhost) {
|
||||||
const STATIC_CACHE = "construction-delivery-static-v106";
|
const STATIC_CACHE = "construction-delivery-static-v107";
|
||||||
const RUNTIME_CACHE = "construction-delivery-runtime-v104";
|
const RUNTIME_CACHE = "construction-delivery-runtime-v107";
|
||||||
const APP_SHELL_URLS = ["/", "/index.html", "/manifest.webmanifest", "/icons/icon-192.png", "/icons/icon-512.png"];
|
const APP_SHELL_URLS = ["/", "/client", "/index.html", "/manifest.webmanifest", "/icons/icon-192.png", "/icons/icon-512.png"];
|
||||||
|
|
||||||
self.addEventListener("install", (event) => {
|
self.addEventListener("install", (event) => {
|
||||||
event.waitUntil(
|
event.waitUntil(
|
||||||
|
|
@ -90,13 +90,27 @@ self.addEventListener("push", (event) => {
|
||||||
data = { title: "Уведомление", body: "" };
|
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(
|
event.waitUntil(
|
||||||
self.registration.showNotification(title || "Уведомление", {
|
self.registration.showNotification(title || "Уведомление", {
|
||||||
body: body || "",
|
body: body || "",
|
||||||
icon: "/icons/icon-512.png",
|
icon: "/icons/icon-512.png",
|
||||||
data: { url: url || "/dashboard" },
|
data: { url: targetUrl },
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
@ -105,14 +119,21 @@ self.addEventListener("push", (event) => {
|
||||||
self.addEventListener("notificationclick", (event) => {
|
self.addEventListener("notificationclick", (event) => {
|
||||||
event.notification.close();
|
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(
|
event.waitUntil(
|
||||||
self.clients.matchAll({ type: "window", includeUncontrolled: true }).then((clientList) => {
|
self.clients.matchAll({ type: "window", includeUncontrolled: true }).then(async (clientList) => {
|
||||||
for (const client of clientList) {
|
const sameOriginClient = clientList.find((client) => {
|
||||||
if ("focus" in client) {
|
try {
|
||||||
return client.focus();
|
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);
|
return self.clients.openWindow(targetUrl);
|
||||||
}),
|
}),
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import React from "react";
|
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 App from "./App";
|
||||||
import { ClientDeliveryPage } from "./pages/ClientDeliveryPage";
|
import { ClientDeliveryPage } from "./pages/ClientDeliveryPage";
|
||||||
import { DashboardPage } from "./pages/DashboardPage";
|
import { DashboardPage } from "./pages/DashboardPage";
|
||||||
|
|
@ -44,9 +44,17 @@ export const router = createBrowserRouter([
|
||||||
element: <LoginPage />,
|
element: <LoginPage />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "delivery/:token",
|
path: "client",
|
||||||
element: <ClientDeliveryPage />,
|
element: <ClientDeliveryPage />,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: "client/:token",
|
||||||
|
element: <ClientDeliveryPage />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "delivery/:token",
|
||||||
|
element: <LegacyDeliveryRedirect />,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: "forbidden",
|
path: "forbidden",
|
||||||
element: <ForbiddenPage />,
|
element: <ForbiddenPage />,
|
||||||
|
|
@ -82,3 +90,8 @@ export const router = createBrowserRouter([
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
function LegacyDeliveryRedirect() {
|
||||||
|
const { token } = useParams();
|
||||||
|
return <Navigate to={`/client/${encodeURIComponent(token || "")}`} replace />;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -197,7 +197,7 @@ export const resolvePublicAppUrl = (
|
||||||
};
|
};
|
||||||
|
|
||||||
export const buildInvitationUrl = (baseUrl: string, token: string) =>
|
export const buildInvitationUrl = (baseUrl: string, token: string) =>
|
||||||
`${baseUrl.replace(/\/$/, "")}/delivery/${token}`;
|
`${baseUrl.replace(/\/$/, "")}/client/${token}`;
|
||||||
|
|
||||||
export type DeliveryInvitationRecord = {
|
export type DeliveryInvitationRecord = {
|
||||||
id?: string;
|
id?: string;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue