Phase 9
This commit is contained in:
parent
e396f5088c
commit
a1801af63b
36 changed files with 4783 additions and 31 deletions
|
|
@ -11,6 +11,10 @@ class ApiClient {
|
|||
return this._accessToken !== null;
|
||||
}
|
||||
|
||||
public get baseUrl(): string {
|
||||
return BASE_URL;
|
||||
}
|
||||
|
||||
private getHeaders(): Record<string, string> {
|
||||
const headers: Record<string, string> = {
|
||||
'Content-Type': 'application/json',
|
||||
|
|
|
|||
71
packages/web/src/services/prices.ts
Normal file
71
packages/web/src/services/prices.ts
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
import { apiClient } from './api-client';
|
||||
import type { z } from 'zod/v4';
|
||||
import type {
|
||||
PriceRecordResponseSchema,
|
||||
PriceHistoryResponseSchema,
|
||||
FoodStoreComparisonResponseSchema,
|
||||
FoodSpendingAnalyticsResponseSchema,
|
||||
CreatePriceRecordSchema,
|
||||
BulkPriceRecordInputSchema,
|
||||
} from '@meshitrack/shared';
|
||||
|
||||
type PriceRecordResponse = z.infer<typeof PriceRecordResponseSchema>;
|
||||
type PriceHistoryResponse = z.infer<typeof PriceHistoryResponseSchema>;
|
||||
type FoodStoreComparisonResponse = z.infer<typeof FoodStoreComparisonResponseSchema>;
|
||||
type FoodSpendingAnalyticsResponse = z.infer<typeof FoodSpendingAnalyticsResponseSchema>;
|
||||
type CreatePriceRecordInput = z.infer<typeof CreatePriceRecordSchema>;
|
||||
type BulkPriceRecordInput = z.infer<typeof BulkPriceRecordInputSchema>;
|
||||
|
||||
export async function recordPrice(
|
||||
householdId: string,
|
||||
data: CreatePriceRecordInput
|
||||
): Promise<PriceRecordResponse> {
|
||||
return apiClient.post<PriceRecordResponse>(`/households/${householdId}/prices`, data);
|
||||
}
|
||||
|
||||
export async function recordBulkPrices(
|
||||
householdId: string,
|
||||
data: BulkPriceRecordInput
|
||||
): Promise<PriceRecordResponse[]> {
|
||||
return apiClient.post<PriceRecordResponse[]>(`/households/${householdId}/prices/bulk`, data);
|
||||
}
|
||||
|
||||
export async function getPriceHistory(
|
||||
householdId: string,
|
||||
productId: string,
|
||||
query?: {
|
||||
storeId?: string;
|
||||
startDate?: string;
|
||||
endDate?: string;
|
||||
cursor?: string;
|
||||
limit?: number;
|
||||
}
|
||||
): Promise<PriceHistoryResponse> {
|
||||
const params = new URLSearchParams();
|
||||
if (query?.storeId) params.set('storeId', query.storeId);
|
||||
if (query?.startDate) params.set('startDate', query.startDate);
|
||||
if (query?.endDate) params.set('endDate', query.endDate);
|
||||
if (query?.cursor) params.set('cursor', query.cursor);
|
||||
if (query?.limit) params.set('limit', String(query.limit));
|
||||
const qs = params.toString();
|
||||
return apiClient.get<PriceHistoryResponse>(
|
||||
`/households/${householdId}/prices/history/${productId}${qs ? `?${qs}` : ''}`
|
||||
);
|
||||
}
|
||||
|
||||
export async function compareStores(
|
||||
householdId: string,
|
||||
productId: string
|
||||
): Promise<FoodStoreComparisonResponse> {
|
||||
return apiClient.get<FoodStoreComparisonResponse>(
|
||||
`/households/${householdId}/prices/compare/${productId}`
|
||||
);
|
||||
}
|
||||
|
||||
export async function getPriceAnalytics(
|
||||
householdId: string
|
||||
): Promise<FoodSpendingAnalyticsResponse> {
|
||||
return apiClient.get<FoodSpendingAnalyticsResponse>(
|
||||
`/households/${householdId}/prices/analytics`
|
||||
);
|
||||
}
|
||||
119
packages/web/src/services/shopping-lists.ts
Normal file
119
packages/web/src/services/shopping-lists.ts
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
import { apiClient } from './api-client';
|
||||
import type { z } from 'zod/v4';
|
||||
import type {
|
||||
ShoppingListResponseSchema,
|
||||
CreateShoppingListSchema,
|
||||
UpdateShoppingListSchema,
|
||||
AddShoppingItemSchema,
|
||||
UpdateShoppingItemSchema,
|
||||
ShoppingListSyncToPantryResponseSchema,
|
||||
BasketStoreComparisonResponseSchema,
|
||||
} from '@meshitrack/shared';
|
||||
|
||||
type ShoppingListResponse = z.infer<typeof ShoppingListResponseSchema>;
|
||||
type CreateShoppingListInput = z.infer<typeof CreateShoppingListSchema>;
|
||||
type UpdateShoppingListInput = z.infer<typeof UpdateShoppingListSchema>;
|
||||
type AddShoppingItemInput = z.infer<typeof AddShoppingItemSchema>;
|
||||
type UpdateShoppingItemInput = z.infer<typeof UpdateShoppingItemSchema>;
|
||||
type ShoppingListSyncToPantryResponse = z.infer<typeof ShoppingListSyncToPantryResponseSchema>;
|
||||
type BasketStoreComparisonResponse = z.infer<typeof BasketStoreComparisonResponseSchema>;
|
||||
|
||||
export async function getShoppingLists(householdId: string): Promise<ShoppingListResponse[]> {
|
||||
return apiClient.get<ShoppingListResponse[]>(`/households/${householdId}/shopping-lists`);
|
||||
}
|
||||
|
||||
export async function getShoppingList(householdId: string, id: string): Promise<ShoppingListResponse> {
|
||||
return apiClient.get<ShoppingListResponse>(`/households/${householdId}/shopping-lists/${id}`);
|
||||
}
|
||||
|
||||
export async function createShoppingList(
|
||||
householdId: string,
|
||||
data: CreateShoppingListInput
|
||||
): Promise<ShoppingListResponse> {
|
||||
return apiClient.post<ShoppingListResponse>(`/households/${householdId}/shopping-lists`, data);
|
||||
}
|
||||
|
||||
export async function updateShoppingList(
|
||||
householdId: string,
|
||||
id: string,
|
||||
data: UpdateShoppingListInput
|
||||
): Promise<ShoppingListResponse> {
|
||||
return apiClient.patch<ShoppingListResponse>(`/households/${householdId}/shopping-lists/${id}`, data);
|
||||
}
|
||||
|
||||
export async function deleteShoppingList(householdId: string, id: string): Promise<void> {
|
||||
return apiClient.delete<void>(`/households/${householdId}/shopping-lists/${id}`);
|
||||
}
|
||||
|
||||
// -- Nested Item Operations --
|
||||
|
||||
export async function addShoppingItem(
|
||||
householdId: string,
|
||||
id: string,
|
||||
data: AddShoppingItemInput
|
||||
): Promise<ShoppingListResponse> {
|
||||
return apiClient.post<ShoppingListResponse>(`/households/${householdId}/shopping-lists/${id}/items`, data);
|
||||
}
|
||||
|
||||
export async function updateShoppingItem(
|
||||
householdId: string,
|
||||
id: string,
|
||||
itemId: string,
|
||||
data: UpdateShoppingItemInput
|
||||
): Promise<ShoppingListResponse> {
|
||||
return apiClient.patch<ShoppingListResponse>(
|
||||
`/households/${householdId}/shopping-lists/${id}/items/${itemId}`,
|
||||
data
|
||||
);
|
||||
}
|
||||
|
||||
export async function removeShoppingItem(
|
||||
householdId: string,
|
||||
id: string,
|
||||
itemId: string
|
||||
): Promise<ShoppingListResponse> {
|
||||
return apiClient.delete<ShoppingListResponse>(
|
||||
`/households/${householdId}/shopping-lists/${id}/items/${itemId}`
|
||||
);
|
||||
}
|
||||
|
||||
// -- Workflows --
|
||||
|
||||
export async function generateFromMealPlan(
|
||||
householdId: string,
|
||||
mealPlanId: string
|
||||
): Promise<ShoppingListResponse> {
|
||||
return apiClient.post<ShoppingListResponse>(
|
||||
`/households/${householdId}/shopping-lists/from-meal-plan/${mealPlanId}`
|
||||
);
|
||||
}
|
||||
|
||||
export async function syncToPantry(
|
||||
householdId: string,
|
||||
id: string
|
||||
): Promise<ShoppingListSyncToPantryResponse> {
|
||||
return apiClient.post<ShoppingListSyncToPantryResponse>(
|
||||
`/households/${householdId}/shopping-lists/${id}/sync-to-pantry`
|
||||
);
|
||||
}
|
||||
|
||||
export async function getBasketStoreComparison(
|
||||
householdId: string,
|
||||
id: string
|
||||
): Promise<BasketStoreComparisonResponse> {
|
||||
return apiClient.get<BasketStoreComparisonResponse>(
|
||||
`/households/${householdId}/shopping-lists/${id}/stores`
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates direct WebSocket path for collaborative checklist syncing.
|
||||
*/
|
||||
export function getShoppingListSyncSocketUrl(householdId: string, id: string): string {
|
||||
// Derive WS protocol based on configured API baseURL protocol (defaulting to unsafe ws for localhost)
|
||||
const baseUrl = apiClient.baseUrl || '';
|
||||
const isSecure = baseUrl.startsWith('https');
|
||||
const cleanHost = baseUrl.replace(/^https?:\/\//, '');
|
||||
const protocol = isSecure ? 'wss' : 'ws';
|
||||
return `${protocol}://${cleanHost}/households/${householdId}/shopping-lists/${id}/sync`;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue