first test

This commit is contained in:
Tomas Dvorak
2026-02-08 14:14:55 +01:00
parent 18aa702174
commit d27cf14110
372 changed files with 98089 additions and 2585 deletions
+269 -5
View File
@@ -1,5 +1,21 @@
const API_BASE_URL = import.meta.env.VITE_API_URL || 'http://localhost:8080/api/v1';
// Check if we're in demo mode
const isDemoMode = () => {
return localStorage.getItem('demoMode') === 'true' ||
document.title.includes('Demo Mode') ||
window.location.search.includes('demo=true');
};
// Helper function to get auth headers
const getAuthHeaders = () => {
const token = localStorage.getItem('token');
return {
'Content-Type': 'application/json',
...(token && { 'Authorization': `Bearer ${token}` }),
};
};
// Generic API client
class ApiClient {
private baseURL: string;
@@ -12,11 +28,16 @@ class ApiClient {
endpoint: string,
options: RequestInit = {}
): Promise<T> {
// If in demo mode, use mock data
if (isDemoMode()) {
return this.getMockResponse(endpoint, options);
}
const url = `${this.baseURL}${endpoint}`;
const config: RequestInit = {
headers: {
'Content-Type': 'application/json',
...getAuthHeaders(),
...options.headers,
},
...options,
@@ -26,17 +47,186 @@ class ApiClient {
const response = await fetch(url, config);
if (!response.ok) {
const error = await response.json().catch(() => ({ error: 'Unknown error' }));
throw new Error(error.error || `HTTP ${response.status}: ${response.statusText}`);
// If backend fails, fall back to demo mode
console.warn(`API endpoint ${endpoint} failed, falling back to demo mode`);
return this.getMockResponse(endpoint, options);
}
return await response.json();
} catch (error) {
console.error('API request failed:', error);
throw error;
console.warn(`API request failed for ${endpoint}, falling back to demo mode:`, error);
return this.getMockResponse(endpoint, options);
}
}
private async getMockResponse<T>(endpoint: string, options: RequestInit): Promise<T> {
// Import mock data dynamically to avoid circular dependencies
const {
getMockStats,
getMockDocuments,
getMockBookmarks,
getMockTasks,
getMockNotes,
getMockTimeEntries,
getMockLearningPaths,
getMockVideos
} = await import('./mockData');
const method = options.method || 'GET';
// Dashboard stats
if (endpoint.includes('/dashboard/stats')) {
return getMockStats() as T;
}
// Documents/Files
if (endpoint.includes('/documents') || endpoint.includes('/files')) {
if (method === 'GET') {
return getMockDocuments() as T;
}
}
// Bookmarks
if (endpoint.includes('/bookmarks')) {
if (method === 'GET') {
return getMockBookmarks() as T;
}
}
// Tasks
if (endpoint.includes('/tasks')) {
if (method === 'GET') {
return getMockTasks() as T;
}
}
// Notes
if (endpoint.includes('/notes')) {
if (method === 'GET') {
return getMockNotes() as T;
}
}
// Time entries
if (endpoint.includes('/time-entries')) {
if (method === 'GET') {
const mockEntries = getMockTimeEntries();
// Convert mock entries to TimeEntry format
const timeEntries = mockEntries.map(entry => ({
id: parseInt(entry.id.replace('time_', '')),
user_id: 1,
task_id: entry.taskId ? parseInt(entry.taskId.replace('task_', '')) : undefined,
start_time: `${entry.date}T${entry.startTime}:00Z`,
end_time: entry.endTime ? `${entry.date}T${entry.endTime}:00Z` : undefined,
duration: entry.duration,
description: entry.description,
tags: entry.tags,
billable: entry.billable,
hourly_rate: entry.hourlyRate,
is_running: false,
source: 'demo',
created_at: `${entry.date}T${entry.startTime}:00Z`,
updated_at: entry.endTime ? `${entry.date}T${entry.endTime}:00Z` : `${entry.date}T${entry.startTime}:00Z`
}));
return { time_entries: timeEntries } as T;
}
if (method === 'POST') {
const mockEntries = getMockTimeEntries();
const entry = mockEntries[0];
// Convert mock entry to TimeEntry format
const timeEntry = {
id: parseInt(entry.id.replace('time_', '')),
user_id: 1,
task_id: entry.taskId ? parseInt(entry.taskId.replace('task_', '')) : undefined,
start_time: `${entry.date}T${entry.startTime}:00Z`,
end_time: entry.endTime ? `${entry.date}T${entry.endTime}:00Z` : undefined,
duration: entry.duration,
description: entry.description,
tags: entry.tags,
billable: entry.billable,
hourly_rate: entry.hourlyRate,
is_running: false,
source: 'demo',
created_at: `${entry.date}T${entry.startTime}:00Z`,
updated_at: entry.endTime ? `${entry.date}T${entry.endTime}:00Z` : `${entry.date}T${entry.startTime}:00Z`
};
return { time_entry: timeEntry } as T;
}
}
// Auth endpoints
if (endpoint.includes('/auth/login-totp')) {
return {
token: 'demo-token',
user: { id: 1, email: 'demo@trackeep.com', name: 'Demo User' }
} as T;
}
// GitHub repos
if (endpoint.includes('/github/repos')) {
return {
repositories: [
{ id: 1, name: 'trackeep', full_name: 'tdvorak/trackeep', stars: 245, forks: 43, watchers: 65, language: 'Go' },
{ id: 2, name: 'frontend', full_name: 'tdvorak/frontend', stars: 89, forks: 12, watchers: 23, language: 'TypeScript' },
{ id: 3, name: 'mobile-app', full_name: 'tdvorak/mobile-app', stars: 34, forks: 8, watchers: 15, language: 'TypeScript' }
],
totalStars: 368,
totalForks: 63,
totalWatchers: 103
} as T;
}
// Learning paths
if (endpoint.includes('/learning-paths/categories')) {
return {
categories: ['Web Development', 'DevOps', 'Programming', 'Design', 'Business', 'Data Science']
} as T;
}
if (endpoint.includes('/learning-paths')) {
return getMockLearningPaths() as T;
}
// Chat sessions
if (endpoint.includes('/chat/sessions')) {
return {
sessions: [
{ id: '1', title: 'Project Planning', created_at: '2024-01-15T10:00:00Z', updated_at: '2024-01-15T11:30:00Z' },
{ id: '2', title: 'Technical Discussion', created_at: '2024-01-14T14:00:00Z', updated_at: '2024-01-14T15:45:00Z' }
]
} as T;
}
// AI providers
if (endpoint.includes('/ai/providers')) {
return {
providers: [
{ id: 'longcat', name: 'LongCat AI', enabled: true, models: ['LongCat-Flash-Chat', 'LongCat-Flash-Thinking'] },
{ id: 'mistral', name: 'Mistral AI', enabled: false, models: ['mistral-small-latest', 'mistral-large-latest'] },
{ id: 'openai', name: 'OpenAI', enabled: false, models: ['gpt-4', 'gpt-3.5-turbo'] }
]
} as T;
}
// YouTube endpoints
if (endpoint.includes('/youtube/video-details')) {
return getMockVideos()[0] as T;
}
if (endpoint.includes('/youtube/predefined-channels')) {
return {
channels: [
{ id: 'UC8butISFwT-Wy7pm24E6Icg', name: 'NetworkChuck', latestVideos: getMockVideos().slice(0, 2) },
{ id: 'UCWv7vHwRQdGJtU2i9hJ8X7A', name: 'Fireship', latestVideos: getMockVideos().slice(1, 3) },
{ id: 'UCsXVk37bltHxD1rDPgtNG6A', name: 'Beyond Fireship', latestVideos: getMockVideos().slice(0, 1) }
]
} as T;
}
// Default empty response
return {} as T;
}
async get<T>(endpoint: string): Promise<T> {
return this.request<T>(endpoint, { method: 'GET' });
}
@@ -65,6 +255,9 @@ class ApiClient {
try {
const response = await fetch(url, {
method: 'POST',
headers: {
'Authorization': getAuthHeaders().Authorization || '',
},
body: formData,
});
@@ -134,6 +327,36 @@ export interface File {
updated_at: string;
}
export interface TimeEntry {
id: number;
user_id: number;
task_id?: number;
bookmark_id?: number;
note_id?: number;
start_time: string;
end_time?: string;
duration?: number;
description: string;
tags: string[];
billable: boolean;
hourly_rate?: number;
is_running: boolean;
source: string;
created_at: string;
updated_at: string;
task?: Task;
bookmark?: Bookmark;
note?: Note;
}
export interface TimeStats {
total_time_seconds: number;
total_entries: number;
running_entries: number;
billable_time_seconds: number;
total_billable_amount: number;
}
// API Functions
export const bookmarksApi = {
getAll: () => api.get<Bookmark[]>('/bookmarks'),
@@ -191,4 +414,45 @@ export const filesApi = {
download: (id: number) => `${API_BASE_URL}/files/${id}/download`,
};
export const timeEntriesApi = {
getAll: (startDate?: string, endDate?: string, isRunning?: boolean) => {
const params = new URLSearchParams();
if (startDate) params.append('start_date', startDate);
if (endDate) params.append('end_date', endDate);
if (isRunning !== undefined) params.append('is_running', isRunning.toString());
const query = params.toString() ? `?${params.toString()}` : '';
return api.get<{ time_entries: TimeEntry[] }>(`/time-entries${query}`);
},
getById: (id: number) => api.get<{ time_entry: TimeEntry }>(`/time-entries/${id}`),
create: (timeEntry: {
task_id?: number;
bookmark_id?: number;
note_id?: number;
description: string;
tags?: string[];
billable?: boolean;
hourly_rate?: number;
source?: string;
}) => api.post<{ time_entry: TimeEntry }>('/time-entries', timeEntry),
update: (id: number, timeEntry: {
description?: string;
tags?: string[];
billable?: boolean;
hourly_rate?: number;
end_time?: string;
}) => api.put<{ time_entry: TimeEntry }>(`/time-entries/${id}`, timeEntry),
stop: (id: number) => api.post<{ time_entry: TimeEntry }>(`/time-entries/${id}/stop`),
delete: (id: number) => api.delete<{ message: string }>(`/time-entries/${id}`),
getStats: () => api.get<{ stats: TimeStats }>('/time-entries/stats'),
};
import {
demoBookmarksApi,
demoTasksApi,
demoNotesApi,
demoFilesApi,
demoTimeEntriesApi
} from './demo-api';
export default api;
export { demoBookmarksApi, demoTasksApi, demoNotesApi, demoFilesApi, demoTimeEntriesApi };