Добавлена Яндекс.Капча

This commit is contained in:
mi
2026-07-23 18:53:32 +03:00
parent 31efbf3b69
commit 8c3c454998
36 changed files with 836 additions and 35 deletions
@@ -18,10 +18,10 @@ npm run web
| Файл | Размер |
|---|---|
| `favicon.png` | 64×64 |
| `icon-192.png` | 360×360 |
| `icon-512.png` | 1024×1024 |
| `apple-touch-icon.png` | 360×360 |
| `favicon.png` | 32×32 |
| `icon-192.png` | 192×192 |
| `icon-512.png` | 512×512 |
| `apple-touch-icon.png` | 180×180 |
Manifest: `public/manifest.json`. Корневой HTML: `app/+html.tsx`.
@@ -34,7 +34,7 @@ Chrome DevTools → Application → Manifest / Service Workers. API и `/auth/`
## Production
`npm run build:pwa` создаёт `dist/` с manifest, иконками и `service-worker.js`. Каталог монтируется в корневой nginx; отдельный frontend nginx не используется. Для SPA nginx должен применять `try_files $uri /index.html`, не кэшировать `index.html`, `manifest.json`, `service-worker.js` и бессрочно кэшировать hashed assets.
`npm run build:pwa` создаёт `dist/` с manifest, иконками и `service-worker.js`. Каталог монтируется в корневой nginx; отдельный frontend nginx не используется. Для SPA nginx должен применять `try_files $uri /index.html`, не кэшировать `index.html`, `manifest.json`, `register-sw.js`, `service-worker.js` и бессрочно кэшировать hashed assets.
Dockerfile собирает статический OCI-артефакт `/dist` без runtime-сервера:
@@ -1,14 +1,6 @@
import { ScrollViewStyleReset } from "expo-router/html";
import type { PropsWithChildren } from "react";
const serviceWorkerBootstrap = `
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('/service-worker.js').catch(() => {});
});
}
`;
export default function Root({ children }: PropsWithChildren) {
return (
<html lang="ru">
@@ -19,9 +11,9 @@ export default function Root({ children }: PropsWithChildren) {
<meta name="theme-color" content="#030213" />
<meta name="description" content="Помощник по документам и жизни в России" />
<link rel="manifest" href="/manifest.json" />
<link rel="icon" type="image/png" sizes="64x64" href="/favicon.png" />
<link rel="apple-touch-icon" sizes="360x360" href="/apple-touch-icon.png" />
<script dangerouslySetInnerHTML={{ __html: serviceWorkerBootstrap }} />
<link rel="icon" type="image/png" sizes="32x32" href="/favicon.png" />
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
<script src="/register-sw.js" defer />
<ScrollViewStyleReset />
</head>
<body>{children}</body>
@@ -7,7 +7,7 @@ export default defineConfig({
reporter: "list",
use: { baseURL: "http://127.0.0.1:4173", trace: "on-first-retry" },
webServer: {
command: "npm run build && npx serve dist -l 4173",
command: "npm run build:pwa && npx serve dist -l 4173",
url: "http://127.0.0.1:4173",
reuseExistingServer: !process.env.CI,
timeout: 120_000,
@@ -1,23 +1,24 @@
{
"short_name": "HAN Chat",
"name": "HAN Chat",
"id": "/",
"description": "Помощник по документам и жизни в России",
"icons": [
{
"src": "/favicon.png",
"sizes": "64x64",
"sizes": "32x32",
"type": "image/png"
},
{
"src": "/icon-192.png",
"type": "image/png",
"sizes": "360x360",
"sizes": "192x192",
"purpose": "any maskable"
},
{
"src": "/icon-512.png",
"type": "image/png",
"sizes": "1024x1024",
"sizes": "512x512",
"purpose": "any maskable"
}
],
@@ -27,5 +28,7 @@
"orientation": "portrait",
"theme_color": "#030213",
"background_color": "#ffffff",
"lang": "ru"
"lang": "ru",
"dir": "ltr",
"categories": ["productivity", "utilities"]
}
@@ -0,0 +1,7 @@
if ("serviceWorker" in navigator) {
window.addEventListener("load", () => {
navigator.serviceWorker
.register("/service-worker.js", { updateViaCache: "none" })
.catch((error) => console.warn("Service worker registration failed:", error));
});
}
@@ -1,5 +1,23 @@
import { expect, test } from "@playwright/test";
test("PWA-манифест и Service Worker доступны", async ({ request }) => {
const manifestResponse = await request.get("/manifest.json");
expect(manifestResponse.ok()).toBe(true);
const manifest = await manifestResponse.json();
expect(manifest).toMatchObject({
name: "HAN Chat",
display: "standalone",
icons: expect.arrayContaining([
expect.objectContaining({ src: "/icon-192.png", sizes: "192x192" }),
expect.objectContaining({ src: "/icon-512.png", sizes: "512x512" }),
]),
});
const serviceWorkerResponse = await request.get("/service-worker.js");
expect(serviceWorkerResponse.ok()).toBe(true);
expect(await serviceWorkerResponse.text()).toContain("precacheAndRoute");
});
test.beforeEach(async ({ page }) => {
await page.route("**/api/v1/public/app-config", (route) => route.fulfill({
json: {
@@ -4,6 +4,7 @@ module.exports = {
globPatterns: ["**/*.{js,css,html,ico,png,json,woff2,svg,webp}"],
globIgnores: ["**/node_modules/**"],
swDest: "dist/service-worker.js",
cleanupOutdatedCaches: true,
navigateFallback: "/index.html",
navigateFallbackDenylist: [/^\/api/, /^\/auth/],
navigateFallbackDenylist: [/^\/api(?:\/|$)/, /^\/auth(?:\/|$)/],
};