mirror of
https://github.com/Dvorinka/MyClubServer.git
synced 2026-06-04 02:32:57 +00:00
Vendored
+1
-1
@@ -1 +1 @@
|
|||||||
{"etag":"","fetched_at":"2025-10-24T14:27:54Z","last_modified":""}
|
{"etag":"","fetched_at":"2025-10-24T15:58:00Z","last_modified":""}
|
||||||
+1
-1
@@ -1 +1 @@
|
|||||||
{"etag":"","fetched_at":"2025-10-24T14:27:54Z","last_modified":""}
|
{"etag":"","fetched_at":"2025-10-24T15:58:00Z","last_modified":""}
|
||||||
+1
-1
@@ -1 +1 @@
|
|||||||
{"etag":"","fetched_at":"2025-10-24T14:27:54Z","last_modified":""}
|
{"etag":"","fetched_at":"2025-10-24T15:58:00Z","last_modified":""}
|
||||||
Vendored
+1
-1
@@ -1 +1 @@
|
|||||||
{"lastUpdated":"2025-10-24T14:27:54Z"}
|
{"lastUpdated":"2025-10-24T15:58:00Z"}
|
||||||
Vendored
+2
-2
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"baseURL": "http://127.0.0.1:8080/api/v1",
|
"baseURL": "http://127.0.0.1:8080/api/v1",
|
||||||
"duration_ms": 15,
|
"duration_ms": 34,
|
||||||
"endpoints": [
|
"endpoints": [
|
||||||
{
|
{
|
||||||
"path": "/settings",
|
"path": "/settings",
|
||||||
@@ -33,5 +33,5 @@
|
|||||||
"ok": true
|
"ok": true
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"lastUpdated": "2025-10-24T14:27:54Z"
|
"lastUpdated": "2025-10-24T15:58:00Z"
|
||||||
}
|
}
|
||||||
Vendored
+1
-1
@@ -1 +1 @@
|
|||||||
{"etag":"","fetched_at":"2025-10-24T14:27:54Z","last_modified":""}
|
{"etag":"","fetched_at":"2025-10-24T15:58:00Z","last_modified":""}
|
||||||
Vendored
+1
-1
@@ -1 +1 @@
|
|||||||
{"etag":"","fetched_at":"2025-10-24T14:27:54Z","last_modified":""}
|
{"etag":"","fetched_at":"2025-10-24T15:58:00Z","last_modified":""}
|
||||||
+1
-1
@@ -1 +1 @@
|
|||||||
{"etag":"","fetched_at":"2025-10-24T14:27:54Z","last_modified":""}
|
{"etag":"","fetched_at":"2025-10-24T15:58:00Z","last_modified":""}
|
||||||
@@ -51,6 +51,7 @@ import { getArticles } from '../services/articles';
|
|||||||
import { getCachedYouTube } from '../services/youtube';
|
import { getCachedYouTube } from '../services/youtube';
|
||||||
import { getZoneramaManifestWithFallbacks } from '../services/zonerama';
|
import { getZoneramaManifestWithFallbacks } from '../services/zonerama';
|
||||||
import { getMyNewsletterToken } from '../services/public/newsletter';
|
import { getMyNewsletterToken } from '../services/public/newsletter';
|
||||||
|
import { API_URL } from '../services/api';
|
||||||
|
|
||||||
type NavLink = { label: string; to?: string; items?: { label: string; to: string }[]; external?: boolean };
|
type NavLink = { label: string; to?: string; items?: { label: string; to: string }[]; external?: boolean };
|
||||||
|
|
||||||
@@ -327,8 +328,7 @@ const Navbar: React.FC<{ fullWidth?: boolean }> = ({ fullWidth = false }) => {
|
|||||||
if (!url) return;
|
if (!url) return;
|
||||||
// Normalize relative upload paths to API origin so favicon resolves on all pages
|
// Normalize relative upload paths to API origin so favicon resolves on all pages
|
||||||
try {
|
try {
|
||||||
const apiUrl = process.env.REACT_APP_API_URL || 'http://localhost:8080/api/v1';
|
const apiOrigin = new URL(API_URL, typeof window !== 'undefined' ? window.location.origin : 'http://localhost:3000').origin;
|
||||||
const apiOrigin = new URL(apiUrl).origin;
|
|
||||||
if (/^\/.+/.test(url) && !/^https?:\/\//i.test(url)) {
|
if (/^\/.+/.test(url) && !/^https?:\/\//i.test(url)) {
|
||||||
// If starts with /uploads or any absolute path, prefix API origin
|
// If starts with /uploads or any absolute path, prefix API origin
|
||||||
url = apiOrigin + url;
|
url = apiOrigin + url;
|
||||||
@@ -420,8 +420,7 @@ const Navbar: React.FC<{ fullWidth?: boolean }> = ({ fullWidth = false }) => {
|
|||||||
try {
|
try {
|
||||||
if (/^https?:\/\//i.test(path)) return path;
|
if (/^https?:\/\//i.test(path)) return path;
|
||||||
if (path.startsWith('/cache') || path.startsWith('/uploads') || path.startsWith('/api/')) {
|
if (path.startsWith('/cache') || path.startsWith('/uploads') || path.startsWith('/api/')) {
|
||||||
const base = process.env.REACT_APP_API_BASE_URL || process.env.REACT_APP_API_URL || 'http://localhost:8080/api/v1';
|
const origin = new URL(API_URL, typeof window !== 'undefined' ? window.location.origin : 'http://localhost:3000').origin;
|
||||||
const origin = new URL(base).origin;
|
|
||||||
return new URL(path, origin).toString();
|
return new URL(path, origin).toString();
|
||||||
}
|
}
|
||||||
return path;
|
return path;
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { assetUrl } from '../../utils/url';
|
import { assetUrl } from '../../utils/url';
|
||||||
|
import { API_URL } from '../../services/api';
|
||||||
|
|
||||||
interface Sponsor {
|
interface Sponsor {
|
||||||
id: number | string;
|
id: number | string;
|
||||||
@@ -18,9 +19,8 @@ const resolveBackendUrl = (path: string) => {
|
|||||||
try {
|
try {
|
||||||
if (/^https?:\/\//i.test(path)) return path;
|
if (/^https?:\/\//i.test(path)) return path;
|
||||||
if (path.startsWith('/cache') || path.startsWith('/uploads') || path.startsWith('/api/')) {
|
if (path.startsWith('/cache') || path.startsWith('/uploads') || path.startsWith('/api/')) {
|
||||||
const base = process.env.REACT_APP_API_BASE_URL || process.env.REACT_APP_API_URL || 'http://localhost:8080/api/v1';
|
const origin = new URL(API_URL, typeof window !== 'undefined' ? window.location.origin : 'http://localhost:3000').origin;
|
||||||
const b = new URL(base);
|
const abs = new URL(path, origin);
|
||||||
const abs = new URL(path, `${b.protocol}//${b.host}`);
|
|
||||||
return abs.toString();
|
return abs.toString();
|
||||||
}
|
}
|
||||||
return path;
|
return path;
|
||||||
@@ -42,7 +42,7 @@ const SponsorsSection: React.FC<SponsorsSectionProps> = ({
|
|||||||
const fetchSponsors = async () => {
|
const fetchSponsors = async () => {
|
||||||
try {
|
try {
|
||||||
// Try API first
|
// Try API first
|
||||||
const apiRes = await fetch(`${process.env.REACT_APP_API_BASE_URL || process.env.REACT_APP_API_URL || 'http://localhost:8080/api/v1'}/public/sponsors`);
|
const apiRes = await fetch(`${API_URL}/public/sponsors`);
|
||||||
if (apiRes.ok) {
|
if (apiRes.ok) {
|
||||||
const data = await apiRes.json();
|
const data = await apiRes.json();
|
||||||
if (!cancelled && Array.isArray(data)) {
|
if (!cancelled && Array.isArray(data)) {
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import { API_URL } from '../../services/api';
|
||||||
import { Link as RouterLink } from 'react-router-dom';
|
import { Link as RouterLink } from 'react-router-dom';
|
||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
@@ -33,9 +34,8 @@ const resolveBackendUrl = (path: string) => {
|
|||||||
try {
|
try {
|
||||||
if (/^https?:\/\//i.test(path)) return path;
|
if (/^https?:\/\//i.test(path)) return path;
|
||||||
if (path.startsWith('/cache') || path.startsWith('/uploads') || path.startsWith('/api/')) {
|
if (path.startsWith('/cache') || path.startsWith('/uploads') || path.startsWith('/api/')) {
|
||||||
const base = process.env.REACT_APP_API_BASE_URL || process.env.REACT_APP_API_URL || 'http://localhost:8080/api/v1';
|
const origin = new URL(API_URL, typeof window !== 'undefined' ? window.location.origin : 'http://localhost:3000').origin;
|
||||||
const b = new URL(base);
|
const abs = new URL(path, origin);
|
||||||
const abs = new URL(path, `${b.protocol}//${b.host}`);
|
|
||||||
return abs.toString();
|
return abs.toString();
|
||||||
}
|
}
|
||||||
return path;
|
return path;
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Box, Grid, GridItem, Heading, Image, Button, HStack, Text, VStack, Badge } from '@chakra-ui/react';
|
import { Box, Grid, GridItem, Heading, Image, Button, HStack, Text, VStack, Badge } from '@chakra-ui/react';
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import { API_URL } from '../../services/api';
|
||||||
import { Link as RouterLink } from 'react-router-dom';
|
import { Link as RouterLink } from 'react-router-dom';
|
||||||
import { Calendar, Image as ImageIcon } from 'lucide-react';
|
import { Calendar, Image as ImageIcon } from 'lucide-react';
|
||||||
|
|
||||||
@@ -22,9 +23,8 @@ const resolveBackendUrl = (path: string) => {
|
|||||||
try {
|
try {
|
||||||
if (/^https?:\/\//i.test(path)) return path;
|
if (/^https?:\/\//i.test(path)) return path;
|
||||||
if (path.startsWith('/cache') || path.startsWith('/uploads') || path.startsWith('/api/')) {
|
if (path.startsWith('/cache') || path.startsWith('/uploads') || path.startsWith('/api/')) {
|
||||||
const base = (process.env.REACT_APP_API_BASE_URL || process.env.REACT_APP_API_URL || 'http://localhost:8080/api/v1');
|
const origin = new URL(API_URL, typeof window !== 'undefined' ? window.location.origin : 'http://localhost:3000').origin;
|
||||||
const b = new URL(base);
|
const abs = new URL(path, origin);
|
||||||
const abs = new URL(path, `${b.protocol}//${b.host}`);
|
|
||||||
return abs.toString();
|
return abs.toString();
|
||||||
}
|
}
|
||||||
return path;
|
return path;
|
||||||
@@ -39,8 +39,7 @@ const PhotosSection: React.FC<{ zoneramaUrl?: string | null }> = ({ zoneramaUrl
|
|||||||
let active = true;
|
let active = true;
|
||||||
(async () => {
|
(async () => {
|
||||||
try {
|
try {
|
||||||
const apiUrl = process.env.REACT_APP_API_URL || 'http://localhost:8080/api/v1';
|
const response = await fetch(`${API_URL}/gallery/albums`);
|
||||||
const response = await fetch(`${apiUrl}/gallery/albums`);
|
|
||||||
|
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
|
|||||||
@@ -6,15 +6,14 @@ import { trackNavigation } from '../../utils/umami';
|
|||||||
import { useClubTheme } from '../../contexts/ClubThemeContext';
|
import { useClubTheme } from '../../contexts/ClubThemeContext';
|
||||||
import { usePublicSettings } from '../../hooks/usePublicSettings';
|
import { usePublicSettings } from '../../hooks/usePublicSettings';
|
||||||
import { assetUrl } from '../../utils/url';
|
import { assetUrl } from '../../utils/url';
|
||||||
|
import { API_URL } from '../../services/api';
|
||||||
|
|
||||||
const resolveBackendUrl = (path: string) => {
|
const resolveBackendUrl = (path: string) => {
|
||||||
try {
|
try {
|
||||||
if (/^https?:\/\//i.test(path)) return path;
|
if (/^https?:\/\//i.test(path)) return path;
|
||||||
if (path.startsWith('/cache') || path.startsWith('/uploads')) {
|
if (path.startsWith('/cache') || path.startsWith('/uploads')) {
|
||||||
const base = process.env.REACT_APP_API_BASE_URL || process.env.REACT_APP_API_URL || 'http://localhost:8080/api/v1';
|
const origin = new URL(API_URL, typeof window !== 'undefined' ? window.location.origin : 'http://localhost:3000').origin;
|
||||||
const u = new URL(base);
|
return new URL(path, origin).toString();
|
||||||
u.pathname = path;
|
|
||||||
return u.toString();
|
|
||||||
}
|
}
|
||||||
return path;
|
return path;
|
||||||
} catch {
|
} catch {
|
||||||
@@ -59,8 +58,7 @@ const Footer: React.FC = () => {
|
|||||||
} catch {}
|
} catch {}
|
||||||
// Fetch sponsors
|
// Fetch sponsors
|
||||||
try {
|
try {
|
||||||
const apiUrl = process.env.REACT_APP_API_BASE_URL || process.env.REACT_APP_API_URL || 'http://localhost:8080/api/v1';
|
const sponsorsRes = await fetch(`${API_URL}/public/sponsors`);
|
||||||
const sponsorsRes = await fetch(`${apiUrl}/public/sponsors`);
|
|
||||||
if (sponsorsRes.ok) {
|
if (sponsorsRes.ok) {
|
||||||
const data = await sponsorsRes.json();
|
const data = await sponsorsRes.json();
|
||||||
if (!cancelled && Array.isArray(data)) {
|
if (!cancelled && Array.isArray(data)) {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import React, { useState } from 'react';
|
|||||||
import { useLocation } from 'react-router-dom';
|
import { useLocation } from 'react-router-dom';
|
||||||
import { FaCalendarAlt, FaFutbol, FaExclamationTriangle, FaMapMarkerAlt } from 'react-icons/fa';
|
import { FaCalendarAlt, FaFutbol, FaExclamationTriangle, FaMapMarkerAlt } from 'react-icons/fa';
|
||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import { api } from '../../services/api';
|
import { api, API_URL } from '../../services/api';
|
||||||
import { useSettings } from '@/hooks/useSettings';
|
import { useSettings } from '@/hooks/useSettings';
|
||||||
import { Widget } from './Widget';
|
import { Widget } from './Widget';
|
||||||
import { format, parse, isToday, isTomorrow, isAfter } from 'date-fns';
|
import { format, parse, isToday, isTomorrow, isAfter } from 'date-fns';
|
||||||
@@ -46,8 +46,7 @@ export const MatchesWidget = () => {
|
|||||||
const resolveUrl = (path: string) => {
|
const resolveUrl = (path: string) => {
|
||||||
try {
|
try {
|
||||||
if (/^https?:\/\//i.test(path)) return path;
|
if (/^https?:\/\//i.test(path)) return path;
|
||||||
const apiUrl = process.env.REACT_APP_API_URL || 'http://localhost:8080/api/v1';
|
const origin = new URL(API_URL, typeof window !== 'undefined' ? window.location.origin : 'http://localhost:3000').origin;
|
||||||
const origin = new URL(apiUrl).origin;
|
|
||||||
return origin + path;
|
return origin + path;
|
||||||
} catch {
|
} catch {
|
||||||
return path;
|
return path;
|
||||||
@@ -109,8 +108,7 @@ export const MatchesWidget = () => {
|
|||||||
}
|
}
|
||||||
const chosen = candidate || orig;
|
const chosen = candidate || orig;
|
||||||
if (typeof chosen === 'string' && chosen.startsWith('/')) {
|
if (typeof chosen === 'string' && chosen.startsWith('/')) {
|
||||||
const apiUrl = process.env.REACT_APP_API_URL || 'http://localhost:8080/api/v1';
|
const origin = new URL(API_URL, typeof window !== 'undefined' ? window.location.origin : 'http://localhost:3000').origin;
|
||||||
const origin = new URL(apiUrl).origin;
|
|
||||||
return origin + chosen;
|
return origin + chosen;
|
||||||
}
|
}
|
||||||
return chosen || (assetUrl('/dist/img/logo-club-empty.svg') as string);
|
return chosen || (assetUrl('/dist/img/logo-club-empty.svg') as string);
|
||||||
@@ -122,8 +120,7 @@ export const MatchesWidget = () => {
|
|||||||
queryKey: ['upcomingMatchesCache'],
|
queryKey: ['upcomingMatchesCache'],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
// Build absolute origin from API URL env (which may include /api/v1)
|
// Build absolute origin from API URL env (which may include /api/v1)
|
||||||
const apiUrl = process.env.REACT_APP_API_URL || 'http://localhost:8080/api/v1';
|
const origin = new URL(API_URL, typeof window !== 'undefined' ? window.location.origin : 'http://localhost:3000').origin;
|
||||||
const origin = new URL(apiUrl).origin;
|
|
||||||
const url = `${origin}/cache/prefetch/facr_club_info.json`;
|
const url = `${origin}/cache/prefetch/facr_club_info.json`;
|
||||||
|
|
||||||
const res = await fetch(url, { headers: { 'Cache-Control': 'no-cache' } });
|
const res = await fetch(url, { headers: { 'Cache-Control': 'no-cache' } });
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import {
|
|||||||
} from '@chakra-ui/react';
|
} from '@chakra-ui/react';
|
||||||
import { ChevronRight, ExternalLink, Calendar, Image as ImageIcon } from 'lucide-react';
|
import { ChevronRight, ExternalLink, Calendar, Image as ImageIcon } from 'lucide-react';
|
||||||
import MainLayout from '../components/layout/MainLayout';
|
import MainLayout from '../components/layout/MainLayout';
|
||||||
|
import { API_URL } from '../services/api';
|
||||||
import PhotoModal from '../components/gallery/PhotoModal';
|
import PhotoModal from '../components/gallery/PhotoModal';
|
||||||
|
|
||||||
interface Photo {
|
interface Photo {
|
||||||
@@ -43,9 +44,8 @@ const resolveBackendUrl = (path: string) => {
|
|||||||
try {
|
try {
|
||||||
if (/^https?:\/\//i.test(path)) return path;
|
if (/^https?:\/\//i.test(path)) return path;
|
||||||
if (path.startsWith('/cache') || path.startsWith('/uploads') || path.startsWith('/api/')) {
|
if (path.startsWith('/cache') || path.startsWith('/uploads') || path.startsWith('/api/')) {
|
||||||
const base = (process.env.REACT_APP_API_BASE_URL || process.env.REACT_APP_API_URL || 'http://localhost:8080/api/v1');
|
const origin = new URL(API_URL, typeof window !== 'undefined' ? window.location.origin : 'http://localhost:3000').origin;
|
||||||
const b = new URL(base);
|
const abs = new URL(path, origin);
|
||||||
const abs = new URL(path, `${b.protocol}//${b.host}`);
|
|
||||||
return abs.toString();
|
return abs.toString();
|
||||||
}
|
}
|
||||||
return path;
|
return path;
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import { ExternalLink, ArrowRight, Eye, Clock } from 'lucide-react';
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { trackEvent as umamiTrackEvent, trackMatchView as umamiTrackMatchView, trackVideoPlay as umamiTrackVideoPlay, trackArticleView as umamiTrackArticleView } from '../utils/umami';
|
import { trackEvent as umamiTrackEvent, trackMatchView as umamiTrackMatchView, trackVideoPlay as umamiTrackVideoPlay, trackArticleView as umamiTrackArticleView } from '../utils/umami';
|
||||||
import { assetUrl } from '../utils/url';
|
import { assetUrl } from '../utils/url';
|
||||||
|
import { API_URL } from '../services/api';
|
||||||
|
|
||||||
const toText = (html?: string) => {
|
const toText = (html?: string) => {
|
||||||
if (!html) return '';
|
if (!html) return '';
|
||||||
@@ -60,8 +61,7 @@ const ArticleDetailPage: React.FC = () => {
|
|||||||
queryKey: ['facr-cached-match', (matchLinkQuery.data as any)?.external_match_id],
|
queryKey: ['facr-cached-match', (matchLinkQuery.data as any)?.external_match_id],
|
||||||
enabled: Boolean((matchLinkQuery.data as any)?.external_match_id),
|
enabled: Boolean((matchLinkQuery.data as any)?.external_match_id),
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const apiUrl = process.env.REACT_APP_API_URL || 'http://localhost:8080/api/v1';
|
const origin = new URL(API_URL, typeof window !== 'undefined' ? window.location.origin : 'http://localhost:3000').origin;
|
||||||
const origin = new URL(apiUrl).origin;
|
|
||||||
const url = `${origin}/cache/prefetch/facr_club_info.json`;
|
const url = `${origin}/cache/prefetch/facr_club_info.json`;
|
||||||
const res = await fetch(url, { cache: 'no-cache' });
|
const res = await fetch(url, { cache: 'no-cache' });
|
||||||
if (!res.ok) return null as any;
|
if (!res.ok) return null as any;
|
||||||
@@ -167,8 +167,7 @@ const ArticleDetailPage: React.FC = () => {
|
|||||||
const toAbsoluteUploads = React.useCallback((html?: string) => {
|
const toAbsoluteUploads = React.useCallback((html?: string) => {
|
||||||
if (!html) return '';
|
if (!html) return '';
|
||||||
try {
|
try {
|
||||||
const base = process.env.REACT_APP_API_BASE_URL || process.env.REACT_APP_API_URL || 'http://localhost:8080/api/v1';
|
const origin = new URL(API_URL, typeof window !== 'undefined' ? window.location.origin : 'http://localhost:3000').origin;
|
||||||
const origin = new URL(base).origin;
|
|
||||||
// Replace src="/uploads... and href="/uploads...
|
// Replace src="/uploads... and href="/uploads...
|
||||||
return html
|
return html
|
||||||
.replace(/src=("|')\s*(\/uploads\/[^"']+)("|')/g, (_m, q1, p2, q3) => `src=${q1}${origin}${p2}${q3}`)
|
.replace(/src=("|')\s*(\/uploads\/[^"']+)("|')/g, (_m, q1, p2, q3) => `src=${q1}${origin}${p2}${q3}`)
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Box, Container, Heading, VStack, Image, Text, Skeleton, LinkBox, HStack, Select, Badge, useColorModeValue } from '@chakra-ui/react';
|
import { Box, Container, Heading, VStack, Image, Text, Skeleton, LinkBox, HStack, Select, Badge, useColorModeValue } from '@chakra-ui/react';
|
||||||
import { useInfiniteQuery } from '@tanstack/react-query';
|
import { useInfiniteQuery } from '@tanstack/react-query';
|
||||||
import { getArticles, Article } from '../services/articles';
|
import { getArticles, Article, Paginated } from '../services/articles';
|
||||||
import { Link as RouterLink } from 'react-router-dom';
|
import { Link as RouterLink } from 'react-router-dom';
|
||||||
import { assetUrl } from '../utils/url';
|
import { assetUrl } from '../utils/url';
|
||||||
import MainLayout from '../components/layout/MainLayout';
|
import MainLayout from '../components/layout/MainLayout';
|
||||||
@@ -110,9 +110,15 @@ const BlogPage: React.FC = () => {
|
|||||||
isFetchingNextPage,
|
isFetchingNextPage,
|
||||||
hasNextPage,
|
hasNextPage,
|
||||||
fetchNextPage,
|
fetchNextPage,
|
||||||
} = useInfiniteQuery(
|
} = useInfiniteQuery<Paginated<Article>>(
|
||||||
['articles-public', { page_size: pageSize, published: true, category_id: categoryId || undefined }],
|
['articles-public', { page_size: pageSize, published: true, category_id: categoryId || undefined }],
|
||||||
({ pageParam = 1 }) => getArticles({ page: pageParam, page_size: pageSize, published: true, ...(categoryId ? { category_id: Number(categoryId) } : {}) }),
|
({ pageParam = 1 }) =>
|
||||||
|
getArticles({
|
||||||
|
page: pageParam,
|
||||||
|
page_size: pageSize,
|
||||||
|
published: true,
|
||||||
|
...(categoryId ? { category_id: Number(categoryId) } : {}),
|
||||||
|
}),
|
||||||
{
|
{
|
||||||
getNextPageParam: (lastPage, allPages) => {
|
getNextPageParam: (lastPage, allPages) => {
|
||||||
const loaded = allPages.reduce((sum, p) => sum + (p?.data?.length || 0), 0);
|
const loaded = allPages.reduce((sum, p) => sum + (p?.data?.length || 0), 0);
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import NewsletterCTA from '../components/common/NewsletterCTA';
|
|||||||
import { sortCategoriesWithOrder } from '../utils/categorySort';
|
import { sortCategoriesWithOrder } from '../utils/categorySort';
|
||||||
import ClubModal from '../components/home/ClubModal';
|
import ClubModal from '../components/home/ClubModal';
|
||||||
import { assetUrl } from '../utils/url';
|
import { assetUrl } from '../utils/url';
|
||||||
|
import { API_URL } from '../services/api';
|
||||||
|
|
||||||
// Weekday headers (Czech, starting Monday)
|
// Weekday headers (Czech, starting Monday)
|
||||||
const WEEKDAYS_SHORT: string[] = ['Po', 'Út', 'St', 'Čt', 'Pá', 'So', 'Ne'];
|
const WEEKDAYS_SHORT: string[] = ['Po', 'Út', 'St', 'Čt', 'Pá', 'So', 'Ne'];
|
||||||
@@ -91,10 +92,8 @@ const CalendarPage: React.FC = () => {
|
|||||||
try {
|
try {
|
||||||
if (/^https?:\/\//i.test(path)) return path;
|
if (/^https?:\/\//i.test(path)) return path;
|
||||||
if (path.startsWith('/cache') || path.startsWith('/uploads') || path.startsWith('/api/')) {
|
if (path.startsWith('/cache') || path.startsWith('/uploads') || path.startsWith('/api/')) {
|
||||||
const base = process.env.REACT_APP_API_BASE_URL || process.env.REACT_APP_API_URL || 'http://localhost:8080/api/v1';
|
const origin = new URL(API_URL, typeof window !== 'undefined' ? window.location.origin : 'http://localhost:3000').origin;
|
||||||
const u = new URL(base);
|
return new URL(path, origin).toString();
|
||||||
u.pathname = path; // use backend origin root
|
|
||||||
return u.toString();
|
|
||||||
}
|
}
|
||||||
return path;
|
return path;
|
||||||
} catch {
|
} catch {
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import {
|
|||||||
} from '@chakra-ui/react';
|
} from '@chakra-ui/react';
|
||||||
import { Calendar, Image as ImageIcon, ExternalLink } from 'lucide-react';
|
import { Calendar, Image as ImageIcon, ExternalLink } from 'lucide-react';
|
||||||
import MainLayout from '../components/layout/MainLayout';
|
import MainLayout from '../components/layout/MainLayout';
|
||||||
|
import { API_URL } from '../services/api';
|
||||||
import SponsorsSection from '../components/common/SponsorsSection';
|
import SponsorsSection from '../components/common/SponsorsSection';
|
||||||
import NewsletterCTA from '../components/common/NewsletterCTA';
|
import NewsletterCTA from '../components/common/NewsletterCTA';
|
||||||
|
|
||||||
@@ -38,9 +39,8 @@ const resolveBackendUrl = (path: string) => {
|
|||||||
try {
|
try {
|
||||||
if (/^https?:\/\//i.test(path)) return path;
|
if (/^https?:\/\//i.test(path)) return path;
|
||||||
if (path.startsWith('/cache') || path.startsWith('/uploads') || path.startsWith('/api/')) {
|
if (path.startsWith('/cache') || path.startsWith('/uploads') || path.startsWith('/api/')) {
|
||||||
const base = (process.env.REACT_APP_API_BASE_URL || process.env.REACT_APP_API_URL || 'http://localhost:8080/api/v1');
|
const origin = new URL(API_URL, typeof window !== 'undefined' ? window.location.origin : 'http://localhost:3000').origin;
|
||||||
const b = new URL(base);
|
const abs = new URL(path, origin);
|
||||||
const abs = new URL(path, `${b.protocol}//${b.host}`);
|
|
||||||
return abs.toString();
|
return abs.toString();
|
||||||
}
|
}
|
||||||
return path;
|
return path;
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import MyUIbrixErrorBoundary from '../components/editor/MyUIbrixErrorBoundary';
|
|||||||
import ClubModal from '../components/home/ClubModal';
|
import ClubModal from '../components/home/ClubModal';
|
||||||
import MatchModal from '../components/home/MatchModal';
|
import MatchModal from '../components/home/MatchModal';
|
||||||
import { useAllPageElementConfigs } from '../hooks/usePageElementConfig';
|
import { useAllPageElementConfigs } from '../hooks/usePageElementConfig';
|
||||||
|
import { API_URL } from '../services/api';
|
||||||
|
|
||||||
// Types for real API-driven data
|
// Types for real API-driven data
|
||||||
type NewsItem = {
|
type NewsItem = {
|
||||||
@@ -132,11 +133,8 @@ const HomePage: React.FC = () => {
|
|||||||
try {
|
try {
|
||||||
if (/^https?:\/\//i.test(path)) return path;
|
if (/^https?:\/\//i.test(path)) return path;
|
||||||
if (path.startsWith('/cache') || path.startsWith('/uploads') || path.startsWith('/api/')) {
|
if (path.startsWith('/cache') || path.startsWith('/uploads') || path.startsWith('/api/')) {
|
||||||
const base = process.env.REACT_APP_API_BASE_URL || process.env.REACT_APP_API_URL || 'http://localhost:8080/api/v1';
|
const origin = new URL(API_URL, typeof window !== 'undefined' ? window.location.origin : 'http://localhost:3000').origin;
|
||||||
const u = new URL(base);
|
return new URL(path, origin).toString();
|
||||||
// We want backend origin root, not /api/v1
|
|
||||||
u.pathname = path;
|
|
||||||
return u.toString();
|
|
||||||
}
|
}
|
||||||
return path;
|
return path;
|
||||||
} catch {
|
} catch {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { useQuery } from '@tanstack/react-query';
|
|||||||
import { getCompetitionAliasesPublic } from '../services/competitionAliases';
|
import { getCompetitionAliasesPublic } from '../services/competitionAliases';
|
||||||
import SponsorsSection from '../components/common/SponsorsSection';
|
import SponsorsSection from '../components/common/SponsorsSection';
|
||||||
import NewsletterCTA from '../components/common/NewsletterCTA';
|
import NewsletterCTA from '../components/common/NewsletterCTA';
|
||||||
|
import { API_URL } from '../services/api';
|
||||||
|
|
||||||
interface MatchItem {
|
interface MatchItem {
|
||||||
id: string | number;
|
id: string | number;
|
||||||
@@ -26,10 +27,8 @@ const resolveBackendUrl = (path: string) => {
|
|||||||
try {
|
try {
|
||||||
if (/^https?:\/\//i.test(path)) return path;
|
if (/^https?:\/\//i.test(path)) return path;
|
||||||
if (path.startsWith('/cache') || path.startsWith('/uploads') || path.startsWith('/api/')) {
|
if (path.startsWith('/cache') || path.startsWith('/uploads') || path.startsWith('/api/')) {
|
||||||
const base = process.env.REACT_APP_API_BASE_URL || process.env.REACT_APP_API_URL || 'http://localhost:8080/api/v1';
|
const origin = new URL(API_URL, typeof window !== 'undefined' ? window.location.origin : 'http://localhost:3000').origin;
|
||||||
const u = new URL(base);
|
return new URL(path, origin).toString();
|
||||||
u.pathname = path; // use backend origin root
|
|
||||||
return u.toString();
|
|
||||||
}
|
}
|
||||||
return path;
|
return path;
|
||||||
} catch {
|
} catch {
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import { assetUrl } from '../../utils/url';
|
|||||||
import { getPublicSettings } from '../../services/settings';
|
import { getPublicSettings } from '../../services/settings';
|
||||||
import { getZoneramaManifestWithFallbacks, getZoneramaAlbum, putZoneramaPick, saveAlbumToCache } from '../../services/zonerama';
|
import { getZoneramaManifestWithFallbacks, getZoneramaAlbum, putZoneramaPick, saveAlbumToCache } from '../../services/zonerama';
|
||||||
import { facrApi } from '../../services/facr/facrApi';
|
import { facrApi } from '../../services/facr/facrApi';
|
||||||
|
import { API_URL } from '../../services/api';
|
||||||
import AlbumPhotoPicker from '../../components/admin/AlbumPhotoPicker';
|
import AlbumPhotoPicker from '../../components/admin/AlbumPhotoPicker';
|
||||||
import PollLinker from '../../components/admin/PollLinker';
|
import PollLinker from '../../components/admin/PollLinker';
|
||||||
import ThumbnailPreview from '../../components/common/ThumbnailPreview';
|
import ThumbnailPreview from '../../components/common/ThumbnailPreview';
|
||||||
@@ -49,8 +50,7 @@ const MatchLinkBadge: React.FC<{ articleId: number }> = ({ articleId }) => {
|
|||||||
retry: false,
|
retry: false,
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
try {
|
try {
|
||||||
const apiUrl = process.env.REACT_APP_API_URL || 'http://localhost:8080/api/v1';
|
const origin = new URL(API_URL, typeof window !== 'undefined' ? window.location.origin : 'http://localhost:3000').origin;
|
||||||
const origin = new URL(apiUrl).origin;
|
|
||||||
const url = `${origin}/cache/prefetch/facr_club_info.json`;
|
const url = `${origin}/cache/prefetch/facr_club_info.json`;
|
||||||
const res = await fetch(url, { cache: 'no-cache' });
|
const res = await fetch(url, { cache: 'no-cache' });
|
||||||
if (!res.ok) return null;
|
if (!res.ok) return null;
|
||||||
@@ -188,8 +188,7 @@ const ArticlesAdminPage = () => {
|
|||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
(async () => {
|
(async () => {
|
||||||
try {
|
try {
|
||||||
const apiUrl = process.env.REACT_APP_API_URL || 'http://localhost:8080/api/v1';
|
const origin = new URL(API_URL, typeof window !== 'undefined' ? window.location.origin : 'http://localhost:3000').origin;
|
||||||
const origin = new URL(apiUrl).origin;
|
|
||||||
const url = `${origin}/cache/prefetch/facr_club_info.json`;
|
const url = `${origin}/cache/prefetch/facr_club_info.json`;
|
||||||
const res = await fetch(url, { headers: { 'Cache-Control': 'no-cache' } });
|
const res = await fetch(url, { headers: { 'Cache-Control': 'no-cache' } });
|
||||||
if (!res.ok) return;
|
if (!res.ok) return;
|
||||||
@@ -495,13 +494,12 @@ const ArticlesAdminPage = () => {
|
|||||||
try {
|
try {
|
||||||
setZLoading(true);
|
setZLoading(true);
|
||||||
// Use correct API endpoint format based on album-api.md
|
// Use correct API endpoint format based on album-api.md
|
||||||
const apiUrl = process.env.REACT_APP_API_URL || 'http://localhost:8080/api/v1';
|
|
||||||
const params = new URLSearchParams({
|
const params = new URLSearchParams({
|
||||||
link: link,
|
link: link,
|
||||||
photo_limit: '48',
|
photo_limit: '48',
|
||||||
rendered: 'true'
|
rendered: 'true'
|
||||||
});
|
});
|
||||||
const res = await fetch(`${apiUrl}/zonerama-album?${params.toString()}`);
|
const res = await fetch(`${API_URL}/zonerama-album?${params.toString()}`);
|
||||||
if (!res.ok) throw new Error('Failed to fetch album');
|
if (!res.ok) throw new Error('Failed to fetch album');
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
|
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ import {
|
|||||||
} from '../../services/competitionAliases';
|
} from '../../services/competitionAliases';
|
||||||
import AdminLayout from '../../layouts/AdminLayout';
|
import AdminLayout from '../../layouts/AdminLayout';
|
||||||
import { PageHeader } from '../../components/admin/PageHeader';
|
import { PageHeader } from '../../components/admin/PageHeader';
|
||||||
|
import { API_URL } from '../../services/api';
|
||||||
|
|
||||||
const CompetitionAliasesAdminPage: React.FC = () => {
|
const CompetitionAliasesAdminPage: React.FC = () => {
|
||||||
const cardBg = useColorModeValue('white', 'gray.800');
|
const cardBg = useColorModeValue('white', 'gray.800');
|
||||||
@@ -79,10 +80,8 @@ const CompetitionAliasesAdminPage: React.FC = () => {
|
|||||||
try {
|
try {
|
||||||
if (/^https?:\/\//i.test(path)) return path;
|
if (/^https?:\/\//i.test(path)) return path;
|
||||||
if (path.startsWith('/cache') || path.startsWith('/uploads')) {
|
if (path.startsWith('/cache') || path.startsWith('/uploads')) {
|
||||||
const base = (process.env.REACT_APP_API_BASE_URL || process.env.REACT_APP_API_URL || 'http://localhost:8080/api/v1');
|
const origin = new URL(API_URL, typeof window !== 'undefined' ? window.location.origin : 'http://localhost:3000').origin;
|
||||||
const u = new URL(base);
|
return new URL(path, origin).toString();
|
||||||
u.pathname = path;
|
|
||||||
return u.toString();
|
|
||||||
}
|
}
|
||||||
return path;
|
return path;
|
||||||
} catch {
|
} catch {
|
||||||
@@ -124,10 +123,8 @@ const CompetitionAliasesAdminPage: React.FC = () => {
|
|||||||
try {
|
try {
|
||||||
if (/^https?:\/\//i.test(path)) return path;
|
if (/^https?:\/\//i.test(path)) return path;
|
||||||
if (path.startsWith('/cache') || path.startsWith('/uploads')) {
|
if (path.startsWith('/cache') || path.startsWith('/uploads')) {
|
||||||
const base = (process.env.REACT_APP_API_BASE_URL || process.env.REACT_APP_API_URL || 'http://localhost:8080/api/v1');
|
const origin = new URL(API_URL, typeof window !== 'undefined' ? window.location.origin : 'http://localhost:3000').origin;
|
||||||
const u = new URL(base);
|
return new URL(path, origin).toString();
|
||||||
u.pathname = path;
|
|
||||||
return u.toString();
|
|
||||||
}
|
}
|
||||||
return path;
|
return path;
|
||||||
} catch {
|
} catch {
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ import {
|
|||||||
formatFileSize,
|
formatFileSize,
|
||||||
getFileIcon,
|
getFileIcon,
|
||||||
} from '../../services/files';
|
} from '../../services/files';
|
||||||
|
import { API_URL } from '../../services/api';
|
||||||
|
|
||||||
const FilesAdminPage: React.FC = () => {
|
const FilesAdminPage: React.FC = () => {
|
||||||
const toast = useToast();
|
const toast = useToast();
|
||||||
@@ -187,8 +188,7 @@ const FilesAdminPage: React.FC = () => {
|
|||||||
|
|
||||||
const getImageUrl = (url: string) => {
|
const getImageUrl = (url: string) => {
|
||||||
if (url.startsWith('http')) return url;
|
if (url.startsWith('http')) return url;
|
||||||
const apiUrl = process.env.REACT_APP_API_URL || 'http://localhost:8080/api/v1';
|
const origin = new URL(API_URL, typeof window !== 'undefined' ? window.location.origin : 'http://localhost:3000').origin;
|
||||||
const origin = new URL(apiUrl).origin;
|
|
||||||
return `${origin}${url}`;
|
return `${origin}${url}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ import { useSearchParams } from 'react-router-dom';
|
|||||||
import { parse } from 'date-fns';
|
import { parse } from 'date-fns';
|
||||||
import { assetUrl } from '../../utils/url';
|
import { assetUrl } from '../../utils/url';
|
||||||
import { batchFetchLogosFromSportLogosAPI } from '../../utils/sportLogosAPI';
|
import { batchFetchLogosFromSportLogosAPI } from '../../utils/sportLogosAPI';
|
||||||
|
import { API_URL } from '../../services/api';
|
||||||
|
|
||||||
const MatchesAdminPage = () => {
|
const MatchesAdminPage = () => {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
@@ -155,8 +156,7 @@ const MatchesAdminPage = () => {
|
|||||||
queryKey: ['admin-matches-list-cache'],
|
queryKey: ['admin-matches-list-cache'],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
// Read cached FACR club info
|
// Read cached FACR club info
|
||||||
const apiUrl = process.env.REACT_APP_API_URL || 'http://localhost:8080/api/v1';
|
const origin = new URL(API_URL, typeof window !== 'undefined' ? window.location.origin : 'http://localhost:3000').origin;
|
||||||
const origin = new URL(apiUrl).origin;
|
|
||||||
const url = `${origin}/cache/prefetch/facr_club_info.json`;
|
const url = `${origin}/cache/prefetch/facr_club_info.json`;
|
||||||
const res = await fetch(url, { headers: { 'Cache-Control': 'no-cache' } });
|
const res = await fetch(url, { headers: { 'Cache-Control': 'no-cache' } });
|
||||||
if (!res.ok) throw new Error(`Failed to load cache: ${res.status}`);
|
if (!res.ok) throw new Error(`Failed to load cache: ${res.status}`);
|
||||||
@@ -225,8 +225,7 @@ const MatchesAdminPage = () => {
|
|||||||
const { data: facrClubInfo } = useQuery({
|
const { data: facrClubInfo } = useQuery({
|
||||||
queryKey: ['facr-club-info-name'],
|
queryKey: ['facr-club-info-name'],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const apiUrl = process.env.REACT_APP_API_URL || 'http://localhost:8080/api/v1';
|
const origin = new URL(API_URL, typeof window !== 'undefined' ? window.location.origin : 'http://localhost:3000').origin;
|
||||||
const origin = new URL(apiUrl).origin;
|
|
||||||
const url = `${origin}/cache/prefetch/facr_club_info.json`;
|
const url = `${origin}/cache/prefetch/facr_club_info.json`;
|
||||||
const res = await fetch(url, { headers: { 'Cache-Control': 'no-cache' } });
|
const res = await fetch(url, { headers: { 'Cache-Control': 'no-cache' } });
|
||||||
if (!res.ok) return null;
|
if (!res.ok) return null;
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ import { Player, getPlayers, createPlayer, updatePlayer, deletePlayer } from '..
|
|||||||
import { uploadFile } from '../../services/articles';
|
import { uploadFile } from '../../services/articles';
|
||||||
import { translateNationality } from '../../utils/nationality';
|
import { translateNationality } from '../../utils/nationality';
|
||||||
import ThumbnailPreview from '../../components/common/ThumbnailPreview';
|
import ThumbnailPreview from '../../components/common/ThumbnailPreview';
|
||||||
|
import { API_URL } from '../../services/api';
|
||||||
|
|
||||||
type Editing = Partial<Player> & { id?: number };
|
type Editing = Partial<Player> & { id?: number };
|
||||||
|
|
||||||
@@ -55,8 +56,7 @@ const PlayersAdminPage: React.FC = () => {
|
|||||||
// If it's already absolute, return as-is
|
// If it's already absolute, return as-is
|
||||||
if (/^https?:\/\//i.test(url)) return url;
|
if (/^https?:\/\//i.test(url)) return url;
|
||||||
// If it's an uploads path, prefix with API origin
|
// If it's an uploads path, prefix with API origin
|
||||||
const apiUrl = process.env.REACT_APP_API_URL || 'http://localhost:8080/api/v1';
|
const origin = new URL(API_URL, typeof window !== 'undefined' ? window.location.origin : 'http://localhost:3000').origin;
|
||||||
const origin = new URL(apiUrl).origin;
|
|
||||||
if (url.startsWith('/uploads/')) return `${origin}${url}`;
|
if (url.startsWith('/uploads/')) return `${origin}${url}`;
|
||||||
// Fallback: treat as relative to origin
|
// Fallback: treat as relative to origin
|
||||||
return `${origin}${url.startsWith('/') ? '' : '/'}${url}`;
|
return `${origin}${url.startsWith('/') ? '' : '/'}${url}`;
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ import { FiEdit2, FiPlus, FiTrash2, FiUpload, FiExternalLink } from 'react-icons
|
|||||||
import AdminLayout from '../../layouts/AdminLayout';
|
import AdminLayout from '../../layouts/AdminLayout';
|
||||||
import { Sponsor, getSponsors, createSponsor, updateSponsor, deleteSponsor } from '../../services/sponsors';
|
import { Sponsor, getSponsors, createSponsor, updateSponsor, deleteSponsor } from '../../services/sponsors';
|
||||||
import { uploadFile } from '../../services/articles';
|
import { uploadFile } from '../../services/articles';
|
||||||
|
import { API_URL } from '../../services/api';
|
||||||
|
|
||||||
const SponsorsAdminPage: React.FC = () => {
|
const SponsorsAdminPage: React.FC = () => {
|
||||||
const cardBg = useColorModeValue('white', 'gray.800');
|
const cardBg = useColorModeValue('white', 'gray.800');
|
||||||
@@ -47,8 +48,7 @@ const SponsorsAdminPage: React.FC = () => {
|
|||||||
const normalizeImageUrl = (url?: string) => {
|
const normalizeImageUrl = (url?: string) => {
|
||||||
if (!url || url === '') return '/logo192.png';
|
if (!url || url === '') return '/logo192.png';
|
||||||
if (/^https?:\/\//i.test(url)) return url;
|
if (/^https?:\/\//i.test(url)) return url;
|
||||||
const apiUrl = process.env.REACT_APP_API_URL || 'http://localhost:8080/api/v1';
|
const origin = new URL(API_URL, typeof window !== 'undefined' ? window.location.origin : 'http://localhost:3000').origin;
|
||||||
const origin = new URL(apiUrl).origin;
|
|
||||||
if (url.startsWith('/uploads/')) return `${origin}${url}`;
|
if (url.startsWith('/uploads/')) return `${origin}${url}`;
|
||||||
return `${origin}${url.startsWith('/') ? '' : '/'}${url}`;
|
return `${origin}${url.startsWith('/') ? '' : '/'}${url}`;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { useMemo, useState } from 'react';
|
|||||||
import AdminLayout from '../../layouts/AdminLayout';
|
import AdminLayout from '../../layouts/AdminLayout';
|
||||||
import { assetUrl } from '../../utils/url';
|
import { assetUrl } from '../../utils/url';
|
||||||
import { TeamLogo } from '../../components/common/TeamLogo';
|
import { TeamLogo } from '../../components/common/TeamLogo';
|
||||||
|
import { API_URL } from '../../services/api';
|
||||||
|
|
||||||
type TableRow = {
|
type TableRow = {
|
||||||
rank?: string;
|
rank?: string;
|
||||||
@@ -22,8 +23,7 @@ const StandingsAdminPage: React.FC = () => {
|
|||||||
const { data, isLoading, error } = useQuery<any>({
|
const { data, isLoading, error } = useQuery<any>({
|
||||||
queryKey: ['facr-tables-cache'],
|
queryKey: ['facr-tables-cache'],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const apiUrl = process.env.REACT_APP_API_URL || 'http://localhost:8080/api/v1';
|
const origin = new URL(API_URL, typeof window !== 'undefined' ? window.location.origin : 'http://localhost:3000').origin;
|
||||||
const origin = new URL(apiUrl).origin;
|
|
||||||
const url = `${origin}/cache/prefetch/facr_tables.json`;
|
const url = `${origin}/cache/prefetch/facr_tables.json`;
|
||||||
const res = await fetch(url, { headers: { 'Cache-Control': 'no-cache' } });
|
const res = await fetch(url, { headers: { 'Cache-Control': 'no-cache' } });
|
||||||
if (!res.ok) throw new Error(`Failed to load cache: ${res.status}`);
|
if (!res.ok) throw new Error(`Failed to load cache: ${res.status}`);
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ import { searchClubs, uploadImage, putTeamLogoOverride, fetchTeamLogoOverrides,
|
|||||||
import { getFacrTablesCache } from '../../services/facr/cache';
|
import { getFacrTablesCache } from '../../services/facr/cache';
|
||||||
import { assetUrl } from '../../utils/url';
|
import { assetUrl } from '../../utils/url';
|
||||||
import { useEffect, useMemo, useRef, useState } from 'react';
|
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||||
|
import { API_URL } from '../../services/api';
|
||||||
|
|
||||||
|
|
||||||
type TableRow = {
|
type TableRow = {
|
||||||
@@ -77,8 +78,7 @@ const TeamsAdminPage = () => {
|
|||||||
|
|
||||||
const competitions: any[] = Array.isArray(data?.competitions) ? data!.competitions : [];
|
const competitions: any[] = Array.isArray(data?.competitions) ? data!.competitions : [];
|
||||||
// Backend origin (used to resolve relative URLs like /uploads/...)
|
// Backend origin (used to resolve relative URLs like /uploads/...)
|
||||||
const apiUrl = process.env.REACT_APP_API_URL || 'http://localhost:8080/api/v1';
|
const backendOrigin = new URL(API_URL, typeof window !== 'undefined' ? window.location.origin : 'http://localhost:3000').origin;
|
||||||
const backendOrigin = new URL(apiUrl).origin;
|
|
||||||
|
|
||||||
// Load public/admin overrides map to apply on cache-fed view
|
// Load public/admin overrides map to apply on cache-fed view
|
||||||
const { data: overrides = {} } = useQuery({
|
const { data: overrides = {} } = useQuery({
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
|
import { API_URL as API_BASE_URL } from './api';
|
||||||
const API_BASE_URL = process.env.REACT_APP_API_BASE_URL || process.env.REACT_APP_API_URL || 'http://localhost:8080/api/v1';
|
|
||||||
|
|
||||||
export interface ClothingItem {
|
export interface ClothingItem {
|
||||||
id: number;
|
id: number;
|
||||||
|
|||||||
@@ -4,10 +4,8 @@ import { API_URL } from '../api';
|
|||||||
function resolveBackend(path: string): string {
|
function resolveBackend(path: string): string {
|
||||||
try {
|
try {
|
||||||
if (/^https?:\/\//i.test(path)) return path;
|
if (/^https?:\/\//i.test(path)) return path;
|
||||||
const base = process.env.REACT_APP_API_BASE_URL || process.env.REACT_APP_API_URL || 'http://localhost:8080/api/v1';
|
const origin = new URL(API_URL, typeof window !== 'undefined' ? window.location.origin : 'http://localhost:3000').origin;
|
||||||
const u = new URL(base);
|
return new URL(path.startsWith('/') ? path : `/${path}`, origin).toString();
|
||||||
u.pathname = path.startsWith('/') ? path : `/${path}`;
|
|
||||||
return u.toString();
|
|
||||||
} catch {
|
} catch {
|
||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ const cache = new Map<string, CacheItem<any>>();
|
|||||||
|
|
||||||
// Create axios instance with base URL from environment variables
|
// Create axios instance with base URL from environment variables
|
||||||
const apiClient: AxiosInstance = axios.create({
|
const apiClient: AxiosInstance = axios.create({
|
||||||
baseURL: process.env.REACT_APP_FACR_API_BASE_URL || 'http://localhost:8080/api/v1/facr',
|
baseURL: process.env.REACT_APP_FACR_API_BASE_URL || '/api/v1/facr',
|
||||||
timeout: parseInt(process.env.REACT_APP_FACR_API_TIMEOUT || '20000', 10),
|
timeout: parseInt(process.env.REACT_APP_FACR_API_TIMEOUT || '20000', 10),
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
@@ -30,10 +30,12 @@ const resolveBackendUrl = (path: string) => {
|
|||||||
try {
|
try {
|
||||||
if (/^https?:\/\//i.test(path)) return path;
|
if (/^https?:\/\//i.test(path)) return path;
|
||||||
if (path.startsWith('/cache') || path.startsWith('/uploads') || path.startsWith('/api/')) {
|
if (path.startsWith('/cache') || path.startsWith('/uploads') || path.startsWith('/api/')) {
|
||||||
const base = process.env.REACT_APP_API_BASE_URL || process.env.REACT_APP_API_URL || 'http://localhost:8080/api/v1';
|
const explicit = process.env.REACT_APP_API_BASE_URL || process.env.REACT_APP_API_URL || '';
|
||||||
const baseOrigin = new URL(base).origin;
|
const origin = explicit
|
||||||
|
? new URL(explicit, typeof window !== 'undefined' ? window.location.origin : 'http://localhost:3000').origin
|
||||||
|
: (typeof window !== 'undefined' ? window.location.origin : 'http://localhost:3000');
|
||||||
// Use URL constructor so query strings in `path` (e.g. /api/v1/x?t=123) are handled correctly
|
// Use URL constructor so query strings in `path` (e.g. /api/v1/x?t=123) are handled correctly
|
||||||
return new URL(path, baseOrigin).toString();
|
return new URL(path, origin).toString();
|
||||||
}
|
}
|
||||||
return path;
|
return path;
|
||||||
} catch {
|
} catch {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
|
import { API_URL } from './api';
|
||||||
|
|
||||||
const API_URL = process.env.REACT_APP_API_URL || 'http://localhost:8080/api/v1';
|
// Use shared API_URL which already resolves to '/api/v1' under current origin
|
||||||
|
|
||||||
export interface FileInfo {
|
export interface FileInfo {
|
||||||
id: number;
|
id: number;
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
|
import { API_URL as API_BASE_URL } from './api';
|
||||||
const API_BASE_URL = process.env.REACT_APP_API_URL || 'http://localhost:8080/api/v1';
|
|
||||||
|
|
||||||
export interface NavigationItem {
|
export interface NavigationItem {
|
||||||
id?: number;
|
id?: number;
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
|
import { API_URL as API_BASE_URL } from './api';
|
||||||
import { IconType } from 'react-icons';
|
import { IconType } from 'react-icons';
|
||||||
import {
|
import {
|
||||||
FaRegClipboard,
|
FaRegClipboard,
|
||||||
@@ -35,7 +36,7 @@ import {
|
|||||||
FaCube
|
FaCube
|
||||||
} from 'react-icons/fa';
|
} from 'react-icons/fa';
|
||||||
|
|
||||||
const API_BASE_URL = process.env.REACT_APP_API_URL || 'http://localhost:8080/api/v1';
|
// Use shared API base URL
|
||||||
|
|
||||||
export interface PageElementConfig {
|
export interface PageElementConfig {
|
||||||
id?: number;
|
id?: number;
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { assetUrl } from '../utils/url';
|
import { assetUrl } from '../utils/url';
|
||||||
|
import { API_URL } from './api';
|
||||||
|
|
||||||
export interface RelatedClub {
|
export interface RelatedClub {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -21,8 +22,7 @@ const resolveBackendUrl = (path: string): string => {
|
|||||||
try {
|
try {
|
||||||
if (/^https?:\/\//i.test(path)) return path;
|
if (/^https?:\/\//i.test(path)) return path;
|
||||||
if (path.startsWith('/cache') || path.startsWith('/uploads') || path.startsWith('/api/')) {
|
if (path.startsWith('/cache') || path.startsWith('/uploads') || path.startsWith('/api/')) {
|
||||||
const base = process.env.REACT_APP_API_BASE_URL || process.env.REACT_APP_API_URL || 'http://localhost:8080/api/v1';
|
const origin = new URL(API_URL, typeof window !== 'undefined' ? window.location.origin : 'http://localhost:3000').origin;
|
||||||
const origin = new URL(base).origin;
|
|
||||||
return new URL(path, origin).toString();
|
return new URL(path, origin).toString();
|
||||||
}
|
}
|
||||||
return path;
|
return path;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import api from './api';
|
import api, { API_URL } from './api';
|
||||||
import { getArticles } from './articles';
|
import { getArticles } from './articles';
|
||||||
import { getPlayers } from './public';
|
import { getPlayers } from './public';
|
||||||
import { getUpcomingEvents } from './eventService';
|
import { getUpcomingEvents } from './eventService';
|
||||||
@@ -96,8 +96,7 @@ const resolveBackendUrl = (path: string): string => {
|
|||||||
try {
|
try {
|
||||||
if (/^https?:\/\//i.test(path)) return path;
|
if (/^https?:\/\//i.test(path)) return path;
|
||||||
if (path.startsWith('/cache') || path.startsWith('/uploads') || path.startsWith('/api/')) {
|
if (path.startsWith('/cache') || path.startsWith('/uploads') || path.startsWith('/api/')) {
|
||||||
const base = process.env.REACT_APP_API_BASE_URL || process.env.REACT_APP_API_URL || 'http://localhost:8080/api/v1';
|
const origin = new URL(API_URL, typeof window !== 'undefined' ? window.location.origin : 'http://localhost:3000').origin;
|
||||||
const origin = new URL(base).origin;
|
|
||||||
return new URL(path, origin).toString();
|
return new URL(path, origin).toString();
|
||||||
}
|
}
|
||||||
return path;
|
return path;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import api from './api';
|
import api, { API_URL } from './api';
|
||||||
|
|
||||||
export type YouTubeVideo = {
|
export type YouTubeVideo = {
|
||||||
video_id: string;
|
video_id: string;
|
||||||
@@ -38,12 +38,9 @@ export const getCachedYouTube = async (): Promise<YouTubeChannelPayload | null>
|
|||||||
// Helper: fetch static cached JSON from /cache/prefetch
|
// Helper: fetch static cached JSON from /cache/prefetch
|
||||||
const fetchStaticYouTubeCache = async (): Promise<YouTubeChannelPayload | null> => {
|
const fetchStaticYouTubeCache = async (): Promise<YouTubeChannelPayload | null> => {
|
||||||
try {
|
try {
|
||||||
// Determine backend origin from env config similar to HomePage resolve logic
|
const origin = new URL(API_URL, typeof window !== 'undefined' ? window.location.origin : 'http://localhost:3000').origin;
|
||||||
const base = (process.env.REACT_APP_API_BASE_URL || process.env.REACT_APP_API_URL || 'http://localhost:8080/api/v1');
|
const url = `${origin}/cache/prefetch/youtube_channel.json`;
|
||||||
const u = new URL(base);
|
const resp = await fetch(url, { cache: 'no-cache' });
|
||||||
// Force path to backend-exposed cache file
|
|
||||||
u.pathname = '/cache/prefetch/youtube_channel.json';
|
|
||||||
const resp = await fetch(u.toString(), { cache: 'no-cache' });
|
|
||||||
if (!resp.ok) return null;
|
if (!resp.ok) return null;
|
||||||
const data = (await resp.json()) as YouTubeChannelPayload;
|
const data = (await resp.json()) as YouTubeChannelPayload;
|
||||||
return sortByPublishedDate(data);
|
return sortByPublishedDate(data);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import api from './api';
|
import api, { API_URL } from './api';
|
||||||
|
|
||||||
export interface ZoneramaPhoto {
|
export interface ZoneramaPhoto {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -60,8 +60,7 @@ export async function saveAlbumToCache(albumLink: string, photoLimit: number = 5
|
|||||||
|
|
||||||
// Helper to read the flat manifest produced by the prefetcher for fast grid rendering
|
// Helper to read the flat manifest produced by the prefetcher for fast grid rendering
|
||||||
export async function getZoneramaManifest(): Promise<Array<{ id: string; album_id: string; src: string; local: string; page_url: string }>> {
|
export async function getZoneramaManifest(): Promise<Array<{ id: string; album_id: string; src: string; local: string; page_url: string }>> {
|
||||||
const apiUrl = process.env.REACT_APP_API_URL || 'http://localhost:8080/api/v1';
|
const origin = new URL(API_URL, typeof window !== 'undefined' ? window.location.origin : 'http://localhost:3000').origin;
|
||||||
const origin = new URL(apiUrl).origin;
|
|
||||||
// New unified path for prefetched Zonerama items
|
// New unified path for prefetched Zonerama items
|
||||||
const url = `${origin}/cache/prefetch/zonerama_flat.json`;
|
const url = `${origin}/cache/prefetch/zonerama_flat.json`;
|
||||||
const res = await fetch(url, { cache: 'no-cache' });
|
const res = await fetch(url, { cache: 'no-cache' });
|
||||||
@@ -81,8 +80,7 @@ export async function getZoneramaManifestWithFallbacks(): Promise<Array<{ id: st
|
|||||||
const primary = await getZoneramaManifest();
|
const primary = await getZoneramaManifest();
|
||||||
if (primary && primary.length > 0) return primary;
|
if (primary && primary.length > 0) return primary;
|
||||||
|
|
||||||
const apiUrl = process.env.REACT_APP_API_URL || 'http://localhost:8080/api/v1';
|
const origin = new URL(API_URL, typeof window !== 'undefined' ? window.location.origin : 'http://localhost:3000').origin;
|
||||||
const origin = new URL(apiUrl).origin;
|
|
||||||
|
|
||||||
// 1b) Backward-compat removed - old path no longer used to avoid 404 errors
|
// 1b) Backward-compat removed - old path no longer used to avoid 404 errors
|
||||||
|
|
||||||
|
|||||||
@@ -43,8 +43,9 @@ export const setHasAdmin = (value: boolean): void => {
|
|||||||
|
|
||||||
export const checkAdminExists = async (): Promise<boolean> => {
|
export const checkAdminExists = async (): Promise<boolean> => {
|
||||||
try {
|
try {
|
||||||
const base = process.env.REACT_APP_API_URL || 'http://localhost:8080/api/v1';
|
// Use shared API base URL which is normalized to '/api/v1'
|
||||||
const response = await fetch(`${base}/auth/admin/exists`, {
|
const { API_URL } = await import('../services/api');
|
||||||
|
const response = await fetch(`${API_URL}/auth/admin/exists`, {
|
||||||
headers: { 'Accept': 'application/json' },
|
headers: { 'Accept': 'application/json' },
|
||||||
});
|
});
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
|
|||||||
Generated
+14
-1
@@ -5,7 +5,8 @@
|
|||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"react-markdown": "^10.1.0"
|
"react-markdown": "^10.1.0",
|
||||||
|
"typescript": "^5.9.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/geojson": "^7946.0.16"
|
"@types/geojson": "^7946.0.16"
|
||||||
@@ -1123,6 +1124,18 @@
|
|||||||
"url": "https://github.com/sponsors/wooorm"
|
"url": "https://github.com/sponsors/wooorm"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/typescript": {
|
||||||
|
"version": "5.9.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
|
||||||
|
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
||||||
|
"bin": {
|
||||||
|
"tsc": "bin/tsc",
|
||||||
|
"tsserver": "bin/tsserver"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.17"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/unified": {
|
"node_modules/unified": {
|
||||||
"version": "11.0.5",
|
"version": "11.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz",
|
||||||
|
|||||||
+2
-1
@@ -3,6 +3,7 @@
|
|||||||
"@types/geojson": "^7946.0.16"
|
"@types/geojson": "^7946.0.16"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"react-markdown": "^10.1.0"
|
"react-markdown": "^10.1.0",
|
||||||
|
"typescript": "^5.9.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user