@cartbase/storefront 0.22.0 → 0.22.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/README.md +9 -0
- package/package.json +274 -274
- package/src/api/integrations.ts +8 -3
- package/src/checkout/pickup-point-selector.tsx +328 -372
- package/src/checkout/pickup-points.ts +65 -0
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import type { StorefrontClient } from "../api/http"
|
|
2
|
+
import { listPickupPoints, type PickupPointsResponse, type StorePickupPoint } from "../api/integrations"
|
|
3
|
+
import { distanceMeters, normalizeForMatch } from "./geocode"
|
|
4
|
+
|
|
5
|
+
// The reference pickers cache the full catalogue. Scope it to the SDK client
|
|
6
|
+
// (the store), carrier, mode and language instead of sharing one global list.
|
|
7
|
+
const catalogues = new WeakMap<StorefrontClient, Map<string, {
|
|
8
|
+
expires: number
|
|
9
|
+
promise: Promise<PickupPointsResponse>
|
|
10
|
+
}>>()
|
|
11
|
+
|
|
12
|
+
export function loadPickupPoints(
|
|
13
|
+
client: StorefrontClient,
|
|
14
|
+
provider: string,
|
|
15
|
+
mode: "office" | "locker",
|
|
16
|
+
locale: string
|
|
17
|
+
): Promise<PickupPointsResponse> {
|
|
18
|
+
let cache = catalogues.get(client)
|
|
19
|
+
if (!cache) catalogues.set(client, cache = new Map())
|
|
20
|
+
const key = JSON.stringify([provider, mode, locale])
|
|
21
|
+
const hit = cache.get(key)
|
|
22
|
+
if (hit && hit.expires > Date.now()) return hit.promise
|
|
23
|
+
const promise = listPickupPoints(client, provider, { mode, catalogue: true, locale })
|
|
24
|
+
.catch((error) => {
|
|
25
|
+
if (cache.get(key)?.promise === promise) cache.delete(key)
|
|
26
|
+
throw error
|
|
27
|
+
})
|
|
28
|
+
cache.set(key, { expires: Date.now() + 10 * 60 * 1000, promise })
|
|
29
|
+
return promise
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** BoxNow's reference picker city-locks both lists, comparing both scripts. */
|
|
33
|
+
export function pickupPointsInCity(points: StorePickupPoint[], city: string): StorePickupPoint[] {
|
|
34
|
+
const needle = normalizeForMatch(city.trim())
|
|
35
|
+
if (!needle) return points
|
|
36
|
+
return points.filter((point) => normalizeForMatch(point.city).includes(needle) ||
|
|
37
|
+
(point.provider === "boxnow" && normalizeForMatch(point.address).includes(needle)))
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** The reference's full catalogue → distance sort → nearest three, in that order. */
|
|
41
|
+
export function nearestPickupPoints(
|
|
42
|
+
points: StorePickupPoint[],
|
|
43
|
+
position: { lat: number; lng: number } | null,
|
|
44
|
+
city: string
|
|
45
|
+
): Array<{ point: StorePickupPoint; distance: number | null }> {
|
|
46
|
+
if (position && Number.isFinite(position.lat) && Number.isFinite(position.lng)) {
|
|
47
|
+
const located = points.filter((p) =>
|
|
48
|
+
p.latitude !== null && p.longitude !== null &&
|
|
49
|
+
Number.isFinite(p.latitude) && Number.isFinite(p.longitude)
|
|
50
|
+
)
|
|
51
|
+
if (located.length) return located.map((point) => ({ point,
|
|
52
|
+
distance: distanceMeters(position.lat, position.lng, point.latitude!, point.longitude!),
|
|
53
|
+
})).sort((a, b) => a.distance - b.distance).slice(0, 3)
|
|
54
|
+
}
|
|
55
|
+
return pickupPointsInCity(points, city).slice(0, 3).map((point) => ({ point, distance: null }))
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** The same catalogue powers search, including streets and either script. */
|
|
59
|
+
export function searchPickupPoints(points: StorePickupPoint[], search: string): StorePickupPoint[] {
|
|
60
|
+
const needle = normalizeForMatch(search.trim())
|
|
61
|
+
if (needle.length < 2) return []
|
|
62
|
+
return points.filter((point) => normalizeForMatch(
|
|
63
|
+
[point.name, point.city, point.address, point.postal_code].join(" ")
|
|
64
|
+
).includes(needle)).slice(0, 8)
|
|
65
|
+
}
|