/** * Client-side API helper for the Tracker Service. * Uses @bytelyst/api-client shared package. * All calls go through Next.js API routes to keep tokens server-side. */ import { createApiClient } from '@bytelyst/api-client'; export interface TrackerItem { id: string; productId: string; type: 'bug' | 'feature' | 'task'; status: 'open' | 'in_progress' | 'done' | 'closed' | 'wont_fix'; priority: 'critical' | 'high' | 'medium' | 'low'; title: string; description: string; labels: string[]; assignee: string | null; reportedBy: string; source: 'internal' | 'user_submitted' | 'auto_detected'; visibility: 'internal' | 'public'; voteCount: number; commentCount: number; targetRelease: string | null; createdAt: string; updatedAt: string; } export interface Comment { id: string; itemId: string; productId: string; authorId: string; authorEmail: string | null; body: string; createdAt: string; updatedAt: string; } export interface TrackerStats { productId: string; total: number; byType: Record; byStatus: Record; byPriority: Record; } export interface ListItemsResponse { items: TrackerItem[]; total: number; limit: number; offset: number; } const trackerApi = createApiClient({ baseUrl: '/api/tracker', getToken: () => (typeof window !== 'undefined' ? localStorage.getItem('tracker_token') : null), }); /** Wrap apiFetch to inject x-product-id header from localStorage. */ function apiFetch(path: string, options?: RequestInit): Promise { const extra: Record = {}; if (typeof window !== 'undefined') { const pid = localStorage.getItem('tracker_selected_product'); if (pid) extra['x-product-id'] = pid; } return trackerApi.fetch(path, { ...options, headers: { ...extra, ...(options?.headers as Record) }, }); } export async function listItems(params?: Record): Promise { const qs = params ? `?${new URLSearchParams(params).toString()}` : ''; return apiFetch(`/items${qs}`); } export async function getItem(id: string): Promise { return apiFetch(`/items/${id}`); } export async function createItem(data: Partial): Promise { return apiFetch('/items', { method: 'POST', body: JSON.stringify(data) }); } export async function updateItem(id: string, data: Partial): Promise { return apiFetch(`/items/${id}`, { method: 'PUT', body: JSON.stringify(data) }); } export async function updateItemStatus(id: string, status: string): Promise { return apiFetch(`/items/${id}/status`, { method: 'PATCH', body: JSON.stringify({ status }) }); } export async function deleteItem(id: string): Promise { await apiFetch(`/items/${id}`, { method: 'DELETE' }); } export async function getStats(productId?: string): Promise { const qs = productId ? `?productId=${productId}` : ''; return apiFetch(`/items/stats${qs}`); } export async function listComments( itemId: string ): Promise<{ comments: Comment[]; count: number }> { return apiFetch(`/items/${itemId}/comments`); } export async function addComment(itemId: string, body: string): Promise { return apiFetch(`/items/${itemId}/comments`, { method: 'POST', body: JSON.stringify({ body }) }); } export async function toggleVote(itemId: string): Promise<{ voted: boolean; voteCount: number }> { return apiFetch(`/items/${itemId}/vote`, { method: 'POST' }); } // ── Public Roadmap API (no auth required) ──────────────────────────── const publicApi = createApiClient({ baseUrl: '/api/tracker' }); const publicFetch = publicApi.fetch; export interface PublicRoadmapStats { total: number; byStatus: Record; byType: Record; totalVotes: number; } export async function getRoadmapItems(params?: Record): Promise { const qs = params ? `?${new URLSearchParams(params).toString()}` : ''; return publicFetch(`/public/roadmap${qs}`); } export async function getRoadmapStats(productId?: string): Promise { const qs = productId ? `?productId=${productId}` : ''; return publicFetch(`/public/roadmap/stats${qs}`); } export async function getPublicItem(id: string): Promise { return publicFetch(`/public/items/${id}`); } export async function submitPublicItem(data: { type?: string; title: string; description?: string; email: string; name: string; }): Promise<{ id: string; title: string; status: string }> { return publicFetch('/public/submit', { method: 'POST', body: JSON.stringify(data) }); } export async function publicVote( itemId: string, email: string ): Promise<{ voted: boolean; voteCount: number }> { return publicFetch(`/public/items/${itemId}/vote`, { method: 'POST', body: JSON.stringify({ email }), }); }