This commit is contained in:
Nông Đức Huy
2026-08-13 23:20:23 +07:00
parent d9f159a8c3
commit cda5d21d0d
53 changed files with 2711 additions and 162 deletions
+3
View File
@@ -1,4 +1,5 @@
import { HttpClient, type HttpClientOptions } from './http-client';
import { createAccountResource, type AccountResource } from './resources/account';
import { createAdminResource, type AdminResource } from './resources/admin';
import { createAuthResource, type AuthResource } from './resources/auth';
import { createCatalogResource, type CatalogResource } from './resources/catalog';
@@ -45,6 +46,7 @@ export interface ApiClient {
readonly promotionsAdmin: PromotionsAdminResource;
readonly reviews: ReviewsResource;
readonly reviewsAdmin: ReviewsAdminResource;
readonly account: AccountResource;
readonly content: ContentResource;
readonly contentAdmin: ContentAdminResource;
}
@@ -64,6 +66,7 @@ export function createApiClient(options: HttpClientOptions): ApiClient {
promotionsAdmin: createPromotionsAdminResource(http),
reviews: createReviewsResource(http),
reviewsAdmin: createReviewsAdminResource(http),
account: createAccountResource(http),
content: createContentResource(http),
contentAdmin: createContentAdminResource(http),
};
+2 -1
View File
@@ -16,7 +16,7 @@ export { HttpClient } from './http-client';
export type { HttpClientOptions, RequestOptions } from './http-client';
export { createApiClient } from './create-client';
export type { CatalogResource, ProductListQuery } from './resources/catalog';
export type { AuthResource, LoginCredentials } from './resources/auth';
export type { AuthResource, LoginCredentials, RegisterPayload } from './resources/auth';
export type {
AdminResource,
CreateUserPayload,
@@ -58,4 +58,5 @@ export type {
ContentTranslationPayload,
PostListParams,
} from './resources/content';
export type { AccountResource, AddressPayload, ProfilePayload } from './resources/account';
export type { ApiClient } from './create-client';
@@ -0,0 +1,89 @@
import type {
CustomerAddress,
CustomerProfile,
OffsetPaginated,
Order,
OrderListItem,
} from '@sport/types';
import type { HttpClient } from '../http-client';
export interface ProfilePayload {
firstName?: string;
lastName?: string;
phone?: string | null;
acceptsMarketing?: boolean;
dateOfBirth?: string | null;
}
export interface AddressPayload {
fullName: string;
phone: string;
line1: string;
line2?: string | null;
ward?: string | null;
district?: string | null;
province: string;
countryCode?: string;
postalCode?: string | null;
isDefaultShipping?: boolean;
isDefaultBilling?: boolean;
}
/**
* The signed-in shopper's own account.
*
* Every method is implicitly scoped to the bearer token — there is no customer
* id parameter anywhere, because the server takes it from the session.
*/
export interface AccountResource {
getProfile(): Promise<CustomerProfile>;
updateProfile(payload: ProfilePayload): Promise<CustomerProfile>;
listAddresses(): Promise<CustomerAddress[]>;
createAddress(payload: AddressPayload): Promise<CustomerAddress>;
updateAddress(id: string, payload: AddressPayload): Promise<CustomerAddress>;
deleteAddress(id: string): Promise<void>;
listOrders(params?: { page?: number; perPage?: number }): Promise<OffsetPaginated<OrderListItem>>;
getOrder(id: string): Promise<Order>;
listWishlist(): Promise<string[]>;
addToWishlist(productId: string): Promise<{ ok: true }>;
removeFromWishlist(productId: string): Promise<{ ok: true }>;
}
export function createAccountResource(http: HttpClient): AccountResource {
// Never cached. Everything here is one person's own data, and a stale
// address book is one that shows a home they moved out of.
const uncached = { cache: 'no-store' } as const;
return {
getProfile: () => http.get<CustomerProfile>('/account/profile', uncached),
updateProfile: (payload) => http.patch<CustomerProfile>('/account/profile', payload, uncached),
listAddresses: () => http.get<CustomerAddress[]>('/account/addresses', uncached),
createAddress: (payload) => http.post<CustomerAddress>('/account/addresses', payload, uncached),
updateAddress: (id, payload) =>
http.patch<CustomerAddress>(
`/account/addresses/${encodeURIComponent(id)}`,
payload,
uncached,
),
deleteAddress: (id) =>
http.delete<void>(`/account/addresses/${encodeURIComponent(id)}`, uncached),
listOrders: (params = {}) =>
http.get<OffsetPaginated<OrderListItem>>('/account/orders', {
...uncached,
query: { ...params },
}),
getOrder: (id) => http.get<Order>(`/account/orders/${encodeURIComponent(id)}`, uncached),
listWishlist: () => http.get<string[]>('/account/wishlist', uncached),
addToWishlist: (productId) =>
http.post<{ ok: true }>('/account/wishlist', { productId }, uncached),
removeFromWishlist: (productId) =>
http.delete<{ ok: true }>(`/account/wishlist/${encodeURIComponent(productId)}`, uncached),
};
}
+12
View File
@@ -12,8 +12,19 @@ export interface LoginCredentials {
* cookies for separate audiences. Exposing them as one method with a flag would
* make it far too easy to point a customer credential at the admin surface.
*/
export interface RegisterPayload {
email: string;
password: string;
firstName: string;
lastName: string;
phone?: string;
acceptsMarketing?: boolean;
}
export interface AuthResource {
login(credentials: LoginCredentials): Promise<LoginResult>;
/** Storefront only — there is deliberately no way to self-register staff. */
register(payload: RegisterPayload): Promise<LoginResult>;
adminLogin(credentials: LoginCredentials): Promise<LoginResult>;
refresh(): Promise<RefreshResult>;
adminRefresh(): Promise<RefreshResult>;
@@ -40,6 +51,7 @@ export function createAuthResource(http: HttpClient): AuthResource {
return {
login: (credentials) => http.post<LoginResult>('/auth/login', credentials, uncached),
register: (payload) => http.post<LoginResult>('/auth/register', payload, uncached),
adminLogin: (credentials) => http.post<LoginResult>('/auth/admin/login', credentials, uncached),
refresh: () => http.post<RefreshResult>('/auth/refresh', undefined, uncached),
adminRefresh: () => http.post<RefreshResult>('/auth/admin/refresh', undefined, uncached),
@@ -18,6 +18,7 @@ import type { HttpClient, RequestOptions } from '../http-client';
* for callers, never the enforcement point.
*/
export interface ProductListQuery {
ids?: string[];
q?: string;
categorySlug?: string;
collectionSlug?: string;