How to Display Deals in Your Application
Once you are successfully fetching deals from the API, the next step is presenting them effectively in your application. This article covers practical strategies for geocoding, displaying deal cards, sorting results, and keeping deals fresh.
Step 1: Obtain the User's Location
The Deals API requires latitude and longitude coordinates. There are several ways to obtain these depending on your platform and use case.
Browser Geolocation API
The most direct approach for web applications. This uses the device's GPS or network-based location:
function getUserLocation() {
return new Promise((resolve, reject) => {
if (!navigator.geolocation) {
reject(new Error('Geolocation is not supported'));
return;
}
navigator.geolocation.getCurrentPosition(
(position) => {
resolve({
lat: position.coords.latitude,
long: position.coords.longitude
});
},
(error) => {
// Fall back to IP-based geolocation or a default location
reject(error);
},
{ timeout: 10000, maximumAge: 300000 }
);
});
}
> Important: Geolocation requires user permission. Always have a fallback strategy for users who decline or are on browsers that do not support it.
IP-Based Geolocation Fallback
When GPS is unavailable, IP-based services provide approximate coordinates:
async function getLocationByIP() {
const response = await fetch('https://ipapi.co/json/');
const data = await response.json();
return {
lat: data.latitude,
long: data.longitude,
city: data.city
};
}
City or Destination Search
For applications where users select a destination, use a geocoding service to convert the location name to coordinates:
async function geocodeCity(cityName) {
// Use your preferred geocoding provider (Google Maps, Mapbox, etc.)
const response = await fetch(
https://api.mapbox.com/geocoding/v5/mapbox.places/${encodeURIComponent(cityName)}.json?accesstoken=YOURTOKEN
);
const data = await response.json();
const [lng, lat] = data.features[0].center;
return { lat, long: lng };
}
Step 2: Build a Deal Card Component
A well-designed deal card should communicate value quickly. Here are the key elements to include:
Essential Card Elements
- Property image — A large, high-quality hero image draws the eye and makes deals visually appealing.
- Hotel name and star rating — Display prominently so users can quickly assess quality.
- Location — City name or neighborhood, plus distance from the searched coordinates if available.
- Original price — Show the reference price with a strikethrough to highlight the discount.
- Deal price — The discounted price, displayed larger and in a contrasting color.
- Discount badge — A percentage or "Save $X" badge in the corner of the card.
- Deal validity — If the response includes date ranges, show when the deal expires or the travel window.
Example HTML Structure
<div class="deal-card">
<div class="deal-image">
<img src="{deal.imageurl}" alt="{deal.propertyname}" loading="lazy" />
<span class="discount-badge">-{deal.discount_percentage}%</span>
</div>
<div class="deal-content">
<div class="star-rating">{'★'.repeat(deal.star_rating)}</div>
<h3 class="property-name">{deal.property_name}</h3>
<p class="location">{deal.address}</p>
<div class="pricing">
<span class="original-price">${deal.original_price}</span>
<span class="deal-price">${deal.deal_price}</span>
<span class="per-night">per night</span>
</div>
</div>
</div>
Styling Tips
- Use a grid layout (2-3 columns on desktop, single column on mobile) for deal cards.
- Make the discount badge visually prominent — a colored pill or ribbon works well.
- Use the strikethrough style on the original price to emphasize the savings.
- Ensure images have a consistent aspect ratio (16:9 or 3:2) to keep the grid clean.
Step 3: Sort and Filter Deals
The API returns deals in its default order, but you may want to sort or filter them on the client side to match user preferences.
Sorting Options
function sortDeals(deals, sortBy) {
const sorted = [...deals];
switch (sortBy) {
case 'discount':
// Highest discount first
return sorted.sort((a, b) => b.discountpercentage - a.discountpercentage);
case 'price-low':
// Lowest deal price first
return sorted.sort((a, b) => a.dealprice - b.dealprice);
case 'price-high':
// Highest deal price first
return sorted.sort((a, b) => b.dealprice - a.dealprice);
case 'rating':
// Highest star rating first
return sorted.sort((a, b) => b.starrating - a.starrating);
default:
return sorted;
}
}
Client-Side Filtering
You can also offer filters so users can narrow results:
- Star rating — Filter by minimum star rating (e.g., 3+ stars, 4+ stars).
- Price range — Slider or min/max inputs to filter by deal price.
- Minimum discount — Only show deals above a certain percentage off.
Step 4: Handle Deal Expiry and Refresh
Deals are time-sensitive. Stale deals lead to a poor user experience when a user clicks through to book and finds the deal is no longer available.
Refresh Strategy
- On page load — Always fetch fresh deals when the user navigates to a deals page.
- Periodic refresh — If the user stays on the deals page, refresh every 5 to 10 minutes to pick up new deals and drop expired ones.
- On user action — Refresh when the user changes location, currency, or sort preferences.
class DealRefresher {
constructor(fetchFn, intervalMs = 5 60 1000) {
this.fetchFn = fetchFn;
this.intervalMs = intervalMs;
this.timer = null;
}
start() {
this.fetchFn(); // Initial fetch
this.timer = setInterval(() => this.fetchFn(), this.intervalMs);
}
stop() {
if (this.timer) {
clearInterval(this.timer);
this.timer = null;
}
}
refresh() {
this.stop();
this.start();
}
}
Handling Expired Deals
If deals include an expiry timestamp, you can proactively remove or dim expired deals in the UI without waiting for the next API call:
function isExpired(deal) {
if (!deal.expires_at) return false;
return new Date(deal.expires_at) < new Date();
}
function filterActiveDeals(deals) {
return deals.filter(deal => !isExpired(deal));
}
Step 5: Empty State and Loading
Loading State
Show skeleton cards or a spinner while deals are loading. Avoid showing an empty page that might be mistaken for "no deals available."
No Deals Available
Some locations may have no current deals. Handle this gracefully:
<div class="no-deals">
<h3>No deals available near this location right now</h3>
<p>Deals change frequently. Check back soon or try a different location.</p>
<button onclick="showTopDestinations()">Browse Top Destinations</button>
</div>
Consider falling back to a top_destination=true query when a proximity-based search returns no results, so users always see something useful.
Putting It All Together
A typical integration flow looks like this:
- Detect or request the user's location.
- Call the Deals API with the coordinates and preferred currency.
- Render deal cards in a responsive grid layout.
- Provide sorting and filtering controls.
- Set up a periodic refresh to keep deals current.
- Handle empty states and loading indicators gracefully.
For guidance on caching and performance, see Best Practices for Integration.