This commit is contained in:
2026-09-19 22:19:35 +02:00
commit 231b3e006a
124 changed files with 13816 additions and 0 deletions
+163
View File
@@ -0,0 +1,163 @@
const API_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3001/api/v1';
interface ApiError {
code: string;
message: string;
}
function getTokens() {
if (typeof window === 'undefined') return { accessToken: null };
const accessToken = document.cookie
.split('; ')
.find((row) => row.startsWith('access_token='))
?.split('=')[1];
return { accessToken };
}
function setTokens(accessToken: string, refreshToken: string) {
document.cookie = `access_token=${accessToken}; path=/; max-age=${24 * 60 * 60}; SameSite=Lax`;
document.cookie = `refresh_token=${refreshToken}; path=/; max-age=${30 * 24 * 60 * 60}; SameSite=Lax`;
}
function clearTokens() {
document.cookie = 'access_token=; path=/; max-age=0';
document.cookie = 'refresh_token=; path=/; max-age=0';
}
async function refreshAccessToken(): Promise<string | null> {
const refreshToken = document.cookie
.split('; ')
.find((row) => row.startsWith('refresh_token='))
?.split('=')[1];
if (!refreshToken) return null;
try {
const res = await fetch(`${API_URL}/auth/refresh`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ refreshToken }),
});
if (!res.ok) return null;
const data = await res.json();
setTokens(data.accessToken, data.refreshToken);
return data.accessToken;
} catch {
return null;
}
}
export async function api<T = any>(
endpoint: string,
options: RequestInit = {},
): Promise<T> {
const { accessToken } = getTokens();
const headers: Record<string, string> = {
'Content-Type': 'application/json',
...(options.headers as Record<string, string>),
};
if (accessToken) {
headers['Authorization'] = `Bearer ${accessToken}`;
}
let res = await fetch(`${API_URL}${endpoint}`, {
...options,
headers,
});
if (res.status === 401 && accessToken) {
const newToken = await refreshAccessToken();
if (newToken) {
headers['Authorization'] = `Bearer ${newToken}`;
res = await fetch(`${API_URL}${endpoint}`, {
...options,
headers,
});
} else {
clearTokens();
if (typeof window !== 'undefined') {
window.location.href = '/login';
}
throw new Error('Session expired');
}
}
if (!res.ok) {
const errorBody = await res.json().catch(() => null);
const error: ApiError = errorBody?.error || {
code: 'unknown',
message: `Request failed with status ${res.status}`,
};
throw error;
}
return res.json();
}
// Auth API
export const authApi = {
login: (email: string, password: string) =>
api<{ accessToken: string; refreshToken: string; user: any }>('/auth/login', {
method: 'POST',
body: JSON.stringify({ email, password }),
}),
logout: () => {
const refreshToken = document.cookie
.split('; ')
.find((row) => row.startsWith('refresh_token='))
?.split('=')[1];
clearTokens();
if (refreshToken) {
api('/auth/logout', {
method: 'POST',
body: JSON.stringify({ refreshToken }),
}).catch(() => {});
}
},
};
// Tabs API
export const tabsApi = {
list: (venueId: string) =>
api<{ tabs: any[] }>(`/venues/${venueId}/tabs`),
};
// Devices API
export const devicesApi = {
list: (venueId: string) =>
api<{ devices: any[] }>(`/venues/${venueId}/devices`),
revoke: (venueId: string, deviceId: string) =>
api(`/venues/${venueId}/devices/${deviceId}/revoke`, { method: 'POST' }),
generateCode: () => api<{ code: string; expiresAt: string }>('/devices/generation-code'),
};
// Commands API
export const commandsApi = {
list: (venueId: string, params?: { status?: string; limit?: number; offset?: number }) => {
const searchParams = new URLSearchParams();
if (params?.status) searchParams.set('status', params.status);
if (params?.limit) searchParams.set('limit', String(params.limit));
if (params?.offset) searchParams.set('offset', String(params.offset));
const qs = searchParams.toString();
return api<{ commands: any[]; total: number }>(
`/venues/${venueId}/commands${qs ? `?${qs}` : ''}`,
);
},
create: (venueId: string, data: { tabId: string; type: string; expectedVersion: number; paymentMethod: string }) =>
api<{ id: string; status: string; warning?: string }>(`/venues/${venueId}/commands`, {
method: 'POST',
body: JSON.stringify(data),
}),
};
// Venues API
export const venuesApi = {
list: () => api<{ id: string; name: string }[]>('/venues'),
get: (id: string) => api<{ id: string; name: string }>(`/venues/${id}`),
};
export { setTokens, clearTokens };