diff --git a/.gitignore b/.gitignore index 33df847..e30cd29 100644 --- a/.gitignore +++ b/.gitignore @@ -1,13 +1,14 @@ -# Node modules +# Dependencies node_modules/ frontend/node_modules/ # Environment variables .env -# Build directories -/frontend/build +# Build outputs /dist +/frontend/.next +/frontend/out # Executables /main @@ -21,11 +22,6 @@ frontend/node_modules/ # OS generated files .DS_Store -.DS_Store? -._* -.Spotlight-V100 -.Trashes -ehthumbs.db Thumbs.db # Logs @@ -35,212 +31,49 @@ npm-debug.log* yarn-debug.log* yarn-error.log* -# Coverage directory used by tools like istanbul -coverage - -# 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/ +# Local development +*.local # Cache .cache/ -.cache-loader/ -# Debug logs from npm -npm-debug.log* +# Docker overrides +docker-compose.override.yml -# Local Netlify folder -.netlify +# Go workspace file +go.work -# Yarn -.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 build files frontend/.next/ frontend/out/ -# Debug logs -npm-debug.log* -yarn-debug.log* -yarn-error.log* +# Uploads directory +/uploads/ + +# Database files +*.db +*.sqlite + +# Local development environment files +.env.local +.env.development.local +.env.test.local +.env.production.local + +# Debug files +*.debug # Local development .DS_Store -# Local env files -.env*.local - -# Local history for editors and IDEs -.history/ - -# Output of 'npm pack' -*.tgz - # 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/ -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* +# Yarn +.yarn/* +!.yarn/patches +!.yarn/plugins +!.yarn/releases +!.yarn/sdks +!.yarn/versions +.pnp.* diff --git a/frontend/build/favicon.ico b/frontend/build/favicon.ico new file mode 100644 index 0000000..a11777c Binary files /dev/null and b/frontend/build/favicon.ico differ diff --git a/frontend/build/logo192.png b/frontend/build/logo192.png new file mode 100644 index 0000000..fc44b0a Binary files /dev/null and b/frontend/build/logo192.png differ diff --git a/frontend/build/logo512.png b/frontend/build/logo512.png new file mode 100644 index 0000000..a4e47a6 Binary files /dev/null and b/frontend/build/logo512.png differ diff --git a/frontend/build/manifest.json b/frontend/build/manifest.json new file mode 100644 index 0000000..080d6c7 --- /dev/null +++ b/frontend/build/manifest.json @@ -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" +} diff --git a/frontend/build/robots.txt b/frontend/build/robots.txt new file mode 100644 index 0000000..3949da0 --- /dev/null +++ b/frontend/build/robots.txt @@ -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 diff --git a/frontend/build/service-worker.js b/frontend/build/service-worker.js new file mode 100644 index 0000000..22b27fd --- /dev/null +++ b/frontend/build/service-worker.js @@ -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');