JezK
Edit File: sc-checkout-geo-permission2.js.map
{"file":"sc-checkout-geo-permission2.js","mappings":"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","names":["checkoutState"],"sources":["src/functions/geolocation.ts","src/components/controllers/checkout-form/checkout/checkout-geo-permission/sc-checkout-geo-permission.scss?tag=sc-checkout-geo-permission&encapsulation=shadow","src/components/controllers/checkout-form/checkout/checkout-geo-permission/sc-checkout-geo-permission.tsx"],"sourcesContent":["import { GeoCoordinates } from '../types';\n\n/**\n * Get the customer's coordinates via the browser Geolocation API.\n * The browser prompts for permission if not already granted; resolves\n * null when denied, unsupported, or the lookup fails — never throws.\n */\nexport const getGeoCoordinates = async (): Promise<GeoCoordinates | null> => {\n try {\n if (!navigator?.geolocation) {\n return null;\n }\n\n return await new Promise<GeoCoordinates | null>(resolve => {\n navigator.geolocation.getCurrentPosition(\n ({ coords }) => resolve({ latitude: coords.latitude, longitude: coords.longitude }),\n () => resolve(null),\n { maximumAge: 15 * 60 * 1000, timeout: 10000 },\n );\n });\n } catch {\n return null;\n }\n};\n",":host {\n display: block;\n}\n\n.geo-permission {\n --body-spacing: var(--sc-spacing-x-large);\n --width: 460px;\n\n // Inherit the dialog's themed (description-slot) color so it stays readable in light and dark.\n &__content {\n line-height: 1.5;\n\n p {\n margin: 0 0 var(--sc-spacing-small);\n\n &:first-child {\n margin-top: 0;\n }\n\n &:last-child {\n margin-bottom: 0;\n }\n }\n }\n}\n","import { Component, Host, h, State } from '@stencil/core';\nimport { state as checkoutState, onChange } from '@store/checkout';\nimport { getGeoCoordinates } from '../../../../../functions/geolocation';\nimport { getGeoPermissionDefaults } from '../../../../../functions/geo-permission';\n\n/** localStorage key that remembers the customer's decision so we honor it on future visits. */\nconst DECISION_KEY = 'sc_geo_capture_decision';\n\n/**\n * Explains why location is requested (e.g. regional / purchasing-power-parity pricing) and gates\n * the browser geolocation prompt behind an explicit opt-in when capture is enabled by the merchant.\n */\n@Component({\n tag: 'sc-checkout-geo-permission',\n styleUrl: 'sc-checkout-geo-permission.scss',\n shadow: true,\n})\nexport class ScCheckoutGeoPermission {\n /** Whether the explainer dialog is open. */\n @State() open: boolean = false;\n\n /** Disposer for the checkout store subscription while we wait for the cart to load. */\n private removeCartListener?: () => void;\n\n /** Whether the cart currently has at least one line item. */\n cartHasItems(): boolean {\n return !!checkoutState.checkout?.line_items?.pagination?.count;\n }\n\n /** Whether the customer dismissed our explainer (\"Not now\") on a previous visit. */\n wasDismissed(): boolean {\n try {\n return window.localStorage.getItem(DECISION_KEY) === 'declined';\n } catch {\n return false;\n }\n }\n\n /** Remember a dismissal so we don't re-show the explainer on every visit. */\n rememberDismissed() {\n try {\n window.localStorage.setItem(DECISION_KEY, 'declined');\n } catch {\n // Storage unavailable (private mode etc.) — the dismissal simply isn't remembered.\n }\n }\n\n /**\n * The browser's current geolocation permission, or null if the Permissions API is\n * unavailable (e.g. older Safari). Lets us avoid showing an explainer whose Allow\n * button can't actually trigger a prompt.\n */\n async getPermissionState(): Promise<PermissionState | null> {\n try {\n const status = await navigator.permissions?.query({ name: 'geolocation' as PermissionName });\n return status?.state ?? null;\n } catch {\n return null;\n }\n }\n\n async componentDidLoad() {\n // Nothing to do unless the merchant enabled capture and the browser can actually geolocate.\n if (!checkoutState.captureGeoAddressEnabled) return;\n if (!window?.isSecureContext || !navigator?.geolocation) return;\n\n // Don't request location for an empty cart — there's nothing to price yet, and a\n // full-screen prompt before adding items feels premature. The cart loads async, so\n // if it's not ready we wait for the first change that adds an item.\n if (this.cartHasItems()) {\n this.maybePromptForLocation();\n return;\n }\n\n this.removeCartListener = onChange('checkout', () => {\n if (!this.cartHasItems()) return;\n this.removeCartListener?.();\n this.removeCartListener = undefined;\n this.maybePromptForLocation();\n });\n }\n\n disconnectedCallback() {\n this.removeCartListener?.();\n }\n\n /** Decide, based on the browser permission, whether to capture silently, explain, or do nothing. */\n async maybePromptForLocation() {\n const permission = await this.getPermissionState();\n\n // Already granted at the browser level: capture silently — no need to ask again.\n if (permission === 'granted') {\n this.capture();\n return;\n }\n\n // Blocked at the browser level: JS can't re-prompt, so don't show an Allow button we can't fulfill.\n // Checkout continues normally with no capture; the customer can re-enable in their browser settings.\n if (permission === 'denied') return;\n\n // State is 'prompt' (or unknown): respect a prior \"Not now\", otherwise explain before prompting.\n if (this.wasDismissed()) return;\n this.open = true;\n }\n\n /** Capture coordinates into the checkout store. They ride along on the next update/finalize. */\n async capture() {\n if (checkoutState.geoCoordinates) return;\n checkoutState.geoCoordinates = await getGeoCoordinates();\n }\n\n /** Customer opted in — close and fire the native browser prompt. */\n onAllow() {\n this.open = false;\n this.capture();\n }\n\n /** Customer dismissed — remember it and close. Checkout continues with no capture. */\n onDecline() {\n this.rememberDismissed();\n this.open = false;\n }\n\n render() {\n const { geoCapture } = checkoutState;\n\n // Shared defaults (see getGeoPermissionDefaults) so merchant fields can stay empty and\n // the admin placeholders and this dialog never drift apart.\n const defaults = getGeoPermissionDefaults();\n const title = geoCapture?.title || defaults.title;\n const content = geoCapture?.content || defaults.content;\n const allowLabel = geoCapture?.allowLabel || defaults.allowLabel;\n const declineLabel = geoCapture?.declineLabel || defaults.declineLabel;\n\n return (\n <Host>\n <sc-dialog open={this.open} noHeader={true} onScRequestClose={() => this.onDecline()} class=\"geo-permission\">\n <sc-dashboard-module style={{ '--sc-dashboard-module-spacing': '1em' }}>\n <sc-flex slot=\"heading\" align-items=\"center\" justify-content=\"flex-start\">\n <sc-icon name=\"map-pin\" style={{ color: 'var(--sc-color-primary-500)' }}></sc-icon>\n {title}\n </sc-flex>\n\n <div slot=\"description\" class=\"geo-permission__content\" innerHTML={content}></div>\n </sc-dashboard-module>\n\n <sc-button slot=\"footer\" type=\"text\" onClick={() => this.onDecline()}>\n {declineLabel}\n </sc-button>\n <sc-button slot=\"footer\" type=\"primary\" onClick={() => this.onAllow()}>\n {allowLabel}\n <sc-icon name=\"map-pin\" slot=\"suffix\"></sc-icon>\n </sc-button>\n </sc-dialog>\n </Host>\n );\n }\n}\n"],"version":3}