fix: WhatsApp banner → thin midnight strip + Collect quick-add appeal picker
WhatsApp notice: - Was: Ugly amber warning box with margins, breaking layout flow - Now: Thin edge-to-edge midnight strip below header, same design language as the header itself. Green WhatsApp icon, 'Set up →' link, subtle × dismiss. Zero layout disruption. Collect quick-add: - Was: 'New link' always added to events[0] with no choice - Now: Multi-appeal orgs see appeal picker buttons. Single-appeal orgs see appeal name as subtle label. Quick-add panel has blue border treatment matching brand. - Removed unused AlertTriangle import
This commit is contained in:
BIN
pledge-now-pay-later/screenshots/pledge-form-amount.png
Normal file
BIN
pledge-now-pay-later/screenshots/pledge-form-amount.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 32 KiB |
BIN
pledge-now-pay-later/screenshots/pledge-match-expanded.png
Normal file
BIN
pledge-now-pay-later/screenshots/pledge-match-expanded.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 31 KiB |
BIN
pledge-now-pay-later/screenshots/pledge-match-funding.png
Normal file
BIN
pledge-now-pay-later/screenshots/pledge-match-funding.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 35 KiB |
BIN
pledge-now-pay-later/screenshots/pledge-match-options.png
Normal file
BIN
pledge-now-pay-later/screenshots/pledge-match-options.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 34 KiB |
BIN
pledge-now-pay-later/screenshots/pledge-match-toggle.png
Normal file
BIN
pledge-now-pay-later/screenshots/pledge-match-toggle.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 35 KiB |
@@ -113,7 +113,12 @@ export default function CollectPage() {
|
|||||||
}).catch(() => {}).finally(() => setLoading(false))
|
}).catch(() => {}).finally(() => setLoading(false))
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const activeEvent = events[0] // Most orgs have one appeal
|
// Quick-add: which appeal does the new link belong to?
|
||||||
|
// Single appeal → auto-select. Multiple → user picks.
|
||||||
|
const [quickAddEventId, setQuickAddEventId] = useState<string | null>(null)
|
||||||
|
const activeEvent = events.length === 1
|
||||||
|
? events[0]
|
||||||
|
: events.find(e => e.id === quickAddEventId) || events[0]
|
||||||
|
|
||||||
// ── Actions ──
|
// ── Actions ──
|
||||||
const copyLink = async (code: string) => {
|
const copyLink = async (code: string) => {
|
||||||
@@ -584,10 +589,40 @@ export default function CollectPage() {
|
|||||||
|
|
||||||
{/* Quick add link inline */}
|
{/* Quick add link inline */}
|
||||||
{showQuickAdd && (
|
{showQuickAdd && (
|
||||||
|
<div className="border-2 border-[#1E40AF] bg-[#1E40AF]/[0.02] p-4 space-y-3">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<p className="text-sm font-bold text-[#111827]">Create a new pledge link</p>
|
||||||
|
<button onClick={() => { setShowQuickAdd(false); setQuickLinkName(""); setQuickAddEventId(null) }}
|
||||||
|
className="text-xs text-gray-400 hover:text-gray-600">Cancel</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Appeal picker — only shows when there are multiple appeals */}
|
||||||
|
{events.length > 1 && (
|
||||||
|
<div>
|
||||||
|
<label className="text-[10px] font-bold text-gray-500 block mb-1.5">Which appeal is this for?</label>
|
||||||
|
<div className="flex flex-wrap gap-2">
|
||||||
|
{events.map(ev => (
|
||||||
|
<button key={ev.id}
|
||||||
|
onClick={() => setQuickAddEventId(ev.id)}
|
||||||
|
className={`px-3 py-1.5 text-xs font-bold border-2 transition-colors ${
|
||||||
|
activeEvent?.id === ev.id
|
||||||
|
? "border-[#1E40AF] bg-[#1E40AF]/5 text-[#1E40AF]"
|
||||||
|
: "border-gray-200 text-gray-500 hover:border-gray-300"
|
||||||
|
}`}>
|
||||||
|
{ev.name}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
<div className="flex gap-2 items-end">
|
<div className="flex gap-2 items-end">
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<label className="text-[10px] font-bold text-gray-500 block mb-1">
|
<label className="text-[10px] font-bold text-gray-500 block mb-1">
|
||||||
New link for "{activeEvent?.name}"
|
Link name
|
||||||
|
{events.length === 1 && activeEvent && (
|
||||||
|
<span className="text-gray-400 font-normal"> · {activeEvent.name}</span>
|
||||||
|
)}
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
value={quickLinkName} onChange={e => setQuickLinkName(e.target.value)}
|
value={quickLinkName} onChange={e => setQuickLinkName(e.target.value)}
|
||||||
@@ -600,8 +635,7 @@ export default function CollectPage() {
|
|||||||
className="h-10 bg-[#1E40AF] px-4 text-xs font-bold text-white disabled:opacity-40">
|
className="h-10 bg-[#1E40AF] px-4 text-xs font-bold text-white disabled:opacity-40">
|
||||||
{quickCreating ? <Loader2 className="h-3.5 w-3.5 animate-spin" /> : "Create"}
|
{quickCreating ? <Loader2 className="h-3.5 w-3.5 animate-spin" /> : "Create"}
|
||||||
</button>
|
</button>
|
||||||
<button onClick={() => { setShowQuickAdd(false); setQuickLinkName("") }}
|
</div>
|
||||||
className="h-10 px-3 text-xs text-gray-400 hover:text-gray-600">Cancel</button>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import Link from "next/link"
|
|||||||
import { usePathname } from "next/navigation"
|
import { usePathname } from "next/navigation"
|
||||||
import { useSession, signOut } from "next-auth/react"
|
import { useSession, signOut } from "next-auth/react"
|
||||||
import { useState, useEffect } from "react"
|
import { useState, useEffect } from "react"
|
||||||
import { Home, Megaphone, Banknote, FileText, Settings, LogOut, Shield, AlertTriangle, MessageCircle, Users, Zap } from "lucide-react"
|
import { Home, Megaphone, Banknote, FileText, Settings, LogOut, Shield, MessageCircle, Users, Zap } from "lucide-react"
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -157,7 +157,7 @@ export default function DashboardLayout({ children }: { children: React.ReactNod
|
|||||||
|
|
||||||
{/* Main content — white background, generous padding */}
|
{/* Main content — white background, generous padding */}
|
||||||
<main className="flex-1 pb-20 md:pb-8 overflow-hidden">
|
<main className="flex-1 pb-20 md:pb-8 overflow-hidden">
|
||||||
<WhatsAppBanner />
|
<WhatsAppNotice />
|
||||||
{children}
|
{children}
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
@@ -165,8 +165,12 @@ export default function DashboardLayout({ children }: { children: React.ReactNod
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** WhatsApp connection banner — shows until connected */
|
/**
|
||||||
function WhatsAppBanner() {
|
* WhatsApp connection notice — thin, edge-to-edge, midnight-brand.
|
||||||
|
* Sits flush below the header. Not a warning — just a next step.
|
||||||
|
* Same design language as the header itself.
|
||||||
|
*/
|
||||||
|
function WhatsAppNotice() {
|
||||||
const [status, setStatus] = useState<string | null>(null)
|
const [status, setStatus] = useState<string | null>(null)
|
||||||
const [dismissed, setDismissed] = useState(false)
|
const [dismissed, setDismissed] = useState(false)
|
||||||
const pathname = usePathname()
|
const pathname = usePathname()
|
||||||
@@ -182,22 +186,30 @@ function WhatsAppBanner() {
|
|||||||
if (status === "CONNECTED" || status === "skip" || status === null || dismissed) return null
|
if (status === "CONNECTED" || status === "skip" || status === null || dismissed) return null
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mx-4 md:mx-6 lg:mx-8 mt-4 md:mt-6 lg:mt-8 border-l-2 border-[#F59E0B] bg-[#F59E0B]/5 p-4 flex items-start gap-3">
|
<div className="bg-[#111827] border-b border-gray-800">
|
||||||
<AlertTriangle className="h-5 w-5 text-[#F59E0B] shrink-0 mt-0.5" />
|
<div className="flex items-center justify-between px-4 md:px-6 lg:px-8 py-2.5">
|
||||||
<div className="flex-1 min-w-0">
|
<div className="flex items-center gap-2.5 min-w-0">
|
||||||
<p className="text-sm font-bold text-[#111827]">WhatsApp not connected — reminders won't send</p>
|
<div className="h-5 w-5 bg-[#25D366] flex items-center justify-center shrink-0">
|
||||||
<p className="text-xs text-gray-600 mt-0.5">
|
<MessageCircle className="h-3 w-3 text-white" />
|
||||||
Connect your WhatsApp so donors automatically get payment reminders.
|
</div>
|
||||||
|
<p className="text-xs text-gray-400 truncate">
|
||||||
|
<span className="text-white font-bold">Connect WhatsApp</span>
|
||||||
|
{" "}to send automatic payment reminders
|
||||||
</p>
|
</p>
|
||||||
<div className="flex items-center gap-3 mt-2.5">
|
</div>
|
||||||
|
<div className="flex items-center gap-3 shrink-0 ml-4">
|
||||||
<Link
|
<Link
|
||||||
href="/dashboard/settings"
|
href="/dashboard/settings"
|
||||||
className="inline-flex items-center gap-1.5 bg-[#25D366] px-3 py-1.5 text-xs font-bold text-white hover:bg-[#25D366]/90 transition-colors"
|
className="text-xs font-bold text-[#25D366] hover:text-[#25D366]/80 transition-colors"
|
||||||
>
|
>
|
||||||
<MessageCircle className="h-3.5 w-3.5" /> Connect WhatsApp
|
Set up →
|
||||||
</Link>
|
</Link>
|
||||||
<button onClick={() => setDismissed(true)} className="text-xs text-gray-400 hover:text-gray-600">
|
<button
|
||||||
Dismiss
|
onClick={() => setDismissed(true)}
|
||||||
|
className="text-gray-600 hover:text-gray-400 transition-colors"
|
||||||
|
aria-label="Dismiss"
|
||||||
|
>
|
||||||
|
<svg className="h-3.5 w-3.5" viewBox="0 0 14 14" fill="none"><path d="M1 1l12 12M13 1L1 13" stroke="currentColor" strokeWidth="2" strokeLinecap="round"/></svg>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user