@quantaroute/checkout 1.2.0 → 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.
|
@@ -451,6 +451,14 @@ const MapPinSelector = ({
|
|
|
451
451
|
] });
|
|
452
452
|
};
|
|
453
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
|
+
}
|
|
454
462
|
async function lookupLocation(lat, lng, apiKey, baseUrl = DEFAULT_BASE_URL) {
|
|
455
463
|
const url = `${baseUrl.replace(/\/$/, "")}/v1/location/lookup`;
|
|
456
464
|
let res;
|
|
@@ -462,11 +470,10 @@ async function lookupLocation(lat, lng, apiKey, baseUrl = DEFAULT_BASE_URL) {
|
|
|
462
470
|
"x-api-key": apiKey
|
|
463
471
|
},
|
|
464
472
|
body: JSON.stringify({ latitude: lat, longitude: lng }),
|
|
465
|
-
signal:
|
|
466
|
-
// 15s timeout
|
|
473
|
+
signal: makeTimeoutSignal(15e3)
|
|
467
474
|
});
|
|
468
475
|
} catch (err) {
|
|
469
|
-
if (err instanceof Error && err.name === "TimeoutError") {
|
|
476
|
+
if (err instanceof Error && (err.name === "TimeoutError" || err.name === "AbortError")) {
|
|
470
477
|
throw new Error("Request timed out. Check your internet connection.");
|
|
471
478
|
}
|
|
472
479
|
throw new Error(`Network error: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -502,11 +509,10 @@ async function reverseGeocode(digipin, apiKey, baseUrl = DEFAULT_BASE_URL) {
|
|
|
502
509
|
"x-api-key": apiKey
|
|
503
510
|
},
|
|
504
511
|
body: JSON.stringify({ digipin }),
|
|
505
|
-
signal:
|
|
506
|
-
// 15s timeout
|
|
512
|
+
signal: makeTimeoutSignal(15e3)
|
|
507
513
|
});
|
|
508
514
|
} catch (err) {
|
|
509
|
-
if (err instanceof Error && err.name === "TimeoutError") {
|
|
515
|
+
if (err instanceof Error && (err.name === "TimeoutError" || err.name === "AbortError")) {
|
|
510
516
|
throw new Error("Request timed out. Check your internet connection.");
|
|
511
517
|
}
|
|
512
518
|
throw new Error(`Network error: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -1,4 +1,4 @@
|
|
|
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
4
|
* @quantaroute/checkout v1.2.0
|
|
@@ -19,76 +19,78 @@
|
|
|
19
19
|
* expo >= 49.0.0
|
|
20
20
|
* expo-osm-sdk >= 2.0.0
|
|
21
21
|
* expo-location >= 17.0.0
|
|
22
|
-
*/const
|
|
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}
|
|
23
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}`)
|
|
24
24
|
if(r<i.minLon||r>i.maxLon)throw new Error(`Longitude ${r} out of range. Must be between ${i.minLon} and ${i.maxLon}`)
|
|
25
|
-
let
|
|
26
|
-
for(let i=1;i<=10;i++){const d=(n
|
|
27
|
-
let u=3-Math.floor((e-
|
|
28
|
-
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,"")
|
|
29
29
|
if(10!==r.length)throw new Error("Invalid DIGIPIN: must be 10 characters (excluding dashes)")
|
|
30
|
-
let
|
|
30
|
+
let n=i.minLat,t=i.maxLat,o=i.minLon,l=i.maxLon
|
|
31
31
|
for(let i=0;i<10;i++){const e=r[i]
|
|
32
32
|
let s=!1,d=-1,c=-1
|
|
33
|
-
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
|
|
34
34
|
break}if(s)break}if(!s)throw new Error(`Invalid character in DIGIPIN: '${e}'`)
|
|
35
|
-
const u=(n
|
|
36
|
-
|
|
37
|
-
return{...e,locate:
|
|
38
|
-
e.code===e.PERMISSION_DENIED?
|
|
39
|
-
try{return o(e,r)}catch{return null}}const u=({onLocationConfirm:e,defaultLat:
|
|
40
|
-
|
|
41
|
-
const e=s?fetch(s).then(e=>e.ok?e.json():null).catch(()=>null):Promise.resolve(null),r=
|
|
42
|
-
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{}})
|
|
43
43
|
const r=function(){const e=document.createElement("div")
|
|
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}(),
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
const
|
|
49
|
-
null==(r=p.current)||r.setLngLat([
|
|
50
|
-
const
|
|
51
|
-
|
|
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:
|
|
53
|
-
|
|
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`
|
|
54
56
|
let i
|
|
55
|
-
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.")
|
|
56
58
|
throw new Error(`Network error: ${l instanceof Error?l.message:String(l)}`)}if(!i.ok){let e=""
|
|
57
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.")
|
|
58
60
|
if(429===i.status)throw new Error("Rate limit exceeded. Upgrade your plan or try again later.")
|
|
59
61
|
throw new Error(`API error ${i.status}: ${e||i.statusText}`)}const o=await i.json()
|
|
60
62
|
if(!o.success)throw new Error(o.message??"Location lookup failed")
|
|
61
|
-
return o}async function
|
|
62
|
-
let
|
|
63
|
-
try{
|
|
64
|
-
throw new Error(`Network error: ${o instanceof Error?o.message:String(o)}`)}if(!
|
|
65
|
-
try{e=await
|
|
66
|
-
if(429===
|
|
67
|
-
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()
|
|
68
70
|
if(!i.success)throw new Error(i.message??"Reverse geocoding failed")
|
|
69
71
|
return i}const g={flatNumber:"",floorNumber:"",buildingName:"",streetName:""}
|
|
70
|
-
function
|
|
72
|
+
function b(e,r){switch(r.type){case"LOAD_START":return{status:"loading"}
|
|
71
73
|
case"LOAD_SUCCESS":{const e=function(e){const r={}
|
|
72
74
|
e.name?r.buildingName=e.name:e.building_name?r.buildingName=e.building_name:e.addr_housename&&(r.buildingName=e.addr_housename)
|
|
73
|
-
const
|
|
74
|
-
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)
|
|
75
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}
|
|
76
78
|
case"SET_FIELD":return"ready"!==e.status?e:{...e,fields:{...e.fields,[r.key]:r.value}}
|
|
77
79
|
case"SET_LOCALITY":return"ready"!==e.status?e:{...e,selectedLocality:r.locality}
|
|
78
80
|
case"SUBMIT_START":return"ready"!==e.status?e:{...e,submitting:!0}
|
|
79
81
|
case"SUBMIT_END":return"ready"!==e.status?e:{...e,submitting:!1}
|
|
80
|
-
default:return e}}const q=({digipin:e,lat:
|
|
81
|
-
try{const[r,
|
|
82
|
-
u({type:"LOAD_SUCCESS",adminInfo:r.data.administrative_info,alternatives:r.data.alternatives||[],addressComponents:
|
|
83
|
-
u({type:"LOAD_ERROR",message:e}),null==d||d(r instanceof Error?r:new Error(e))}},[e,
|
|
84
|
-
|
|
85
|
-
const
|
|
86
|
-
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(", ")}
|
|
87
89
|
u({type:"SUBMIT_START"})
|
|
88
|
-
try{l(d)}finally{u({type:"SUBMIT_END"})}},[c,e,
|
|
89
|
-
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:
|
|
90
|
-
return r.jsxs("div",{className:`qr-checkout qr-checkout--${s} ${d}`,style:c,"data-testid":"quantaroute-checkout",children:["done"!==
|
|
91
|
-
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
|
|
92
94
|
if(!/^[A-Z0-9]{3}-[A-Z0-9]{3}-[A-Z0-9]{4}$/i.test(e.trim()))return!1
|
|
93
|
-
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
|
|
94
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@quantaroute/checkout",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.1",
|
|
4
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",
|
|
@@ -3,12 +3,11 @@ import {
|
|
|
3
3
|
View,
|
|
4
4
|
Text,
|
|
5
5
|
TouchableOpacity,
|
|
6
|
-
ActivityIndicator,
|
|
7
6
|
} from 'react-native';
|
|
8
|
-
import { OSMView } from 'expo-osm-sdk';
|
|
7
|
+
import { OSMView, LocationButton } from 'expo-osm-sdk';
|
|
9
8
|
import type { OSMViewRef, MarkerConfig } from 'expo-osm-sdk';
|
|
9
|
+
import * as Location from 'expo-location';
|
|
10
10
|
import { getDigiPin, isWithinIndia } from '../core/digipin';
|
|
11
|
-
import { useGeolocation } from '../hooks/useGeolocation.native';
|
|
12
11
|
import type { MapPinSelectorProps } from '../core/types';
|
|
13
12
|
import { styles, COLORS } from '../styles/checkout.native';
|
|
14
13
|
|
|
@@ -75,8 +74,7 @@ const MapPinSelector: React.FC<MapPinSelectorProps> = ({
|
|
|
75
74
|
computeDigiPinSafe(initLat, initLng)
|
|
76
75
|
);
|
|
77
76
|
const [mapReady, setMapReady] = useState(false);
|
|
78
|
-
|
|
79
|
-
const { loading: geoLoading, error: geoError, locate, clearError } = useGeolocation();
|
|
77
|
+
const [locateError, setLocateError] = useState<string | null>(null);
|
|
80
78
|
|
|
81
79
|
const isDark = theme === 'dark';
|
|
82
80
|
|
|
@@ -107,14 +105,18 @@ const MapPinSelector: React.FC<MapPinSelectorProps> = ({
|
|
|
107
105
|
},
|
|
108
106
|
];
|
|
109
107
|
|
|
110
|
-
// ── Locate-me
|
|
111
|
-
const
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
108
|
+
// ── Locate-me: delegate to expo-osm-sdk's LocationButton via getCurrentLocation ──
|
|
109
|
+
const getCurrentLocation = useCallback(async () => {
|
|
110
|
+
setLocateError(null);
|
|
111
|
+
const { status } = await Location.requestForegroundPermissionsAsync();
|
|
112
|
+
if (status !== Location.PermissionStatus.GRANTED) {
|
|
113
|
+
throw new Error('Location permission denied');
|
|
114
|
+
}
|
|
115
|
+
const loc = await Location.getCurrentPositionAsync({
|
|
116
|
+
accuracy: Location.Accuracy.High,
|
|
116
117
|
});
|
|
117
|
-
|
|
118
|
+
return { latitude: loc.coords.latitude, longitude: loc.coords.longitude };
|
|
119
|
+
}, []);
|
|
118
120
|
|
|
119
121
|
// ── Confirm handler ──────────────────────────────────────────────────────────
|
|
120
122
|
const handleConfirm = useCallback(() => {
|
|
@@ -186,31 +188,26 @@ const MapPinSelector: React.FC<MapPinSelectorProps> = ({
|
|
|
186
188
|
</View>
|
|
187
189
|
) : null}
|
|
188
190
|
|
|
189
|
-
{/* Locate-me button */}
|
|
190
|
-
<
|
|
191
|
-
style={
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
<Text style={{ fontSize: 20, color: isDark ? COLORS.textDark : COLORS.text }}>
|
|
202
|
-
⊕
|
|
203
|
-
</Text>
|
|
204
|
-
)}
|
|
205
|
-
</TouchableOpacity>
|
|
191
|
+
{/* Locate-me button — expo-osm-sdk's built-in LocationButton */}
|
|
192
|
+
<LocationButton
|
|
193
|
+
style={styles.locateBtn}
|
|
194
|
+
color={COLORS.primary}
|
|
195
|
+
size={44}
|
|
196
|
+
getCurrentLocation={getCurrentLocation}
|
|
197
|
+
onLocationFound={({ latitude, longitude }) => {
|
|
198
|
+
handleCoordChange(latitude, longitude);
|
|
199
|
+
void mapRef.current?.animateToLocation(latitude, longitude, STREET_ZOOM);
|
|
200
|
+
}}
|
|
201
|
+
onLocationError={(err) => setLocateError(err)}
|
|
202
|
+
/>
|
|
206
203
|
|
|
207
204
|
{/* Geo error toast */}
|
|
208
|
-
{
|
|
205
|
+
{locateError ? (
|
|
209
206
|
<View style={styles.geoError}>
|
|
210
|
-
<Text style={styles.geoErrorText}>{
|
|
207
|
+
<Text style={styles.geoErrorText}>{locateError}</Text>
|
|
211
208
|
<TouchableOpacity
|
|
212
209
|
style={styles.geoErrorDismiss}
|
|
213
|
-
onPress={
|
|
210
|
+
onPress={() => setLocateError(null)}
|
|
214
211
|
accessibilityLabel="Dismiss error"
|
|
215
212
|
>
|
|
216
213
|
<Text style={styles.geoErrorDismissText}>×</Text>
|
package/src/core/api.ts
CHANGED
|
@@ -2,6 +2,23 @@ import type { LocationLookupResponse } from './types';
|
|
|
2
2
|
|
|
3
3
|
const DEFAULT_BASE_URL = 'https://api.quantaroute.com';
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* Cross-platform timeout signal.
|
|
7
|
+
* AbortSignal.timeout() is not available in React Native / Hermes — fall back
|
|
8
|
+
* to a manual AbortController + setTimeout when it is missing.
|
|
9
|
+
*/
|
|
10
|
+
function makeTimeoutSignal(ms: number): AbortSignal {
|
|
11
|
+
if (
|
|
12
|
+
typeof AbortSignal !== 'undefined' &&
|
|
13
|
+
typeof (AbortSignal as { timeout?: unknown }).timeout === 'function'
|
|
14
|
+
) {
|
|
15
|
+
return AbortSignal.timeout(ms);
|
|
16
|
+
}
|
|
17
|
+
const ctrl = new AbortController();
|
|
18
|
+
setTimeout(() => ctrl.abort(), ms);
|
|
19
|
+
return ctrl.signal;
|
|
20
|
+
}
|
|
21
|
+
|
|
5
22
|
/**
|
|
6
23
|
* Address components from Nominatim/OpenStreetMap (via reverse geocoding).
|
|
7
24
|
*/
|
|
@@ -61,10 +78,10 @@ export async function lookupLocation(
|
|
|
61
78
|
'x-api-key': apiKey,
|
|
62
79
|
},
|
|
63
80
|
body: JSON.stringify({ latitude: lat, longitude: lng }),
|
|
64
|
-
signal:
|
|
81
|
+
signal: makeTimeoutSignal(15_000),
|
|
65
82
|
});
|
|
66
83
|
} catch (err) {
|
|
67
|
-
if (err instanceof Error && err.name === 'TimeoutError') {
|
|
84
|
+
if (err instanceof Error && (err.name === 'TimeoutError' || err.name === 'AbortError')) {
|
|
68
85
|
throw new Error('Request timed out. Check your internet connection.');
|
|
69
86
|
}
|
|
70
87
|
throw new Error(`Network error: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -115,10 +132,10 @@ export async function reverseGeocode(
|
|
|
115
132
|
'x-api-key': apiKey,
|
|
116
133
|
},
|
|
117
134
|
body: JSON.stringify({ digipin }),
|
|
118
|
-
signal:
|
|
135
|
+
signal: makeTimeoutSignal(15_000),
|
|
119
136
|
});
|
|
120
137
|
} catch (err) {
|
|
121
|
-
if (err instanceof Error && err.name === 'TimeoutError') {
|
|
138
|
+
if (err instanceof Error && (err.name === 'TimeoutError' || err.name === 'AbortError')) {
|
|
122
139
|
throw new Error('Request timed out. Check your internet connection.');
|
|
123
140
|
}
|
|
124
141
|
throw new Error(`Network error: ${err instanceof Error ? err.message : String(err)}`);
|