feat(api): add shop management clients

This commit is contained in:
zetaloop
2026-04-25 14:49:25 +08:00
parent e559204347
commit 358bfc7ac9
3 changed files with 193 additions and 7 deletions
+14 -1
View File
@@ -21,7 +21,20 @@ export {
listServicesByPlayer,
updatePlayerService,
} from "./services"
export { getShopById, getShopByOwnerId, listShops } from "./shops"
export {
addShopAnnouncement,
deleteShopAnnouncement,
getMyShop,
getShopById,
getShopByOwnerId,
getShopIncomeStats,
inviteShopPlayer,
listShopInvitations,
listShops,
removeShopPlayer,
updateShop,
updateShopTemplate,
} from "./shops"
export {
getWalletBalance,
listWalletTransactions,
+155 -6
View File
@@ -1,10 +1,11 @@
import { getDefaultShopSections } from "@/lib/domain/shop-template"
import { isApiError } from "@/lib/errors"
import type { Shop } from "@/lib/types"
import type { Shop, ShopTemplateConfig } from "@/lib/types"
import { httpJson } from "./http"
type Paginated<T> = {
items: T[]
items: T[] | null
meta: {
total: number
offset: number
@@ -12,18 +13,101 @@ type Paginated<T> = {
}
}
type ShopResponse = Omit<Shop, "announcements" | "templateConfig"> & {
announcements?: string[] | null
templateConfig?: ShopTemplateConfig | null
}
export type ShopUpdateInput = Pick<
Shop,
| "name"
| "description"
| "commissionType"
| "commissionValue"
| "allowMultiShop"
| "allowIndependentOrders"
| "dispatchMode"
>
export type ShopInvitation = {
id: number
shopId: number
playerId: number
status: string
invitedBy: number
createdAt: number
respondedAt?: number
}
export type ShopIncomeStats = {
monthlyIncome: string
pendingSettlement: string
totalWithdrawn: string
totalOrders: number
completedOrders: number
}
type ShopInvitationList = {
items: ShopInvitation[] | null
}
function normalizeAnnouncement(value: string): string {
const trimmed = value.trim()
if (!trimmed.startsWith('"') || !trimmed.endsWith('"')) return value
try {
const parsed = JSON.parse(trimmed) as unknown
return typeof parsed === "string" ? parsed : value
} catch {
return value
}
}
function normalizeShop(shop: ShopResponse): Shop {
const templateConfig = shop.templateConfig ?? { sections: getDefaultShopSections() }
return {
...shop,
announcements: (shop.announcements ?? []).map(normalizeAnnouncement),
templateConfig: {
...templateConfig,
sections: Array.isArray(templateConfig.sections)
? templateConfig.sections
: getDefaultShopSections(),
},
}
}
export async function listShops(): Promise<Shop[]> {
const res = await httpJson<Paginated<Shop>>("/api/v1/shops?offset=0&limit=100", {
const res = await httpJson<Paginated<ShopResponse>>("/api/v1/shops?offset=0&limit=100", {
cache: "no-store",
})
return res.items
return (res.items ?? []).map(normalizeShop)
}
export async function getMyShop(): Promise<Shop | undefined> {
try {
const shop = await httpJson<ShopResponse>("/api/v1/shops/mine", {
cache: "no-store",
})
return normalizeShop(shop)
} catch (error) {
if (error instanceof Error && error.message === "UNAUTHORIZED") {
throw error
}
if (isApiError(error) && error.code === 404) {
return undefined
}
throw error
}
}
export async function getShopById(shopId: string): Promise<Shop | undefined> {
try {
return await httpJson<Shop>(`/api/v1/shops/${encodeURIComponent(shopId)}`, {
const shop = await httpJson<ShopResponse>(`/api/v1/shops/${encodeURIComponent(shopId)}`, {
cache: "no-store",
})
return normalizeShop(shop)
} catch (error) {
if (error instanceof Error && error.message === "UNAUTHORIZED") {
throw error
@@ -37,9 +121,10 @@ export async function getShopById(shopId: string): Promise<Shop | undefined> {
export async function getShopByOwnerId(ownerId: string): Promise<Shop | undefined> {
try {
return await httpJson<Shop>(`/api/v1/users/${encodeURIComponent(ownerId)}/shop`, {
const shop = await httpJson<ShopResponse>(`/api/v1/users/${encodeURIComponent(ownerId)}/shop`, {
cache: "no-store",
})
return normalizeShop(shop)
} catch (error) {
if (error instanceof Error && error.message === "UNAUTHORIZED") {
throw error
@@ -50,3 +135,67 @@ export async function getShopByOwnerId(ownerId: string): Promise<Shop | undefine
throw error
}
}
export async function updateShop(shopId: string, input: ShopUpdateInput): Promise<Shop> {
const shop = await httpJson<ShopResponse>(`/api/v1/shops/${encodeURIComponent(shopId)}`, {
method: "PUT",
json: input,
})
return normalizeShop(shop)
}
export async function updateShopTemplate(
shopId: string,
templateConfig: ShopTemplateConfig,
): Promise<void> {
await httpJson<unknown>(`/api/v1/shops/${encodeURIComponent(shopId)}/template`, {
method: "PUT",
json: {
sections: JSON.stringify(templateConfig),
},
})
}
export async function addShopAnnouncement(shopId: string, content: string): Promise<void> {
await httpJson<unknown>(`/api/v1/shops/${encodeURIComponent(shopId)}/announcements`, {
method: "POST",
json: { content },
})
}
export async function deleteShopAnnouncement(shopId: string, index: number): Promise<void> {
await httpJson<unknown>(
`/api/v1/shops/${encodeURIComponent(shopId)}/announcements/${encodeURIComponent(String(index))}`,
{ method: "DELETE" },
)
}
export async function inviteShopPlayer(shopId: string, playerId: string): Promise<void> {
await httpJson<unknown>(`/api/v1/shops/${encodeURIComponent(shopId)}/invitations`, {
method: "POST",
json: {
playerId: Number(playerId),
},
})
}
export async function listShopInvitations(shopId: string): Promise<ShopInvitation[]> {
const res = await httpJson<ShopInvitationList>(
`/api/v1/shops/${encodeURIComponent(shopId)}/invitations`,
{ cache: "no-store" },
)
return res.items ?? []
}
export async function removeShopPlayer(shopId: string, playerId: string): Promise<void> {
await httpJson<unknown>(
`/api/v1/shops/${encodeURIComponent(shopId)}/players/${encodeURIComponent(playerId)}`,
{ method: "DELETE" },
)
}
export async function getShopIncomeStats(shopId: string): Promise<ShopIncomeStats> {
return httpJson<ShopIncomeStats>(`/api/v1/shops/${encodeURIComponent(shopId)}/income-stats`, {
cache: "no-store",
})
}
+24
View File
@@ -0,0 +1,24 @@
import type { Shop, ShopSection } from "@/lib/types"
const defaultSectionTypes: ShopSection["type"][] = [
"banner",
"intro",
"services",
"players",
"announcements",
"reviews",
]
export function getDefaultShopSections(): ShopSection[] {
return defaultSectionTypes.map((type, order) => ({
type,
enabled: true,
order,
}))
}
export function getShopSections(shop: Pick<Shop, "templateConfig">): ShopSection[] {
const sections = shop.templateConfig.sections
if (!Array.isArray(sections) || sections.length === 0) return getDefaultShopSections()
return [...sections].sort((a, b) => a.order - b.order)
}