more-apartments-astro-integration 1.0.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 ADDED
@@ -0,0 +1,982 @@
1
+ # More Apartments Astro Integration
2
+
3
+ A fully-typed Astro integration for the More Apartments REST API, providing easy access to property listings, availability, bookings, and content management.
4
+
5
+ ## Features
6
+
7
+ - 🚀 **Full TypeScript Support** - Complete type definitions for all API responses
8
+ - 🔄 **Built-in Retry Logic** - Automatic retry with exponential backoff
9
+ - 💾 **Response Caching** - Configurable caching to reduce API calls
10
+ - 🎯 **Zod Validation** - Runtime validation of API responses
11
+ - 🔌 **Virtual Modules** - Easy importing in Astro components
12
+ - 🛣️ **Optional API Routes** - Proxy API calls through your Astro server
13
+ - ⚡ **Optimized for Astro** - Follows Astro integration best practices
14
+
15
+ ## Installation
16
+
17
+ ```bash
18
+ bun add @shelfwood/more-apartments-astro-integration
19
+ # or
20
+ npm install @shelfwood/more-apartments-astro-integration
21
+ # or
22
+ yarn add @shelfwood/more-apartments-astro-integration
23
+ # or
24
+ bun add @shelfwood/more-apartments-astro-integration
25
+ ```
26
+
27
+ ## CLI Usage
28
+
29
+ The package includes a powerful CLI for interacting with the More Apartments API directly from the command line.
30
+
31
+ ### CLI Installation
32
+
33
+ The CLI is automatically available after installing the package:
34
+
35
+ ```bash
36
+ npx more-apartments --help
37
+ # or with bun
38
+ bunx more-apartments --help
39
+ ```
40
+
41
+ ### CLI Configuration
42
+
43
+ The CLI reads configuration from environment variables or command-line options:
44
+
45
+ ```bash
46
+ # .env file
47
+ MORE_APARTMENTS_BASE_URL=http://prj-more-apartments.test
48
+ MORE_APARTMENTS_API_KEY=your-api-key
49
+ MORE_APARTMENTS_INSTANCE=your-instance-name
50
+ ```
51
+
52
+ Or provide options directly:
53
+
54
+ ```bash
55
+ more-apartments --base-url http://prj-more-apartments.test \
56
+ --api-key your-api-key \
57
+ --instance your-instance-name \
58
+ properties list
59
+ ```
60
+
61
+ ### CLI Commands
62
+
63
+ #### Properties
64
+
65
+ ```bash
66
+ # List properties with pagination
67
+ more-apartments properties list --page 1 --limit 10
68
+
69
+ # Show specific property
70
+ more-apartments properties show adipisci-voluptas-maiores
71
+
72
+ # Search properties with filters
73
+ more-apartments properties search \
74
+ --city "The Hague" \
75
+ --bedrooms 2 \
76
+ --guests 4 \
77
+ --arrival 2024-06-01 \
78
+ --departure 2024-06-07
79
+
80
+ # Advanced search with amenities
81
+ more-apartments properties search \
82
+ --city Amsterdam \
83
+ --parking \
84
+ --balcony \
85
+ --elevator
86
+
87
+ # Get property availability
88
+ more-apartments properties availability <property-slug> \
89
+ --start 2024-06-01 \
90
+ --end 2024-06-30
91
+ ```
92
+
93
+ #### Content Management
94
+
95
+ ```bash
96
+ # List all pages
97
+ more-apartments content pages list
98
+
99
+ # Show specific page
100
+ more-apartments content pages show about-us
101
+
102
+ # List posts with filters
103
+ more-apartments content posts list --page 1 --limit 5 --tag news
104
+
105
+ # Show specific post
106
+ more-apartments content posts show welcome-to-our-blog
107
+
108
+ # List categories
109
+ more-apartments content categories list
110
+
111
+ # Show category with segments
112
+ more-apartments content categories show locations
113
+ more-apartments content categories segments locations
114
+
115
+ # Show specific segment
116
+ more-apartments content categories segment locations the-hague
117
+
118
+ # List all locations
119
+ more-apartments content locations
120
+ ```
121
+
122
+ #### Settings
123
+
124
+ ```bash
125
+ # Get main site settings
126
+ more-apartments settings main
127
+
128
+ # Get theme settings
129
+ more-apartments settings theme
130
+
131
+ # Get property settings
132
+ more-apartments settings properties
133
+
134
+ # Get booking settings
135
+ more-apartments settings booking
136
+
137
+ # Get all settings at once
138
+ more-apartments settings all
139
+ ```
140
+
141
+ #### Bookings
142
+
143
+ ```bash
144
+ # Create booking from JSON file
145
+ more-apartments bookings create --file booking.json
146
+
147
+ # Show booking details
148
+ more-apartments bookings show <booking-id>
149
+
150
+ # Generate example booking JSON
151
+ more-apartments bookings example > booking.json
152
+ ```
153
+
154
+ ### Output Formats
155
+
156
+ The CLI supports multiple output formats:
157
+
158
+ ```bash
159
+ # JSON format (default)
160
+ more-apartments properties list --format json
161
+
162
+ # Table format (great for quick viewing)
163
+ more-apartments properties list --format table
164
+
165
+ # YAML format (human-readable)
166
+ more-apartments properties list --format yaml
167
+ ```
168
+
169
+ ### CLI Examples
170
+
171
+ **Find available 2-bedroom apartments in The Hague for a specific week:**
172
+
173
+ ```bash
174
+ more-apartments properties search \
175
+ --city "The Hague" \
176
+ --bedrooms 2 \
177
+ --arrival 2024-07-15 \
178
+ --departure 2024-07-22 \
179
+ --format table
180
+ ```
181
+
182
+ **Get property details and availability in one command:**
183
+
184
+ ```bash
185
+ # First get property details
186
+ more-apartments properties show my-apartment-slug --format yaml
187
+
188
+ # Then check availability
189
+ more-apartments properties availability my-apartment-slug \
190
+ --start 2024-06-01 \
191
+ --end 2024-06-30 \
192
+ --format json
193
+ ```
194
+
195
+ **Export all properties to JSON file:**
196
+
197
+ ```bash
198
+ more-apartments properties list --limit 100 --format json > properties.json
199
+ ```
200
+
201
+ **Get site configuration:**
202
+
203
+ ```bash
204
+ more-apartments settings all --format yaml > config.yaml
205
+ ```
206
+
207
+ ## Configuration
208
+
209
+ Add the integration to your `astro.config.mjs`:
210
+
211
+ ```js
212
+ import { defineConfig } from 'astro/config';
213
+ import moreApartments from '@shelfwood/more-apartments-astro-integration';
214
+
215
+ export default defineConfig({
216
+ integrations: [
217
+ moreApartments({
218
+ baseUrl: 'http://prj-more-apartments.test', // Your API base URL
219
+ apiKey: process.env.MORE_APARTMENTS_API_KEY, // Optional API key
220
+ instance: 'cheap-gotham-apartments', // Optional instance identifier
221
+
222
+ // Optional configuration
223
+ timeout: 30000, // Request timeout in ms (default: 30000)
224
+ retry: {
225
+ attempts: 3, // Number of retry attempts (default: 3)
226
+ delay: 1000, // Initial retry delay in ms (default: 1000)
227
+ },
228
+ cache: {
229
+ enabled: true, // Enable response caching (default: true)
230
+ ttl: 300000, // Cache TTL in ms (default: 5 minutes)
231
+ },
232
+
233
+ // Integration features
234
+ virtualModules: true, // Enable virtual module imports (default: true)
235
+ injectClient: true, // Inject global client (default: true)
236
+ addApiRoutes: true, // Add proxy API routes for security (default: true, recommended)
237
+ apiRoutePrefix: '/api/apartments', // API route prefix when addApiRoutes is true
238
+ })
239
+ ]
240
+ });
241
+ ```
242
+
243
+ **Environment Variables (.env):**
244
+
245
+ ```bash
246
+ # API Configuration (required)
247
+ MORE_APARTMENTS_BASE_URL=https://api.moreapartments.com
248
+ MORE_APARTMENTS_API_KEY=your-api-key-here
249
+ MORE_APARTMENTS_INSTANCE=your-instance-name
250
+ ```
251
+
252
+ ## Usage
253
+
254
+ ### Hybrid Booking Flow (Phase 1)
255
+
256
+ The integration includes a **Hybrid Booking Flow** that allows you to display properties in your Astro frontend while redirecting users to the Laravel backend for the booking process. This provides a fast time-to-market solution.
257
+
258
+ #### PropertyBookingWidget Component
259
+
260
+ ```astro
261
+ ---
262
+ // src/pages/property/[id].astro
263
+ import client from 'virtual:more-apartments/client';
264
+ import PropertyBookingWidget from '@shelfwood/more-apartments-astro-integration/components/PropertyBookingWidget.astro';
265
+ import { generateBookingUrl } from '@shelfwood/more-apartments-astro-integration';
266
+
267
+ const { id } = Astro.params;
268
+ const property = await client.getProperty(id);
269
+
270
+ // Example search parameters (these would typically come from URL params or form state)
271
+ const searchParams = {
272
+ arrival: '2024-03-15',
273
+ departure: '2024-03-20',
274
+ guests: 2
275
+ };
276
+ ---
277
+
278
+ <PropertyBookingWidget
279
+ propertyExternalId={property.external_id}
280
+ propertyName={property.name}
281
+ basePrice={property.base_price}
282
+ arrival={searchParams.arrival}
283
+ departure={searchParams.departure}
284
+ guests={searchParams.guests}
285
+ buttonText="Reserve Now"
286
+ />
287
+ ```
288
+
289
+ #### Manual Booking URL Generation
290
+
291
+ ```typescript
292
+ import { generateBookingUrl } from '@shelfwood/more-apartments-astro-integration';
293
+
294
+ // Generate booking URL for Laravel backend
295
+ const bookingUrl = generateBookingUrl(
296
+ property.external_id, // Property's external_id
297
+ '2024-03-15', // Arrival date (YYYY-MM-DD)
298
+ '2024-03-20', // Departure date (YYYY-MM-DD)
299
+ 2 // Number of guests
300
+ );
301
+
302
+ // Use in a regular HTML anchor tag
303
+ // <a href={bookingUrl}>Book This Property</a>
304
+ ```
305
+
306
+ #### Booking Complete Page
307
+
308
+ Copy the example booking complete page to your project:
309
+
310
+ ```bash
311
+ # Copy the example page to your Astro project
312
+ cp node_modules/@shelfwood/more-apartments-astro-integration/src/pages/booking-complete.astro src/pages/
313
+ ```
314
+
315
+ #### Environment Configuration
316
+
317
+ ```bash
318
+ # .env
319
+ PUBLIC_LARAVEL_BOOKING_URL=http://prj-more-apartments.test/booking/initiate
320
+ ```
321
+
322
+ ### Advanced Property Search (Phase 2)
323
+
324
+ The integration now includes powerful property search functionality that leverages the backend's PropertySearchService for rich filtering capabilities.
325
+
326
+ #### PropertySearchGrid Component
327
+
328
+ ```astro
329
+ ---
330
+ // src/pages/search.astro
331
+ import PropertySearchGrid from '@shelfwood/more-apartments-astro-integration/components/PropertySearchGrid.astro';
332
+
333
+ // Get search parameters from URL or form
334
+ const url = new URL(Astro.request.url);
335
+ const searchParams = {
336
+ destination: url.searchParams.get('destination') || undefined,
337
+ arrival: url.searchParams.get('arrival') || undefined,
338
+ departure: url.searchParams.get('departure') || undefined,
339
+ guests: url.searchParams.get('guests') ? parseInt(url.searchParams.get('guests')) : undefined,
340
+ };
341
+ ---
342
+
343
+ <PropertySearchGrid
344
+ destination={searchParams.destination}
345
+ arrival={searchParams.arrival}
346
+ departure={searchParams.departure}
347
+ guests={searchParams.guests}
348
+ showBookingWidgets={true}
349
+ maxResults={20}
350
+ />
351
+ ```
352
+
353
+ #### Manual Property Search
354
+
355
+ ```typescript
356
+ import { searchProperties } from '@shelfwood/more-apartments-astro-integration';
357
+ import client from 'virtual:more-apartments/client';
358
+
359
+ // Search with various filters
360
+ const properties = await searchProperties(client, {
361
+ destination: 'Amsterdam',
362
+ arrival: '2024-03-15',
363
+ departure: '2024-03-20',
364
+ guests: 2,
365
+ segment: 'luxury-apartments' // Optional segment filter
366
+ });
367
+ ```
368
+
369
+ #### Search Parameters
370
+
371
+ - **destination**: Free-text search across city, country, and area fields
372
+ - **arrival/departure**: Date range filtering with availability checking
373
+ - **guests**: Minimum occupancy requirements
374
+ - **segment**: Filter by property segments/categories
375
+
376
+ ### Building Custom Components
377
+
378
+ While the integration provides ready-to-use components, you can also build custom components using the image utilities and helper functions.
379
+
380
+ #### Custom Property Card Example
381
+
382
+ ```astro
383
+ ---
384
+ // src/components/CustomPropertyCard.astro
385
+ import type { Property } from '@shelfwood/more-apartments-astro-integration';
386
+ import { getPrimaryImageUrl, getImageCount } from '@shelfwood/more-apartments-astro-integration';
387
+
388
+ interface Props {
389
+ property: Property;
390
+ class?: string;
391
+ }
392
+
393
+ const { property, class: className } = Astro.props;
394
+
395
+ // Use image utilities for consistent fallback handling
396
+ const imageUrl = getPrimaryImageUrl(property, '/images/default-property.jpg');
397
+ const imageCount = getImageCount(property);
398
+ ---
399
+
400
+ <article class:list={['property-card', className]}>
401
+ <div class="property-image">
402
+ <img
403
+ src={imageUrl}
404
+ alt={property.name}
405
+ loading="lazy"
406
+ width="400"
407
+ height="300"
408
+ />
409
+ {imageCount > 1 && (
410
+ <span class="image-count">{imageCount} photos</span>
411
+ )}
412
+ </div>
413
+
414
+ <div class="property-details">
415
+ <h3>{property.name}</h3>
416
+ <p class="location">{property.area}, {property.city}</p>
417
+ <p class="description">{property.short_description}</p>
418
+
419
+ <div class="property-features">
420
+ {property.bedrooms && (
421
+ <span>{property.bedrooms} bed{property.bedrooms > 1 ? 's' : ''}</span>
422
+ )}
423
+ {property.max_persons && (
424
+ <span>Up to {property.max_persons} guests</span>
425
+ )}
426
+ {property.size && (
427
+ <span>{property.size}m²</span>
428
+ )}
429
+ </div>
430
+
431
+ {property.min_rate && (
432
+ <div class="pricing">
433
+ <span class="price">€{property.min_rate}</span>
434
+ <span class="price-label">per night</span>
435
+ </div>
436
+ )}
437
+ </div>
438
+ </article>
439
+
440
+ <style>
441
+ .property-card {
442
+ border: 1px solid #e5e7eb;
443
+ border-radius: 8px;
444
+ overflow: hidden;
445
+ transition: shadow 0.3s ease;
446
+ }
447
+
448
+ .property-card:hover {
449
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
450
+ }
451
+
452
+ .property-image {
453
+ position: relative;
454
+ }
455
+
456
+ .image-count {
457
+ position: absolute;
458
+ bottom: 8px;
459
+ right: 8px;
460
+ background: rgba(0, 0, 0, 0.7);
461
+ color: white;
462
+ padding: 4px 8px;
463
+ border-radius: 4px;
464
+ font-size: 12px;
465
+ }
466
+
467
+ /* Additional styling... */
468
+ </style>
469
+ ```
470
+
471
+ #### Custom Property Gallery Example
472
+
473
+ ```astro
474
+ ---
475
+ // src/components/PropertyGallery.astro
476
+ import type { Property } from '@shelfwood/more-apartments-astro-integration';
477
+ import {
478
+ getAllImageUrls,
479
+ getAllThumbnailUrls,
480
+ getImageWithAlt
481
+ } from '@shelfwood/more-apartments-astro-integration';
482
+
483
+ interface Props {
484
+ property: Property;
485
+ }
486
+
487
+ const { property } = Astro.props;
488
+
489
+ // Get all images for the gallery
490
+ const images = getAllImageUrls(property);
491
+ const thumbnails = getAllThumbnailUrls(property);
492
+
493
+ // Get primary image with alt text for main display
494
+ const { url: primaryUrl, alt: primaryAlt } = getImageWithAlt(property, 0);
495
+ ---
496
+
497
+ <div class="gallery">
498
+ {images.length > 0 ? (
499
+ <>
500
+ <div class="gallery-main">
501
+ <img
502
+ src={primaryUrl}
503
+ alt={primaryAlt}
504
+ class="main-image"
505
+ />
506
+ </div>
507
+
508
+ {images.length > 1 && (
509
+ <div class="gallery-thumbnails">
510
+ {images.map((imageUrl, index) => {
511
+ const { alt } = getImageWithAlt(property, index);
512
+ return (
513
+ <button
514
+ class="thumbnail"
515
+ data-image={imageUrl}
516
+ aria-label={`View ${alt}`}
517
+ >
518
+ <img
519
+ src={thumbnails[index] || imageUrl}
520
+ alt={alt}
521
+ loading="lazy"
522
+ />
523
+ </button>
524
+ );
525
+ })}
526
+ </div>
527
+ )}
528
+ </>
529
+ ) : (
530
+ <div class="no-images">
531
+ <p>No images available for this property</p>
532
+ </div>
533
+ )}
534
+ </div>
535
+
536
+ <script>
537
+ // Add client-side gallery interactivity
538
+ document.querySelectorAll('.thumbnail').forEach(thumb => {
539
+ thumb.addEventListener('click', (e) => {
540
+ const imageUrl = (e.currentTarget as HTMLElement).dataset.image;
541
+ const mainImage = document.querySelector('.main-image') as HTMLImageElement;
542
+ if (mainImage && imageUrl) {
543
+ mainImage.src = imageUrl;
544
+ }
545
+ });
546
+ });
547
+ </script>
548
+
549
+ <style>
550
+ .gallery {
551
+ display: flex;
552
+ flex-direction: column;
553
+ gap: 1rem;
554
+ }
555
+
556
+ .gallery-main {
557
+ width: 100%;
558
+ aspect-ratio: 16 / 9;
559
+ overflow: hidden;
560
+ border-radius: 8px;
561
+ }
562
+
563
+ .main-image {
564
+ width: 100%;
565
+ height: 100%;
566
+ object-fit: cover;
567
+ }
568
+
569
+ .gallery-thumbnails {
570
+ display: grid;
571
+ grid-template-columns: repeat(auto-fill, minmax(80px, 1fr));
572
+ gap: 0.5rem;
573
+ }
574
+
575
+ .thumbnail {
576
+ aspect-ratio: 1;
577
+ border: 2px solid transparent;
578
+ border-radius: 4px;
579
+ overflow: hidden;
580
+ cursor: pointer;
581
+ transition: border-color 0.2s;
582
+ }
583
+
584
+ .thumbnail:hover {
585
+ border-color: #3b82f6;
586
+ }
587
+
588
+ .thumbnail img {
589
+ width: 100%;
590
+ height: 100%;
591
+ object-fit: cover;
592
+ }
593
+ </style>
594
+ ```
595
+
596
+ ### Using Virtual Modules (Recommended)
597
+
598
+ The integration provides virtual modules for easy importing:
599
+
600
+ ```astro
601
+ ---
602
+ // In any .astro component
603
+ import client from 'virtual:more-apartments/client';
604
+
605
+ // Fetch properties with full type safety
606
+ const propertiesResponse = await client.getProperties({
607
+ page: 1,
608
+ per_page: 10
609
+ });
610
+ const properties = propertiesResponse.data;
611
+
612
+ // Fetch a single property
613
+ const property = await client.getProperty('luxury-apartment-1');
614
+
615
+ // Get availability
616
+ const availability = await client.getAvailability(
617
+ property.id,
618
+ '2024-03-01',
619
+ '2024-03-15'
620
+ );
621
+ ---
622
+
623
+ <div>
624
+ {properties.map(property => (
625
+ <article>
626
+ <h2>{property.name}</h2>
627
+ <p>{property.description}</p>
628
+ <p>Max occupancy: {property.max_occupancy}</p>
629
+ </article>
630
+ ))}
631
+ </div>
632
+ ```
633
+
634
+ ### Direct Client Import
635
+
636
+ You can also import and instantiate the client directly:
637
+
638
+ ```ts
639
+ import { MoreApartmentsClient } from '@shelfwood/more-apartments-astro-integration';
640
+
641
+ const client = new MoreApartmentsClient({
642
+ baseUrl: 'http://prj-more-apartments.test',
643
+ apiKey: 'your-api-key',
644
+ });
645
+
646
+ const properties = await client.getProperties();
647
+ ```
648
+
649
+ ### Using Helper Functions
650
+
651
+ The integration exports helper functions with built-in error handling:
652
+
653
+ ```astro
654
+ ---
655
+ import client from 'virtual:more-apartments/client';
656
+ import {
657
+ fetchProperties,
658
+ fetchProperty,
659
+ fetchAvailability,
660
+ fetchPages,
661
+ fetchPosts
662
+ } from '@shelfwood/more-apartments-astro-integration';
663
+
664
+ // These helpers return null or empty arrays on error
665
+ const properties = await fetchProperties(client, { page: 1 });
666
+ const property = await fetchProperty(client, 'apartment-slug');
667
+ const pages = await fetchPages(client);
668
+ ---
669
+ ```
670
+
671
+ ### Image Utilities
672
+
673
+ The integration provides type-safe image utility functions for handling property images. These utilities simplify image handling and provide consistent fallback behavior.
674
+
675
+ > **⚠️ Server-Side Only**: Image utilities are designed for use in Astro component frontmatter (server-side). They cannot be used in `<script>` tags, framework components with `client:` directives, or browser console.
676
+
677
+ ```astro
678
+ ---
679
+ import type { Property } from '@shelfwood/more-apartments-astro-integration';
680
+ import {
681
+ getPrimaryImageUrl,
682
+ getThumbnailUrl,
683
+ getAllImageUrls,
684
+ getImageWithAlt,
685
+ hasImages,
686
+ getImageCount
687
+ } from '@shelfwood/more-apartments-astro-integration';
688
+
689
+ const { property } = Astro.props;
690
+
691
+ // Get primary image with optional fallback
692
+ const imageUrl = getPrimaryImageUrl(property, '/images/default.jpg');
693
+
694
+ // Get thumbnail image
695
+ const thumbUrl = getThumbnailUrl(property, '/images/default-thumb.jpg');
696
+
697
+ // Get all image URLs for gallery
698
+ const allImages = getAllImageUrls(property);
699
+
700
+ // Get image with alt text for accessibility
701
+ const { url, alt } = getImageWithAlt(property, 0);
702
+
703
+ // Check if property has images
704
+ if (hasImages(property)) {
705
+ const count = getImageCount(property);
706
+ // property has {count} images
707
+ }
708
+ ---
709
+
710
+ <img src={imageUrl} alt={alt} loading="lazy" />
711
+
712
+ <!-- Image gallery -->
713
+ {allImages.map((imgUrl) => (
714
+ <img src={imgUrl} alt={property.name} />
715
+ ))}
716
+ ```
717
+
718
+ #### Available Image Functions
719
+
720
+ - **`getPrimaryImageUrl(property, fallbackUrl?)`** - Get the first image URL, returns fallback if no images
721
+ - **`getThumbnailUrl(property, fallbackUrl?)`** - Get the first thumbnail URL, returns fallback if no images
722
+ - **`getAllImageUrls(property)`** - Get array of all image URLs (empty array if none)
723
+ - **`getAllThumbnailUrls(property)`** - Get array of all thumbnail URLs (empty array if none)
724
+ - **`getImageWithAlt(property, index?)`** - Get image URL and alt text (defaults to property name)
725
+ - **`getImage(property, index?)`** - Get complete PropertyImage object or null
726
+ - **`hasImages(property)`** - Returns true if property has at least one image
727
+ - **`getImageCount(property)`** - Returns number of images (0 if none)
728
+
729
+ #### Migration from Local Implementations
730
+
731
+ If you previously had custom image handling functions:
732
+
733
+ ```diff
734
+ ---
735
+ - function getPrimaryImageUrl(property) {
736
+ - if (property.images?.[0]) {
737
+ - return property.images[0].url;
738
+ - }
739
+ - return '/placeholder.jpg';
740
+ - }
741
+ + import { getPrimaryImageUrl } from '@shelfwood/more-apartments-astro-integration';
742
+
743
+ - const imageUrl = getPrimaryImageUrl(property);
744
+ + const imageUrl = getPrimaryImageUrl(property, '/placeholder.jpg');
745
+ ---
746
+ ```
747
+
748
+ ## API Methods
749
+
750
+ ### Properties
751
+
752
+ ```ts
753
+ // Get paginated properties
754
+ const properties = await client.getProperties({
755
+ page: 1,
756
+ per_page: 15
757
+ });
758
+
759
+ // Get single property by ID or slug
760
+ const property = await client.getProperty(123);
761
+ // or
762
+ const property = await client.getProperty('property-slug');
763
+ ```
764
+
765
+ ### Availability
766
+
767
+ ```ts
768
+ // Get availability for date range
769
+ const availability = await client.getAvailability(
770
+ propertyId,
771
+ '2024-03-01',
772
+ '2024-03-15'
773
+ );
774
+ ```
775
+
776
+ ### Content Management
777
+
778
+ ```ts
779
+ // Get all pages
780
+ const pages = await client.getPages();
781
+
782
+ // Get single page
783
+ const page = await client.getPage('about-us');
784
+
785
+ // Get paginated posts
786
+ const posts = await client.getPosts({
787
+ page: 1,
788
+ per_page: 10,
789
+ tag: 'news'
790
+ });
791
+
792
+ // Get single post
793
+ const post = await client.getPost('latest-update');
794
+ ```
795
+
796
+ ### Settings
797
+
798
+ ```ts
799
+ // Get various settings
800
+ const mainSettings = await client.getMainSettings();
801
+ const themeSettings = await client.getThemeSettings();
802
+ const propertySettings = await client.getPropertySettings();
803
+ const bookingSettings = await client.getBookingSettings();
804
+ ```
805
+
806
+ ### Bookings
807
+
808
+ ```ts
809
+ // Create a booking
810
+ const booking = await client.createBooking({
811
+ property_id: 123,
812
+ check_in: '2024-03-01',
813
+ check_out: '2024-03-07',
814
+ guests: 2,
815
+ first_name: 'John',
816
+ last_name: 'Doe',
817
+ email: 'john@example.com',
818
+ phone: '+1234567890',
819
+ notes: 'Late arrival',
820
+ total_amount: 1500,
821
+ payment_method: 'stripe'
822
+ });
823
+
824
+ // Get booking details
825
+ const bookingDetails = await client.getBooking('booking-id');
826
+ ```
827
+
828
+ ## TypeScript Types
829
+
830
+ All response types are fully typed and exported:
831
+
832
+ ```ts
833
+ import type {
834
+ Property,
835
+ Page,
836
+ Post,
837
+ Availability,
838
+ BookingRequest,
839
+ BookingResponse,
840
+ MainSettings,
841
+ ThemeSettings,
842
+ PropertySettings,
843
+ BookingSettings,
844
+ PaginatedResponse,
845
+ ApiError
846
+ } from '@shelfwood/more-apartments-astro-integration';
847
+ ```
848
+
849
+ ## Cache Management
850
+
851
+ The client includes built-in caching for GET requests:
852
+
853
+ ```ts
854
+ // Clear all cached data
855
+ client.clearCache();
856
+
857
+ // Get cache statistics
858
+ const stats = client.getCacheStats();
859
+ console.log(`Cache has ${stats.entries} entries`);
860
+ ```
861
+
862
+ ## Error Handling
863
+
864
+ The client includes comprehensive error handling with retry logic:
865
+
866
+ ```ts
867
+ try {
868
+ const property = await client.getProperty('non-existent');
869
+ } catch (error) {
870
+ // Error will be thrown after all retry attempts fail
871
+ console.error('Failed to fetch property:', error.message);
872
+ }
873
+ ```
874
+
875
+ ## Environment Variables
876
+
877
+ For security, store sensitive configuration in environment variables:
878
+
879
+ ```bash
880
+ # .env
881
+ MORE_APARTMENTS_BASE_URL=http://prj-more-apartments.test
882
+ MORE_APARTMENTS_API_KEY=your-api-key-here
883
+ MORE_APARTMENTS_INSTANCE=cheap-gotham-apartments
884
+ ```
885
+
886
+ Then use in your config:
887
+
888
+ ```js
889
+ moreApartments({
890
+ baseUrl: import.meta.env.MORE_APARTMENTS_BASE_URL,
891
+ apiKey: import.meta.env.MORE_APARTMENTS_API_KEY,
892
+ instance: import.meta.env.MORE_APARTMENTS_INSTANCE,
893
+ })
894
+ ```
895
+
896
+ ## Security Best Practices
897
+
898
+ ### ⚠️ Important: API Key Security
899
+
900
+ **Never expose your API key in client-side code!** The integration provides two patterns:
901
+
902
+ #### 🔒 Secure Pattern (Recommended - Default)
903
+ Use the API proxy routes to keep your API key secure on the server:
904
+
905
+ ```js
906
+ // astro.config.mjs
907
+ export default defineConfig({
908
+ integrations: [
909
+ moreApartments({
910
+ // For client-side modules: point to your own API routes
911
+ baseUrl: '/api/apartments',
912
+ instance: import.meta.env.MORE_APARTMENTS_INSTANCE,
913
+ // Don't include apiKey here - it would be exposed!
914
+
915
+ // Enable server-side proxy routes (default: true)
916
+ addApiRoutes: true,
917
+ apiRoutePrefix: '/api/apartments'
918
+ })
919
+ ]
920
+ });
921
+ ```
922
+
923
+ ```bash
924
+ # .env (server-side only)
925
+ MORE_APARTMENTS_BASE_URL=http://prj-more-apartments.test
926
+ MORE_APARTMENTS_API_KEY=your-secret-api-key
927
+ MORE_APARTMENTS_INSTANCE=your-instance
928
+ ```
929
+
930
+ #### ❌ Insecure Pattern (Avoid)
931
+ Never do this - it exposes your API key in the browser:
932
+
933
+ ```js
934
+ // DON'T DO THIS
935
+ moreApartments({
936
+ baseUrl: 'http://prj-more-apartments.test', // Direct backend URL
937
+ apiKey: 'your-secret-key', // ❌ EXPOSED IN BROWSER!
938
+ addApiRoutes: false
939
+ })
940
+ ```
941
+
942
+ ### How API Proxying Works
943
+
944
+ When `addApiRoutes: true` (default), the integration creates secure server-side routes:
945
+
946
+ ```
947
+ Browser → /api/apartments/properties → Your Astro Server → Your Laravel API
948
+ (with secret API key)
949
+ ```
950
+
951
+ This creates proxy routes like:
952
+ - `/api/apartments/properties`
953
+ - `/api/apartments/properties/[id]`
954
+ - `/api/apartments/pages`
955
+ - etc.
956
+
957
+ The API routes automatically read these server-side environment variables:
958
+ - `MORE_APARTMENTS_BASE_URL` - Your actual backend URL
959
+ - `MORE_APARTMENTS_API_KEY` - Your secret API key
960
+ - `MORE_APARTMENTS_INSTANCE` - Your instance identifier
961
+
962
+ ## Advanced Features
963
+
964
+ ### Global Client Access
965
+
966
+ When `injectClient` is enabled, a global object is available in the browser:
967
+
968
+ ```js
969
+ // In client-side scripts
970
+ if (window.__moreApartments) {
971
+ console.log('API Base URL:', window.__moreApartments.baseUrl);
972
+ console.log('Instance:', window.__moreApartments.instance);
973
+ }
974
+ ```
975
+
976
+ ## Contributing
977
+
978
+ Contributions are welcome! Please feel free to submit a Pull Request.
979
+
980
+ ## License
981
+
982
+ MIT