UNPKG

@salla.sa/twilight-components

Version:
110 lines (109 loc) 5.7 kB
/*! * Crafted with ❤ by Salla */ /** * Helper functions for salla-bullet-delivery component */ import { isSaudiArabia } from "./api-service"; const EARTH_RADIUS_KM = 6371; const deg2rad = (deg) => deg * (Math.PI / 180); /** Haversine distance in km */ function calculateDistance(lat1, lon1, lat2, lon2) { const dLat = deg2rad(lat2 - lat1); const dLon = deg2rad(lon2 - lon1); const a = Math.sin(dLat / 2) ** 2 + Math.cos(deg2rad(lat1)) * Math.cos(deg2rad(lat2)) * Math.sin(dLon / 2) ** 2; return EARTH_RADIUS_KM * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); } /** First working-hours slot from API branch.working_hours[].times[]. */ export const getBranchFirstSlot = (workingHours) => workingHours?.[0]?.times?.[0] ?? null; /** Nearest available branch by distance; single pass O(n). */ export function findNearestBranch(branches, userLat, userLon) { let nearest = null; let minD = Number.POSITIVE_INFINITY; for (const b of branches) { const lat = b.location?.lat != null ? Number(b.location.lat) : Number.NaN; const lng = b.location?.lng != null ? Number(b.location.lng) : Number.NaN; if (Number.isNaN(lat) || Number.isNaN(lng) || b.is_open === false) continue; const d = calculateDistance(userLat, userLon, lat, lng); if (d < minD) { minD = d; nearest = b; } } return nearest; } /** 24h time string → locale 12h (e.g. "23:55" → "11:55 PM"). */ export function formatTimeTo12h(time24, locale) { const [h, m] = String(time24).trim().split(':').map(Number); return Number.isNaN(h) || Number.isNaN(m) ? time24 : new Date(2000, 0, 1, h, m).toLocaleTimeString(locale, { hour: 'numeric', minute: '2-digit', hour12: true }); } /** Working hours display: "until X" when from is 00:00, else "from - to" in 12h. */ export function formatWorkingHoursDisplay(from, to, locale, untilLabel) { const f = String(from).trim(); const t = String(to).trim(); return !f || !t ? '' : f === '00:00' ? `${untilLabel} ${formatTimeTo12h(t, locale)}` : `${formatTimeTo12h(f, locale)} - ${formatTimeTo12h(t, locale)}`; } export const filterBranches = (branches, searchQuery) => { if (!searchQuery) return branches; const q = searchQuery.toLowerCase(); return branches.filter((b) => b.name.toLowerCase().includes(q) || b.city?.name?.toLowerCase().includes(q)); }; export const getIntentSubtitle = (intent, toAddress, fromBranch) => { if (intent.type === 'branch') { const loc = intent.branch_details?.city ?? intent.branch_details?.name; return loc ? `${fromBranch} ${loc}`.trim() : ''; } if (intent.type === 'address' && intent.address_details) { const addr = [intent.address_details.district?.name, intent.address_details.city?.name, intent.address_details.country?.name].filter(Boolean).join('، ') || intent.address_details.short_address || ''; return addr ? `${toAddress} ${addr}`.trim() : ''; } return ''; }; const idFrom = (val) => (val != null ? Number(val) : undefined); export const getIntentCountryId = (i) => idFrom(i?.address_details?.country?.id); export const getIntentCountryCode = (i) => i?.country_code ?? i?.address_details?.country?.code ?? ''; export const getIntentRegionId = (i) => idFrom(i?.address_details?.region?.id); export const getIntentCityId = (i) => idFrom(i?.address_details?.city?.id); export const getIntentDistrictId = (i) => idFrom(i?.address_details?.district?.id); export const getIntentBranchId = (i) => i?.branch_details?.id; /** Intent coordinates (branch tab only; address flow no longer uses coordinates). */ export const getIntentLatitude = (i) => i?.branch_details?.latitude; export const getIntentLongitude = (i) => i?.branch_details?.longitude; export const hasSessionAddressIntent = (intent) => { if (!intent || intent.type !== 'address' || intent.address_id) return false; const c = getIntentCountryId(intent); const city = getIntentCityId(intent); const d = getIntentDistrictId(intent); const code = getIntentCountryCode(intent); const hasDistrictId = d != null && Number(d) !== 0; const hasDistrictName = Boolean(intent.address_details?.district?.name?.trim()); const districtOk = !isSaudiArabia(code) || hasDistrictId || hasDistrictName; return !!(c && city && districtOk); }; export const requireRegionAndDistrictForSA = (countryCode, regionId, districtIdOrName) => !countryCode || !isSaudiArabia(countryCode) ? true : (regionId != null && Number(regionId) !== 0) && (districtIdOrName != null && (typeof districtIdOrName === 'number' ? districtIdOrName !== 0 : String(districtIdOrName).trim().length > 0)); export function buildAddressLocationPayloadFromSelection(options) { const { countryId, countryCode, regionId, cityId, districtId, description = '' } = options; const desc = description || ''; return { country_id: Number(countryId) || undefined, region_id: regionId != null ? Number(regionId) : undefined, city_id: cityId != null ? Number(cityId) : undefined, district_id: districtId != null ? Number(districtId) : undefined, description: desc, ...(countryCode && isSaudiArabia(countryCode) && { local: desc }), }; } const GEO_ERROR_MESSAGES = { [GeolocationPositionError.PERMISSION_DENIED]: 'Location permission denied', [GeolocationPositionError.POSITION_UNAVAILABLE]: 'Location unavailable', [GeolocationPositionError.TIMEOUT]: 'Location timeout', }; export const getGeolocationErrorMessage = (errorCode) => GEO_ERROR_MESSAGES[errorCode] ?? 'An error occurred while detecting location';