dd0209898b
- SearchWidget: provider dropdown, text input, button; opens search in new tab via window.open (D-14/D-15) - NoteWidget: MDEditor with compact toolbar, debounced autosave (1500ms), AbortController for in-flight cancellation (D-16/D-17/D-18) - rehype-sanitize enabled for Markdown XSS prevention (T-05-05) - Widget registry updated with wireSearchWidget/wireNoteWidget (no more placeholders) - dashboard-api.ts: added fetchSearchProviders, addSearchProvider, removeSearchProvider, signal support on updateWidgetConfig - 9 new tests passing (search: 5, note: 4) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
113 lines
3.3 KiB
TypeScript
113 lines
3.3 KiB
TypeScript
/**
|
|
* Dashboard API client functions.
|
|
* All calls use credentials: 'include' for cookie-based auth.
|
|
*/
|
|
|
|
const API_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3001';
|
|
|
|
export async function fetchLayout(): Promise<Record<string, unknown>> {
|
|
const res = await fetch(`${API_URL}/dashboard/layout`, {
|
|
credentials: 'include',
|
|
});
|
|
if (!res.ok) throw new Error('Failed to fetch layout');
|
|
return res.json();
|
|
}
|
|
|
|
export async function saveLayout(
|
|
layouts: Record<string, unknown>,
|
|
): Promise<void> {
|
|
const res = await fetch(`${API_URL}/dashboard/layout`, {
|
|
method: 'PUT',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
credentials: 'include',
|
|
body: JSON.stringify({ layouts }),
|
|
});
|
|
if (!res.ok) throw new Error('Failed to save layout');
|
|
}
|
|
|
|
export async function fetchWidgets(): Promise<
|
|
Array<{ id: string; widgetType: string; config: Record<string, unknown> }>
|
|
> {
|
|
const res = await fetch(`${API_URL}/dashboard/widgets`, {
|
|
credentials: 'include',
|
|
});
|
|
if (!res.ok) throw new Error('Failed to fetch widgets');
|
|
return res.json();
|
|
}
|
|
|
|
export async function addWidget(
|
|
widgetType: string,
|
|
): Promise<{ id: string; widgetType: string; config: Record<string, unknown> }> {
|
|
const res = await fetch(`${API_URL}/dashboard/widgets`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
credentials: 'include',
|
|
body: JSON.stringify({ widgetType }),
|
|
});
|
|
if (!res.ok) throw new Error('Failed to add widget');
|
|
return res.json();
|
|
}
|
|
|
|
export async function removeWidget(id: string): Promise<void> {
|
|
const res = await fetch(`${API_URL}/dashboard/widgets/${id}`, {
|
|
method: 'DELETE',
|
|
credentials: 'include',
|
|
});
|
|
if (!res.ok) throw new Error('Failed to remove widget');
|
|
}
|
|
|
|
export async function updateWidgetConfig(
|
|
id: string,
|
|
config: Record<string, unknown>,
|
|
signal?: AbortSignal,
|
|
): Promise<void> {
|
|
const res = await fetch(`${API_URL}/dashboard/widgets/${id}/config`, {
|
|
method: 'PATCH',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
credentials: 'include',
|
|
body: JSON.stringify({ config }),
|
|
signal,
|
|
});
|
|
if (!res.ok) throw new Error('Failed to update widget config');
|
|
}
|
|
|
|
// --- Search Provider API (05-02) ---
|
|
|
|
export interface SearchProviderResponse {
|
|
id: string;
|
|
name: string;
|
|
urlTemplate: string;
|
|
isDefault: boolean;
|
|
userId?: string | null;
|
|
}
|
|
|
|
export async function fetchSearchProviders(): Promise<SearchProviderResponse[]> {
|
|
const res = await fetch(`${API_URL}/dashboard/search-providers`, {
|
|
credentials: 'include',
|
|
});
|
|
if (!res.ok) throw new Error('Failed to fetch search providers');
|
|
return res.json();
|
|
}
|
|
|
|
export async function addSearchProvider(payload: {
|
|
name: string;
|
|
urlTemplate: string;
|
|
}): Promise<SearchProviderResponse> {
|
|
const res = await fetch(`${API_URL}/dashboard/search-providers`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
credentials: 'include',
|
|
body: JSON.stringify(payload),
|
|
});
|
|
if (!res.ok) throw new Error('Failed to add search provider');
|
|
return res.json();
|
|
}
|
|
|
|
export async function removeSearchProvider(id: string): Promise<void> {
|
|
const res = await fetch(`${API_URL}/dashboard/search-providers/${id}`, {
|
|
method: 'DELETE',
|
|
credentials: 'include',
|
|
});
|
|
if (!res.ok) throw new Error('Failed to remove search provider');
|
|
}
|