@mapfirst.ai/react 0.0.14 → 0.0.16

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/EXAMPLES.md DELETED
@@ -1,740 +0,0 @@
1
- # React SDK Examples
2
-
3
- ## Complete Example with Reactive State
4
-
5
- ```tsx
6
- import React, { useEffect, useRef, useState } from "react";
7
- import maplibregl from "maplibre-gl";
8
- import {
9
- useMapFirstCore,
10
- useMapLibreAttachment,
11
- usePrimaryType,
12
- useSelectedMarker,
13
- } from "@mapfirst/react";
14
- import "maplibre-gl/dist/maplibre-gl.css";
15
-
16
- function HotelSearchApp() {
17
- const mapContainerRef = useRef<HTMLDivElement>(null);
18
- const [map, setMap] = useState<maplibregl.Map | null>(null);
19
-
20
- // Create SDK instance with reactive state
21
- const { mapFirst, state } = useMapFirstCore({
22
- initialLocationData: {
23
- city: "Paris",
24
- country: "France",
25
- currency: "EUR",
26
- },
27
- callbacks: {
28
- onError: (error, context) => {
29
- console.error(`Error in ${context}:`, error);
30
- },
31
- },
32
- });
33
-
34
- // Use dedicated hooks for controlling state
35
- const [primaryType, setPrimaryType] = usePrimaryType(mapFirst);
36
- const [selectedMarker, setSelectedMarker] = useSelectedMarker(mapFirst);
37
-
38
- // Access reactive state
39
- const properties = state?.properties || [];
40
- const isSearching = state?.isSearching || false;
41
- const filters = state?.filters;
42
-
43
- // Initialize map
44
- useEffect(() => {
45
- if (!mapContainerRef.current) return;
46
-
47
- const mapInstance = new maplibregl.Map({
48
- container: mapContainerRef.current,
49
- style: "https://demotiles.maplibre.org/style.json",
50
- center: [2.3522, 48.8566],
51
- zoom: 12,
52
- });
53
-
54
- mapInstance.on("load", () => {
55
- setMap(mapInstance);
56
- });
57
-
58
- return () => {
59
- mapInstance.remove();
60
- };
61
- }, []);
62
-
63
- // Attach map to SDK
64
- useMapLibreAttachment({
65
- mapFirst,
66
- map,
67
- maplibregl,
68
- onMarkerClick: (marker) => {
69
- console.log("Clicked:", marker.name);
70
- },
71
- });
72
-
73
- // Handle search
74
- const handleSearch = async () => {
75
- if (!mapFirst) return;
76
-
77
- await mapFirst.runPropertiesSearch({
78
- body: {
79
- city: "Paris",
80
- country: "France",
81
- filters: {
82
- checkIn: "2024-06-01",
83
- checkOut: "2024-06-07",
84
- numAdults: 2,
85
- currency: "EUR",
86
- },
87
- },
88
- });
89
- };
90
-
91
- // Handle date change
92
- const handleDateChange = (checkIn: Date, checkOut: Date) => {
93
- if (!mapFirst) return;
94
-
95
- mapFirst.setFilters({
96
- ...filters,
97
- checkIn,
98
- checkOut,
99
- });
100
- };
101
-
102
- return (
103
- <div style={{ display: "flex", flexDirection: "column", height: "100vh" }}>
104
- {/* Search Controls */}
105
- <div style={{ padding: "20px", background: "#f5f5f5" }}>
106
- <h1>Hotel Search</h1>
107
- <button onClick={handleSearch} disabled={isSearching}>
108
- {isSearching ? "Searching..." : "Search"}
109
- </button>
110
- <p>
111
- {isSearching
112
- ? "Searching for properties..."
113
- : `Found ${properties.length} properties`}
114
- </p>
115
- {selectedId && <p>Selected Property ID: {selectedId}</p>}
116
- </div>
117
-
118
- {/* Map */}
119
- <div style={{ flex: 1, position: "relative" }}>
120
- <div ref={mapContainerRef} style={{ width: "100%", height: "100%" }} />
121
- </div>
122
-
123
- {/* Property List */}
124
- <div
125
- style={{
126
- maxHeight: "200px",
127
- overflow: "auto",
128
- background: "#fff",
129
- borderTop: "1px solid #ddd",
130
- }}
131
- >
132
- {properties.map((property) => (
133
- <div
134
- key={property.tripadvisor_id}
135
- style={{
136
- padding: "10px",
137
- borderBottom: "1px solid #eee",
138
- background:
139
- selectedId === property.tripadvisor_id
140
- ? "#e3f2fd"
141
- : "transparent",
142
- cursor: "pointer",
143
- }}
144
- onClick={() => {
145
- // Use the hook setter instead of calling the SDK method directly
146
- setSelectedMarker(property.tripadvisor_id);
147
- if (mapFirst && property.location) {
148
- mapFirst.flyMapTo(
149
- property.location.lon,
150
- property.location.lat,
151
- 14
152
- );
153
- }
154
- }}
155
- >
156
- <strong>{property.name}</strong>
157
- <br />
158
- {property.pricing && (
159
- <span>
160
- {property.pricing.isPending
161
- ? "Loading price..."
162
- : `$${property.pricing.lead_rate?.display_price}`}
163
- </span>
164
- )}
165
- </div>
166
- ))}
167
- </div>
168
- </div>
169
- );
170
- }
171
-
172
- export default HotelSearchApp;
173
- ```
174
-
175
- ## Using Dedicated Hooks for Performance
176
-
177
- When you only need specific state values, use the dedicated hooks to avoid unnecessary re-renders:
178
-
179
- ```tsx
180
- import React from "react";
181
- import {
182
- useMapFirstCore,
183
- useMapFirstProperties,
184
- useMapFirstSelectedProperty,
185
- } from "@mapfirst/react";
186
-
187
- function PropertyStats() {
188
- const { mapFirst } = useMapFirstCore({
189
- initialLocationData: {
190
- city: "New York",
191
- country: "United States",
192
- },
193
- });
194
-
195
- // Only re-renders when properties change
196
- const properties = useMapFirstProperties(mapFirst);
197
-
198
- // Only re-renders when selection changes
199
- const selectedId = useMapFirstSelectedProperty(mapFirst);
200
-
201
- const selectedProperty = properties.find(
202
- (p) => p.tripadvisor_id === selectedId
203
- );
204
-
205
- return (
206
- <div>
207
- <h2>Statistics</h2>
208
- <p>Total Properties: {properties.length}</p>
209
- <p>
210
- Accommodations:{" "}
211
- {properties.filter((p) => p.type === "Accommodation").length}
212
- </p>
213
- <p>
214
- Restaurants: {properties.filter((p) => p.type === "Restaurant").length}
215
- </p>
216
- <p>
217
- Attractions: {properties.filter((p) => p.type === "Attraction").length}
218
- </p>
219
-
220
- {selectedProperty && (
221
- <div>
222
- <h3>Selected</h3>
223
- <p>{selectedProperty.name}</p>
224
- <p>{selectedProperty.type}</p>
225
- </div>
226
- )}
227
- </div>
228
- );
229
- }
230
- ```
231
-
232
- ## Combining Multiple State Sources
233
-
234
- ```tsx
235
- import React, { useState } from "react";
236
- import { useMapFirstCore } from "@mapfirst/react";
237
-
238
- function AdvancedSearch() {
239
- const { mapFirst, state } = useMapFirstCore({
240
- initialLocationData: {
241
- city: "London",
242
- country: "United Kingdom",
243
- currency: "GBP",
244
- },
245
- });
246
-
247
- // Local UI state
248
- const [showFilters, setShowFilters] = useState(false);
249
- const [priceRange, setPriceRange] = useState({ min: 0, max: 1000 });
250
-
251
- // SDK reactive state
252
- const properties = state?.properties || [];
253
- const isSearching = state?.isSearching || false;
254
- const filters = state?.filters;
255
-
256
- // Computed values
257
- const filteredProperties = properties.filter((p) => {
258
- if (!p.pricing?.lead_rate?.display_price) return true;
259
- const price = p.pricing.lead_rate.display_price;
260
- return price >= priceRange.min && price <= priceRange.max;
261
- });
262
-
263
- const handleApplyFilters = async () => {
264
- if (!mapFirst) return;
265
-
266
- await mapFirst.runPropertiesSearch({
267
- body: {
268
- city: "London",
269
- country: "United Kingdom",
270
- filters: {
271
- ...filters,
272
- min_price: priceRange.min,
273
- max_price: priceRange.max,
274
- },
275
- },
276
- });
277
- };
278
-
279
- return (
280
- <div>
281
- <button onClick={() => setShowFilters(!showFilters)}>
282
- {showFilters ? "Hide" : "Show"} Filters
283
- </button>
284
-
285
- {showFilters && (
286
- <div>
287
- <label>
288
- Min Price: ${priceRange.min}
289
- <input
290
- type="range"
291
- min="0"
292
- max="1000"
293
- value={priceRange.min}
294
- onChange={(e) =>
295
- setPriceRange({
296
- ...priceRange,
297
- min: parseInt(e.target.value),
298
- })
299
- }
300
- />
301
- </label>
302
-
303
- <label>
304
- Max Price: ${priceRange.max}
305
- <input
306
- type="range"
307
- min="0"
308
- max="1000"
309
- value={priceRange.max}
310
- onChange={(e) =>
311
- setPriceRange({
312
- ...priceRange,
313
- max: parseInt(e.target.value),
314
- })
315
- }
316
- />
317
- </label>
318
-
319
- <button onClick={handleApplyFilters} disabled={isSearching}>
320
- Apply Filters
321
- </button>
322
- </div>
323
- )}
324
-
325
- <p>
326
- Showing {filteredProperties.length} of {properties.length} properties
327
- {isSearching && " (searching...)"}
328
- </p>
329
-
330
- {filteredProperties.map((property) => (
331
- <div key={property.tripadvisor_id}>{property.name}</div>
332
- ))}
333
- </div>
334
- );
335
- }
336
- ```
337
-
338
- ## Real-time Updates
339
-
340
- The reactive state automatically updates when:
341
-
342
- - Properties are loaded or updated
343
- - A property is selected/deselected
344
- - Filters are changed
345
- - Search state changes (loading, searching, etc.)
346
- - Map bounds change
347
- - Primary type changes
348
-
349
- ```tsx
350
- function RealTimeUpdates() {
351
- const { mapFirst, state } = useMapFirstCore({
352
- initialLocationData: {
353
- city: "Tokyo",
354
- country: "Japan",
355
- },
356
- });
357
-
358
- // These values update automatically
359
- const properties = state?.properties || [];
360
- const isSearching = state?.isSearching || false;
361
- const selectedId = state?.selectedPropertyId;
362
- const bounds = state?.bounds;
363
- const center = state?.center;
364
- const zoom = state?.zoom;
365
-
366
- // Display real-time updates
367
- return (
368
- <div>
369
- <h2>Real-time State</h2>
370
- <pre>
371
- {JSON.stringify(
372
- {
373
- propertyCount: properties.length,
374
- isSearching,
375
- selectedId,
376
- bounds,
377
- center,
378
- zoom,
379
- },
380
- null,
381
- 2
382
- )}
383
- </pre>
384
- </div>
385
- );
386
- }
387
- ```
388
-
389
- ## Search Hooks Examples
390
-
391
- ### Using `usePropertiesSearch` for Location-Based Search
392
-
393
- ```tsx
394
- import React, { useState } from "react";
395
- import {
396
- useMapFirstCore,
397
- useMapLibreAttachment,
398
- usePropertiesSearch,
399
- } from "@mapfirst/react";
400
- import maplibregl from "maplibre-gl";
401
-
402
- function LocationSearchExample() {
403
- const [city, setCity] = useState("Paris");
404
- const [country, setCountry] = useState("France");
405
-
406
- const { mapFirst, state } = useMapFirstCore({
407
- initialLocationData: { city, country, currency: "USD" },
408
- });
409
-
410
- const { search, isLoading, error } = usePropertiesSearch(mapFirst);
411
-
412
- const handleSearch = async () => {
413
- try {
414
- await search({
415
- body: {
416
- city,
417
- country,
418
- filters: {
419
- checkIn: new Date("2024-06-01"),
420
- checkOut: new Date("2024-06-07"),
421
- numAdults: 2,
422
- numRooms: 1,
423
- currency: "USD",
424
- },
425
- },
426
- });
427
- } catch (err) {
428
- console.error("Search failed:", err);
429
- }
430
- };
431
-
432
- return (
433
- <div>
434
- <div>
435
- <input
436
- value={city}
437
- onChange={(e) => setCity(e.target.value)}
438
- placeholder="City"
439
- />
440
- <input
441
- value={country}
442
- onChange={(e) => setCountry(e.target.value)}
443
- placeholder="Country"
444
- />
445
- <button onClick={handleSearch} disabled={isLoading}>
446
- {isLoading ? "Searching..." : "Search"}
447
- </button>
448
- </div>
449
- {error && <div style={{ color: "red" }}>Error: {error.message}</div>}
450
- <div>Found {state?.properties.length || 0} properties</div>
451
- </div>
452
- );
453
- }
454
- ```
455
-
456
- ### Using `useSmartFilterSearch` for Natural Language Queries
457
-
458
- ```tsx
459
- import React, { useState } from "react";
460
- import {
461
- useMapFirstCore,
462
- useMapLibreAttachment,
463
- useSmartFilterSearch,
464
- } from "@mapfirst/react";
465
- import maplibregl from "maplibre-gl";
466
-
467
- function SmartSearchExample() {
468
- const [query, setQuery] = useState("");
469
-
470
- const { mapFirst, state } = useMapFirstCore({
471
- initialLocationData: {
472
- city: "New York",
473
- country: "United States",
474
- currency: "USD",
475
- },
476
- });
477
-
478
- const { search, isLoading, error } = useSmartFilterSearch(mapFirst);
479
-
480
- const handleSearch = async (e: React.FormEvent) => {
481
- e.preventDefault();
482
- if (!query.trim()) return;
483
-
484
- try {
485
- await search({ query });
486
- } catch (err) {
487
- console.error("Search failed:", err);
488
- }
489
- };
490
-
491
- const exampleQueries = [
492
- "Hotels near Times Square with free wifi",
493
- "4-star hotels with pool and gym",
494
- "Budget hotels under $150",
495
- "Luxury hotels with spa",
496
- ];
497
-
498
- return (
499
- <div>
500
- <form onSubmit={handleSearch}>
501
- <input
502
- type="text"
503
- value={query}
504
- onChange={(e) => setQuery(e.target.value)}
505
- placeholder="Try: hotels near beach with pool"
506
- style={{ width: "400px", padding: "8px" }}
507
- />
508
- <button type="submit" disabled={isLoading}>
509
- {isLoading ? "Searching..." : "Search"}
510
- </button>
511
- </form>
512
-
513
- <div style={{ marginTop: "10px" }}>
514
- <strong>Try these examples:</strong>
515
- <ul>
516
- {exampleQueries.map((q) => (
517
- <li key={q}>
518
- <button
519
- onClick={() => {
520
- setQuery(q);
521
- search({ query: q });
522
- }}
523
- disabled={isLoading}
524
- style={{ textAlign: "left", padding: "4px 8px" }}
525
- >
526
- {q}
527
- </button>
528
- </li>
529
- ))}
530
- </ul>
531
- </div>
532
-
533
- {error && (
534
- <div style={{ color: "red", marginTop: "10px" }}>
535
- Error: {error.message}
536
- </div>
537
- )}
538
-
539
- <div style={{ marginTop: "10px" }}>
540
- {isLoading ? (
541
- <p>Searching...</p>
542
- ) : (
543
- <p>Found {state?.properties.length || 0} properties</p>
544
- )}
545
- </div>
546
- </div>
547
- );
548
- }
549
- ```
550
-
551
- ### Combined Search with Filters
552
-
553
- ```tsx
554
- import React, { useState } from "react";
555
- import {
556
- useMapFirstCore,
557
- usePropertiesSearch,
558
- useSmartFilterSearch,
559
- } from "@mapfirst/react";
560
-
561
- type SearchMode = "location" | "smart";
562
-
563
- function CombinedSearchExample() {
564
- const [mode, setMode] = useState<SearchMode>("location");
565
- const [city, setCity] = useState("Los Angeles");
566
- const [country, setCountry] = useState("United States");
567
- const [query, setQuery] = useState("");
568
-
569
- const { mapFirst, state } = useMapFirstCore({
570
- initialLocationData: { city, country, currency: "USD" },
571
- });
572
-
573
- const propertiesSearch = usePropertiesSearch(mapFirst);
574
- const smartSearch = useSmartFilterSearch(mapFirst);
575
-
576
- const currentSearch = mode === "location" ? propertiesSearch : smartSearch;
577
-
578
- const handleLocationSearch = async () => {
579
- try {
580
- await propertiesSearch.search({
581
- body: {
582
- city,
583
- country,
584
- filters: {
585
- checkIn: new Date("2024-06-01"),
586
- checkOut: new Date("2024-06-07"),
587
- numAdults: 2,
588
- numRooms: 1,
589
- currency: "USD",
590
- },
591
- },
592
- });
593
- } catch (err) {
594
- console.error("Location search failed:", err);
595
- }
596
- };
597
-
598
- const handleSmartSearch = async () => {
599
- if (!query.trim()) return;
600
- try {
601
- await smartSearch.search({ query });
602
- } catch (err) {
603
- console.error("Smart search failed:", err);
604
- }
605
- };
606
-
607
- return (
608
- <div>
609
- <div>
610
- <label>
611
- <input
612
- type="radio"
613
- checked={mode === "location"}
614
- onChange={() => setMode("location")}
615
- />
616
- Location Search
617
- </label>
618
- <label style={{ marginLeft: "20px" }}>
619
- <input
620
- type="radio"
621
- checked={mode === "smart"}
622
- onChange={() => setMode("smart")}
623
- />
624
- Smart Search
625
- </label>
626
- </div>
627
-
628
- {mode === "location" ? (
629
- <div style={{ marginTop: "10px" }}>
630
- <input
631
- value={city}
632
- onChange={(e) => setCity(e.target.value)}
633
- placeholder="City"
634
- />
635
- <input
636
- value={country}
637
- onChange={(e) => setCountry(e.target.value)}
638
- placeholder="Country"
639
- style={{ marginLeft: "10px" }}
640
- />
641
- <button
642
- onClick={handleLocationSearch}
643
- disabled={currentSearch.isLoading}
644
- >
645
- Search
646
- </button>
647
- </div>
648
- ) : (
649
- <div style={{ marginTop: "10px" }}>
650
- <input
651
- value={query}
652
- onChange={(e) => setQuery(e.target.value)}
653
- placeholder="Describe what you're looking for..."
654
- style={{ width: "400px" }}
655
- />
656
- <button
657
- onClick={handleSmartSearch}
658
- disabled={currentSearch.isLoading}
659
- >
660
- Search
661
- </button>
662
- </div>
663
- )}
664
-
665
- {currentSearch.error && (
666
- <div style={{ color: "red", marginTop: "10px" }}>
667
- Error: {currentSearch.error.message}
668
- </div>
669
- )}
670
-
671
- <div style={{ marginTop: "10px" }}>
672
- {currentSearch.isLoading ? (
673
- <p>Searching...</p>
674
- ) : (
675
- <p>Found {state?.properties.length || 0} properties</p>
676
- )}
677
- </div>
678
-
679
- <div style={{ marginTop: "20px" }}>
680
- <h3>Results:</h3>
681
- <ul>
682
- {state?.properties.slice(0, 5).map((property) => (
683
- <li key={property.tripadvisor_id}>
684
- <strong>{property.name}</strong>
685
- {property.pricing && (
686
- <span> - ${property.pricing.display_price}</span>
687
- )}
688
- </li>
689
- ))}
690
- </ul>
691
- </div>
692
- </div>
693
- );
694
- }
695
- ```
696
-
697
- ## TypeScript Usage
698
-
699
- Full type safety with TypeScript:
700
-
701
- ```tsx
702
- import React from "react";
703
- import { useMapFirstCore } from "@mapfirst/react";
704
- import type { Property, MapState, FilterState } from "@mapfirst/core";
705
-
706
- function TypeSafeComponent() {
707
- const { mapFirst, state } = useMapFirstCore({
708
- initialLocationData: {
709
- city: "Paris",
710
- country: "France",
711
- currency: "EUR",
712
- },
713
- });
714
-
715
- // All properly typed
716
- const properties: Property[] = state?.properties || [];
717
- const mapState: MapState | null = state;
718
- const filters: FilterState | undefined = state?.filters;
719
-
720
- // Type-safe property access
721
- const handlePropertyClick = (property: Property) => {
722
- if (mapFirst && property.location) {
723
- mapFirst.flyMapTo(property.location.lon, property.location.lat, 14);
724
- }
725
- };
726
-
727
- return (
728
- <div>
729
- {properties.map((property) => (
730
- <button
731
- key={property.tripadvisor_id}
732
- onClick={() => handlePropertyClick(property)}
733
- >
734
- {property.name}
735
- </button>
736
- ))}
737
- </div>
738
- );
739
- }
740
- ```