This commit is contained in:
Tomáš Dvořák
2025-10-16 13:32:05 +02:00
commit 12cba639b9
663 changed files with 168914 additions and 0 deletions
@@ -0,0 +1,93 @@
import { api } from '../api';
export interface ContactMessage {
id: string;
name: string;
email: string;
subject?: string;
message: string;
source?: string;
ipAddress?: string;
userAgent?: string;
isRead: boolean;
createdAt: string;
updatedAt: string;
}
export interface ContactMessagesResponse {
data: ContactMessage[];
total: number;
page: number;
limit: number;
totalPages: number;
}
export const getContactMessages = async (params?: {
page?: number;
limit?: number;
search?: string;
isRead?: boolean;
sortBy?: string;
sortOrder?: 'asc' | 'desc';
}): Promise<ContactMessagesResponse> => {
// Backend returns { data: ContactMessage[], pagination: { total, page, limit, pages, has_more } }
const response = await api.get<{ data: ContactMessage[]; pagination: { total: number; page: number; limit: number; pages: number } }>(
'/admin/contact-messages',
{ params }
);
const r = response.data;
// Normalize possible snake_case fields coming from backend to camelCase expected by UI
const normalized = (r.data as any[]).map((m) => ({
id: String(m.id),
name: m.name,
email: m.email,
subject: m.subject,
message: m.message,
source: m.source,
ipAddress: m.ipAddress ?? m.ip_address,
userAgent: m.userAgent ?? m.user_agent,
isRead: m.isRead ?? m.is_read ?? false,
createdAt: m.createdAt ?? m.created_at,
updatedAt: m.updatedAt ?? m.updated_at,
})) as ContactMessage[];
return {
data: normalized,
total: r.pagination.total,
page: r.pagination.page,
limit: r.pagination.limit,
totalPages: r.pagination.pages,
};
};
export const getContactMessage = async (id: string) => {
// Backend returns the message object directly
const response = await api.get<ContactMessage>(`/admin/contact-messages/${id}`);
return response.data;
};
export const markAsRead = async (id: string) => {
// Backend returns only a message string; caller doesn't need data
await api.patch(
`/admin/contact-messages/${id}/read`,
{ isRead: true }
);
};
export const deleteMessage = async (id: string) => {
await api.delete(`/admin/contact-messages/${id}`);
};
export const deleteMultipleMessages = async (ids: Array<string | number>) => {
// Backend expects a raw JSON array []int in the body. Convert to numbers.
const numericIds = ids.map((v) => (typeof v === 'string' ? Number(v) : v));
await api.delete('/admin/contact-messages', { data: numericIds });
};
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 });
return response.data;
};
+165
View File
@@ -0,0 +1,165 @@
import { api } from '../api';
export interface NewsletterSubscriber {
id: number;
email: string;
is_active: boolean;
created_at: string;
updated_at: string;
}
export interface NewsletterSendData {
subject: string;
content: string; // backend accepts either 'content' or 'body'
}
export const getNewsletterSubscribers = async (): Promise<NewsletterSubscriber[]> => {
const response = await api.get<NewsletterSubscriber[]>(`/admin/newsletter/subscribers`);
return response.data;
};
export const sendNewsletter = async (data: NewsletterSendData): Promise<{ message: string }> => {
// Backend will accept either 'content' or 'body'. We send 'content'.
const response = await api.post<{ message: string }>(
`/admin/newsletter/send`,
{
subject: data.subject,
content: data.content,
},
// Increase timeout to 60s to accommodate SMTP and bulk send delays
{ timeout: 60000 }
);
return response.data;
};
export const sendNewsletterTest = async (email?: string): Promise<{ message: string; recipient: string }> => {
const payload = email ? { email } : {};
// Allow more time for SMTP connection during test sends
const response = await api.post<{ message: string; recipient: string }>(
`/admin/newsletter/test`,
payload,
{ timeout: 60000 }
);
return response.data;
};
export type NewsletterTestType = 'newsletter' | 'welcome' | 'welcome_back' | 'blogs' | 'events' | 'matches' | 'scores' | 'weekly';
export interface NewsletterTestPayload {
email?: string;
emails?: string[];
type?: NewsletterTestType;
}
export type AdminSmtpTestPayload = {
host: string;
port: number;
username?: string;
password?: string;
from: string;
to: string;
subject?: string;
body?: string;
use_tls?: boolean;
};
export const adminSendSmtpTest = async (payload: AdminSmtpTestPayload): Promise<{ ok: boolean; message?: string; error?: string }> => {
const res = await api.post('/admin/newsletter/smtp-test', payload, { timeout: 60000 });
return res.data;
};
export const sendNewsletterTestAdvanced = async (payload: NewsletterTestPayload): Promise<{ message: string; recipients?: string[]; recipient?: string; type: string }> => {
// Allow more time for SMTP connection during test sends
const response = await api.post<{ message: string; recipients?: string[]; recipient?: string; type: string }>(
`/admin/newsletter/test`,
payload,
{ timeout: 60000 }
);
return response.data;
};
export interface NewsletterStatus {
total_subscribers: number;
active_subscribers: number;
sample_recipients: string[];
interval_minutes: number;
next_approximate: string;
newsletter_enabled?: boolean;
}
export const getNewsletterStatus = async (): Promise<NewsletterStatus> => {
const response = await api.get<NewsletterStatus>(`/admin/newsletter/status`);
return response.data as any;
};
export type DigestType = 'blogs' | 'events' | 'matches' | 'scores' | 'weekly';
export const sendNewsletterDigest = async (type: DigestType, competitions?: string): Promise<{ message: string; recipients: number; type: string }> => {
const response = await api.post(`/admin/newsletter/send-digest`, { type, competitions });
return response.data;
};
export const setNewsletterAutomation = async (enabled: boolean): Promise<{ newsletter_enabled: boolean }> => {
const response = await api.patch(`/admin/newsletter/enable`, { enabled });
return response.data;
};
export interface NewsletterPreviewPayload {
preferences?: Record<string, boolean> & { competitions?: string };
}
export interface NewsletterPreviewResponse {
subject: string;
html: string;
}
export const previewNewsletter = async (payload: NewsletterPreviewPayload): Promise<NewsletterPreviewResponse> => {
const response = await api.post<NewsletterPreviewResponse>(`/admin/newsletter/preview`, payload);
return response.data;
};
export interface EmailStatRow {
id: number;
created_at: string;
subject: string;
recipient: string;
type: string;
status: string;
opens: number;
clicks: number;
spam: number;
unsubs: number;
}
export const getRecentEmailStats = async (): Promise<EmailStatRow[]> => {
const response = await api.get<{ data: EmailStatRow[] }>(`/admin/newsletter/stats/recent`);
return response.data?.data || [];
};
export interface EmailEventRow {
id: number;
created_at: string;
email_log_id: number;
event_type: string;
meta?: Record<string, any>;
}
export const getEmailEventsForLog = async (id: number): Promise<EmailEventRow[]> => {
const response = await api.get<{ data: EmailEventRow[] }>(`/admin/newsletter/stats/${id}/events`);
return response.data?.data || [];
};
export const deleteSubscriber = async (id: number): Promise<void> => {
await api.delete(`/admin/newsletter/subscribers/${id}`);
};
export const toggleSubscriberStatus = async (id: number, isActive: boolean): Promise<NewsletterSubscriber> => {
const response = await api.patch<NewsletterSubscriber>(
`/admin/newsletter/subscribers/${id}/status`,
{ is_active: isActive }
);
return response.data;
};
export const updateSubscriberPreferences = async (id: number, preferences: Record<string, boolean>) => {
const response = await api.patch<NewsletterSubscriber>(`/admin/newsletter/subscribers/${id}/preferences`, preferences);
return response.data;
};
+18
View File
@@ -0,0 +1,18 @@
import api from '../../services/api';
export type PrefetchStatus = {
lastUpdated?: string;
intervalMinutes: number;
fastMode: boolean;
nextApproximate: string;
};
export async function getPrefetchStatus(): Promise<PrefetchStatus> {
const { data } = await api.get('/admin/prefetch/status');
return data as PrefetchStatus;
}
export async function triggerPrefetch(): Promise<{ message: string }>{
const { data } = await api.post('/admin/prefetch/trigger');
return data as { message: string };
}