This commit is contained in:
Tomas Dvorak
2025-10-29 21:20:16 +01:00
parent 823fabee02
commit 16e4533202
61 changed files with 2308 additions and 942 deletions
+19 -2
View File
@@ -87,7 +87,24 @@ export const forwardMessage = async (id: string, toEmail: string) => {
await api.post(`/admin/contact-messages/${id}/forward`, { to_email: toEmail });
};
export const forwardAllMessages = async (toEmail: string) => {
const response = await api.post('/admin/contact-messages/forward-all', { to_email: toEmail });
export const forwardAllMessages = async (
emails: string | string[],
options?: { saveDefault?: boolean }
) => {
let payload: any = {};
if (Array.isArray(emails)) {
const arr = emails.map((e) => String(e || '').trim()).filter(Boolean);
payload = arr.length > 1 ? { to_emails: arr } : { to_email: arr[0] || '' };
} else {
const parts = String(emails || '')
.split(/[;\,\s]+/)
.map((s) => s.trim())
.filter(Boolean);
payload = parts.length > 1 ? { to_emails: parts } : { to_email: parts[0] || '' };
}
if (options?.saveDefault) {
payload.save_default = true;
}
const response = await api.post('/admin/contact-messages/forward-all', payload);
return response.data;
};
+38
View File
@@ -0,0 +1,38 @@
import api from './api';
export interface Banner {
id: number | string;
name: string;
image_url?: string;
click_url?: string;
placement?: string;
width?: number;
height?: number;
is_active?: boolean;
display_order?: number;
}
export async function getBanners(params?: { active?: boolean; placement?: string }): Promise<Banner[]> {
const res = await api.get<any>('/banners', { params });
const body = res.data;
const list = Array.isArray(body) ? body : (Array.isArray(body?.data) ? body.data : []);
return (list || []).map((b: any) => ({
...b,
id: b.id ?? b.ID ?? b.Id ?? b.iD,
}));
}
export async function createBanner(payload: { name: string; image_url?: string; click_url?: string; placement?: string; width?: number; height?: number; is_active?: boolean; display_order?: number }) {
const res = await api.post<Banner>('/banners', payload);
return res.data;
}
export async function updateBanner(id: number | string, payload: Partial<{ name: string; image_url?: string; click_url?: string; placement?: string; width?: number; height?: number; is_active?: boolean; display_order?: number }>) {
const res = await api.put<Banner>(`/banners/${id}`, payload);
return res.data;
}
export async function deleteBanner(id: number | string) {
const res = await api.delete<{ zprava: string }>(`/banners/${id}`);
return res.data;
}
+16 -4
View File
@@ -1,4 +1,5 @@
import api from './api';
import { assetUrl } from '../utils/url';
export interface ImageProcessRequest {
image_url: string;
@@ -44,7 +45,11 @@ export interface ImageProcessResponse {
*/
export const processImage = async (request: ImageProcessRequest): Promise<ImageProcessResponse> => {
const response = await api.post('/image-processing/process', request);
return response.data;
const data = response.data || {};
if (data && typeof data.url === 'string') {
data.url = assetUrl(data.url) || data.url;
}
return data;
};
/**
@@ -52,7 +57,11 @@ export const processImage = async (request: ImageProcessRequest): Promise<ImageP
*/
export const quickEditImage = async (request: QuickEditRequest): Promise<ImageProcessResponse> => {
const response = await api.post('/image-processing/quick-edit', request);
return response.data;
const data = response.data || {};
if (data && typeof data.url === 'string') {
data.url = assetUrl(data.url) || data.url;
}
return data;
};
/**
@@ -79,8 +88,11 @@ export const cropAndUpload = async (
'Content-Type': 'multipart/form-data',
},
});
return response.data;
const data = response.data || {};
if (data && typeof data.url === 'string') {
data.url = assetUrl(data.url) || data.url;
}
return data;
};
/**
+14 -37
View File
@@ -1,5 +1,4 @@
import axios from 'axios';
import { API_URL as API_BASE_URL } from './api';
import api, { API_URL as API_BASE_URL } from './api';
export interface NavigationItem {
id?: number;
@@ -30,86 +29,64 @@ export interface SocialLink {
// Public endpoints
export const getNavigationItems = async (): Promise<NavigationItem[]> => {
const response = await axios.get(`${API_BASE_URL}/navigation`);
const response = await api.get(`/navigation`);
return response.data;
};
export const getSocialLinks = async (): Promise<SocialLink[]> => {
const response = await axios.get(`${API_BASE_URL}/social-links`);
const response = await api.get(`/social-links`);
return response.data;
};
// Admin endpoints
export const getAllNavigationItems = async (): Promise<NavigationItem[]> => {
const response = await axios.get(`${API_BASE_URL}/admin/navigation`, {
withCredentials: true,
});
const response = await api.get(`/admin/navigation`);
return response.data;
};
export const createNavigationItem = async (item: Partial<NavigationItem>): Promise<NavigationItem> => {
const response = await axios.post(`${API_BASE_URL}/admin/navigation`, item, {
withCredentials: true,
});
const response = await api.post(`/admin/navigation`, item);
return response.data;
};
export const updateNavigationItem = async (id: number, item: Partial<NavigationItem>): Promise<NavigationItem> => {
const response = await axios.put(`${API_BASE_URL}/admin/navigation/${id}`, item, {
withCredentials: true,
});
const response = await api.put(`/admin/navigation/${id}`, item);
return response.data;
};
export const deleteNavigationItem = async (id: number): Promise<void> => {
await axios.delete(`${API_BASE_URL}/admin/navigation/${id}`, {
withCredentials: true,
});
await api.delete(`/admin/navigation/${id}`);
};
export const reorderNavigationItems = async (orders: { id: number; display_order: number }[]): Promise<void> => {
await axios.post(`${API_BASE_URL}/admin/navigation/reorder`, orders, {
withCredentials: true,
});
await api.post(`/admin/navigation/reorder`, orders);
};
// Social links admin endpoints
export const getAllSocialLinks = async (): Promise<SocialLink[]> => {
const response = await axios.get(`${API_BASE_URL}/admin/social-links`, {
withCredentials: true,
});
const response = await api.get(`/admin/social-links`);
return response.data;
};
export const createSocialLink = async (link: Partial<SocialLink>): Promise<SocialLink> => {
const response = await axios.post(`${API_BASE_URL}/admin/social-links`, link, {
withCredentials: true,
});
const response = await api.post(`/admin/social-links`, link);
return response.data;
};
export const updateSocialLink = async (id: number, link: Partial<SocialLink>): Promise<SocialLink> => {
const response = await axios.put(`${API_BASE_URL}/admin/social-links/${id}`, link, {
withCredentials: true,
});
const response = await api.put(`/admin/social-links/${id}`, link);
return response.data;
};
export const deleteSocialLink = async (id: number): Promise<void> => {
await axios.delete(`${API_BASE_URL}/admin/social-links/${id}`, {
withCredentials: true,
});
await api.delete(`/admin/social-links/${id}`);
};
export const reorderSocialLinks = async (orders: { id: number; display_order: number }[]): Promise<void> => {
await axios.post(`${API_BASE_URL}/admin/social-links/reorder`, orders, {
withCredentials: true,
});
await api.post(`/admin/social-links/reorder`, orders);
};
export const seedDefaultNavigation = async (): Promise<{ message: string; count: number; seeded: boolean }> => {
const response = await axios.post(`${API_BASE_URL}/admin/navigation/seed`, {}, {
withCredentials: true,
});
const response = await api.post(`/admin/navigation/seed`, {});
return response.data;
};
+4 -2
View File
@@ -116,14 +116,14 @@ export const PREDEFINED_ELEMENTS: PredefinedElement[] = [
// Layout - Rozvržení
{ name: 'style-pack', label: 'Styl balíček', description: 'Globální vizuální balíček pro celou stránku', icon: FaCube, category: 'layout', defaultVariant: 'default' },
{ name: 'header', label: 'Hlavička', description: 'Hlavička stránky s logem a navigací', icon: FaRegClipboard, category: 'layout', defaultVariant: 'unified' },
{ name: 'hero-topbar', label: 'Klub lišta nad hero', description: 'Pruh nad hero s logem klubu, názvem a akcemi', icon: FaCube, category: 'layout', defaultVariant: 'brand' },
{ name: 'hero-topbar', label: 'Klub lišta nad hero', description: 'Pruh nad hero s logem klubu, názvem a akcemi', icon: FaCube, category: 'layout', defaultVariant: 'minimal' },
{ name: 'hero', label: 'Hlavní Sekce', description: 'Hlavní obsahová oblast s úvodním obsahem', icon: FaBullseye, category: 'layout', defaultVariant: 'grid' },
{ name: 'footer', label: 'Patička', description: 'Spodní část stránky s odkazy a kontakty', icon: FaMapSigns, category: 'layout', defaultVariant: 'standard' },
{ name: 'sidebar', label: 'Boční Panel', description: 'Boční sloupec s doplňkovým obsahem', icon: FaColumns, category: 'layout', defaultVariant: 'right' },
{ name: 'banner', label: 'Banner', description: 'Reklamní nebo informační banner', icon: FaFlag, category: 'layout', defaultVariant: 'top' },
// Content - Obsah
{ name: 'news', label: 'Novinky', description: 'Nejnovější články a zprávy', icon: FaNewspaper, category: 'content', defaultVariant: 'grid' },
{ name: 'news', label: 'Novinky', description: 'Nejnovější články a zprávy', icon: FaNewspaper, category: 'content', defaultVariant: 'grid_one' },
{ name: 'matches', label: 'Zápasy', description: 'Nadcházející a poslední zápasy', icon: FaFutbol, category: 'content', defaultVariant: 'compact' },
{ name: 'matches-slider', label: 'Zápasy (slider)', description: 'Přehled zápasů podle soutěže ve slideru', icon: FaFutbol, category: 'content', defaultVariant: 'carousel' },
{ name: 'team', label: 'Tým', description: 'Hráči a realizační tým', icon: FaUsers, category: 'content', defaultVariant: 'grid' },
@@ -190,6 +190,8 @@ export const ELEMENT_VARIANTS: Record<string, ElementVariant[]> = {
{ value: 'sparta_featured_carousel', label: 'Sparta Featured Carousel', description: 'Hero header s pozadím, článek s kategoriemi, thumbnail navigace, auto-swap' },
],
news: [
{ value: 'grid_one', label: 'Mřížka (1 sloupec)', description: 'Jednosloupcová mřížka bez tabulek vpravo (skryje sekci Tabulky)' },
{ value: 'grid_two', label: 'Mřížka (2 sloupce)', description: 'Aktuality vlevo a Tabulky vpravo (pokud jsou k dispozici)' },
{ value: 'grid', label: 'Mřížka', description: 'Rozložení karet v mřížce' },
{ value: 'scroller', label: 'Posuvník', description: 'Horizontální posuvník' },
{ value: 'hero_carousel', label: 'Hero Karusel', description: 'Jeden článek najednou. Tlačítko: ZJISTIT VÍCE (vlevo dole). Numerace: 01 02 03 (vpravo dole). Auto-swap' },
+2
View File
@@ -116,6 +116,8 @@ export type AdminSettings = PublicSettings & {
location_longitude?: number;
map_zoom_level?: number;
show_map_on_homepage?: boolean;
frontend_base_url?: string;
api_base_url?: string;
// Homepage matches display configuration
finished_match_display_days?: number; // Number of days to show finished matches with scores on homepage
};
+3
View File
@@ -15,6 +15,9 @@ export type SetupInitializePayload = {
club_name?: string;
club_logo_url?: string;
club_url?: string;
// deployment bases
frontend_base_url?: string;
api_base_url?: string;
frontpage_style?: 'unified' | 'magazine' | 'pro' | 'edge';
primary_color?: string;
secondary_color?: string;