feat(08-03): FavoritesWidget frontend + API client + page.tsx wiring (GREEN)
- favorites-api.ts: FavoriteLink type + fetchFavorites/createFavorite/updateFavorite/deleteFavorite all use credentials: include and API_URL/favorites - favorites-widget.tsx: list/grid view, inline add/edit/delete in edit mode, icon + letter fallback, rel=noreferrer + target=_blank, no dangerouslySetInnerHTML (T-08-07) - useEffect deps fixed to [instanceId] only — excludes t() to prevent re-fetch on each render - page.tsx: wireFavoritesWidget(FavoritesWidget) wired - Full test suite: 81/81 pass (17 test files) - Web TypeScript: clean
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
/**
|
||||
* Favorites API client functions.
|
||||
* Mirrors the NestJS FavoritesController routes (08-03).
|
||||
* All calls use credentials: 'include' for cookie-based auth.
|
||||
*/
|
||||
|
||||
const API_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3001';
|
||||
|
||||
export interface FavoriteLink {
|
||||
id: string;
|
||||
widgetId: string;
|
||||
title: string;
|
||||
url: string;
|
||||
iconUrl: string | null;
|
||||
position: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch all favorite links for a specific widget instance.
|
||||
* The widgetId parameter scopes the query to the correct widget (Pitfall 3).
|
||||
*/
|
||||
export async function fetchFavorites(widgetId: string): Promise<FavoriteLink[]> {
|
||||
const res = await fetch(
|
||||
`${API_URL}/favorites?widgetId=${encodeURIComponent(widgetId)}`,
|
||||
{ credentials: 'include' },
|
||||
);
|
||||
if (!res.ok) throw new Error('Failed to fetch favorites');
|
||||
|
||||
return res.json();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new favorite link.
|
||||
* Server-side icon discovery runs automatically if iconUrl is not provided.
|
||||
*/
|
||||
export async function createFavorite(payload: {
|
||||
widgetId: string;
|
||||
title: string;
|
||||
url: string;
|
||||
iconUrl?: string;
|
||||
}): Promise<FavoriteLink> {
|
||||
const res = await fetch(`${API_URL}/favorites`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
credentials: 'include',
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
if (!res.ok) throw new Error('Failed to create favorite');
|
||||
|
||||
return res.json();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update an existing favorite link.
|
||||
* Pass iconUrl: null to clear a stored icon.
|
||||
*/
|
||||
export async function updateFavorite(
|
||||
id: string,
|
||||
payload: Partial<{ title: string; url: string; iconUrl: string | null; position: number }>,
|
||||
): Promise<FavoriteLink> {
|
||||
const res = await fetch(`${API_URL}/favorites/${encodeURIComponent(id)}`, {
|
||||
method: 'PATCH',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
credentials: 'include',
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
if (!res.ok) throw new Error('Failed to update favorite');
|
||||
|
||||
return res.json();
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a favorite link by id.
|
||||
*/
|
||||
export async function deleteFavorite(id: string): Promise<void> {
|
||||
const res = await fetch(`${API_URL}/favorites/${encodeURIComponent(id)}`, {
|
||||
method: 'DELETE',
|
||||
credentials: 'include',
|
||||
});
|
||||
if (!res.ok) throw new Error('Failed to delete favorite');
|
||||
}
|
||||
Reference in New Issue
Block a user