enhance
@@ -1,58 +0,0 @@
|
||||
{
|
||||
"files": {
|
||||
"main.css": "/static/css/main.e9950038.css",
|
||||
"main.js": "/static/js/main.dd323bc8.js",
|
||||
"runtime.js": "/static/js/runtime.0b5f0839.js",
|
||||
"static/js/469.ec731235.chunk.js": "/static/js/469.ec731235.chunk.js",
|
||||
"static/js/910.bec7c150.chunk.js": "/static/js/910.bec7c150.chunk.js",
|
||||
"static/js/154.a93817c6.chunk.js": "/static/js/154.a93817c6.chunk.js",
|
||||
"static/js/771.2b2378c2.chunk.js": "/static/js/771.2b2378c2.chunk.js",
|
||||
"static/js/558.0464ebe3.chunk.js": "/static/js/558.0464ebe3.chunk.js",
|
||||
"static/js/334.0d196877.chunk.js": "/static/js/334.0d196877.chunk.js",
|
||||
"static/js/118.8a550f36.chunk.js": "/static/js/118.8a550f36.chunk.js",
|
||||
"static/js/339.c1ec8d08.chunk.js": "/static/js/339.c1ec8d08.chunk.js",
|
||||
"static/js/158.c7e50479.chunk.js": "/static/js/158.c7e50479.chunk.js",
|
||||
"static/js/798.21001033.chunk.js": "/static/js/798.21001033.chunk.js",
|
||||
"static/js/620.edc51951.chunk.js": "/static/js/620.edc51951.chunk.js",
|
||||
"static/js/548.da462cb8.chunk.js": "/static/js/548.da462cb8.chunk.js",
|
||||
"static/js/501.0b99fbb0.chunk.js": "/static/js/501.0b99fbb0.chunk.js",
|
||||
"static/js/266.e18a46b8.chunk.js": "/static/js/266.e18a46b8.chunk.js",
|
||||
"static/js/452.bad3d00e.chunk.js": "/static/js/452.bad3d00e.chunk.js",
|
||||
"static/js/791.2cae987d.chunk.js": "/static/js/791.2cae987d.chunk.js",
|
||||
"static/js/579.f8a21d69.chunk.js": "/static/js/579.f8a21d69.chunk.js",
|
||||
"static/js/453.d9749a7d.chunk.js": "/static/js/453.d9749a7d.chunk.js",
|
||||
"static/css/664.02d4d120.css": "/static/css/664.02d4d120.css",
|
||||
"static/js/664.d9b6a63b.js": "/static/js/664.d9b6a63b.js",
|
||||
"index.html": "/index.html",
|
||||
"main.e9950038.css.map": "/static/css/main.e9950038.css.map",
|
||||
"main.dd323bc8.js.map": "/static/js/main.dd323bc8.js.map",
|
||||
"runtime.0b5f0839.js.map": "/static/js/runtime.0b5f0839.js.map",
|
||||
"469.ec731235.chunk.js.map": "/static/js/469.ec731235.chunk.js.map",
|
||||
"910.bec7c150.chunk.js.map": "/static/js/910.bec7c150.chunk.js.map",
|
||||
"154.a93817c6.chunk.js.map": "/static/js/154.a93817c6.chunk.js.map",
|
||||
"771.2b2378c2.chunk.js.map": "/static/js/771.2b2378c2.chunk.js.map",
|
||||
"558.0464ebe3.chunk.js.map": "/static/js/558.0464ebe3.chunk.js.map",
|
||||
"334.0d196877.chunk.js.map": "/static/js/334.0d196877.chunk.js.map",
|
||||
"118.8a550f36.chunk.js.map": "/static/js/118.8a550f36.chunk.js.map",
|
||||
"339.c1ec8d08.chunk.js.map": "/static/js/339.c1ec8d08.chunk.js.map",
|
||||
"158.c7e50479.chunk.js.map": "/static/js/158.c7e50479.chunk.js.map",
|
||||
"798.21001033.chunk.js.map": "/static/js/798.21001033.chunk.js.map",
|
||||
"620.edc51951.chunk.js.map": "/static/js/620.edc51951.chunk.js.map",
|
||||
"548.da462cb8.chunk.js.map": "/static/js/548.da462cb8.chunk.js.map",
|
||||
"501.0b99fbb0.chunk.js.map": "/static/js/501.0b99fbb0.chunk.js.map",
|
||||
"266.e18a46b8.chunk.js.map": "/static/js/266.e18a46b8.chunk.js.map",
|
||||
"452.bad3d00e.chunk.js.map": "/static/js/452.bad3d00e.chunk.js.map",
|
||||
"791.2cae987d.chunk.js.map": "/static/js/791.2cae987d.chunk.js.map",
|
||||
"579.f8a21d69.chunk.js.map": "/static/js/579.f8a21d69.chunk.js.map",
|
||||
"453.d9749a7d.chunk.js.map": "/static/js/453.d9749a7d.chunk.js.map",
|
||||
"664.02d4d120.css.map": "/static/css/664.02d4d120.css.map",
|
||||
"664.d9b6a63b.js.map": "/static/js/664.d9b6a63b.js.map"
|
||||
},
|
||||
"entrypoints": [
|
||||
"static/js/runtime.0b5f0839.js",
|
||||
"static/css/664.02d4d120.css",
|
||||
"static/js/664.d9b6a63b.js",
|
||||
"static/css/main.e9950038.css",
|
||||
"static/js/main.dd323bc8.js"
|
||||
]
|
||||
}
|
||||
@@ -1 +1,29 @@
|
||||
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="/favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1,viewport-fit=cover"/><meta name="theme-color" content="#000000"/><meta name="color-scheme" content="light dark"/><meta name="description" content="Oficiální webové stránky fotbalového klubu - aktuality, zápasy, tabulky, hráči a fotogalerie"/><link rel="apple-touch-icon" href="/logo192.png"/><link rel="manifest" href="/manifest.json"/><link rel="preconnect" href="https://fonts.googleapis.com"/><link rel="preconnect" href="https://fonts.gstatic.com" crossorigin/><link rel="preconnect" href="https://www.youtube.com" crossorigin/><link rel="preconnect" href="https://i.ytimg.com" crossorigin/><link rel="preconnect" href="https://s.ytimg.com" crossorigin/><link rel="preconnect" href="https://www.google.com" crossorigin/><title>Fotbal Club</title><script defer="defer" src="/static/js/runtime.0b5f0839.js"></script><script defer="defer" src="/static/js/664.d9b6a63b.js"></script><script defer="defer" src="/static/js/main.dd323bc8.js"></script><link href="/static/css/664.02d4d120.css" rel="stylesheet"><link href="/static/css/main.e9950038.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<meta name="color-scheme" content="light dark" />
|
||||
<meta
|
||||
name="description"
|
||||
content="Oficiální webové stránky fotbalového klubu - aktuality, zápasy, tabulky, hráči a fotogalerie"
|
||||
/>
|
||||
<link rel="apple-touch-icon" href="/logo192.png" />
|
||||
<link rel="manifest" href="/manifest.json" />
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link rel="preconnect" href="https://www.youtube.com" crossorigin />
|
||||
<link rel="preconnect" href="https://i.ytimg.com" crossorigin />
|
||||
<link rel="preconnect" href="https://s.ytimg.com" crossorigin />
|
||||
<link rel="preconnect" href="https://www.google.com" crossorigin />
|
||||
<title>Fotbal Club</title>
|
||||
<script type="module" crossorigin src="/static/index-CLCx9azl.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/static/index-BHzfWAR3.css">
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 5.2 KiB |
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 9.4 KiB |
@@ -1 +1,26 @@
|
||||
{"short_name": "Fotbal Club", "name": "Fotbal Club - Oficiální aplikace", "description": "Oficiální webové stránky fotbalového klubu - aktuality, zápasy, tabulky, hráči a fotogalerie", "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"}
|
||||
{
|
||||
"short_name": "Fotbal Club",
|
||||
"name": "Fotbal Club - Oficiální aplikace",
|
||||
"description": "Oficiální webové stránky fotbalového klubu - aktuality, zápasy, tabulky, hráči a fotogalerie",
|
||||
"icons": [
|
||||
{
|
||||
"src": "favicon.ico",
|
||||
"sizes": "64x64 32x32 24x24 16x16",
|
||||
"type": "image/x-icon"
|
||||
},
|
||||
{
|
||||
"src": "logo192.png?v=2",
|
||||
"type": "image/png",
|
||||
"sizes": "192x192"
|
||||
},
|
||||
{
|
||||
"src": "logo512.png?v=2",
|
||||
"type": "image/png",
|
||||
"sizes": "512x512"
|
||||
}
|
||||
],
|
||||
"start_url": ".",
|
||||
"display": "standalone",
|
||||
"theme_color": "#000000",
|
||||
"background_color": "#ffffff"
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/* eslint-disable no-restricted-globals */
|
||||
// Service Worker for PWA support and offline functionality
|
||||
|
||||
const CACHE_VERSION = 'v1.0.2';
|
||||
const CACHE_VERSION = 'v1.0.3';
|
||||
const CACHE_NAME = `fotbal-club-cache-${CACHE_VERSION}`;
|
||||
|
||||
// Rate limiting for background updates
|
||||
@@ -14,8 +14,8 @@ const STATIC_ASSETS = [
|
||||
'/index.html',
|
||||
'/manifest.json',
|
||||
'/favicon.ico',
|
||||
'/logo192.png',
|
||||
'/logo512.png',
|
||||
'/logo192.png?v=2',
|
||||
'/logo512.png?v=2',
|
||||
'/robots.txt',
|
||||
];
|
||||
|
||||
@@ -266,8 +266,8 @@ self.addEventListener('push', (event) => {
|
||||
const title = data.title || 'Fotbal Club';
|
||||
const options = {
|
||||
body: data.body || 'Nová notifikace',
|
||||
icon: '/logo192.png',
|
||||
badge: '/logo192.png',
|
||||
icon: '/logo192.png?v=2',
|
||||
badge: '/logo192.png?v=2',
|
||||
data: data.url || '/',
|
||||
};
|
||||
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
"use strict";(self.webpackChunkfrontend=self.webpackChunkfrontend||[]).push([[118],{58118:(e,t,r)=>{r.r(t),r.d(t,{default:()=>m});var o=r(65043),s=r(22107),n=r(98662),i=r(25011),a=r(10202),l=r(28846),c=r(39081),h=r(1009),u=r(70579);class d extends o.Component{constructor(e){super(e),this.resetTimeout=null,this.scheduleAutoReset=()=>{this.resetTimeout&&clearTimeout(this.resetTimeout),this.resetTimeout=setTimeout(()=>{this.handleReset()},3e3)},this.handleReset=()=>{this.resetTimeout&&(clearTimeout(this.resetTimeout),this.resetTimeout=null),this.cleanupDOMElements(),this.setState({hasError:!1,error:null,errorInfo:null}),this.props.onReset&&this.props.onReset()},this.cleanupDOMElements=()=>{try{document.querySelectorAll(".elementor-overlay").forEach(e=>{try{e.remove()}catch(t){console.warn("Failed to remove overlay:",t)}});const c=document.querySelector(".myuibrix-viewport-wrapper");if(c&&c.parentElement)try{const l=c.parentElement;if(l.hasAttribute("data-myuibrix-restore"))Array.from(c.children).forEach(e=>{try{l.appendChild(e)}catch(t){console.warn("Failed to move child:",t)}}),c.remove(),l.removeAttribute("data-myuibrix-restore");else{try{c.classList.remove("myuibrix-viewport-wrapper")}catch(e){}try{c.removeAttribute("data-myuibrix-wrapped")}catch(t){}try{c.style.width=""}catch(r){}try{c.style.maxWidth=""}catch(o){}try{c.style.transition=""}catch(s){}try{c.style.margin=""}catch(n){}try{c.style.transform=""}catch(i){}try{c.style.transformOrigin=""}catch(a){}}}catch(l){console.warn("Failed to cleanup viewport wrapper:",l)}document.body.style.paddingTop="0",document.body.style.backgroundColor="",document.body.style.userSelect=""}catch(l){console.error("Error during DOM cleanup:",l)}},this.state={hasError:!1,error:null,errorInfo:null,errorCount:0}}static getDerivedStateFromError(e){return{hasError:!0,error:e}}componentDidCatch(e,t){console.error("MyUIbrix Error Boundary caught an error:",e,t),this.setState(e=>({errorInfo:t,errorCount:e.errorCount+1})),(e.message.includes("removeChild")||e.message.includes("insertBefore")||e.message.includes("replaceChild")||"DOMException"===e.name)&&(console.warn("DOM manipulation error detected - will auto-reset in 3 seconds"),this.scheduleAutoReset())}componentWillUnmount(){this.resetTimeout&&clearTimeout(this.resetTimeout)}render(){if(this.state.hasError&&this.state.error){const e=this.state.error.message.includes("removeChild")||this.state.error.message.includes("insertBefore")||this.state.error.message.includes("replaceChild")||"DOMException"===this.state.error.name;return(0,u.jsx)(s.a,{position:"fixed",top:"0",left:"0",right:"0",bottom:"0",bg:"rgba(0, 0, 0, 0.85)",backdropFilter:"blur(8px)",display:"flex",alignItems:"center",justifyContent:"center",zIndex:99999,children:(0,u.jsxs)(a.T,{spacing:6,bg:"white",p:8,borderRadius:"2xl",boxShadow:"0 20px 60px rgba(0,0,0,0.5)",maxW:"600px",w:"90%",children:[(0,u.jsx)(i.I,{as:h.eHT,boxSize:16,color:"orange.500"}),(0,u.jsxs)(a.T,{spacing:2,children:[(0,u.jsx)(l.D,{size:"lg",color:"gray.800",children:e?"Chyba p\u0159i manipulaci s prvky":"Chyba editoru"}),(0,u.jsx)(c.E,{color:"gray.600",textAlign:"center",children:e?"Nastala chyba p\u0159i p\u0159esouv\xe1n\xed nebo upravov\xe1n\xed prvk\u016f. Editor se automaticky obnov\xed za 3 sekundy.":"V editoru nastala neo\u010dek\xe1van\xe1 chyba. Klikn\u011bte na tla\u010d\xedtko pro obnoven\xed."})]}),!1,(0,u.jsxs)(a.T,{spacing:3,w:"100%",children:[(0,u.jsx)(n.$,{leftIcon:(0,u.jsx)(h.jTZ,{}),colorScheme:"blue",size:"lg",w:"100%",onClick:this.handleReset,children:"Obnovit editor"}),this.state.errorCount>3&&(0,u.jsxs)(c.E,{fontSize:"sm",color:"orange.600",textAlign:"center",children:["\u26a0\ufe0f Opakovan\xe9 chyby (",this.state.errorCount,"x). Zva\u017ete obnoven\xed str\xe1nky."]}),this.state.errorCount>3&&(0,u.jsx)(n.$,{size:"sm",variant:"ghost",colorScheme:"gray",onClick:()=>window.location.reload(),children:"Obnovit celou str\xe1nku"})]})]})})}return this.props.children}}const m=d}}]);
|
||||
//# sourceMappingURL=118.8a550f36.chunk.js.map
|
||||
@@ -1,2 +0,0 @@
|
||||
"use strict";(self.webpackChunkfrontend=self.webpackChunkfrontend||[]).push([[453],{46453:(e,t,n)=>{n.r(t),n.d(t,{getCLS:()=>y,getFCP:()=>g,getFID:()=>C,getLCP:()=>P,getTTFB:()=>D});var i,r,a,o,u=function(e,t){return{name:e,value:void 0===t?-1:t,delta:0,entries:[],id:"v2-".concat(Date.now(),"-").concat(Math.floor(8999999999999*Math.random())+1e12)}},c=function(e,t){try{if(PerformanceObserver.supportedEntryTypes.includes(e)){if("first-input"===e&&!("PerformanceEventTiming"in self))return;var n=new PerformanceObserver(function(e){return e.getEntries().map(t)});return n.observe({type:e,buffered:!0}),n}}catch(e){}},f=function(e,t){var n=function n(i){"pagehide"!==i.type&&"hidden"!==document.visibilityState||(e(i),t&&(removeEventListener("visibilitychange",n,!0),removeEventListener("pagehide",n,!0)))};addEventListener("visibilitychange",n,!0),addEventListener("pagehide",n,!0)},s=function(e){addEventListener("pageshow",function(t){t.persisted&&e(t)},!0)},m=function(e,t,n){var i;return function(r){t.value>=0&&(r||n)&&(t.delta=t.value-(i||0),(t.delta||void 0===i)&&(i=t.value,e(t)))}},v=-1,d=function(){return"hidden"===document.visibilityState?0:1/0},p=function(){f(function(e){var t=e.timeStamp;v=t},!0)},l=function(){return v<0&&(v=d(),p(),s(function(){setTimeout(function(){v=d(),p()},0)})),{get firstHiddenTime(){return v}}},g=function(e,t){var n,i=l(),r=u("FCP"),a=function(e){"first-contentful-paint"===e.name&&(f&&f.disconnect(),e.startTime<i.firstHiddenTime&&(r.value=e.startTime,r.entries.push(e),n(!0)))},o=window.performance&&performance.getEntriesByName&&performance.getEntriesByName("first-contentful-paint")[0],f=o?null:c("paint",a);(o||f)&&(n=m(e,r,t),o&&a(o),s(function(i){r=u("FCP"),n=m(e,r,t),requestAnimationFrame(function(){requestAnimationFrame(function(){r.value=performance.now()-i.timeStamp,n(!0)})})}))},h=!1,T=-1,y=function(e,t){h||(g(function(e){T=e.value}),h=!0);var n,i=function(t){T>-1&&e(t)},r=u("CLS",0),a=0,o=[],v=function(e){if(!e.hadRecentInput){var t=o[0],i=o[o.length-1];a&&e.startTime-i.startTime<1e3&&e.startTime-t.startTime<5e3?(a+=e.value,o.push(e)):(a=e.value,o=[e]),a>r.value&&(r.value=a,r.entries=o,n())}},d=c("layout-shift",v);d&&(n=m(i,r,t),f(function(){d.takeRecords().map(v),n(!0)}),s(function(){a=0,T=-1,r=u("CLS",0),n=m(i,r,t)}))},E={passive:!0,capture:!0},w=new Date,L=function(e,t){i||(i=t,r=e,a=new Date,F(removeEventListener),S())},S=function(){if(r>=0&&r<a-w){var e={entryType:"first-input",name:i.type,target:i.target,cancelable:i.cancelable,startTime:i.timeStamp,processingStart:i.timeStamp+r};o.forEach(function(t){t(e)}),o=[]}},b=function(e){if(e.cancelable){var t=(e.timeStamp>1e12?new Date:performance.now())-e.timeStamp;"pointerdown"==e.type?function(e,t){var n=function(){L(e,t),r()},i=function(){r()},r=function(){removeEventListener("pointerup",n,E),removeEventListener("pointercancel",i,E)};addEventListener("pointerup",n,E),addEventListener("pointercancel",i,E)}(t,e):L(t,e)}},F=function(e){["mousedown","keydown","touchstart","pointerdown"].forEach(function(t){return e(t,b,E)})},C=function(e,t){var n,a=l(),v=u("FID"),d=function(e){e.startTime<a.firstHiddenTime&&(v.value=e.processingStart-e.startTime,v.entries.push(e),n(!0))},p=c("first-input",d);n=m(e,v,t),p&&f(function(){p.takeRecords().map(d),p.disconnect()},!0),p&&s(function(){var a;v=u("FID"),n=m(e,v,t),o=[],r=-1,i=null,F(addEventListener),a=d,o.push(a),S()})},k={},P=function(e,t){var n,i=l(),r=u("LCP"),a=function(e){var t=e.startTime;t<i.firstHiddenTime&&(r.value=t,r.entries.push(e),n())},o=c("largest-contentful-paint",a);if(o){n=m(e,r,t);var v=function(){k[r.id]||(o.takeRecords().map(a),o.disconnect(),k[r.id]=!0,n(!0))};["keydown","click"].forEach(function(e){addEventListener(e,v,{once:!0,capture:!0})}),f(v,!0),s(function(i){r=u("LCP"),n=m(e,r,t),requestAnimationFrame(function(){requestAnimationFrame(function(){r.value=performance.now()-i.timeStamp,k[r.id]=!0,n(!0)})})})}},D=function(e){var t,n=u("TTFB");t=function(){try{var t=performance.getEntriesByType("navigation")[0]||function(){var e=performance.timing,t={entryType:"navigation",startTime:0};for(var n in e)"navigationStart"!==n&&"toJSON"!==n&&(t[n]=Math.max(e[n]-e.navigationStart,0));return t}();if(n.value=n.delta=t.responseStart,n.value<0||n.value>performance.now())return;n.entries=[t],e(n)}catch(e){}},"complete"===document.readyState?setTimeout(t,0):addEventListener("load",function(){return setTimeout(t,0)})}}}]);
|
||||
//# sourceMappingURL=453.d9749a7d.chunk.js.map
|
||||
@@ -1,2 +0,0 @@
|
||||
"use strict";(self.webpackChunkfrontend=self.webpackChunkfrontend||[]).push([[469],{69469:(e,r,t)=>{t.r(r),t.d(r,{default:()=>c});var a=t(89379),n=(t(65043),t(22107)),i=t(7189),o=t(25189),l=t(70579);const c=e=>{let{banners:r,placement:t,containerStyle:c}=e;const s=(r||[]).filter(e=>e.placement===t&&!1!==e.is_active);if(0===s.length)return null;const d=(0,a.A)((0,a.A)({},(()=>{const e={margin:"24px 0",display:"flex",justifyContent:"center",alignItems:"center",gap:"16px",flexWrap:"wrap"};switch(t){case"homepage_top":return(0,a.A)((0,a.A)({},e),{},{width:"100vw",position:"relative",left:"50%",right:"50%",marginLeft:"-50vw",marginRight:"-50vw",backgroundColor:"rgba(0, 0, 0, 0.02)",padding:"16px",borderTop:"1px solid rgba(0, 0, 0, 0.05)",borderBottom:"1px solid rgba(0, 0, 0, 0.05)"});case"homepage_footer":return(0,a.A)((0,a.A)({},e),{},{width:"100vw",position:"relative",left:"50%",right:"50%",marginLeft:"-50vw",marginRight:"-50vw",backgroundColor:"rgba(0, 0, 0, 0.02)",padding:"24px 16px",borderTop:"1px solid rgba(0, 0, 0, 0.05)"});case"homepage_under_table":return(0,a.A)((0,a.A)({},e),{},{margin:"12px 0 0",justifyContent:"center"});default:return e}})()),c);return(0,l.jsx)(n.a,{as:"section",className:(()=>{switch(t){case"homepage_top":return"banner-top";case"homepage_middle":return"banner-middle";case"homepage_footer":return"banner-footer";case"article_inline":return"banner-article";case"homepage_under_table":return"banner-under-table";default:return"banner"}})(),sx:d,children:s.map(e=>(0,l.jsx)(i.N,{href:e.url||e.click_url||"#",isExternal:!(!e.url&&!e.click_url),target:e.url||e.click_url?"_blank":void 0,rel:e.url||e.click_url?"noopener noreferrer":void 0,display:"inline-block",_hover:{opacity:.9,transform:"translateY(-2px)"},transition:"all 0.2s",children:(0,l.jsx)("img",{src:(0,o.uq)(e.image||e.image_url||"")||e.image||e.image_url||"",alt:e.name,style:{maxWidth:"100%",width:e.width?"".concat(e.width,"px"):"auto",height:e.height?"".concat(e.height,"px"):"auto",objectFit:"contain",borderRadius:"4px",boxShadow:"none"},loading:"lazy"})},e.id))})}}}]);
|
||||
//# sourceMappingURL=469.ec731235.chunk.js.map
|
||||
@@ -1,2 +0,0 @@
|
||||
"use strict";(self.webpackChunkfrontend=self.webpackChunkfrontend||[]).push([[771],{69771:(e,l,s)=>{s.r(l),s.d(l,{default:()=>x});s(65043);var a=s(22107),n=s(44093),i=s(76659),t=s(10202),d=s(28846),r=s(39081),c=s(72526),o=s(71093),u=s(35349),p=s(70579);const x=e=>{let{featuredOnly:l=!0,maxPolls:s=1,title:x="Hlasov\xe1n\xed"}=e;const h=(0,n.dU)("gray.50","gray.900"),{data:g,isLoading:y}=(0,c.I)({queryKey:["polls",{featured:l}],queryFn:()=>(0,o.Ps)(l?{featured:!0}:void 0),staleTime:12e4}),f=(null===g||void 0===g?void 0:g.slice(0,s))||[],{data:j,isLoading:m}=(0,c.I)({queryKey:["polls-details",f.map(e=>e.id)],queryFn:async()=>{const e=f.map(e=>(0,o.gU)(e.id));return await Promise.all(e)},enabled:f.length>0});return y||m?(0,p.jsx)(a.a,{bg:h,py:12,px:4,children:(0,p.jsxs)(t.T,{spacing:4,children:[(0,p.jsx)(i.y,{size:"lg"}),(0,p.jsx)(r.E,{children:"Na\u010d\xedt\xe1n\xed ankety..."})]})}):j&&0!==j.length?(0,p.jsx)(a.a,{bg:h,py:12,px:4,children:(0,p.jsxs)(t.T,{spacing:8,maxW:"4xl",mx:"auto",children:[(0,p.jsx)(d.D,{size:"lg",textAlign:"center",children:x}),(0,p.jsx)(t.T,{spacing:6,w:"full",children:j.map(e=>(0,p.jsx)(a.a,{w:"full",maxW:"600px",children:(0,p.jsx)(u.A,{poll:e.poll,hasVoted:e.has_voted,isActive:e.is_active,canShowResults:e.can_show_results})},e.poll.id))})]})}):null}}}]);
|
||||
//# sourceMappingURL=771.2b2378c2.chunk.js.map
|
||||
@@ -1 +0,0 @@
|
||||
{"version":3,"file":"static/js/771.2b2378c2.chunk.js","mappings":"6PAqBA,MAiEA,EAjEgDA,IAIzC,IAJ0C,aAC/CC,GAAe,EAAI,SACnBC,EAAW,EAAC,MACZC,EAAQ,mBACTH,EACC,MAAMI,GAAYC,EAAAA,EAAAA,IAAkB,UAAW,aAGvCC,KAAMC,EAAK,UAAEC,IAAcC,EAAAA,EAAAA,GAAS,CAC1CC,SAAU,CAAC,QAAS,CAAEC,SAAUV,IAChCW,QAASA,KAAMC,EAAAA,EAAAA,IAASZ,EAAe,CAAEU,UAAU,QAASG,GAC5DC,UAAW,OAIPC,GAAsB,OAALT,QAAK,IAALA,OAAK,EAALA,EAAOU,MAAM,EAAGf,KAAa,IAE5CI,KAAMY,EAAWV,UAAWW,IAAmBV,EAAAA,EAAAA,GAAS,CAC9DC,SAAU,CAAC,gBAAiBM,EAAeI,IAAKC,GAAMA,EAAEC,KACxDV,QAASW,UACP,MAAMC,EAAWR,EAAeI,IAAKK,IAASC,EAAAA,EAAAA,IAAQD,EAAKH,KAC3D,aAAaK,QAAQC,IAAIJ,IAE3BK,QAASb,EAAec,OAAS,IAGnC,OAAItB,GAAaW,GAEbY,EAAAA,EAAAA,KAACC,EAAAA,EAAG,CAACC,GAAI7B,EAAW8B,GAAI,GAAIC,GAAI,EAAEC,UAChCC,EAAAA,EAAAA,MAACC,EAAAA,EAAM,CAACC,QAAS,EAAEH,SAAA,EACjBL,EAAAA,EAAAA,KAACS,EAAAA,EAAO,CAACC,KAAK,QACdV,EAAAA,EAAAA,KAACW,EAAAA,EAAI,CAAAN,SAAC,0CAMTlB,GAAkC,IAArBA,EAAUY,QAK1BC,EAAAA,EAAAA,KAACC,EAAAA,EAAG,CAACC,GAAI7B,EAAW8B,GAAI,GAAIC,GAAI,EAAEC,UAChCC,EAAAA,EAAAA,MAACC,EAAAA,EAAM,CAACC,QAAS,EAAGI,KAAK,MAAMC,GAAG,OAAMR,SAAA,EACtCL,EAAAA,EAAAA,KAACc,EAAAA,EAAO,CAACJ,KAAK,KAAKK,UAAU,SAAQV,SAClCjC,KAGH4B,EAAAA,EAAAA,KAACO,EAAAA,EAAM,CAACC,QAAS,EAAGQ,EAAE,OAAMX,SACzBlB,EAAUE,IAAK4B,IACdjB,EAAAA,EAAAA,KAACC,EAAAA,EAAG,CAA4Be,EAAE,OAAOJ,KAAK,QAAOP,UACnDL,EAAAA,EAAAA,KAACkB,EAAAA,EAAQ,CACPxB,KAAMuB,EAAavB,KACnByB,SAAUF,EAAaG,UACvBC,SAAUJ,EAAaK,UACvBC,eAAgBN,EAAaO,oBALvBP,EAAavB,KAAKH,YAZ7B,K","sources":["components/home/PollsWidget.tsx"],"sourcesContent":["import React from 'react';\nimport {\n Box,\n VStack,\n Heading,\n Text,\n Spinner,\n Alert,\n AlertIcon,\n useColorModeValue,\n} from '@chakra-ui/react';\nimport { useQuery } from '@tanstack/react-query';\nimport { getPolls, getPoll } from '../../services/polls';\nimport PollCard from '../polls/PollCard';\n\ninterface PollsWidgetProps {\n featuredOnly?: boolean;\n maxPolls?: number;\n title?: string;\n}\n\nconst PollsWidget: React.FC<PollsWidgetProps> = ({\n featuredOnly = true,\n maxPolls = 1,\n title = 'Hlasování',\n}) => {\n const bgSection = useColorModeValue('gray.50', 'gray.900');\n\n // Fetch polls list\n const { data: polls, isLoading } = useQuery({\n queryKey: ['polls', { featured: featuredOnly }],\n queryFn: () => getPolls(featuredOnly ? { featured: true } : undefined),\n staleTime: 2 * 60 * 1000, // 2 minutes\n });\n\n // Get full poll data for each featured poll\n const pollsToDisplay = polls?.slice(0, maxPolls) || [];\n\n const { data: pollsData, isLoading: isLoadingPolls } = useQuery({\n queryKey: ['polls-details', pollsToDisplay.map((p) => p.id)],\n queryFn: async () => {\n const promises = pollsToDisplay.map((poll) => getPoll(poll.id));\n return await Promise.all(promises);\n },\n enabled: pollsToDisplay.length > 0,\n });\n\n if (isLoading || isLoadingPolls) {\n return (\n <Box bg={bgSection} py={12} px={4}>\n <VStack spacing={4}>\n <Spinner size=\"lg\" />\n <Text>Načítání ankety...</Text>\n </VStack>\n </Box>\n );\n }\n\n if (!pollsData || pollsData.length === 0) {\n return null; // Don't show widget if no polls\n }\n\n return (\n <Box bg={bgSection} py={12} px={4}>\n <VStack spacing={8} maxW=\"4xl\" mx=\"auto\">\n <Heading size=\"lg\" textAlign=\"center\">\n {title}\n </Heading>\n\n <VStack spacing={6} w=\"full\">\n {pollsData.map((pollResponse) => (\n <Box key={pollResponse.poll.id} w=\"full\" maxW=\"600px\">\n <PollCard\n poll={pollResponse.poll}\n hasVoted={pollResponse.has_voted}\n isActive={pollResponse.is_active}\n canShowResults={pollResponse.can_show_results}\n />\n </Box>\n ))}\n </VStack>\n </VStack>\n </Box>\n );\n};\n\nexport default PollsWidget;\n"],"names":["_ref","featuredOnly","maxPolls","title","bgSection","useColorModeValue","data","polls","isLoading","useQuery","queryKey","featured","queryFn","getPolls","undefined","staleTime","pollsToDisplay","slice","pollsData","isLoadingPolls","map","p","id","async","promises","poll","getPoll","Promise","all","enabled","length","_jsx","Box","bg","py","px","children","_jsxs","VStack","spacing","Spinner","size","Text","maxW","mx","Heading","textAlign","w","pollResponse","PollCard","hasVoted","has_voted","isActive","is_active","canShowResults","can_show_results"],"sourceRoot":""}
|
||||
@@ -1,2 +0,0 @@
|
||||
"use strict";(self.webpackChunkfrontend=self.webpackChunkfrontend||[]).push([[798],{78798:(e,s,n)=>{n.r(s),n.d(s,{default:()=>o});n(65043);var a=n(74117),l=n(22107),i=n(98662),r=n(98939),c=n(10202),d=n(39081),t=n(70579);const o=()=>{const{t:e,i18n:s}=(0,a.Bd)();return(0,t.jsx)(r.m,{maxW:"container.md",py:8,children:(0,t.jsx)(l.a,{p:4,borderWidth:1,borderRadius:"md",children:(0,t.jsxs)(c.T,{spacing:4,align:"start",children:[(0,t.jsx)(d.E,{fontSize:"2xl",fontWeight:"bold",children:"i18n Test Page"}),(0,t.jsxs)(d.E,{children:["Current language: ",(0,t.jsx)("strong",{children:s.language})]}),(0,t.jsxs)(d.E,{children:["Is ready: ",s.isInitialized?"Yes":"No"]}),(0,t.jsxs)(d.E,{children:["Loaded languages: ",s.languages.join(", ")]}),(0,t.jsxs)(l.a,{borderWidth:1,p:3,borderRadius:"md",w:"full",children:[(0,t.jsx)(d.E,{fontWeight:"bold",children:"Translation tests:"}),(0,t.jsxs)(d.E,{children:["Welcome: ",e("common.welcome_message")]}),(0,t.jsxs)(d.E,{children:["Subtitle: ",e("common.welcome_subtitle")]}),(0,t.jsxs)(d.E,{children:["Home: ",e("nav.home")]}),(0,t.jsxs)(d.E,{children:["Matches: ",e("nav.matches")]}),(0,t.jsxs)(d.E,{children:["Gallery: ",e("nav.gallery")]}),(0,t.jsxs)(d.E,{children:["Save: ",e("action.save")]}),(0,t.jsxs)(d.E,{children:["Loading: ",e("message.loading")]})]}),(0,t.jsxs)(i.$,{onClick:()=>{const e="cs"===s.language?"en":"cs";s.changeLanguage(e)},colorScheme:"blue",children:["Switch to ","cs"===s.language?"English":"\u010ce\u0161tina"]})]})})})}}}]);
|
||||
//# sourceMappingURL=798.21001033.chunk.js.map
|
||||
@@ -1 +0,0 @@
|
||||
{"version":3,"file":"static/js/798.21001033.chunk.js","mappings":"4NAIA,MAqCA,EArC+BA,KAC7B,MAAM,EAAEC,EAAC,KAAEC,IAASC,EAAAA,EAAAA,MAOpB,OACEC,EAAAA,EAAAA,KAACC,EAAAA,EAAS,CAACC,KAAK,eAAeC,GAAI,EAAEC,UACnCJ,EAAAA,EAAAA,KAACK,EAAAA,EAAG,CAACC,EAAG,EAAGC,YAAa,EAAGC,aAAa,KAAIJ,UAC1CK,EAAAA,EAAAA,MAACC,EAAAA,EAAM,CAACC,QAAS,EAAGC,MAAM,QAAOR,SAAA,EAC/BJ,EAAAA,EAAAA,KAACa,EAAAA,EAAI,CAACC,SAAS,MAAMC,WAAW,OAAMX,SAAC,oBACvCK,EAAAA,EAAAA,MAACI,EAAAA,EAAI,CAAAT,SAAA,CAAC,sBAAkBJ,EAAAA,EAAAA,KAAA,UAAAI,SAASN,EAAKkB,eACtCP,EAAAA,EAAAA,MAACI,EAAAA,EAAI,CAAAT,SAAA,CAAC,aAAWN,EAAKmB,cAAgB,MAAQ,SAC9CR,EAAAA,EAAAA,MAACI,EAAAA,EAAI,CAAAT,SAAA,CAAC,qBAAmBN,EAAKoB,UAAUC,KAAK,UAE7CV,EAAAA,EAAAA,MAACJ,EAAAA,EAAG,CAACE,YAAa,EAAGD,EAAG,EAAGE,aAAa,KAAKY,EAAE,OAAMhB,SAAA,EACnDJ,EAAAA,EAAAA,KAACa,EAAAA,EAAI,CAACE,WAAW,OAAMX,SAAC,wBACxBK,EAAAA,EAAAA,MAACI,EAAAA,EAAI,CAAAT,SAAA,CAAC,YAAUP,EAAE,8BAClBY,EAAAA,EAAAA,MAACI,EAAAA,EAAI,CAAAT,SAAA,CAAC,aAAWP,EAAE,+BACnBY,EAAAA,EAAAA,MAACI,EAAAA,EAAI,CAAAT,SAAA,CAAC,SAAOP,EAAE,gBACfY,EAAAA,EAAAA,MAACI,EAAAA,EAAI,CAAAT,SAAA,CAAC,YAAUP,EAAE,mBAClBY,EAAAA,EAAAA,MAACI,EAAAA,EAAI,CAAAT,SAAA,CAAC,YAAUP,EAAE,mBAClBY,EAAAA,EAAAA,MAACI,EAAAA,EAAI,CAAAT,SAAA,CAAC,SAAOP,EAAE,mBACfY,EAAAA,EAAAA,MAACI,EAAAA,EAAI,CAAAT,SAAA,CAAC,YAAUP,EAAE,0BAGpBY,EAAAA,EAAAA,MAACY,EAAAA,EAAM,CAACC,QAzBOC,KACrB,MAAMC,EAA4B,OAAlB1B,EAAKkB,SAAoB,KAAO,KAChDlB,EAAK2B,eAAeD,IAuBmBE,YAAY,OAAMtB,SAAA,CAAC,aACrB,OAAlBN,EAAKkB,SAAoB,UAAY,8B","sources":["pages/I18nTestPage.tsx"],"sourcesContent":["import React from 'react';\nimport { useTranslation } from 'react-i18next';\nimport { Box, Text, Button, VStack, Container } from '@chakra-ui/react';\n\nconst I18nTestPage: React.FC = () => {\n const { t, i18n } = useTranslation();\n\n const toggleLanguage = () => {\n const newLang = i18n.language === 'cs' ? 'en' : 'cs';\n i18n.changeLanguage(newLang);\n };\n\n return (\n <Container maxW=\"container.md\" py={8}>\n <Box p={4} borderWidth={1} borderRadius=\"md\">\n <VStack spacing={4} align=\"start\">\n <Text fontSize=\"2xl\" fontWeight=\"bold\">i18n Test Page</Text>\n <Text>Current language: <strong>{i18n.language}</strong></Text>\n <Text>Is ready: {i18n.isInitialized ? 'Yes' : 'No'}</Text>\n <Text>Loaded languages: {i18n.languages.join(', ')}</Text>\n \n <Box borderWidth={1} p={3} borderRadius=\"md\" w=\"full\">\n <Text fontWeight=\"bold\">Translation tests:</Text>\n <Text>Welcome: {t('common.welcome_message')}</Text>\n <Text>Subtitle: {t('common.welcome_subtitle')}</Text>\n <Text>Home: {t('nav.home')}</Text>\n <Text>Matches: {t('nav.matches')}</Text>\n <Text>Gallery: {t('nav.gallery')}</Text>\n <Text>Save: {t('action.save')}</Text>\n <Text>Loading: {t('message.loading')}</Text>\n </Box>\n \n <Button onClick={toggleLanguage} colorScheme=\"blue\">\n Switch to {i18n.language === 'cs' ? 'English' : 'Čeština'}\n </Button>\n </VStack>\n </Box>\n </Container>\n );\n};\n\nexport default I18nTestPage;\n"],"names":["I18nTestPage","t","i18n","useTranslation","_jsx","Container","maxW","py","children","Box","p","borderWidth","borderRadius","_jsxs","VStack","spacing","align","Text","fontSize","fontWeight","language","isInitialized","languages","join","w","Button","onClick","toggleLanguage","newLang","changeLanguage","colorScheme"],"sourceRoot":""}
|
||||
@@ -1,2 +0,0 @@
|
||||
"use strict";(self.webpackChunkfrontend=self.webpackChunkfrontend||[]).push([[910],{15910:(e,i,r)=>{r.r(i),r.d(i,{default:()=>z});r(65043);var t=r(87139),a=r(22107),o=r(98662),s=r(44093),n=r(33572),l=r(99032),d=r(96148),c=r(10202),p=r(28846),g=r(39081),h=r(72526),u=r(34636),x=r(70118),m=r(35475),b=r(78801),f=r(25189),j=r(75088),v=r(35087),w=r(12255),y=r(70579);const _=e=>{var i;let{a:r}=e;const o=(0,s.dU)("white","gray.800"),l=(0,s.dU)("gray.200","whiteAlpha.300"),h=((0,b.N)(),r.slug?"/news/".concat(r.slug):"/articles/".concat(r.id)),u=((0,s.dU)("gray.100","whiteAlpha.200"),(null===r||void 0===r||null===(i=r.category)||void 0===i?void 0:i.name)||"");return(0,y.jsxs)(a.a,{as:m.N_,to:h,minW:{base:"85%",md:"60%",lg:"33%"},scrollSnapAlign:"start",bg:o,borderRadius:"xl",overflow:"hidden",boxShadow:"lg",borderWidth:"1px",borderColor:l,_hover:{transform:"translateY(-4px)",boxShadow:"2xl"},transition:"all 0.3s cubic-bezier(0.4, 0, 0.2, 1)",position:"relative",children:[(0,y.jsxs)(a.a,{position:"relative",overflow:"hidden",children:[(0,y.jsx)(n._,{src:(0,f.uq)(r.image_url)||"/stadium-placeholder.jpg",alt:r.title,w:"100%",h:{base:"200px",md:"240px"},objectFit:"cover",transition:"transform 0.3s ease",_groupHover:{transform:"scale(1.05)"}}),u&&(0,y.jsx)(t.E,{position:"absolute",top:3,left:3,colorScheme:"blue",fontSize:"xs",px:3,py:1,borderRadius:"full",textTransform:"uppercase",fontWeight:"bold",children:u})]}),(0,y.jsxs)(c.T,{align:"stretch",spacing:3,p:5,children:[(0,y.jsx)(p.D,{size:"md",noOfLines:2,lineHeight:"1.3",children:r.title}),r.content&&(0,y.jsx)(g.E,{fontSize:"sm",color:"gray.600",noOfLines:3,lineHeight:"1.5",children:r.content.replace(/<[^>]*>/g,"").trim()}),(0,y.jsxs)(d.z,{spacing:3,pt:2,borderTopWidth:"1px",borderColor:l,flexWrap:"wrap",children:[(r.read_time||r.estimated_read_minutes)&&(0,y.jsxs)(d.z,{spacing:1,children:[(0,y.jsx)(j.A,{size:14,color:"gray"}),(0,y.jsxs)(g.E,{fontSize:"xs",color:"gray.500",children:[r.read_time||r.estimated_read_minutes," min"]})]}),void 0!==r.view_count&&r.view_count>0&&(0,y.jsxs)(d.z,{spacing:1,children:[(0,y.jsx)(v.A,{size:14,color:"gray"}),(0,y.jsx)(g.E,{fontSize:"xs",color:"gray.500",children:r.view_count})]}),r.published_at&&(0,y.jsx)(g.E,{fontSize:"xs",color:"gray.500",children:new Date(r.published_at).toLocaleDateString("cs-CZ")})]})]}),(0,y.jsx)(w.A,{article:r,targetUrl:"undefined"!==typeof window?new URL(h,window.location.origin).toString():void 0,placement:"inline",position:"bottom-right",size:"sm"})]})},z=()=>{(0,b.N)();const{data:e,isLoading:i}=(0,h.I)({queryKey:["articles",{page:1,page_size:12,published:!0}],queryFn:()=>(0,u.GE)({page:1,page_size:12,published:!0})}),{data:r}=(0,h.I)({queryKey:["articles","featured",{page:1,page_size:100}],queryFn:()=>(0,u.A0)({page:1,page_size:100})}),t=(null===e||void 0===e?void 0:e.data)||[],s=new Set(((null===r||void 0===r?void 0:r.data)||[]).map(e=>e.slug?"s:".concat(e.slug):"i:".concat(e.id))),n=t.filter(e=>!s.has(e.slug?"s:".concat(e.slug):"i:".concat(e.id)));return(0,y.jsx)(a.a,{children:(0,y.jsxs)(x.A,{title:"Novinky",rightAction:(0,y.jsx)(o.$,{as:m.N_,to:"/blog",variant:"link",color:"brand.primary",children:"V\xedce"}),children:[i&&Array.from({length:4}).map((e,i)=>(0,y.jsx)(l.E,{minW:{base:"85%",md:"60%",lg:"33%"},h:{base:"260px",md:"300px"},borderRadius:"xl"},i)),!i&&n.map(e=>(0,y.jsx)(_,{a:e},e.id))]})})}}}]);
|
||||
//# sourceMappingURL=910.bec7c150.chunk.js.map
|
||||
@@ -1,2 +0,0 @@
|
||||
(()=>{"use strict";var e={},t={};function r(o){var n=t[o];if(void 0!==n)return n.exports;var a=t[o]={id:o,loaded:!1,exports:{}};return e[o].call(a.exports,a,a.exports,r),a.loaded=!0,a.exports}r.m=e,(()=>{var e=[];r.O=(t,o,n,a)=>{if(!o){var i=1/0;for(l=0;l<e.length;l++){o=e[l][0],n=e[l][1],a=e[l][2];for(var d=!0,c=0;c<o.length;c++)(!1&a||i>=a)&&Object.keys(r.O).every(e=>r.O[e](o[c]))?o.splice(c--,1):(d=!1,a<i&&(i=a));if(d){e.splice(l--,1);var f=n();void 0!==f&&(t=f)}}return t}a=a||0;for(var l=e.length;l>0&&e[l-1][2]>a;l--)e[l]=e[l-1];e[l]=[o,n,a]}})(),r.n=e=>{var t=e&&e.__esModule?()=>e.default:()=>e;return r.d(t,{a:t}),t},(()=>{var e,t=Object.getPrototypeOf?e=>Object.getPrototypeOf(e):e=>e.__proto__;r.t=function(o,n){if(1&n&&(o=this(o)),8&n)return o;if("object"===typeof o&&o){if(4&n&&o.__esModule)return o;if(16&n&&"function"===typeof o.then)return o}var a=Object.create(null);r.r(a);var i={};e=e||[null,t({}),t([]),t(t)];for(var d=2&n&&o;("object"==typeof d||"function"==typeof d)&&!~e.indexOf(d);d=t(d))Object.getOwnPropertyNames(d).forEach(e=>i[e]=()=>o[e]);return i.default=()=>o,r.d(a,i),a}})(),r.d=(e,t)=>{for(var o in t)r.o(t,o)&&!r.o(e,o)&&Object.defineProperty(e,o,{enumerable:!0,get:t[o]})},r.f={},r.e=e=>Promise.all(Object.keys(r.f).reduce((t,o)=>(r.f[o](e,t),t),[])),r.u=e=>"static/js/"+e+"."+{118:"8a550f36",154:"a93817c6",158:"c7e50479",266:"e18a46b8",334:"0d196877",339:"c1ec8d08",452:"bad3d00e",453:"d9749a7d",469:"ec731235",501:"0b99fbb0",548:"da462cb8",558:"0464ebe3",579:"f8a21d69",620:"edc51951",771:"2b2378c2",791:"2cae987d",798:"21001033",910:"bec7c150"}[e]+".chunk.js",r.miniCssF=e=>{},r.g=function(){if("object"===typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"===typeof window)return window}}(),r.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),(()=>{var e={},t="frontend:";r.l=(o,n,a,i)=>{if(e[o])e[o].push(n);else{var d,c;if(void 0!==a)for(var f=document.getElementsByTagName("script"),l=0;l<f.length;l++){var u=f[l];if(u.getAttribute("src")==o||u.getAttribute("data-webpack")==t+a){d=u;break}}d||(c=!0,(d=document.createElement("script")).charset="utf-8",d.timeout=120,r.nc&&d.setAttribute("nonce",r.nc),d.setAttribute("data-webpack",t+a),d.src=o),e[o]=[n];var s=(t,r)=>{d.onerror=d.onload=null,clearTimeout(p);var n=e[o];if(delete e[o],d.parentNode&&d.parentNode.removeChild(d),n&&n.forEach(e=>e(r)),t)return t(r)},p=setTimeout(s.bind(null,void 0,{type:"timeout",target:d}),12e4);d.onerror=s.bind(null,d.onerror),d.onload=s.bind(null,d.onload),c&&document.head.appendChild(d)}}})(),r.r=e=>{"undefined"!==typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},r.nmd=e=>(e.paths=[],e.children||(e.children=[]),e),r.p="/",(()=>{var e={121:0};r.f.j=(t,o)=>{var n=r.o(e,t)?e[t]:void 0;if(0!==n)if(n)o.push(n[2]);else if(121!=t){var a=new Promise((r,o)=>n=e[t]=[r,o]);o.push(n[2]=a);var i=r.p+r.u(t),d=new Error;r.l(i,o=>{if(r.o(e,t)&&(0!==(n=e[t])&&(e[t]=void 0),n)){var a=o&&("load"===o.type?"missing":o.type),i=o&&o.target&&o.target.src;d.message="Loading chunk "+t+" failed.\n("+a+": "+i+")",d.name="ChunkLoadError",d.type=a,d.request=i,n[1](d)}},"chunk-"+t,t)}else e[t]=0},r.O.j=t=>0===e[t];var t=(t,o)=>{var n,a,i=o[0],d=o[1],c=o[2],f=0;if(i.some(t=>0!==e[t])){for(n in d)r.o(d,n)&&(r.m[n]=d[n]);if(c)var l=c(r)}for(t&&t(o);f<i.length;f++)a=i[f],r.o(e,a)&&e[a]&&e[a][0](),e[a]=0;return r.O(l)},o=self.webpackChunkfrontend=self.webpackChunkfrontend||[];o.forEach(t.bind(null,0)),o.push=t.bind(null,o.push.bind(o))})(),r.nc=void 0})();
|
||||
//# sourceMappingURL=runtime.0b5f0839.js.map
|
||||
@@ -10,6 +10,7 @@
|
||||
"test": "vitest run"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hello-pangea/dnd": "^18.0.1",
|
||||
"@chakra-ui/icons": "^2.1.1",
|
||||
"@chakra-ui/react": "^2.8.2",
|
||||
"@emotion/react": "^11.11.1",
|
||||
@@ -17,7 +18,7 @@
|
||||
"@hookform/resolvers": "^3.3.4",
|
||||
"@lobehub/icons": "^1.10.1",
|
||||
"@monaco-editor/react": "^4.6.0",
|
||||
"@tanstack/react-query": "^4.36.1",
|
||||
"@tanstack/react-query": "^4.43.0",
|
||||
"@testing-library/jest-dom": "^5.17.0",
|
||||
"@testing-library/react": "^13.4.0",
|
||||
"@testing-library/user-event": "^13.5.0",
|
||||
@@ -29,56 +30,58 @@
|
||||
"@types/react-dom": "^18.2.18",
|
||||
"@types/react-frame-component": "^4.1.6",
|
||||
"axios": "^1.13.6",
|
||||
"chart.js": "^4.4.1",
|
||||
"chart.js": "^4.5.1",
|
||||
"date-fns": "^4.1.0",
|
||||
"dompurify": "^3.3.0",
|
||||
"framer-motion": "^10.16.4",
|
||||
"i18next": "^23.7.16",
|
||||
"i18next-browser-languagedetector": "^7.2.0",
|
||||
"i18next-http-backend": "^2.4.2",
|
||||
"i18next": "^23.16.8",
|
||||
"i18next-browser-languagedetector": "^7.2.2",
|
||||
"i18next-http-backend": "^2.7.3",
|
||||
"lucide-react": "^0.379.0",
|
||||
"maplibre-gl": "^5.9.0",
|
||||
"maplibre-gl": "^5.20.1",
|
||||
"monaco-editor": "^0.49.0",
|
||||
"popmotion": "^11.0.5",
|
||||
"qrcode": "^1.5.4",
|
||||
"quill": "^2.0.3",
|
||||
"react": "^18.2.0",
|
||||
"react-beautiful-dnd": "^13.1.1",
|
||||
"react-chartjs-2": "^5.2.0",
|
||||
"react-datepicker": "^8.7.0",
|
||||
"react-chartjs-2": "^5.3.1",
|
||||
"react-datepicker": "^8.10.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-frame-component": "^5.2.7",
|
||||
"react-helmet-async": "^2.0.5",
|
||||
"react-hook-form": "^7.48.2",
|
||||
"react-hook-form": "^7.71.2",
|
||||
"react-i18next": "^13.5.0",
|
||||
"react-icons": "^4.12.0",
|
||||
"react-icons": "^5.6.0",
|
||||
"react-image-crop": "^11.0.10",
|
||||
"react-markdown": "^10.1.0",
|
||||
"react-quill": "^2.0.0",
|
||||
"react-router-dom": "^6.30.2",
|
||||
"react-router-dom": "^6.30.3",
|
||||
"react-simple-maps": "^3.0.0",
|
||||
"react-syntax-highlighter": "^15.6.6",
|
||||
"tinymce": "^8.2.2",
|
||||
"react-syntax-highlighter": "^16.1.1",
|
||||
"tinymce": "^8.3.2",
|
||||
"typescript": "^4.9.5",
|
||||
"web-vitals": "^2.1.4",
|
||||
"yup": "^1.3.3"
|
||||
"yup": "^1.7.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/chart.js": "^2.9.41",
|
||||
"@types/dompurify": "^3.0.5",
|
||||
"@types/geojson": "^7946.0.8",
|
||||
"@types/maplibre-gl": "^1.13.2",
|
||||
"@types/react-beautiful-dnd": "^13.1.8",
|
||||
"@types/react-chartjs-2": "^2.0.2",
|
||||
"@types/react-image-crop": "^8.1.6",
|
||||
"@types/react-syntax-highlighter": "^15.5.13",
|
||||
"@vitejs/plugin-react": "^4.4.1",
|
||||
"eslint-plugin-jsx-a11y": "^6.7.1",
|
||||
"eslint": "^9.39.1",
|
||||
"eslint-plugin-jsx-a11y": "^6.10.2",
|
||||
"eslint-plugin-react-hooks": "^7.0.1",
|
||||
"jsdom": "^26.1.0",
|
||||
"vite": "^6.3.5",
|
||||
"vitest": "^3.1.1"
|
||||
},
|
||||
"overrides": {
|
||||
"flatted": "3.4.1",
|
||||
"glob@7": {
|
||||
"minimatch": "3.1.3"
|
||||
}
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
">0.2%",
|
||||
|
||||
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 5.2 KiB |
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 9.4 KiB |
@@ -1 +1,26 @@
|
||||
{"short_name": "Fotbal Club", "name": "Fotbal Club - Oficiální aplikace", "description": "Oficiální webové stránky fotbalového klubu - aktuality, zápasy, tabulky, hráči a fotogalerie", "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"}
|
||||
{
|
||||
"short_name": "Fotbal Club",
|
||||
"name": "Fotbal Club - Oficiální aplikace",
|
||||
"description": "Oficiální webové stránky fotbalového klubu - aktuality, zápasy, tabulky, hráči a fotogalerie",
|
||||
"icons": [
|
||||
{
|
||||
"src": "favicon.ico",
|
||||
"sizes": "64x64 32x32 24x24 16x16",
|
||||
"type": "image/x-icon"
|
||||
},
|
||||
{
|
||||
"src": "logo192.png?v=2",
|
||||
"type": "image/png",
|
||||
"sizes": "192x192"
|
||||
},
|
||||
{
|
||||
"src": "logo512.png?v=2",
|
||||
"type": "image/png",
|
||||
"sizes": "512x512"
|
||||
}
|
||||
],
|
||||
"start_url": ".",
|
||||
"display": "standalone",
|
||||
"theme_color": "#000000",
|
||||
"background_color": "#ffffff"
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/* eslint-disable no-restricted-globals */
|
||||
// Service Worker for PWA support and offline functionality
|
||||
|
||||
const CACHE_VERSION = 'v1.0.2';
|
||||
const CACHE_VERSION = 'v1.0.3';
|
||||
const CACHE_NAME = `fotbal-club-cache-${CACHE_VERSION}`;
|
||||
|
||||
// Rate limiting for background updates
|
||||
@@ -14,8 +14,8 @@ const STATIC_ASSETS = [
|
||||
'/index.html',
|
||||
'/manifest.json',
|
||||
'/favicon.ico',
|
||||
'/logo192.png',
|
||||
'/logo512.png',
|
||||
'/logo192.png?v=2',
|
||||
'/logo512.png?v=2',
|
||||
'/robots.txt',
|
||||
];
|
||||
|
||||
@@ -266,8 +266,8 @@ self.addEventListener('push', (event) => {
|
||||
const title = data.title || 'Fotbal Club';
|
||||
const options = {
|
||||
body: data.body || 'Nová notifikace',
|
||||
icon: '/logo192.png',
|
||||
badge: '/logo192.png',
|
||||
icon: '/logo192.png?v=2',
|
||||
badge: '/logo192.png?v=2',
|
||||
data: data.url || '/',
|
||||
};
|
||||
|
||||
|
||||
@@ -1,9 +1,33 @@
|
||||
import React from 'react';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { vi } from 'vitest';
|
||||
import { HelmetProvider } from 'react-helmet-async';
|
||||
import App from './App';
|
||||
|
||||
test('renders learn react link', () => {
|
||||
render(<App />);
|
||||
const linkElement = screen.getByText(/learn react/i);
|
||||
expect(linkElement).toBeInTheDocument();
|
||||
vi.mock('./hooks/useUmami', () => ({
|
||||
useUmami: () => ({ isEnabled: false, isLoaded: false, trackEvent: vi.fn() }),
|
||||
}));
|
||||
|
||||
vi.mock('./hooks/usePublicSettings', () => ({
|
||||
usePublicSettings: () => ({ data: null }),
|
||||
}));
|
||||
|
||||
vi.mock('./utils/auth', () => ({
|
||||
isAuthenticated: () => false,
|
||||
checkAdminExists: async () => false,
|
||||
getToken: () => null,
|
||||
clearToken: vi.fn(),
|
||||
setToken: vi.fn(),
|
||||
}));
|
||||
|
||||
test('renders the app shell', () => {
|
||||
window.history.pushState({}, 'Test 404', '/test-not-found');
|
||||
|
||||
render(
|
||||
<HelmetProvider>
|
||||
<App />
|
||||
</HelmetProvider>
|
||||
);
|
||||
|
||||
expect(screen.getByText(/stránka nenalezena/i)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
@@ -13,6 +13,7 @@ interface UseFacrApiReturn {
|
||||
searchResults: SearchResponse['results'] | [];
|
||||
searchLoading: boolean;
|
||||
searchError: Error | null;
|
||||
clearSearchResults: () => void;
|
||||
|
||||
// Get club details by ID and type
|
||||
getClub: (clubId: string, clubType?: 'football' | 'futsal') => Promise<ClubInfo>;
|
||||
@@ -64,6 +65,7 @@ export const useFacrApi = (): UseFacrApiReturn => {
|
||||
async (query: string): Promise<SearchResponse> => {
|
||||
setSearchLoading(true);
|
||||
setSearchError(null);
|
||||
setSearchResults([]);
|
||||
try {
|
||||
const response = await handleApiCall(() => facrApi.searchClubs(query));
|
||||
setSearchResults(response.results || []);
|
||||
@@ -107,11 +109,18 @@ export const useFacrApi = (): UseFacrApiReturn => {
|
||||
facrApi.clearCache();
|
||||
}, []);
|
||||
|
||||
const clearSearchResults = useCallback(() => {
|
||||
setSearchResults([]);
|
||||
setSearchError(null);
|
||||
setSearchLoading(false);
|
||||
}, []);
|
||||
|
||||
return {
|
||||
searchClubs,
|
||||
searchResults,
|
||||
searchLoading,
|
||||
searchError,
|
||||
clearSearchResults,
|
||||
getClub,
|
||||
getClubTable,
|
||||
getClubCompetitions,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useEffect, useState, useMemo, startTransition } from 'react';
|
||||
import { useEffect, useState, useMemo, useRef, startTransition } from 'react';
|
||||
import { Box, Button, FormControl, FormLabel, Input, VStack, Heading, useToast, SimpleGrid, Divider, Text, useColorModeValue, InputGroup, InputRightElement, List, ListItem, Spinner, HStack, Image, Checkbox, Tooltip, Alert, AlertIcon, Select, FormHelperText, Badge, Link } from '@chakra-ui/react';
|
||||
import { InfoOutlineIcon } from '@chakra-ui/icons';
|
||||
import './styles/MagazineHome.css';
|
||||
@@ -62,9 +62,11 @@ const SetupPage: React.FC = () => {
|
||||
const [clubUrl, setClubUrl] = useState('');
|
||||
const [clubLink, setClubLink] = useState('');
|
||||
const [clubQuery, setClubQuery] = useState('');
|
||||
const [selectedClubSearchLabel, setSelectedClubSearchLabel] = useState('');
|
||||
const { data: publicSettings } = usePublicSettings();
|
||||
const isManualClubDataMode = (publicSettings?.club_data_mode || '').toLowerCase() === 'manual';
|
||||
const { searchClubs, searchResults, searchLoading } = useFacrApi();
|
||||
const { searchClubs, searchResults, searchLoading, clearSearchResults } = useFacrApi();
|
||||
const suppressNextClubSearchRef = useRef(false);
|
||||
|
||||
const resolveLogoUrl = (u?: string | null) => {
|
||||
if (!u) return undefined;
|
||||
@@ -216,11 +218,16 @@ const SetupPage: React.FC = () => {
|
||||
useEffect(() => {
|
||||
const q = clubQuery.trim();
|
||||
if (!q) return;
|
||||
if (selectedClubSearchLabel.trim() && q === selectedClubSearchLabel.trim()) return;
|
||||
if (suppressNextClubSearchRef.current) {
|
||||
suppressNextClubSearchRef.current = false;
|
||||
return;
|
||||
}
|
||||
const t = setTimeout(() => {
|
||||
searchClubs(q).catch(() => {});
|
||||
}, 300);
|
||||
return () => clearTimeout(t);
|
||||
}, [clubQuery, searchClubs]);
|
||||
}, [clubQuery, searchClubs, selectedClubSearchLabel]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isDomainHost && !showAdvancedApi) {
|
||||
@@ -332,12 +339,15 @@ const SetupPage: React.FC = () => {
|
||||
};
|
||||
|
||||
const handleSelectClub = async (item: SearchResult) => {
|
||||
suppressNextClubSearchRef.current = true;
|
||||
setSelectedClubSearchLabel(item.name || '');
|
||||
clearSearchResults();
|
||||
const clubIdValue = item.club_id || '';
|
||||
setClubId(clubIdValue);
|
||||
setClubType(item.club_type || 'football');
|
||||
setClubName(item.name || '');
|
||||
setClubUrl(item.url || '');
|
||||
setClubQuery(item.name || '');
|
||||
setClubQuery('');
|
||||
|
||||
// Try to fetch both logo and club name from logoapi first
|
||||
let logoUrl = '';
|
||||
@@ -827,12 +837,20 @@ const SetupPage: React.FC = () => {
|
||||
<FormControl>
|
||||
<FormLabel>Hledat klub (FAČR)</FormLabel>
|
||||
<InputGroup>
|
||||
<Input value={clubQuery} onChange={(e) => setClubQuery(e.target.value)} placeholder="Hledejte podle názvu klubu" />
|
||||
<Input
|
||||
value={clubQuery}
|
||||
onChange={(e) => {
|
||||
suppressNextClubSearchRef.current = false;
|
||||
setSelectedClubSearchLabel('');
|
||||
setClubQuery(e.target.value);
|
||||
}}
|
||||
placeholder="Hledejte podle názvu klubu"
|
||||
/>
|
||||
<InputRightElement>
|
||||
{searchLoading ? <Spinner size="sm" /> : null}
|
||||
</InputRightElement>
|
||||
</InputGroup>
|
||||
{clubQuery && searchResults?.length > 0 && (
|
||||
{clubQuery.trim() && clubQuery.trim() !== selectedClubSearchLabel && searchResults?.length > 0 && (
|
||||
<Box mt={2} borderWidth="1px" borderRadius="md" maxH="240px" overflowY="auto">
|
||||
<List spacing={0}>
|
||||
{searchResults.filter((r) => r.name && r.name.trim() !== '').slice(0, 8).map((r) => (
|
||||
|
||||
@@ -47,7 +47,7 @@ import {
|
||||
Collapse,
|
||||
Icon,
|
||||
} from '@chakra-ui/react';
|
||||
import { DragDropContext, Droppable, Draggable, DropResult } from 'react-beautiful-dnd';
|
||||
import { DragDropContext, Droppable, Draggable, DropResult } from '@hello-pangea/dnd';
|
||||
import AdminLayout from '../../layouts/AdminLayout';
|
||||
import {
|
||||
AddIcon,
|
||||
|
||||
@@ -2,24 +2,17 @@ import axios, { AxiosInstance, AxiosResponse, InternalAxiosRequestConfig } from
|
||||
import { reportError } from './errorReporter';
|
||||
import { getToken } from '../utils/auth';
|
||||
import { logAction } from './actionLog';
|
||||
import { resolveApiBaseUrl } from '../utils/apiBaseUrl';
|
||||
|
||||
function readStored(key: string): string | null {
|
||||
try { return localStorage.getItem(key); } catch { return null; }
|
||||
try {
|
||||
return localStorage.getItem(key);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
const storedApi = typeof window !== 'undefined' ? (readStored('fc_api_base_url') || readStored('api_base_url')) : null;
|
||||
const envApiUrl = process.env.REACT_APP_API_URL || process.env.REACT_APP_API_BASE_URL;
|
||||
let API_URL = storedApi || envApiUrl || '/api/v1';
|
||||
|
||||
try {
|
||||
const maybe = new URL(API_URL, typeof window !== 'undefined' ? window.location.origin : undefined);
|
||||
if (!/\/api\//.test(maybe.pathname)) {
|
||||
maybe.pathname = maybe.pathname.replace(/\/$/, '') + '/api/v1';
|
||||
API_URL = maybe.toString();
|
||||
} else {
|
||||
API_URL = maybe.toString();
|
||||
}
|
||||
} catch {}
|
||||
const API_URL = resolveApiBaseUrl();
|
||||
|
||||
export const api: AxiosInstance = axios.create({
|
||||
baseURL: API_URL,
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
function readStoredApiBase(key: string): string | null {
|
||||
try {
|
||||
return localStorage.getItem(key);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export function normalizeApiBaseUrl(input?: string | null): string {
|
||||
const fallbackOrigin = typeof window !== 'undefined' ? window.location.origin : 'http://localhost';
|
||||
const candidate = (input || '').trim() || '/api/v1';
|
||||
|
||||
try {
|
||||
const resolved = new URL(candidate, fallbackOrigin);
|
||||
if (!/\/api\//.test(resolved.pathname)) {
|
||||
resolved.pathname = resolved.pathname.replace(/\/$/, '') + '/api/v1';
|
||||
}
|
||||
return resolved.toString();
|
||||
} catch {
|
||||
const trimmed = candidate.replace(/\/$/, '');
|
||||
return /\/api\//.test(trimmed) ? trimmed : `${trimmed}/api/v1`;
|
||||
}
|
||||
}
|
||||
|
||||
export function resolveApiBaseUrl(): string {
|
||||
const storedApi =
|
||||
typeof window !== 'undefined'
|
||||
? readStoredApiBase('fc_api_base_url') || readStoredApiBase('api_base_url')
|
||||
: null;
|
||||
const envApiUrl = process.env.REACT_APP_API_URL || process.env.REACT_APP_API_BASE_URL;
|
||||
|
||||
return normalizeApiBaseUrl(storedApi || envApiUrl || '/api/v1');
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
// Use a single canonical key for the auth token across the app
|
||||
// Many modules expect 'auth_token' (see usages in services and build artifacts).
|
||||
import { resolveApiBaseUrl } from './apiBaseUrl';
|
||||
|
||||
const TOKEN_KEY = 'auth_token';
|
||||
const HAS_ADMIN_KEY = 'fotbal_club_has_admin';
|
||||
|
||||
@@ -43,9 +45,8 @@ export const setHasAdmin = (value: boolean): void => {
|
||||
|
||||
export const checkAdminExists = async (): Promise<boolean> => {
|
||||
try {
|
||||
// Use shared API base URL which is normalized to '/api/v1'
|
||||
const { API_URL } = await import('../services/api');
|
||||
const response = await fetch(`${API_URL}/auth/admin/exists`, {
|
||||
const apiUrl = resolveApiBaseUrl();
|
||||
const response = await fetch(`${apiUrl}/auth/admin/exists`, {
|
||||
headers: { 'Accept': 'application/json' },
|
||||
});
|
||||
if (response.ok) {
|
||||
|
||||
@@ -78,7 +78,7 @@ export function parseGoogleMapsUrl(url: string): MapCoordinates | null {
|
||||
}
|
||||
|
||||
// Try to extract from pathname (/@lat,lng,zoom format)
|
||||
const pathMatch = urlObj.pathname.match(/@(-?\d+\.\d+),(-?\d+\.\d+),(\d+)z/);
|
||||
const pathMatch = urlObj.pathname.match(/@(-?\d+\.\d+),(-?\d+\.\d+),(\d+)(?:[mz])/);
|
||||
if (pathMatch) {
|
||||
const latitude = parseFloat(pathMatch[1]);
|
||||
const longitude = parseFloat(pathMatch[2]);
|
||||
|
||||
@@ -253,6 +253,29 @@ const recordCircuitBreakerSuccess = (): void => {
|
||||
}
|
||||
};
|
||||
|
||||
const LOGO_API_HOSTS = new Set([
|
||||
'logoapi.sportcreative.eu',
|
||||
'www.logoapi.sportcreative.eu',
|
||||
]);
|
||||
|
||||
const normalizeLogoApiUrl = (raw?: string | null): string | null => {
|
||||
const value = String(raw || '').trim();
|
||||
if (!value) return null;
|
||||
|
||||
try {
|
||||
const fallbackOrigin = typeof window !== 'undefined'
|
||||
? window.location.origin
|
||||
: 'https://logoapi.sportcreative.eu';
|
||||
const parsed = new URL(value, fallbackOrigin);
|
||||
if (LOGO_API_HOSTS.has(parsed.hostname)) {
|
||||
parsed.protocol = 'https:';
|
||||
}
|
||||
return parsed.toString();
|
||||
} catch {
|
||||
return value;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Fetch logo from logoapi.sportcreative.eu with optimization and circuit breaker
|
||||
*/
|
||||
@@ -267,8 +290,16 @@ export const fetchLogoFromLogoAPI = async (teamId: string, teamName?: string): P
|
||||
// Check cache first
|
||||
const cached = await getCachedLogo(teamId);
|
||||
if (cached?.url && !cached.url.startsWith('blob:')) {
|
||||
const normalizedCachedUrl = normalizeLogoApiUrl(cached.url);
|
||||
await updateLastUsed(teamId);
|
||||
return cached.url;
|
||||
if (normalizedCachedUrl && normalizedCachedUrl !== cached.url) {
|
||||
await saveCachedLogo({
|
||||
...cached,
|
||||
url: normalizedCachedUrl,
|
||||
lastUsed: Date.now(),
|
||||
});
|
||||
}
|
||||
return normalizedCachedUrl;
|
||||
}
|
||||
|
||||
// Fetch from logoapi with timeout
|
||||
@@ -289,7 +320,7 @@ export const fetchLogoFromLogoAPI = async (teamId: string, teamName?: string): P
|
||||
}
|
||||
|
||||
const data = await res.json();
|
||||
const url = data.logo_url_svg || data.logo_url_png || data.logo_url;
|
||||
const url = normalizeLogoApiUrl(data.logo_url_svg || data.logo_url_png || data.logo_url);
|
||||
|
||||
if (!url) {
|
||||
recordCircuitBreakerFailure();
|
||||
@@ -319,19 +350,38 @@ export const fetchLogoFromLogoAPI = async (teamId: string, teamName?: string): P
|
||||
*/
|
||||
export const fetchClubNameAndLogoFromAPI = async (teamId: string): Promise<{ clubName?: string; logoUrl?: string } | null> => {
|
||||
try {
|
||||
const cached = await getCachedLogo(teamId);
|
||||
const cachedLogoUrl = cached?.url && !cached.url.startsWith('blob:')
|
||||
? normalizeLogoApiUrl(cached.url)
|
||||
: null;
|
||||
|
||||
// Fetch from logoapi
|
||||
const res = await fetch(`https://logoapi.sportcreative.eu/logos/${teamId}/json`, {
|
||||
method: 'GET',
|
||||
headers: { 'Accept': 'application/json' },
|
||||
});
|
||||
const controller = new AbortController();
|
||||
const timeoutId = setTimeout(() => controller.abort(), 5000);
|
||||
let res: Response;
|
||||
try {
|
||||
res = await fetch(`https://logoapi.sportcreative.eu/logos/${teamId}/json`, {
|
||||
method: 'GET',
|
||||
headers: { 'Accept': 'application/json' },
|
||||
signal: controller.signal,
|
||||
});
|
||||
} finally {
|
||||
clearTimeout(timeoutId);
|
||||
}
|
||||
|
||||
if (!res.ok) return null;
|
||||
if (!res.ok) {
|
||||
recordCircuitBreakerFailure();
|
||||
return cachedLogoUrl ? { logoUrl: cachedLogoUrl } : null;
|
||||
}
|
||||
|
||||
const data = await res.json();
|
||||
const logoUrl = data.logo_url_svg || data.logo_url_png || data.logo_url;
|
||||
const logoUrl = normalizeLogoApiUrl(data.logo_url_svg || data.logo_url_png || data.logo_url);
|
||||
const clubName = data.club_name;
|
||||
|
||||
if (!logoUrl && !clubName) return null;
|
||||
if (!logoUrl && !clubName) {
|
||||
recordCircuitBreakerFailure();
|
||||
return cachedLogoUrl ? { logoUrl: cachedLogoUrl } : null;
|
||||
}
|
||||
|
||||
// Cache the logo if available
|
||||
if (logoUrl) {
|
||||
@@ -343,14 +393,21 @@ export const fetchClubNameAndLogoFromAPI = async (teamId: string): Promise<{ clu
|
||||
lastUsed: Date.now(),
|
||||
});
|
||||
}
|
||||
|
||||
recordCircuitBreakerSuccess();
|
||||
|
||||
return {
|
||||
clubName: clubName || undefined,
|
||||
logoUrl: logoUrl || undefined,
|
||||
logoUrl: logoUrl || cachedLogoUrl || undefined,
|
||||
};
|
||||
} catch (e) {
|
||||
recordCircuitBreakerFailure();
|
||||
console.warn(`Failed to fetch club info for team ${teamId}:`, e);
|
||||
return null;
|
||||
const cached = await getCachedLogo(teamId).catch(() => null);
|
||||
const cachedLogoUrl = cached?.url && !cached.url.startsWith('blob:')
|
||||
? normalizeLogoApiUrl(cached.url)
|
||||
: null;
|
||||
return cachedLogoUrl ? { logoUrl: cachedLogoUrl } : null;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||