chore: initialize WanderQ MiniAPP 2.0 repository
This commit is contained in:
217
src/api.ts
Normal file
217
src/api.ts
Normal file
@@ -0,0 +1,217 @@
|
||||
const API_BASE = import.meta.env.VITE_API_BASE_URL ?? "";
|
||||
|
||||
export type PublicHeroSlide = {
|
||||
id: string;
|
||||
title: string;
|
||||
kicker?: string | null;
|
||||
image: string;
|
||||
targetType?: string | null;
|
||||
targetValue?: string | null;
|
||||
isActive?: boolean;
|
||||
};
|
||||
|
||||
export type PublicDestination = {
|
||||
id: string;
|
||||
name: string;
|
||||
image?: string | null;
|
||||
isHot?: boolean;
|
||||
isActive?: boolean;
|
||||
aliases?: { id: string; alias: string }[];
|
||||
};
|
||||
|
||||
export type PublicTheme = {
|
||||
id: string;
|
||||
label: string;
|
||||
image: string;
|
||||
targetType?: string | null;
|
||||
targetValue?: string | null;
|
||||
isActive?: boolean;
|
||||
};
|
||||
|
||||
export type PublicCta = {
|
||||
id: string;
|
||||
alt: string;
|
||||
image: string;
|
||||
targetType?: string | null;
|
||||
targetValue?: string | null;
|
||||
isActive?: boolean;
|
||||
};
|
||||
|
||||
export type PublicSearchPageKeyword = {
|
||||
id: string;
|
||||
label: string;
|
||||
image?: string | null;
|
||||
isActive?: boolean;
|
||||
sortOrder: number;
|
||||
};
|
||||
|
||||
export type PublicSearchPageGroup = {
|
||||
id: string;
|
||||
label: string;
|
||||
items: PublicSearchPageKeyword[];
|
||||
isActive?: boolean;
|
||||
sortOrder: number;
|
||||
};
|
||||
|
||||
export type PublicSearchPageModuleCopy = {
|
||||
title: string;
|
||||
subtitle: string;
|
||||
};
|
||||
|
||||
export type PublicSearchPageConfig = {
|
||||
placeholder: string;
|
||||
modules: {
|
||||
seasonalInspiration: PublicSearchPageModuleCopy;
|
||||
preferenceDiscovery: PublicSearchPageModuleCopy;
|
||||
};
|
||||
popularKeywords: PublicSearchPageKeyword[];
|
||||
groups: PublicSearchPageGroup[];
|
||||
/** Transitional fields accepted while an older API response is still cached. */
|
||||
title?: string;
|
||||
subtitle?: string;
|
||||
};
|
||||
|
||||
export type PublicHomeModule = {
|
||||
id: string;
|
||||
label: string;
|
||||
sortOrder: number;
|
||||
isActive?: boolean;
|
||||
templateType?: "explore" | "themes" | "deals" | "hotels" | "vehicles";
|
||||
content?: {
|
||||
items: Array<{
|
||||
id: string;
|
||||
title: string;
|
||||
subtitle?: string;
|
||||
description?: string;
|
||||
image?: string | null;
|
||||
targetType?: string | null;
|
||||
targetValue?: string | null;
|
||||
productIds?: string[];
|
||||
chips?: string[];
|
||||
isActive: boolean;
|
||||
sortOrder: number;
|
||||
}>;
|
||||
};
|
||||
};
|
||||
|
||||
export type ProductPricingTier = {
|
||||
groupSize: number;
|
||||
adultPrice: number;
|
||||
child6PlusPrice: number;
|
||||
childUnder6Price: number;
|
||||
};
|
||||
|
||||
export type PublicProduct = {
|
||||
id: string;
|
||||
sourceId?: number | null;
|
||||
title: string;
|
||||
subtitle?: string | null;
|
||||
destination?: { id: string; name: string } | null;
|
||||
priceAmount?: number | null;
|
||||
priceUnit?: string | null;
|
||||
pricingTiers?: ProductPricingTier[] | null;
|
||||
tags?: string[];
|
||||
coverImage?: string | null;
|
||||
summary?: string | null;
|
||||
durationDays?: number | null;
|
||||
durationNights?: number | null;
|
||||
departureDates?: string[];
|
||||
remainingSpots?: number | null;
|
||||
recommendation?: string | null;
|
||||
images?: Array<{ id?: string; url: string; alt?: string | null; sortOrder: number }>;
|
||||
keyFacts?: ProductKeyFact[] | null;
|
||||
contentBlocks?: ProductContentBlock[] | null;
|
||||
detailSections?: ProductDetailSection[] | null;
|
||||
status?: string;
|
||||
sortWeight?: number;
|
||||
};
|
||||
|
||||
export type ProductKeyFact = { label: string; value: string };
|
||||
|
||||
export type ProductContentBlock =
|
||||
| { type: "title"; text: string }
|
||||
| { type: "image"; url: string; alt?: string | null };
|
||||
|
||||
export type ProductDetailBlock =
|
||||
| { type: "text"; text: string }
|
||||
| { type: "image"; url: string; alt?: string | null };
|
||||
|
||||
export type ProductDetailSection = {
|
||||
key: string;
|
||||
label: string;
|
||||
title?: string | null;
|
||||
blocks: ProductDetailBlock[];
|
||||
};
|
||||
|
||||
export type PublicSiteConfig = {
|
||||
homeModules?: PublicHomeModule[];
|
||||
heroSlides?: PublicHeroSlide[];
|
||||
destinations?: PublicDestination[];
|
||||
themes?: PublicTheme[];
|
||||
ctaBanners?: PublicCta[];
|
||||
destinationRecommendations?: { productIds: string[] };
|
||||
searchPage?: PublicSearchPageConfig;
|
||||
};
|
||||
|
||||
export type PublicLeadPayload = {
|
||||
destination?: string;
|
||||
phone: string;
|
||||
contactName?: string;
|
||||
wechat?: string;
|
||||
travelDate?: string;
|
||||
peopleCount?: number;
|
||||
adultCount?: number;
|
||||
childCount?: number;
|
||||
roomType?: string;
|
||||
plan?: string;
|
||||
addOns?: string[];
|
||||
budgetMin?: number;
|
||||
budgetMax?: number;
|
||||
note?: string;
|
||||
sourcePage?: string;
|
||||
sourceTheme?: string;
|
||||
sourceProductId?: string;
|
||||
};
|
||||
|
||||
async function request<T>(path: string, init?: RequestInit) {
|
||||
const response = await fetch(`${API_BASE}${path}`, {
|
||||
headers: {
|
||||
"content-type": "application/json",
|
||||
...init?.headers,
|
||||
},
|
||||
...init,
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const message = await response.text();
|
||||
throw new Error(message || `Request failed: ${response.status}`);
|
||||
}
|
||||
|
||||
return response.json() as Promise<T>;
|
||||
}
|
||||
|
||||
export async function fetchPublicContent() {
|
||||
const [siteConfig, products] = await Promise.all([
|
||||
request<PublicSiteConfig>("/api/public/site-config"),
|
||||
request<{ items: PublicProduct[] }>("/api/public/products"),
|
||||
]);
|
||||
|
||||
return {
|
||||
siteConfig,
|
||||
products: products.items,
|
||||
};
|
||||
}
|
||||
|
||||
export async function createPublicLead(payload: PublicLeadPayload) {
|
||||
return request<{ id: string; status: string }>("/api/public/leads", {
|
||||
method: "POST",
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
}
|
||||
|
||||
export async function createPublicBooking(payload: PublicLeadPayload) {
|
||||
return request<{ id: string; status: string; requestType: "booking" }>("/api/public/bookings", {
|
||||
method: "POST",
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user