@salla.sa/twilight-components
Version:
Salla Web Component
203 lines (202 loc) • 8.47 kB
JavaScript
/*!
* Crafted with ❤ by Salla
*/
/**
* API Service for salla-bullet-delivery component
* Uses Salla's shipping API and scope API for delivery location management
*/
const API_LOG_PREFIX = 'BulletDeliveryAPI';
const validId = (id) => {
const n = id == null ? Number.NaN : Number(id);
return !Number.isNaN(n) && n !== 0;
};
async function withApiErrorHandling(fn, fallback, logLabel) {
try {
return await fn();
}
catch (error) {
console.error(`${API_LOG_PREFIX}: ${logLabel}`, error);
return fallback;
}
}
const apiCache = new Map();
const CACHE_TTL = 5 * 60 * 1000;
function getCached(key) {
const entry = apiCache.get(key);
if (!entry || Date.now() - entry.timestamp > CACHE_TTL) {
apiCache.delete(key);
return null;
}
return entry.data;
}
function setCache(key, data) {
apiCache.set(key, { data, timestamp: Date.now() });
}
/** Uses country code from API (e.g. 'SA'). */
export const isSaudiArabia = (countryCode) => String(countryCode).toUpperCase() === 'SA';
/**
* Fast Delivery API Service
* Uses Salla's shipping API and scopes API. Branch and address data are used as returned by the API (no mapping).
*/
export const bulletDeliveryAPI = {
/**
* Get available countries from Salla shipping API
* @param forBranch - when true, use for_branch=1 (e.g. for pickup/branches tab)
*/
async getCountries(forBranch = false, query) {
const cacheKey = !query?.trim() ? `countries_${forBranch ? 1 : 0}` : null;
if (cacheKey) {
const cached = getCached(cacheKey);
if (cached)
return cached;
}
const result = await withApiErrorHandling(async () => {
const params = { for_branch: forBranch ? 1 : 0 };
if (forBranch) {
params.for_allocation = 1;
}
if (query?.trim())
params.query = query.trim();
const data = (await salla.api.request("shipping/countries", { params }))?.data ?? [];
return data.map((c) => ({ id: c.id, code: c.code, name: c.name, has_regions: isSaudiArabia(c.code) }));
}, [], 'Error getting countries');
if (cacheKey && result.length > 0)
setCache(cacheKey, result);
return result;
},
/**
* Get regions for a country from Salla shipping API
* Endpoint: GET /shipping/countries/<COUNTRY_ID>/region
*/
async getRegions(countryId, query) {
if (!validId(countryId)) {
console.warn(`${API_LOG_PREFIX}: getRegions called without valid country_id`);
return [];
}
const cacheKey = !query?.trim() ? `regions_${countryId}` : null;
if (cacheKey) {
const cached = getCached(cacheKey);
if (cached)
return cached;
}
const result = await withApiErrorHandling(async () => {
const params = {};
if (query?.trim())
params.query = query.trim();
const data = (await salla.api.request(`shipping/countries/${countryId}/regions`, { params }))?.data ?? [];
return data.map((r) => ({ id: r.id, name: r.name, code: r.code, country_id: Number(countryId) }));
}, [], 'Error fetching regions');
if (cacheKey && result.length > 0)
setCache(cacheKey, result);
return result;
},
/**
* Get cities from Salla shipping API
* @param regionId - Optional; when provided (e.g. for SA), cities are filtered by region
*/
async getCities(countryId, regionId, query) {
if (!validId(countryId)) {
console.warn(`${API_LOG_PREFIX}: getCities called without valid country_id`);
return [];
}
const cacheKey = !query?.trim() ? `cities_${countryId}_${regionId || 'all'}` : null;
if (cacheKey) {
const cached = getCached(cacheKey);
if (cached)
return cached;
}
const result = await withApiErrorHandling(async () => {
const params = { for_branch: 0, country_id: countryId };
if (regionId)
params.region_id = regionId;
if (query?.trim())
params.query = query.trim();
const data = (await salla.api.request("shipping/cities", { params }))?.data ?? [];
return data.map((c) => ({ id: c.id, name: c.name, country_id: countryId, ...(c.region_id != null && { region_id: c.region_id }) }));
}, [], 'Error fetching cities');
if (cacheKey && result.length > 0)
setCache(cacheKey, result);
return result;
},
/**
* Get districts from Salla shipping API
*/
async getDistricts(cityId, query) {
if (!validId(cityId)) {
console.warn(`${API_LOG_PREFIX}: getDistricts called without valid city_id`);
return [];
}
return withApiErrorHandling(async () => {
const params = { for_branch: 0, city_id: cityId };
if (query?.trim())
params.query = query.trim();
const raw = (await salla.api.request("shipping/districts", { params }))?.data;
const list = Array.isArray(raw) ? raw : (Array.isArray(raw?.districts) ? raw.districts : []);
return list.map((d) => ({ id: d.id, name: d.name, name_en: d.name_en, city_id: cityId }));
}, [], 'Error fetching districts');
},
async getSavedAddresses() {
return withApiErrorHandling(async () => {
const data = (await salla.api.request("address"))?.data;
return Array.isArray(data) ? data : [];
}, [], 'Error fetching user addresses');
},
async getBranches({ query, lat, lng, country_id, per_page = 20 } = {}) {
const hasSearchParams = query?.trim()?.length >= 2 || lat || lng;
const cacheKey = !hasSearchParams && country_id ? `branches_${country_id}` : null;
if (cacheKey) {
const cached = getCached(cacheKey);
if (cached)
return cached;
}
const result = await withApiErrorHandling(async () => {
const params = { per_page };
if (query?.trim().length >= 2)
params.query = query;
if (lat)
params.lat = lat;
if (lng)
params.lng = lng;
if (country_id)
params.country_id = country_id;
const res = await salla.api.request("branches", { params });
return (Array.isArray(res?.data) ? res.data : []);
}, [], 'Error fetching branches');
if (cacheKey && result.length > 0)
setCache(cacheKey, result);
return result;
},
async saveAddressLocation(payload) {
return withApiErrorHandling(async () => {
const res = await salla.api.request('address/location', { ...payload, for_allocation: true }, 'post');
const data = res?.data ?? res;
const address = typeof data === 'object' && data != null && 'id' in data ? data : undefined;
return { success: true, address };
}, { success: false }, 'Error saving address/location');
},
async setDeliveryScope(scopeId) {
return withApiErrorHandling(async () => {
await salla.api.withoutNotifier(() => salla.scope.change({ id: scopeId }));
salla.storage.set("scope", { ...(salla.storage.get("scope") || {}), id: scopeId });
return true;
}, false, 'Error setting delivery scope');
},
async allocateScope(payload) {
const errMsg = (d) => d?.error?.message ?? d?.message ?? 'Failed to allocate scope';
try {
const response = await salla.api.withoutNotifier(() => salla.api.request("scopes/allocation", payload, 'post'));
// SDK resolves with { data } and throws on non-2xx; any resolved value is success
const data = response?.data ?? response;
return { success: true, data: data };
}
catch (error) {
console.error(`${API_LOG_PREFIX}: Error allocating scope`, error);
const err = error;
return { success: false, error: err?.response?.data != null ? errMsg(err.response.data) : (err?.message ?? 'Unknown error') };
}
},
};
export function clearApiCache() {
apiCache.clear();
}
export default bulletDeliveryAPI;