313 lines
7.7 KiB
TypeScript
313 lines
7.7 KiB
TypeScript
import { apiRequest, getAccessToken } from "@/lib/meshkee-api";
|
|
|
|
export interface MeshkeeBusinessSearchItem {
|
|
id: string;
|
|
name: string;
|
|
nameFa: string | null;
|
|
slug: string;
|
|
label: string;
|
|
ownerName?: string | null;
|
|
ownerCellNumber?: string | null;
|
|
}
|
|
|
|
export interface MeshkeeBusinessCategory {
|
|
id: string;
|
|
name: string;
|
|
slug: string;
|
|
parentId: string | null;
|
|
}
|
|
|
|
export interface MeshkeeBusinessOwner {
|
|
id: string;
|
|
cellNumber: string;
|
|
firstName: string | null;
|
|
lastName: string | null;
|
|
firstNameEn: string | null;
|
|
lastNameEn: string | null;
|
|
email: string | null;
|
|
}
|
|
|
|
export interface MeshkeeBusinessDetail {
|
|
id: string;
|
|
name: string;
|
|
nameFa: string | null;
|
|
slug: string;
|
|
categories: MeshkeeBusinessCategory[];
|
|
categoryIds: string[];
|
|
owner: MeshkeeBusinessOwner | null;
|
|
logoUrl: string | null;
|
|
logoMediaId: string | null;
|
|
bannerUrl: string | null;
|
|
bannerMediaId: string | null;
|
|
createdAt?: string;
|
|
}
|
|
|
|
function mapSearchItem(
|
|
item: MeshkeeBusinessSearchItem,
|
|
): MeshkeeBusinessSearchItem {
|
|
const name = item.name ?? "";
|
|
const nameFa = item.nameFa ?? null;
|
|
return {
|
|
...item,
|
|
id: String(item.id),
|
|
name,
|
|
nameFa,
|
|
slug: item.slug ?? "",
|
|
label: item.label || name || item.slug || String(item.id),
|
|
ownerName: item.ownerName ?? null,
|
|
ownerCellNumber: item.ownerCellNumber ?? null,
|
|
};
|
|
}
|
|
|
|
export async function searchMeshkeeBusinesses(
|
|
q: string,
|
|
signal?: AbortSignal,
|
|
) {
|
|
const params = new URLSearchParams({
|
|
q,
|
|
limit: "20",
|
|
});
|
|
|
|
const data = await apiRequest<{ items: MeshkeeBusinessSearchItem[] }>(
|
|
`/businesses/search?${params}`,
|
|
{ auth: true, signal },
|
|
);
|
|
|
|
return {
|
|
items: (data.items ?? []).map(mapSearchItem),
|
|
};
|
|
}
|
|
|
|
/** Newest Meshkee businesses (super-admin list, created_at desc). */
|
|
export async function listLatestMeshkeeBusinesses(
|
|
limit = 5,
|
|
signal?: AbortSignal,
|
|
) {
|
|
const pageSize = Math.min(Math.max(limit, 5), 50);
|
|
const params = new URLSearchParams({
|
|
page: "1",
|
|
pageSize: String(pageSize),
|
|
sort: "created",
|
|
});
|
|
|
|
const data = await apiRequest<{ items: MeshkeeBusinessSearchItem[] }>(
|
|
`/businesses?${params}`,
|
|
{ auth: true, signal },
|
|
);
|
|
|
|
return {
|
|
items: (data.items ?? []).slice(0, limit).map(mapSearchItem),
|
|
};
|
|
}
|
|
|
|
export async function getMeshkeeBusiness(
|
|
businessId: string,
|
|
signal?: AbortSignal,
|
|
) {
|
|
const data = await apiRequest<MeshkeeBusinessDetail>(
|
|
`/businesses/${businessId}`,
|
|
{ auth: true, signal },
|
|
);
|
|
|
|
return {
|
|
...data,
|
|
id: String(data.id),
|
|
createdAt: data.createdAt,
|
|
categories: (data.categories ?? []).map((category) => ({
|
|
...category,
|
|
id: String(category.id),
|
|
parentId:
|
|
category.parentId == null ? null : String(category.parentId),
|
|
})),
|
|
categoryIds: (data.categoryIds ?? []).map(String),
|
|
owner: data.owner
|
|
? {
|
|
...data.owner,
|
|
id: String(data.owner.id),
|
|
}
|
|
: null,
|
|
};
|
|
}
|
|
|
|
export async function createMeshkeeBusiness(body: {
|
|
name: string;
|
|
nameFa: string;
|
|
slug?: string;
|
|
categoryIds: number[];
|
|
ownerFirstName: string;
|
|
ownerLastName: string;
|
|
ownerFirstNameEn: string;
|
|
ownerLastNameEn: string;
|
|
ownerCellNumber: string;
|
|
ownerPassword: string;
|
|
}) {
|
|
const data = await apiRequest<MeshkeeBusinessDetail>(`/businesses`, {
|
|
method: "POST",
|
|
auth: true,
|
|
body,
|
|
});
|
|
return { ...data, id: String(data.id) };
|
|
}
|
|
|
|
export async function uploadBusinessMedia(
|
|
businessId: string,
|
|
file: File,
|
|
): Promise<{ id: string; publicUrl: string }> {
|
|
const token = getAccessToken();
|
|
const form = new FormData();
|
|
form.append("files", file);
|
|
|
|
const base =
|
|
process.env.NEXT_PUBLIC_MESHKEE_API_URL?.replace(/\/$/, "") ||
|
|
"http://localhost:3010/api/v1";
|
|
|
|
const response = await fetch(`${base}/businesses/${businessId}/media`, {
|
|
method: "POST",
|
|
headers: token ? { Authorization: `Bearer ${token}` } : undefined,
|
|
body: form,
|
|
});
|
|
|
|
const payload = await response.json().catch(() => null);
|
|
if (!response.ok) {
|
|
const message =
|
|
typeof payload?.message === "string"
|
|
? payload.message
|
|
: Array.isArray(payload?.message)
|
|
? payload.message.join(", ")
|
|
: `Upload failed (${response.status})`;
|
|
throw new Error(message);
|
|
}
|
|
|
|
const item = Array.isArray(payload?.items)
|
|
? payload.items[0]
|
|
: Array.isArray(payload)
|
|
? payload[0]
|
|
: payload;
|
|
|
|
return {
|
|
id: String(item.id),
|
|
publicUrl: String(item.publicUrl ?? item.url ?? ""),
|
|
};
|
|
}
|
|
|
|
export async function updateBusinessBanner(
|
|
businessId: string,
|
|
bannerMediaId: string | null,
|
|
) {
|
|
return updateBusinessProfile(businessId, {
|
|
bannerMediaId: bannerMediaId == null ? null : Number(bannerMediaId),
|
|
});
|
|
}
|
|
|
|
export type BusinessPhoneType = "landline" | "cell";
|
|
|
|
export interface BusinessPhoneNumber {
|
|
type: BusinessPhoneType;
|
|
number: string;
|
|
/** Optional; supported after Meshkee deploy with label field */
|
|
label?: string;
|
|
}
|
|
|
|
export interface BusinessAddress {
|
|
id?: string;
|
|
/** Display name / label for the address */
|
|
name: string;
|
|
province: string;
|
|
city: string;
|
|
address: string;
|
|
postalCode: string;
|
|
landline?: string | null;
|
|
}
|
|
|
|
export interface BusinessProfile {
|
|
businessId: string;
|
|
profile: {
|
|
nameEn: string;
|
|
nameFa: string;
|
|
title: string;
|
|
about: string;
|
|
vision: string;
|
|
emails: string[];
|
|
phoneNumbers: BusinessPhoneNumber[];
|
|
socialMedia: Record<string, string>;
|
|
logoMediaId: string | null;
|
|
logoUrl: string | null;
|
|
bannerMediaId: string | null;
|
|
bannerUrl: string | null;
|
|
categoryIds: string[];
|
|
};
|
|
addresses: BusinessAddress[];
|
|
}
|
|
|
|
export type UpdateBusinessProfileInput = {
|
|
nameEn?: string;
|
|
nameFa?: string;
|
|
title?: string;
|
|
about?: string;
|
|
vision?: string;
|
|
phoneNumbers?: BusinessPhoneNumber[];
|
|
logoMediaId?: number | null;
|
|
bannerMediaId?: number | null;
|
|
categoryIds?: number[];
|
|
addresses?: BusinessAddress[];
|
|
};
|
|
|
|
export async function getBusinessProfile(businessId: string) {
|
|
const data = await apiRequest<BusinessProfile>(
|
|
`/businesses/${businessId}/profile`,
|
|
{ auth: true },
|
|
);
|
|
|
|
return {
|
|
businessId: String(data.businessId),
|
|
profile: {
|
|
...data.profile,
|
|
logoMediaId:
|
|
data.profile.logoMediaId == null
|
|
? null
|
|
: String(data.profile.logoMediaId),
|
|
bannerMediaId:
|
|
data.profile.bannerMediaId == null
|
|
? null
|
|
: String(data.profile.bannerMediaId),
|
|
categoryIds: (data.profile.categoryIds ?? []).map(String),
|
|
phoneNumbers: (data.profile.phoneNumbers ?? []).map((phone) => ({
|
|
type: phone.type === "landline" ? "landline" : "cell",
|
|
number: phone.number ?? "",
|
|
...(phone.label?.trim() ? { label: phone.label.trim() } : {}),
|
|
})),
|
|
emails: data.profile.emails ?? [],
|
|
about: data.profile.about ?? "",
|
|
vision: data.profile.vision ?? "",
|
|
nameEn: data.profile.nameEn ?? "",
|
|
nameFa: data.profile.nameFa ?? "",
|
|
title: data.profile.title ?? "",
|
|
logoUrl: data.profile.logoUrl ?? null,
|
|
bannerUrl: data.profile.bannerUrl ?? null,
|
|
socialMedia: data.profile.socialMedia ?? {},
|
|
},
|
|
addresses: (data.addresses ?? []).map((address) => ({
|
|
...address,
|
|
id: address.id == null ? undefined : String(address.id),
|
|
name: String(
|
|
(address as BusinessAddress & { label?: string | null }).name ??
|
|
(address as { label?: string | null }).label ??
|
|
"",
|
|
),
|
|
landline: address.landline ?? "",
|
|
postalCode: address.postalCode ?? "",
|
|
})),
|
|
};
|
|
}
|
|
|
|
export async function updateBusinessProfile(
|
|
businessId: string,
|
|
body: UpdateBusinessProfileInput,
|
|
) {
|
|
return apiRequest<BusinessProfile>(`/businesses/${businessId}/profile`, {
|
|
method: "PATCH",
|
|
auth: true,
|
|
body,
|
|
});
|
|
}
|