- Early-bird pricing: RegistrationOption now tracks each purchase as a separate price tranche instead of overwriting a single price/quantity on repeat purchases, so buying more tickets after a tier expires no longer re-prices tickets already bought at the old price. Stock-limit checks, total-due calculation, and the Finance report's revenue-by- option are all tranche-aware; pages that showed one blended price per line now render/total each tranche. Viewing a pending/partially-paid registration (dashboard, detail page, or an event's registration list) now refreshes stale pricing on the spot instead of only at payment time. - Fixed the "(early bird)" dashboard label incorrectly firing on any line priced below the base option price (e.g. a plain cheaper variant) — it now checks the real applied-tier flag. - Added contact-only events (e.g. baptism): no registration/payment flow, shown on the public site with a "Contact us" popup instead of a Register button. Configurable via the admin event wizard. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
73 lines
2.6 KiB
TypeScript
73 lines
2.6 KiB
TypeScript
"use client";
|
|
|
|
import { useState } from "react";
|
|
import { X, Phone, Mail, User } from "lucide-react";
|
|
|
|
type ContactButtonProps = {
|
|
contactName?: string | null;
|
|
contactPhone?: string | null;
|
|
contactEmail?: string | null;
|
|
className?: string;
|
|
label?: string;
|
|
};
|
|
|
|
export function ContactButton({ contactName, contactPhone, contactEmail, className, label = "Contact us" }: ContactButtonProps) {
|
|
const [open, setOpen] = useState(false);
|
|
const hasDetails = !!(contactName || contactPhone || contactEmail);
|
|
|
|
return (
|
|
<>
|
|
<button
|
|
type="button"
|
|
onClick={() => setOpen(true)}
|
|
className={className || "flex-1 text-center text-sm text-white bg-brand-600 rounded-lg py-2 hover:bg-brand-700 transition-colors"}
|
|
>
|
|
{label}
|
|
</button>
|
|
{open && (
|
|
<div
|
|
className="fixed inset-0 bg-black/40 flex items-center justify-center z-50"
|
|
onClick={() => setOpen(false)}
|
|
>
|
|
<div className="bg-white rounded-lg shadow-lg max-w-sm w-full mx-4 p-5" onClick={e => e.stopPropagation()}>
|
|
<div className="flex items-center justify-between mb-3">
|
|
<h3 className="text-lg font-semibold">Contact us</h3>
|
|
<button
|
|
className="p-1 rounded hover:bg-gray-100 text-gray-500"
|
|
onClick={() => setOpen(false)}
|
|
aria-label="Close"
|
|
>
|
|
<X className="w-4 h-4" />
|
|
</button>
|
|
</div>
|
|
{hasDetails ? (
|
|
<div className="space-y-2 text-sm">
|
|
{contactName && (
|
|
<div className="flex items-center gap-2 text-gray-800">
|
|
<User className="w-4 h-4 text-gray-400 shrink-0" />
|
|
<span>{contactName}</span>
|
|
</div>
|
|
)}
|
|
{contactPhone && (
|
|
<div className="flex items-center gap-2">
|
|
<Phone className="w-4 h-4 text-gray-400 shrink-0" />
|
|
<a href={`tel:${contactPhone}`} className="text-brand-600 hover:underline">{contactPhone}</a>
|
|
</div>
|
|
)}
|
|
{contactEmail && (
|
|
<div className="flex items-center gap-2">
|
|
<Mail className="w-4 h-4 text-gray-400 shrink-0" />
|
|
<a href={`mailto:${contactEmail}`} className="text-brand-600 hover:underline">{contactEmail}</a>
|
|
</div>
|
|
)}
|
|
</div>
|
|
) : (
|
|
<p className="text-sm text-gray-500">No contact details have been provided for this event.</p>
|
|
)}
|
|
</div>
|
|
</div>
|
|
)}
|
|
</>
|
|
);
|
|
}
|