This commit is contained in:
Tomáš Dvořák
2025-10-16 13:34:40 +02:00
parent 12cba639b9
commit f5e7be92c7
7 changed files with 317 additions and 201 deletions
+34 -201
View File
@@ -1,13 +1,14 @@
# Node modules # Dependencies
node_modules/ node_modules/
frontend/node_modules/ frontend/node_modules/
# Environment variables # Environment variables
.env .env
# Build directories # Build outputs
/frontend/build
/dist /dist
/frontend/.next
/frontend/out
# Executables # Executables
/main /main
@@ -21,11 +22,6 @@ frontend/node_modules/
# OS generated files # OS generated files
.DS_Store .DS_Store
.DS_Store?
._*
.Spotlight-V100
.Trashes
ehthumbs.db
Thumbs.db Thumbs.db
# Logs # Logs
@@ -35,212 +31,49 @@ npm-debug.log*
yarn-debug.log* yarn-debug.log*
yarn-error.log* yarn-error.log*
# Coverage directory used by tools like istanbul # Local development
coverage *.local
# Dependency directories
jspm_packages/
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env.local
.env.development.local
.env.test.local
.env.production.local
# Next.js
.next/
# Cache # Cache
.cache/ .cache/
.cache-loader/
# Debug logs from npm # Docker overrides
npm-debug.log* docker-compose.override.yml
# Local Netlify folder # Go workspace file
.netlify go.work
# Yarn # Frontend build files
.yarn/
.pnp.*
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.js
# IDE specific files
.idea/
.vscode/
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
# Docker
**/docker-compose.override.yml
# Local development
frontend/.env.local
frontend/.env.development.local
frontend/.env.test.local
frontend/.env.production.local
# Build output
frontend/.next/ frontend/.next/
frontend/out/ frontend/out/
# Debug logs # Uploads directory
npm-debug.log* /uploads/
yarn-debug.log*
yarn-error.log* # Database files
*.db
*.sqlite
# Local development environment files
.env.local
.env.development.local
.env.test.local
.env.production.local
# Debug files
*.debug
# Local development # Local development
.DS_Store .DS_Store
# Local env files
.env*.local
# Local history for editors and IDEs
.history/
# Output of 'npm pack'
*.tgz
# Optional REPL history # Optional REPL history
.node_repl_history .node_repl_history
# Output of 'npm pack' # Yarn
*.tgz .yarn/*
!.yarn/patches
# Yarn Integrity file !.yarn/plugins
.yarn-integrity !.yarn/releases
!.yarn/sdks
# dotenv environment variables file !.yarn/versions
.env.local .pnp.*
.env.development.local
.env.test.local
.env.production.local
# Next.js
.next/
out/
# Production
build
# Debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Local development
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
# IDE
.vscode/*
!.vscode/extensions.json
.idea
# Logs
logs
*.log
# Dependencies
node_modules/
# Build output
.next/
out/
# Local env files
.env*.local
# Debug logs
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Local development
.DS_Store
# Local env files
.env*.local
# Local history for editors and IDEs
.history/
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env.local
.env.development.local
.env.test.local
.env.production.local
# Next.js
.next/
out/
# Production
build
# Debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Local development
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
# IDE
.vscode/*
!.vscode/extensions.json
.idea
# Logs
logs
*.log
# Dependencies
node_modules/
# Build output
.next/
out/
# Local env files
.env*.local
# Debug logs
npm-debug.log*
yarn-debug.log*
yarn-error.log*
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

+25
View File
@@ -0,0 +1,25 @@
{
"short_name": "React App",
"name": "Create React App Sample",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
},
{
"src": "logo192.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "logo512.png",
"type": "image/png",
"sizes": "512x512"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}
+10
View File
@@ -0,0 +1,10 @@
# https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow: /admin/
Disallow: /api/
Disallow: /login
Disallow: /setup
Allow: /
# Sitemap will be served dynamically from backend
# Sitemap: /sitemap.xml
+248
View File
@@ -0,0 +1,248 @@
/* eslint-disable no-restricted-globals */
// Service Worker for PWA support and offline functionality
const CACHE_VERSION = 'v1.0.0';
const CACHE_NAME = `fotbal-club-cache-${CACHE_VERSION}`;
// Assets to cache on install
const STATIC_ASSETS = [
'/',
'/index.html',
'/static/css/main.css',
'/static/js/main.js',
'/manifest.json',
'/favicon.ico',
'/logo192.png',
'/logo512.png',
];
// API endpoints to cache
const API_CACHE_ENDPOINTS = [
'/api/v1/settings/public',
'/api/v1/seo',
];
// Install event - cache static assets
self.addEventListener('install', (event) => {
console.log('[SW] Installing service worker...');
event.waitUntil(
caches.open(CACHE_NAME).then((cache) => {
console.log('[SW] Caching static assets');
return cache.addAll(STATIC_ASSETS.map(url => new Request(url, { cache: 'reload' })));
}).catch((error) => {
console.error('[SW] Failed to cache static assets:', error);
})
);
// Activate immediately
self.skipWaiting();
});
// Activate event - clean up old caches
self.addEventListener('activate', (event) => {
console.log('[SW] Activating service worker...');
event.waitUntil(
caches.keys().then((cacheNames) => {
return Promise.all(
cacheNames
.filter((name) => name !== CACHE_NAME)
.map((name) => {
console.log('[SW] Deleting old cache:', name);
return caches.delete(name);
})
);
})
);
// Take control immediately
return self.clients.claim();
});
// Fetch event - serve from cache, fall back to network
self.addEventListener('fetch', (event) => {
const { request } = event;
const url = new URL(request.url);
// Skip non-GET requests
if (request.method !== 'GET') {
return;
}
// Skip Chrome extensions and non-http(s) requests
if (!url.protocol.startsWith('http')) {
return;
}
// Skip admin routes
if (url.pathname.startsWith('/admin')) {
return;
}
// Handle API requests
if (url.pathname.startsWith('/api/')) {
event.respondWith(handleAPIRequest(request));
return;
}
// Handle static assets and pages
event.respondWith(handleStaticRequest(request));
});
// Handle static requests - Cache First strategy
async function handleStaticRequest(request) {
try {
// Try cache first
const cachedResponse = await caches.match(request);
if (cachedResponse) {
// Return cached response and update in background
fetchAndUpdateCache(request);
return cachedResponse;
}
// Not in cache - fetch from network
const networkResponse = await fetch(request);
// Cache successful responses
if (networkResponse.ok) {
const cache = await caches.open(CACHE_NAME);
cache.put(request, networkResponse.clone());
}
return networkResponse;
} catch (error) {
console.error('[SW] Fetch failed:', error);
// Return offline page if available
const cachedOffline = await caches.match('/offline.html');
if (cachedOffline) {
return cachedOffline;
}
// Return basic offline response
return new Response('Offline - Please check your connection', {
status: 503,
statusText: 'Service Unavailable',
headers: { 'Content-Type': 'text/plain' },
});
}
}
// Handle API requests - Network First strategy with cache fallback
async function handleAPIRequest(request) {
try {
// Try network first for fresh data
const networkResponse = await fetch(request);
// Cache successful responses
if (networkResponse.ok) {
const cache = await caches.open(CACHE_NAME);
cache.put(request, networkResponse.clone());
}
return networkResponse;
} catch (error) {
console.log('[SW] Network failed, trying cache:', request.url);
// Fall back to cache
const cachedResponse = await caches.match(request);
if (cachedResponse) {
return cachedResponse;
}
// Return error response
return new Response(
JSON.stringify({ error: 'Offline - cached data not available' }),
{
status: 503,
statusText: 'Service Unavailable',
headers: { 'Content-Type': 'application/json' },
}
);
}
}
// Update cache in background
async function fetchAndUpdateCache(request) {
try {
const response = await fetch(request);
if (response.ok) {
const cache = await caches.open(CACHE_NAME);
cache.put(request, response);
}
} catch (error) {
// Silent fail - we already returned cached version
}
}
// Handle background sync for offline actions
self.addEventListener('sync', (event) => {
console.log('[SW] Background sync:', event.tag);
if (event.tag === 'sync-data') {
event.waitUntil(syncOfflineData());
}
});
async function syncOfflineData() {
// Implement offline data sync logic here
// For example, sync form submissions, votes, etc.
console.log('[SW] Syncing offline data...');
}
// Handle push notifications
self.addEventListener('push', (event) => {
const data = event.data ? event.data.json() : {};
const title = data.title || 'Fotbal Club';
const options = {
body: data.body || 'Nová notifikace',
icon: '/logo192.png',
badge: '/logo192.png',
data: data.url || '/',
};
event.waitUntil(
self.registration.showNotification(title, options)
);
});
// Handle notification clicks
self.addEventListener('notificationclick', (event) => {
event.notification.close();
const urlToOpen = event.notification.data || '/';
event.waitUntil(
clients.matchAll({ type: 'window', includeUncontrolled: true }).then((clientList) => {
// Check if there's already a window open
for (const client of clientList) {
if (client.url === urlToOpen && 'focus' in client) {
return client.focus();
}
}
// Open new window
if (clients.openWindow) {
return clients.openWindow(urlToOpen);
}
})
);
});
// Message handler for manual cache updates
self.addEventListener('message', (event) => {
if (event.data && event.data.type === 'SKIP_WAITING') {
self.skipWaiting();
}
if (event.data && event.data.type === 'CLEAR_CACHE') {
event.waitUntil(
caches.delete(CACHE_NAME).then(() => {
return caches.open(CACHE_NAME);
})
);
}
});
console.log('[SW] Service Worker loaded successfully');