@quantaroute/checkout 1.1.1 → 1.2.0
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 +9 -3
- package/dist/lib/quantaroute-checkout.umd.js +9 -3
- 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 +254 -0
- package/src/components/MapPinSelector.tsx +405 -0
- package/src/core/api.ts +150 -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/README.md
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
# @quantaroute/checkout
|
|
2
2
|
|
|
3
3
|
> **DigiPin-powered smart address checkout widget for Indian e-commerce.**
|
|
4
|
-
> Two-step: Map pin → Auto-filled form.
|
|
4
|
+
> Two-step: Map pin → Auto-filled form.
|
|
5
|
+
> One package. Three platforms: **React web · iOS · Android.**
|
|
5
6
|
|
|
6
7
|
[](https://quantaroute.com)
|
|
7
8
|
[](https://www.indiapost.gov.in)
|
|
@@ -12,17 +13,17 @@
|
|
|
12
13
|
|
|
13
14
|
## What it does
|
|
14
15
|
|
|
15
|
-
Replaces
|
|
16
|
+
Replaces broken Indian address forms with a precise two-step pin-drop flow:
|
|
16
17
|
|
|
17
18
|
```
|
|
18
19
|
Step 1 – Map Pin Step 2 – Auto-fill + Details
|
|
19
20
|
┌──────────────────────────┐ ┌──────────────────────────┐
|
|
20
21
|
│ [DigiPin: 39J-438-TJC7] │ │ 📍 Auto-detected │
|
|
21
22
|
│ │ │ State: Delhi │
|
|
22
|
-
│ 🗺
|
|
23
|
+
│ 🗺 OSM vector map │ ──► │ District: New Delhi │
|
|
23
24
|
│ 📍 ← drag │ │ Pincode: 110011 │
|
|
24
25
|
│ │ │ ✓ Deliverable │
|
|
25
|
-
│ [
|
|
26
|
+
│ [⊕ Locate Me] │ │ 🏠 Add details │
|
|
26
27
|
│ [Confirm Location →] │ │ Flat No: [_________] │
|
|
27
28
|
└──────────────────────────┘ │ [← Adjust] [Save ✓] │
|
|
28
29
|
└──────────────────────────┘
|
|
@@ -30,13 +31,26 @@ Step 1 – Map Pin Step 2 – Auto-fill + Details
|
|
|
30
31
|
|
|
31
32
|
**Key features:**
|
|
32
33
|
|
|
33
|
-
- DigiPin shown **offline** in real-time as the user drags the pin (~
|
|
34
|
-
- **No Google Maps.** Free Carto Positron vector basemap
|
|
34
|
+
- DigiPin shown **offline** in real-time as the user drags the pin (~4 m × 4 m precision)
|
|
35
|
+
- **No Google Maps.** Free Carto Positron vector basemap
|
|
35
36
|
- Auto-fills State, District, Locality, Pincode, Delivery status from QuantaRoute API
|
|
36
|
-
- Manual fields: Flat no., Floor, Building (OSM
|
|
37
|
-
- Mobile-first (full-screen on phones, card on desktop)
|
|
37
|
+
- Manual fields: Flat no., Floor, Building (OSM pre-filled), Street/Area (OSM pre-filled)
|
|
38
|
+
- Mobile-first (full-screen on phones, card on desktop/tablet)
|
|
38
39
|
- Dark mode, reduced-motion, keyboard navigation, ARIA labels
|
|
39
|
-
- TypeScript
|
|
40
|
+
- TypeScript — zero runtime deps beyond peer dependencies
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## Platform support
|
|
45
|
+
|
|
46
|
+
| Platform | Bundler | Map engine | Install |
|
|
47
|
+
|---|---|---|---|
|
|
48
|
+
| React / Next.js / Vite / Nuxt | Webpack / Vite | MapLibre GL JS | `maplibre-gl` |
|
|
49
|
+
| iOS (Expo / React Native) | Metro | expo-osm-sdk (MapLibre GL Native) | `expo-osm-sdk expo-location` |
|
|
50
|
+
| Android (Expo / React Native) | Metro | expo-osm-sdk (MapLibre GL Native) | `expo-osm-sdk expo-location` |
|
|
51
|
+
|
|
52
|
+
> **Same import on all platforms.**
|
|
53
|
+
> Metro automatically resolves `.native.tsx` files on mobile; Vite/Webpack use the web `.tsx` files.
|
|
40
54
|
|
|
41
55
|
---
|
|
42
56
|
|
|
@@ -46,36 +60,28 @@ Step 1 – Map Pin Step 2 – Auto-fill + Details
|
|
|
46
60
|
|
|
47
61
|
1. Sign up at **[developers.quantaroute.com](https://developers.quantaroute.com)**
|
|
48
62
|
2. Create a project → copy your **API key**
|
|
49
|
-
3. Store it as an environment variable in your app:
|
|
50
|
-
|
|
51
|
-
| Framework | Variable name convention |
|
|
52
|
-
|-----------|--------------------------|
|
|
53
|
-
| Next.js | `NEXT_PUBLIC_QUANTAROUTE_KEY` in `.env.local` |
|
|
54
|
-
| Vite | `VITE_QR_API_KEY` in `.env` |
|
|
55
|
-
| Nuxt | `NUXT_PUBLIC_QR_API_KEY` in `.env` |
|
|
56
|
-
| Node/Express backend | `QUANTAROUTE_KEY` (server-side only) |
|
|
57
63
|
|
|
58
64
|
> **Never hard-code or commit API keys to git.**
|
|
59
65
|
|
|
60
66
|
---
|
|
61
67
|
|
|
62
|
-
|
|
68
|
+
## Web (React / Next.js / Vite / Nuxt)
|
|
69
|
+
|
|
70
|
+
### Install
|
|
63
71
|
|
|
64
72
|
```bash
|
|
65
73
|
npm install @quantaroute/checkout maplibre-gl
|
|
66
|
-
# or
|
|
67
|
-
yarn add @quantaroute/checkout maplibre-gl
|
|
68
74
|
```
|
|
69
75
|
|
|
70
|
-
###
|
|
76
|
+
### Import CSS
|
|
71
77
|
|
|
72
78
|
```ts
|
|
73
|
-
// In your app
|
|
79
|
+
// In your app entry file (main.tsx / _app.tsx / layout.tsx / nuxt.config.ts)
|
|
74
80
|
import 'maplibre-gl/dist/maplibre-gl.css';
|
|
75
81
|
import '@quantaroute/checkout/style.css';
|
|
76
82
|
```
|
|
77
83
|
|
|
78
|
-
###
|
|
84
|
+
### Use
|
|
79
85
|
|
|
80
86
|
```tsx
|
|
81
87
|
import { CheckoutWidget } from '@quantaroute/checkout';
|
|
@@ -83,11 +89,11 @@ import { CheckoutWidget } from '@quantaroute/checkout';
|
|
|
83
89
|
export default function CheckoutPage() {
|
|
84
90
|
return (
|
|
85
91
|
<CheckoutWidget
|
|
86
|
-
apiKey={process.env.NEXT_PUBLIC_QUANTAROUTE_KEY!}
|
|
92
|
+
apiKey={process.env.NEXT_PUBLIC_QUANTAROUTE_KEY!}
|
|
87
93
|
onComplete={(address) => {
|
|
88
|
-
console.log(address.digipin);
|
|
89
|
-
console.log(address.pincode);
|
|
90
|
-
console.log(address.formattedAddress);
|
|
94
|
+
console.log(address.digipin); // "39J-438-TJC7"
|
|
95
|
+
console.log(address.pincode); // "110011"
|
|
96
|
+
console.log(address.formattedAddress); // "Flat 4B, Floor 3rd, ..."
|
|
91
97
|
// → send to your backend / payment gateway
|
|
92
98
|
}}
|
|
93
99
|
/>
|
|
@@ -95,80 +101,24 @@ export default function CheckoutPage() {
|
|
|
95
101
|
}
|
|
96
102
|
```
|
|
97
103
|
|
|
98
|
-
> **Get your API key** (free tier: 25,000 requests/month) →
|
|
99
|
-
> **[developers.quantaroute.com](https://developers.quantaroute.com)**
|
|
100
|
-
>
|
|
101
|
-
> Store it as an environment variable — **never commit API keys to source control**.
|
|
102
|
-
|
|
103
|
-
---
|
|
104
|
-
|
|
105
|
-
## Props
|
|
106
|
-
|
|
107
|
-
| Prop | Type | Default | Description |
|
|
108
|
-
|------|------|---------|-------------|
|
|
109
|
-
| `apiKey` | `string` | **required** | QuantaRoute API key |
|
|
110
|
-
| `onComplete` | `(addr: CompleteAddress) => void` | **required** | Fired after user saves address |
|
|
111
|
-
| `apiBaseUrl` | `string` | `https://api.quantaroute.com` | Custom API URL (self-hosted) |
|
|
112
|
-
| `onError` | `(err: Error) => void` | – | Optional error handler |
|
|
113
|
-
| `defaultLat` | `number` | India center | Pre-center the map here |
|
|
114
|
-
| `defaultLng` | `number` | India center | Pre-center the map here |
|
|
115
|
-
| `theme` | `'light' \| 'dark'` | `'light'` | Color theme |
|
|
116
|
-
| `mapHeight` | `string` | `'380px'` | CSS height of the map area |
|
|
117
|
-
| `title` | `string` | `'Add Delivery Address'` | Widget header text |
|
|
118
|
-
| `className` | `string` | – | Extra class on root element |
|
|
119
|
-
| `style` | `CSSProperties` | – | Inline styles on root element |
|
|
120
|
-
|
|
121
|
-
---
|
|
122
|
-
|
|
123
|
-
## `CompleteAddress` output
|
|
124
|
-
|
|
125
|
-
```ts
|
|
126
|
-
interface CompleteAddress {
|
|
127
|
-
digipin: string; // "39J-438-TJC7"
|
|
128
|
-
lat: number; // 28.61390
|
|
129
|
-
lng: number; // 77.20900
|
|
130
|
-
|
|
131
|
-
// Auto-filled from QuantaRoute API
|
|
132
|
-
state: string; // "Delhi"
|
|
133
|
-
district: string; // "New Delhi"
|
|
134
|
-
division: string; // "New Delhi Central"
|
|
135
|
-
locality: string; // "Nirman Bhawan SO"
|
|
136
|
-
pincode: string; // "110011"
|
|
137
|
-
delivery: string; // "Delivery" | "Non Delivery"
|
|
138
|
-
country: string; // "India"
|
|
139
|
-
|
|
140
|
-
// Manual entry by user
|
|
141
|
-
flatNumber: string; // "4B"
|
|
142
|
-
floorNumber: string; // "3rd"
|
|
143
|
-
buildingName: string; // "Sunshine Apartments" (pre-filled from Nominatim OSM)
|
|
144
|
-
streetName: string; // "MG Road, Action Area I" (pre-filled from road + suburb)
|
|
145
|
-
|
|
146
|
-
formattedAddress: string; // "4B, 3rd Floor, Sunshine Apartments, MG Road, ..."
|
|
147
|
-
}
|
|
148
|
-
```
|
|
149
|
-
|
|
150
|
-
---
|
|
151
|
-
|
|
152
|
-
## Framework integration guides
|
|
153
|
-
|
|
154
104
|
### Next.js (App Router)
|
|
155
105
|
|
|
156
106
|
```tsx
|
|
157
107
|
// app/checkout/page.tsx
|
|
158
|
-
'use client';
|
|
108
|
+
'use client';
|
|
159
109
|
|
|
160
110
|
import dynamic from 'next/dynamic';
|
|
161
111
|
|
|
162
112
|
const CheckoutWidget = dynamic(
|
|
163
113
|
() => import('@quantaroute/checkout').then((m) => m.CheckoutWidget),
|
|
164
|
-
{ ssr: false }
|
|
114
|
+
{ ssr: false }
|
|
165
115
|
);
|
|
166
116
|
|
|
167
117
|
export default function CheckoutPage() {
|
|
168
118
|
return (
|
|
169
119
|
<main className="max-w-lg mx-auto p-4">
|
|
170
120
|
<CheckoutWidget
|
|
171
|
-
apiKey={process.env.NEXT_PUBLIC_QUANTAROUTE_KEY!}
|
|
121
|
+
apiKey={process.env.NEXT_PUBLIC_QUANTAROUTE_KEY!}
|
|
172
122
|
onComplete={(addr) => console.log(addr)}
|
|
173
123
|
/>
|
|
174
124
|
</main>
|
|
@@ -176,172 +126,317 @@ export default function CheckoutPage() {
|
|
|
176
126
|
}
|
|
177
127
|
```
|
|
178
128
|
|
|
179
|
-
|
|
129
|
+
CSS in `app/layout.tsx`:
|
|
180
130
|
```tsx
|
|
181
131
|
import 'maplibre-gl/dist/maplibre-gl.css';
|
|
182
132
|
import '@quantaroute/checkout/style.css';
|
|
183
133
|
```
|
|
184
134
|
|
|
185
|
-
---
|
|
186
|
-
|
|
187
135
|
### Next.js (Pages Router)
|
|
188
136
|
|
|
189
137
|
```tsx
|
|
190
|
-
// pages/checkout.tsx
|
|
191
138
|
import dynamic from 'next/dynamic';
|
|
192
139
|
const CheckoutWidget = dynamic(() => import('@quantaroute/checkout'), { ssr: false });
|
|
193
140
|
|
|
194
141
|
export default function CheckoutPage() {
|
|
195
142
|
return (
|
|
196
143
|
<CheckoutWidget
|
|
197
|
-
apiKey={process.env.NEXT_PUBLIC_QUANTAROUTE_KEY!}
|
|
144
|
+
apiKey={process.env.NEXT_PUBLIC_QUANTAROUTE_KEY!}
|
|
198
145
|
onComplete={(a) => console.log(a)}
|
|
199
146
|
/>
|
|
200
147
|
);
|
|
201
148
|
}
|
|
202
149
|
```
|
|
203
150
|
|
|
204
|
-
---
|
|
205
|
-
|
|
206
151
|
### Nuxt 3
|
|
207
152
|
|
|
208
153
|
```ts
|
|
209
154
|
// nuxt.config.ts
|
|
210
155
|
export default defineNuxtConfig({
|
|
211
|
-
css: [
|
|
212
|
-
'maplibre-gl/dist/maplibre-gl.css',
|
|
213
|
-
'@quantaroute/checkout/dist/style.css',
|
|
214
|
-
],
|
|
156
|
+
css: ['maplibre-gl/dist/maplibre-gl.css', '@quantaroute/checkout/style.css'],
|
|
215
157
|
});
|
|
216
158
|
```
|
|
217
159
|
|
|
218
160
|
```vue
|
|
219
|
-
<!-- components/AddressWidget.client.vue -->
|
|
220
|
-
<!-- The .client suffix ensures this only renders on the client -->
|
|
161
|
+
<!-- components/AddressWidget.client.vue (.client = browser-only) -->
|
|
221
162
|
<script setup lang="ts">
|
|
222
163
|
import { CheckoutWidget } from '@quantaroute/checkout';
|
|
223
|
-
|
|
224
|
-
function handleComplete(address) {
|
|
225
|
-
console.log(address.digipin, address.formattedAddress);
|
|
226
|
-
}
|
|
164
|
+
const { public: { qrApiKey } } = useRuntimeConfig();
|
|
227
165
|
</script>
|
|
228
|
-
|
|
229
166
|
<template>
|
|
230
|
-
|
|
231
|
-
<CheckoutWidget
|
|
232
|
-
:api-key="runtimeConfig.public.qrApiKey"
|
|
233
|
-
map-height="360px"
|
|
234
|
-
@complete="handleComplete"
|
|
235
|
-
/>
|
|
167
|
+
<CheckoutWidget :api-key="qrApiKey" map-height="360px" @complete="console.log" />
|
|
236
168
|
</template>
|
|
237
169
|
```
|
|
238
170
|
|
|
239
|
-
---
|
|
240
|
-
|
|
241
171
|
### Vite + React
|
|
242
172
|
|
|
243
173
|
```tsx
|
|
244
|
-
// src/App.tsx
|
|
245
174
|
import 'maplibre-gl/dist/maplibre-gl.css';
|
|
246
175
|
import '@quantaroute/checkout/style.css';
|
|
247
176
|
import { CheckoutWidget } from '@quantaroute/checkout';
|
|
248
177
|
|
|
249
178
|
function App() {
|
|
250
179
|
return (
|
|
251
|
-
<
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
/>
|
|
256
|
-
</div>
|
|
180
|
+
<CheckoutWidget
|
|
181
|
+
apiKey={import.meta.env.VITE_QR_API_KEY}
|
|
182
|
+
onComplete={(addr) => console.log('Saved:', addr)}
|
|
183
|
+
/>
|
|
257
184
|
);
|
|
258
185
|
}
|
|
259
186
|
```
|
|
260
187
|
|
|
188
|
+
### Vanilla JS (UMD / script tag)
|
|
189
|
+
|
|
190
|
+
```html
|
|
191
|
+
<link rel="stylesheet" href="https://unpkg.com/maplibre-gl/dist/maplibre-gl.css" />
|
|
192
|
+
<link rel="stylesheet" href="https://unpkg.com/@quantaroute/checkout/dist/style.css" />
|
|
193
|
+
|
|
194
|
+
<div id="checkout-root"></div>
|
|
195
|
+
|
|
196
|
+
<script src="https://unpkg.com/react@18/umd/react.production.min.js"></script>
|
|
197
|
+
<script src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script>
|
|
198
|
+
<script src="https://unpkg.com/maplibre-gl/dist/maplibre-gl.js"></script>
|
|
199
|
+
<script src="https://unpkg.com/@quantaroute/checkout/dist/lib/quantaroute-checkout.umd.js"></script>
|
|
200
|
+
<script>
|
|
201
|
+
ReactDOM.createRoot(document.getElementById('checkout-root')).render(
|
|
202
|
+
React.createElement(QuantaRouteCheckout.CheckoutWidget, {
|
|
203
|
+
apiKey: 'YOUR_KEY',
|
|
204
|
+
onComplete: (addr) => console.log('Done:', addr),
|
|
205
|
+
})
|
|
206
|
+
);
|
|
207
|
+
</script>
|
|
208
|
+
```
|
|
209
|
+
|
|
261
210
|
---
|
|
262
211
|
|
|
263
|
-
|
|
212
|
+
## Native (Expo / React Native — iOS & Android)
|
|
264
213
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
214
|
+
> **Requires a [development build](https://docs.expo.dev/develop/development-builds/introduction/).**
|
|
215
|
+
> This does NOT work in Expo Go — `expo-osm-sdk` uses native modules.
|
|
216
|
+
|
|
217
|
+
### 1 · Install
|
|
218
|
+
|
|
219
|
+
```bash
|
|
220
|
+
npm install @quantaroute/checkout expo-osm-sdk expo-location
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
### 2 · Add the config plugin to `app.json`
|
|
224
|
+
|
|
225
|
+
```json
|
|
226
|
+
{
|
|
227
|
+
"expo": {
|
|
228
|
+
"plugins": [
|
|
229
|
+
["@quantaroute/checkout/plugin", {
|
|
230
|
+
"locationPermissionText": "Allow access to your location to place the delivery pin on the map."
|
|
231
|
+
}]
|
|
232
|
+
]
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
This single plugin automatically configures:
|
|
238
|
+
|
|
239
|
+
| What | iOS | Android |
|
|
240
|
+
|---|---|---|
|
|
241
|
+
| MapLibre native SDK | `expo-osm-sdk/plugin` | `expo-osm-sdk/plugin` |
|
|
242
|
+
| Location permission | `NSLocationWhenInUseUsageDescription` | `ACCESS_FINE_LOCATION` |
|
|
243
|
+
| Internet permission | – | `INTERNET` |
|
|
244
|
+
|
|
245
|
+
### 3 · Rebuild your dev client
|
|
246
|
+
|
|
247
|
+
```bash
|
|
248
|
+
npx expo run:ios
|
|
249
|
+
# or
|
|
250
|
+
npx expo run:android
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
### 4 · Use — identical import as web
|
|
254
|
+
|
|
255
|
+
```tsx
|
|
256
|
+
import { CheckoutWidget } from '@quantaroute/checkout';
|
|
257
|
+
// No CSS import — styles are React Native StyleSheet objects
|
|
258
|
+
|
|
259
|
+
export default function CheckoutScreen() {
|
|
260
|
+
return (
|
|
261
|
+
<CheckoutWidget
|
|
262
|
+
apiKey={process.env.EXPO_PUBLIC_QUANTAROUTE_KEY!}
|
|
263
|
+
mapHeight={380}
|
|
264
|
+
onComplete={(address) => {
|
|
265
|
+
console.log(address.digipin); // "39J-438-TJC7"
|
|
266
|
+
console.log(address.formattedAddress); // "Flat 4B, ..."
|
|
267
|
+
}}
|
|
268
|
+
/>
|
|
269
|
+
);
|
|
270
|
+
}
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
### Demo native app
|
|
274
|
+
|
|
275
|
+
A working Expo demo is in `demo-native/`:
|
|
276
|
+
|
|
277
|
+
```bash
|
|
278
|
+
cd demo-native
|
|
279
|
+
npm install
|
|
280
|
+
# Set your API key in .env: EXPO_PUBLIC_QUANTAROUTE_API_KEY=your_key
|
|
281
|
+
npx expo run:ios
|
|
282
|
+
npx expo run:android
|
|
293
283
|
```
|
|
294
284
|
|
|
295
285
|
---
|
|
296
286
|
|
|
297
|
-
|
|
287
|
+
## Props
|
|
288
|
+
|
|
289
|
+
| Prop | Type | Default | Platform |
|
|
290
|
+
|---|---|---|---|
|
|
291
|
+
| `apiKey` | `string` | **required** | all |
|
|
292
|
+
| `onComplete` | `(addr: CompleteAddress) => void` | **required** | all |
|
|
293
|
+
| `apiBaseUrl` | `string` | `https://api.quantaroute.com` | all |
|
|
294
|
+
| `onError` | `(err: Error) => void` | – | all |
|
|
295
|
+
| `defaultLat` | `number` | India center | all |
|
|
296
|
+
| `defaultLng` | `number` | India center | all |
|
|
297
|
+
| `theme` | `'light' \| 'dark'` | `'light'` | all |
|
|
298
|
+
| `mapHeight` | `string \| number` | `'380px'` / `380` | all |
|
|
299
|
+
| `title` | `string` | `'Add Delivery Address'` | all |
|
|
300
|
+
| `className` | `string` | – | web only |
|
|
301
|
+
| `style` | `CSSProperties \| StyleProp<ViewStyle>` | – | all |
|
|
302
|
+
| `indiaBoundaryUrl` | `string` | – | web only |
|
|
303
|
+
|
|
304
|
+
---
|
|
305
|
+
|
|
306
|
+
## `CompleteAddress` output
|
|
307
|
+
|
|
308
|
+
```ts
|
|
309
|
+
interface CompleteAddress {
|
|
310
|
+
digipin: string; // "39J-438-TJC7"
|
|
311
|
+
lat: number; // 28.61390
|
|
312
|
+
lng: number; // 77.20900
|
|
313
|
+
|
|
314
|
+
// Auto-filled from QuantaRoute API
|
|
315
|
+
state: string; // "Delhi"
|
|
316
|
+
district: string; // "New Delhi"
|
|
317
|
+
division: string; // "New Delhi Central"
|
|
318
|
+
locality: string; // "Nirman Bhawan SO"
|
|
319
|
+
pincode: string; // "110011"
|
|
320
|
+
delivery: string; // "Delivery" | "Non Delivery"
|
|
321
|
+
country: string; // "India"
|
|
322
|
+
|
|
323
|
+
// Manual entry by user
|
|
324
|
+
flatNumber: string; // "4B"
|
|
325
|
+
floorNumber: string; // "3rd"
|
|
326
|
+
buildingName: string; // "Sunshine Apartments" (OSM pre-filled)
|
|
327
|
+
streetName: string; // "MG Road, Action Area" (OSM pre-filled)
|
|
298
328
|
|
|
299
|
-
|
|
329
|
+
formattedAddress: string; // "4B, 3rd Floor, Sunshine Apartments, MG Road, ..."
|
|
330
|
+
}
|
|
331
|
+
```
|
|
300
332
|
|
|
301
333
|
---
|
|
302
334
|
|
|
303
335
|
## Advanced usage
|
|
304
336
|
|
|
305
|
-
### Use sub-components individually
|
|
337
|
+
### Use sub-components individually (web + native)
|
|
306
338
|
|
|
307
339
|
```tsx
|
|
308
340
|
import { MapPinSelector, AddressForm, getDigiPin, isWithinIndia } from '@quantaroute/checkout';
|
|
309
341
|
|
|
310
|
-
// Offline DigiPin
|
|
342
|
+
// Offline DigiPin — no API call, ~0.1 ms
|
|
311
343
|
const dp = getDigiPin(28.6139, 77.2090); // "39J-438-TJC7"
|
|
312
|
-
const
|
|
344
|
+
const ok = isWithinIndia(28.6139, 77.2090); // true
|
|
313
345
|
|
|
314
346
|
// Custom two-step flow
|
|
315
347
|
function MyCheckout() {
|
|
316
|
-
const [loc, setLoc] = useState(null);
|
|
348
|
+
const [loc, setLoc] = useState<{ lat: number; lng: number; digipin: string } | null>(null);
|
|
317
349
|
|
|
318
350
|
return loc == null
|
|
319
|
-
? <MapPinSelector onLocationConfirm={(lat, lng,
|
|
320
|
-
: <AddressForm
|
|
351
|
+
? <MapPinSelector onLocationConfirm={(lat, lng, digipin) => setLoc({ lat, lng, digipin })} />
|
|
352
|
+
: <AddressForm
|
|
353
|
+
lat={loc.lat}
|
|
354
|
+
lng={loc.lng}
|
|
355
|
+
digipin={loc.digipin}
|
|
356
|
+
apiKey="..."
|
|
357
|
+
onAddressComplete={(addr) => console.log(addr)}
|
|
358
|
+
onBack={() => setLoc(null)}
|
|
359
|
+
/>;
|
|
321
360
|
}
|
|
322
361
|
```
|
|
323
362
|
|
|
324
|
-
###
|
|
363
|
+
### Dark mode (web)
|
|
364
|
+
|
|
365
|
+
```tsx
|
|
366
|
+
<CheckoutWidget apiKey="..." theme="dark" onComplete={...} />
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
### Custom web theme via CSS variables
|
|
325
370
|
|
|
326
371
|
```css
|
|
327
|
-
/* Override the design tokens in your global CSS */
|
|
328
372
|
.qr-checkout {
|
|
329
|
-
--qr-primary: #6366f1;
|
|
373
|
+
--qr-primary: #6366f1;
|
|
330
374
|
--qr-primary-dark: #4f46e5;
|
|
331
375
|
--qr-radius: 8px;
|
|
332
376
|
--qr-font: 'Poppins', sans-serif;
|
|
333
377
|
}
|
|
334
378
|
```
|
|
335
379
|
|
|
380
|
+
### India boundary overlay (web only)
|
|
381
|
+
|
|
382
|
+
```tsx
|
|
383
|
+
<CheckoutWidget
|
|
384
|
+
apiKey="..."
|
|
385
|
+
indiaBoundaryUrl="/geojson/india.geojson" {/* hosted in your public folder */}
|
|
386
|
+
onComplete={...}
|
|
387
|
+
/>
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
---
|
|
391
|
+
|
|
392
|
+
## Architecture
|
|
393
|
+
|
|
394
|
+
```
|
|
395
|
+
@quantaroute/checkout/
|
|
396
|
+
├── src/
|
|
397
|
+
│ ├── components/
|
|
398
|
+
│ │ ├── CheckoutWidget.tsx ← web (MapLibre GL JS)
|
|
399
|
+
│ │ ├── CheckoutWidget.native.tsx ← native (SafeAreaView)
|
|
400
|
+
│ │ ├── MapPinSelector.tsx ← web (MapLibre marker)
|
|
401
|
+
│ │ ├── MapPinSelector.native.tsx ← native (expo-osm-sdk OSMView)
|
|
402
|
+
│ │ ├── AddressForm.tsx ← web (HTML form)
|
|
403
|
+
│ │ └── AddressForm.native.tsx ← native (TextInput / Modal)
|
|
404
|
+
│ ├── core/
|
|
405
|
+
│ │ ├── digipin.ts ← offline DigiPin algorithm (shared, no DOM)
|
|
406
|
+
│ │ ├── api.ts ← QuantaRoute API client (shared, fetch)
|
|
407
|
+
│ │ └── types.ts ← TypeScript types (shared)
|
|
408
|
+
│ ├── hooks/
|
|
409
|
+
│ │ ├── useGeolocation.ts ← web (navigator.geolocation)
|
|
410
|
+
│ │ ├── useGeolocation.native.ts ← native (expo-location)
|
|
411
|
+
│ │ └── useDigiPin.ts ← shared (pure math)
|
|
412
|
+
│ └── styles/
|
|
413
|
+
│ ├── checkout.css ← web styles
|
|
414
|
+
│ └── checkout.native.ts ← native StyleSheet.create()
|
|
415
|
+
├── expo-plugin.js ← Expo config plugin
|
|
416
|
+
├── babel.config.js ← Metro/Babel config
|
|
417
|
+
└── dist/ ← web build output (Vite)
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
**Platform resolution:**
|
|
421
|
+
|
|
422
|
+
```
|
|
423
|
+
Metro (Expo app) → "react-native" export → src/index.ts
|
|
424
|
+
→ MapPinSelector.native.tsx (expo-osm-sdk)
|
|
425
|
+
→ useGeolocation.native.ts (expo-location)
|
|
426
|
+
|
|
427
|
+
Vite / Webpack / Next.js → "import" export → dist/lib/quantaroute-checkout.es.js
|
|
428
|
+
→ MapPinSelector.tsx (MapLibre GL JS)
|
|
429
|
+
→ useGeolocation.ts (navigator.geolocation)
|
|
430
|
+
```
|
|
431
|
+
|
|
336
432
|
---
|
|
337
433
|
|
|
338
434
|
## Map tile license
|
|
339
435
|
|
|
340
|
-
|
|
436
|
+
Uses **Carto Positron** vector tiles (Carto Voyager on native):
|
|
341
437
|
|
|
342
|
-
- Free for commercial use
|
|
438
|
+
- Free for commercial use — attribution required
|
|
343
439
|
- Attribution: `© OpenStreetMap contributors © CARTO`
|
|
344
|
-
- Does **not** use Google Maps, Mapbox, or OSM raster tiles
|
|
345
440
|
- No API key required for Carto basemaps
|
|
346
441
|
|
|
347
442
|
---
|
|
@@ -361,28 +456,60 @@ The offline DigiPin algorithm is the official India Post implementation:
|
|
|
361
456
|
```bash
|
|
362
457
|
git clone https://github.com/quantaroute/checkout.git
|
|
363
458
|
cd quantaroute-checkout
|
|
364
|
-
|
|
365
459
|
npm install
|
|
366
|
-
npm run dev # Dev server at http://localhost:5173
|
|
367
460
|
|
|
368
|
-
|
|
369
|
-
npm run
|
|
370
|
-
|
|
461
|
+
# Web dev server
|
|
462
|
+
npm run dev # http://localhost:5173
|
|
463
|
+
|
|
464
|
+
# Type checks
|
|
465
|
+
npm run type-check # web (Vite tsconfig)
|
|
466
|
+
npm run type-check:native # native (RN tsconfig)
|
|
467
|
+
|
|
468
|
+
# Build
|
|
469
|
+
npm run build:lib # library → dist/lib/
|
|
470
|
+
npm run build # library + demo
|
|
471
|
+
```
|
|
472
|
+
|
|
473
|
+
**Native demo:**
|
|
474
|
+
|
|
475
|
+
```bash
|
|
476
|
+
cd demo-native
|
|
477
|
+
npm install
|
|
478
|
+
npx expo run:ios # requires Xcode
|
|
479
|
+
npx expo run:android # requires Android Studio
|
|
371
480
|
```
|
|
372
481
|
|
|
373
482
|
---
|
|
374
483
|
|
|
375
484
|
## Changelog
|
|
376
485
|
|
|
486
|
+
### v1.2.0
|
|
487
|
+
|
|
488
|
+
- **iOS & Android support** via `expo-osm-sdk` (MapLibre GL Native)
|
|
489
|
+
- `MapPinSelector.native.tsx` — draggable OSMView pin
|
|
490
|
+
- `AddressForm.native.tsx` — `TextInput` / `ScrollView` / `Modal` locality picker
|
|
491
|
+
- `CheckoutWidget.native.tsx` — `SafeAreaView` layout with identical flow logic
|
|
492
|
+
- `useGeolocation.native.ts` — GPS via `expo-location`
|
|
493
|
+
- `checkout.native.ts` — complete `StyleSheet` design system (light + dark)
|
|
494
|
+
- `expo-plugin.js` — one-line `app.json` setup for iOS/Android
|
|
495
|
+
- `babel.config.js` — Metro transpilation config
|
|
496
|
+
- `mapHeight` prop now accepts `number` (native) or `string` (web, e.g. `'380px'`)
|
|
497
|
+
- All native peer dependencies marked optional (zero impact on web bundles)
|
|
498
|
+
|
|
499
|
+
### v1.1.x
|
|
500
|
+
|
|
501
|
+
- DigiPin badge overlay on map
|
|
502
|
+
- Locality alternatives dropdown
|
|
503
|
+
- OSM address pre-fill (building name, road, suburb)
|
|
504
|
+
- India boundary GeoJSON overlay
|
|
505
|
+
|
|
377
506
|
### v1.0.0
|
|
378
507
|
|
|
379
|
-
- Initial release
|
|
380
|
-
-
|
|
381
|
-
-
|
|
382
|
-
- QuantaRoute location lookup API integration
|
|
508
|
+
- Initial release: MapLibre GL + Carto Positron
|
|
509
|
+
- Offline DigiPin generation
|
|
510
|
+
- QuantaRoute API integration
|
|
383
511
|
- Mobile-first responsive design
|
|
384
512
|
- Dark mode, reduced-motion, keyboard accessible
|
|
385
|
-
- React / Next.js / Nuxt / Vite / Vanilla JS support
|
|
386
513
|
|
|
387
514
|
---
|
|
388
515
|
|
package/babel.config.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Babel configuration for React Native / Expo consumers.
|
|
3
|
+
*
|
|
4
|
+
* This file is picked up automatically by Metro bundler when the package
|
|
5
|
+
* is used inside an Expo / React Native app. It transpiles the TypeScript
|
|
6
|
+
* source (src/) that Metro reads via the "react-native" export condition.
|
|
7
|
+
*
|
|
8
|
+
* Web builds continue to use Vite (which handles its own transpilation).
|
|
9
|
+
*/
|
|
10
|
+
module.exports = {
|
|
11
|
+
presets: [
|
|
12
|
+
['@babel/preset-env', { targets: { node: 'current' } }],
|
|
13
|
+
['@babel/preset-react', { runtime: 'automatic' }],
|
|
14
|
+
'@babel/preset-typescript',
|
|
15
|
+
],
|
|
16
|
+
};
|