mirror of
https://github.com/Dvorinka/MyClubServer.git
synced 2026-06-04 02:32:57 +00:00
@@ -20,6 +20,7 @@ import {
|
||||
} from '@chakra-ui/react';
|
||||
import { ChevronRight, ExternalLink, Calendar, Image as ImageIcon } from 'lucide-react';
|
||||
import MainLayout from '../components/layout/MainLayout';
|
||||
import { API_URL } from '../services/api';
|
||||
import PhotoModal from '../components/gallery/PhotoModal';
|
||||
|
||||
interface Photo {
|
||||
@@ -43,9 +44,8 @@ const resolveBackendUrl = (path: string) => {
|
||||
try {
|
||||
if (/^https?:\/\//i.test(path)) return path;
|
||||
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 b = new URL(base);
|
||||
const abs = new URL(path, `${b.protocol}//${b.host}`);
|
||||
const origin = new URL(API_URL, typeof window !== 'undefined' ? window.location.origin : 'http://localhost:3000').origin;
|
||||
const abs = new URL(path, origin);
|
||||
return abs.toString();
|
||||
}
|
||||
return path;
|
||||
|
||||
@@ -12,6 +12,7 @@ import { ExternalLink, ArrowRight, Eye, Clock } from 'lucide-react';
|
||||
import React from 'react';
|
||||
import { trackEvent as umamiTrackEvent, trackMatchView as umamiTrackMatchView, trackVideoPlay as umamiTrackVideoPlay, trackArticleView as umamiTrackArticleView } from '../utils/umami';
|
||||
import { assetUrl } from '../utils/url';
|
||||
import { API_URL } from '../services/api';
|
||||
|
||||
const toText = (html?: string) => {
|
||||
if (!html) return '';
|
||||
@@ -60,8 +61,7 @@ const ArticleDetailPage: React.FC = () => {
|
||||
queryKey: ['facr-cached-match', (matchLinkQuery.data as any)?.external_match_id],
|
||||
enabled: Boolean((matchLinkQuery.data as any)?.external_match_id),
|
||||
queryFn: async () => {
|
||||
const apiUrl = process.env.REACT_APP_API_URL || 'http://localhost:8080/api/v1';
|
||||
const origin = new URL(apiUrl).origin;
|
||||
const origin = new URL(API_URL, typeof window !== 'undefined' ? window.location.origin : 'http://localhost:3000').origin;
|
||||
const url = `${origin}/cache/prefetch/facr_club_info.json`;
|
||||
const res = await fetch(url, { cache: 'no-cache' });
|
||||
if (!res.ok) return null as any;
|
||||
@@ -167,8 +167,7 @@ const ArticleDetailPage: React.FC = () => {
|
||||
const toAbsoluteUploads = React.useCallback((html?: string) => {
|
||||
if (!html) return '';
|
||||
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(base).origin;
|
||||
const origin = new URL(API_URL, typeof window !== 'undefined' ? window.location.origin : 'http://localhost:3000').origin;
|
||||
// Replace src="/uploads... and href="/uploads...
|
||||
return html
|
||||
.replace(/src=("|')\s*(\/uploads\/[^"']+)("|')/g, (_m, q1, p2, q3) => `src=${q1}${origin}${p2}${q3}`)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from '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 { getArticles, Article } from '../services/articles';
|
||||
import { getArticles, Article, Paginated } from '../services/articles';
|
||||
import { Link as RouterLink } from 'react-router-dom';
|
||||
import { assetUrl } from '../utils/url';
|
||||
import MainLayout from '../components/layout/MainLayout';
|
||||
@@ -110,9 +110,15 @@ const BlogPage: React.FC = () => {
|
||||
isFetchingNextPage,
|
||||
hasNextPage,
|
||||
fetchNextPage,
|
||||
} = useInfiniteQuery(
|
||||
} = useInfiniteQuery<Paginated<Article>>(
|
||||
['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) => {
|
||||
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 ClubModal from '../components/home/ClubModal';
|
||||
import { assetUrl } from '../utils/url';
|
||||
import { API_URL } from '../services/api';
|
||||
|
||||
// Weekday headers (Czech, starting Monday)
|
||||
const WEEKDAYS_SHORT: string[] = ['Po', 'Út', 'St', 'Čt', 'Pá', 'So', 'Ne'];
|
||||
@@ -91,10 +92,8 @@ const CalendarPage: React.FC = () => {
|
||||
try {
|
||||
if (/^https?:\/\//i.test(path)) return path;
|
||||
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 u = new URL(base);
|
||||
u.pathname = path; // use backend origin root
|
||||
return u.toString();
|
||||
const origin = new URL(API_URL, typeof window !== 'undefined' ? window.location.origin : 'http://localhost:3000').origin;
|
||||
return new URL(path, origin).toString();
|
||||
}
|
||||
return path;
|
||||
} catch {
|
||||
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
} from '@chakra-ui/react';
|
||||
import { Calendar, Image as ImageIcon, ExternalLink } from 'lucide-react';
|
||||
import MainLayout from '../components/layout/MainLayout';
|
||||
import { API_URL } from '../services/api';
|
||||
import SponsorsSection from '../components/common/SponsorsSection';
|
||||
import NewsletterCTA from '../components/common/NewsletterCTA';
|
||||
|
||||
@@ -38,9 +39,8 @@ const resolveBackendUrl = (path: string) => {
|
||||
try {
|
||||
if (/^https?:\/\//i.test(path)) return path;
|
||||
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 b = new URL(base);
|
||||
const abs = new URL(path, `${b.protocol}//${b.host}`);
|
||||
const origin = new URL(API_URL, typeof window !== 'undefined' ? window.location.origin : 'http://localhost:3000').origin;
|
||||
const abs = new URL(path, origin);
|
||||
return abs.toString();
|
||||
}
|
||||
return path;
|
||||
|
||||
@@ -23,6 +23,7 @@ import MyUIbrixErrorBoundary from '../components/editor/MyUIbrixErrorBoundary';
|
||||
import ClubModal from '../components/home/ClubModal';
|
||||
import MatchModal from '../components/home/MatchModal';
|
||||
import { useAllPageElementConfigs } from '../hooks/usePageElementConfig';
|
||||
import { API_URL } from '../services/api';
|
||||
|
||||
// Types for real API-driven data
|
||||
type NewsItem = {
|
||||
@@ -132,11 +133,8 @@ const HomePage: React.FC = () => {
|
||||
try {
|
||||
if (/^https?:\/\//i.test(path)) return path;
|
||||
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 u = new URL(base);
|
||||
// We want backend origin root, not /api/v1
|
||||
u.pathname = path;
|
||||
return u.toString();
|
||||
const origin = new URL(API_URL, typeof window !== 'undefined' ? window.location.origin : 'http://localhost:3000').origin;
|
||||
return new URL(path, origin).toString();
|
||||
}
|
||||
return path;
|
||||
} catch {
|
||||
|
||||
@@ -7,6 +7,7 @@ import { useQuery } from '@tanstack/react-query';
|
||||
import { getCompetitionAliasesPublic } from '../services/competitionAliases';
|
||||
import SponsorsSection from '../components/common/SponsorsSection';
|
||||
import NewsletterCTA from '../components/common/NewsletterCTA';
|
||||
import { API_URL } from '../services/api';
|
||||
|
||||
interface MatchItem {
|
||||
id: string | number;
|
||||
@@ -26,10 +27,8 @@ const resolveBackendUrl = (path: string) => {
|
||||
try {
|
||||
if (/^https?:\/\//i.test(path)) return path;
|
||||
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 u = new URL(base);
|
||||
u.pathname = path; // use backend origin root
|
||||
return u.toString();
|
||||
const origin = new URL(API_URL, typeof window !== 'undefined' ? window.location.origin : 'http://localhost:3000').origin;
|
||||
return new URL(path, origin).toString();
|
||||
}
|
||||
return path;
|
||||
} catch {
|
||||
|
||||
@@ -20,6 +20,7 @@ import { assetUrl } from '../../utils/url';
|
||||
import { getPublicSettings } from '../../services/settings';
|
||||
import { getZoneramaManifestWithFallbacks, getZoneramaAlbum, putZoneramaPick, saveAlbumToCache } from '../../services/zonerama';
|
||||
import { facrApi } from '../../services/facr/facrApi';
|
||||
import { API_URL } from '../../services/api';
|
||||
import AlbumPhotoPicker from '../../components/admin/AlbumPhotoPicker';
|
||||
import PollLinker from '../../components/admin/PollLinker';
|
||||
import ThumbnailPreview from '../../components/common/ThumbnailPreview';
|
||||
@@ -49,8 +50,7 @@ const MatchLinkBadge: React.FC<{ articleId: number }> = ({ articleId }) => {
|
||||
retry: false,
|
||||
queryFn: async () => {
|
||||
try {
|
||||
const apiUrl = process.env.REACT_APP_API_URL || 'http://localhost:8080/api/v1';
|
||||
const origin = new URL(apiUrl).origin;
|
||||
const origin = new URL(API_URL, typeof window !== 'undefined' ? window.location.origin : 'http://localhost:3000').origin;
|
||||
const url = `${origin}/cache/prefetch/facr_club_info.json`;
|
||||
const res = await fetch(url, { cache: 'no-cache' });
|
||||
if (!res.ok) return null;
|
||||
@@ -188,8 +188,7 @@ const ArticlesAdminPage = () => {
|
||||
React.useEffect(() => {
|
||||
(async () => {
|
||||
try {
|
||||
const apiUrl = process.env.REACT_APP_API_URL || 'http://localhost:8080/api/v1';
|
||||
const origin = new URL(apiUrl).origin;
|
||||
const origin = new URL(API_URL, typeof window !== 'undefined' ? window.location.origin : 'http://localhost:3000').origin;
|
||||
const url = `${origin}/cache/prefetch/facr_club_info.json`;
|
||||
const res = await fetch(url, { headers: { 'Cache-Control': 'no-cache' } });
|
||||
if (!res.ok) return;
|
||||
@@ -495,13 +494,12 @@ const ArticlesAdminPage = () => {
|
||||
try {
|
||||
setZLoading(true);
|
||||
// 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({
|
||||
link: link,
|
||||
photo_limit: '48',
|
||||
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');
|
||||
const data = await res.json();
|
||||
|
||||
|
||||
@@ -38,6 +38,7 @@ import {
|
||||
} from '../../services/competitionAliases';
|
||||
import AdminLayout from '../../layouts/AdminLayout';
|
||||
import { PageHeader } from '../../components/admin/PageHeader';
|
||||
import { API_URL } from '../../services/api';
|
||||
|
||||
const CompetitionAliasesAdminPage: React.FC = () => {
|
||||
const cardBg = useColorModeValue('white', 'gray.800');
|
||||
@@ -79,10 +80,8 @@ const CompetitionAliasesAdminPage: React.FC = () => {
|
||||
try {
|
||||
if (/^https?:\/\//i.test(path)) return path;
|
||||
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 u = new URL(base);
|
||||
u.pathname = path;
|
||||
return u.toString();
|
||||
const origin = new URL(API_URL, typeof window !== 'undefined' ? window.location.origin : 'http://localhost:3000').origin;
|
||||
return new URL(path, origin).toString();
|
||||
}
|
||||
return path;
|
||||
} catch {
|
||||
@@ -124,10 +123,8 @@ const CompetitionAliasesAdminPage: React.FC = () => {
|
||||
try {
|
||||
if (/^https?:\/\//i.test(path)) return path;
|
||||
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 u = new URL(base);
|
||||
u.pathname = path;
|
||||
return u.toString();
|
||||
const origin = new URL(API_URL, typeof window !== 'undefined' ? window.location.origin : 'http://localhost:3000').origin;
|
||||
return new URL(path, origin).toString();
|
||||
}
|
||||
return path;
|
||||
} catch {
|
||||
|
||||
@@ -63,6 +63,7 @@ import {
|
||||
formatFileSize,
|
||||
getFileIcon,
|
||||
} from '../../services/files';
|
||||
import { API_URL } from '../../services/api';
|
||||
|
||||
const FilesAdminPage: React.FC = () => {
|
||||
const toast = useToast();
|
||||
@@ -187,8 +188,7 @@ const FilesAdminPage: React.FC = () => {
|
||||
|
||||
const getImageUrl = (url: string) => {
|
||||
if (url.startsWith('http')) return url;
|
||||
const apiUrl = process.env.REACT_APP_API_URL || 'http://localhost:8080/api/v1';
|
||||
const origin = new URL(apiUrl).origin;
|
||||
const origin = new URL(API_URL, typeof window !== 'undefined' ? window.location.origin : 'http://localhost:3000').origin;
|
||||
return `${origin}${url}`;
|
||||
};
|
||||
|
||||
|
||||
@@ -46,6 +46,7 @@ import { useSearchParams } from 'react-router-dom';
|
||||
import { parse } from 'date-fns';
|
||||
import { assetUrl } from '../../utils/url';
|
||||
import { batchFetchLogosFromSportLogosAPI } from '../../utils/sportLogosAPI';
|
||||
import { API_URL } from '../../services/api';
|
||||
|
||||
const MatchesAdminPage = () => {
|
||||
const queryClient = useQueryClient();
|
||||
@@ -155,8 +156,7 @@ const MatchesAdminPage = () => {
|
||||
queryKey: ['admin-matches-list-cache'],
|
||||
queryFn: async () => {
|
||||
// Read cached FACR club info
|
||||
const apiUrl = process.env.REACT_APP_API_URL || 'http://localhost:8080/api/v1';
|
||||
const origin = new URL(apiUrl).origin;
|
||||
const origin = new URL(API_URL, typeof window !== 'undefined' ? window.location.origin : 'http://localhost:3000').origin;
|
||||
const url = `${origin}/cache/prefetch/facr_club_info.json`;
|
||||
const res = await fetch(url, { headers: { 'Cache-Control': 'no-cache' } });
|
||||
if (!res.ok) throw new Error(`Failed to load cache: ${res.status}`);
|
||||
@@ -225,8 +225,7 @@ const MatchesAdminPage = () => {
|
||||
const { data: facrClubInfo } = useQuery({
|
||||
queryKey: ['facr-club-info-name'],
|
||||
queryFn: async () => {
|
||||
const apiUrl = process.env.REACT_APP_API_URL || 'http://localhost:8080/api/v1';
|
||||
const origin = new URL(apiUrl).origin;
|
||||
const origin = new URL(API_URL, typeof window !== 'undefined' ? window.location.origin : 'http://localhost:3000').origin;
|
||||
const url = `${origin}/cache/prefetch/facr_club_info.json`;
|
||||
const res = await fetch(url, { headers: { 'Cache-Control': 'no-cache' } });
|
||||
if (!res.ok) return null;
|
||||
|
||||
@@ -42,6 +42,7 @@ import { Player, getPlayers, createPlayer, updatePlayer, deletePlayer } from '..
|
||||
import { uploadFile } from '../../services/articles';
|
||||
import { translateNationality } from '../../utils/nationality';
|
||||
import ThumbnailPreview from '../../components/common/ThumbnailPreview';
|
||||
import { API_URL } from '../../services/api';
|
||||
|
||||
type Editing = Partial<Player> & { id?: number };
|
||||
|
||||
@@ -55,8 +56,7 @@ const PlayersAdminPage: React.FC = () => {
|
||||
// If it's already absolute, return as-is
|
||||
if (/^https?:\/\//i.test(url)) return url;
|
||||
// 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(apiUrl).origin;
|
||||
const origin = new URL(API_URL, typeof window !== 'undefined' ? window.location.origin : 'http://localhost:3000').origin;
|
||||
if (url.startsWith('/uploads/')) return `${origin}${url}`;
|
||||
// Fallback: treat as relative to origin
|
||||
return `${origin}${url.startsWith('/') ? '' : '/'}${url}`;
|
||||
|
||||
@@ -39,6 +39,7 @@ import { FiEdit2, FiPlus, FiTrash2, FiUpload, FiExternalLink } from 'react-icons
|
||||
import AdminLayout from '../../layouts/AdminLayout';
|
||||
import { Sponsor, getSponsors, createSponsor, updateSponsor, deleteSponsor } from '../../services/sponsors';
|
||||
import { uploadFile } from '../../services/articles';
|
||||
import { API_URL } from '../../services/api';
|
||||
|
||||
const SponsorsAdminPage: React.FC = () => {
|
||||
const cardBg = useColorModeValue('white', 'gray.800');
|
||||
@@ -47,8 +48,7 @@ const SponsorsAdminPage: React.FC = () => {
|
||||
const normalizeImageUrl = (url?: string) => {
|
||||
if (!url || url === '') return '/logo192.png';
|
||||
if (/^https?:\/\//i.test(url)) return url;
|
||||
const apiUrl = process.env.REACT_APP_API_URL || 'http://localhost:8080/api/v1';
|
||||
const origin = new URL(apiUrl).origin;
|
||||
const origin = new URL(API_URL, typeof window !== 'undefined' ? window.location.origin : 'http://localhost:3000').origin;
|
||||
if (url.startsWith('/uploads/')) return `${origin}${url}`;
|
||||
return `${origin}${url.startsWith('/') ? '' : '/'}${url}`;
|
||||
};
|
||||
|
||||
@@ -4,6 +4,7 @@ import { useMemo, useState } from 'react';
|
||||
import AdminLayout from '../../layouts/AdminLayout';
|
||||
import { assetUrl } from '../../utils/url';
|
||||
import { TeamLogo } from '../../components/common/TeamLogo';
|
||||
import { API_URL } from '../../services/api';
|
||||
|
||||
type TableRow = {
|
||||
rank?: string;
|
||||
@@ -22,8 +23,7 @@ const StandingsAdminPage: React.FC = () => {
|
||||
const { data, isLoading, error } = useQuery<any>({
|
||||
queryKey: ['facr-tables-cache'],
|
||||
queryFn: async () => {
|
||||
const apiUrl = process.env.REACT_APP_API_URL || 'http://localhost:8080/api/v1';
|
||||
const origin = new URL(apiUrl).origin;
|
||||
const origin = new URL(API_URL, typeof window !== 'undefined' ? window.location.origin : 'http://localhost:3000').origin;
|
||||
const url = `${origin}/cache/prefetch/facr_tables.json`;
|
||||
const res = await fetch(url, { headers: { 'Cache-Control': 'no-cache' } });
|
||||
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 { assetUrl } from '../../utils/url';
|
||||
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { API_URL } from '../../services/api';
|
||||
|
||||
|
||||
type TableRow = {
|
||||
@@ -77,8 +78,7 @@ const TeamsAdminPage = () => {
|
||||
|
||||
const competitions: any[] = Array.isArray(data?.competitions) ? data!.competitions : [];
|
||||
// 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(apiUrl).origin;
|
||||
const backendOrigin = new URL(API_URL, typeof window !== 'undefined' ? window.location.origin : 'http://localhost:3000').origin;
|
||||
|
||||
// Load public/admin overrides map to apply on cache-fed view
|
||||
const { data: overrides = {} } = useQuery({
|
||||
|
||||
Reference in New Issue
Block a user