Files
fotospiel-app/resources/js/guest/services/photosApi.ts
Codex Agent 48a2974152 Marketing packages now use localized name/description data plus seeded placeholder-
driven breakdown tables, with frontend/cards/dialog updated accordingly (database/
  migrations/2025_10_17_000001_add_description_table_to_packages.php, database/
  migrations/2025_10_17_000002_add_translation_columns_to_packages.php, database/seeders/PackageSeeder.php, app/
  Http/Controllers/MarketingController.php, resources/js/pages/marketing/Packages.tsx).
  Filament Package resource gains locale tabs, markdown editor, numeric/toggle inputs, and simplified feature
  management (app/Filament/Resources/PackageResource.php, app/Filament/Resources/PackageResource/Pages/
  CreatePackage.php, .../EditPackage.php).
  Legal pages now render markdown-backed content inside the main layout via a new controller/view route setup and
  updated footer links (app/Http/Controllers/LegalPageController.php, routes/web.php, resources/views/partials/
  footer.blade.php, resources/js/pages/legal/Show.tsx, remove old static pages).
  Translation files and shared assets updated to cover new marketing/legal strings and styling tweaks (public/
  lang/*/marketing.json, resources/lang/*/marketing.php, resources/css/app.css, resources/js/admin/components/
  LanguageSwitcher.tsx).
2025-10-17 21:20:54 +02:00

102 lines
3.1 KiB
TypeScript

import { getDeviceId } from '../lib/device';
function getCsrfToken(): string | null {
// Method 1: Meta tag (preferred for SPA)
const metaToken = document.querySelector('meta[name="csrf-token"]');
if (metaToken) {
return (metaToken as HTMLMetaElement).getAttribute('content') || null;
}
// Method 2: XSRF-TOKEN cookie (Sanctum fallback)
const name = 'XSRF-TOKEN=';
const decodedCookie = decodeURIComponent(document.cookie);
const ca = decodedCookie.split(';');
for (let i = 0; i < ca.length; i++) {
const c = ca[i].trimStart();
if (c.startsWith(name)) {
const token = c.substring(name.length);
try {
// Decode base64 if needed
return decodeURIComponent(atob(token));
} catch {
return token;
}
}
}
console.warn('No CSRF token found - API requests may fail');
return null;
}
function getCsrfHeaders(): Record<string, string> {
const token = getCsrfToken();
const headers: Record<string, string> = {
'X-Device-Id': getDeviceId(),
'Accept': 'application/json',
};
if (token) {
headers['X-CSRF-TOKEN'] = token;
headers['X-XSRF-TOKEN'] = token;
}
return headers;
}
export async function likePhoto(id: number): Promise<number> {
const headers = getCsrfHeaders();
const res = await fetch(`/api/v1/photos/${id}/like`, {
method: 'POST',
credentials: 'include',
headers: {
...headers,
'Content-Type': 'application/json',
},
});
if (!res.ok) {
const errorText = await res.text();
if (res.status === 419) {
throw new Error('CSRF Token mismatch. This usually means:\n\n' +
'1. The page needs to be refreshed\n' +
'2. Check if <meta name="csrf-token"> is present in HTML source\n' +
'3. API routes might need CSRF exemption in VerifyCsrfToken middleware');
}
throw new Error(`Like failed: ${res.status} - ${errorText}`);
}
const json = await res.json();
return json.likes_count ?? json.data?.likes_count ?? 0;
}
export async function uploadPhoto(slug: string, file: File, taskId?: number, emotionSlug?: string): Promise<number> {
const formData = new FormData();
formData.append('photo', file, `photo-${Date.now()}.jpg`);
if (taskId) formData.append('task_id', taskId.toString());
if (emotionSlug) formData.append('emotion_slug', emotionSlug);
formData.append('device_id', getDeviceId());
const res = await fetch(`/api/v1/events/${encodeURIComponent(slug)}/upload`, {
method: 'POST',
credentials: 'include',
body: formData,
// Don't set Content-Type for FormData - let browser handle it with boundary
});
if (!res.ok) {
const errorText = await res.text();
if (res.status === 419) {
throw new Error('CSRF Token mismatch during upload.\n\n' +
'This usually means:\n' +
'1. API routes need CSRF exemption in VerifyCsrfToken middleware\n' +
'2. Check if <meta name="csrf-token"> is present in page source\n' +
'3. The page might need to be refreshed');
}
throw new Error(`Upload failed: ${res.status} - ${errorText}`);
}
const json = await res.json();
return json.photo_id ?? json.id ?? json.data?.id ?? 0;
}