@quantaroute/checkout 1.1.1 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,3 @@
1
- import { CSSProperties } from 'react';
2
1
  import { default as default_2 } from 'react';
3
2
 
4
3
  /**
@@ -49,6 +48,8 @@ export declare interface AdministrativeInfo {
49
48
  max_population_density?: number;
50
49
  }
51
50
 
51
+ declare type AnyStyle = any;
52
+
52
53
  /**
53
54
  * QuantaRoute Checkout Widget
54
55
  *
@@ -81,12 +82,12 @@ export declare interface CheckoutWidgetProps {
81
82
  defaultLng?: number;
82
83
  /** Color theme. Defaults to 'light'. */
83
84
  theme?: 'light' | 'dark';
84
- /** CSS class name(s) to add to the root element. */
85
+ /** CSS class name(s) to add to the root element (web only). */
85
86
  className?: string;
86
- /** Inline styles for the root element. */
87
- style?: CSSProperties;
88
- /** Map area height. Defaults to '380px'. */
89
- mapHeight?: string;
87
+ /** Inline styles for the root element (CSSProperties on web, StyleProp<ViewStyle> on native). */
88
+ style?: AnyStyle;
89
+ /** Map area height. String on web ('380px'), number on native (380). Defaults to '380px' / 380. */
90
+ mapHeight?: string | number;
90
91
  /** Widget header title. Defaults to 'Add Delivery Address'. */
91
92
  title?: string;
92
93
  /**
@@ -211,7 +212,8 @@ export declare interface MapPinSelectorProps {
211
212
  onLocationConfirm: (lat: number, lng: number, digipin: string) => void;
212
213
  defaultLat?: number;
213
214
  defaultLng?: number;
214
- mapHeight?: string;
215
+ /** Map height. String on web ('380px'), number on native (380). */
216
+ mapHeight?: string | number;
215
217
  theme?: 'light' | 'dark';
216
218
  /**
217
219
  * URL to an India boundary GeoJSON file (FeatureCollection).
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * @quantaroute/checkout v1.1.1
2
+ * @quantaroute/checkout v1.2.0
3
3
  * (c) 2026 QuantaRoute – https://quantaroute.com
4
4
  * Released under the MIT License.
5
5
  *
@@ -7,10 +7,16 @@
7
7
  * (Apache 2.0 – © India Post, IIT Hyderabad, ISRO NRSC)
8
8
  * https://github.com/INDIAPOST-gov/digipin
9
9
  *
10
- * Peer dependencies (install separately):
11
- * react >= 18.2.0
10
+ * Web peer dependencies (install separately):
11
+ * react >= 18.0.0
12
12
  * react-dom >= 18.2.0
13
13
  * maplibre-gl >= 4.0.0
14
+ *
15
+ * Native peer dependencies (Expo / React Native):
16
+ * react-native >= 0.72.0
17
+ * expo >= 49.0.0
18
+ * expo-osm-sdk >= 2.0.0
19
+ * expo-location >= 17.0.0
14
20
  */
15
21
  import { jsxs, jsx, Fragment } from "react/jsx-runtime";
16
22
  import { useState, useCallback, useRef, useEffect, useReducer, useMemo } from "react";
@@ -445,6 +451,14 @@ const MapPinSelector = ({
445
451
  ] });
446
452
  };
447
453
  const DEFAULT_BASE_URL = "https://api.quantaroute.com";
454
+ function makeTimeoutSignal(ms) {
455
+ if (typeof AbortSignal !== "undefined" && typeof AbortSignal.timeout === "function") {
456
+ return AbortSignal.timeout(ms);
457
+ }
458
+ const ctrl = new AbortController();
459
+ setTimeout(() => ctrl.abort(), ms);
460
+ return ctrl.signal;
461
+ }
448
462
  async function lookupLocation(lat, lng, apiKey, baseUrl = DEFAULT_BASE_URL) {
449
463
  const url = `${baseUrl.replace(/\/$/, "")}/v1/location/lookup`;
450
464
  let res;
@@ -456,11 +470,10 @@ async function lookupLocation(lat, lng, apiKey, baseUrl = DEFAULT_BASE_URL) {
456
470
  "x-api-key": apiKey
457
471
  },
458
472
  body: JSON.stringify({ latitude: lat, longitude: lng }),
459
- signal: AbortSignal.timeout(15e3)
460
- // 15s timeout
473
+ signal: makeTimeoutSignal(15e3)
461
474
  });
462
475
  } catch (err) {
463
- if (err instanceof Error && err.name === "TimeoutError") {
476
+ if (err instanceof Error && (err.name === "TimeoutError" || err.name === "AbortError")) {
464
477
  throw new Error("Request timed out. Check your internet connection.");
465
478
  }
466
479
  throw new Error(`Network error: ${err instanceof Error ? err.message : String(err)}`);
@@ -496,11 +509,10 @@ async function reverseGeocode(digipin, apiKey, baseUrl = DEFAULT_BASE_URL) {
496
509
  "x-api-key": apiKey
497
510
  },
498
511
  body: JSON.stringify({ digipin }),
499
- signal: AbortSignal.timeout(15e3)
500
- // 15s timeout
512
+ signal: makeTimeoutSignal(15e3)
501
513
  });
502
514
  } catch (err) {
503
- if (err instanceof Error && err.name === "TimeoutError") {
515
+ if (err instanceof Error && (err.name === "TimeoutError" || err.name === "AbortError")) {
504
516
  throw new Error("Request timed out. Check your internet connection.");
505
517
  }
506
518
  throw new Error(`Network error: ${err instanceof Error ? err.message : String(err)}`);
@@ -1,7 +1,7 @@
1
- !function(e,r){"object"==typeof exports&&"undefined"!=typeof module?r(exports,require("react/jsx-runtime"),require("react"),require("maplibre-gl")):"function"==typeof define&&define.amd?define(["exports","react/jsx-runtime","react","maplibre-gl"],r):r((e="undefined"!=typeof globalThis?globalThis:e||self).QuantaRouteCheckout={},e.ReactJSXRuntime,e.React,e.maplibregl)}(this,function(e,r,a,n){"use strict"
1
+ !function(e,r){"object"==typeof exports&&"undefined"!=typeof module?r(exports,require("react/jsx-runtime"),require("react"),require("maplibre-gl")):"function"==typeof define&&define.amd?define(["exports","react/jsx-runtime","react","maplibre-gl"],r):r((e="undefined"!=typeof globalThis?globalThis:e||self).QuantaRouteCheckout={},e.ReactJSXRuntime,e.React,e.maplibregl)}(this,function(e,r,n,t){"use strict"
2
2
 
3
3
  ;/*!
4
- * @quantaroute/checkout v1.1.1
4
+ * @quantaroute/checkout v1.2.0
5
5
  * (c) 2026 QuantaRoute – https://quantaroute.com
6
6
  * Released under the MIT License.
7
7
  *
@@ -9,80 +9,88 @@
9
9
  * (Apache 2.0 – © India Post, IIT Hyderabad, ISRO NRSC)
10
10
  * https://github.com/INDIAPOST-gov/digipin
11
11
  *
12
- * Peer dependencies (install separately):
13
- * react >= 18.2.0
12
+ * Web peer dependencies (install separately):
13
+ * react >= 18.0.0
14
14
  * react-dom >= 18.2.0
15
15
  * maplibre-gl >= 4.0.0
16
- */const t=[["F","C","9","8"],["J","3","2","7"],["K","4","5","6"],["L","M","P","T"]],i={minLat:2.5,maxLat:38.5,minLon:63.5,maxLon:99.5}
16
+ *
17
+ * Native peer dependencies (Expo / React Native):
18
+ * react-native >= 0.72.0
19
+ * expo >= 49.0.0
20
+ * expo-osm-sdk >= 2.0.0
21
+ * expo-location >= 17.0.0
22
+ */const a=[["F","C","9","8"],["J","3","2","7"],["K","4","5","6"],["L","M","P","T"]],i={minLat:2.5,maxLat:38.5,minLon:63.5,maxLon:99.5}
17
23
  function o(e,r){if(e<i.minLat||e>i.maxLat)throw new Error(`Latitude ${e} out of range. Must be between ${i.minLat} and ${i.maxLat}`)
18
24
  if(r<i.minLon||r>i.maxLon)throw new Error(`Longitude ${r} out of range. Must be between ${i.minLon} and ${i.maxLon}`)
19
- let a=i.minLat,n=i.maxLat,o=i.minLon,l=i.maxLon,s=""
20
- for(let i=1;i<=10;i++){const d=(n-a)/4,c=(l-o)/4
21
- let u=3-Math.floor((e-a)/d),h=Math.floor((r-o)/c)
22
- u=Math.max(0,Math.min(u,3)),h=Math.max(0,Math.min(h,3)),s+=t[u][h],3!==i&&6!==i||(s+="-"),n=a+d*(4-u),a+=d*(3-u),o+=c*h,l=o+c}return s}function l(e){const r=e.replace(/-/g,"")
25
+ let n=i.minLat,t=i.maxLat,o=i.minLon,l=i.maxLon,s=""
26
+ for(let i=1;i<=10;i++){const d=(t-n)/4,c=(l-o)/4
27
+ let u=3-Math.floor((e-n)/d),h=Math.floor((r-o)/c)
28
+ u=Math.max(0,Math.min(u,3)),h=Math.max(0,Math.min(h,3)),s+=a[u][h],3!==i&&6!==i||(s+="-"),t=n+d*(4-u),n+=d*(3-u),o+=c*h,l=o+c}return s}function l(e){const r=e.replace(/-/g,"")
23
29
  if(10!==r.length)throw new Error("Invalid DIGIPIN: must be 10 characters (excluding dashes)")
24
- let a=i.minLat,n=i.maxLat,o=i.minLon,l=i.maxLon
30
+ let n=i.minLat,t=i.maxLat,o=i.minLon,l=i.maxLon
25
31
  for(let i=0;i<10;i++){const e=r[i]
26
32
  let s=!1,d=-1,c=-1
27
- for(let r=0;r<4;r++){for(let a=0;a<4;a++)if(t[r][a]===e){d=r,c=a,s=!0
33
+ for(let r=0;r<4;r++){for(let n=0;n<4;n++)if(a[r][n]===e){d=r,c=n,s=!0
28
34
  break}if(s)break}if(!s)throw new Error(`Invalid character in DIGIPIN: '${e}'`)
29
- const u=(n-a)/4,h=(l-o)/4,p=o+h*(c+1)
30
- a=n-u*(d+1),n-=u*d,o+=h*c,l=p}return{latitude:((a+n)/2).toFixed(6),longitude:((o+l)/2).toFixed(6)}}function s(e,r){return e>=i.minLat&&e<=i.maxLat&&r>=i.minLon&&r<=i.maxLon}function d(){const[e,r]=a.useState({loading:!1,error:null})
31
- return{...e,locate:a.useCallback(e=>{navigator.geolocation?(r({loading:!0,error:null}),navigator.geolocation.getCurrentPosition(({coords:a})=>{r({loading:!1,error:null}),e(a.latitude,a.longitude)},e=>{let a="Unable to get location."
32
- e.code===e.PERMISSION_DENIED?a="Location permission denied. Please enable it in browser settings.":e.code===e.POSITION_UNAVAILABLE?a="Location unavailable. Try again or place the pin manually.":e.code===e.TIMEOUT&&(a="Location request timed out. Try again."),r({loading:!1,error:a})},{enableHighAccuracy:!0,timeout:12e3,maximumAge:0})):r({loading:!1,error:"Geolocation is not supported by your browser."})},[]),clearError:a.useCallback(()=>{r(e=>({...e,error:null}))},[])}}function c(e,r){if(!s(e,r))return null
33
- try{return o(e,r)}catch{return null}}const u=({onLocationConfirm:e,defaultLat:t,defaultLng:i,mapHeight:o="380px",theme:l="light",indiaBoundaryUrl:s})=>{const u=a.useRef(null),h=a.useRef(null),p=a.useRef(null),m=t??13.00427,g=i??77.589291,f=void 0!==t&&void 0!==i,[q,b]=a.useState(m),[y,N]=a.useState(g),[v,_]=a.useState(()=>c(m,g)),[w,k]=a.useState(!1),{loading:x,error:L,locate:C,clearError:E}=d(),S=a.useCallback((e,r)=>{_(c(e,r))},[])
34
- a.useEffect(()=>{if(!u.current)return
35
- const e=s?fetch(s).then(e=>e.ok?e.json():null).catch(()=>null):Promise.resolve(null),r=f?18:9,a=new n.Map({container:u.current,style:"https://basemaps.cartocdn.com/gl/positron-gl-style/style.json",center:[g,m],zoom:r,minZoom:6,attributionControl:!1,touchZoomRotate:!0})
36
- return a.addControl(new n.AttributionControl({compact:!0,customAttribution:'© <a href="https://www.openstreetmap.org/copyright" target="_blank" rel="noopener">OpenStreetMap</a> contributors &nbsp;© <a href="https://carto.com/attributions" target="_blank" rel="noopener">CARTO</a>'}),"bottom-right"),a.addControl(new n.NavigationControl({showCompass:!1}),"top-right"),a.on("load",()=>{k(!0),e.then(e=>{if(e&&a.isStyleLoaded())try{a.addSource("india-boundary",{type:"geojson",data:e}),a.addLayer({id:"india-boundary-line",type:"line",source:"india-boundary",paint:{"line-color":"#94a3b8","line-width":1.5,"line-opacity":.65}})}catch{}})
35
+ const u=(t-n)/4,h=(l-o)/4,p=o+h*(c+1)
36
+ n=t-u*(d+1),t-=u*d,o+=h*c,l=p}return{latitude:((n+t)/2).toFixed(6),longitude:((o+l)/2).toFixed(6)}}function s(e,r){return e>=i.minLat&&e<=i.maxLat&&r>=i.minLon&&r<=i.maxLon}function d(){const[e,r]=n.useState({loading:!1,error:null})
37
+ return{...e,locate:n.useCallback(e=>{navigator.geolocation?(r({loading:!0,error:null}),navigator.geolocation.getCurrentPosition(({coords:n})=>{r({loading:!1,error:null}),e(n.latitude,n.longitude)},e=>{let n="Unable to get location."
38
+ e.code===e.PERMISSION_DENIED?n="Location permission denied. Please enable it in browser settings.":e.code===e.POSITION_UNAVAILABLE?n="Location unavailable. Try again or place the pin manually.":e.code===e.TIMEOUT&&(n="Location request timed out. Try again."),r({loading:!1,error:n})},{enableHighAccuracy:!0,timeout:12e3,maximumAge:0})):r({loading:!1,error:"Geolocation is not supported by your browser."})},[]),clearError:n.useCallback(()=>{r(e=>({...e,error:null}))},[])}}function c(e,r){if(!s(e,r))return null
39
+ try{return o(e,r)}catch{return null}}const u=({onLocationConfirm:e,defaultLat:a,defaultLng:i,mapHeight:o="380px",theme:l="light",indiaBoundaryUrl:s})=>{const u=n.useRef(null),h=n.useRef(null),p=n.useRef(null),m=a??13.00427,f=i??77.589291,g=void 0!==a&&void 0!==i,[b,q]=n.useState(m),[y,N]=n.useState(f),[v,_]=n.useState(()=>c(m,f)),[w,k]=n.useState(!1),{loading:x,error:L,locate:C,clearError:E}=d(),A=n.useCallback((e,r)=>{_(c(e,r))},[])
40
+ n.useEffect(()=>{if(!u.current)return
41
+ const e=s?fetch(s).then(e=>e.ok?e.json():null).catch(()=>null):Promise.resolve(null),r=g?18:9,n=new t.Map({container:u.current,style:"https://basemaps.cartocdn.com/gl/positron-gl-style/style.json",center:[f,m],zoom:r,minZoom:6,attributionControl:!1,touchZoomRotate:!0})
42
+ return n.addControl(new t.AttributionControl({compact:!0,customAttribution:'© <a href="https://www.openstreetmap.org/copyright" target="_blank" rel="noopener">OpenStreetMap</a> contributors &nbsp;© <a href="https://carto.com/attributions" target="_blank" rel="noopener">CARTO</a>'}),"bottom-right"),n.addControl(new t.NavigationControl({showCompass:!1}),"top-right"),n.on("load",()=>{k(!0),e.then(e=>{if(e&&n.isStyleLoaded())try{n.addSource("india-boundary",{type:"geojson",data:e}),n.addLayer({id:"india-boundary-line",type:"line",source:"india-boundary",paint:{"line-color":"#94a3b8","line-width":1.5,"line-opacity":.65}})}catch{}})
37
43
  const r=function(){const e=document.createElement("div")
38
- return e.className="qr-pin",e.setAttribute("aria-label","Drag to adjust your location"),e.style.width="40px",e.style.height="52px",e.innerHTML='\n <svg\n class="qr-pin__svg"\n width="40"\n height="52"\n viewBox="0 0 40 52"\n overflow="visible"\n fill="none"\n xmlns="http://www.w3.org/2000/svg"\n aria-hidden="true"\n >\n <defs>\n \x3c!--\n The filter region must be generous enough to contain the shadow blur.\n stdDeviation=3 → ~9px blur radius; our region extends 50% on each side.\n overflow="visible" on the <svg> lets it paint outside the viewport.\n --\x3e\n <filter id="qr-pin-shadow" x="-60%" y="-40%" width="220%" height="220%">\n <feDropShadow dx="0" dy="3" stdDeviation="3.5"\n flood-color="#000" flood-opacity="0.28"/>\n </filter>\n </defs>\n\n \x3c!-- Teardrop body — tip is at (20, 52) = bottom-center of the SVG --\x3e\n <path\n d="M20 0C9.402 0 0 9.402 0 20C0 34 20 52 20 52S40 34 40 20C40 9.402 30.598 0 20 0Z"\n fill="#0ea5e9"\n filter="url(#qr-pin-shadow)"\n />\n \x3c!-- Dot inside the pin --\x3e\n <circle cx="20" cy="20" r="9" fill="white"/>\n <circle cx="20" cy="20" r="5" fill="#0ea5e9"/>\n </svg>\n\n \x3c!--\n Pulse ring — position: absolute inside .qr-pin (position: relative).\n bottom: -(height/2) → bottom: -10px centres the 20×20 circle\n exactly at y = PIN_H = 52px = the pin tip coordinate.\n --\x3e\n <div class="qr-pin__pulse" aria-hidden="true"></div>\n ',e}(),t=new n.Marker({element:r,draggable:!0,anchor:"bottom"}).setLngLat([g,m]).addTo(a)
39
- t.on("drag",()=>{const e=t.getLngLat()
40
- b(e.lat),N(e.lng),S(e.lat,e.lng)}),t.on("dragend",()=>{const e=t.getLngLat()
41
- a.easeTo({center:[e.lng,e.lat],duration:250})}),p.current=t}),a.on("click",e=>{var r
42
- const n=e.lngLat
43
- null==(r=p.current)||r.setLngLat([n.lng,n.lat]),b(n.lat),N(n.lng),S(n.lat,n.lng),a.easeTo({center:[n.lng,n.lat],duration:200})}),h.current=a,()=>{a.remove(),h.current=null,p.current=null}},[])
44
- const A=a.useCallback(()=>{E(),C((e,r)=>{var a
45
- b(e),N(r),S(e,r),h.current&&h.current.flyTo({center:[r,e],zoom:18,duration:1400,essential:!0}),null==(a=p.current)||a.setLngLat([r,e])})},[C,E,S]),T=a.useCallback(()=>{v&&e(q,y,v)},[q,y,v,e]),I=null!==v,M=I&&w
46
- return r.jsxs("div",{className:"qr-map-wrapper",children:[r.jsxs("div",{className:"qr-step-header",children:[r.jsx("div",{className:"qr-step-badge",children:"1"}),r.jsxs("div",{className:"qr-step-text",children:[r.jsx("span",{className:"qr-step-title",children:"Pin Your Location"}),r.jsx("span",{className:"qr-step-sub",children:"Tap the map or drag the pin to your exact home / office"})]})]}),r.jsxs("div",{className:"qr-map-outer",style:{height:o},children:[r.jsx("div",{ref:u,className:"qr-map-canvas"}),v&&r.jsxs("div",{className:"qr-digipin-badge","aria-live":"polite","aria-label":`DigiPin: ${v}`,children:[r.jsx("span",{className:"qr-digipin-badge__label",children:"DigiPin"}),r.jsx("span",{className:"qr-digipin-badge__code",children:v})]}),!I&&w&&r.jsx("div",{className:"qr-map-notice",role:"status",children:"Move map to India to get a DigiPin"}),r.jsx("button",{className:"qr-locate-btn"+(x?" qr-locate-btn--loading":""),onClick:A,disabled:x,title:"Use my current location","aria-label":"Use my current location",type:"button",children:x?r.jsx("span",{className:"qr-spinner","aria-hidden":"true"}):r.jsxs("svg",{"aria-hidden":"true",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",children:[r.jsx("circle",{cx:"12",cy:"12",r:"3",fill:"currentColor",stroke:"none"}),r.jsx("circle",{cx:"12",cy:"12",r:"7"}),r.jsx("path",{d:"M12 2v3M12 19v3M2 12h3M19 12h3"})]})}),L&&r.jsxs("div",{className:"qr-geo-error",role:"alert",children:[r.jsx("span",{children:L}),r.jsx("button",{type:"button",className:"qr-geo-error__dismiss",onClick:E,"aria-label":"Dismiss error",children:"×"})]})]}),w&&r.jsxs("div",{className:"qr-coords-strip","aria-label":"Current pin coordinates",children:[r.jsxs("span",{children:[q.toFixed(5),"° N"]}),r.jsx("span",{className:"qr-coords-sep",children:"·"}),r.jsxs("span",{children:[y.toFixed(5),"° E"]})]}),r.jsxs("div",{className:"qr-map-actions",children:[r.jsxs("button",{type:"button",className:"qr-btn qr-btn--primary qr-btn--full",onClick:T,disabled:!M,children:["Confirm Location",r.jsx("svg",{"aria-hidden":"true",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2.5",strokeLinecap:"round",strokeLinejoin:"round",children:r.jsx("path",{d:"M5 12h14M12 5l7 7-7 7"})})]}),!I&&w&&r.jsx("p",{className:"qr-map-hint",children:"Move the pin to a location in India to continue"})]})]})},h="https://api.quantaroute.com"
47
- async function p(e,r,a,n=h){const t=`${n.replace(/\/$/,"")}/v1/location/lookup`
44
+ return e.className="qr-pin",e.setAttribute("aria-label","Drag to adjust your location"),e.style.width="40px",e.style.height="52px",e.innerHTML='\n <svg\n class="qr-pin__svg"\n width="40"\n height="52"\n viewBox="0 0 40 52"\n overflow="visible"\n fill="none"\n xmlns="http://www.w3.org/2000/svg"\n aria-hidden="true"\n >\n <defs>\n \x3c!--\n The filter region must be generous enough to contain the shadow blur.\n stdDeviation=3 → ~9px blur radius; our region extends 50% on each side.\n overflow="visible" on the <svg> lets it paint outside the viewport.\n --\x3e\n <filter id="qr-pin-shadow" x="-60%" y="-40%" width="220%" height="220%">\n <feDropShadow dx="0" dy="3" stdDeviation="3.5"\n flood-color="#000" flood-opacity="0.28"/>\n </filter>\n </defs>\n\n \x3c!-- Teardrop body — tip is at (20, 52) = bottom-center of the SVG --\x3e\n <path\n d="M20 0C9.402 0 0 9.402 0 20C0 34 20 52 20 52S40 34 40 20C40 9.402 30.598 0 20 0Z"\n fill="#0ea5e9"\n filter="url(#qr-pin-shadow)"\n />\n \x3c!-- Dot inside the pin --\x3e\n <circle cx="20" cy="20" r="9" fill="white"/>\n <circle cx="20" cy="20" r="5" fill="#0ea5e9"/>\n </svg>\n\n \x3c!--\n Pulse ring — position: absolute inside .qr-pin (position: relative).\n bottom: -(height/2) → bottom: -10px centres the 20×20 circle\n exactly at y = PIN_H = 52px = the pin tip coordinate.\n --\x3e\n <div class="qr-pin__pulse" aria-hidden="true"></div>\n ',e}(),a=new t.Marker({element:r,draggable:!0,anchor:"bottom"}).setLngLat([f,m]).addTo(n)
45
+ a.on("drag",()=>{const e=a.getLngLat()
46
+ q(e.lat),N(e.lng),A(e.lat,e.lng)}),a.on("dragend",()=>{const e=a.getLngLat()
47
+ n.easeTo({center:[e.lng,e.lat],duration:250})}),p.current=a}),n.on("click",e=>{var r
48
+ const t=e.lngLat
49
+ null==(r=p.current)||r.setLngLat([t.lng,t.lat]),q(t.lat),N(t.lng),A(t.lat,t.lng),n.easeTo({center:[t.lng,t.lat],duration:200})}),h.current=n,()=>{n.remove(),h.current=null,p.current=null}},[])
50
+ const S=n.useCallback(()=>{E(),C((e,r)=>{var n
51
+ q(e),N(r),A(e,r),h.current&&h.current.flyTo({center:[r,e],zoom:18,duration:1400,essential:!0}),null==(n=p.current)||n.setLngLat([r,e])})},[C,E,A]),T=n.useCallback(()=>{v&&e(b,y,v)},[b,y,v,e]),I=null!==v,M=I&&w
52
+ return r.jsxs("div",{className:"qr-map-wrapper",children:[r.jsxs("div",{className:"qr-step-header",children:[r.jsx("div",{className:"qr-step-badge",children:"1"}),r.jsxs("div",{className:"qr-step-text",children:[r.jsx("span",{className:"qr-step-title",children:"Pin Your Location"}),r.jsx("span",{className:"qr-step-sub",children:"Tap the map or drag the pin to your exact home / office"})]})]}),r.jsxs("div",{className:"qr-map-outer",style:{height:o},children:[r.jsx("div",{ref:u,className:"qr-map-canvas"}),v&&r.jsxs("div",{className:"qr-digipin-badge","aria-live":"polite","aria-label":`DigiPin: ${v}`,children:[r.jsx("span",{className:"qr-digipin-badge__label",children:"DigiPin"}),r.jsx("span",{className:"qr-digipin-badge__code",children:v})]}),!I&&w&&r.jsx("div",{className:"qr-map-notice",role:"status",children:"Move map to India to get a DigiPin"}),r.jsx("button",{className:"qr-locate-btn"+(x?" qr-locate-btn--loading":""),onClick:S,disabled:x,title:"Use my current location","aria-label":"Use my current location",type:"button",children:x?r.jsx("span",{className:"qr-spinner","aria-hidden":"true"}):r.jsxs("svg",{"aria-hidden":"true",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",children:[r.jsx("circle",{cx:"12",cy:"12",r:"3",fill:"currentColor",stroke:"none"}),r.jsx("circle",{cx:"12",cy:"12",r:"7"}),r.jsx("path",{d:"M12 2v3M12 19v3M2 12h3M19 12h3"})]})}),L&&r.jsxs("div",{className:"qr-geo-error",role:"alert",children:[r.jsx("span",{children:L}),r.jsx("button",{type:"button",className:"qr-geo-error__dismiss",onClick:E,"aria-label":"Dismiss error",children:"×"})]})]}),w&&r.jsxs("div",{className:"qr-coords-strip","aria-label":"Current pin coordinates",children:[r.jsxs("span",{children:[b.toFixed(5),"° N"]}),r.jsx("span",{className:"qr-coords-sep",children:"·"}),r.jsxs("span",{children:[y.toFixed(5),"° E"]})]}),r.jsxs("div",{className:"qr-map-actions",children:[r.jsxs("button",{type:"button",className:"qr-btn qr-btn--primary qr-btn--full",onClick:T,disabled:!M,children:["Confirm Location",r.jsx("svg",{"aria-hidden":"true",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2.5",strokeLinecap:"round",strokeLinejoin:"round",children:r.jsx("path",{d:"M5 12h14M12 5l7 7-7 7"})})]}),!I&&w&&r.jsx("p",{className:"qr-map-hint",children:"Move the pin to a location in India to continue"})]})]})},h="https://api.quantaroute.com"
53
+ function p(e){if("undefined"!=typeof AbortSignal&&"function"==typeof AbortSignal.timeout)return AbortSignal.timeout(e)
54
+ const r=new AbortController
55
+ return setTimeout(()=>r.abort(),e),r.signal}async function m(e,r,n,t=h){const a=`${t.replace(/\/$/,"")}/v1/location/lookup`
48
56
  let i
49
- try{i=await fetch(t,{method:"POST",headers:{"Content-Type":"application/json","x-api-key":a},body:JSON.stringify({latitude:e,longitude:r}),signal:AbortSignal.timeout(15e3)})}catch(l){if(l instanceof Error&&"TimeoutError"===l.name)throw new Error("Request timed out. Check your internet connection.")
57
+ try{i=await fetch(a,{method:"POST",headers:{"Content-Type":"application/json","x-api-key":n},body:JSON.stringify({latitude:e,longitude:r}),signal:p(15e3)})}catch(l){if(l instanceof Error&&("TimeoutError"===l.name||"AbortError"===l.name))throw new Error("Request timed out. Check your internet connection.")
50
58
  throw new Error(`Network error: ${l instanceof Error?l.message:String(l)}`)}if(!i.ok){let e=""
51
59
  try{e=await i.text()}catch{}if(401===i.status||403===i.status)throw new Error("Invalid API key. Check your QuantaRoute API key.")
52
60
  if(429===i.status)throw new Error("Rate limit exceeded. Upgrade your plan or try again later.")
53
61
  throw new Error(`API error ${i.status}: ${e||i.statusText}`)}const o=await i.json()
54
62
  if(!o.success)throw new Error(o.message??"Location lookup failed")
55
- return o}async function m(e,r,a=h){const n=`${a.replace(/\/$/,"")}/v1/digipin/reverse`
56
- let t
57
- try{t=await fetch(n,{method:"POST",headers:{"Content-Type":"application/json","x-api-key":r},body:JSON.stringify({digipin:e}),signal:AbortSignal.timeout(15e3)})}catch(o){if(o instanceof Error&&"TimeoutError"===o.name)throw new Error("Request timed out. Check your internet connection.")
58
- throw new Error(`Network error: ${o instanceof Error?o.message:String(o)}`)}if(!t.ok){let e=""
59
- try{e=await t.text()}catch{}if(401===t.status||403===t.status)throw new Error("Invalid API key. Check your QuantaRoute API key.")
60
- if(429===t.status)throw new Error("Rate limit exceeded. Upgrade your plan or try again later.")
61
- throw new Error(`API error ${t.status}: ${e||t.statusText}`)}const i=await t.json()
63
+ return o}async function f(e,r,n=h){const t=`${n.replace(/\/$/,"")}/v1/digipin/reverse`
64
+ let a
65
+ try{a=await fetch(t,{method:"POST",headers:{"Content-Type":"application/json","x-api-key":r},body:JSON.stringify({digipin:e}),signal:p(15e3)})}catch(o){if(o instanceof Error&&("TimeoutError"===o.name||"AbortError"===o.name))throw new Error("Request timed out. Check your internet connection.")
66
+ throw new Error(`Network error: ${o instanceof Error?o.message:String(o)}`)}if(!a.ok){let e=""
67
+ try{e=await a.text()}catch{}if(401===a.status||403===a.status)throw new Error("Invalid API key. Check your QuantaRoute API key.")
68
+ if(429===a.status)throw new Error("Rate limit exceeded. Upgrade your plan or try again later.")
69
+ throw new Error(`API error ${a.status}: ${e||a.statusText}`)}const i=await a.json()
62
70
  if(!i.success)throw new Error(i.message??"Reverse geocoding failed")
63
71
  return i}const g={flatNumber:"",floorNumber:"",buildingName:"",streetName:""}
64
- function f(e,r){switch(r.type){case"LOAD_START":return{status:"loading"}
72
+ function b(e,r){switch(r.type){case"LOAD_START":return{status:"loading"}
65
73
  case"LOAD_SUCCESS":{const e=function(e){const r={}
66
74
  e.name?r.buildingName=e.name:e.building_name?r.buildingName=e.building_name:e.addr_housename&&(r.buildingName=e.addr_housename)
67
- const a=[]
68
- return e.road&&a.push(e.road),e.suburb&&a.push(e.suburb),a.length>0&&(r.streetName=a.join(", ")),r}(r.addressComponents)
75
+ const n=[]
76
+ return e.road&&n.push(e.road),e.suburb&&n.push(e.suburb),n.length>0&&(r.streetName=n.join(", ")),r}(r.addressComponents)
69
77
  return{status:"ready",adminInfo:r.adminInfo,alternatives:r.alternatives||[],selectedLocality:r.adminInfo.locality,fields:{...g,...e},submitting:!1}}case"LOAD_ERROR":return{status:"error",message:r.message}
70
78
  case"SET_FIELD":return"ready"!==e.status?e:{...e,fields:{...e.fields,[r.key]:r.value}}
71
79
  case"SET_LOCALITY":return"ready"!==e.status?e:{...e,selectedLocality:r.locality}
72
80
  case"SUBMIT_START":return"ready"!==e.status?e:{...e,submitting:!0}
73
81
  case"SUBMIT_END":return"ready"!==e.status?e:{...e,submitting:!1}
74
- default:return e}}const q=({digipin:e,lat:n,lng:t,apiKey:i,apiBaseUrl:o,onAddressComplete:l,onBack:s,onError:d})=>{const[c,u]=a.useReducer(f,{status:"loading"}),h=a.useCallback(async()=>{u({type:"LOAD_START"})
75
- try{const[r,a]=await Promise.all([p(n,t,i,o),m(e,i,o)])
76
- u({type:"LOAD_SUCCESS",adminInfo:r.data.administrative_info,alternatives:r.data.alternatives||[],addressComponents:a.data.addressComponents})}catch(r){const e=r instanceof Error?r.message:"Failed to fetch address data."
77
- u({type:"LOAD_ERROR",message:e}),null==d||d(r instanceof Error?r:new Error(e))}},[e,n,t,i,o,d])
78
- a.useEffect(()=>{h()},[h])
79
- const g=a.useCallback(r=>{if(r.preventDefault(),"ready"!==c.status)return
80
- const{adminInfo:a,selectedLocality:i,fields:o}=c,s=[o.flatNumber,o.floorNumber?`Floor ${o.floorNumber}`:"",o.buildingName,o.streetName,i,a.district,a.state,a.pincode].filter(Boolean),d={digipin:e,lat:n,lng:t,state:a.state,district:a.district,division:a.division,locality:i,pincode:a.pincode,delivery:a.delivery,country:a.country??"India",...o,formattedAddress:s.join(", ")}
82
+ default:return e}}const q=({digipin:e,lat:t,lng:a,apiKey:i,apiBaseUrl:o,onAddressComplete:l,onBack:s,onError:d})=>{const[c,u]=n.useReducer(b,{status:"loading"}),h=n.useCallback(async()=>{u({type:"LOAD_START"})
83
+ try{const[r,n]=await Promise.all([m(t,a,i,o),f(e,i,o)])
84
+ u({type:"LOAD_SUCCESS",adminInfo:r.data.administrative_info,alternatives:r.data.alternatives||[],addressComponents:n.data.addressComponents})}catch(r){const e=r instanceof Error?r.message:"Failed to fetch address data."
85
+ u({type:"LOAD_ERROR",message:e}),null==d||d(r instanceof Error?r:new Error(e))}},[e,t,a,i,o,d])
86
+ n.useEffect(()=>{h()},[h])
87
+ const p=n.useCallback(r=>{if(r.preventDefault(),"ready"!==c.status)return
88
+ const{adminInfo:n,selectedLocality:i,fields:o}=c,s=[o.flatNumber,o.floorNumber?`Floor ${o.floorNumber}`:"",o.buildingName,o.streetName,i,n.district,n.state,n.pincode].filter(Boolean),d={digipin:e,lat:t,lng:a,state:n.state,district:n.district,division:n.division,locality:i,pincode:n.pincode,delivery:n.delivery,country:n.country??"India",...o,formattedAddress:s.join(", ")}
81
89
  u({type:"SUBMIT_START"})
82
- try{l(d)}finally{u({type:"SUBMIT_END"})}},[c,e,n,t,l])
83
- return r.jsxs("div",{className:"qr-form-wrapper",children:[r.jsxs("div",{className:"qr-step-header",children:[r.jsx("button",{type:"button",className:"qr-back-btn",onClick:s,"aria-label":"Back to map",children:r.jsx("svg",{"aria-hidden":"true",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2.5",strokeLinecap:"round",strokeLinejoin:"round",children:r.jsx("path",{d:"M19 12H5M12 19l-7-7 7-7"})})}),r.jsx("div",{className:"qr-step-badge",children:"2"}),r.jsxs("div",{className:"qr-step-text",children:[r.jsx("span",{className:"qr-step-title",children:"Add Address Details"}),r.jsx("span",{className:"qr-step-sub",children:"Flat number and building info"})]})]}),r.jsxs("div",{className:"qr-form-digipin-strip",children:[r.jsxs("svg",{"aria-hidden":"true",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",children:[r.jsx("path",{d:"M21 10c0 7-9 13-9 13S3 17 3 10a9 9 0 0118 0z"}),r.jsx("circle",{cx:"12",cy:"10",r:"3"})]}),r.jsx("span",{className:"qr-form-digipin-strip__label",children:"DigiPin"}),r.jsx("span",{className:"qr-form-digipin-strip__code",children:e})]}),"loading"===c.status&&r.jsxs("div",{className:"qr-loading-state","aria-busy":"true","aria-label":"Fetching address details",children:[r.jsx("div",{className:"qr-spinner qr-spinner--lg","aria-hidden":"true"}),r.jsx("p",{className:"qr-loading-state__text",children:"Fetching address details…"})]}),"error"===c.status&&r.jsxs("div",{className:"qr-error-state",role:"alert",children:[r.jsx("div",{className:"qr-error-state__icon","aria-hidden":"true",children:"⚠"}),r.jsx("p",{className:"qr-error-state__msg",children:c.message}),r.jsx("button",{type:"button",className:"qr-btn qr-btn--secondary qr-btn--sm",onClick:()=>{h()},children:"Retry"})]}),"ready"===c.status&&r.jsxs("form",{onSubmit:g,className:"qr-form",noValidate:!0,children:[r.jsxs("fieldset",{className:"qr-fieldset",children:[r.jsxs("legend",{className:"qr-fieldset__legend",children:[r.jsx("span",{className:"qr-fieldset__icon","aria-hidden":"true",children:"📍"}),"Auto-detected from your pin"]}),r.jsxs("div",{className:"qr-auto-grid",children:[r.jsxs("div",{className:"qr-auto-row",children:[r.jsx("span",{className:"qr-auto-row__label",children:"State"}),r.jsx("span",{className:"qr-auto-row__value",children:c.adminInfo.state})]}),r.jsxs("div",{className:"qr-auto-row",children:[r.jsx("span",{className:"qr-auto-row__label",children:"District"}),r.jsx("span",{className:"qr-auto-row__value",children:c.adminInfo.district})]}),r.jsxs("div",{className:"qr-auto-row qr-auto-row--full",children:[r.jsx("span",{className:"qr-auto-row__label",children:"Locality"}),c.alternatives.length>0?r.jsxs("select",{className:"qr-auto-row__select",value:c.selectedLocality,onChange:e=>u({type:"SET_LOCALITY",locality:e.target.value}),"aria-label":"Select locality",children:[r.jsx("option",{value:c.adminInfo.locality,children:c.adminInfo.locality}),c.alternatives.map((e,a)=>r.jsx("option",{value:e.name,children:e.name},a))]}):r.jsx("span",{className:"qr-auto-row__value",children:c.selectedLocality})]}),r.jsxs("div",{className:"qr-auto-row",children:[r.jsx("span",{className:"qr-auto-row__label",children:"Pincode"}),r.jsx("span",{className:"qr-auto-row__value qr-auto-row__value--pin",children:c.adminInfo.pincode})]})]})]}),r.jsxs("fieldset",{className:"qr-fieldset",children:[r.jsxs("legend",{className:"qr-fieldset__legend",children:[r.jsx("span",{className:"qr-fieldset__icon","aria-hidden":"true",children:"🏠"}),"Your details"]}),r.jsxs("div",{className:"qr-fields-grid",children:[r.jsxs("div",{className:"qr-field qr-field--full",children:[r.jsxs("label",{className:"qr-field__label",htmlFor:"qr-flatNumber",children:["Flat / House Number",r.jsx("span",{className:"qr-required","aria-hidden":"true",children:"*"})]}),r.jsx("input",{id:"qr-flatNumber",type:"text",className:"qr-field__input",placeholder:"e.g. 4B, Flat 201, House No. 12",value:c.fields.flatNumber,onChange:e=>u({type:"SET_FIELD",key:"flatNumber",value:e.target.value}),autoComplete:"address-line1",required:!0,"aria-required":"true"})]}),r.jsxs("div",{className:"qr-field",children:[r.jsxs("label",{className:"qr-field__label",htmlFor:"qr-floorNumber",children:["Floor",r.jsx("span",{className:"qr-optional",children:"optional"})]}),r.jsx("input",{id:"qr-floorNumber",type:"text",className:"qr-field__input",placeholder:"e.g. 3rd, Ground",value:c.fields.floorNumber,onChange:e=>u({type:"SET_FIELD",key:"floorNumber",value:e.target.value})})]}),r.jsxs("div",{className:"qr-field",children:[r.jsxs("label",{className:"qr-field__label",htmlFor:"qr-buildingName",children:["Building / Society",r.jsx("span",{className:"qr-optional",children:"optional"})]}),r.jsx("input",{id:"qr-buildingName",type:"text",className:"qr-field__input",placeholder:"e.g. Sunshine Apts, DDA Colony",value:c.fields.buildingName,onChange:e=>u({type:"SET_FIELD",key:"buildingName",value:e.target.value}),autoComplete:"address-line2"})]}),r.jsxs("div",{className:"qr-field qr-field--full",children:[r.jsxs("label",{className:"qr-field__label",htmlFor:"qr-streetName",children:["Street / Area",r.jsx("span",{className:"qr-optional",children:"optional"})]}),r.jsx("input",{id:"qr-streetName",type:"text",className:"qr-field__input",placeholder:"e.g. MG Road, Sector 12",value:c.fields.streetName,onChange:e=>u({type:"SET_FIELD",key:"streetName",value:e.target.value}),autoComplete:"address-level3"})]})]})]}),r.jsxs("div",{className:"qr-form-actions",children:[r.jsxs("button",{type:"button",className:"qr-btn qr-btn--ghost",onClick:s,children:[r.jsx("svg",{"aria-hidden":"true",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",children:r.jsx("path",{d:"M19 12H5M12 19l-7-7 7-7"})}),"Adjust Pin"]}),r.jsx("button",{type:"submit",className:"qr-btn qr-btn--primary qr-btn--grow",disabled:c.submitting||!c.fields.flatNumber.trim(),children:c.submitting?r.jsxs(r.Fragment,{children:[r.jsx("span",{className:"qr-spinner qr-spinner--sm","aria-hidden":"true"}),"Saving…"]}):r.jsxs(r.Fragment,{children:["Save Address",r.jsx("svg",{"aria-hidden":"true",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2.5",strokeLinecap:"round",strokeLinejoin:"round",children:r.jsx("path",{d:"M20 6L9 17l-5-5"})})]})})]})]})]})},b=({address:e,onEditAddress:a})=>r.jsxs("div",{className:"qr-success",children:[r.jsx("div",{className:"qr-success__icon","aria-hidden":"true",children:r.jsxs("svg",{viewBox:"0 0 52 52",fill:"none",xmlns:"http://www.w3.org/2000/svg",children:[r.jsx("circle",{cx:"26",cy:"26",r:"26",fill:"var(--qr-success, #10b981)",opacity:"0.12"}),r.jsx("circle",{cx:"26",cy:"26",r:"20",fill:"var(--qr-success, #10b981)"}),r.jsx("path",{d:"M15 26l8 8 14-16",stroke:"white",strokeWidth:"3",strokeLinecap:"round",strokeLinejoin:"round"})]})}),r.jsx("h3",{className:"qr-success__title",children:"Address Saved!"}),r.jsx("p",{className:"qr-success__address",children:e.formattedAddress}),r.jsx("div",{className:"qr-success__meta",children:r.jsxs("span",{className:"qr-digipin-badge qr-digipin-badge--inline",children:[r.jsx("span",{className:"qr-digipin-badge__label",children:"DigiPin"}),r.jsx("span",{className:"qr-digipin-badge__code",children:e.digipin})]})}),r.jsx("button",{type:"button",className:"qr-btn qr-btn--ghost qr-btn--sm",onClick:a,children:"Change Address"})]}),y=({apiKey:e,apiBaseUrl:n="https://api.quantaroute.com",onComplete:t,onError:i,defaultLat:o,defaultLng:l,theme:s="light",className:d="",style:c,mapHeight:h="380px",title:p="Add Delivery Address",indiaBoundaryUrl:m})=>{const[g,f]=a.useState("map"),[y,N]=a.useState(null),[v,_]=a.useState(null)
84
- return r.jsxs("div",{className:`qr-checkout qr-checkout--${s} ${d}`,style:c,"data-testid":"quantaroute-checkout",children:["done"!==g&&r.jsxs("div",{className:"qr-header",children:[r.jsxs("div",{className:"qr-header__brand",children:[r.jsxs("svg",{className:"qr-header__logo","aria-hidden":"true",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",children:[r.jsx("path",{d:"M21 10c0 7-9 13-9 13S3 17 3 10a9 9 0 0118 0z"}),r.jsx("circle",{cx:"12",cy:"10",r:"3"})]}),r.jsx("span",{className:"qr-header__title",children:p})]}),r.jsxs("div",{className:"qr-progress","aria-label":"Progress: step ${ step === 'map' ? 1 : 2 } of 2",role:"progressbar",children:[r.jsx("div",{className:"qr-progress__dot "+("map"===g||"form"===g||"done"===g?"qr-progress__dot--active":"")}),r.jsx("div",{className:"qr-progress__line "+("form"===g||"done"===g?"qr-progress__line--active":"")}),r.jsx("div",{className:"qr-progress__dot "+("form"===g||"done"===g?"qr-progress__dot--active":"")})]})]}),"map"===g&&r.jsx(u,{onLocationConfirm:(e,r,a)=>{N({lat:e,lng:r,digipin:a}),f("form")},defaultLat:o,defaultLng:l,mapHeight:h,theme:s,indiaBoundaryUrl:m}),"form"===g&&y&&r.jsx(q,{digipin:y.digipin,lat:y.lat,lng:y.lng,apiKey:e,apiBaseUrl:n,onAddressComplete:e=>{_(e),f("done"),t(e)},onBack:()=>{f("map")},onError:i,theme:s}),"done"===g&&v&&r.jsx(b,{address:v,onEditAddress:()=>{f("map"),N(null),_(null)}}),r.jsxs("div",{className:"qr-footer",children:[r.jsx("span",{children:"Powered by"}),r.jsx("a",{href:"https://quantaroute.com",target:"_blank",rel:"noopener noreferrer",className:"qr-footer__link",children:"QuantaRoute"}),r.jsx("span",{className:"qr-footer__flag","aria-label":"Made in India",children:"🇮🇳"})]})]})}
85
- e.AddressForm=q,e.CheckoutWidget=y,e.DIGIPIN_BOUNDS=i,e.MapPinSelector=u,e.default=y,e.getDigiPin=o,e.getLatLngFromDigiPin=l,e.isValidDigiPin=function(e){if(!e||"string"!=typeof e)return!1
90
+ try{l(d)}finally{u({type:"SUBMIT_END"})}},[c,e,t,a,l])
91
+ return r.jsxs("div",{className:"qr-form-wrapper",children:[r.jsxs("div",{className:"qr-step-header",children:[r.jsx("button",{type:"button",className:"qr-back-btn",onClick:s,"aria-label":"Back to map",children:r.jsx("svg",{"aria-hidden":"true",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2.5",strokeLinecap:"round",strokeLinejoin:"round",children:r.jsx("path",{d:"M19 12H5M12 19l-7-7 7-7"})})}),r.jsx("div",{className:"qr-step-badge",children:"2"}),r.jsxs("div",{className:"qr-step-text",children:[r.jsx("span",{className:"qr-step-title",children:"Add Address Details"}),r.jsx("span",{className:"qr-step-sub",children:"Flat number and building info"})]})]}),r.jsxs("div",{className:"qr-form-digipin-strip",children:[r.jsxs("svg",{"aria-hidden":"true",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",children:[r.jsx("path",{d:"M21 10c0 7-9 13-9 13S3 17 3 10a9 9 0 0118 0z"}),r.jsx("circle",{cx:"12",cy:"10",r:"3"})]}),r.jsx("span",{className:"qr-form-digipin-strip__label",children:"DigiPin"}),r.jsx("span",{className:"qr-form-digipin-strip__code",children:e})]}),"loading"===c.status&&r.jsxs("div",{className:"qr-loading-state","aria-busy":"true","aria-label":"Fetching address details",children:[r.jsx("div",{className:"qr-spinner qr-spinner--lg","aria-hidden":"true"}),r.jsx("p",{className:"qr-loading-state__text",children:"Fetching address details…"})]}),"error"===c.status&&r.jsxs("div",{className:"qr-error-state",role:"alert",children:[r.jsx("div",{className:"qr-error-state__icon","aria-hidden":"true",children:"⚠"}),r.jsx("p",{className:"qr-error-state__msg",children:c.message}),r.jsx("button",{type:"button",className:"qr-btn qr-btn--secondary qr-btn--sm",onClick:()=>{h()},children:"Retry"})]}),"ready"===c.status&&r.jsxs("form",{onSubmit:p,className:"qr-form",noValidate:!0,children:[r.jsxs("fieldset",{className:"qr-fieldset",children:[r.jsxs("legend",{className:"qr-fieldset__legend",children:[r.jsx("span",{className:"qr-fieldset__icon","aria-hidden":"true",children:"📍"}),"Auto-detected from your pin"]}),r.jsxs("div",{className:"qr-auto-grid",children:[r.jsxs("div",{className:"qr-auto-row",children:[r.jsx("span",{className:"qr-auto-row__label",children:"State"}),r.jsx("span",{className:"qr-auto-row__value",children:c.adminInfo.state})]}),r.jsxs("div",{className:"qr-auto-row",children:[r.jsx("span",{className:"qr-auto-row__label",children:"District"}),r.jsx("span",{className:"qr-auto-row__value",children:c.adminInfo.district})]}),r.jsxs("div",{className:"qr-auto-row qr-auto-row--full",children:[r.jsx("span",{className:"qr-auto-row__label",children:"Locality"}),c.alternatives.length>0?r.jsxs("select",{className:"qr-auto-row__select",value:c.selectedLocality,onChange:e=>u({type:"SET_LOCALITY",locality:e.target.value}),"aria-label":"Select locality",children:[r.jsx("option",{value:c.adminInfo.locality,children:c.adminInfo.locality}),c.alternatives.map((e,n)=>r.jsx("option",{value:e.name,children:e.name},n))]}):r.jsx("span",{className:"qr-auto-row__value",children:c.selectedLocality})]}),r.jsxs("div",{className:"qr-auto-row",children:[r.jsx("span",{className:"qr-auto-row__label",children:"Pincode"}),r.jsx("span",{className:"qr-auto-row__value qr-auto-row__value--pin",children:c.adminInfo.pincode})]})]})]}),r.jsxs("fieldset",{className:"qr-fieldset",children:[r.jsxs("legend",{className:"qr-fieldset__legend",children:[r.jsx("span",{className:"qr-fieldset__icon","aria-hidden":"true",children:"🏠"}),"Your details"]}),r.jsxs("div",{className:"qr-fields-grid",children:[r.jsxs("div",{className:"qr-field qr-field--full",children:[r.jsxs("label",{className:"qr-field__label",htmlFor:"qr-flatNumber",children:["Flat / House Number",r.jsx("span",{className:"qr-required","aria-hidden":"true",children:"*"})]}),r.jsx("input",{id:"qr-flatNumber",type:"text",className:"qr-field__input",placeholder:"e.g. 4B, Flat 201, House No. 12",value:c.fields.flatNumber,onChange:e=>u({type:"SET_FIELD",key:"flatNumber",value:e.target.value}),autoComplete:"address-line1",required:!0,"aria-required":"true"})]}),r.jsxs("div",{className:"qr-field",children:[r.jsxs("label",{className:"qr-field__label",htmlFor:"qr-floorNumber",children:["Floor",r.jsx("span",{className:"qr-optional",children:"optional"})]}),r.jsx("input",{id:"qr-floorNumber",type:"text",className:"qr-field__input",placeholder:"e.g. 3rd, Ground",value:c.fields.floorNumber,onChange:e=>u({type:"SET_FIELD",key:"floorNumber",value:e.target.value})})]}),r.jsxs("div",{className:"qr-field",children:[r.jsxs("label",{className:"qr-field__label",htmlFor:"qr-buildingName",children:["Building / Society",r.jsx("span",{className:"qr-optional",children:"optional"})]}),r.jsx("input",{id:"qr-buildingName",type:"text",className:"qr-field__input",placeholder:"e.g. Sunshine Apts, DDA Colony",value:c.fields.buildingName,onChange:e=>u({type:"SET_FIELD",key:"buildingName",value:e.target.value}),autoComplete:"address-line2"})]}),r.jsxs("div",{className:"qr-field qr-field--full",children:[r.jsxs("label",{className:"qr-field__label",htmlFor:"qr-streetName",children:["Street / Area",r.jsx("span",{className:"qr-optional",children:"optional"})]}),r.jsx("input",{id:"qr-streetName",type:"text",className:"qr-field__input",placeholder:"e.g. MG Road, Sector 12",value:c.fields.streetName,onChange:e=>u({type:"SET_FIELD",key:"streetName",value:e.target.value}),autoComplete:"address-level3"})]})]})]}),r.jsxs("div",{className:"qr-form-actions",children:[r.jsxs("button",{type:"button",className:"qr-btn qr-btn--ghost",onClick:s,children:[r.jsx("svg",{"aria-hidden":"true",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",children:r.jsx("path",{d:"M19 12H5M12 19l-7-7 7-7"})}),"Adjust Pin"]}),r.jsx("button",{type:"submit",className:"qr-btn qr-btn--primary qr-btn--grow",disabled:c.submitting||!c.fields.flatNumber.trim(),children:c.submitting?r.jsxs(r.Fragment,{children:[r.jsx("span",{className:"qr-spinner qr-spinner--sm","aria-hidden":"true"}),"Saving…"]}):r.jsxs(r.Fragment,{children:["Save Address",r.jsx("svg",{"aria-hidden":"true",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2.5",strokeLinecap:"round",strokeLinejoin:"round",children:r.jsx("path",{d:"M20 6L9 17l-5-5"})})]})})]})]})]})},y=({address:e,onEditAddress:n})=>r.jsxs("div",{className:"qr-success",children:[r.jsx("div",{className:"qr-success__icon","aria-hidden":"true",children:r.jsxs("svg",{viewBox:"0 0 52 52",fill:"none",xmlns:"http://www.w3.org/2000/svg",children:[r.jsx("circle",{cx:"26",cy:"26",r:"26",fill:"var(--qr-success, #10b981)",opacity:"0.12"}),r.jsx("circle",{cx:"26",cy:"26",r:"20",fill:"var(--qr-success, #10b981)"}),r.jsx("path",{d:"M15 26l8 8 14-16",stroke:"white",strokeWidth:"3",strokeLinecap:"round",strokeLinejoin:"round"})]})}),r.jsx("h3",{className:"qr-success__title",children:"Address Saved!"}),r.jsx("p",{className:"qr-success__address",children:e.formattedAddress}),r.jsx("div",{className:"qr-success__meta",children:r.jsxs("span",{className:"qr-digipin-badge qr-digipin-badge--inline",children:[r.jsx("span",{className:"qr-digipin-badge__label",children:"DigiPin"}),r.jsx("span",{className:"qr-digipin-badge__code",children:e.digipin})]})}),r.jsx("button",{type:"button",className:"qr-btn qr-btn--ghost qr-btn--sm",onClick:n,children:"Change Address"})]}),N=({apiKey:e,apiBaseUrl:t="https://api.quantaroute.com",onComplete:a,onError:i,defaultLat:o,defaultLng:l,theme:s="light",className:d="",style:c,mapHeight:h="380px",title:p="Add Delivery Address",indiaBoundaryUrl:m})=>{const[f,g]=n.useState("map"),[b,N]=n.useState(null),[v,_]=n.useState(null)
92
+ return r.jsxs("div",{className:`qr-checkout qr-checkout--${s} ${d}`,style:c,"data-testid":"quantaroute-checkout",children:["done"!==f&&r.jsxs("div",{className:"qr-header",children:[r.jsxs("div",{className:"qr-header__brand",children:[r.jsxs("svg",{className:"qr-header__logo","aria-hidden":"true",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",children:[r.jsx("path",{d:"M21 10c0 7-9 13-9 13S3 17 3 10a9 9 0 0118 0z"}),r.jsx("circle",{cx:"12",cy:"10",r:"3"})]}),r.jsx("span",{className:"qr-header__title",children:p})]}),r.jsxs("div",{className:"qr-progress","aria-label":"Progress: step ${ step === 'map' ? 1 : 2 } of 2",role:"progressbar",children:[r.jsx("div",{className:"qr-progress__dot "+("map"===f||"form"===f||"done"===f?"qr-progress__dot--active":"")}),r.jsx("div",{className:"qr-progress__line "+("form"===f||"done"===f?"qr-progress__line--active":"")}),r.jsx("div",{className:"qr-progress__dot "+("form"===f||"done"===f?"qr-progress__dot--active":"")})]})]}),"map"===f&&r.jsx(u,{onLocationConfirm:(e,r,n)=>{N({lat:e,lng:r,digipin:n}),g("form")},defaultLat:o,defaultLng:l,mapHeight:h,theme:s,indiaBoundaryUrl:m}),"form"===f&&b&&r.jsx(q,{digipin:b.digipin,lat:b.lat,lng:b.lng,apiKey:e,apiBaseUrl:t,onAddressComplete:e=>{_(e),g("done"),a(e)},onBack:()=>{g("map")},onError:i,theme:s}),"done"===f&&v&&r.jsx(y,{address:v,onEditAddress:()=>{g("map"),N(null),_(null)}}),r.jsxs("div",{className:"qr-footer",children:[r.jsx("span",{children:"Powered by"}),r.jsx("a",{href:"https://quantaroute.com",target:"_blank",rel:"noopener noreferrer",className:"qr-footer__link",children:"QuantaRoute"}),r.jsx("span",{className:"qr-footer__flag","aria-label":"Made in India",children:"🇮🇳"})]})]})}
93
+ e.AddressForm=q,e.CheckoutWidget=N,e.DIGIPIN_BOUNDS=i,e.MapPinSelector=u,e.default=N,e.getDigiPin=o,e.getLatLngFromDigiPin=l,e.isValidDigiPin=function(e){if(!e||"string"!=typeof e)return!1
86
94
  if(!/^[A-Z0-9]{3}-[A-Z0-9]{3}-[A-Z0-9]{4}$/i.test(e.trim()))return!1
87
- try{return l(e.trim().toUpperCase()),!0}catch{return!1}},e.isWithinIndia=s,e.lookupLocation=p,e.reverseGeocode=m,e.useDigiPin=function(e,r){return a.useMemo(()=>{if(!s(e,r))return null
95
+ try{return l(e.trim().toUpperCase()),!0}catch{return!1}},e.isWithinIndia=s,e.lookupLocation=m,e.reverseGeocode=f,e.useDigiPin=function(e,r){return n.useMemo(()=>{if(!s(e,r))return null
88
96
  try{return o(e,r)}catch{return null}},[e,r])},e.useGeolocation=d,Object.defineProperties(e,{t:{value:!0},[Symbol.toStringTag]:{value:"Module"}})})
package/expo-plugin.js ADDED
@@ -0,0 +1,109 @@
1
+ /**
2
+ * @quantaroute/checkout — Expo Config Plugin
3
+ *
4
+ * Wires up all native permissions and native modules needed by the
5
+ * checkout widget in one step. Add this to your app.json:
6
+ *
7
+ * "plugins": [
8
+ * ["@quantaroute/checkout/plugin", {
9
+ * "locationPermissionText": "Used to place your delivery pin on the map"
10
+ * }]
11
+ * ]
12
+ *
13
+ * This plugin internally applies:
14
+ * • expo-osm-sdk/plugin → MapLibre GL native map (iOS/Android)
15
+ * • expo-location → GPS location permission
16
+ * • Android manifest → INTERNET + ACCESS_FINE_LOCATION permissions
17
+ * • iOS Info.plist → NSLocationWhenInUseUsageDescription
18
+ *
19
+ * @param {object} [options]
20
+ * @param {string} [options.locationPermissionText] - Custom permission text shown to the user.
21
+ * Defaults to a sensible delivery-address copy.
22
+ */
23
+
24
+ const { withAndroidManifest, withInfoPlist, withPlugins } = require('@expo/config-plugins');
25
+
26
+ const DEFAULT_LOCATION_TEXT =
27
+ 'Allow access to your location to place the delivery pin accurately on the map.';
28
+
29
+ // ─── Android helper ───────────────────────────────────────────────────────────
30
+
31
+ function ensurePermission(manifest, name) {
32
+ if (!manifest['uses-permission']) {
33
+ manifest['uses-permission'] = [];
34
+ }
35
+ const exists = manifest['uses-permission'].some(
36
+ (p) => p.$?.['android:name'] === name
37
+ );
38
+ if (!exists) {
39
+ manifest['uses-permission'].push({ $: { 'android:name': name } });
40
+ }
41
+ return manifest;
42
+ }
43
+
44
+ const withAndroidPermissions = (config) =>
45
+ withAndroidManifest(config, (cfg) => {
46
+ const m = cfg.modResults.manifest;
47
+ ensurePermission(m, 'android.permission.INTERNET');
48
+ ensurePermission(m, 'android.permission.ACCESS_FINE_LOCATION');
49
+ ensurePermission(m, 'android.permission.ACCESS_COARSE_LOCATION');
50
+ return cfg;
51
+ });
52
+
53
+ // ─── iOS helper ───────────────────────────────────────────────────────────────
54
+
55
+ const withIosPermissions = (config, { locationPermissionText }) =>
56
+ withInfoPlist(config, (cfg) => {
57
+ if (!cfg.modResults.NSLocationWhenInUseUsageDescription) {
58
+ cfg.modResults.NSLocationWhenInUseUsageDescription = locationPermissionText;
59
+ }
60
+ return cfg;
61
+ });
62
+
63
+ // ─── Main plugin ──────────────────────────────────────────────────────────────
64
+
65
+ const withQuantaRouteCheckout = (config, options = {}) => {
66
+ const locationPermissionText =
67
+ options.locationPermissionText ?? DEFAULT_LOCATION_TEXT;
68
+
69
+ return withPlugins(config, [
70
+ // 1. Apply expo-osm-sdk plugin (MapLibre GL native maps)
71
+ (cfg) => {
72
+ try {
73
+ const osmPlugin = require('expo-osm-sdk/plugin');
74
+ return osmPlugin(cfg, { locationPermissionText });
75
+ } catch {
76
+ console.warn(
77
+ '[@quantaroute/checkout] expo-osm-sdk/plugin not found. ' +
78
+ 'Make sure expo-osm-sdk is installed: npm install expo-osm-sdk'
79
+ );
80
+ return cfg;
81
+ }
82
+ },
83
+
84
+ // 2. Apply expo-location plugin (GPS location API)
85
+ (cfg) => {
86
+ try {
87
+ const locationPlugin = require('expo-location/build/ExpoLocationPlugin');
88
+ return locationPlugin(cfg, { locationAlwaysAndWhenInUsePermission: locationPermissionText });
89
+ } catch {
90
+ try {
91
+ // Fallback: expo-location ships its plugin at a different path in some versions
92
+ const mod = require('expo-location');
93
+ if (mod.withLocation) return mod.withLocation(cfg);
94
+ } catch {
95
+ // Silent: expo-location permissions are handled by withIosPermissions below
96
+ }
97
+ return cfg;
98
+ }
99
+ },
100
+
101
+ // 3. Ensure Android permissions (belt-and-suspenders on top of the above)
102
+ (cfg) => withAndroidPermissions(cfg),
103
+
104
+ // 4. Ensure iOS Info.plist key (belt-and-suspenders)
105
+ (cfg) => withIosPermissions(cfg, { locationPermissionText }),
106
+ ]);
107
+ };
108
+
109
+ module.exports = withQuantaRouteCheckout;
package/package.json CHANGED
@@ -1,21 +1,30 @@
1
1
  {
2
2
  "name": "@quantaroute/checkout",
3
- "version": "1.1.1",
4
- "description": "Embeddable DigiPin-powered smart address checkout widget for Indian e-commerce",
3
+ "version": "1.2.1",
4
+ "description": "Embeddable DigiPin-powered smart address checkout widget React (web), React Native & Expo (iOS/Android)",
5
5
  "main": "./dist/lib/quantaroute-checkout.umd.js",
6
6
  "module": "./dist/lib/quantaroute-checkout.es.js",
7
7
  "types": "./dist/lib/index.d.ts",
8
+ "source": "src/index.ts",
9
+ "react-native": "src/index.ts",
8
10
  "exports": {
9
11
  ".": {
12
+ "types": "./dist/lib/index.d.ts",
13
+ "react-native": "./src/index.ts",
10
14
  "import": "./dist/lib/quantaroute-checkout.es.js",
11
- "require": "./dist/lib/quantaroute-checkout.umd.js",
12
- "types": "./dist/lib/index.d.ts"
15
+ "require": "./dist/lib/quantaroute-checkout.umd.js"
16
+ },
17
+ "./plugin": {
18
+ "default": "./expo-plugin.js"
13
19
  },
14
20
  "./style.css": "./dist/style.css"
15
21
  },
16
22
  "files": [
23
+ "src",
17
24
  "dist/lib",
18
25
  "dist/style.css",
26
+ "expo-plugin.js",
27
+ "babel.config.js",
19
28
  "README.md",
20
29
  "LICENSE"
21
30
  ],
@@ -26,6 +35,7 @@
26
35
  "build:demo": "vite build",
27
36
  "preview": "vite preview --outDir dist/demo",
28
37
  "type-check": "tsc --noEmit",
38
+ "type-check:native": "tsc --project tsconfig.native.json --noEmit",
29
39
  "prepublishOnly": "npm run type-check && npm run build:lib"
30
40
  },
31
41
  "keywords": [
@@ -36,6 +46,10 @@
36
46
  "geocoding",
37
47
  "maplibre",
38
48
  "react",
49
+ "react-native",
50
+ "expo",
51
+ "ios",
52
+ "android",
39
53
  "ecommerce",
40
54
  "india-post",
41
55
  "address-autocomplete",
@@ -63,30 +77,53 @@
63
77
  "npm": ">=10.0.0"
64
78
  },
65
79
  "peerDependencies": {
80
+ "@expo/config-plugins": ">=7.0.0",
81
+ "expo": ">=49.0.0",
82
+ "expo-location": ">=17.0.0",
83
+ "expo-osm-sdk": ">=2.0.0",
66
84
  "maplibre-gl": ">=4.0.0",
67
- "react": ">=18.2.0",
68
- "react-dom": ">=18.2.0"
85
+ "react": ">=18.0.0",
86
+ "react-dom": ">=18.2.0",
87
+ "react-native": ">=0.72.0"
69
88
  },
70
89
  "peerDependenciesMeta": {
90
+ "@expo/config-plugins": {
91
+ "optional": true
92
+ },
71
93
  "react": {
72
94
  "optional": false
73
95
  },
74
96
  "react-dom": {
75
- "optional": false
97
+ "optional": true
76
98
  },
77
99
  "maplibre-gl": {
78
- "optional": false
100
+ "optional": true
101
+ },
102
+ "react-native": {
103
+ "optional": true
104
+ },
105
+ "expo": {
106
+ "optional": true
107
+ },
108
+ "expo-osm-sdk": {
109
+ "optional": true
110
+ },
111
+ "expo-location": {
112
+ "optional": true
79
113
  }
80
114
  },
81
- "dependencies": {},
82
115
  "devDependencies": {
83
- "@quantaroute/checkout": "^1.1.1",
116
+ "@expo/config-plugins": "^54.0.4",
84
117
  "@types/react": "^18.3.12",
85
118
  "@types/react-dom": "^18.3.1",
119
+ "@types/react-native": "^0.73.0",
86
120
  "@vitejs/plugin-react": "^4.3.4",
121
+ "expo-location": "^18.1.6",
122
+ "expo-osm-sdk": "^2.0.0",
87
123
  "maplibre-gl": "^4.7.1",
88
124
  "react": "^18.3.1",
89
125
  "react-dom": "^18.3.1",
126
+ "react-native": "^0.73.11",
90
127
  "terser": "^5.46.0",
91
128
  "typescript": "^5.6.3",
92
129
  "vite": "^5.4.11",