learning_ai_common_plat/dashboards/tracker-web/src/lib/tracker-client.ts
saravanakumardb1 2d54795c30 feat(dashboards): migrate admin + tracker dashboards to common-plat as product-agnostic
- Copy admin-dashboard-web → dashboards/admin-web
- Copy tracker-dashboard-web → dashboards/tracker-web
- Update pnpm-workspace.yaml to include dashboards/*
- Replace file: refs with workspace:* for @bytelyst/* packages
- Replace all hardcoded LysnrAI/lysnn.com branding with generic platform refs
- Make telemetry use NEXT_PUBLIC_PRODUCT_ID / PRODUCT_ID env vars
- Update mock credentials, seed data, invitation codes, placeholders
- Update READMEs, e2e tests, unit tests for product-agnostic content
- Both dashboards pass tsc --noEmit clean
2026-02-28 02:17:35 -08:00

151 lines
4.5 KiB
TypeScript

/**
* 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<string, number>;
byStatus: Record<string, number>;
byPriority: Record<string, number>;
}
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),
});
const apiFetch = trackerApi.fetch;
export async function listItems(params?: Record<string, string>): Promise<ListItemsResponse> {
const qs = params ? `?${new URLSearchParams(params).toString()}` : '';
return apiFetch(`/items${qs}`);
}
export async function getItem(id: string): Promise<TrackerItem> {
return apiFetch(`/items/${id}`);
}
export async function createItem(data: Partial<TrackerItem>): Promise<TrackerItem> {
return apiFetch('/items', { method: 'POST', body: JSON.stringify(data) });
}
export async function updateItem(id: string, data: Partial<TrackerItem>): Promise<TrackerItem> {
return apiFetch(`/items/${id}`, { method: 'PUT', body: JSON.stringify(data) });
}
export async function updateItemStatus(id: string, status: string): Promise<TrackerItem> {
return apiFetch(`/items/${id}/status`, { method: 'PATCH', body: JSON.stringify({ status }) });
}
export async function deleteItem(id: string): Promise<void> {
await apiFetch(`/items/${id}`, { method: 'DELETE' });
}
export async function getStats(productId?: string): Promise<TrackerStats> {
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<Comment> {
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<string, number>;
byType: Record<string, number>;
totalVotes: number;
}
export async function getRoadmapItems(params?: Record<string, string>): Promise<ListItemsResponse> {
const qs = params ? `?${new URLSearchParams(params).toString()}` : '';
return publicFetch(`/public/roadmap${qs}`);
}
export async function getRoadmapStats(productId?: string): Promise<PublicRoadmapStats> {
const qs = productId ? `?productId=${productId}` : '';
return publicFetch(`/public/roadmap/stats${qs}`);
}
export async function getPublicItem(id: string): Promise<TrackerItem> {
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 }),
});
}