Initial commit: MVP structure + Cursor documentation + Onboarding components

This commit is contained in:
Apple
2025-11-13 06:12:20 -08:00
commit 5520665600
58 changed files with 7683 additions and 0 deletions

83
src/api/client.ts Normal file
View File

@@ -0,0 +1,83 @@
// API Client для MicroDAO
const API_BASE_URL = import.meta.env.VITE_API_URL || 'https://api.microdao.xyz';
export class ApiError extends Error {
constructor(
message: string,
public status: number,
public data?: unknown
) {
super(message);
this.name = 'ApiError';
}
}
async function getAuthToken(): Promise<string | null> {
// Перевіряємо localStorage або httpOnly cookie
return localStorage.getItem('auth_token');
}
async function fetchApi(
endpoint: string,
options: RequestInit = {}
): Promise<Response> {
const token = await getAuthToken();
const headers: HeadersInit = {
'Content-Type': 'application/json',
...options.headers,
};
if (token) {
headers['Authorization'] = `Bearer ${token}`;
}
const url = endpoint.startsWith('http') ? endpoint : `${API_BASE_URL}${endpoint}`;
const response = await fetch(url, {
...options,
headers,
});
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
throw new ApiError(
errorData.message || `HTTP ${response.status}: ${response.statusText}`,
response.status,
errorData
);
}
return response;
}
export async function apiGet<T>(endpoint: string): Promise<T> {
const response = await fetchApi(endpoint, { method: 'GET' });
return response.json();
}
export async function apiPost<T>(endpoint: string, data?: unknown): Promise<T> {
const response = await fetchApi(endpoint, {
method: 'POST',
body: data ? JSON.stringify(data) : undefined,
});
return response.json();
}
export async function apiPatch<T>(endpoint: string, data: unknown): Promise<T> {
const response = await fetchApi(endpoint, {
method: 'PATCH',
body: JSON.stringify(data),
});
return response.json();
}
export async function apiDelete<T>(endpoint: string): Promise<T> {
const response = await fetchApi(endpoint, { method: 'DELETE' });
if (response.status === 204) {
return {} as T;
}
return response.json();
}