Maps Widget Integration
The NowPost Maps Widget is an embeddable iframe component that provides an interactive map interface for selecting PUDO (Pick Up Drop Off) points. It can be easily integrated into any web application to allow users to browse and select pickup locations.
Overview
The Maps Widget is a standalone React application that:
- Displays PUDO points on an interactive Maps interface
- Provides search functionality with location-based filtering
- Handles user selection and returns pickup point data with quotes
- Communicates via postMessage for seamless iframe integration
- Auto-resizes to fit content dynamically
Quick Integration
Basic Iframe Embedding
<!DOCTYPE html>
<html>
<head>
<title>PUDO Point Selection</title>
</head>
<body>
<h1>Choose Your Pickup Location</h1>
<!-- Embed the maps widget -->
<iframe
id="nowpost-maps"
src="https://maps.nowpost.com/?token=demo-token-12345&address=Lagos,%20Nigeria"
width="100%"
height="600"
frameborder="0"
style="border-radius: 8px; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);">
</iframe>
<script>
// Listen for messages from the iframe
window.addEventListener('message', function(event) {
// Verify origin for security
if (event.origin !== 'https://maps.nowpost.com') return;
const { type, payload } = event.data;
switch(type) {
case 'ready':
console.log('Maps widget is ready');
break;
case 'select':
console.log('User selected pickup point:', payload.point);
console.log('Quote:', payload.quote);
handlePickupSelection(payload);
break;
case 'close':
console.log('User closed the widget');
break;
case 'error':
console.error('Widget error:', payload);
break;
}
});
function handlePickupSelection(data) {
// Handle the selected pickup point
const { point, quote } = data;
alert(`Selected: ${point.name} - Fee: ₦${quote.fee.amount}`);
}
</script>
</body>
</html>
URL Parameters
The Maps Widget accepts several URL parameters to customize its behavior:
Required Parameters
| Parameter | Type | Description |
|---|---|---|
token | string | Authentication token (minimum 10 characters) |
Optional Parameters
| Parameter | Type | Description | Example |
|---|---|---|---|
address | string | Initial search address | Lagos, Nigeria |
lat | number | Initial latitude | 6.5244 |
lng | number | Initial longitude | 3.3792 |
radius | number | Search radius in kilometers | 10 |
locale | string | Localization language | en or ng |
origin | string | Your website's origin for security | https://yoursite.com |
mode | string | Display mode (inline or modal) | modal |
Example URLs
// Basic integration
const basicUrl = "https://maps.nowpost.com/?token=your-token-here";
// With location and customization
const customUrl = "https://maps.nowpost.com/?" + new URLSearchParams({
token: "your-token-here",
address: "Victoria Island, Lagos",
lat: "6.4281",
lng: "3.4219",
radius: "15",
locale: "en",
origin: "https://yourwebsite.com",
mode: "modal"
}).toString();
JavaScript Integration
For dynamic integration, you can create and manage the iframe programmatically:
class NowPostMaps {
constructor(options) {
this.options = {
token: options.token,
address: options.address,
lat: options.lat,
lng: options.lng,
container: options.container,
width: options.width || '100%',
height: options.height || '600px',
onReady: options.onReady || (() => {}),
onSelect: options.onSelect || (() => {}),
onClose: options.onClose || (() => {}),
onError: options.onError || (() => {}),
...options
};
this.iframe = null;
this.isReady = false;
this.init();
}
init() {
// Create iframe
this.iframe = document.createElement('iframe');
this.iframe.style.width = this.options.width;
this.iframe.style.height = this.options.height;
this.iframe.style.border = 'none';
this.iframe.style.borderRadius = '8px';
this.iframe.frameBorder = '0';
// Build URL with parameters
const url = new URL('https://maps.nowpost.com/');
Object.entries(this.options).forEach(([key, value]) => {
if (['token', 'address', 'lat', 'lng', 'radius', 'locale', 'origin', 'mode'].includes(key) && value) {
url.searchParams.set(key, value.toString());
}
});
this.iframe.src = url.toString();
// Add message listener
window.addEventListener('message', this.handleMessage.bind(this));
// Append to container
const container = typeof this.options.container === 'string'
? document.getElementById(this.options.container)
: this.options.container;
if (container) {
container.appendChild(this.iframe);
}
}
handleMessage(event) {
// Security check
if (event.origin !== 'https://maps.nowpost.com') return;
const { type, payload } = event.data;
switch(type) {
case 'ready':
this.isReady = true;
this.options.onReady();
break;
case 'select':
this.options.onSelect(payload);
break;
case 'close':
this.options.onClose();
break;
case 'error':
this.options.onError(payload);
break;
case 'heightChange':
if (this.options.autoResize) {
this.iframe.style.height = `${payload}px`;
}
break;
}
}
destroy() {
if (this.iframe && this.iframe.parentNode) {
this.iframe.parentNode.removeChild(this.iframe);
}
window.removeEventListener('message', this.handleMessage.bind(this));
}
}
// Usage
const mapsWidget = new NowPostMaps({
token: 'your-api-token',
container: 'maps-container',
address: 'Lagos, Nigeria',
width: '100%',
height: '500px',
autoResize: true,
onReady: () => {
console.log('Maps widget loaded successfully');
},
onSelect: (data) => {
console.log('Pickup point selected:', data);
// Handle selection in your application
processPickupSelection(data.point, data.quote);
},
onClose: () => {
console.log('User closed the widget');
},
onError: (error) => {
console.error('Maps widget error:', error);
// Handle errors gracefully
showErrorMessage('Failed to load pickup locations');
}
});
Event Handling
The Maps Widget communicates through postMessage events:
Incoming Events (from widget to parent)
ready
Fired when the widget has loaded successfully.
{ type: 'ready' }
select
Fired when a user selects a pickup point.
{
type: 'select',
payload: {
point: {
id: "pudo-point-id",
name: "Main Street Pharmacy",
address: {
address: "123 Main Street",
city: "Lagos",
latitude: 6.5244,
longitude: 3.3792
},
partner: {
name: "Downtown Pharmacy Ltd",
type: "pharmacy"
},
workingHours: {
openingMonday: "08:00",
closingMonday: "18:00"
}
},
quote: {
fee: { amount: 1000, currency: "NGN" },
etaDays: 2
}
}
}
close
Fired when the user closes the widget.
{ type: 'close', payload: {} }
error
Fired when an error occurs.
{
type: 'error',
payload: {
code: 'bootstrap_failed' | 'quote_failed',
message: 'Error description'
}
}
heightChange
Fired when the widget content height changes (for auto-resize).
{
type: 'heightChange',
payload: 450 // height in pixels
}
React Integration
For React applications, you can create a component wrapper:
import React, { useRef, useEffect, useState } from 'react';
interface NowPostMapsProps {
token: string;
address?: string;
lat?: number;
lng?: number;
onSelect?: (data: any) => void;
onReady?: () => void;
onError?: (error: any) => void;
className?: string;
style?: React.CSSProperties;
}
const NowPostMaps: React.FC<NowPostMapsProps> = ({
token,
address,
lat,
lng,
onSelect,
onReady,
onError,
className,
style = { width: '100%', height: '600px' }
}) => {
const iframeRef = useRef<HTMLIFrameElement>(null);
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
const handleMessage = (event: MessageEvent) => {
if (event.origin !== 'https://maps.nowpost.com') return;
const { type, payload } = event.data;
switch(type) {
case 'ready':
setIsLoading(false);
onReady?.();
break;
case 'select':
onSelect?.(payload);
break;
case 'error':
setIsLoading(false);
onError?.(payload);
break;
}
};
window.addEventListener('message', handleMessage);
return () => window.removeEventListener('message', handleMessage);
}, [onSelect, onReady, onError]);
const url = new URL('https://maps.nowpost.com/');
url.searchParams.set('token', token);
if (address) url.searchParams.set('address', address);
if (lat) url.searchParams.set('lat', lat.toString());
if (lng) url.searchParams.set('lng', lng.toString());
url.searchParams.set('origin', window.location.origin);
return (
<div className={className} style={style}>
{isLoading && (
<div style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
height: '100%'
}}>
Loading maps...
</div>
)}
<iframe
ref={iframeRef}
src={url.toString()}
width="100%"
height="100%"
frameBorder="0"
style={{
border: 'none',
borderRadius: '8px',
display: isLoading ? 'none' : 'block'
}}
/>
</div>
);
};
// Usage in your React component
function CheckoutPage() {
const handlePickupSelection = (data) => {
console.log('Selected pickup point:', data.point);
console.log('Delivery quote:', data.quote);
// Update your application state
setSelectedPickupPoint(data.point);
setDeliveryFee(data.quote.fee.amount);
};
return (
<div>
<h2>Choose Pickup Location</h2>
<NowPostMaps
token="your-api-token"
address="Lagos, Nigeria"
onSelect={handlePickupSelection}
onReady={() => console.log('Maps ready')}
onError={(error) => console.error('Maps error:', error)}
style={{ width: '100%', height: '500px' }}
/>
</div>
);
}
Styling & Customization
The Maps Widget is designed to fit seamlessly into your application:
Responsive Design
.maps-container {
width: 100%;
max-width: 800px;
margin: 0 auto;
}
.maps-widget {
width: 100%;
height: 500px;
min-height: 400px;
border: none;
border-radius: 8px;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
}
@media (max-width: 768px) {
.maps-widget {
height: 400px;
}
}
Modal Integration
// Show as modal overlay
function showMapsModal() {
const modal = document.createElement('div');
modal.style.cssText = `
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
`;
const container = document.createElement('div');
container.style.cssText = `
width: 90%;
max-width: 900px;
height: 90%;
max-height: 700px;
background: white;
border-radius: 8px;
overflow: hidden;
`;
modal.appendChild(container);
document.body.appendChild(modal);
const mapsWidget = new NowPostMaps({
token: 'your-token',
container: container,
width: '100%',
height: '100%',
onClose: () => {
document.body.removeChild(modal);
},
onSelect: (data) => {
handlePickupSelection(data);
document.body.removeChild(modal);
}
});
}
Error Handling
Implement proper error handling for a better user experience:
const mapsWidget = new NowPostMaps({
token: 'your-token',
container: 'maps-container',
onError: (error) => {
console.error('Maps widget error:', error);
switch(error.code) {
case 'bootstrap_failed':
showErrorMessage('Failed to initialize maps. Please try again.');
break;
case 'quote_failed':
showErrorMessage('Unable to get delivery quote. Please try selecting another location.');
break;
default:
showErrorMessage('An unexpected error occurred. Please refresh and try again.');
}
}
});
function showErrorMessage(message) {
// Show user-friendly error message
const errorDiv = document.createElement('div');
errorDiv.className = 'error-message';
errorDiv.textContent = message;
document.getElementById('maps-container').appendChild(errorDiv);
}
Security Considerations
Origin Validation
Always validate the message origin for security:
window.addEventListener('message', (event) => {
// Only accept messages from the maps widget
if (event.origin !== 'https://maps.nowpost.com') {
console.warn('Rejected message from unauthorized origin:', event.origin);
return;
}
// Process the message
handleMapsMessage(event.data);
});
Token Management
- Only use your public key (token) to initialize the app
Browser Compatibility
The Maps Widget supports all modern browsers:
- Chrome 90+
- Firefox 88+
- Safari 14+
- Edge 90+
Feature Detection
// Check for required browser features
if (typeof window.postMessage === 'function' &&
typeof document.createElement === 'function') {
// Initialize maps widget
initializeMaps();
} else {
// Show fallback content
showFallbackMessage();
}
Troubleshooting
Common Issues
Widget not loading:
- Verify the token is correct and has sufficient permissions
- Check browser console for CORS or CSP errors
- Ensure the iframe src URL is correct
Messages not being received:
- Verify origin validation is not blocking legitimate messages
- Check that event listeners are properly attached
- Ensure the parent window is not being destroyed before messages arrive
Map not displaying:
- Verify network connectivity
- Check for JavaScript console errors
Debug Mode
Enable debug logging for development:
const mapsWidget = new NowPostMaps({
token: 'your-token',
debug: true, // Enable debug logging
onReady: () => console.log('🗺️ Maps ready'),
onSelect: (data) => console.log('📍 Selection:', data),
onError: (error) => console.error('❌ Error:', error)
});
Next Steps
- Explore the JavaScript SDK for direct API integration
- Check the PUDO Points API for data structure details
- Review the E-commerce Partners guide for authentication
Support
For technical support or integration questions:
- Email: support@nowpost.com
- Documentation: https://docs.nowpost.com