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/.more-apartments/availability.json +36 -0
- package/.more-apartments/booking-response.json +15 -0
- package/.more-apartments/categories.json +11 -0
- package/.more-apartments/locations.json +3 -0
- package/.more-apartments/pages.json +46 -0
- package/.more-apartments/posts.json +3 -0
- package/.more-apartments/properties.json +1761 -0
- package/.more-apartments/settings-booking.json +13 -0
- package/.more-apartments/settings-main.json +32 -0
- package/.more-apartments/settings-properties.json +36 -0
- package/.more-apartments/settings-theme.json +78 -0
- package/README.md +982 -0
- package/dist/cli/index.js +1010 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/index.d.mts +2732 -0
- package/dist/index.d.ts +2732 -0
- package/dist/index.js +1009 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +964 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +75 -0
- package/routes/availability.js +40 -0
- package/routes/bookings.js +66 -0
- package/routes/locations.js +28 -0
- package/routes/page.js +27 -0
- package/routes/pages.js +27 -0
- package/routes/post.js +27 -0
- package/routes/posts.js +32 -0
- package/routes/properties.js +31 -0
- package/routes/property.js +27 -0
- package/routes/settings.js +50 -0
- package/src/components/PropertyBookingWidget.astro +169 -0
- package/src/components/PropertySearchGrid.astro +315 -0
- package/src/pages/booking-complete.astro +155 -0
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
|