@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.
- package/LICENSE +29 -0
- package/README.md +299 -172
- package/babel.config.js +16 -0
- package/dist/lib/index.d.ts +9 -7
- package/dist/lib/quantaroute-checkout.es.js +21 -9
- package/dist/lib/quantaroute-checkout.umd.js +60 -52
- package/expo-plugin.js +109 -0
- package/package.json +47 -10
- package/src/components/AddressForm.native.tsx +540 -0
- package/src/components/AddressForm.tsx +477 -0
- package/src/components/CheckoutWidget.native.tsx +218 -0
- package/src/components/CheckoutWidget.tsx +196 -0
- package/src/components/MapPinSelector.native.tsx +251 -0
- package/src/components/MapPinSelector.tsx +405 -0
- package/src/core/api.ts +167 -0
- package/src/core/digipin.ts +169 -0
- package/src/core/types.ts +150 -0
- package/src/hooks/useDigiPin.ts +20 -0
- package/src/hooks/useGeolocation.native.ts +55 -0
- package/src/hooks/useGeolocation.ts +48 -0
- package/src/index.ts +59 -0
- package/src/styles/checkout.css +1082 -0
- package/src/styles/checkout.native.ts +839 -0
package/dist/lib/index.d.ts
CHANGED
|
@@ -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?:
|
|
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
|
-
|
|
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.
|
|
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
|
-
*
|
|
11
|
-
* react >= 18.
|
|
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:
|
|
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:
|
|
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,
|
|
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.
|
|
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
|
-
*
|
|
13
|
-
* react >= 18.
|
|
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
|
-
|
|
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
|
|
20
|
-
for(let i=1;i<=10;i++){const d=(n
|
|
21
|
-
let u=3-Math.floor((e-
|
|
22
|
-
u=Math.max(0,Math.min(u,3)),h=Math.max(0,Math.min(h,3)),s+=
|
|
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
|
|
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
|
|
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
|
|
30
|
-
|
|
31
|
-
return{...e,locate:
|
|
32
|
-
e.code===e.PERMISSION_DENIED?
|
|
33
|
-
try{return o(e,r)}catch{return null}}const u=({onLocationConfirm:e,defaultLat:
|
|
34
|
-
|
|
35
|
-
const e=s?fetch(s).then(e=>e.ok?e.json():null).catch(()=>null):Promise.resolve(null),r=
|
|
36
|
-
return
|
|
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 © <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}(),
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
const
|
|
43
|
-
null==(r=p.current)||r.setLngLat([
|
|
44
|
-
const
|
|
45
|
-
|
|
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:
|
|
47
|
-
|
|
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(
|
|
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
|
|
56
|
-
let
|
|
57
|
-
try{
|
|
58
|
-
throw new Error(`Network error: ${o instanceof Error?o.message:String(o)}`)}if(!
|
|
59
|
-
try{e=await
|
|
60
|
-
if(429===
|
|
61
|
-
throw new Error(`API error ${
|
|
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
|
|
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
|
|
68
|
-
return e.road&&
|
|
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:
|
|
75
|
-
try{const[r,
|
|
76
|
-
u({type:"LOAD_SUCCESS",adminInfo:r.data.administrative_info,alternatives:r.data.alternatives||[],addressComponents:
|
|
77
|
-
u({type:"LOAD_ERROR",message:e}),null==d||d(r instanceof Error?r:new Error(e))}},[e,
|
|
78
|
-
|
|
79
|
-
const
|
|
80
|
-
const{adminInfo:
|
|
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,
|
|
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:
|
|
84
|
-
return r.jsxs("div",{className:`qr-checkout qr-checkout--${s} ${d}`,style:c,"data-testid":"quantaroute-checkout",children:["done"!==
|
|
85
|
-
e.AddressForm=q,e.CheckoutWidget=
|
|
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=
|
|
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.
|
|
4
|
-
"description": "Embeddable DigiPin-powered smart address checkout widget
|
|
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
|
-
|
|
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.
|
|
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":
|
|
97
|
+
"optional": true
|
|
76
98
|
},
|
|
77
99
|
"maplibre-gl": {
|
|
78
|
-
"optional":
|
|
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
|
-
"@
|
|
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",
|