creates application

This commit is contained in:
simon.franken
2026-02-16 10:15:27 +01:00
parent 791c661395
commit 7d678c1c4d
65 changed files with 10389 additions and 0 deletions

25
frontend/src/api/auth.ts Normal file
View File

@@ -0,0 +1,25 @@
import axios from 'axios';
import type { User } from '@/types';
const AUTH_BASE = '/auth';
export const authApi = {
login: (): void => {
window.location.href = `${AUTH_BASE}/login`;
},
logout: async (): Promise<void> => {
await axios.post(`${AUTH_BASE}/logout`, {}, { withCredentials: true });
},
getCurrentUser: async (): Promise<User | null> => {
try {
const { data } = await axios.get<User>(`${AUTH_BASE}/me`, {
withCredentials: true,
});
return data;
} catch {
return null;
}
},
};

View File

@@ -0,0 +1,26 @@
import axios, { AxiosError } from 'axios';
const apiClient = axios.create({
baseURL: '/api',
headers: {
'Content-Type': 'application/json',
},
withCredentials: true,
});
// Response interceptor for error handling
apiClient.interceptors.response.use(
(response) => response,
(error: AxiosError<{ error?: string; details?: unknown }>) => {
if (error.response?.status === 401) {
// Redirect to login on 401
window.location.href = '/login';
return Promise.reject(error);
}
const message = error.response?.data?.error || error.message || 'An error occurred';
return Promise.reject(new Error(message));
}
);
export default apiClient;

View File

@@ -0,0 +1,23 @@
import apiClient from './client';
import type { Client, CreateClientInput, UpdateClientInput } from '@/types';
export const clientsApi = {
getAll: async (): Promise<Client[]> => {
const { data } = await apiClient.get<Client[]>('/clients');
return data;
},
create: async (input: CreateClientInput): Promise<Client> => {
const { data } = await apiClient.post<Client>('/clients', input);
return data;
},
update: async (id: string, input: UpdateClientInput): Promise<Client> => {
const { data } = await apiClient.put<Client>(`/clients/${id}`, input);
return data;
},
delete: async (id: string): Promise<void> => {
await apiClient.delete(`/clients/${id}`);
},
};

View File

@@ -0,0 +1,25 @@
import apiClient from './client';
import type { Project, CreateProjectInput, UpdateProjectInput } from '@/types';
export const projectsApi = {
getAll: async (clientId?: string): Promise<Project[]> => {
const { data } = await apiClient.get<Project[]>('/projects', {
params: clientId ? { clientId } : undefined,
});
return data;
},
create: async (input: CreateProjectInput): Promise<Project> => {
const { data } = await apiClient.post<Project>('/projects', input);
return data;
},
update: async (id: string, input: UpdateProjectInput): Promise<Project> => {
const { data } = await apiClient.put<Project>(`/projects/${id}`, input);
return data;
},
delete: async (id: string): Promise<void> => {
await apiClient.delete(`/projects/${id}`);
},
};

View File

@@ -0,0 +1,31 @@
import apiClient from './client';
import type {
TimeEntry,
PaginatedTimeEntries,
CreateTimeEntryInput,
UpdateTimeEntryInput,
TimeEntryFilters,
} from '@/types';
export const timeEntriesApi = {
getAll: async (filters?: TimeEntryFilters): Promise<PaginatedTimeEntries> => {
const { data } = await apiClient.get<PaginatedTimeEntries>('/time-entries', {
params: filters,
});
return data;
},
create: async (input: CreateTimeEntryInput): Promise<TimeEntry> => {
const { data } = await apiClient.post<TimeEntry>('/time-entries', input);
return data;
},
update: async (id: string, input: UpdateTimeEntryInput): Promise<TimeEntry> => {
const { data } = await apiClient.put<TimeEntry>(`/time-entries/${id}`, input);
return data;
},
delete: async (id: string): Promise<void> => {
await apiClient.delete(`/time-entries/${id}`);
},
};

30
frontend/src/api/timer.ts Normal file
View File

@@ -0,0 +1,30 @@
import apiClient from './client';
import type { OngoingTimer, TimeEntry } from '@/types';
export const timerApi = {
getOngoing: async (): Promise<OngoingTimer | null> => {
const { data } = await apiClient.get<OngoingTimer | null>('/timer');
return data;
},
start: async (projectId?: string): Promise<OngoingTimer> => {
const { data } = await apiClient.post<OngoingTimer>('/timer/start', {
projectId,
});
return data;
},
update: async (projectId?: string | null): Promise<OngoingTimer> => {
const { data } = await apiClient.put<OngoingTimer>('/timer', {
projectId,
});
return data;
},
stop: async (projectId?: string): Promise<TimeEntry> => {
const { data } = await apiClient.post<TimeEntry>('/timer/stop', {
projectId,
});
return data;
},
};