feat: premium UI overhaul, AI suggestions, WAHA WhatsApp integration
PREMIUM UI: - All animations: fade-up, scale-in, stagger children, confetti celebration - Glass effects, gradient icons, premium card hover states - Custom CSS: shimmer, pulse-ring, bounce, counter-roll animations - Smooth progress bar with gradient AI-POWERED (GPT-4o-mini nano model): - Smart amount suggestions based on peer data (/api/ai/suggest) - Social proof: '42 people pledged · Average £85' - AI-generated nudge text for conversion - AI fuzzy matching for bank reconciliation - AI reminder message generation WAHA WHATSAPP INTEGRATION: - Auto-send pledge receipt with bank details via WhatsApp - 4-step reminder sequence: gentle → nudge → urgent → final - Chatbot: donors reply PAID, HELP, CANCEL, STATUS - Volunteer notification on new pledges - WhatsApp status in dashboard settings - Webhook endpoint for incoming messages DONOR FLOW (CRO): - Amount step: AI suggestions, Gift Aid preview, social proof, haptic feedback - Payment step: trust signals, fee comparison, benefit badges - Identity step: email/phone toggle, WhatsApp reminder indicator - Bank instructions: tap-to-copy each field, WhatsApp delivery confirmation - Confirmation: confetti, pulse animation, share CTA, WhatsApp receipt COMPOSE: - Added WAHA env vars + qc-comms network for WhatsApp access
This commit is contained in:
@@ -1,10 +1,8 @@
|
||||
"use client"
|
||||
|
||||
import { useState } from "react"
|
||||
import { useState, useRef, useEffect } from "react"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import { Label } from "@/components/ui/label"
|
||||
import { Gift, Shield } from "lucide-react"
|
||||
import { Gift, Shield, Sparkles, Phone, Mail } from "lucide-react"
|
||||
|
||||
interface Props {
|
||||
onSubmit: (data: {
|
||||
@@ -22,10 +20,15 @@ export function IdentityStep({ onSubmit, amount }: Props) {
|
||||
const [phone, setPhone] = useState("")
|
||||
const [giftAid, setGiftAid] = useState(false)
|
||||
const [submitting, setSubmitting] = useState(false)
|
||||
const [contactMode, setContactMode] = useState<"email" | "phone">("email")
|
||||
const nameRef = useRef<HTMLInputElement>(null)
|
||||
|
||||
const hasContact = email.includes("@") || phone.length >= 10
|
||||
useEffect(() => { nameRef.current?.focus() }, [])
|
||||
|
||||
const hasContact = contactMode === "email" ? email.includes("@") : phone.length >= 10
|
||||
const isValid = hasContact
|
||||
const giftAidBonus = Math.round(amount * 0.25)
|
||||
const totalWithAid = amount + giftAidBonus
|
||||
|
||||
const handleSubmit = async () => {
|
||||
if (!isValid) return
|
||||
@@ -38,123 +41,168 @@ export function IdentityStep({ onSubmit, amount }: Props) {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="max-w-md mx-auto pt-4 space-y-6">
|
||||
<div className="max-w-md mx-auto pt-2 space-y-5 animate-fade-up">
|
||||
<div className="text-center space-y-2">
|
||||
<h1 className="text-2xl font-extrabold text-gray-900">
|
||||
<h1 className="text-2xl font-black text-gray-900 tracking-tight">
|
||||
Almost there!
|
||||
</h1>
|
||||
<p className="text-muted-foreground">
|
||||
We need a way to send you payment details
|
||||
<p className="text-muted-foreground text-sm">
|
||||
We just need a way to send you payment details
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="name">Full Name <span className="text-muted-foreground font-normal">(for Gift Aid)</span></Label>
|
||||
<Input
|
||||
id="name"
|
||||
placeholder="Your full name"
|
||||
{/* Minimal form */}
|
||||
<div className="space-y-3">
|
||||
{/* Name */}
|
||||
<div className="relative">
|
||||
<input
|
||||
ref={nameRef}
|
||||
type="text"
|
||||
placeholder="Your name"
|
||||
value={name}
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
autoComplete="name"
|
||||
className="w-full h-14 px-4 rounded-2xl border-2 border-gray-200 bg-white text-base font-medium placeholder:text-gray-300 focus:border-trust-blue focus:ring-4 focus:ring-trust-blue/10 outline-none transition-all"
|
||||
/>
|
||||
{name && (
|
||||
<div className="absolute right-3 top-1/2 -translate-y-1/2 text-success-green animate-scale-in">✓</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="email">Email</Label>
|
||||
<Input
|
||||
id="email"
|
||||
type="email"
|
||||
placeholder="your@email.com"
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
autoComplete="email"
|
||||
inputMode="email"
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
We'll send your payment instructions and receipt here
|
||||
</p>
|
||||
{/* Contact mode toggle */}
|
||||
<div className="flex rounded-xl bg-gray-100 p-1">
|
||||
<button
|
||||
onClick={() => setContactMode("email")}
|
||||
className={`flex-1 flex items-center justify-center gap-2 py-2.5 rounded-lg text-sm font-medium transition-all ${
|
||||
contactMode === "email" ? "bg-white shadow-sm text-gray-900" : "text-gray-500"
|
||||
}`}
|
||||
>
|
||||
<Mail className="h-4 w-4" /> Email
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setContactMode("phone")}
|
||||
className={`flex-1 flex items-center justify-center gap-2 py-2.5 rounded-lg text-sm font-medium transition-all ${
|
||||
contactMode === "phone" ? "bg-white shadow-sm text-gray-900" : "text-gray-500"
|
||||
}`}
|
||||
>
|
||||
<Phone className="h-4 w-4" /> Mobile
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="relative flex items-center">
|
||||
<div className="flex-grow border-t border-gray-200" />
|
||||
<span className="flex-shrink mx-3 text-xs text-muted-foreground">or</span>
|
||||
<div className="flex-grow border-t border-gray-200" />
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="phone">Mobile Number</Label>
|
||||
<Input
|
||||
id="phone"
|
||||
type="tel"
|
||||
placeholder="07700 900000"
|
||||
value={phone}
|
||||
onChange={(e) => setPhone(e.target.value)}
|
||||
autoComplete="tel"
|
||||
inputMode="tel"
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
We can send reminders via SMS if you prefer
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Gift Aid — prominent UK-specific */}
|
||||
<div
|
||||
onClick={() => setGiftAid(!giftAid)}
|
||||
className={`rounded-2xl border-2 p-5 cursor-pointer transition-all ${
|
||||
giftAid
|
||||
? "border-success-green bg-success-green/5 shadow-md shadow-success-green/10"
|
||||
: "border-gray-200 bg-white hover:border-success-green/50"
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-start gap-4">
|
||||
<div className={`rounded-xl p-2.5 ${giftAid ? "bg-success-green/10" : "bg-gray-100"}`}>
|
||||
<Gift className={`h-6 w-6 ${giftAid ? "text-success-green" : "text-gray-400"}`} />
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<div className="flex items-center gap-2">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={giftAid}
|
||||
onChange={() => {}}
|
||||
className="h-5 w-5 rounded border-gray-300 text-success-green focus:ring-success-green"
|
||||
/>
|
||||
<span className="font-bold text-gray-900">Add Gift Aid</span>
|
||||
{giftAid && (
|
||||
<span className="text-xs font-bold px-2 py-0.5 rounded-full bg-success-green text-white">
|
||||
+£{(giftAidBonus / 100).toFixed(0)} free
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground mt-1">
|
||||
Boost your £{(amount / 100).toFixed(0)} pledge to{" "}
|
||||
<span className="font-bold text-success-green">£{((amount + giftAidBonus) / 100).toFixed(0)}</span> at no extra cost.
|
||||
HMRC adds 25% — the charity claims it back.
|
||||
</p>
|
||||
{giftAid && (
|
||||
<p className="text-xs text-muted-foreground mt-2 italic">
|
||||
I confirm I am a UK taxpayer and understand that if I pay less Income Tax and/or
|
||||
Capital Gains Tax than the amount of Gift Aid claimed on all my donations in that
|
||||
tax year it is my responsibility to pay any difference.
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
{/* Contact input */}
|
||||
{contactMode === "email" ? (
|
||||
<div className="relative animate-fade-in">
|
||||
<Mail className="absolute left-4 top-1/2 -translate-y-1/2 h-5 w-5 text-gray-300" />
|
||||
<input
|
||||
type="email"
|
||||
placeholder="your@email.com"
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
autoComplete="email"
|
||||
inputMode="email"
|
||||
className="w-full h-14 pl-12 pr-4 rounded-2xl border-2 border-gray-200 bg-white text-base font-medium placeholder:text-gray-300 focus:border-trust-blue focus:ring-4 focus:ring-trust-blue/10 outline-none transition-all"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="relative animate-fade-in">
|
||||
<Phone className="absolute left-4 top-1/2 -translate-y-1/2 h-5 w-5 text-gray-300" />
|
||||
<input
|
||||
type="tel"
|
||||
placeholder="07700 900 000"
|
||||
value={phone}
|
||||
onChange={(e) => setPhone(e.target.value)}
|
||||
autoComplete="tel"
|
||||
inputMode="tel"
|
||||
className="w-full h-14 pl-12 pr-4 rounded-2xl border-2 border-gray-200 bg-white text-base font-medium placeholder:text-gray-300 focus:border-trust-blue focus:ring-4 focus:ring-trust-blue/10 outline-none transition-all"
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground mt-1 ml-1">
|
||||
We'll send reminders via WhatsApp ✓
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Gift Aid — the hero */}
|
||||
<button
|
||||
onClick={() => setGiftAid(!giftAid)}
|
||||
className={`w-full text-left rounded-2xl border-2 p-5 transition-all duration-300 card-hover ${
|
||||
giftAid
|
||||
? "border-success-green bg-gradient-to-br from-success-green/5 to-emerald-50 shadow-lg shadow-success-green/10"
|
||||
: "border-gray-200 bg-white hover:border-success-green/40"
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-start gap-4">
|
||||
<div className={`rounded-xl p-3 transition-all ${giftAid ? "bg-success-green shadow-lg shadow-success-green/30" : "bg-gray-100"}`}>
|
||||
<Gift className={`h-6 w-6 transition-colors ${giftAid ? "text-white" : "text-gray-400"}`} />
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="font-bold text-gray-900">
|
||||
{giftAid ? "Gift Aid added!" : "Add Gift Aid"}
|
||||
</span>
|
||||
{giftAid ? (
|
||||
<span className="text-xs font-bold px-2.5 py-0.5 rounded-full bg-success-green text-white animate-scale-in flex items-center gap-1">
|
||||
<Sparkles className="h-3 w-3" /> +£{(giftAidBonus / 100).toFixed(0)} free
|
||||
</span>
|
||||
) : (
|
||||
<span className="text-xs font-medium px-2 py-0.5 rounded-full bg-success-green/10 text-success-green">
|
||||
+25%
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{giftAid ? (
|
||||
<div className="mt-2 space-y-2 animate-fade-in">
|
||||
<div className="flex items-center justify-between bg-white rounded-xl p-3 border border-success-green/20">
|
||||
<div>
|
||||
<p className="text-xs text-muted-foreground">Your pledge</p>
|
||||
<p className="font-bold">£{(amount / 100).toFixed(0)}</p>
|
||||
</div>
|
||||
<div className="text-success-green font-bold text-xl">+</div>
|
||||
<div>
|
||||
<p className="text-xs text-muted-foreground">HMRC adds</p>
|
||||
<p className="font-bold text-success-green">£{(giftAidBonus / 100).toFixed(0)}</p>
|
||||
</div>
|
||||
<div className="text-success-green font-bold text-xl">=</div>
|
||||
<div>
|
||||
<p className="text-xs text-muted-foreground">Charity gets</p>
|
||||
<p className="font-black text-success-green text-lg">£{(totalWithAid / 100).toFixed(0)}</p>
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-[11px] text-muted-foreground leading-relaxed">
|
||||
I confirm I am a UK taxpayer and understand that if I pay less Income Tax and/or Capital Gains Tax than the amount of Gift Aid claimed on all my donations in that tax year, it is my responsibility to pay any difference.
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<p className="text-sm text-muted-foreground mt-1">
|
||||
Boost your £{(amount / 100).toFixed(0)} to{" "}
|
||||
<span className="font-bold text-success-green">£{(totalWithAid / 100).toFixed(0)}</span> at zero cost. HMRC adds 25%.
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
{/* Submit */}
|
||||
<Button
|
||||
size="xl"
|
||||
className="w-full"
|
||||
className={`w-full transition-all duration-300 ${isValid ? "opacity-100" : "opacity-50"}`}
|
||||
disabled={!isValid || submitting}
|
||||
onClick={handleSubmit}
|
||||
>
|
||||
{submitting ? "Submitting..." : "Complete Pledge ✓"}
|
||||
{submitting ? (
|
||||
<span className="flex items-center gap-2">
|
||||
<span className="h-5 w-5 border-2 border-white border-t-transparent rounded-full animate-spin" />
|
||||
Submitting...
|
||||
</span>
|
||||
) : (
|
||||
"Complete Pledge ✓"
|
||||
)}
|
||||
</Button>
|
||||
|
||||
<div className="flex items-center justify-center gap-2 text-xs text-muted-foreground">
|
||||
<Shield className="h-3 w-3" />
|
||||
<span>Your data is kept secure and only used for this pledge</span>
|
||||
<span>Your data is encrypted and only used for this pledge</span>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user