89 lines
3.0 KiB
TypeScript
89 lines
3.0 KiB
TypeScript
'use client'
|
|
import { useEffect, useState } from 'react'
|
|
|
|
interface Address {
|
|
street: string | null
|
|
locality: string | null
|
|
region: string | null
|
|
postalCode: string | null
|
|
country: string | null
|
|
}
|
|
interface Profile {
|
|
businessName: string | null
|
|
address: Address | null
|
|
}
|
|
|
|
/** One line a map search understands, in South African order. */
|
|
export function placeFor(a: Address): string {
|
|
const country = !a.country || a.country === 'ZA' ? 'South Africa' : a.country
|
|
return [a.street, a.locality, [a.region, a.postalCode].filter(Boolean).join(' '), country]
|
|
.filter(Boolean)
|
|
.join(', ')
|
|
}
|
|
|
|
/**
|
|
* The business's own address on a map, with directions.
|
|
*
|
|
* Nothing at all until the owner has entered a full address. The map is
|
|
* loaded when a visitor asks for it, not before.
|
|
*/
|
|
export default function BusinessMap({ className = '' }: { className?: string }) {
|
|
const [p, setP] = useState<Profile | null>(null)
|
|
const [show, setShow] = useState(false)
|
|
|
|
useEffect(() => {
|
|
const base = process.env.NEXT_PUBLIC_VULA_COMMERCE_URL
|
|
const id = process.env.NEXT_PUBLIC_VULA_PROJECT_ID
|
|
if (!base || !id) return
|
|
let live = true
|
|
fetch(base + '/api/public/profile/' + id)
|
|
.then((r) => (r.ok ? r.json() : null))
|
|
.then((d) => { if (live && d?.profile) setP(d.profile) })
|
|
.catch(() => { /* no map is where we already were */ })
|
|
return () => { live = false }
|
|
}, [])
|
|
|
|
const a = p?.address
|
|
if (!a) return null
|
|
const q = encodeURIComponent(placeFor(a))
|
|
const name = p?.businessName || 'our location'
|
|
|
|
return (
|
|
<div className={'overflow-hidden rounded-2xl border border-border bg-card ' + className}>
|
|
{show ? (
|
|
<iframe
|
|
title={'Map showing ' + name}
|
|
src={'https://maps.google.com/maps?q=' + q + '&z=16&output=embed'}
|
|
className="block h-64 w-full border-0 sm:h-80"
|
|
loading="lazy"
|
|
referrerPolicy="no-referrer-when-downgrade"
|
|
allowFullScreen
|
|
/>
|
|
) : (
|
|
<button
|
|
type="button"
|
|
onClick={() => setShow(true)}
|
|
className="flex h-64 w-full flex-col items-center justify-center gap-2 bg-muted px-6 text-center transition-colors hover:bg-muted/70 sm:h-80"
|
|
>
|
|
<svg aria-hidden="true" viewBox="0 0 24 24" className="h-7 w-7 text-primary" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
|
<path d="M20 10c0 6-8 12-8 12S4 16 4 10a8 8 0 0 1 16 0Z" />
|
|
<circle cx="12" cy="10" r="3" />
|
|
</svg>
|
|
<span className="font-semibold text-foreground">Show map</span>
|
|
<span className="text-xs text-muted-foreground">Loads Google Maps</span>
|
|
</button>
|
|
)}
|
|
<div className="flex justify-end p-4">
|
|
<a
|
|
href={'https://www.google.com/maps/dir/?api=1&destination=' + q}
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
className="text-sm font-medium text-primary hover:underline"
|
|
>
|
|
Get directions
|
|
</a>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|