84 lines
3.1 KiB
TypeScript
84 lines
3.1 KiB
TypeScript
import { clientFetch } from '@/lib/api/client';
|
|
import { unwrap, type ApiEnvelope, type Paginated } from '@/lib/api/types';
|
|
import { ADDRESSES_PAGE_SIZE } from '../constants';
|
|
import type { CreateAddressInput, CustomerAddress, CustomerAddressDto, AddressesApi } from '../types';
|
|
|
|
const BASE = '/api/v1/customer_addresses';
|
|
|
|
// REQ-009 (delivered): the wire `CustomerAddressDto` now carries `provinceId` (joined from
|
|
// `cities.province_id`), so a freshly-fetched address prefills the cascade. The caller's chosen
|
|
// province is kept as a fallback for the optimistic just-saved echo.
|
|
interface AddressWire extends CustomerAddressDto {
|
|
provinceId: number;
|
|
}
|
|
function toAddress(dto: AddressWire, provinceId?: number | null): CustomerAddress {
|
|
return { ...dto, provinceId: dto.provinceId ?? provinceId ?? null };
|
|
}
|
|
|
|
// Only the contract fields cross the wire. `latitude`/`longitude` are the picked pin (REQ-008 —
|
|
// the server geocodes today; sending the pin makes the coordinate the user's once accepted);
|
|
// `provinceId` stays client-side (city implies province server-side).
|
|
function toBody(input: CreateAddressInput) {
|
|
return {
|
|
title: input.title,
|
|
cityId: input.cityId,
|
|
districtId: input.districtId ?? null,
|
|
addressLine: input.addressLine,
|
|
postalCode: input.postalCode ?? null,
|
|
recipientName: input.recipientName ?? null,
|
|
recipientPhone: input.recipientPhone ?? null,
|
|
latitude: input.latitude ?? null,
|
|
longitude: input.longitude ?? null,
|
|
isPrimary: input.isPrimary ?? false,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Real HTTP implementation of the AddressesApi seam (b4 action-style routes). `clientFetch`
|
|
* returns the raw envelope, so each call reads its payload via `unwrap`. Selected once
|
|
* USE_ADDRESSES_MOCK is false and REQ-008/REQ-009 land.
|
|
*/
|
|
export const addressesClientApi: AddressesApi = {
|
|
list: async (params) => {
|
|
const query = new URLSearchParams();
|
|
query.set('page', String(params?.page ?? 1));
|
|
// Pagination params bind case-insensitively to the server's `PageSize` (not snake_cased, unlike the
|
|
// geo lookups' explicit `province_id`/`city_id`); match the patients template's `pageSize`.
|
|
query.set('pageSize', String(params?.pageSize ?? ADDRESSES_PAGE_SIZE));
|
|
const page = unwrap(
|
|
await clientFetch<ApiEnvelope<Paginated<AddressWire>>>(`${BASE}/list?${query.toString()}`),
|
|
);
|
|
return { ...page, items: page.items.map((dto) => toAddress(dto)) };
|
|
},
|
|
|
|
create: async (input) =>
|
|
toAddress(
|
|
unwrap(
|
|
await clientFetch<ApiEnvelope<AddressWire>>(`${BASE}/create`, {
|
|
method: 'POST',
|
|
body: JSON.stringify(toBody(input)),
|
|
}),
|
|
),
|
|
input.provinceId,
|
|
),
|
|
|
|
update: async (id, input) =>
|
|
toAddress(
|
|
unwrap(
|
|
await clientFetch<ApiEnvelope<AddressWire>>(`${BASE}/update/${id}`, {
|
|
method: 'POST',
|
|
body: JSON.stringify(toBody(input)),
|
|
}),
|
|
),
|
|
input.provinceId,
|
|
),
|
|
|
|
setPrimary: async (id) => {
|
|
await clientFetch<ApiEnvelope<boolean>>(`${BASE}/set_primary/${id}`, { method: 'POST' });
|
|
},
|
|
|
|
remove: async (id) => {
|
|
await clientFetch<ApiEnvelope<boolean>>(`${BASE}/delete/${id}`, { method: 'DELETE' });
|
|
},
|
|
};
|