@mapfirst.ai/react 0.0.6 → 0.0.8

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 CHANGED
@@ -386,6 +386,314 @@ function RealTimeUpdates() {
386
386
  }
387
387
  ```
388
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
+
389
697
  ## TypeScript Usage
390
698
 
391
699
  Full type safety with TypeScript:
package/README.md CHANGED
@@ -1,6 +1,16 @@
1
1
  # @mapfirst/react
2
2
 
3
- React hooks for the MapFirst SDK supporting MapLibre, Google Maps, and Mapbox.
3
+ React hooks and components for the MapFirst SDK supporting MapLibre, Google Maps, and Mapbox.
4
+
5
+ ## Features
6
+
7
+ - 🗺️ **Multi-Platform Support**: Works with MapLibre GL JS, Google Maps, and Mapbox GL JS
8
+ - 🔍 **SmartFilter Component**: AI-powered search with interactive filter chips
9
+ - ⚛️ **React Hooks**: Reactive state management for properties, filters, and map state
10
+ - 🎨 **Customizable**: Native React styles (CSS-in-JS) - no framework dependencies
11
+ - 📱 **Responsive**: Adapts to different screen sizes and orientations
12
+ - ♿ **Accessible**: Full keyboard navigation and ARIA support
13
+ - 🌍 **i18n Ready**: Built-in translations with extensibility
4
14
 
5
15
  ## Installation
6
16
 
@@ -12,6 +22,39 @@ pnpm add @mapfirst/react @mapfirst/core
12
22
  yarn add @mapfirst/react @mapfirst/core
13
23
  ```
14
24
 
25
+ ## Quick Start - SmartFilter Component
26
+
27
+ ```tsx
28
+ import { useMapFirstCore, SmartFilter } from "@mapfirst/react";
29
+ import { useState } from "react";
30
+
31
+ function App() {
32
+ const { mapFirst, state } = useMapFirstCore({
33
+ initialLocationData: {
34
+ city: "New York",
35
+ country: "United States",
36
+ currency: "USD",
37
+ },
38
+ });
39
+
40
+ const [filters, setFilters] = useState([]);
41
+
42
+ return (
43
+ <SmartFilter
44
+ mapFirst={mapFirst}
45
+ filters={filters}
46
+ isSearching={state?.isSearching}
47
+ onSearch={async (query) => {
48
+ // Implement search logic
49
+ }}
50
+ onFilterChange={setFilters}
51
+ />
52
+ );
53
+ }
54
+ ```
55
+
56
+ See [SMARTFILTER.md](./SMARTFILTER.md) for complete SmartFilter documentation.
57
+
15
58
  ## Usage
16
59
 
17
60
  The React SDK supports a two-phase initialization pattern:
@@ -499,6 +542,119 @@ const selectedId = useMapFirstSelectedProperty(mapFirst);
499
542
  return <div>Selected: {selectedId || 'None'}</div>;
500
543
  ```
501
544
 
545
+ ### `usePropertiesSearch`
546
+
547
+ Hook to run properties search with the MapFirst SDK. Returns a function to trigger the search and loading state.
548
+
549
+ **Parameters:**
550
+
551
+ - `mapFirst` - SDK instance from `useMapFirstCore`
552
+
553
+ **Returns:** `{ search: Function, isLoading: boolean, error: Error | null }`
554
+
555
+ **Example:**
556
+
557
+ ```tsx
558
+ const { mapFirst } = useMapFirstCore({ ... });
559
+ const { search, isLoading, error } = usePropertiesSearch(mapFirst);
560
+
561
+ const handleSearch = async () => {
562
+ try {
563
+ await search({
564
+ body: {
565
+ city: "Paris",
566
+ country: "France",
567
+ filters: {
568
+ checkIn: new Date(),
569
+ checkOut: new Date(Date.now() + 86400000 * 3), // 3 days later
570
+ numAdults: 2,
571
+ numRooms: 1,
572
+ currency: "EUR"
573
+ }
574
+ }
575
+ });
576
+ } catch (err) {
577
+ console.error("Search failed:", err);
578
+ }
579
+ };
580
+
581
+ return (
582
+ <div>
583
+ <button onClick={handleSearch} disabled={isLoading}>
584
+ {isLoading ? "Searching..." : "Search Properties"}
585
+ </button>
586
+ {error && <p>Error: {error.message}</p>}
587
+ </div>
588
+ );
589
+ ```
590
+
591
+ ### `useSmartFilterSearch`
592
+
593
+ Hook to run smart filter search with natural language queries or predefined filters.
594
+
595
+ **Parameters:**
596
+
597
+ - `mapFirst` - SDK instance from `useMapFirstCore`
598
+
599
+ **Returns:** `{ search: Function, isLoading: boolean, error: Error | null }`
600
+
601
+ **Example with natural language query:**
602
+
603
+ ```tsx
604
+ const { mapFirst } = useMapFirstCore({ ... });
605
+ const { search, isLoading, error } = useSmartFilterSearch(mapFirst);
606
+
607
+ const handleSearch = async (query: string) => {
608
+ try {
609
+ await search({ query });
610
+ } catch (err) {
611
+ console.error("Search failed:", err);
612
+ }
613
+ };
614
+
615
+ return (
616
+ <div>
617
+ <input
618
+ type="text"
619
+ placeholder="e.g., hotels near beach with pool"
620
+ onKeyDown={(e) => {
621
+ if (e.key === 'Enter') {
622
+ handleSearch(e.currentTarget.value);
623
+ }
624
+ }}
625
+ />
626
+ {isLoading && <p>Searching...</p>}
627
+ {error && <p>Error: {error.message}</p>}
628
+ </div>
629
+ );
630
+ ```
631
+
632
+ **Example with predefined filters:**
633
+
634
+ ```tsx
635
+ const { mapFirst } = useMapFirstCore({ ... });
636
+ const { search, isLoading, error } = useSmartFilterSearch(mapFirst);
637
+
638
+ const handleFilterSearch = async () => {
639
+ try {
640
+ await search({
641
+ filters: [
642
+ { id: "pool", label: "Pool", type: "amenity", value: "pool" },
643
+ { id: "4star", label: "4 Star", type: "starRating", value: "4", numericValue: 4 }
644
+ ]
645
+ });
646
+ } catch (err) {
647
+ console.error("Search failed:", err);
648
+ }
649
+ };
650
+
651
+ return (
652
+ <button onClick={handleFilterSearch} disabled={isLoading}>
653
+ Apply Filters
654
+ </button>
655
+ );
656
+ ```
657
+
502
658
  ## Legacy API
503
659
 
504
660
  The old `useMapFirst` hook is still available but deprecated: