Deploy
This commit is contained in:
@@ -0,0 +1,130 @@
|
||||
'use client'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
|
||||
export interface Enquiry {
|
||||
name?: string
|
||||
phone?: string
|
||||
email?: string
|
||||
message?: string
|
||||
}
|
||||
export type EnquiryChannel = 'whatsapp' | 'email'
|
||||
export interface EnquiryResult {
|
||||
ok: boolean
|
||||
message: string
|
||||
}
|
||||
|
||||
/** The message as it will appear to her, whichever way it is sent. */
|
||||
export function enquiryText(businessName: string | null, e: Enquiry): string {
|
||||
const name = (e.name ?? '').trim()
|
||||
const phone = (e.phone ?? '').trim()
|
||||
const email = (e.email ?? '').trim()
|
||||
const greeting = 'Hi' + (businessName ? ' ' + businessName : '') + (name ? ', this is ' + name + '.' : '.')
|
||||
return [greeting, (e.message ?? '').trim(), phone ? 'My number: ' + phone : '', email ? 'My email: ' + email : '']
|
||||
.filter(Boolean)
|
||||
.join('\n\n')
|
||||
}
|
||||
|
||||
/**
|
||||
* What the visitor is told afterwards. Written here rather than in each form,
|
||||
* so no form can say a message arrived when it has not.
|
||||
*/
|
||||
export function resultMessage(via: EnquiryChannel, ok: boolean, businessName: string | null): string {
|
||||
if (!ok) return 'Your message could not be sent. Please try again, or contact us another way.'
|
||||
if (via === 'whatsapp') return "WhatsApp has opened with your message. Press send and we'll reply there."
|
||||
return businessName
|
||||
? 'Your message has been sent to ' + businessName + '. They will reply to you directly.'
|
||||
: 'Your message has been sent. We will reply to you directly.'
|
||||
}
|
||||
|
||||
/** Her number in international digits, with the message typed in. */
|
||||
export function whatsAppUrl(number: string, text: string): string {
|
||||
return 'https://wa.me/' + number + '?text=' + encodeURIComponent(text)
|
||||
}
|
||||
|
||||
/** Phones first offer WhatsApp; laptops first offer email. Only what is switched on. */
|
||||
export function channelsFor(on: { whatsapp: boolean; email: boolean }, onPhone: boolean): EnquiryChannel[] {
|
||||
const order: EnquiryChannel[] = onPhone ? ['whatsapp', 'email'] : ['email', 'whatsapp']
|
||||
return order.filter((c) => on[c])
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends an enquiry to the business, the ways this site can: WhatsApp opened on
|
||||
* the visitor's device with the message typed in, or email sent by the server
|
||||
* when the business has it switched on.
|
||||
*
|
||||
* `available` is false when neither is on. Do not show a form then — a form
|
||||
* that cannot reach her is worse than none.
|
||||
*
|
||||
* `channels` is what to offer, in order. On a laptop both are offered: whether
|
||||
* WhatsApp is linked to that computer cannot be known from here.
|
||||
*
|
||||
* `send` resolves to what happened, with the words to show. WhatsApp "ok"
|
||||
* means it OPENED — the visitor still presses send, and the message says so.
|
||||
*/
|
||||
export function useEnquiry() {
|
||||
const [whatsapp, setWhatsapp] = useState<string | null>(null)
|
||||
const [email, setEmail] = useState(false)
|
||||
const [businessName, setBusinessName] = useState<string | null>(null)
|
||||
const [onPhone, setOnPhone] = useState(true)
|
||||
const [loading, setLoading] = useState(true)
|
||||
const opened = useRef(0)
|
||||
|
||||
useEffect(() => {
|
||||
// A touch-first screen is a phone or a tablet, where WhatsApp lives.
|
||||
setOnPhone(window.matchMedia('(pointer: coarse)').matches)
|
||||
const base = process.env.NEXT_PUBLIC_VULA_COMMERCE_URL
|
||||
const id = process.env.NEXT_PUBLIC_VULA_PROJECT_ID
|
||||
if (!base || !id) { setLoading(false); return }
|
||||
let live = true
|
||||
fetch(base + '/api/public/profile/' + id)
|
||||
.then((r) => (r.ok ? r.json() : null))
|
||||
.then((d) => {
|
||||
const p = d?.profile
|
||||
if (!live || !p) return
|
||||
setWhatsapp(p.enquiry?.whatsapp === false ? null : p.whatsapp || null)
|
||||
setEmail(p.enquiry?.email === true)
|
||||
setBusinessName(p.businessName || null)
|
||||
})
|
||||
.catch(() => { /* nothing to send through means no form, which is where we already were */ })
|
||||
.finally(() => { if (live) setLoading(false) })
|
||||
return () => { live = false }
|
||||
}, [])
|
||||
|
||||
const channels = channelsFor({ whatsapp: !!whatsapp, email }, onPhone)
|
||||
|
||||
async function send(e: Enquiry, via: EnquiryChannel = channels[0]): Promise<EnquiryResult> {
|
||||
if (via === 'whatsapp') {
|
||||
if (!whatsapp) return { ok: false, message: resultMessage(via, false, businessName) }
|
||||
// Opened before anything is awaited. A browser only lets a page open a
|
||||
// window in direct response to a tap, and the first await ends that.
|
||||
// A second tap within a few seconds is the same enquiry: WhatsApp adds a
|
||||
// link's text to an unsent draft, so opening it twice doubles the message.
|
||||
const now = Date.now()
|
||||
if (now - opened.current >= 5000) {
|
||||
opened.current = now
|
||||
window.open(whatsAppUrl(whatsapp, enquiryText(businessName, e)), '_blank', 'noopener,noreferrer')
|
||||
}
|
||||
return { ok: true, message: resultMessage(via, true, businessName) }
|
||||
}
|
||||
|
||||
if (!email) return { ok: false, message: resultMessage(via, false, businessName) }
|
||||
try {
|
||||
const base = process.env.NEXT_PUBLIC_VULA_COMMERCE_URL
|
||||
const id = process.env.NEXT_PUBLIC_VULA_PROJECT_ID
|
||||
const r = await fetch(base + '/api/public/enquiry/' + id, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(e),
|
||||
})
|
||||
const d = r.ok ? await r.json() : null
|
||||
// Sent only when the server says it sent. A reply that is merely "ok"
|
||||
// is not the same claim, and the visitor is about to be told.
|
||||
const sent = d?.sent === true
|
||||
return { ok: sent, message: resultMessage(via, sent, businessName) }
|
||||
} catch {
|
||||
return { ok: false, message: resultMessage(via, false, businessName) }
|
||||
}
|
||||
}
|
||||
|
||||
return { available: !loading && channels.length > 0, loading, channels, send }
|
||||
}
|
||||
Reference in New Issue
Block a user