feat(api): add shop management clients
This commit is contained in:
+14
-1
@@ -21,7 +21,20 @@ export {
|
|||||||
listServicesByPlayer,
|
listServicesByPlayer,
|
||||||
updatePlayerService,
|
updatePlayerService,
|
||||||
} from "./services"
|
} from "./services"
|
||||||
export { getShopById, getShopByOwnerId, listShops } from "./shops"
|
export {
|
||||||
|
addShopAnnouncement,
|
||||||
|
deleteShopAnnouncement,
|
||||||
|
getMyShop,
|
||||||
|
getShopById,
|
||||||
|
getShopByOwnerId,
|
||||||
|
getShopIncomeStats,
|
||||||
|
inviteShopPlayer,
|
||||||
|
listShopInvitations,
|
||||||
|
listShops,
|
||||||
|
removeShopPlayer,
|
||||||
|
updateShop,
|
||||||
|
updateShopTemplate,
|
||||||
|
} from "./shops"
|
||||||
export {
|
export {
|
||||||
getWalletBalance,
|
getWalletBalance,
|
||||||
listWalletTransactions,
|
listWalletTransactions,
|
||||||
|
|||||||
+155
-6
@@ -1,10 +1,11 @@
|
|||||||
|
import { getDefaultShopSections } from "@/lib/domain/shop-template"
|
||||||
import { isApiError } from "@/lib/errors"
|
import { isApiError } from "@/lib/errors"
|
||||||
import type { Shop } from "@/lib/types"
|
import type { Shop, ShopTemplateConfig } from "@/lib/types"
|
||||||
|
|
||||||
import { httpJson } from "./http"
|
import { httpJson } from "./http"
|
||||||
|
|
||||||
type Paginated<T> = {
|
type Paginated<T> = {
|
||||||
items: T[]
|
items: T[] | null
|
||||||
meta: {
|
meta: {
|
||||||
total: number
|
total: number
|
||||||
offset: 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[]> {
|
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",
|
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> {
|
export async function getShopById(shopId: string): Promise<Shop | undefined> {
|
||||||
try {
|
try {
|
||||||
return await httpJson<Shop>(`/api/v1/shops/${encodeURIComponent(shopId)}`, {
|
const shop = await httpJson<ShopResponse>(`/api/v1/shops/${encodeURIComponent(shopId)}`, {
|
||||||
cache: "no-store",
|
cache: "no-store",
|
||||||
})
|
})
|
||||||
|
return normalizeShop(shop)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof Error && error.message === "UNAUTHORIZED") {
|
if (error instanceof Error && error.message === "UNAUTHORIZED") {
|
||||||
throw error
|
throw error
|
||||||
@@ -37,9 +121,10 @@ export async function getShopById(shopId: string): Promise<Shop | undefined> {
|
|||||||
|
|
||||||
export async function getShopByOwnerId(ownerId: string): Promise<Shop | undefined> {
|
export async function getShopByOwnerId(ownerId: string): Promise<Shop | undefined> {
|
||||||
try {
|
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",
|
cache: "no-store",
|
||||||
})
|
})
|
||||||
|
return normalizeShop(shop)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof Error && error.message === "UNAUTHORIZED") {
|
if (error instanceof Error && error.message === "UNAUTHORIZED") {
|
||||||
throw error
|
throw error
|
||||||
@@ -50,3 +135,67 @@ export async function getShopByOwnerId(ownerId: string): Promise<Shop | undefine
|
|||||||
throw error
|
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",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user