Surgical photography: continuous brand experience from landing page to dashboard

ONE image, surgically placed.

'digital-03-notification-smile.jpg' — young man at a London bus
stop smiling at his phone. The moment a WhatsApp reminder lands
and he thinks 'oh right, I need to do that.'

This IS the product working. Not decoration — context.

Uses the same image-panel + dark-panel split from the landing page:
┌──────────────┬──────────────────────────────┐
│ [photo]      │   AI optimisation           │
│ Man smiling  │  Let AI improve your messages │
│ at phone     │  [Start optimising]           │
└──────────────┴──────────────────────────────┘

The image only appears in the onboarding state (never optimised).
Once AI is running, the hero compacts to a single dark bar.
The image served its purpose — motivation to start.

Brand alignment:
- Left-border accent (generosity-gold) on header
- 11px uppercase tracking-[0.15em] labels
- gap-px grid for timing controls
- Sharp edges everywhere (phone mockup is the only exception)
- 60-30-10 color rule maintained
- Dark inversion for AI hero sections
- Typography-driven hierarchy

No new images generated. Used existing brand photography.
This commit is contained in:
2026-03-05 01:58:17 +08:00
parent 7f347260c5
commit b2cfdff959

View File

@@ -1,6 +1,7 @@
"use client" "use client"
import { useState, useEffect, useCallback, useRef } from "react" import { useState, useEffect, useCallback, useRef } from "react"
import Image from "next/image"
import { import {
Loader2, Check, Send, Sparkles, Trophy, CheckCheck, Loader2, Check, Send, Sparkles, Trophy, CheckCheck,
ChevronDown, Clock, MessageCircle ChevronDown, Clock, MessageCircle
@@ -11,23 +12,16 @@ import { resolvePreview, STEP_META } from "@/lib/templates"
/** /**
* /dashboard/automations * /dashboard/automations
* *
* AI DOES THE WORK. * AI DOES THE WORK. PHOTOGRAPHY SETS THE CONTEXT.
* *
* The page has three states: * The AI hero uses the same image-panel + dark-panel split from
* the landing page. The image is `digital-03-notification-smile` —
* a young man at a bus stop smiling at his phone. It IS the product
* working. The moment a WhatsApp reminder lands and someone thinks
* "oh right, I need to do that."
* *
* 1. NOT STARTED — big hero: "Let AI improve your messages" * Once AI is running, the hero compacts down — the image served its
* One button. AI generates challengers for all 4 steps. * purpose (motivation to start). Now the data takes over.
*
* 2. TESTING — "AI is testing 4 experiments"
* Each message shows your version vs AI's version with live stats.
* Progress bar toward verdict.
*
* 3. WINNERS — "AI improved your messages by 47%"
* Messages marked with 🏆 badges showing the lift.
* "Run another round" to keep improving.
*
* Aaisha never writes a message. She never picks a winner.
* She just sees: "AI is making your messages better."
*/ */
interface Template { interface Template {
@@ -81,18 +75,14 @@ export default function AutomationsPage() {
templates.find(t => t.step === step && t.channel === "whatsapp" && t.variant === variant) templates.find(t => t.step === step && t.channel === "whatsapp" && t.variant === variant)
|| templates.find(t => t.step === step && t.variant === variant) || templates.find(t => t.step === step && t.variant === variant)
// ── Derived state ──────────────────────
const testsRunning = STEP_META.filter((_, i) => !!tpl(i, "B")).length const testsRunning = STEP_META.filter((_, i) => !!tpl(i, "B")).length
const stepsWithoutTest = STEP_META.filter((_, i) => !tpl(i, "B")).length const stepsWithoutTest = STEP_META.filter((_, i) => !tpl(i, "B")).length
const neverOptimised = testsRunning === 0 && templates.every(t => t.variant === "A") const neverOptimised = testsRunning === 0 && templates.every(t => t.variant === "A")
// ── Actions ────────────────────────────
const optimiseAll = async () => { const optimiseAll = async () => {
setAiWorking(true) setAiWorking(true)
// Generate challengers for all steps that don't have one
for (let step = 0; step < 4; step++) { for (let step = 0; step < 4; step++) {
if (tpl(step, "B")) continue // already has a test if (tpl(step, "B")) continue
try { try {
await fetch("/api/automations/ai", { await fetch("/api/automations/ai", {
method: "POST", headers: { "Content-Type": "application/json" }, method: "POST", headers: { "Content-Type": "application/json" },
@@ -153,46 +143,71 @@ export default function AutomationsPage() {
const delays = [0, config?.step1Delay || 2, config?.step2Delay || 7, config?.step3Delay || 14] const delays = [0, config?.step1Delay || 2, config?.step2Delay || 7, config?.step3Delay || 14]
return ( return (
<div className="max-w-lg mx-auto space-y-5"> <div className="space-y-6">
{/* Header */} {/* ── Header ── */}
<div> <div>
<h1 className="text-2xl font-black text-[#111827] tracking-tight">What your donors receive</h1> <div className="border-l-2 border-[#F59E0B] pl-3 mb-3">
<p className="text-xs text-gray-500 mt-1">4 messages over {delays[3]} days. Click any to edit.</p> <p className="text-[11px] font-semibold tracking-[0.15em] uppercase text-gray-500">Automations</p>
</div>
<h1 className="text-3xl md:text-4xl font-black text-[#111827] tracking-tight">
What your donors receive
</h1>
<p className="text-sm text-gray-500 mt-1">
4 messages over {delays[3]} days. Click any to edit.
</p>
</div> </div>
{/* WhatsApp status */} {/* ── WhatsApp status ── */}
{!waConnected && ( {!waConnected && (
<div className="border-l-2 border-[#F59E0B] bg-[#FEF3C7] px-4 py-3"> <div className="border-l-2 border-[#F59E0B] pl-4 py-2">
<p className="text-xs text-[#111827]"><strong>WhatsApp not connected.</strong> Messages start sending once you <Link href="/dashboard/settings" className="text-[#1E40AF] font-bold underline">connect WhatsApp</Link>.</p> <p className="text-sm text-gray-600"><strong className="text-[#111827]">WhatsApp not connected.</strong> Messages start once you <Link href="/dashboard/settings" className="text-[#1E40AF] font-bold hover:underline">connect WhatsApp</Link>.</p>
</div> </div>
)} )}
{/* ── AI HERO — the main CTA ── */} {/* ━━ AI HERO ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Same image-panel + dark-panel split from the landing page.
The photo is the PRODUCT WORKING — a real person receiving
a WhatsApp reminder and acting on it.
Once AI is running, the hero compacts. The image did its job.
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ */}
{neverOptimised ? ( {neverOptimised ? (
/* State 1: Never optimised */ <div className="grid md:grid-cols-5 gap-0">
<div className="bg-[#111827] p-6"> {/* Photo — the moment a reminder lands */}
<div className="flex items-start gap-3"> <div className="md:col-span-2 relative min-h-[200px] md:min-h-[280px] overflow-hidden">
<Sparkles className="h-5 w-5 text-[#60A5FA] mt-0.5 shrink-0" /> <Image
<div> src="/images/brand/digital-03-notification-smile.jpg"
<p className="text-sm font-bold text-white">Let AI improve your messages</p> alt="Young man at a London bus stop smiling at his phone — the moment a gentle WhatsApp reminder lands"
<p className="text-xs text-gray-400 mt-1 leading-relaxed"> fill
className="object-cover"
sizes="(max-width: 768px) 100vw, 40vw"
/>
</div>
{/* Dark panel — CTA */}
<div className="md:col-span-3 bg-[#111827] p-8 md:p-10 flex flex-col justify-center">
<div className="flex items-center gap-2 mb-4">
<Sparkles className="h-4 w-4 text-[#60A5FA]" />
<p className="text-[11px] font-semibold tracking-[0.15em] uppercase text-gray-500">AI optimisation</p>
</div>
<h2 className="text-2xl md:text-3xl font-black text-white tracking-tight">
Let AI improve your&nbsp;messages
</h2>
<p className="text-sm text-gray-400 leading-relaxed mt-3 max-w-md">
AI writes a different version of each message and tests both with real donors. AI writes a different version of each message and tests both with real donors.
After enough responses, the better version wins automatically. The better version wins automatically. Your messages get better over time without you doing anything.
Your messages get better over time without you doing anything.
</p> </p>
</div>
</div>
<button onClick={optimiseAll} disabled={aiWorking} <button onClick={optimiseAll} disabled={aiWorking}
className="mt-4 w-full bg-white text-[#111827] py-3 text-sm font-bold flex items-center justify-center gap-2 hover:bg-gray-100 transition-colors disabled:opacity-60"> className="mt-6 inline-flex items-center justify-center bg-white px-6 py-3 text-sm font-bold text-[#111827] hover:bg-gray-100 transition-colors self-start disabled:opacity-60">
{aiWorking {aiWorking
? <><Loader2 className="h-4 w-4 animate-spin" /> AI is writing new versions</> ? <><Loader2 className="h-4 w-4 animate-spin mr-2" /> AI is writing new versions</>
: <><Sparkles className="h-4 w-4" /> Start optimising</> : <><Sparkles className="h-4 w-4 mr-2" /> Start optimising</>
} }
</button> </button>
<p className="text-[11px] text-gray-500 mt-3">Uses GPT-4.1 nano · Costs less than 1p per message</p>
</div>
</div> </div>
) : testsRunning > 0 ? ( ) : testsRunning > 0 ? (
/* State 2: Tests running */ /* ── Compact hero: tests running ── */
<div className="bg-[#111827] p-5 flex items-center gap-4"> <div className="bg-[#111827] p-5 flex items-center gap-4">
<div className="relative shrink-0"> <div className="relative shrink-0">
<Sparkles className="h-5 w-5 text-[#60A5FA]" /> <Sparkles className="h-5 w-5 text-[#60A5FA]" />
@@ -200,38 +215,37 @@ export default function AutomationsPage() {
</div> </div>
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">
<p className="text-sm font-bold text-white">AI is testing {testsRunning} experiment{testsRunning > 1 ? "s" : ""}</p> <p className="text-sm font-bold text-white">AI is testing {testsRunning} experiment{testsRunning > 1 ? "s" : ""}</p>
<p className="text-[10px] text-gray-400 mt-0.5"> <p className="text-[11px] text-gray-500 mt-0.5">Each message has two versions. The better one wins automatically.</p>
Each message has two versions. The better one wins automatically.
</p>
</div> </div>
{stepsWithoutTest > 0 && ( {stepsWithoutTest > 0 && (
<button onClick={optimiseAll} disabled={aiWorking} <button onClick={optimiseAll} disabled={aiWorking}
className="shrink-0 bg-white/10 text-white px-3 py-1.5 text-[10px] font-bold hover:bg-white/20 transition-colors disabled:opacity-50"> className="shrink-0 border border-gray-600 text-gray-300 px-3 py-1.5 text-[11px] font-bold hover:text-white hover:border-white transition-colors disabled:opacity-50">
{aiWorking ? <Loader2 className="h-3 w-3 animate-spin" /> : `+ ${stepsWithoutTest} more`} {aiWorking ? <Loader2 className="h-3 w-3 animate-spin" /> : `+ ${stepsWithoutTest} more`}
</button> </button>
)} )}
</div> </div>
) : ( ) : (
/* State 3: All tests resolved (or manually cleared) */ /* ── Compact hero: optimised ── */
<div className="bg-[#111827] p-5 flex items-center gap-4"> <div className="bg-[#111827] p-5 flex items-center gap-4">
<Trophy className="h-5 w-5 text-[#4ADE80] shrink-0" /> <Trophy className="h-5 w-5 text-[#16A34A] shrink-0" />
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">
<p className="text-sm font-bold text-white">Messages optimised</p> <p className="text-sm font-bold text-white">Messages optimised</p>
<p className="text-[10px] text-gray-400 mt-0.5"> <p className="text-[11px] text-gray-500 mt-0.5">
{stats && stats.total > 0 {stats && stats.total > 0 ? `${stats.total} sent this week · ${stats.deliveryRate}% delivered` : "Winning versions are live."}
? `${stats.total} sent this week · ${stats.deliveryRate}% delivered`
: "Winning versions are live. Run another round to keep improving."
}
</p> </p>
</div> </div>
<button onClick={optimiseAll} disabled={aiWorking} <button onClick={optimiseAll} disabled={aiWorking}
className="shrink-0 bg-white/10 text-white px-3 py-1.5 text-[10px] font-bold hover:bg-white/20 transition-colors disabled:opacity-50 flex items-center gap-1"> className="shrink-0 border border-gray-600 text-gray-300 px-3 py-1.5 text-[11px] font-bold hover:text-white hover:border-white transition-colors disabled:opacity-50 flex items-center gap-1.5">
{aiWorking ? <Loader2 className="h-3 w-3 animate-spin" /> : <><Sparkles className="h-3 w-3" /> New round</>} {aiWorking ? <Loader2 className="h-3 w-3 animate-spin" /> : <><Sparkles className="h-3 w-3" /> New round</>}
</button> </button>
</div> </div>
)} )}
{/* ── THE CONVERSATION ── */} {/* ━━ THE CONVERSATION ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
The WhatsApp mockup uses rounded corners because it IS a
phone. Everything else follows brand rules (sharp edges).
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ */}
<div className="max-w-lg mx-auto">
<div className="border border-gray-300 overflow-hidden shadow-lg" style={{ borderRadius: "20px" }}> <div className="border border-gray-300 overflow-hidden shadow-lg" style={{ borderRadius: "20px" }}>
{/* WhatsApp header */} {/* WhatsApp header */}
@@ -259,7 +273,6 @@ export default function AutomationsPage() {
const previewA = a ? resolvePreview(a.body) : "" const previewA = a ? resolvePreview(a.body) : ""
const previewB = b ? resolvePreview(b.body) : "" const previewB = b ? resolvePreview(b.body) : ""
// A/B stats
const rateA = a && a.sentCount > 0 ? Math.round((a.convertedCount / a.sentCount) * 100) : 0 const rateA = a && a.sentCount > 0 ? Math.round((a.convertedCount / a.sentCount) * 100) : 0
const rateB = b && b.sentCount > 0 ? Math.round((b.convertedCount / b.sentCount) * 100) : 0 const rateB = b && b.sentCount > 0 ? Math.round((b.convertedCount / b.sentCount) * 100) : 0
const totalSent = (a?.sentCount || 0) + (b?.sentCount || 0) const totalSent = (a?.sentCount || 0) + (b?.sentCount || 0)
@@ -277,7 +290,6 @@ export default function AutomationsPage() {
</div> </div>
{isEditing ? ( {isEditing ? (
/* ── Editing ── */
<div className="flex justify-end"> <div className="flex justify-end">
<div className="bg-[#DCF8C6] max-w-[90%] w-full shadow-sm" style={{ borderRadius: "8px 0 8px 8px" }}> <div className="bg-[#DCF8C6] max-w-[90%] w-full shadow-sm" style={{ borderRadius: "8px 0 8px 8px" }}>
<textarea ref={editorRef} value={editBody} onChange={e => setEditBody(e.target.value)} <textarea ref={editorRef} value={editBody} onChange={e => setEditBody(e.target.value)}
@@ -293,81 +305,49 @@ export default function AutomationsPage() {
</div> </div>
</div> </div>
) : b ? ( ) : b ? (
/* ── A/B test in progress ── */
<div className="flex justify-end"> <div className="flex justify-end">
<div className="max-w-[90%] w-full" style={{ borderRadius: "8px" }}> <div className="max-w-[90%] w-full" style={{ borderRadius: "8px" }}>
{/* Test header */}
<div className="bg-[#075E54] text-white px-3 py-1.5 flex items-center gap-1.5" style={{ borderRadius: "8px 8px 0 0" }}> <div className="bg-[#075E54] text-white px-3 py-1.5 flex items-center gap-1.5" style={{ borderRadius: "8px 8px 0 0" }}>
<Sparkles className="h-3 w-3 text-[#60A5FA]" /> <Sparkles className="h-3 w-3 text-[#60A5FA]" />
<span className="text-[10px] font-bold flex-1">AI is testing this message</span> <span className="text-[10px] font-bold flex-1">AI is testing</span>
{hasEnoughData && winner && ( {hasEnoughData && winner && (
<span className="text-[9px] bg-[#4ADE80]/20 text-[#4ADE80] px-1.5 py-0.5 font-bold flex items-center gap-0.5"> <span className="text-[9px] bg-[#16A34A]/20 text-[#4ADE80] px-1.5 py-0.5 font-bold flex items-center gap-0.5">
<Trophy className="h-2.5 w-2.5" /> {winner === "B" ? "AI" : "Yours"} winning <Trophy className="h-2.5 w-2.5" /> {winner === "B" ? "AI" : "Yours"} winning
</span> </span>
)} )}
{!hasEnoughData && <span className="text-[9px] text-white/40">{progress}%</span>} {!hasEnoughData && <span className="text-[9px] text-white/40">{progress}%</span>}
</div> </div>
{/* Two versions side by side */}
<div className="grid grid-cols-2 gap-px bg-[#075E54]/20"> <div className="grid grid-cols-2 gap-px bg-[#075E54]/20">
{/* Your version */}
<button onClick={() => startEdit(step)} className="bg-[#DCF8C6] p-2.5 text-left hover:brightness-[0.97] transition-all"> <button onClick={() => startEdit(step)} className="bg-[#DCF8C6] p-2.5 text-left hover:brightness-[0.97] transition-all">
<div className="flex items-center gap-1 mb-1.5"> <div className="flex items-center gap-1 mb-1.5">
<span className="text-[8px] font-bold text-[#075E54] bg-[#075E54]/10 px-1.5 py-0.5">Yours</span> <span className="text-[8px] font-bold text-[#075E54] bg-[#075E54]/10 px-1.5 py-0.5">Yours</span>
{a && a.sentCount > 0 && ( {a && a.sentCount > 0 && <span className={`text-[9px] font-bold ml-auto ${winner === "A" ? "text-[#075E54]" : "text-[#667781]"}`}>{rateA}%{winner === "A" && " 🏆"}</span>}
<span className={`text-[9px] font-bold ml-auto ${winner === "A" ? "text-[#075E54]" : "text-[#667781]"}`}>
{rateA}% {winner === "A" && "🏆"}
</span>
)}
</div> </div>
<div className="text-[10px] leading-[1.4] text-[#303030] line-clamp-4"> <div className="text-[10px] leading-[1.4] text-[#303030] line-clamp-4"><WhatsAppFormatted text={previewA} /></div>
<WhatsAppFormatted text={previewA} /> {a && a.sentCount > 0 && <p className="text-[8px] text-[#667781] mt-1">{a.convertedCount}/{a.sentCount} paid</p>}
</div>
{a && a.sentCount > 0 && (
<p className="text-[8px] text-[#667781] mt-1">{a.convertedCount} paid / {a.sentCount} sent</p>
)}
</button> </button>
{/* AI version */}
<div className="bg-[#DCF8C6] p-2.5"> <div className="bg-[#DCF8C6] p-2.5">
<div className="flex items-center gap-1 mb-1.5"> <div className="flex items-center gap-1 mb-1.5">
<span className="text-[8px] font-bold text-[#1E40AF] bg-[#1E40AF]/10 px-1.5 py-0.5 flex items-center gap-0.5"> <span className="text-[8px] font-bold text-[#1E40AF] bg-[#1E40AF]/10 px-1.5 py-0.5 flex items-center gap-0.5"><Sparkles className="h-2 w-2" /> AI</span>
<Sparkles className="h-2 w-2" /> AI {b.sentCount > 0 && <span className={`text-[9px] font-bold ml-auto ${winner === "B" ? "text-[#075E54]" : "text-[#667781]"}`}>{rateB}%{winner === "B" && " 🏆"}</span>}
</span>
{b.sentCount > 0 && (
<span className={`text-[9px] font-bold ml-auto ${winner === "B" ? "text-[#075E54]" : "text-[#667781]"}`}>
{rateB}% {winner === "B" && "🏆"}
</span>
)}
</div> </div>
<div className="text-[10px] leading-[1.4] text-[#303030] line-clamp-4"> <div className="text-[10px] leading-[1.4] text-[#303030] line-clamp-4"><WhatsAppFormatted text={previewB} /></div>
<WhatsAppFormatted text={previewB} /> {b.sentCount > 0 && <p className="text-[8px] text-[#667781] mt-1">{b.convertedCount}/{b.sentCount} paid</p>}
</div>
{b.sentCount > 0 && (
<p className="text-[8px] text-[#667781] mt-1">{b.convertedCount} paid / {b.sentCount} sent</p>
)}
</div> </div>
</div> </div>
{/* Progress bar */}
<div className="bg-white/60 px-3 py-1.5" style={{ borderRadius: "0 0 8px 8px" }}> <div className="bg-white/60 px-3 py-1.5" style={{ borderRadius: "0 0 8px 8px" }}>
<div className="h-1 bg-gray-200 overflow-hidden" style={{ borderRadius: "2px" }}> <div className="h-1 bg-gray-200 overflow-hidden" style={{ borderRadius: "2px" }}>
<div className={`h-full transition-all ${hasEnoughData ? "bg-[#4ADE80]" : "bg-[#60A5FA]"}`} <div className={`h-full transition-all ${hasEnoughData ? "bg-[#16A34A]" : "bg-[#60A5FA]"}`} style={{ width: `${progress}%` }} />
style={{ width: `${progress}%` }} />
</div> </div>
<p className="text-[8px] text-[#667781] mt-1"> <p className="text-[8px] text-[#667781] mt-1">
{hasEnoughData {hasEnoughData
? winner ? winner ? `${winner === "B" ? "AI" : "Your"} version converts ${Math.abs(rateB - rateA)}% better` : "Too close to call"
? `${winner === "B" ? "AI" : "Your"} version converts ${Math.abs(rateB - rateA)}% better` : `${totalSent} of ${MIN_SAMPLE * 2} sends to verdict`}
: "Too close to call — collecting more data"
: `${totalSent} of ${MIN_SAMPLE * 2} sends needed for verdict`
}
</p> </p>
</div> </div>
</div> </div>
</div> </div>
) : ( ) : (
/* ── Normal bubble ── */
<div className="flex justify-end"> <div className="flex justify-end">
<button onClick={() => startEdit(step)} <button onClick={() => startEdit(step)}
className="bg-[#DCF8C6] max-w-[85%] px-3 py-2 text-left text-[12px] leading-[1.45] text-[#303030] relative shadow-sm cursor-pointer hover:brightness-[0.97] transition-all" className="bg-[#DCF8C6] max-w-[85%] px-3 py-2 text-left text-[12px] leading-[1.45] text-[#303030] relative shadow-sm cursor-pointer hover:brightness-[0.97] transition-all"
@@ -400,32 +380,37 @@ export default function AutomationsPage() {
</div> </div>
</div> </div>
</div> </div>
</div>
{/* Pick winners — only when tests have enough data */} {/* ── Pick winners ── */}
{testsRunning > 0 && ( {testsRunning > 0 && (
<div className="max-w-lg mx-auto">
<button onClick={pickWinnersAndContinue} disabled={aiWorking} <button onClick={pickWinnersAndContinue} disabled={aiWorking}
className="w-full bg-[#111827] text-white py-3 text-xs font-bold flex items-center justify-center gap-2 hover:bg-gray-800 transition-colors disabled:opacity-50"> className="w-full bg-[#111827] text-white py-3 text-sm font-bold flex items-center justify-center gap-2 hover:bg-gray-800 transition-colors disabled:opacity-50">
{aiWorking {aiWorking
? <><Loader2 className="h-3.5 w-3.5 animate-spin" /> Picking winners</> ? <><Loader2 className="h-3.5 w-3.5 animate-spin" /> Picking winners</>
: <><Trophy className="h-3.5 w-3.5" /> Pick winners &amp; start new round</> : <><Trophy className="h-3.5 w-3.5" /> Pick winners &amp; start new round</>
} }
</button> </button>
</div>
)} )}
{/* Timing */} {/* ── Timing (expandable, in brand language) ── */}
<button onClick={() => setShowTiming(!showTiming)} className="w-full text-left flex items-center gap-2 text-xs text-gray-400 hover:text-gray-600 transition-colors py-1"> <div className="max-w-lg mx-auto">
<button onClick={() => setShowTiming(!showTiming)}
className="flex items-center gap-2 text-[11px] text-gray-400 hover:text-gray-600 transition-colors py-1 font-semibold tracking-wide uppercase">
<Clock className="h-3 w-3" /> Change timing <Clock className="h-3 w-3" /> Change timing
<ChevronDown className={`h-3 w-3 transition-transform ${showTiming ? "rotate-180" : ""}`} /> <ChevronDown className={`h-3 w-3 transition-transform ${showTiming ? "rotate-180" : ""}`} />
</button> </button>
{showTiming && ( {showTiming && (
<div className="grid grid-cols-3 gap-3"> <div className="grid grid-cols-3 gap-px bg-gray-200 mt-3">
{[ {[
{ label: "First reminder", key: "step1Delay", value: config?.step1Delay || 2 }, { label: "First reminder", key: "step1Delay", value: config?.step1Delay || 2 },
{ label: "Second reminder", key: "step2Delay", value: config?.step2Delay || 7 }, { label: "Second reminder", key: "step2Delay", value: config?.step2Delay || 7 },
{ label: "Final reminder", key: "step3Delay", value: config?.step3Delay || 14 }, { label: "Final reminder", key: "step3Delay", value: config?.step3Delay || 14 },
].map(t => ( ].map(t => (
<div key={t.key}> <div key={t.key} className="bg-white p-4">
<p className="text-[9px] font-bold text-gray-400 uppercase tracking-wide mb-1">{t.label}</p> <p className="text-[10px] font-bold text-gray-400 uppercase tracking-wide mb-2">{t.label}</p>
<select value={t.value} onChange={e => saveTiming(t.key, parseInt(e.target.value))} <select value={t.value} onChange={e => saveTiming(t.key, parseInt(e.target.value))}
className="w-full border-2 border-gray-200 px-2 py-1.5 text-xs font-bold text-[#111827] bg-white focus:border-[#1E40AF] outline-none"> className="w-full border-2 border-gray-200 px-2 py-1.5 text-xs font-bold text-[#111827] bg-white focus:border-[#1E40AF] outline-none">
{[1, 2, 3, 5, 7, 10, 14, 21, 28].map(d => <option key={d} value={d}>Day {d}</option>)} {[1, 2, 3, 5, 7, 10, 14, 21, 28].map(d => <option key={d} value={d}>Day {d}</option>)}
@@ -435,6 +420,7 @@ export default function AutomationsPage() {
</div> </div>
)} )}
</div> </div>
</div>
) )
} }