fixed like action, better dark mode, bottom navigation working, added taskcollection
This commit is contained in:
42
resources/js/guest/services/eventApi.ts
Normal file
42
resources/js/guest/services/eventApi.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import { getDeviceId } from '../lib/device';
|
||||
|
||||
export interface EventData {
|
||||
id: number;
|
||||
slug: string;
|
||||
name: string;
|
||||
default_locale: string;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
type?: {
|
||||
slug: string;
|
||||
name: string;
|
||||
icon: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface EventStats {
|
||||
onlineGuests: number;
|
||||
tasksSolved: number;
|
||||
latestPhotoAt: string | null;
|
||||
}
|
||||
|
||||
export async function fetchEvent(slug: string): Promise<EventData> {
|
||||
const res = await fetch(`/api/v1/events/${slug}`);
|
||||
if (!res.ok) throw new Error('Event fetch failed');
|
||||
return await res.json();
|
||||
}
|
||||
|
||||
export async function fetchStats(slug: string): Promise<EventStats> {
|
||||
const res = await fetch(`/api/v1/events/${slug}/stats`, {
|
||||
headers: {
|
||||
'X-Device-Id': getDeviceId(),
|
||||
},
|
||||
});
|
||||
if (!res.ok) throw new Error('Stats fetch failed');
|
||||
const json = await res.json();
|
||||
return {
|
||||
onlineGuests: json.onlineGuests ?? 0,
|
||||
tasksSolved: json.tasksSolved ?? 0,
|
||||
latestPhotoAt: json.latestPhotoAt ?? null,
|
||||
};
|
||||
}
|
||||
@@ -1,15 +1,102 @@
|
||||
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++) {
|
||||
let 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: {
|
||||
'X-Device-Id': getDeviceId(),
|
||||
...headers,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
if (!res.ok) throw new Error('like failed');
|
||||
|
||||
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 ?? 0;
|
||||
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/${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;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user