mirror of
https://github.com/Dvorinka/MyClubServer.git
synced 2026-06-04 18:52:56 +00:00
dev day #62
This commit is contained in:
@@ -17,6 +17,7 @@ import { FONT_PAIRINGS, loadGoogleFont, getFontStyleColor } from '../config/font
|
||||
import MapLinkImporter from '../components/admin/MapLinkImporter';
|
||||
import MapStyleSelector from '../components/admin/MapStyleSelector';
|
||||
import { MapCoordinates } from '../utils/mapUrlParser';
|
||||
import { fetchLogoFromLogoAPI } from '../utils/sportLogosAPI';
|
||||
|
||||
const SetupPage: React.FC = () => {
|
||||
const [loading, setLoading] = useState(true);
|
||||
@@ -41,6 +42,8 @@ const SetupPage: React.FC = () => {
|
||||
|
||||
const resolveLogoUrl = (u?: string | null) => {
|
||||
if (!u) return undefined;
|
||||
// If it's a logoapi URL, use it directly (no proxy needed)
|
||||
if (u.includes('logoapi.sportcreative.eu')) return u;
|
||||
// If it's a backend-relative path or dist asset, use assetUrl helper
|
||||
if (u.startsWith('/uploads') || u.startsWith('/dist') || u.startsWith('/api/')) return assetUrl(u);
|
||||
// If it's an absolute remote URL, route through backend proxy to avoid CORS/hotlinking issues
|
||||
@@ -170,13 +173,28 @@ const SetupPage: React.FC = () => {
|
||||
}
|
||||
}, [selectedFont]);
|
||||
|
||||
const handleSelectClub = (item: SearchResult) => {
|
||||
setClubId(item.club_id || '');
|
||||
const handleSelectClub = async (item: SearchResult) => {
|
||||
const clubIdValue = item.club_id || '';
|
||||
setClubId(clubIdValue);
|
||||
setClubType(item.club_type || 'football');
|
||||
setClubName(item.name || '');
|
||||
setClubLogoUrl(item.logo_url || '');
|
||||
setClubUrl(item.url || '');
|
||||
setClubQuery(item.name || '');
|
||||
|
||||
// Try to fetch logo from logoapi first, fallback to FACR logo
|
||||
let logoUrl = '';
|
||||
if (clubIdValue) {
|
||||
const logoApiUrl = await fetchLogoFromLogoAPI(clubIdValue, item.name);
|
||||
if (logoApiUrl) {
|
||||
logoUrl = logoApiUrl;
|
||||
}
|
||||
}
|
||||
// Fallback to FACR logo if logoapi doesn't have it
|
||||
if (!logoUrl && item.logo_url) {
|
||||
logoUrl = item.logo_url;
|
||||
}
|
||||
setClubLogoUrl(logoUrl);
|
||||
|
||||
// Auto-fill sender display name from club name if empty
|
||||
if (!smtpFromName && item.name) {
|
||||
setSmtpFromName(item.name);
|
||||
@@ -188,8 +206,8 @@ const SetupPage: React.FC = () => {
|
||||
}
|
||||
} catch {}
|
||||
// Try to extract colors
|
||||
if (item.logo_url) {
|
||||
extractPalette(item.logo_url, 5)
|
||||
if (logoUrl) {
|
||||
extractPalette(logoUrl, 5)
|
||||
.then((colors) => {
|
||||
if (!colors || colors.length === 0) return;
|
||||
const presets = generateThemeCandidates(colors);
|
||||
@@ -304,8 +322,8 @@ const SetupPage: React.FC = () => {
|
||||
const fd = new FormData();
|
||||
fd.append('file', f);
|
||||
fd.append('preserve_quality', 'true');
|
||||
// Upload should go to the API root (usually /api/v1/upload). Use configured API_URL
|
||||
const uploadUrl = `${(API_URL || '').replace(/\/$/, '')}/upload`;
|
||||
// Upload should go to the API root (usually /api/v1/upload). Use configured API_URL
|
||||
const uploadUrl = `${(API_URL || '').replace(/\/$/, '')}/upload`;
|
||||
const res = await fetch(uploadUrl, { method: 'POST', body: fd });
|
||||
if (!res.ok) throw new Error('Upload failed');
|
||||
const data = await res.json();
|
||||
@@ -315,6 +333,25 @@ const SetupPage: React.FC = () => {
|
||||
url = parsed.pathname + parsed.search + parsed.hash;
|
||||
} catch {}
|
||||
setClubLogoUrl(url);
|
||||
|
||||
// Also upload to logoapi if we have a club ID
|
||||
if (clubId) {
|
||||
try {
|
||||
const logoFd = new FormData();
|
||||
logoFd.append('logo', f);
|
||||
const logoApiRes = await fetch(`https://logoapi.sportcreative.eu/logos/${clubId}`, {
|
||||
method: 'POST',
|
||||
body: logoFd,
|
||||
});
|
||||
if (logoApiRes.ok) {
|
||||
toast({ title: 'Logo nahráno', description: 'Logo bylo nahráno na logoapi i lokálně', status: 'success', duration: 3000 });
|
||||
}
|
||||
} catch (logoApiErr) {
|
||||
console.warn('Failed to upload to logoapi:', logoApiErr);
|
||||
// Don't fail the whole upload if logoapi fails
|
||||
}
|
||||
}
|
||||
|
||||
// Try to extract colors from uploaded logo
|
||||
try { const colors = await extractPalette(url, 5); const presets = generateThemeCandidates(colors); setThemePresets(presets); if (presets[0]) { setPrimaryColor(presets[0].primary); setSecondaryColor(presets[0].secondary); setAccentColor(presets[0].accent); setBackgroundColor(presets[0].background); setTextColor(presets[0].text); setSelectedPreset(0); } } catch {}
|
||||
} catch (e) {
|
||||
@@ -376,17 +413,28 @@ const SetupPage: React.FC = () => {
|
||||
setSelectedPreset(idx);
|
||||
};
|
||||
|
||||
// Redirect if setup not required
|
||||
useEffect(() => {
|
||||
if (!loading && !requiresSetup) {
|
||||
navigate('/login', { replace: true });
|
||||
}
|
||||
}, [loading, requiresSetup, navigate]);
|
||||
|
||||
if (loading) return <Box p={8}>Načítání…</Box>;
|
||||
if (!requiresSetup) {
|
||||
navigate('/login', { replace: true });
|
||||
return null;
|
||||
}
|
||||
|
||||
// Get selected font pairing for live preview
|
||||
const selectedFontPairing = FONT_PAIRINGS.find((f) => f.id === selectedFont);
|
||||
const fontHeading = selectedFontPairing?.cssHeading || 'inherit';
|
||||
const fontBody = selectedFontPairing?.cssBody || 'inherit';
|
||||
|
||||
return (
|
||||
<Box minH="100vh" bg="gray.50" display="flex" alignItems="center" justifyContent="center" px={8} py={8}>
|
||||
<Box as="form" onSubmit={handleSubmit} w="100%" maxW="3xl" p={8} bg={bg} borderRadius="xl" boxShadow="lg" borderWidth="1px" borderColor={borderCol}>
|
||||
<Box minH="100vh" bg="gray.50" display="flex" alignItems="center" justifyContent="center" px={8} py={8} fontFamily={fontBody}>
|
||||
<Box as="form" onSubmit={handleSubmit} w="100%" maxW="3xl" p={8} bg={bg} borderRadius="xl" boxShadow="lg" borderWidth="1px" borderColor={borderCol} fontFamily={fontBody}>
|
||||
<VStack spacing={3} mb={6} align="stretch">
|
||||
<Heading size="xl">🚀 Vítejte v nastavení vašeho webu!</Heading>
|
||||
<Heading size="xl" fontFamily={fontHeading}>🚀 Vítejte v nastavení vašeho webu!</Heading>
|
||||
<Text fontSize="md" color="gray.600">
|
||||
Nastavte základní informace o vašem klubu. Můžete vše vyplnit nyní, nebo některé údaje doplnit později v administraci.
|
||||
</Text>
|
||||
@@ -401,7 +449,7 @@ const SetupPage: React.FC = () => {
|
||||
|
||||
<SimpleGrid columns={[1, 1, 2]} spacing={6}>
|
||||
<Box>
|
||||
<Heading as="h3" size="md" mb={4}>🔐 Administrátorský účet</Heading>
|
||||
<Heading as="h3" size="md" mb={4} fontFamily={fontHeading}>🔐 Administrátorský účet</Heading>
|
||||
<VStack align="stretch" spacing={4}>
|
||||
<FormControl isRequired>
|
||||
<FormLabel>E‑mail administrátora</FormLabel>
|
||||
@@ -439,7 +487,7 @@ const SetupPage: React.FC = () => {
|
||||
</Box>
|
||||
|
||||
<Box>
|
||||
<Heading as="h3" size="md" mb={4}>⚽ Informace o klubu</Heading>
|
||||
<Heading as="h3" size="md" mb={4} fontFamily={fontHeading}>⚽ Informace o klubu</Heading>
|
||||
<VStack align="stretch" spacing={4}>
|
||||
<FormControl>
|
||||
<FormLabel>Hledat klub (FAČR)</FormLabel>
|
||||
@@ -452,7 +500,7 @@ const SetupPage: React.FC = () => {
|
||||
{clubQuery && searchResults?.length > 0 && (
|
||||
<Box mt={2} borderWidth="1px" borderRadius="md" maxH="240px" overflowY="auto">
|
||||
<List spacing={0}>
|
||||
{searchResults.slice(0, 8).map((r) => (
|
||||
{searchResults.filter((r) => r.name && r.name.trim() !== '').slice(0, 8).map((r) => (
|
||||
<ListItem
|
||||
key={`${r.club_type}-${r.club_id}`}
|
||||
px={3} py={2} _hover={{ bg: 'gray.50', cursor: 'pointer' }}
|
||||
@@ -522,33 +570,7 @@ const SetupPage: React.FC = () => {
|
||||
|
||||
<Divider my={6} />
|
||||
|
||||
<Heading as="h3" size="md" mb={2}>📱 Sociální sítě a fotogalerie</Heading>
|
||||
<Text fontSize="sm" mb={3} color="gray.600">Zadejte odkazy na profily klubu a volitelně na fotogalerii. Lze později upravit v administraci.</Text>
|
||||
<SimpleGrid columns={[1, 1, 2]} spacing={6} mb={2}>
|
||||
<FormControl>
|
||||
<FormLabel>Facebook URL</FormLabel>
|
||||
<Input placeholder="https://www.facebook.com/vas.klub" value={facebookUrl} onChange={(e) => setFacebookUrl(e.target.value)} />
|
||||
</FormControl>
|
||||
<FormControl>
|
||||
<FormLabel>Instagram URL</FormLabel>
|
||||
<Input placeholder="https://www.instagram.com/vas.klub" value={instagramUrl} onChange={(e) => setInstagramUrl(e.target.value)} />
|
||||
</FormControl>
|
||||
<FormControl>
|
||||
<FormLabel>YouTube URL</FormLabel>
|
||||
<Input placeholder="https://www.youtube.com/@vas_klub" value={youtubeUrl} onChange={(e) => setYoutubeUrl(e.target.value)} />
|
||||
</FormControl>
|
||||
<FormControl>
|
||||
<FormLabel>URL fotogalerie</FormLabel>
|
||||
<Input placeholder="https://photos.example.com/club" value={galleryUrl} onChange={(e) => setGalleryUrl(e.target.value)} />
|
||||
<FormHelperText>Můžete použít libovolný web (SmugMug, Flickr, Google Photos, Zonerama...).</FormHelperText>
|
||||
</FormControl>
|
||||
<FormControl>
|
||||
<FormLabel>Popisek odkazu fotogalerie</FormLabel>
|
||||
<Input placeholder="Fotogalerie" value={galleryLabel} onChange={(e) => setGalleryLabel(e.target.value)} />
|
||||
</FormControl>
|
||||
</SimpleGrid>
|
||||
|
||||
<Heading as="h3" size="md" mb={2}>🎨 Barvy a vzhled webu</Heading>
|
||||
<Heading as="h3" size="md" mb={2} fontFamily={fontHeading}>🎨 Barvy a vzhled webu</Heading>
|
||||
<Text fontSize="sm" mb={3} color="gray.600">Automaticky z loga (lze upravit). Vyberte jednu z předloh nebo barvy ručně dolaďte.</Text>
|
||||
|
||||
{/* Preset selector */}
|
||||
@@ -571,17 +593,7 @@ const SetupPage: React.FC = () => {
|
||||
<Button mt={3} variant="ghost" onClick={regenerateFromLogo}>Znovu z loga</Button>
|
||||
</Box>
|
||||
)}
|
||||
<SimpleGrid columns={[1, 1, 3]} spacing={6}>
|
||||
<FormControl>
|
||||
<FormLabel>Styl webu</FormLabel>
|
||||
<Select value={frontpageStyle} onChange={(e) => setFrontpageStyle((e.target.value as any) || 'unified')}>
|
||||
<option value="unified">Aktuální (Unified)</option>
|
||||
<option value="magazine">Nový (Magazine)</option>
|
||||
<option value="pro">Pro (Hero fullscreen)</option>
|
||||
<option value="edge">Edge (Full‑width minimal)</option>
|
||||
</Select>
|
||||
<FormHelperText>Zvolte výchozí vzhled. Lze později změnit v administraci.</FormHelperText>
|
||||
</FormControl>
|
||||
<SimpleGrid columns={[1, 1, 2]} spacing={6}>
|
||||
<FormControl>
|
||||
<FormLabel>Primární
|
||||
<Tooltip label="Hlavní barva značky (tlačítka, odkazy, zvýraznění)." hasArrow><InfoOutlineIcon ml={2} /></Tooltip>
|
||||
@@ -632,8 +644,36 @@ const SetupPage: React.FC = () => {
|
||||
|
||||
<Divider my={6} />
|
||||
|
||||
<Heading as="h3" size="md" mb={2}>✍️ Písmo a typografie</Heading>
|
||||
<Text fontSize="sm" mb={3} color="gray.600">Vyberte vzhled písma pro váš web. Můžete kdykoliv změnit v administraci.</Text>
|
||||
<Heading as="h3" size="md" mb={2} fontFamily={fontHeading}>📱 Sociální sítě a fotogalerie</Heading>
|
||||
<Text fontSize="sm" mb={3} color="gray.600">Zadejte odkazy na profily klubu a volitelně na fotogalerii. Lze později upravit v administraci.</Text>
|
||||
<SimpleGrid columns={[1, 1, 2]} spacing={6} mb={2}>
|
||||
<FormControl>
|
||||
<FormLabel>Facebook URL</FormLabel>
|
||||
<Input placeholder="https://www.facebook.com/vas.klub" value={facebookUrl} onChange={(e) => setFacebookUrl(e.target.value)} />
|
||||
</FormControl>
|
||||
<FormControl>
|
||||
<FormLabel>Instagram URL</FormLabel>
|
||||
<Input placeholder="https://www.instagram.com/vas.klub" value={instagramUrl} onChange={(e) => setInstagramUrl(e.target.value)} />
|
||||
</FormControl>
|
||||
<FormControl>
|
||||
<FormLabel>YouTube URL</FormLabel>
|
||||
<Input placeholder="https://www.youtube.com/@vas_klub" value={youtubeUrl} onChange={(e) => setYoutubeUrl(e.target.value)} />
|
||||
</FormControl>
|
||||
<FormControl>
|
||||
<FormLabel>URL fotogalerie</FormLabel>
|
||||
<Input placeholder="https://photos.example.com/club" value={galleryUrl} onChange={(e) => setGalleryUrl(e.target.value)} />
|
||||
<FormHelperText>Můžete použít libovolný web (SmugMug, Flickr, Google Photos, Zonerama...).</FormHelperText>
|
||||
</FormControl>
|
||||
<FormControl>
|
||||
<FormLabel>Popisek odkazu fotogalerie</FormLabel>
|
||||
<Input placeholder="Fotogalerie" value={galleryLabel} onChange={(e) => setGalleryLabel(e.target.value)} />
|
||||
</FormControl>
|
||||
</SimpleGrid>
|
||||
|
||||
<Divider my={6} />
|
||||
|
||||
<Heading as="h3" size="md" mb={2} fontFamily={fontHeading}>✍️ Písmo a typografie</Heading>
|
||||
<Text fontSize="sm" mb={3} color="gray.600">Vyberte vzhled písma pro váš web. Náhled se aplikuje okamžitě na celou stránku.</Text>
|
||||
<Box mb={4}>
|
||||
<SimpleGrid columns={{ base: 1, md: 3 }} spacing={3}>
|
||||
{FONT_PAIRINGS.map((font) => (
|
||||
@@ -662,8 +702,8 @@ const SetupPage: React.FC = () => {
|
||||
|
||||
<Divider my={6} />
|
||||
|
||||
<Heading as="h3" size="md" mb={2}>📍 GPS poloha a mapa</Heading>
|
||||
<Text fontSize="sm" mb={4} color="gray.600">Nastavte polohu vašeho stadionu. Můžete vložit odkaz z mapy, nebo zadat souřadnice ručně.</Text>
|
||||
<Heading as="h3" size="md" mb={2} fontFamily={fontHeading}>📍 GPS poloha a mapa</Heading>
|
||||
<Text fontSize="sm" mb={4} color="gray.600">Nastavte polohu vašeho stadionu. Můžete vložit odkaz z mapy, nebo zadat souřadnice ručně. Vyberte také styl mapy.</Text>
|
||||
|
||||
<Box mb={4}>
|
||||
<MapLinkImporter
|
||||
@@ -695,21 +735,7 @@ const SetupPage: React.FC = () => {
|
||||
|
||||
<Divider my={6} />
|
||||
|
||||
<Heading as="h3" size="md" mb={2}>🎨 Styl mapy</Heading>
|
||||
<Text fontSize="sm" mb={4} color="gray.600">Vyberte vzhled mapy, který nejlépe pasuje k barvám vašeho klubu.</Text>
|
||||
<Box mb={4}>
|
||||
<MapStyleSelector
|
||||
value={mapStyle}
|
||||
onChange={setMapStyle}
|
||||
clubPrimaryColor={primaryColor}
|
||||
clubSecondaryColor={accentColor}
|
||||
showPreview={true}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<Divider my={6} />
|
||||
|
||||
<Heading as="h3" size="md" mb={2}>📧 Kontaktní údaje</Heading>
|
||||
<Heading as="h3" size="md" mb={2} fontFamily={fontHeading}>📧 Kontaktní údaje</Heading>
|
||||
<Text fontSize="sm" mb={3} color="gray.600">Tyto údaje se automaticky vyplní při importu z mapy. Můžete je upravit nebo doplnit ručně.</Text>
|
||||
<SimpleGrid columns={[1, 1, 2]} spacing={4} mb={4}>
|
||||
<FormControl>
|
||||
@@ -742,7 +768,7 @@ const SetupPage: React.FC = () => {
|
||||
|
||||
<Divider my={6} />
|
||||
|
||||
<Heading as="h3" size="md" mb={4}>🔒 Zabezpečení a SMTP</Heading>
|
||||
<Heading as="h3" size="md" mb={4} fontFamily={fontHeading}>🔒 Zabezpečení a SMTP</Heading>
|
||||
<SimpleGrid columns={[1, 1, 2]} spacing={6}>
|
||||
<FormControl>
|
||||
<FormLabel>JWT tajemství</FormLabel>
|
||||
|
||||
Reference in New Issue
Block a user