UNPKG

@salla.sa/twilight-components

Version:
398 lines (392 loc) 21.8 kB
/*! * Crafted with ❤ by Salla */ import { r as registerInstance, h, H as Host, a as getElement } from './index-CFtXUFT2.js'; const DEBUG_KEY = 'salla-delivery-promise-debug'; function log(message, data) { if (localStorage.getItem(DEBUG_KEY)) { data !== undefined ? console.log(message, data) : console.log(message); } } /** Fetch available cities for a product's delivery promises. */ async function fetchCities(productId, keyword) { log('fetchCities start', { productId, keyword }); const queryString = keyword != null ? `?keyword=${encodeURIComponent(keyword)}` : ''; const response = await salla.api.request(salla.url.api(`products/${productId}/delivery-promises/cities${queryString}`)); if (!response.success || !Array.isArray(response.data) || response.data.length === 0) { log('fetchCities: no data', { response }); return { cities: [] }; } log('fetchCities success', { count: response.data.length }); return { cities: response.data }; } /** Fetch the delivery promise message for a given city or branch. */ async function fetchDeliveryMessage(productId, id, optionType, isLoginCycleEnabled) { log('fetchDeliveryMessage start', { productId, id, optionType }); let queryString = ''; if (optionType === 'city' && !isLoginCycleEnabled) { queryString = `?reference_id=${id}`; } else { queryString = `?reference_id=${id}&reference_type=${optionType}`; } const response = (await salla.api.withoutNotifier(() => salla.api.request(salla.url.api(`products/${productId}/delivery-promises${queryString}`)))); if (!response.success) { log('fetchDeliveryMessage error', { response }); throw new Error(response.error?.message || 'Failed to fetch delivery promise'); } const message = response.data?.message?.trim(); log('fetchDeliveryMessage success', { message }); return message || null; } const INTENT_KEY = 'bullet_delivery_intent'; function getIntentStorage() { const rememberLastSession = Boolean(salla.config.get('store.settings.bullet_delivery.settings.remember_last_session')); return rememberLastSession ? salla.storage.store : salla.storage.session; } function getStoredIntent() { const raw = getIntentStorage().get(INTENT_KEY); return raw && typeof raw === 'object' ? raw : null; } /** Resolve the stored intent into a city or branch selection. Returns null when no usable intent. */ function resolveIntent() { const intent = getStoredIntent(); if (!intent) return null; if (intent.type === 'address') { const city = intent.address_details?.city; const cityId = city?.id != null ? Number(city.id) : intent.city_id != null ? Number(intent.city_id) : null; const cityName = city?.name?.trim() || ''; if (cityId != null) { return { type: 'address', option: { id: cityId, name: cityName } }; } return null; } if (intent.type === 'branch') { const branchId = intent.branch_id != null ? Number(intent.branch_id) : intent.branch_details?.id != null ? Number(intent.branch_details.id) : null; const branchName = intent.branch_details?.name?.trim() || ''; if (branchId != null) { return { type: 'branch', option: { id: branchId, name: branchName } }; } return null; } return null; } /** Build the payload for opening the bullet delivery modal with preselection. */ function buildBulletOpenPayload() { const intent = getStoredIntent(); const payload = {}; if (intent?.type === 'address' && intent.address_id != null) { payload.preselected_address_id = Number(intent.address_id); } if (intent?.type === 'branch') { const branchId = intent.branch_id ?? intent.branch_details?.id; if (branchId != null) { payload.preselected_branch_id = Number(branchId); } } return payload; } const DEFAULT_LABELS = { deliveryTo: 'توصيل إلى', pickupFromBranch: 'الاستلام من فرع', noResults: 'لا توجد نتائج', selectCity: 'اختر المدينة', changeCityTitle: 'تغيير المدينة', changeCitySubtitle: '', cityFieldLabel: 'المدينة', modalSearchPlaceholder: 'ابحث عن مدينة', confirmAddress: 'تأكيد العنوان', errorDeliveryPromise: 'لا يتوفر وعد تسليم لهذه المدينة', errorPickupPromise: 'لا يتوفر وعد تسليم لهذا الفرع', }; function loadLabels() { return { deliveryTo: salla.lang.getWithDefault('pages.products.promise_deliver_to', DEFAULT_LABELS.deliveryTo), pickupFromBranch: salla.lang.getWithDefault('pages.products.promise_pickup_from_branch', DEFAULT_LABELS.pickupFromBranch), noResults: salla.lang.getWithDefault('common.elements.no_options', DEFAULT_LABELS.noResults), selectCity: salla.lang.getWithDefault('common.elements.select_city', DEFAULT_LABELS.selectCity), changeCityTitle: salla.lang.getWithDefault('pages.products.promise_change_city_title', DEFAULT_LABELS.changeCityTitle), changeCitySubtitle: salla.lang.getWithDefault('pages.products.promise_change_city_subtitle', 'قد تتغيّر مدة التوصيل حسب المدينة.'), cityFieldLabel: salla.lang.getWithDefault('pages.products.promise_city_field', DEFAULT_LABELS.cityFieldLabel), modalSearchPlaceholder: salla.lang.getWithDefault('pages.products.promise_search_city', DEFAULT_LABELS.modalSearchPlaceholder), confirmAddress: salla.lang.getWithDefault('pages.checkout.confirm_address', DEFAULT_LABELS.confirmAddress), errorDeliveryPromise: salla.lang.getWithDefault('pages.products.promise_delivery_not_available', DEFAULT_LABELS.errorDeliveryPromise), errorPickupPromise: salla.lang.getWithDefault('pages.products.promise_pickup_not_available', DEFAULT_LABELS.errorPickupPromise), }; } /** Localized city display name (English when available and user language is not Arabic). */ function getCityDisplayName(city) { const lang = salla.config.get('user.language_code'); if (lang && lang !== 'ar' && city.name_en?.trim()) { return city.name_en.trim(); } return city.name; } const OVERRIDE_IP_KEY = 'salla-bullet-delivery-override-ip'; const SallaDeliveryPromise = class { constructor(hostRef) { registerInstance(this, hostRef); // ── Feature flags & config ──────────────────────────────────────────── this.isDeliveryPromiseEnabled = false; this.isLoginCycleEnabled = false; this.canRender = false; // ── Shared display state ────────────────────────────────────────────── this.labels = DEFAULT_LABELS; this.selectedLabel = null; this.deliveryMessage = null; this.hasError = false; this.errorMessage = ''; this.isLoadingPromises = false; // ── Login-cycle state (bullet delivery integration) ─────────────────── this.selectedCityLoginCycle = null; this.selectedBranchLoginCycle = null; // ── Standard flow state (city list) ─────────────────────────────────── this.cities = []; this.selectedCity = null; this.isLoadingCities = false; // ── City-change modal state (non–login-cycle) ───────────────────────── this.cityPendingSelection = null; this.modalCities = []; this.isSearchingCities = false; this.citySearchCounter = 0; // ═══════════════════════════════════════════════════════════════════════ // Login-cycle (bullet delivery) integration // ═══════════════════════════════════════════════════════════════════════ this.onBulletDeliveryConfirmed = () => { this.applyBulletIntent(); }; this.handleCitySearch = (keyword) => { clearTimeout(this.citySearchTimer); if (!keyword.trim()) { this.modalCities = [...this.cities]; this.isSearchingCities = false; return; } this.citySearchTimer = setTimeout(() => this.searchCitiesRemote(keyword), 300); }; // ═══════════════════════════════════════════════════════════════════════ // Header click (dispatches to correct flow) // ═══════════════════════════════════════════════════════════════════════ this.handleHeaderClick = (e) => { e.stopPropagation(); if (this.isLoginCycleEnabled) { this.openBulletDeliveryModal(); } else { void this.openCityChangeModal(); } }; } // ═══════════════════════════════════════════════════════════════════════ // Lifecycle // ═══════════════════════════════════════════════════════════════════════ async componentDidLoad() { await salla.onReady(); await salla.lang.onLoaded(); this.labels = loadLabels(); this.isDeliveryPromiseEnabled = salla.config.get('store.features', []).includes('delivery-promises'); this.isLoginCycleEnabled = salla.config.get('store.features', []).includes('bullet-delivery-v2'); this.productId = salla.config.get('page.id'); if (!this.isDeliveryPromiseEnabled) { this.canRender = false; return; } if (this.isLoginCycleEnabled) { await this.initLoginCycleFlow(); } else { this.selectedLabel = `${this.labels.deliveryTo} ${this.labels.selectCity}`; await this.loadCities(); } } disconnectedCallback() { window.removeEventListener('bulletDeliveryConfirmed', this.onBulletDeliveryConfirmed); } getIPDeliveryLocation() { const configPath = "store.shipping.delivery_location"; const ipAddress = { cityId: salla.config.get(`${configPath}.city_id`), cityName: salla.config.get(`${configPath}.city_name`), }; return localStorage.getItem(OVERRIDE_IP_KEY) ? JSON.parse(localStorage.getItem(OVERRIDE_IP_KEY)) : ipAddress; } async initLoginCycleFlow() { const hasIntent = this.applyBulletIntent(); const { cityId, cityName } = this.getIPDeliveryLocation(); log("delivery promise login cycle flow getIPDeliveryLocation", this.getIPDeliveryLocation()); if (cityId && cityName && !hasIntent) { this.selectedLabel = `${this.labels.deliveryTo} ${cityName}`; await this.loadDeliveryMessage(cityId, 'city'); } this.canRender = true; window.addEventListener('bulletDeliveryConfirmed', this.onBulletDeliveryConfirmed); } applyBulletIntent() { const resolved = resolveIntent(); if (!resolved) { this.selectedLabel = `${this.labels.deliveryTo} ${this.labels.selectCity}`; this.selectedCityLoginCycle = null; this.selectedBranchLoginCycle = null; this.deliveryMessage = null; return false; } if (resolved.type === 'address') { this.selectedCityLoginCycle = resolved.option; this.selectedBranchLoginCycle = null; this.selectedLabel = `${this.labels.deliveryTo} ${resolved.option.name || this.labels.selectCity}`; void this.loadDeliveryMessage(resolved.option.id, 'city'); } else { this.selectedBranchLoginCycle = resolved.option; this.selectedCityLoginCycle = null; this.selectedLabel = `${this.labels.pickupFromBranch} ${resolved.option.name}`; void this.loadDeliveryMessage(resolved.option.id, 'branch'); } return true; } openBulletDeliveryModal() { salla.event.emit('salla::bullet-delivery.modal.open.requested', buildBulletOpenPayload()); } // ═══════════════════════════════════════════════════════════════════════ // Standard flow (city list, no login cycle) // ═══════════════════════════════════════════════════════════════════════ async loadCities() { this.isLoadingCities = true; this.hasError = false; const { cityId, cityName } = this.getIPDeliveryLocation(); log("delivery promise standard flow getIPDeliveryLocation", this.getIPDeliveryLocation()); try { const { cities } = await fetchCities(this.productId); if (!cities.length) { this.canRender = false; return; } this.cities = cities; this.canRender = true; // get delivery message for the IP location if (cityId && cityName) { this.selectedLabel = `${this.labels.deliveryTo} ${cityName}`; const autoCity = this.cities.find(c => String(c.id) === String(cityId)); if (autoCity) { log("delivery promise standard flow autoCity found", autoCity); this.selectedCity = autoCity; } await this.loadDeliveryMessage(cityId, 'city'); } } catch (error) { this.hasError = true; this.errorMessage = error.message || 'Failed to load cities'; this.canRender = false; } finally { this.isLoadingCities = false; } } // ═══════════════════════════════════════════════════════════════════════ // Delivery message (shared by both flows) // ═══════════════════════════════════════════════════════════════════════ async loadDeliveryMessage(id, optionType) { this.isLoadingPromises = true; this.hasError = false; this.deliveryMessage = null; try { this.deliveryMessage = await fetchDeliveryMessage(this.productId, id, optionType, this.isLoginCycleEnabled); } catch (error) { this.hasError = true; this.errorMessage = optionType === 'city' ? this.labels.errorDeliveryPromise : this.labels.errorPickupPromise; } finally { this.isLoadingPromises = false; } } // ═══════════════════════════════════════════════════════════════════════ // City-change modal handlers (non–login-cycle) // ═══════════════════════════════════════════════════════════════════════ async openCityChangeModal() { if (!this.cityModalRef) return; this.cityPendingSelection = null; this.modalCities = [...this.cities]; await this.cityModalRef.setTitle(this.labels.changeCityTitle); await this.cityModalRef.open(); } async searchCitiesRemote(keyword) { const requestId = ++this.citySearchCounter; this.isSearchingCities = true; try { const { cities } = await fetchCities(this.productId, keyword); if (requestId !== this.citySearchCounter) return; this.modalCities = cities; } catch { if (requestId !== this.citySearchCounter) return; this.modalCities = []; } finally { if (requestId === this.citySearchCounter) { this.isSearchingCities = false; } } } async handleConfirmCityModal() { if (!this.cityPendingSelection) { await this.cityModalRef?.close(); return; } const city = this.cityPendingSelection; this.selectedCity = city; this.selectedLabel = `${this.labels.deliveryTo} ${getCityDisplayName(city)}`; await this.loadDeliveryMessage(city.id, 'city'); await this.cityModalRef?.close(); } // ═══════════════════════════════════════════════════════════════════════ // Render helpers // ═══════════════════════════════════════════════════════════════════════ renderLoadingSkeleton() { return (h(Host, { class: "s-delivery-promise-wrapper s-delivery-promise-skeleton" }, h("div", { class: "s-delivery-promise-container" }, h("div", { class: "s-delivery-promise-header s-delivery-promise-header-skeleton" }, h("div", { class: "s-delivery-promise-location" }, h("salla-skeleton", { height: "14px", width: "180px" })), h("salla-skeleton", { height: "18px", width: "18px" })), h("div", { class: "s-delivery-promise-loading" }, h("salla-skeleton", { height: "20px", width: "80%" }))))); } renderDeliveryMessage() { if (this.isLoadingPromises) { return (h("div", { class: "s-delivery-promise-loading" }, h("salla-skeleton", { height: "20px", width: "80%" }))); } if (!this.deliveryMessage) return null; return h("div", { class: "s-delivery-promise-message" }, this.deliveryMessage); } renderCityModalBody() { return (h("div", { class: "s-delivery-promise-modal-body" }, h("salla-searchable-dropdown", { label: this.labels.cityFieldLabel, placeholder: this.labels.modalSearchPlaceholder, items: this.modalCities, selectedItem: this.cityPendingSelection, searching: this.isSearchingCities, required: true, noResultsText: this.labels.noResults, inputId: "s-delivery-promise-modal-search", onItemSelected: (e) => { this.cityPendingSelection = e.detail; }, onSearchInput: (e) => { this.handleCitySearch(e.detail); } }))); } renderCityChangeModal() { if (this.isLoginCycleEnabled) return null; return (h("salla-modal", { id: "s-delivery-promise-city-modal", ref: el => { this.cityModalRef = el; }, class: "s-delivery-promise-city-modal", isClosable: true, width: "md", "modal-title": this.labels.changeCityTitle, subTitle: this.labels.changeCitySubtitle }, this.renderCityModalBody(), h("salla-button", { slot: "footer", width: "wide", disabled: !this.cityPendingSelection, onClick: () => this.handleConfirmCityModal() }, !this.isLoadingPromises && this.labels.confirmAddress))); } // ═══════════════════════════════════════════════════════════════════════ // Main render // ═══════════════════════════════════════════════════════════════════════ render() { if (!this.isDeliveryPromiseEnabled) return null; if (this.isLoadingCities) return this.renderLoadingSkeleton(); if (!this.canRender) return null; return (h(Host, { class: "s-delivery-promise-wrapper" }, h("div", { class: "s-delivery-promise-container" }, h("div", { class: "s-delivery-promise-header", onClick: this.handleHeaderClick }, h("div", { class: "s-delivery-promise-location" }, h("span", { class: "s-delivery-promise-title" }, this.selectedLabel)), h("i", { class: `sicon-keyboard_arrow_down s-delivery-promise-arrow` })), this.hasError && h("div", { class: "s-delivery-promise-error" }, this.errorMessage), !this.hasError && this.renderDeliveryMessage()), this.renderCityChangeModal())); } get host() { return getElement(this); } }; export { SallaDeliveryPromise as salla_delivery_promise };