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:
2026-03-05 17:58:33 +08:00
parent 50d449e2b7
commit 5c615ad35e
7 changed files with 80 additions and 34 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

View File

@@ -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,24 +589,53 @@ export default function CollectPage() {
{/* Quick add link inline */} {/* Quick add link inline */}
{showQuickAdd && ( {showQuickAdd && (
<div className="flex gap-2 items-end"> <div className="border-2 border-[#1E40AF] bg-[#1E40AF]/[0.02] p-4 space-y-3">
<div className="flex-1"> <div className="flex items-center justify-between">
<label className="text-[10px] font-bold text-gray-500 block mb-1"> <p className="text-sm font-bold text-[#111827]">Create a new pledge link</p>
New link for &quot;{activeEvent?.name}&quot; <button onClick={() => { setShowQuickAdd(false); setQuickLinkName(""); setQuickAddEventId(null) }}
</label> className="text-xs text-gray-400 hover:text-gray-600">Cancel</button>
<input </div>
value={quickLinkName} onChange={e => setQuickLinkName(e.target.value)}
placeholder="e.g. Table 5, Imam Yusuf, WhatsApp group" {/* Appeal picker — only shows when there are multiple appeals */}
autoFocus onKeyDown={e => e.key === "Enter" && handleQuickAdd()} {events.length > 1 && (
className="w-full h-10 px-3 border-2 border-gray-200 text-sm focus:border-[#1E40AF] outline-none" <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-1">
<label className="text-[10px] font-bold text-gray-500 block mb-1">
Link name
{events.length === 1 && activeEvent && (
<span className="text-gray-400 font-normal"> · {activeEvent.name}</span>
)}
</label>
<input
value={quickLinkName} onChange={e => setQuickLinkName(e.target.value)}
placeholder="e.g. Table 5, Imam Yusuf, WhatsApp group"
autoFocus onKeyDown={e => e.key === "Enter" && handleQuickAdd()}
className="w-full h-10 px-3 border-2 border-gray-200 text-sm focus:border-[#1E40AF] outline-none"
/>
</div>
<button onClick={handleQuickAdd} disabled={quickCreating || !quickLinkName.trim()}
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"}
</button>
</div> </div>
<button onClick={handleQuickAdd} disabled={quickCreating || !quickLinkName.trim()}
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"}
</button>
<button onClick={() => { setShowQuickAdd(false); setQuickLinkName("") }}
className="h-10 px-3 text-xs text-gray-400 hover:text-gray-600">Cancel</button>
</div> </div>
)} )}

View File

@@ -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&apos;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> <p className="text-xs text-gray-400 truncate">
<div className="flex items-center gap-3 mt-2.5"> <span className="text-white font-bold">Connect WhatsApp</span>
{" "}to send automatic payment reminders
</p>
</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>