Skip to main content

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

ParameterTypeDescription
tokenstringAuthentication token (minimum 10 characters)

Optional Parameters

ParameterTypeDescriptionExample
addressstringInitial search addressLagos, Nigeria
latnumberInitial latitude6.5244
lngnumberInitial longitude3.3792
radiusnumberSearch radius in kilometers10
localestringLocalization languageen or ng
originstringYour website's origin for securityhttps://yoursite.com
modestringDisplay 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;
}
}
// 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

Support

For technical support or integration questions: