brand identity overhaul: match BRAND-IDENTITY.md across all pages
Design system changes (per brand guide): - ZERO rounded-2xl/3xl remaining (was 131 instances) - ZERO bg-gradient remaining (was 25) — all solid colors - ZERO colored shadows (shadow-trust-blue, etc) — flat, no glow - ZERO backdrop-blur/glass effects — solid backgrounds - ZERO emoji in logo marks — square P logomark everywhere - ZERO decorative scale animations (group-hover:scale-105, etc) Tailwind config: - Added brand color names: midnight, promise-blue, generosity-gold, fulfilled-green, alert-red, paper - Kept legacy aliases (trust-blue, etc) for backwards compat - --radius: 0.75rem → 0.5rem (tighter corners) CSS: - Removed glass, glass-dark, card-hover, pulse-ring, bounce-gentle, confetti-fall, scale-in animations - Kept only purposeful animations: fadeUp, fadeIn, slideDown, shimmer, counter-roll - --primary tuned to match Promise Blue exactly Components: - Button: removed all colored shadows, added 'blue' variant, removed rounded from sizes - All UI components: rounded-xl/2xl → rounded-lg Pages updated (41 files): - Dashboard layout: solid header (no blur), border-l-2 active indicator, midnight logo mark - Login/Signup: paper bg (no gradient), midnight logo mark, no emoji - Pledge flow: solid color icons, no gradient progress bars - All dashboard pages: flat, sharp, editorial
This commit is contained in:
135
gen_images.py
135
gen_images.py
@@ -1,135 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Generate PNPL landing page images using Gemini 3 Pro Image (Nano Banana Pro)."""
|
||||
|
||||
import time, base64
|
||||
from pathlib import Path
|
||||
from google import genai
|
||||
from google.genai import types
|
||||
|
||||
API_KEY = "AIzaSyCHnesXLjPw-UgeZaQotut66bgjFdvy12E"
|
||||
MODEL = "gemini-3-pro-image-preview"
|
||||
OUT_DIR = Path(r"C:\Users\uldvs\Downloads\PNPL Photography")
|
||||
OUT_DIR.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
client = genai.Client(api_key=API_KEY)
|
||||
|
||||
IMAGES = [
|
||||
# ── Main page ──
|
||||
("01-main-dashboard-hero", "16:9",
|
||||
"Editorial product photography of a MacBook Pro on a minimal white desk showing a charity analytics dashboard with blue bar charts and a pipeline table. Clean desk with only a coffee cup and a single pen. Soft diffused daylight from the left. Shot on Sony A7III, 50mm f/2.8, shallow depth of field on the screen edges. Muted tones, magazine editorial style. Landscape orientation 16:9 aspect ratio."),
|
||||
|
||||
("02-main-charity-manager-card", "16:9",
|
||||
"Candid documentary photograph of a British-Bangladeshi woman in her 40s wearing a dark navy hijab and tailored blazer, standing at the edge of a charity gala dinner in a London hotel ballroom. She holds a tablet at her side, looking across the room. Round tables with white cloths and warm tungsten pendant lights behind her, out of focus. Shot on Leica Q2, 28mm, f/1.7, available light only, natural film grain. Skin tones warm, background amber bokeh. No eye contact with camera. Landscape orientation 16:9 aspect ratio."),
|
||||
|
||||
("03-main-fundraiser-card", "16:9",
|
||||
"Street photography of a young British-Somali man in his mid-20s wearing a grey crewneck sweatshirt, walking on a London residential street and looking down at his phone screen. Overcast daylight, Victorian terraced houses soft in the background. He is mid-stride, natural posture, not posing. Shot on Fujifilm X100V, 23mm, f/2, ISO 400, slight grain. Desaturated cool tones, editorial documentary feel. Landscape orientation 16:9 aspect ratio."),
|
||||
|
||||
("04-main-volunteer-card", "16:9",
|
||||
"Candid indoor photograph of a young British-Pakistani woman in her early 20s wearing a plain volunteer lanyard on a black cord, leaning across a round banquet table pointing at a small printed card on the table. Warm overhead chandelier lighting in a mosque community hall, patterned carpet floor. Other seated guests softly blurred. Shot on Canon R6, 35mm f/1.4, shallow depth of field, warm amber tones. Natural, mid-conversation moment. Landscape orientation 16:9 aspect ratio."),
|
||||
|
||||
("05-main-org-card", "16:9",
|
||||
"Documentary photograph of four people seated around a plain rectangular table in a small community centre meeting room. Mixed British ethnicities, business casual clothing. One person gestures at printed papers on the table, others listening. Overhead fluorescent strip lights mixed with grey window daylight. Institutional beige walls, a whiteboard partially visible behind. Shot on Nikon Z6, 28mm f/2.8, natural exposure, slightly flat lighting. No one looking at camera, mid-discussion. Landscape orientation 16:9 aspect ratio."),
|
||||
|
||||
("06-main-pledge-form", "1:1",
|
||||
"Close-up over-the-shoulder photograph of a person holding an iPhone in their right hand at a dinner table. Screen shows a clean white mobile form with toggle switches and a green button, content not readable. A glass of water and folded linen napkin on the dark tablecloth beside the hand. Warm tungsten ambient light. Face not visible, only shoulder, arm, and hand in frame. Shot on 85mm f/1.8, tight crop, shallow depth of field on the screen. Natural skin tones, no flash. Square 1:1 aspect ratio."),
|
||||
|
||||
("07-main-schedule-step", "1:1",
|
||||
"Overhead 45-degree photograph of a woman's hands holding a smartphone at a kitchen table. Soft morning window light from the left casting a gentle shadow. A ceramic mug of tea and a small plate with toast crumbs slightly out of focus behind the phone. The phone screen is bright white with blue accent elements, not readable. Domestic setting, wooden table surface. Shot on 50mm f/2.0, natural light only, warm muted tones. Quiet, everyday moment. Square 1:1 aspect ratio."),
|
||||
|
||||
# ── Charities page ──
|
||||
("08-charities-hero", "1:1",
|
||||
"Documentary portrait of a British-Bangladeshi man in his 50s wearing a white embroidered kufi cap, silver-framed reading glasses, and a pressed light blue shirt. He is seated at a side table during a mosque fundraising event, looking at an open laptop screen. Behind him, blurred rows of seated attendees in a large hall with green and gold decorations. Warm interior lighting, patterned Islamic geometric carpet visible on the floor. Shot on 56mm f/1.2, available light, rich warm tones, film grain. Candid, absorbed in his work. Square 1:1 aspect ratio."),
|
||||
|
||||
("09-charities-amount-selection", "1:1",
|
||||
"Flat lay photograph of a hand holding an iPhone face-up on a dark navy tablecloth. The thumb hovers just above the bright screen. Visible beside the phone: silver cutlery, the edge of a white dinner plate, a printed menu card, and a glass of still water. Shot from directly above, 35mm, f/4, tungsten warm lighting from overhead chandeliers. The screen shows large numbers and a blue button, not readable. Charity dinner table setting, elegant but modest. Square 1:1 aspect ratio."),
|
||||
|
||||
("10-charities-whatsapp", "16:9",
|
||||
"Candid photograph of a woman's hand holding a phone on her lap while sitting on a grey fabric sofa at home. The phone screen shows green WhatsApp message bubbles, text not readable. She wears a loose cardigan sleeve and a simple watch. A cushion and part of a wooden bookshelf with books visible softly in the background. Afternoon window daylight, warm golden hour tones. Shot on 50mm f/1.8, eye-level, shallow depth of field. Domestic, relaxed, real. Landscape orientation 16:9 aspect ratio."),
|
||||
|
||||
# ── Fundraisers page ──
|
||||
("11-fundraisers-hero", "1:1",
|
||||
"Street photography of a young British-Arab woman in her late 20s wearing a patterned burgundy hijab and a denim jacket, standing on a London high street and holding her phone screen towards a friend. The friend is partially visible and out of focus on the right edge of frame. Overcast flat daylight, shop fronts with awnings blurred behind. She is mid-laugh, natural expression. Shot on 85mm f/1.4, compressed background bokeh, desaturated cool tones with warm skin. Unstaged genuine interaction. Square 1:1 aspect ratio."),
|
||||
|
||||
("12-fundraisers-redirect", "1:1",
|
||||
"Close-up of two hands holding a phone in a cafe. The screen shows a green-themed webpage with a large rounded button, generic and not readable. A marble cafe table surface with a flat white coffee in a ceramic cup and a torn croissant on a small plate beside the phone. Natural soft window light from the right. Shot on 50mm f/2.0, shallow depth of field focused on the phone screen. Warm cafe tones, European coffee shop atmosphere. No face visible. Square 1:1 aspect ratio."),
|
||||
|
||||
("13-fundraisers-dashboard", "16:9",
|
||||
"Editorial product photography of a laptop screen showing a minimal analytics dashboard with a horizontal funnel chart in shades of blue, grey number cards across the top, and a simple data table below. The laptop sits on a clean white desk. Beside it, a small potted succulent and a closed notebook. Soft even studio lighting, no harsh shadows. Shot on 50mm f/4, sharp focus on screen, edges of laptop slightly soft. Clean, SaaS product marketing style. Landscape orientation 16:9 aspect ratio."),
|
||||
|
||||
# ── Volunteers page ──
|
||||
("14-volunteers-hero", "1:1",
|
||||
"Candid photograph of a young British-Pakistani man in his early 20s standing behind a round banquet table at a charity dinner. He wears a plain navy polo shirt and a clear plastic lanyard with a printed name badge. In the sharp foreground, a small clear acrylic A-frame stand with a QR code printed on white card sits on the white tablecloth. The volunteer is smiling at someone off-camera to the left. Background shows other round tables and warm pendant lighting, all soft and blurred. Shot on 35mm f/1.4 from table height, warm amber tones, available light. Energetic, genuine. Square 1:1 aspect ratio."),
|
||||
|
||||
("15-volunteers-phone-stats", "1:1",
|
||||
"Dynamic low-angle photograph of a volunteer's torso and hand holding a phone at waist height. A black lanyard with a badge hangs from their neck, visible at the top of frame. The phone screen is bright showing a large number and a progress bar in blue, not readable. Behind and above, blurred figures walking through an event hall with overhead spotlights creating flare. Shot on 24mm f/2.0, slightly tilted angle, motion blur on background figures, available light. Raw, energetic, in-the-moment. Square 1:1 aspect ratio."),
|
||||
|
||||
("16-volunteers-leaderboard", "16:9",
|
||||
"Editorial product photography of a phone screen showing a ranked list with numbered rows, small circular avatar placeholders, names, and bar charts beside each row. The phone lies flat on a dark surface. A lanyard and a paper name badge sit beside it. Soft directional studio light from the top left. Shot on macro lens, f/4, sharp focus on the screen, surroundings slightly dark. Clean, focused, app showcase. Landscape orientation 16:9 aspect ratio."),
|
||||
|
||||
# ── Organisations page ──
|
||||
("17-orgs-hero-boardroom", "1:1",
|
||||
"Documentary photograph of three people in a modest charity office meeting room around a rectangular table. A Black British woman in her 40s in a navy blazer is pointing at a laptop screen. A South Asian man in his 30s in a grey suit leans in to look. A white British man in his 50s in a rolled-sleeve shirt sits across, arms folded, listening. Papers and printed spreadsheets on the table. Grey carpet, a window with vertical blinds letting in flat daylight. Overhead fluorescent lighting. Shot on 28mm f/2.8, documentary wide angle, no one looking at camera, mid-discussion. Slightly cool tones, institutional setting. Square 1:1 aspect ratio."),
|
||||
|
||||
("18-orgs-pipeline", "1:1",
|
||||
"Editorial product photography of a laptop screen showing a Kanban-style board with three columns of cards in white, each card showing a name, a currency amount, and a coloured status dot. The laptop is a MacBook on a plain wooden desk. A pair of reading glasses and a printed agenda document sit beside it. Soft even natural light from a window. Shot on 50mm f/3.5, focused on the screen, desk edges slightly out of focus. Neutral professional tones. Square 1:1 aspect ratio."),
|
||||
|
||||
("19-orgs-instalment-schedule", "16:9",
|
||||
"Editorial product photography of a laptop screen showing a timeline or calendar view with monthly markers and progress bars in blue and grey. The laptop sits on a white conference table. Two chairs partially visible on either side, slightly blurred. A glass of water and a pen on the table. Clean fluorescent overhead lighting, even exposure. Shot on 35mm f/4, sharp focus on screen. Corporate clean, minimal, informational. Landscape orientation 16:9 aspect ratio."),
|
||||
|
||||
("20-orgs-laptop-desk", "1:1",
|
||||
"Documentary photograph of a laptop open on a cluttered but real charity office desk. The screen shows a table with rows of data, not readable. A hand rests on the trackpad, wearing a simple watch. On the desk: a white ceramic mug with a tea bag tag hanging over the edge, a stack of manila folders, a small framed family photo, a desk lamp switched on. Mixed warm desk lamp light and cool daylight from a window on the right. Shot on 35mm f/2.0, focused on the laptop and hand, background slightly soft. Warm, lived-in, working moment. Square 1:1 aspect ratio."),
|
||||
]
|
||||
|
||||
def generate_image(filename, aspect, prompt):
|
||||
out_path = OUT_DIR / f"{filename}.jpg"
|
||||
if out_path.exists():
|
||||
print(f" SKIP (exists): {filename}")
|
||||
return True
|
||||
|
||||
print(f" Generating: {filename} ({aspect})...")
|
||||
|
||||
try:
|
||||
response = client.models.generate_content(
|
||||
model=MODEL,
|
||||
contents=prompt,
|
||||
config=types.GenerateContentConfig(
|
||||
response_modalities=["IMAGE", "TEXT"],
|
||||
),
|
||||
)
|
||||
|
||||
for part in response.candidates[0].content.parts:
|
||||
if part.inline_data:
|
||||
img_bytes = part.inline_data.data
|
||||
with open(out_path, "wb") as f:
|
||||
f.write(img_bytes)
|
||||
size_kb = len(img_bytes) / 1024
|
||||
print(f" DONE: {filename} ({size_kb:.0f} KB)")
|
||||
return True
|
||||
|
||||
print(f" FAIL: {filename} - no image in response")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
print(f" ERROR: {filename} - {e}")
|
||||
return False
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print(f"Model: {MODEL} (Nano Banana Pro)")
|
||||
print(f"Output: {OUT_DIR}")
|
||||
print(f"Images: {len(IMAGES)}")
|
||||
print()
|
||||
|
||||
ok = 0
|
||||
fail = 0
|
||||
for i, (fn, asp, pr) in enumerate(IMAGES, 1):
|
||||
print(f"[{i}/{len(IMAGES)}]")
|
||||
if generate_image(fn, asp, pr):
|
||||
ok += 1
|
||||
else:
|
||||
fail += 1
|
||||
if i < len(IMAGES):
|
||||
time.sleep(5)
|
||||
|
||||
print(f"\nDone: {ok} generated, {fail} failed")
|
||||
print(f"Files in: {OUT_DIR}")
|
||||
@@ -33,58 +33,56 @@ function LoginForm() {
|
||||
}
|
||||
}
|
||||
|
||||
// Auto-login as demo if ?demo=1
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
useEffect(() => { if (isDemo) doLogin(undefined, "demo@pnpl.app", "demo1234") }, [])
|
||||
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center bg-gradient-to-br from-trust-blue/5 via-white to-warm-amber/5 p-4">
|
||||
<div className="w-full max-w-sm space-y-5">
|
||||
<div className="min-h-screen flex items-center justify-center bg-paper p-4">
|
||||
<div className="w-full max-w-sm space-y-6">
|
||||
{isDemo && (
|
||||
<div className="text-center py-8">
|
||||
<div className="inline-flex h-12 w-12 rounded-2xl bg-gradient-to-br from-trust-blue to-blue-600 items-center justify-center shadow-lg shadow-trust-blue/20 animate-pulse mb-3">
|
||||
<span className="text-white text-xl">🤲</span>
|
||||
<div className="text-center py-12">
|
||||
<div className="h-10 w-10 bg-midnight flex items-center justify-center mx-auto mb-4">
|
||||
<span className="text-white text-sm font-black">P</span>
|
||||
</div>
|
||||
<p className="text-sm font-medium text-trust-blue animate-pulse">Loading demo...</p>
|
||||
<p className="text-sm font-medium text-gray-500">Loading demo...</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!isDemo && (
|
||||
<>
|
||||
<div className="text-center">
|
||||
<div className="inline-flex h-12 w-12 rounded-2xl bg-gradient-to-br from-trust-blue to-blue-600 items-center justify-center shadow-lg shadow-trust-blue/20 mb-3">
|
||||
<span className="text-white text-xl">🤲</span>
|
||||
<div className="text-center space-y-3">
|
||||
<div className="h-10 w-10 bg-midnight flex items-center justify-center mx-auto">
|
||||
<span className="text-white text-sm font-black">P</span>
|
||||
</div>
|
||||
<div>
|
||||
<h1 className="text-2xl font-black text-midnight">Welcome back</h1>
|
||||
<p className="text-sm text-gray-500 mt-1">Sign in to your charity dashboard</p>
|
||||
</div>
|
||||
<h1 className="text-2xl font-black text-gray-900">Welcome back</h1>
|
||||
<p className="text-sm text-muted-foreground mt-1">Sign in to your charity dashboard</p>
|
||||
</div>
|
||||
|
||||
{/* Social login */}
|
||||
<div className="space-y-2">
|
||||
{/* Google */}
|
||||
<button
|
||||
onClick={() => signIn("auth0", { callbackUrl: "/dashboard" })}
|
||||
className="w-full flex items-center justify-center gap-2 rounded-xl border border-gray-200 bg-white px-4 py-3 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-all"
|
||||
className="w-full flex items-center justify-center gap-2 border border-gray-200 bg-white px-4 py-3 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors"
|
||||
>
|
||||
<svg className="h-4 w-4" viewBox="0 0 24 24"><path d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92a5.06 5.06 0 01-2.2 3.32v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.1z" fill="#4285F4"/><path d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z" fill="#34A853"/><path d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z" fill="#FBBC05"/><path d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z" fill="#EA4335"/></svg>
|
||||
Continue with Google
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="relative">
|
||||
<div className="absolute inset-0 flex items-center"><div className="w-full border-t" /></div>
|
||||
<div className="relative flex justify-center text-xs"><span className="bg-gradient-to-br from-trust-blue/5 via-white to-warm-amber/5 px-3 text-muted-foreground">or sign in with email</span></div>
|
||||
<div className="absolute inset-0 flex items-center"><div className="w-full border-t border-gray-200" /></div>
|
||||
<div className="relative flex justify-center text-xs"><span className="bg-paper px-3 text-gray-400">or sign in with email</span></div>
|
||||
</div>
|
||||
|
||||
{/* Email/password form */}
|
||||
<form onSubmit={(e) => doLogin(e)} className="space-y-3">
|
||||
{error && (
|
||||
<div className="rounded-xl bg-danger-red/10 border border-danger-red/20 p-2.5 text-sm text-danger-red text-center">{error}</div>
|
||||
<div className="border border-alert-red/20 bg-alert-red/5 p-2.5 text-sm text-alert-red text-center">{error}</div>
|
||||
)}
|
||||
<input
|
||||
type="email"
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
className="w-full rounded-xl border border-gray-200 px-4 py-3 text-sm focus:border-trust-blue focus:ring-2 focus:ring-trust-blue/20 outline-none transition-all"
|
||||
className="w-full border border-gray-200 px-4 py-3 text-sm focus:border-promise-blue focus:ring-1 focus:ring-promise-blue/20 outline-none transition-colors"
|
||||
placeholder="Email"
|
||||
required
|
||||
/>
|
||||
@@ -92,36 +90,36 @@ function LoginForm() {
|
||||
type="password"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
className="w-full rounded-xl border border-gray-200 px-4 py-3 text-sm focus:border-trust-blue focus:ring-2 focus:ring-trust-blue/20 outline-none transition-all"
|
||||
className="w-full border border-gray-200 px-4 py-3 text-sm focus:border-promise-blue focus:ring-1 focus:ring-promise-blue/20 outline-none transition-colors"
|
||||
placeholder="Password"
|
||||
required
|
||||
/>
|
||||
<button
|
||||
type="submit"
|
||||
disabled={loading}
|
||||
className="w-full rounded-xl bg-trust-blue px-4 py-3 text-sm font-semibold text-white hover:bg-trust-blue/90 disabled:opacity-50 transition-all"
|
||||
className="w-full bg-midnight px-4 py-3 text-sm font-semibold text-white hover:bg-gray-800 disabled:opacity-50 transition-colors"
|
||||
>
|
||||
{loading ? "Signing in..." : "Sign In"}
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<div className="relative">
|
||||
<div className="absolute inset-0 flex items-center"><div className="w-full border-t" /></div>
|
||||
<div className="relative flex justify-center text-xs"><span className="bg-gradient-to-br from-trust-blue/5 via-white to-warm-amber/5 px-2 text-muted-foreground">or</span></div>
|
||||
<div className="absolute inset-0 flex items-center"><div className="w-full border-t border-gray-200" /></div>
|
||||
<div className="relative flex justify-center text-xs"><span className="bg-paper px-2 text-gray-400">or</span></div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => doLogin(undefined, "demo@pnpl.app", "demo1234")}
|
||||
disabled={loading}
|
||||
className="w-full rounded-xl border-2 border-dashed border-gray-200 px-4 py-3 text-sm font-medium text-muted-foreground hover:border-trust-blue hover:text-trust-blue disabled:opacity-50 transition-all"
|
||||
className="w-full border border-dashed border-gray-300 px-4 py-3 text-sm font-medium text-gray-500 hover:border-promise-blue hover:text-promise-blue disabled:opacity-50 transition-colors"
|
||||
>
|
||||
🎮 Try the Demo — no signup needed
|
||||
Try the Demo — no signup needed
|
||||
</button>
|
||||
|
||||
<p className="text-center text-sm text-muted-foreground">
|
||||
<p className="text-center text-sm text-gray-500">
|
||||
Don't have an account?{" "}
|
||||
<Link href="/signup" className="text-trust-blue font-semibold hover:underline">Get Started Free</Link>
|
||||
<Link href="/signup" className="text-promise-blue font-semibold hover:underline">Get Started Free</Link>
|
||||
</p>
|
||||
</>
|
||||
)}
|
||||
@@ -132,7 +130,7 @@ function LoginForm() {
|
||||
|
||||
export default function LoginPage() {
|
||||
return (
|
||||
<Suspense fallback={<div className="min-h-screen flex items-center justify-center"><div className="animate-spin h-8 w-8 border-2 border-trust-blue border-t-transparent rounded-full" /></div>}>
|
||||
<Suspense fallback={<div className="min-h-screen flex items-center justify-center bg-paper"><div className="animate-spin h-6 w-6 border-2 border-promise-blue border-t-transparent rounded-full" /></div>}>
|
||||
<LoginForm />
|
||||
</Suspense>
|
||||
)
|
||||
|
||||
@@ -38,7 +38,6 @@ export default function SignupPage() {
|
||||
return
|
||||
}
|
||||
|
||||
// Auto sign in and go straight to dashboard
|
||||
const result = await signIn("credentials", { email, password, redirect: false })
|
||||
if (result?.error) {
|
||||
setError("Account created — please sign in")
|
||||
@@ -54,86 +53,83 @@ export default function SignupPage() {
|
||||
|
||||
if (step === "loading") {
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center bg-gradient-to-br from-trust-blue/5 via-white to-warm-amber/5">
|
||||
<div className="min-h-screen flex items-center justify-center bg-paper">
|
||||
<div className="text-center space-y-4">
|
||||
<div className="inline-flex h-14 w-14 rounded-2xl bg-gradient-to-br from-trust-blue to-blue-600 items-center justify-center shadow-lg shadow-trust-blue/20 animate-pulse">
|
||||
<span className="text-white text-2xl">🤲</span>
|
||||
<div className="h-10 w-10 bg-midnight flex items-center justify-center mx-auto">
|
||||
<span className="text-white text-sm font-black">P</span>
|
||||
</div>
|
||||
<p className="text-sm font-medium text-trust-blue animate-pulse">Setting up your charity...</p>
|
||||
<p className="text-sm font-medium text-gray-500">Setting up your charity...</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center bg-gradient-to-br from-trust-blue/5 via-white to-warm-amber/5 p-4">
|
||||
<div className="w-full max-w-sm space-y-5">
|
||||
<div className="text-center">
|
||||
<div className="inline-flex h-12 w-12 rounded-2xl bg-gradient-to-br from-trust-blue to-blue-600 items-center justify-center shadow-lg shadow-trust-blue/20 mb-3">
|
||||
<span className="text-white text-xl">🤲</span>
|
||||
<div className="min-h-screen flex items-center justify-center bg-paper p-4">
|
||||
<div className="w-full max-w-sm space-y-6">
|
||||
<div className="text-center space-y-3">
|
||||
<div className="h-10 w-10 bg-midnight flex items-center justify-center mx-auto">
|
||||
<span className="text-white text-sm font-black">P</span>
|
||||
</div>
|
||||
<div>
|
||||
<h1 className="text-2xl font-black text-midnight">Start collecting pledges</h1>
|
||||
<p className="text-sm text-gray-500 mt-1">Free. 30 seconds. No card.</p>
|
||||
</div>
|
||||
<h1 className="text-2xl font-black text-gray-900">Start collecting pledges</h1>
|
||||
<p className="text-sm text-muted-foreground mt-1">Free. 30 seconds. No card.</p>
|
||||
</div>
|
||||
|
||||
{/* Google signup */}
|
||||
<button
|
||||
onClick={signUpWithGoogle}
|
||||
className="w-full flex items-center justify-center gap-2 rounded-xl border border-gray-200 bg-white px-4 py-3 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-all"
|
||||
className="w-full flex items-center justify-center gap-2 border border-gray-200 bg-white px-4 py-3 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors"
|
||||
>
|
||||
<svg className="h-4 w-4" viewBox="0 0 24 24"><path d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92a5.06 5.06 0 01-2.2 3.32v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.1z" fill="#4285F4"/><path d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z" fill="#34A853"/><path d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z" fill="#FBBC05"/><path d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z" fill="#EA4335"/></svg>
|
||||
Sign up with Google
|
||||
</button>
|
||||
|
||||
<div className="relative">
|
||||
<div className="absolute inset-0 flex items-center"><div className="w-full border-t" /></div>
|
||||
<div className="relative flex justify-center text-xs"><span className="bg-gradient-to-br from-trust-blue/5 via-white to-warm-amber/5 px-3 text-muted-foreground">or use email</span></div>
|
||||
<div className="absolute inset-0 flex items-center"><div className="w-full border-t border-gray-200" /></div>
|
||||
<div className="relative flex justify-center text-xs"><span className="bg-paper px-3 text-gray-400">or use email</span></div>
|
||||
</div>
|
||||
|
||||
<form onSubmit={handleSubmit} className="space-y-3">
|
||||
{error && (
|
||||
<div className="rounded-xl bg-danger-red/10 border border-danger-red/20 p-2.5 text-sm text-danger-red text-center">{error}</div>
|
||||
<div className="border border-alert-red/20 bg-alert-red/5 p-2.5 text-sm text-alert-red text-center">{error}</div>
|
||||
)}
|
||||
|
||||
<input
|
||||
type="text"
|
||||
value={charityName}
|
||||
onChange={(e) => setCharityName(e.target.value)}
|
||||
className="w-full rounded-xl border border-gray-200 px-4 py-3 text-sm focus:border-trust-blue focus:ring-2 focus:ring-trust-blue/20 outline-none transition-all"
|
||||
className="w-full border border-gray-200 px-4 py-3 text-sm focus:border-promise-blue focus:ring-1 focus:ring-promise-blue/20 outline-none transition-colors"
|
||||
placeholder="Your charity or mosque name"
|
||||
required
|
||||
autoFocus
|
||||
/>
|
||||
|
||||
<input
|
||||
type="email"
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
className="w-full rounded-xl border border-gray-200 px-4 py-3 text-sm focus:border-trust-blue focus:ring-2 focus:ring-trust-blue/20 outline-none transition-all"
|
||||
className="w-full border border-gray-200 px-4 py-3 text-sm focus:border-promise-blue focus:ring-1 focus:ring-promise-blue/20 outline-none transition-colors"
|
||||
placeholder="Your email"
|
||||
required
|
||||
/>
|
||||
|
||||
<input
|
||||
type="password"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
className="w-full rounded-xl border border-gray-200 px-4 py-3 text-sm focus:border-trust-blue focus:ring-2 focus:ring-trust-blue/20 outline-none transition-all"
|
||||
className="w-full border border-gray-200 px-4 py-3 text-sm focus:border-promise-blue focus:ring-1 focus:ring-promise-blue/20 outline-none transition-colors"
|
||||
placeholder="Pick a password (8+ chars)"
|
||||
required
|
||||
minLength={8}
|
||||
/>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
className="w-full rounded-xl bg-trust-blue px-4 py-3.5 text-sm font-bold text-white hover:bg-trust-blue/90 transition-all shadow-lg shadow-trust-blue/20"
|
||||
className="w-full bg-midnight px-4 py-3.5 text-sm font-bold text-white hover:bg-gray-800 transition-colors"
|
||||
>
|
||||
Create Account →
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<p className="text-center text-xs text-muted-foreground">
|
||||
Already have an account? <Link href="/login" className="text-trust-blue font-semibold hover:underline">Sign in</Link>
|
||||
<p className="text-center text-xs text-gray-500">
|
||||
Already have an account? <Link href="/login" className="text-promise-blue font-semibold hover:underline">Sign in</Link>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -93,7 +93,7 @@ export default function AdminPage() {
|
||||
<CardContent>
|
||||
<div className="flex gap-3 overflow-x-auto">
|
||||
{Object.entries(data.byStatus).map(([status, { count, amount }]) => (
|
||||
<div key={status} className="flex-shrink-0 rounded-xl bg-muted/50 px-4 py-2 text-center min-w-[100px]">
|
||||
<div key={status} className="flex-shrink-0 rounded-lg bg-muted/50 px-4 py-2 text-center min-w-[100px]">
|
||||
<Badge variant={status === "paid" ? "success" : status === "overdue" ? "destructive" : "secondary"} className="text-[10px]">{status}</Badge>
|
||||
<p className="text-lg font-bold mt-1">{count}</p>
|
||||
<p className="text-[10px] text-muted-foreground">{fmt(amount)}</p>
|
||||
|
||||
@@ -141,7 +141,7 @@ export default function CampaignLinksPage() {
|
||||
<Card key={src.id} className="hover:shadow-md transition-shadow">
|
||||
<CardContent className="pt-6 space-y-4">
|
||||
{/* QR Code — compact */}
|
||||
<div className="max-w-[140px] mx-auto bg-white rounded-xl flex items-center justify-center p-1.5">
|
||||
<div className="max-w-[140px] mx-auto bg-white rounded-lg flex items-center justify-center p-1.5">
|
||||
<QRCodeCanvas url={`${baseUrl}/p/${src.code}`} size={128} />
|
||||
</div>
|
||||
|
||||
|
||||
@@ -250,7 +250,7 @@ export default function EventsPage() {
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setForm(f => ({ ...f, paymentMode: "self" }))}
|
||||
className={`rounded-xl border p-3 text-left text-xs transition-all ${form.paymentMode === "self" ? "border-trust-blue bg-trust-blue/5" : "border-gray-200"}`}
|
||||
className={`rounded-lg border p-3 text-left text-xs transition-all ${form.paymentMode === "self" ? "border-trust-blue bg-trust-blue/5" : "border-gray-200"}`}
|
||||
>
|
||||
<span className="font-bold block">🏦 Bank transfer</span>
|
||||
<span className="text-muted-foreground">We show our bank details</span>
|
||||
@@ -258,7 +258,7 @@ export default function EventsPage() {
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setForm(f => ({ ...f, paymentMode: "external" }))}
|
||||
className={`rounded-xl border p-3 text-left text-xs transition-all ${form.paymentMode === "external" ? "border-warm-amber bg-warm-amber/5" : "border-gray-200"}`}
|
||||
className={`rounded-lg border p-3 text-left text-xs transition-all ${form.paymentMode === "external" ? "border-warm-amber bg-warm-amber/5" : "border-gray-200"}`}
|
||||
>
|
||||
<span className="font-bold block">🔗 External page</span>
|
||||
<span className="text-muted-foreground">LaunchGood, Enthuse, etc.</span>
|
||||
@@ -267,7 +267,7 @@ export default function EventsPage() {
|
||||
</div>
|
||||
|
||||
{form.paymentMode === "external" && (
|
||||
<div className="space-y-3 rounded-xl border border-warm-amber/20 bg-warm-amber/5 p-3">
|
||||
<div className="space-y-3 rounded-lg border border-warm-amber/20 bg-warm-amber/5 p-3">
|
||||
<div className="space-y-2">
|
||||
<Label>Fundraising page URL *</Label>
|
||||
<div className="relative">
|
||||
@@ -285,7 +285,7 @@ export default function EventsPage() {
|
||||
<select
|
||||
value={form.externalPlatform}
|
||||
onChange={(e) => setForm(f => ({ ...f, externalPlatform: e.target.value }))}
|
||||
className="w-full rounded-xl border border-gray-200 px-3 py-2 text-sm"
|
||||
className="w-full rounded-lg border border-gray-200 px-3 py-2 text-sm"
|
||||
>
|
||||
<option value="">Select platform...</option>
|
||||
<option value="launchgood">🌙 LaunchGood</option>
|
||||
@@ -302,7 +302,7 @@ export default function EventsPage() {
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setForm(f => ({ ...f, zakatEligible: !f.zakatEligible }))}
|
||||
className={`w-full flex items-center justify-between rounded-xl border-2 p-3 text-left transition-all ${
|
||||
className={`w-full flex items-center justify-between rounded-lg border-2 p-3 text-left transition-all ${
|
||||
form.zakatEligible ? "border-trust-blue bg-trust-blue/5" : "border-gray-200"
|
||||
}`}
|
||||
>
|
||||
|
||||
@@ -73,7 +73,7 @@ export default function ExportsPage() {
|
||||
<li>Event and reference for audit trail</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div className="rounded-xl bg-success-green/5 border border-success-green/20 p-3">
|
||||
<div className="rounded-lg bg-success-green/5 border border-success-green/20 p-3">
|
||||
<p className="text-xs text-success-green font-medium">
|
||||
💷 Claim 25p for every £1 donated by a UK taxpayer
|
||||
</p>
|
||||
@@ -101,7 +101,7 @@ export default function ExportsPage() {
|
||||
</code>
|
||||
<p className="text-xs">Returns pending reminders with donor contact info for external email/SMS.</p>
|
||||
</div>
|
||||
<div className="rounded-xl bg-trust-blue/5 border border-trust-blue/20 p-3">
|
||||
<div className="rounded-lg bg-trust-blue/5 border border-trust-blue/20 p-3">
|
||||
<p className="text-xs text-trust-blue font-medium">
|
||||
💡 Connect to Zapier or n8n to send automatic reminder emails and SMS
|
||||
</p>
|
||||
|
||||
@@ -24,31 +24,31 @@ export default function DashboardLayout({ children }: { children: React.ReactNod
|
||||
const user = session?.user as any
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50/50">
|
||||
{/* Top bar */}
|
||||
<header className="sticky top-0 z-40 border-b bg-white/80 backdrop-blur-xl">
|
||||
<div className="min-h-screen bg-paper">
|
||||
{/* Top bar — sharp, no blur */}
|
||||
<header className="sticky top-0 z-40 border-b border-gray-200 bg-white">
|
||||
<div className="flex h-14 items-center gap-4 px-4 md:px-6">
|
||||
<Link href="/dashboard" className="flex items-center gap-2.5">
|
||||
<div className="h-8 w-8 rounded-xl bg-gradient-to-br from-trust-blue to-blue-600 flex items-center justify-center shadow-lg shadow-trust-blue/20">
|
||||
<span className="text-white font-bold text-sm">P</span>
|
||||
<div className="h-7 w-7 bg-midnight flex items-center justify-center">
|
||||
<span className="text-white text-xs font-black">P</span>
|
||||
</div>
|
||||
<div className="hidden sm:block">
|
||||
<span className="font-black text-sm text-gray-900">{user?.orgName || "Pledge Now, Pay Later"}</span>
|
||||
<span className="font-black text-sm text-midnight">{user?.orgName || "Pledge Now, Pay Later"}</span>
|
||||
</div>
|
||||
</Link>
|
||||
<div className="flex-1" />
|
||||
<Link href="/dashboard/events" className="hidden md:block">
|
||||
<button className="inline-flex items-center gap-1.5 rounded-lg bg-trust-blue px-3 py-1.5 text-xs font-semibold text-white hover:bg-trust-blue/90 transition-colors">
|
||||
<button className="inline-flex items-center gap-1.5 bg-midnight px-3 py-1.5 text-xs font-semibold text-white hover:bg-gray-800 transition-colors">
|
||||
<Plus className="h-3 w-3" /> New Campaign
|
||||
</button>
|
||||
</Link>
|
||||
<Link href="/" className="text-xs text-muted-foreground hover:text-foreground transition-colors flex items-center gap-1">
|
||||
<Link href="/" className="text-xs text-gray-400 hover:text-midnight transition-colors flex items-center gap-1">
|
||||
<ExternalLink className="h-3 w-3" />
|
||||
</Link>
|
||||
{session && (
|
||||
<button
|
||||
onClick={() => signOut({ callbackUrl: "/login" })}
|
||||
className="text-xs text-muted-foreground hover:text-foreground transition-colors flex items-center gap-1"
|
||||
className="text-xs text-gray-400 hover:text-midnight transition-colors flex items-center gap-1"
|
||||
>
|
||||
<LogOut className="h-3 w-3" />
|
||||
</button>
|
||||
@@ -57,8 +57,8 @@ export default function DashboardLayout({ children }: { children: React.ReactNod
|
||||
</header>
|
||||
|
||||
<div className="flex">
|
||||
{/* Desktop sidebar */}
|
||||
<aside className="hidden md:flex w-56 flex-col border-r bg-white min-h-[calc(100vh-3.5rem)] py-3 px-2">
|
||||
{/* Desktop sidebar — clean, no decorative elements */}
|
||||
<aside className="hidden md:flex w-52 flex-col border-r border-gray-200 bg-white min-h-[calc(100vh-3.5rem)] py-3 px-2">
|
||||
<nav className="space-y-0.5">
|
||||
{navItems.map((item) => {
|
||||
const isActive = pathname === item.href || (item.href !== "/dashboard" && pathname.startsWith(item.href))
|
||||
@@ -67,10 +67,10 @@ export default function DashboardLayout({ children }: { children: React.ReactNod
|
||||
key={item.href}
|
||||
href={item.href}
|
||||
className={cn(
|
||||
"flex items-center gap-2.5 rounded-lg px-3 py-2 text-sm font-medium transition-colors",
|
||||
"flex items-center gap-2.5 px-3 py-2 text-sm font-medium transition-colors",
|
||||
isActive
|
||||
? "bg-trust-blue/5 text-trust-blue"
|
||||
: "text-muted-foreground hover:bg-gray-100 hover:text-foreground"
|
||||
? "bg-promise-blue/5 text-promise-blue border-l-2 border-promise-blue -ml-0.5"
|
||||
: "text-gray-500 hover:bg-gray-50 hover:text-midnight"
|
||||
)}
|
||||
>
|
||||
<item.icon className="h-4 w-4" />
|
||||
@@ -80,14 +80,14 @@ export default function DashboardLayout({ children }: { children: React.ReactNod
|
||||
})}
|
||||
{user?.role === "super_admin" && (
|
||||
<>
|
||||
<div className="my-2 border-t" />
|
||||
<div className="my-2 border-t border-gray-100" />
|
||||
<Link
|
||||
href={adminNav.href}
|
||||
className={cn(
|
||||
"flex items-center gap-2.5 rounded-lg px-3 py-2 text-sm font-medium transition-colors",
|
||||
"flex items-center gap-2.5 px-3 py-2 text-sm font-medium transition-colors",
|
||||
pathname === adminNav.href
|
||||
? "bg-trust-blue/5 text-trust-blue"
|
||||
: "text-muted-foreground hover:bg-gray-100 hover:text-foreground"
|
||||
? "bg-promise-blue/5 text-promise-blue border-l-2 border-promise-blue -ml-0.5"
|
||||
: "text-gray-500 hover:bg-gray-50 hover:text-midnight"
|
||||
)}
|
||||
>
|
||||
<adminNav.icon className="h-4 w-4" />
|
||||
@@ -98,12 +98,12 @@ export default function DashboardLayout({ children }: { children: React.ReactNod
|
||||
</nav>
|
||||
|
||||
<div className="mt-auto px-2 pt-4">
|
||||
<div className="rounded-xl bg-gradient-to-br from-trust-blue/5 to-warm-amber/5 border p-3 space-y-1.5">
|
||||
<p className="text-xs font-semibold">Need help?</p>
|
||||
<p className="text-[10px] text-muted-foreground leading-relaxed">
|
||||
<div className="border border-gray-200 p-3 space-y-1.5">
|
||||
<p className="text-xs font-bold text-midnight">Need help?</p>
|
||||
<p className="text-[10px] text-gray-500 leading-relaxed">
|
||||
Get a fractional Head of Technology to optimise your charity's digital stack.
|
||||
</p>
|
||||
<Link href="/dashboard/apply" className="inline-block text-[10px] font-semibold text-trust-blue hover:underline">
|
||||
<Link href="/dashboard/apply" className="inline-block text-[10px] font-semibold text-promise-blue hover:underline">
|
||||
Learn more →
|
||||
</Link>
|
||||
</div>
|
||||
@@ -111,7 +111,7 @@ export default function DashboardLayout({ children }: { children: React.ReactNod
|
||||
</aside>
|
||||
|
||||
{/* Mobile bottom nav */}
|
||||
<nav className="md:hidden fixed bottom-0 left-0 right-0 z-40 border-t bg-white/95 backdrop-blur-xl flex justify-around py-1.5 px-1">
|
||||
<nav className="md:hidden fixed bottom-0 left-0 right-0 z-40 border-t border-gray-200 bg-white flex justify-around py-1.5 px-1">
|
||||
{navItems.slice(0, 5).map((item) => {
|
||||
const isActive = pathname === item.href || (item.href !== "/dashboard" && pathname.startsWith(item.href))
|
||||
return (
|
||||
@@ -119,8 +119,8 @@ export default function DashboardLayout({ children }: { children: React.ReactNod
|
||||
key={item.href}
|
||||
href={item.href}
|
||||
className={cn(
|
||||
"flex flex-col items-center gap-0.5 py-1 px-2 rounded-lg transition-colors",
|
||||
isActive ? "text-trust-blue" : "text-muted-foreground"
|
||||
"flex flex-col items-center gap-0.5 py-1 px-2 transition-colors",
|
||||
isActive ? "text-promise-blue" : "text-gray-400"
|
||||
)}
|
||||
>
|
||||
<item.icon className="h-5 w-5" />
|
||||
|
||||
@@ -9,15 +9,15 @@ export default function DashboardLoading() {
|
||||
</div>
|
||||
<div className="grid grid-cols-2 lg:grid-cols-4 gap-4">
|
||||
{[...Array(4)].map((_, i) => (
|
||||
<Skeleton key={i} className="h-24 rounded-2xl" />
|
||||
<Skeleton key={i} className="h-24 rounded-lg" />
|
||||
))}
|
||||
</div>
|
||||
<Skeleton className="h-16 rounded-2xl" />
|
||||
<Skeleton className="h-16 rounded-lg" />
|
||||
<div className="grid md:grid-cols-2 gap-4">
|
||||
<Skeleton className="h-64 rounded-2xl" />
|
||||
<Skeleton className="h-64 rounded-2xl" />
|
||||
<Skeleton className="h-64 rounded-lg" />
|
||||
<Skeleton className="h-64 rounded-lg" />
|
||||
</div>
|
||||
<Skeleton className="h-96 rounded-2xl" />
|
||||
<Skeleton className="h-96 rounded-lg" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -43,9 +43,9 @@ function RolePicker({ onSelect }: { onSelect: (role: string) => void }) {
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<button
|
||||
onClick={() => onSelect("charity")}
|
||||
className="rounded-2xl border-2 border-gray-100 hover:border-trust-blue bg-white p-5 text-center space-y-2 transition-all hover:shadow-md group"
|
||||
className="rounded-lg border-2 border-gray-100 hover:border-trust-blue bg-white p-5 text-center space-y-2 transition-all hover:shadow-md group"
|
||||
>
|
||||
<div className="mx-auto w-12 h-12 rounded-xl bg-trust-blue/10 flex items-center justify-center group-hover:scale-110 transition-transform">
|
||||
<div className="mx-auto w-12 h-12 rounded-lg bg-trust-blue/10 flex items-center justify-center group-hover:scale-110 transition-transform">
|
||||
<Building2 className="h-6 w-6 text-trust-blue" />
|
||||
</div>
|
||||
<p className="text-sm font-bold text-gray-900">Charity / Mosque</p>
|
||||
@@ -53,9 +53,9 @@ function RolePicker({ onSelect }: { onSelect: (role: string) => void }) {
|
||||
</button>
|
||||
<button
|
||||
onClick={() => onSelect("fundraiser")}
|
||||
className="rounded-2xl border-2 border-gray-100 hover:border-warm-amber bg-white p-5 text-center space-y-2 transition-all hover:shadow-md group"
|
||||
className="rounded-lg border-2 border-gray-100 hover:border-warm-amber bg-white p-5 text-center space-y-2 transition-all hover:shadow-md group"
|
||||
>
|
||||
<div className="mx-auto w-12 h-12 rounded-xl bg-warm-amber/10 flex items-center justify-center group-hover:scale-110 transition-transform">
|
||||
<div className="mx-auto w-12 h-12 rounded-lg bg-warm-amber/10 flex items-center justify-center group-hover:scale-110 transition-transform">
|
||||
<Heart className="h-6 w-6 text-warm-amber" />
|
||||
</div>
|
||||
<p className="text-sm font-bold text-gray-900">Personal Fundraiser</p>
|
||||
@@ -85,14 +85,14 @@ function GettingStartedBanner({
|
||||
const isFirstTime = ob.completed === 0 && (!ob.orgType || ob.orgType === "charity")
|
||||
|
||||
return (
|
||||
<div className="rounded-2xl border border-trust-blue/20 bg-gradient-to-r from-trust-blue/5 via-white to-warm-amber/5 p-5 space-y-4 relative">
|
||||
<div className="rounded-lg border border-trust-blue/20 bg-paper p-5 space-y-4 relative">
|
||||
{/* Dismiss X */}
|
||||
<button onClick={onDismiss} className="absolute top-3 right-3 text-muted-foreground hover:text-foreground p-1">
|
||||
<X className="h-4 w-4" />
|
||||
</button>
|
||||
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="h-10 w-10 rounded-xl bg-gradient-to-br from-trust-blue to-blue-600 flex items-center justify-center flex-shrink-0">
|
||||
<div className="h-10 w-10 rounded-lg bg-midnight flex items-center justify-center flex-shrink-0">
|
||||
<span className="text-white text-lg">🤲</span>
|
||||
</div>
|
||||
<div>
|
||||
@@ -100,7 +100,7 @@ function GettingStartedBanner({
|
||||
{isFirstTime ? "Welcome! What best describes you?" : `Getting started · ${ob.completed}/${ob.total}`}
|
||||
</h2>
|
||||
{!isFirstTime && (
|
||||
<Progress value={(ob.completed / ob.total) * 100} className="h-1.5 mt-1.5 w-32" indicatorClassName="bg-gradient-to-r from-trust-blue to-success-green" />
|
||||
<Progress value={(ob.completed / ob.total) * 100} className="h-1.5 mt-1.5 w-32" indicatorClassName="bg-promise-blue" />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
@@ -113,7 +113,7 @@ function GettingStartedBanner({
|
||||
const isNext = !step.done && ob.steps.slice(0, i).every(s => s.done)
|
||||
return (
|
||||
<Link key={step.id} href={step.href}>
|
||||
<div className={`flex items-center gap-2.5 rounded-xl border px-3 py-2.5 transition-all ${
|
||||
<div className={`flex items-center gap-2.5 rounded-lg border px-3 py-2.5 transition-all ${
|
||||
step.done ? "bg-success-green/5 border-success-green/20" :
|
||||
isNext ? "bg-trust-blue/5 border-trust-blue/20 shadow-sm" :
|
||||
"bg-white border-gray-100"
|
||||
@@ -233,7 +233,7 @@ export default function DashboardPage() {
|
||||
<Card className={isEmpty ? "opacity-60" : ""}>
|
||||
<CardContent className="pt-5 pb-4">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="rounded-xl bg-trust-blue/10 p-2.5"><Users className="h-5 w-5 text-trust-blue" /></div>
|
||||
<div className="rounded-lg bg-trust-blue/10 p-2.5"><Users className="h-5 w-5 text-trust-blue" /></div>
|
||||
<div>
|
||||
<p className="text-2xl font-black">{s.totalPledges}</p>
|
||||
<p className="text-xs text-muted-foreground">Total Pledges</p>
|
||||
@@ -244,7 +244,7 @@ export default function DashboardPage() {
|
||||
<Card className={isEmpty ? "opacity-60" : ""}>
|
||||
<CardContent className="pt-5 pb-4">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="rounded-xl bg-warm-amber/10 p-2.5"><Banknote className="h-5 w-5 text-warm-amber" /></div>
|
||||
<div className="rounded-lg bg-warm-amber/10 p-2.5"><Banknote className="h-5 w-5 text-warm-amber" /></div>
|
||||
<div>
|
||||
<p className="text-2xl font-black">{formatPence(s.totalPledgedPence)}</p>
|
||||
<p className="text-xs text-muted-foreground">Total Pledged</p>
|
||||
@@ -255,7 +255,7 @@ export default function DashboardPage() {
|
||||
<Card className={isEmpty ? "opacity-60" : ""}>
|
||||
<CardContent className="pt-5 pb-4">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="rounded-xl bg-success-green/10 p-2.5"><TrendingUp className="h-5 w-5 text-success-green" /></div>
|
||||
<div className="rounded-lg bg-success-green/10 p-2.5"><TrendingUp className="h-5 w-5 text-success-green" /></div>
|
||||
<div>
|
||||
<p className="text-2xl font-black">{formatPence(s.totalCollectedPence)}</p>
|
||||
<p className="text-xs text-muted-foreground">Collected ({s.collectionRate}%)</p>
|
||||
@@ -266,7 +266,7 @@ export default function DashboardPage() {
|
||||
<Card className={isEmpty ? "opacity-60" : s.overdueRate > 10 ? "border-danger-red/30" : ""}>
|
||||
<CardContent className="pt-5 pb-4">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="rounded-xl bg-danger-red/10 p-2.5"><AlertTriangle className="h-5 w-5 text-danger-red" /></div>
|
||||
<div className="rounded-lg bg-danger-red/10 p-2.5"><AlertTriangle className="h-5 w-5 text-danger-red" /></div>
|
||||
<div>
|
||||
<p className="text-2xl font-black">{byStatus.overdue || 0}</p>
|
||||
<p className="text-xs text-muted-foreground">Overdue</p>
|
||||
@@ -283,7 +283,7 @@ export default function DashboardPage() {
|
||||
<span className="text-sm font-medium">Pledged → Collected</span>
|
||||
<span className="text-sm font-bold text-muted-foreground">{s.collectionRate}%</span>
|
||||
</div>
|
||||
<Progress value={s.collectionRate} indicatorClassName="bg-gradient-to-r from-trust-blue to-success-green" />
|
||||
<Progress value={s.collectionRate} indicatorClassName="bg-promise-blue" />
|
||||
<div className="flex justify-between mt-2 text-xs text-muted-foreground">
|
||||
<span>{formatPence(s.totalCollectedPence)} collected</span>
|
||||
<span>{formatPence(s.totalPledgedPence - s.totalCollectedPence)} outstanding</span>
|
||||
|
||||
@@ -251,7 +251,7 @@ export default function PledgesPage() {
|
||||
|
||||
{/* Collection progress */}
|
||||
<div className="flex items-center gap-4">
|
||||
<Progress value={collectionRate} className="flex-1 h-2" indicatorClassName="bg-gradient-to-r from-trust-blue to-success-green" />
|
||||
<Progress value={collectionRate} className="flex-1 h-2" indicatorClassName="bg-promise-blue" />
|
||||
<span className="text-sm font-medium text-muted-foreground whitespace-nowrap">
|
||||
{formatPence(stats.totalCollected)} / {formatPence(stats.totalPledged)}
|
||||
</span>
|
||||
|
||||
@@ -130,7 +130,7 @@ export default function ReconcilePage() {
|
||||
</div>
|
||||
|
||||
{/* File upload */}
|
||||
<div className="border-2 border-dashed border-gray-200 rounded-2xl p-8 text-center hover:border-trust-blue/50 transition-colors">
|
||||
<div className="border-2 border-dashed border-gray-200 rounded-lg p-8 text-center hover:border-trust-blue/50 transition-colors">
|
||||
<Upload className="h-10 w-10 text-muted-foreground mx-auto mb-3" />
|
||||
<input
|
||||
type="file"
|
||||
|
||||
@@ -66,7 +66,7 @@ export default function SettingsPage() {
|
||||
<p className="text-sm text-muted-foreground mt-0.5">Configure your organisation's payment details and integrations</p>
|
||||
</div>
|
||||
|
||||
{error && <div className="rounded-xl bg-danger-red/10 border border-danger-red/20 p-3 text-sm text-danger-red">{error}</div>}
|
||||
{error && <div className="rounded-lg bg-danger-red/10 border border-danger-red/20 p-3 text-sm text-danger-red">{error}</div>}
|
||||
|
||||
{/* WhatsApp — MOST IMPORTANT, first */}
|
||||
<WhatsAppPanel />
|
||||
@@ -194,7 +194,7 @@ function WhatsAppPanel() {
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="w-12 h-12 rounded-full bg-[#25D366]/10 flex items-center justify-center">
|
||||
<div className="w-12 h-12 rounded-lg bg-[#25D366]/10 flex items-center justify-center">
|
||||
<Smartphone className="h-6 w-6 text-[#25D366]" />
|
||||
</div>
|
||||
<div>
|
||||
@@ -240,7 +240,7 @@ function WhatsAppPanel() {
|
||||
<div className="relative">
|
||||
{/* Crop to QR area: the screenshot shows full WhatsApp web page.
|
||||
QR code is roughly in center. We use overflow hidden + object positioning. */}
|
||||
<div className="w-72 h-72 rounded-xl border-2 border-[#25D366]/20 overflow-hidden bg-white">
|
||||
<div className="w-72 h-72 rounded-lg border-2 border-[#25D366]/20 overflow-hidden bg-white">
|
||||
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||
<img
|
||||
src={qrImage}
|
||||
@@ -254,7 +254,7 @@ function WhatsAppPanel() {
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="w-72 h-72 rounded-xl border-2 border-dashed border-muted flex items-center justify-center">
|
||||
<div className="w-72 h-72 rounded-lg border-2 border-dashed border-muted flex items-center justify-center">
|
||||
<Loader2 className="h-8 w-8 text-muted-foreground animate-spin" />
|
||||
</div>
|
||||
)}
|
||||
@@ -285,9 +285,9 @@ function WhatsAppPanel() {
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="rounded-xl bg-muted/50 p-4 space-y-3">
|
||||
<div className="rounded-lg bg-muted/50 p-4 space-y-3">
|
||||
<div className="flex items-start gap-3">
|
||||
<div className="w-10 h-10 rounded-full bg-[#25D366]/10 flex items-center justify-center flex-shrink-0">
|
||||
<div className="w-10 h-10 rounded-lg bg-[#25D366]/10 flex items-center justify-center flex-shrink-0">
|
||||
<Smartphone className="h-5 w-5 text-[#25D366]" />
|
||||
</div>
|
||||
<div>
|
||||
|
||||
@@ -125,7 +125,7 @@ export default function SetupPage() {
|
||||
<div className="flex items-center justify-between">
|
||||
{steps.map((s, i) => (
|
||||
<div key={s.num} className="flex items-center">
|
||||
<div className={`w-10 h-10 rounded-full flex items-center justify-center text-sm font-bold transition-all ${
|
||||
<div className={`w-10 h-10 rounded-lg flex items-center justify-center text-sm font-bold transition-all ${
|
||||
step >= s.num ? "bg-trust-blue text-white" : "bg-gray-100 text-gray-400"
|
||||
}`}>
|
||||
{step > s.num ? <CheckCircle2 className="h-5 w-5" /> : <s.icon className="h-5 w-5" />}
|
||||
@@ -231,14 +231,14 @@ export default function SetupPage() {
|
||||
{step === 4 && (
|
||||
<Card className="border-success-green/30">
|
||||
<CardHeader className="text-center pb-2">
|
||||
<div className="mx-auto w-16 h-16 rounded-full bg-success-green/10 flex items-center justify-center mb-3">
|
||||
<div className="mx-auto w-14 h-14 bg-success-green/10 flex items-center justify-center mb-3">
|
||||
<Sparkles className="h-8 w-8 text-success-green" />
|
||||
</div>
|
||||
<CardTitle>You're All Set! 🎉</CardTitle>
|
||||
<p className="text-sm text-muted-foreground">Your charity is ready to collect pledges.</p>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<div className="bg-gray-50 rounded-xl p-4 space-y-2">
|
||||
<div className="bg-gray-50 rounded-lg p-4 space-y-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-sm text-muted-foreground">Charity</span>
|
||||
<span className="text-sm font-medium">{orgName}</span>
|
||||
@@ -254,7 +254,7 @@ export default function SetupPage() {
|
||||
</div>
|
||||
|
||||
{setupResult?.qrToken && (
|
||||
<div className="bg-trust-blue/5 rounded-xl p-4 text-center space-y-2">
|
||||
<div className="bg-trust-blue/5 rounded-lg p-4 text-center space-y-2">
|
||||
<QrCode className="h-8 w-8 text-trust-blue mx-auto" />
|
||||
<p className="text-sm font-medium">Your pledge link is ready</p>
|
||||
<code className="text-xs bg-white px-3 py-1.5 rounded-lg border block overflow-x-auto">
|
||||
@@ -278,7 +278,7 @@ export default function SetupPage() {
|
||||
|
||||
{/* Tips */}
|
||||
{step < 4 && (
|
||||
<div className="bg-warm-amber/5 rounded-xl border border-warm-amber/20 p-4">
|
||||
<div className="bg-warm-amber/5 rounded-lg border border-warm-amber/20 p-4">
|
||||
<p className="text-xs font-semibold text-warm-amber mb-1">💡 Tip</p>
|
||||
{step === 1 && <p className="text-xs text-muted-foreground">Your charity name appears on the donor pledge page and WhatsApp receipts.</p>}
|
||||
{step === 2 && <p className="text-xs text-muted-foreground">Bank details are shown to donors who choose "Bank Transfer". Each pledge gets a unique reference number for easy reconciliation.</p>}
|
||||
|
||||
@@ -82,7 +82,7 @@ export default function PublicEventPage() {
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center bg-gradient-to-br from-trust-blue/5 via-white to-warm-amber/5">
|
||||
<div className="min-h-screen flex items-center justify-center bg-paper">
|
||||
<Loader2 className="h-8 w-8 text-trust-blue animate-spin" />
|
||||
</div>
|
||||
)
|
||||
@@ -90,7 +90,7 @@ export default function PublicEventPage() {
|
||||
|
||||
if (error || !data) {
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center bg-gradient-to-br from-trust-blue/5 via-white to-warm-amber/5 p-4">
|
||||
<div className="min-h-screen flex items-center justify-center bg-paper p-4">
|
||||
<div className="text-center space-y-4">
|
||||
<Heart className="h-12 w-12 text-muted-foreground mx-auto" />
|
||||
<h1 className="text-xl font-bold">Event not found</h1>
|
||||
@@ -106,7 +106,7 @@ export default function PublicEventPage() {
|
||||
const giftAidBonus = Math.round(data.stats.totalPledged * 0.25 * (data.stats.giftAidCount / Math.max(1, data.stats.pledgeCount)))
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gradient-to-br from-trust-blue/5 via-white to-warm-amber/5">
|
||||
<div className="min-h-screen bg-paper">
|
||||
<div className="max-w-lg mx-auto px-4 py-8 space-y-6">
|
||||
{/* Header */}
|
||||
<div className="text-center space-y-3">
|
||||
@@ -146,7 +146,7 @@ export default function PublicEventPage() {
|
||||
</div>
|
||||
<div className="h-4 rounded-full bg-gray-100 overflow-hidden">
|
||||
<div
|
||||
className="h-full rounded-full bg-gradient-to-r from-trust-blue to-success-green transition-all duration-1000"
|
||||
className="h-full rounded-full bg-promise-blue transition-all duration-1000"
|
||||
style={{ width: `${progressPercent}%` }}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -7,25 +7,25 @@
|
||||
@layer base {
|
||||
:root {
|
||||
--background: 0 0% 100%;
|
||||
--foreground: 222.2 84% 4.9%;
|
||||
--foreground: 222 47% 11%;
|
||||
--card: 0 0% 100%;
|
||||
--card-foreground: 222.2 84% 4.9%;
|
||||
--card-foreground: 222 47% 11%;
|
||||
--popover: 0 0% 100%;
|
||||
--popover-foreground: 222.2 84% 4.9%;
|
||||
--primary: 221.2 83.2% 53.3%;
|
||||
--primary-foreground: 210 40% 98%;
|
||||
--secondary: 210 40% 96.1%;
|
||||
--secondary-foreground: 222.2 47.4% 11.2%;
|
||||
--muted: 210 40% 96.1%;
|
||||
--muted-foreground: 215.4 16.3% 46.9%;
|
||||
--accent: 210 40% 96.1%;
|
||||
--accent-foreground: 222.2 47.4% 11.2%;
|
||||
--destructive: 0 84.2% 60.2%;
|
||||
--destructive-foreground: 210 40% 98%;
|
||||
--border: 214.3 31.8% 91.4%;
|
||||
--input: 214.3 31.8% 91.4%;
|
||||
--ring: 221.2 83.2% 53.3%;
|
||||
--radius: 0.75rem;
|
||||
--popover-foreground: 222 47% 11%;
|
||||
--primary: 224 76% 40%;
|
||||
--primary-foreground: 0 0% 100%;
|
||||
--secondary: 220 14% 96%;
|
||||
--secondary-foreground: 222 47% 11%;
|
||||
--muted: 220 14% 96%;
|
||||
--muted-foreground: 215 16% 47%;
|
||||
--accent: 220 14% 96%;
|
||||
--accent-foreground: 222 47% 11%;
|
||||
--destructive: 0 72% 51%;
|
||||
--destructive-foreground: 0 0% 100%;
|
||||
--border: 220 13% 91%;
|
||||
--input: 220 13% 91%;
|
||||
--ring: 224 76% 40%;
|
||||
--radius: 0.5rem;
|
||||
}
|
||||
|
||||
* {
|
||||
@@ -45,9 +45,9 @@
|
||||
}
|
||||
}
|
||||
|
||||
/* Premium animations */
|
||||
/* Animations — subtle, purposeful */
|
||||
@keyframes fadeUp {
|
||||
from { opacity: 0; transform: translateY(12px); }
|
||||
from { opacity: 0; transform: translateY(8px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
|
||||
@@ -56,55 +56,32 @@
|
||||
to { opacity: 1; }
|
||||
}
|
||||
|
||||
@keyframes scaleIn {
|
||||
from { opacity: 0; transform: scale(0.9); }
|
||||
to { opacity: 1; transform: scale(1); }
|
||||
}
|
||||
|
||||
@keyframes slideDown {
|
||||
from { opacity: 0; transform: translateY(-8px); }
|
||||
from { opacity: 0; transform: translateY(-4px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
|
||||
@keyframes pulse-ring {
|
||||
0% { transform: scale(0.8); opacity: 1; }
|
||||
100% { transform: scale(2); opacity: 0; }
|
||||
}
|
||||
|
||||
@keyframes shimmer {
|
||||
0% { background-position: -200% 0; }
|
||||
100% { background-position: 200% 0; }
|
||||
}
|
||||
|
||||
@keyframes confetti-fall {
|
||||
0% { transform: translateY(-10vh) rotate(0deg); opacity: 1; }
|
||||
100% { transform: translateY(100vh) rotate(720deg); opacity: 0; }
|
||||
}
|
||||
|
||||
@keyframes bounce-gentle {
|
||||
0%, 100% { transform: translateY(0); }
|
||||
50% { transform: translateY(-4px); }
|
||||
}
|
||||
|
||||
@keyframes counter-roll {
|
||||
from { transform: translateY(100%); opacity: 0; }
|
||||
to { transform: translateY(0); opacity: 1; }
|
||||
}
|
||||
|
||||
.animate-fade-up { animation: fadeUp 0.5s ease-out forwards; }
|
||||
.animate-fade-up { animation: fadeUp 0.4s ease-out forwards; }
|
||||
.animate-fade-in { animation: fadeIn 0.3s ease-out forwards; }
|
||||
.animate-scale-in { animation: scaleIn 0.3s ease-out forwards; }
|
||||
.animate-slide-down { animation: slideDown 0.3s ease-out forwards; }
|
||||
.animate-pulse-ring { animation: pulse-ring 1.5s ease-out infinite; }
|
||||
.animate-shimmer {
|
||||
background: linear-gradient(90deg, transparent 30%, rgba(255,255,255,0.4) 50%, transparent 70%);
|
||||
background-size: 200% 100%;
|
||||
animation: shimmer 2s infinite;
|
||||
}
|
||||
.animate-bounce-gentle { animation: bounce-gentle 2s ease-in-out infinite; }
|
||||
.animate-counter-roll { animation: counter-roll 0.4s ease-out forwards; }
|
||||
|
||||
/* Stagger children animations */
|
||||
/* Stagger children */
|
||||
.stagger-children > * { opacity: 0; animation: fadeUp 0.4s ease-out forwards; }
|
||||
.stagger-children > *:nth-child(1) { animation-delay: 0ms; }
|
||||
.stagger-children > *:nth-child(2) { animation-delay: 60ms; }
|
||||
@@ -112,34 +89,6 @@
|
||||
.stagger-children > *:nth-child(4) { animation-delay: 180ms; }
|
||||
.stagger-children > *:nth-child(5) { animation-delay: 240ms; }
|
||||
.stagger-children > *:nth-child(6) { animation-delay: 300ms; }
|
||||
.stagger-children > *:nth-child(7) { animation-delay: 360ms; }
|
||||
.stagger-children > *:nth-child(8) { animation-delay: 420ms; }
|
||||
|
||||
/* Glass effects */
|
||||
.glass {
|
||||
background: rgba(255, 255, 255, 0.7);
|
||||
backdrop-filter: blur(20px);
|
||||
-webkit-backdrop-filter: blur(20px);
|
||||
border: 1px solid rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
|
||||
.glass-dark {
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
backdrop-filter: blur(20px);
|
||||
-webkit-backdrop-filter: blur(20px);
|
||||
}
|
||||
|
||||
/* Premium card hover */
|
||||
.card-hover {
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
.card-hover:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 12px 40px -8px rgba(0,0,0,0.12);
|
||||
}
|
||||
.card-hover:active {
|
||||
transform: translateY(0) scale(0.98);
|
||||
}
|
||||
|
||||
/* Number input clean */
|
||||
input[type="number"]::-webkit-inner-spin-button,
|
||||
@@ -151,20 +100,10 @@ input[type="number"] {
|
||||
-moz-appearance: textfield;
|
||||
}
|
||||
|
||||
/* Scrollbar */
|
||||
::-webkit-scrollbar {
|
||||
width: 4px;
|
||||
}
|
||||
::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: hsl(var(--muted));
|
||||
border-radius: 2px;
|
||||
}
|
||||
/* Scrollbar — thin, unobtrusive */
|
||||
::-webkit-scrollbar { width: 4px; }
|
||||
::-webkit-scrollbar-track { background: transparent; }
|
||||
::-webkit-scrollbar-thumb { background: #d1d5db; border-radius: 2px; }
|
||||
|
||||
/* Selection */
|
||||
::selection {
|
||||
background: #1e40af20;
|
||||
color: #1e40af;
|
||||
}
|
||||
/* Selection — brand blue */
|
||||
::selection { background: #1e40af15; color: #1e40af; }
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
export default function PledgeLoading() {
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center bg-gradient-to-br from-trust-blue/5 via-white to-warm-amber/5">
|
||||
<div className="min-h-screen flex items-center justify-center bg-paper">
|
||||
<div className="text-center space-y-4">
|
||||
<div className="inline-flex items-center justify-center w-16 h-16 rounded-full bg-trust-blue/10 animate-pulse">
|
||||
<div className="inline-flex items-center justify-center w-14 h-14 bg-trust-blue/10 animate-pulse">
|
||||
<div className="w-8 h-8 rounded-full bg-trust-blue/30" />
|
||||
</div>
|
||||
<p className="text-muted-foreground animate-pulse">Loading pledge page...</p>
|
||||
|
||||
@@ -189,8 +189,8 @@ export default function PledgePage() {
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="min-h-screen flex flex-col items-center justify-center bg-gradient-to-br from-trust-blue/5 via-white to-warm-amber/5 gap-4">
|
||||
<div className="w-12 h-12 rounded-2xl bg-gradient-to-br from-trust-blue to-blue-600 flex items-center justify-center animate-pulse">
|
||||
<div className="min-h-screen flex flex-col items-center justify-center bg-paper gap-4">
|
||||
<div className="w-12 h-12 rounded-lg bg-midnight flex items-center justify-center animate-pulse">
|
||||
<span className="text-white text-xl">🤲</span>
|
||||
</div>
|
||||
<p className="text-trust-blue font-medium animate-pulse">Loading...</p>
|
||||
@@ -200,7 +200,7 @@ export default function PledgePage() {
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center bg-gradient-to-br from-trust-blue/5 via-white to-warm-amber/5 p-4">
|
||||
<div className="min-h-screen flex items-center justify-center bg-paper p-4">
|
||||
<div className="text-center space-y-4 animate-fade-up">
|
||||
<div className="text-5xl">😔</div>
|
||||
<h1 className="text-xl font-bold text-gray-900">Something went wrong</h1>
|
||||
@@ -260,11 +260,11 @@ export default function PledgePage() {
|
||||
const progressPercent = progressMap[step] || 10
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gradient-to-br from-trust-blue/5 via-white to-warm-amber/5">
|
||||
<div className="min-h-screen bg-paper">
|
||||
{/* Progress bar */}
|
||||
<div className="fixed top-0 left-0 right-0 h-1 bg-gray-100 z-50">
|
||||
<div
|
||||
className="h-full bg-gradient-to-r from-trust-blue to-success-green transition-all duration-700 ease-out"
|
||||
className="h-full bg-promise-blue transition-all duration-700 ease-out"
|
||||
style={{ width: `${progressPercent}%` }}
|
||||
/>
|
||||
</div>
|
||||
@@ -282,7 +282,7 @@ export default function PledgePage() {
|
||||
|
||||
{/* Back button */}
|
||||
{backableSteps.has(step) && (
|
||||
<div className="fixed bottom-0 left-0 right-0 pb-6 pt-4 px-4 bg-gradient-to-t from-white via-white/80 to-transparent">
|
||||
<div className="fixed bottom-0 left-0 right-0 pb-6 pt-4 px-4 bg-white">
|
||||
<button
|
||||
onClick={() => setStep(getBackStep(step))}
|
||||
className="text-sm text-muted-foreground hover:text-foreground transition-colors tap-target flex items-center gap-1"
|
||||
|
||||
@@ -64,7 +64,7 @@ export function AmountStep({ onSelect, eventName, eventId }: Props) {
|
||||
<div className="max-w-md mx-auto pt-2 space-y-6 animate-fade-up">
|
||||
{/* Hero */}
|
||||
<div className="text-center space-y-3">
|
||||
<div className="inline-flex items-center justify-center w-14 h-14 rounded-2xl bg-gradient-to-br from-trust-blue to-blue-600 shadow-lg shadow-trust-blue/30">
|
||||
<div className="inline-flex items-center justify-center w-14 h-14 rounded-lg bg-midnight">
|
||||
<Heart className="h-7 w-7 text-white" />
|
||||
</div>
|
||||
<h1 className="text-3xl font-black text-gray-900 tracking-tight">
|
||||
@@ -80,7 +80,7 @@ export function AmountStep({ onSelect, eventName, eventId }: Props) {
|
||||
<div className="flex items-center justify-center gap-2 animate-fade-in">
|
||||
<div className="flex -space-x-2">
|
||||
{[...Array(3)].map((_, i) => (
|
||||
<div key={i} className="w-6 h-6 rounded-full bg-gradient-to-br from-trust-blue to-blue-400 border-2 border-white flex items-center justify-center">
|
||||
<div key={i} className="w-6 h-6 rounded-full bg-promise-blue border-2 border-white flex items-center justify-center">
|
||||
<span className="text-[8px] text-white font-bold">{["A", "S", "M"][i]}</span>
|
||||
</div>
|
||||
))}
|
||||
@@ -106,9 +106,9 @@ export function AmountStep({ onSelect, eventName, eventId }: Props) {
|
||||
onMouseEnter={() => setHovering(amount)}
|
||||
onMouseLeave={() => setHovering(null)}
|
||||
className={`
|
||||
relative tap-target rounded-2xl border-2 py-4 text-center font-bold transition-all duration-200
|
||||
relative tap-target rounded-lg border-2 py-4 text-center font-bold transition-all duration-200
|
||||
${isSelected
|
||||
? "border-trust-blue bg-trust-blue text-white shadow-xl shadow-trust-blue/30 scale-[1.03]"
|
||||
? "border-trust-blue bg-trust-blue text-white"
|
||||
: isHovering
|
||||
? "border-trust-blue/40 bg-trust-blue/5 text-gray-900"
|
||||
: "border-gray-200 bg-white text-gray-900 hover:border-trust-blue/30"
|
||||
@@ -118,7 +118,7 @@ export function AmountStep({ onSelect, eventName, eventId }: Props) {
|
||||
<span className="text-xl">£{pounds >= 1000 ? `${pounds / 1000}k` : pounds}</span>
|
||||
{/* Gift Aid indicator */}
|
||||
{isSelected && (
|
||||
<div className="absolute -top-2 -right-2 bg-success-green text-white text-[10px] font-bold px-1.5 py-0.5 rounded-full animate-scale-in shadow-sm">
|
||||
<div className="absolute -top-2 -right-2 bg-success-green text-white text-[10px] font-bold px-1.5 py-0.5 rounded-full animate-fade-in shadow-sm">
|
||||
+£{Math.round(amount * 0.25 / 100)}
|
||||
</div>
|
||||
)}
|
||||
@@ -152,14 +152,14 @@ export function AmountStep({ onSelect, eventName, eventId }: Props) {
|
||||
placeholder="0"
|
||||
value={custom}
|
||||
onChange={(e) => handleCustomChange(e.target.value)}
|
||||
className="w-full pl-10 pr-4 h-16 text-2xl font-black text-center rounded-2xl border-2 border-gray-200 bg-white focus:border-trust-blue focus:ring-4 focus:ring-trust-blue/10 outline-none transition-all"
|
||||
className="w-full pl-10 pr-4 h-16 text-2xl font-black text-center rounded-lg border-2 border-gray-200 bg-white focus:border-trust-blue focus:ring-4 focus:ring-trust-blue/10 outline-none transition-all"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Live Gift Aid preview */}
|
||||
{isValid && (
|
||||
<div className="rounded-2xl bg-gradient-to-r from-success-green/5 to-success-green/10 border border-success-green/20 p-4 text-center animate-scale-in">
|
||||
<div className="rounded-lg bg-fulfilled-green/5 border border-success-green/20 p-4 text-center animate-fade-in">
|
||||
<p className="text-sm">
|
||||
<span className="font-bold text-success-green">With Gift Aid:</span>{" "}
|
||||
your £{(activeAmount / 100).toFixed(0)} becomes{" "}
|
||||
|
||||
@@ -77,8 +77,8 @@ export function BankInstructionsStep({ pledge, amount, eventName, donorPhone }:
|
||||
return (
|
||||
<div className="max-w-md mx-auto pt-8 text-center space-y-6 animate-fade-up">
|
||||
<div className="relative inline-flex items-center justify-center">
|
||||
<div className="absolute w-20 h-20 rounded-full bg-success-green/20 animate-pulse-ring" />
|
||||
<div className="relative w-20 h-20 rounded-full bg-gradient-to-br from-success-green to-emerald-500 flex items-center justify-center shadow-xl shadow-success-green/30">
|
||||
<div className="absolute w-16 h-16 bg-success-green/20" />
|
||||
<div className="relative w-16 h-16 bg-fulfilled-green flex items-center justify-center">
|
||||
<Check className="h-10 w-10 text-white" strokeWidth={3} />
|
||||
</div>
|
||||
</div>
|
||||
@@ -88,7 +88,7 @@ export function BankInstructionsStep({ pledge, amount, eventName, donorPhone }:
|
||||
</p>
|
||||
|
||||
{whatsappSent && (
|
||||
<div className="rounded-xl bg-[#25D366]/10 border border-[#25D366]/20 p-3 text-sm text-[#25D366] font-medium flex items-center justify-center gap-2 animate-fade-in">
|
||||
<div className="rounded-lg bg-[#25D366]/10 border border-[#25D366]/20 p-3 text-sm text-[#25D366] font-medium flex items-center justify-center gap-2 animate-fade-in">
|
||||
<MessageCircle className="h-4 w-4" /> Details sent to your WhatsApp ✓
|
||||
</div>
|
||||
)}
|
||||
@@ -107,7 +107,7 @@ export function BankInstructionsStep({ pledge, amount, eventName, donorPhone }:
|
||||
</Card>
|
||||
|
||||
{/* Share */}
|
||||
<div className="rounded-2xl bg-gradient-to-br from-warm-amber/5 to-orange-50 border border-warm-amber/20 p-5 space-y-3">
|
||||
<div className="rounded-lg bg-generosity-gold/5 border border-warm-amber/20 p-5 space-y-3">
|
||||
<div className="flex items-center justify-center gap-2">
|
||||
<Sparkles className="h-4 w-4 text-warm-amber" />
|
||||
<p className="text-sm font-bold text-gray-900">Know someone who'd donate too?</p>
|
||||
@@ -164,7 +164,7 @@ export function BankInstructionsStep({ pledge, amount, eventName, donorPhone }:
|
||||
return (
|
||||
<div className="max-w-md mx-auto pt-2 space-y-5 animate-fade-up">
|
||||
<div className="text-center space-y-2">
|
||||
<div className="inline-flex items-center justify-center w-14 h-14 rounded-2xl bg-gradient-to-br from-trust-blue to-blue-600 shadow-lg shadow-trust-blue/30">
|
||||
<div className="inline-flex items-center justify-center w-14 h-14 rounded-lg bg-midnight">
|
||||
<span className="text-2xl">🏦</span>
|
||||
</div>
|
||||
<h1 className="text-2xl font-black text-gray-900 tracking-tight">
|
||||
@@ -176,7 +176,7 @@ export function BankInstructionsStep({ pledge, amount, eventName, donorPhone }:
|
||||
</div>
|
||||
|
||||
{whatsappSent && (
|
||||
<div className="rounded-xl bg-[#25D366]/10 border border-[#25D366]/20 p-2.5 text-xs text-[#25D366] font-medium flex items-center justify-center gap-2 animate-fade-in">
|
||||
<div className="rounded-lg bg-[#25D366]/10 border border-[#25D366]/20 p-2.5 text-xs text-[#25D366] font-medium flex items-center justify-center gap-2 animate-fade-in">
|
||||
<MessageCircle className="h-3.5 w-3.5" /> Bank details also sent to your WhatsApp
|
||||
</div>
|
||||
)}
|
||||
@@ -184,7 +184,7 @@ export function BankInstructionsStep({ pledge, amount, eventName, donorPhone }:
|
||||
{/* Bank details — tap to copy each field */}
|
||||
{bd && (
|
||||
<Card className="overflow-hidden">
|
||||
<div className="h-1 bg-gradient-to-r from-trust-blue to-blue-400" />
|
||||
<div className="h-1 bg-promise-blue" />
|
||||
<CardContent className="pt-4 divide-y">
|
||||
<CopyField label="Sort Code" value={bd.sortCode} fieldKey="sortCode" mono />
|
||||
<CopyField label="Account Number" value={bd.accountNo} fieldKey="accountNo" mono />
|
||||
@@ -194,7 +194,7 @@ export function BankInstructionsStep({ pledge, amount, eventName, donorPhone }:
|
||||
)}
|
||||
|
||||
{/* THE reference — the most important thing */}
|
||||
<div className="rounded-2xl border-2 border-trust-blue bg-gradient-to-br from-trust-blue/5 to-blue-50 p-5 text-center space-y-3">
|
||||
<div className="rounded-lg border-2 border-trust-blue bg-promise-blue/5 p-5 text-center space-y-3">
|
||||
<p className="text-xs font-bold text-trust-blue uppercase tracking-widest">
|
||||
Payment Reference
|
||||
</p>
|
||||
@@ -202,7 +202,7 @@ export function BankInstructionsStep({ pledge, amount, eventName, donorPhone }:
|
||||
onClick={() => copyField(pledge.reference, "reference")}
|
||||
className="group"
|
||||
>
|
||||
<p className="text-3xl font-mono font-black text-trust-blue tracking-wider group-hover:scale-105 transition-transform">
|
||||
<p className="text-3xl font-mono font-black text-trust-blue tracking-wider transition-transform">
|
||||
{pledge.reference}
|
||||
</p>
|
||||
</button>
|
||||
@@ -223,7 +223,7 @@ export function BankInstructionsStep({ pledge, amount, eventName, donorPhone }:
|
||||
</div>
|
||||
|
||||
{/* Open banking app hint */}
|
||||
<div className="rounded-xl bg-warm-amber/5 border border-warm-amber/20 p-3 text-center">
|
||||
<div className="rounded-lg bg-warm-amber/5 border border-warm-amber/20 p-3 text-center">
|
||||
<p className="text-xs font-medium text-warm-amber flex items-center justify-center gap-1.5">
|
||||
<ExternalLink className="h-3.5 w-3.5" />
|
||||
Open your banking app → New payment → Paste the details
|
||||
|
||||
@@ -171,7 +171,7 @@ export function CardPaymentStep({ amount, eventName, eventId, qrSourceId, onComp
|
||||
</div>
|
||||
|
||||
{/* Card form */}
|
||||
<div className="rounded-2xl border-2 border-gray-200 bg-white p-5 space-y-4">
|
||||
<div className="rounded-lg border-2 border-gray-200 bg-white p-5 space-y-4">
|
||||
{/* Card number */}
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="card-number">Card Number</Label>
|
||||
@@ -261,7 +261,7 @@ export function CardPaymentStep({ amount, eventName, eventId, qrSourceId, onComp
|
||||
</div>
|
||||
|
||||
{/* Gift Aid */}
|
||||
<label className="flex items-start gap-3 rounded-2xl border-2 border-gray-200 bg-white p-4 cursor-pointer hover:border-trust-blue/50 transition-colors">
|
||||
<label className="flex items-start gap-3 rounded-lg border-2 border-gray-200 bg-white p-4 cursor-pointer hover:border-trust-blue/50 transition-colors">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={giftAid}
|
||||
|
||||
@@ -130,8 +130,8 @@ export function ConfirmationStep({ pledge, amount, rail, eventName, shareUrl, do
|
||||
<div className="max-w-md mx-auto pt-6 text-center space-y-6 animate-fade-up">
|
||||
{/* Success icon with pulse */}
|
||||
<div className="relative inline-flex items-center justify-center">
|
||||
<div className="absolute w-20 h-20 rounded-full bg-success-green/20 animate-pulse-ring" />
|
||||
<div className="relative w-20 h-20 rounded-full bg-gradient-to-br from-success-green to-emerald-500 flex items-center justify-center shadow-xl shadow-success-green/30">
|
||||
<div className="absolute w-16 h-16 bg-success-green/20" />
|
||||
<div className="relative w-16 h-16 bg-fulfilled-green flex items-center justify-center">
|
||||
<Check className="h-10 w-10 text-white" strokeWidth={3} />
|
||||
</div>
|
||||
</div>
|
||||
@@ -150,7 +150,7 @@ export function ConfirmationStep({ pledge, amount, rail, eventName, shareUrl, do
|
||||
|
||||
{/* Details card */}
|
||||
<Card className="text-left overflow-hidden">
|
||||
<div className="h-1 bg-gradient-to-r from-trust-blue via-success-green to-warm-amber" />
|
||||
<div className="h-1 bg-promise-blue" />
|
||||
<CardContent className="pt-5 space-y-3 text-sm">
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-muted-foreground">Amount</span>
|
||||
@@ -190,19 +190,19 @@ export function ConfirmationStep({ pledge, amount, rail, eventName, shareUrl, do
|
||||
</Card>
|
||||
|
||||
{/* What happens next */}
|
||||
<div className="rounded-2xl bg-trust-blue/5 border border-trust-blue/10 p-4 text-left">
|
||||
<div className="rounded-lg bg-trust-blue/5 border border-trust-blue/10 p-4 text-left">
|
||||
<p className="text-sm font-semibold text-trust-blue mb-1">What happens next?</p>
|
||||
<p className="text-sm text-muted-foreground">{nextStepMessages[rail] || nextStepMessages.bank}</p>
|
||||
</div>
|
||||
|
||||
{whatsappSent && (
|
||||
<div className="rounded-xl bg-[#25D366]/10 border border-[#25D366]/20 p-3 text-sm text-[#25D366] font-medium flex items-center justify-center gap-2 animate-fade-in">
|
||||
<div className="rounded-lg bg-[#25D366]/10 border border-[#25D366]/20 p-3 text-sm text-[#25D366] font-medium flex items-center justify-center gap-2 animate-fade-in">
|
||||
<MessageCircle className="h-4 w-4" /> Receipt sent to your WhatsApp ✓
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Share */}
|
||||
<div className="rounded-2xl bg-gradient-to-br from-warm-amber/5 to-orange-50 border border-warm-amber/20 p-5 space-y-3">
|
||||
<div className="rounded-lg bg-generosity-gold/5 border border-warm-amber/20 p-5 space-y-3">
|
||||
<div className="flex items-center justify-center gap-2">
|
||||
<Sparkles className="h-4 w-4 text-warm-amber" />
|
||||
<p className="text-sm font-bold text-gray-900">
|
||||
|
||||
@@ -118,7 +118,7 @@ export function DirectDebitStep({ amount, eventName, organizationName, eventId,
|
||||
if (phase === "processing") {
|
||||
return (
|
||||
<div className="max-w-md mx-auto pt-16 text-center space-y-6">
|
||||
<div className="inline-flex items-center justify-center w-20 h-20 rounded-full bg-trust-blue/10">
|
||||
<div className="inline-flex items-center justify-center w-16 h-16 bg-trust-blue/10">
|
||||
<div className="h-10 w-10 border-4 border-trust-blue border-t-transparent rounded-full animate-spin" />
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
@@ -141,7 +141,7 @@ export function DirectDebitStep({ amount, eventName, organizationName, eventId,
|
||||
return (
|
||||
<div className="max-w-md mx-auto pt-4 space-y-6">
|
||||
<div className="text-center space-y-2">
|
||||
<div className="inline-flex items-center justify-center w-16 h-16 rounded-full bg-trust-blue/10 mb-2">
|
||||
<div className="inline-flex items-center justify-center w-14 h-14 bg-trust-blue/10 mb-2">
|
||||
<ShieldCheck className="h-8 w-8 text-trust-blue" />
|
||||
</div>
|
||||
<h1 className="text-2xl font-extrabold text-gray-900">
|
||||
@@ -153,7 +153,7 @@ export function DirectDebitStep({ amount, eventName, organizationName, eventId,
|
||||
</div>
|
||||
|
||||
{/* Summary card */}
|
||||
<div className="rounded-2xl border-2 border-gray-200 bg-white p-5 space-y-4">
|
||||
<div className="rounded-lg border-2 border-gray-200 bg-white p-5 space-y-4">
|
||||
<div className="grid grid-cols-2 gap-4 text-sm">
|
||||
<div>
|
||||
<p className="text-xs text-muted-foreground uppercase tracking-wider">Account Holder</p>
|
||||
@@ -189,7 +189,7 @@ export function DirectDebitStep({ amount, eventName, organizationName, eventId,
|
||||
</div>
|
||||
|
||||
{/* Guarantee box */}
|
||||
<div className="rounded-2xl bg-emerald-50 border border-emerald-200 p-4 space-y-2">
|
||||
<div className="rounded-lg bg-emerald-50 border border-emerald-200 p-4 space-y-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<ShieldCheck className="h-5 w-5 text-emerald-600" />
|
||||
<p className="text-sm font-semibold text-emerald-800">Direct Debit Guarantee</p>
|
||||
@@ -234,7 +234,7 @@ export function DirectDebitStep({ amount, eventName, organizationName, eventId,
|
||||
</div>
|
||||
|
||||
{/* Info banner */}
|
||||
<div className="rounded-2xl bg-trust-blue/5 border border-trust-blue/20 p-4 flex items-start gap-3">
|
||||
<div className="rounded-lg bg-trust-blue/5 border border-trust-blue/20 p-4 flex items-start gap-3">
|
||||
<Landmark className="h-5 w-5 text-trust-blue flex-shrink-0 mt-0.5" />
|
||||
<div>
|
||||
<p className="text-sm font-medium text-trust-blue">How it works</p>
|
||||
@@ -245,7 +245,7 @@ export function DirectDebitStep({ amount, eventName, organizationName, eventId,
|
||||
</div>
|
||||
|
||||
{/* Bank details form */}
|
||||
<div className="rounded-2xl border-2 border-gray-200 bg-white p-5 space-y-4">
|
||||
<div className="rounded-lg border-2 border-gray-200 bg-white p-5 space-y-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="dd-name">Account Holder Name</Label>
|
||||
<Input
|
||||
@@ -306,7 +306,7 @@ export function DirectDebitStep({ amount, eventName, organizationName, eventId,
|
||||
</div>
|
||||
|
||||
{/* Gift Aid */}
|
||||
<label className="flex items-start gap-3 rounded-2xl border-2 border-gray-200 bg-white p-4 cursor-pointer hover:border-trust-blue/50 transition-colors">
|
||||
<label className="flex items-start gap-3 rounded-lg border-2 border-gray-200 bg-white p-4 cursor-pointer hover:border-trust-blue/50 transition-colors">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={giftAid}
|
||||
@@ -322,7 +322,7 @@ export function DirectDebitStep({ amount, eventName, organizationName, eventId,
|
||||
</label>
|
||||
|
||||
{/* Direct Debit mandate agreement */}
|
||||
<label className={`flex items-start gap-3 rounded-2xl border-2 p-4 cursor-pointer transition-colors ${
|
||||
<label className={`flex items-start gap-3 rounded-lg border-2 p-4 cursor-pointer transition-colors ${
|
||||
errors.mandate ? "border-red-300 bg-red-50" : "border-gray-200 bg-white hover:border-trust-blue/50"
|
||||
}`}>
|
||||
<input
|
||||
@@ -349,7 +349,7 @@ export function DirectDebitStep({ amount, eventName, organizationName, eventId,
|
||||
</Button>
|
||||
|
||||
{/* DD Guarantee mini */}
|
||||
<div className="rounded-xl bg-emerald-50 border border-emerald-200 p-3 flex items-start gap-2">
|
||||
<div className="rounded-lg bg-emerald-50 border border-emerald-200 p-3 flex items-start gap-2">
|
||||
<ShieldCheck className="h-4 w-4 text-emerald-600 flex-shrink-0 mt-0.5" />
|
||||
<p className="text-xs text-emerald-700">
|
||||
Protected by the <span className="font-semibold">Direct Debit Guarantee</span>. You can cancel anytime by contacting your bank. Full refund if any errors occur.
|
||||
|
||||
@@ -60,8 +60,8 @@ export function ExternalRedirectStep({ pledge, amount, eventName, externalUrl, e
|
||||
return (
|
||||
<div className="max-w-md mx-auto pt-8 text-center space-y-6 animate-fade-up">
|
||||
<div className="relative inline-flex items-center justify-center">
|
||||
<div className="absolute w-20 h-20 rounded-full bg-success-green/20 animate-pulse-ring" />
|
||||
<div className="relative w-20 h-20 rounded-full bg-gradient-to-br from-success-green to-emerald-500 flex items-center justify-center shadow-xl shadow-success-green/30">
|
||||
<div className="absolute w-16 h-16 bg-success-green/20" />
|
||||
<div className="relative w-16 h-16 bg-fulfilled-green flex items-center justify-center">
|
||||
<Check className="h-10 w-10 text-white" strokeWidth={3} />
|
||||
</div>
|
||||
</div>
|
||||
@@ -72,7 +72,7 @@ export function ExternalRedirectStep({ pledge, amount, eventName, externalUrl, e
|
||||
</p>
|
||||
|
||||
{whatsappSent && (
|
||||
<div className="rounded-xl bg-[#25D366]/10 border border-[#25D366]/20 p-3 text-sm text-[#25D366] font-medium flex items-center justify-center gap-2 animate-fade-in">
|
||||
<div className="rounded-lg bg-[#25D366]/10 border border-[#25D366]/20 p-3 text-sm text-[#25D366] font-medium flex items-center justify-center gap-2 animate-fade-in">
|
||||
<MessageCircle className="h-4 w-4" /> Link sent to your WhatsApp ✓
|
||||
</div>
|
||||
)}
|
||||
@@ -107,7 +107,7 @@ export function ExternalRedirectStep({ pledge, amount, eventName, externalUrl, e
|
||||
</div>
|
||||
|
||||
{/* Share */}
|
||||
<div className="rounded-2xl bg-gradient-to-br from-warm-amber/5 to-orange-50 border border-warm-amber/20 p-5 space-y-3">
|
||||
<div className="rounded-lg bg-generosity-gold/5 border border-warm-amber/20 p-5 space-y-3">
|
||||
<div className="flex items-center justify-center gap-2">
|
||||
<Sparkles className="h-4 w-4 text-warm-amber" />
|
||||
<p className="text-sm font-bold text-gray-900">Know someone who'd donate too?</p>
|
||||
@@ -144,7 +144,7 @@ export function ExternalRedirectStep({ pledge, amount, eventName, externalUrl, e
|
||||
return (
|
||||
<div className="max-w-md mx-auto pt-2 space-y-5 animate-fade-up">
|
||||
<div className="text-center space-y-2">
|
||||
<div className="inline-flex items-center justify-center w-14 h-14 rounded-2xl shadow-lg" style={{ background: platform.color + "20" }}>
|
||||
<div className="inline-flex items-center justify-center w-14 h-14 rounded-lg shadow-lg" style={{ background: platform.color + "20" }}>
|
||||
<span className="text-2xl">{platform.icon}</span>
|
||||
</div>
|
||||
<h1 className="text-2xl font-black text-gray-900 tracking-tight">
|
||||
@@ -156,7 +156,7 @@ export function ExternalRedirectStep({ pledge, amount, eventName, externalUrl, e
|
||||
</div>
|
||||
|
||||
{whatsappSent && (
|
||||
<div className="rounded-xl bg-[#25D366]/10 border border-[#25D366]/20 p-2.5 text-xs text-[#25D366] font-medium flex items-center justify-center gap-2 animate-fade-in">
|
||||
<div className="rounded-lg bg-[#25D366]/10 border border-[#25D366]/20 p-2.5 text-xs text-[#25D366] font-medium flex items-center justify-center gap-2 animate-fade-in">
|
||||
<MessageCircle className="h-3.5 w-3.5" /> Donation link also sent to your WhatsApp
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -156,10 +156,10 @@ export function IdentityStep({ onSubmit, amount, zakatEligible, orgName }: Props
|
||||
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"
|
||||
className="w-full h-14 px-4 rounded-lg 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 className="absolute right-3 top-1/2 -translate-y-1/2 text-success-green animate-fade-in">✓</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -173,10 +173,10 @@ export function IdentityStep({ onSubmit, amount, zakatEligible, orgName }: Props
|
||||
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"
|
||||
className="w-full h-14 pl-12 pr-4 rounded-lg 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"
|
||||
/>
|
||||
{hasEmail && (
|
||||
<div className="absolute right-3 top-1/2 -translate-y-1/2 text-success-green animate-scale-in">✓</div>
|
||||
<div className="absolute right-3 top-1/2 -translate-y-1/2 text-success-green animate-fade-in">✓</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -191,10 +191,10 @@ export function IdentityStep({ onSubmit, amount, zakatEligible, orgName }: Props
|
||||
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"
|
||||
className="w-full h-14 pl-12 pr-4 rounded-lg 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"
|
||||
/>
|
||||
{hasPhone && (
|
||||
<div className="absolute right-3 top-1/2 -translate-y-1/2 text-success-green animate-scale-in">✓</div>
|
||||
<div className="absolute right-3 top-1/2 -translate-y-1/2 text-success-green animate-fade-in">✓</div>
|
||||
)}
|
||||
</div>
|
||||
{!hasEmail && !hasPhone && (
|
||||
@@ -220,14 +220,14 @@ export function IdentityStep({ onSubmit, amount, zakatEligible, orgName }: Props
|
||||
<div className="space-y-3">
|
||||
<button
|
||||
onClick={() => setGiftAid(!giftAid)}
|
||||
className={`w-full text-left rounded-2xl border-2 p-5 transition-all duration-300 ${
|
||||
className={`w-full text-left rounded-lg border-2 p-5 transition-all duration-300 ${
|
||||
giftAid
|
||||
? "border-success-green bg-gradient-to-br from-success-green/5 to-emerald-50 shadow-lg shadow-success-green/10"
|
||||
? "border-success-green bg-fulfilled-green/5"
|
||||
: "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"}`}>
|
||||
<div className={`rounded-lg p-3 transition-all ${giftAid ? "bg-success-green" : "bg-gray-100"}`}>
|
||||
<Gift className={`h-6 w-6 transition-colors ${giftAid ? "text-white" : "text-gray-400"}`} />
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
@@ -236,7 +236,7 @@ export function IdentityStep({ onSubmit, amount, zakatEligible, orgName }: Props
|
||||
{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">
|
||||
<span className="text-xs font-bold px-2.5 py-0.5 rounded-full bg-success-green text-white animate-fade-in flex items-center gap-1">
|
||||
<Sparkles className="h-3 w-3" /> +£{(giftAidBonus / 100).toFixed(0)} free
|
||||
</span>
|
||||
) : (
|
||||
@@ -249,7 +249,7 @@ export function IdentityStep({ onSubmit, amount, zakatEligible, orgName }: Props
|
||||
{giftAid ? (
|
||||
<div className="mt-2 space-y-2 animate-fade-in">
|
||||
{/* Live math */}
|
||||
<div className="flex items-center justify-between bg-white rounded-xl p-3 border border-success-green/20">
|
||||
<div className="flex items-center justify-between bg-white rounded-lg 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>
|
||||
@@ -278,7 +278,7 @@ export function IdentityStep({ onSubmit, amount, zakatEligible, orgName }: Props
|
||||
|
||||
{/* Gift Aid address + declaration (shown when Gift Aid is on) */}
|
||||
{giftAid && showGiftAidAddress && (
|
||||
<div className="rounded-2xl border-2 border-success-green/20 bg-white p-4 space-y-3 animate-fade-in">
|
||||
<div className="rounded-lg border-2 border-success-green/20 bg-white p-4 space-y-3 animate-fade-in">
|
||||
{/* HMRC requires home address */}
|
||||
<div className="flex items-center gap-2 text-xs font-medium text-muted-foreground">
|
||||
<MapPin className="h-3.5 w-3.5" />
|
||||
@@ -292,10 +292,10 @@ export function IdentityStep({ onSubmit, amount, zakatEligible, orgName }: Props
|
||||
value={addressLine1}
|
||||
onChange={(e) => setAddressLine1(e.target.value)}
|
||||
autoComplete="address-line1"
|
||||
className="w-full h-12 px-4 rounded-xl border-2 border-gray-200 bg-white text-sm font-medium placeholder:text-gray-300 focus:border-success-green focus:ring-4 focus:ring-success-green/10 outline-none transition-all"
|
||||
className="w-full h-12 px-4 rounded-lg border-2 border-gray-200 bg-white text-sm font-medium placeholder:text-gray-300 focus:border-success-green focus:ring-4 focus:ring-success-green/10 outline-none transition-all"
|
||||
/>
|
||||
{addressLine1 && (
|
||||
<div className="absolute right-3 top-1/2 -translate-y-1/2 text-success-green text-sm animate-scale-in">✓</div>
|
||||
<div className="absolute right-3 top-1/2 -translate-y-1/2 text-success-green text-sm animate-fade-in">✓</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -306,15 +306,15 @@ export function IdentityStep({ onSubmit, amount, zakatEligible, orgName }: Props
|
||||
value={postcode}
|
||||
onChange={(e) => setPostcode(e.target.value.toUpperCase())}
|
||||
autoComplete="postal-code"
|
||||
className="w-full h-12 px-4 rounded-xl border-2 border-gray-200 bg-white text-sm font-medium placeholder:text-gray-300 focus:border-success-green focus:ring-4 focus:ring-success-green/10 outline-none transition-all uppercase"
|
||||
className="w-full h-12 px-4 rounded-lg border-2 border-gray-200 bg-white text-sm font-medium placeholder:text-gray-300 focus:border-success-green focus:ring-4 focus:ring-success-green/10 outline-none transition-all uppercase"
|
||||
/>
|
||||
{postcode.length >= 5 && (
|
||||
<div className="absolute right-3 top-1/2 -translate-y-1/2 text-success-green text-sm animate-scale-in">✓</div>
|
||||
<div className="absolute right-3 top-1/2 -translate-y-1/2 text-success-green text-sm animate-fade-in">✓</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* HMRC declaration — exact model wording */}
|
||||
<div className="rounded-xl bg-success-green/5 border border-success-green/10 p-3">
|
||||
<div className="rounded-lg bg-success-green/5 border border-success-green/10 p-3">
|
||||
<p className="text-xs font-bold text-gray-700 mb-1.5">Gift Aid Declaration</p>
|
||||
<p className="text-[11px] text-muted-foreground leading-relaxed">
|
||||
{GIFT_AID_DECLARATION}
|
||||
@@ -406,7 +406,7 @@ function ConsentCheckbox({ checked, onChange, icon, label, description }: {
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => onChange(!checked)}
|
||||
className={`w-full text-left rounded-2xl border-2 p-4 transition-all ${
|
||||
className={`w-full text-left rounded-lg border-2 p-4 transition-all ${
|
||||
checked
|
||||
? "border-trust-blue bg-trust-blue/5 shadow-sm"
|
||||
: "border-gray-200 bg-white hover:border-trust-blue/40"
|
||||
|
||||
@@ -22,7 +22,7 @@ export function PaymentStep({ onSelect, amount }: Props) {
|
||||
detail: "We'll give you the bank details. Transfer in your own time.",
|
||||
fee: "Free",
|
||||
feeClass: "text-success-green font-bold",
|
||||
iconBg: "from-emerald-500 to-green-600",
|
||||
iconBg: "bg-fulfilled-green",
|
||||
highlight: true,
|
||||
benefits: ["Zero fees", "Most charities prefer this"],
|
||||
},
|
||||
@@ -36,7 +36,7 @@ export function PaymentStep({ onSelect, amount }: Props) {
|
||||
detail: "GoCardless collects it for you. Protected by the DD Guarantee.",
|
||||
fee: "1% + 20p",
|
||||
feeClass: "text-muted-foreground",
|
||||
iconBg: "from-trust-blue to-blue-600",
|
||||
iconBg: "bg-promise-blue",
|
||||
highlight: false,
|
||||
benefits: ["No action needed", "DD Guarantee"],
|
||||
},
|
||||
@@ -50,7 +50,7 @@ export function PaymentStep({ onSelect, amount }: Props) {
|
||||
detail: "Powered by Stripe. Receipt emailed instantly.",
|
||||
fee: "1.4% + 20p",
|
||||
feeClass: "text-muted-foreground",
|
||||
iconBg: "from-purple-500 to-violet-600",
|
||||
iconBg: "bg-midnight",
|
||||
highlight: false,
|
||||
benefits: ["Instant confirmation", "All major cards"],
|
||||
},
|
||||
@@ -74,15 +74,15 @@ export function PaymentStep({ onSelect, amount }: Props) {
|
||||
key={opt.id}
|
||||
onClick={() => onSelect(opt.id)}
|
||||
className={`
|
||||
w-full text-left rounded-2xl border-2 bg-white p-5 transition-all duration-200 group card-hover
|
||||
w-full text-left rounded-lg border-2 bg-white p-5 transition-all duration-200 group
|
||||
${opt.highlight
|
||||
? "border-success-green/40 shadow-sm shadow-success-green/10 hover:border-success-green hover:shadow-lg hover:shadow-success-green/15"
|
||||
: "border-gray-200 hover:border-trust-blue/40 hover:shadow-lg hover:shadow-trust-blue/10"
|
||||
? "border-success-green/40 hover:border-success-green hover:border-fulfilled-green"
|
||||
: "border-gray-200 hover:border-trust-blue/40 hover:border-promise-blue"
|
||||
}
|
||||
`}
|
||||
>
|
||||
<div className="flex items-start gap-4">
|
||||
<div className={`rounded-xl bg-gradient-to-br ${opt.iconBg} p-3 shadow-lg shadow-trust-blue/10 group-hover:scale-105 transition-transform`}>
|
||||
<div className={`rounded-lg ${opt.iconBg} p-3 transition-transform`}>
|
||||
<opt.icon className="h-5 w-5 text-white" />
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
|
||||
@@ -150,10 +150,10 @@ export function ScheduleStep({ amount, onSelect }: Props) {
|
||||
{/* Pay Now */}
|
||||
<button
|
||||
onClick={() => onSelect({ mode: "now" })}
|
||||
className="w-full text-left rounded-2xl border-2 border-gray-200 bg-white p-5 transition-all duration-200 card-hover hover:border-trust-blue/40 hover:shadow-lg group"
|
||||
className="w-full text-left rounded-lg border-2 border-gray-200 bg-white p-5 transition-all duration-200 hover:border-trust-blue/40 hover:shadow-lg group"
|
||||
>
|
||||
<div className="flex items-start gap-4">
|
||||
<div className="rounded-xl bg-gradient-to-br from-trust-blue to-blue-600 p-3 shadow-lg shadow-trust-blue/20 group-hover:scale-105 transition-transform">
|
||||
<div className="rounded-lg bg-midnight p-3 transition-transform">
|
||||
<Zap className="h-5 w-5 text-white" />
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
@@ -172,10 +172,10 @@ export function ScheduleStep({ amount, onSelect }: Props) {
|
||||
{/* Pay on a date */}
|
||||
<button
|
||||
onClick={() => setMode("calendar")}
|
||||
className="w-full text-left rounded-2xl border-2 border-success-green/30 bg-gradient-to-r from-success-green/5 to-white p-5 transition-all duration-200 card-hover hover:border-success-green hover:shadow-lg group"
|
||||
className="w-full text-left rounded-lg border-2 border-success-green/30 bg-fulfilled-green/5 p-5 transition-all duration-200 hover:border-success-green hover:shadow-lg group"
|
||||
>
|
||||
<div className="flex items-start gap-4">
|
||||
<div className="rounded-xl bg-gradient-to-br from-success-green to-emerald-600 p-3 shadow-lg shadow-success-green/20 group-hover:scale-105 transition-transform">
|
||||
<div className="rounded-lg bg-fulfilled-green p-3 transition-transform">
|
||||
<Calendar className="h-5 w-5 text-white" />
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
@@ -194,10 +194,10 @@ export function ScheduleStep({ amount, onSelect }: Props) {
|
||||
{/* Installments */}
|
||||
<button
|
||||
onClick={() => setMode("installments")}
|
||||
className="w-full text-left rounded-2xl border-2 border-gray-200 bg-white p-5 transition-all duration-200 card-hover hover:border-warm-amber/40 hover:shadow-lg group"
|
||||
className="w-full text-left rounded-lg border-2 border-gray-200 bg-white p-5 transition-all duration-200 hover:border-warm-amber/40 hover:shadow-lg group"
|
||||
>
|
||||
<div className="flex items-start gap-4">
|
||||
<div className="rounded-xl bg-gradient-to-br from-warm-amber to-orange-500 p-3 shadow-lg shadow-warm-amber/20 group-hover:scale-105 transition-transform">
|
||||
<div className="rounded-lg bg-generosity-gold p-3 transition-transform">
|
||||
<Repeat className="h-5 w-5 text-white" />
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
@@ -241,9 +241,9 @@ export function ScheduleStep({ amount, onSelect }: Props) {
|
||||
<button
|
||||
key={i}
|
||||
onClick={() => setSelectedDate(s.date)}
|
||||
className={`shrink-0 rounded-xl border-2 px-3.5 py-2 text-left transition-all ${
|
||||
className={`shrink-0 rounded-lg border-2 px-3.5 py-2 text-left transition-all ${
|
||||
isSelected
|
||||
? "border-success-green bg-success-green/5 shadow-md shadow-success-green/10"
|
||||
? "border-success-green bg-success-green/5"
|
||||
: "border-gray-200 bg-white hover:border-success-green/40"
|
||||
}`}
|
||||
>
|
||||
@@ -283,7 +283,7 @@ export function ScheduleStep({ amount, onSelect }: Props) {
|
||||
disabled={day.isPast || !day.inMonth}
|
||||
className={`h-9 w-9 mx-auto rounded-lg text-xs font-medium transition-all ${
|
||||
isSelected
|
||||
? "bg-success-green text-white font-bold shadow-md shadow-success-green/30"
|
||||
? "bg-success-green text-white font-bold"
|
||||
: isToday
|
||||
? "bg-trust-blue/10 text-trust-blue font-bold"
|
||||
: day.isPast || !day.inMonth
|
||||
@@ -301,7 +301,7 @@ export function ScheduleStep({ amount, onSelect }: Props) {
|
||||
|
||||
{/* Selected date summary */}
|
||||
{selectedDate && (
|
||||
<div className="rounded-2xl bg-success-green/5 border border-success-green/20 p-4 text-center animate-scale-in">
|
||||
<div className="rounded-lg bg-success-green/5 border border-success-green/20 p-4 text-center animate-fade-in">
|
||||
<p className="text-xs text-muted-foreground">You'll pay</p>
|
||||
<p className="text-xl font-black text-gray-900">£{pounds}</p>
|
||||
<p className="text-sm font-medium text-success-green">{formatFull(selectedDate)}</p>
|
||||
@@ -313,7 +313,7 @@ export function ScheduleStep({ amount, onSelect }: Props) {
|
||||
|
||||
<Button
|
||||
size="xl"
|
||||
className={`w-full ${selectedDate ? "bg-success-green hover:bg-success-green/90 shadow-lg shadow-success-green/25" : ""}`}
|
||||
className={`w-full ${selectedDate ? "bg-success-green hover:bg-success-green/90" : ""}`}
|
||||
disabled={!selectedDate}
|
||||
onClick={handleDateConfirm}
|
||||
>
|
||||
@@ -345,9 +345,9 @@ export function ScheduleStep({ amount, onSelect }: Props) {
|
||||
<button
|
||||
key={n}
|
||||
onClick={() => setInstallmentCount(n)}
|
||||
className={`rounded-xl border-2 px-4 py-3 text-center transition-all min-w-[70px] ${
|
||||
className={`rounded-lg border-2 px-4 py-3 text-center transition-all min-w-[70px] ${
|
||||
installmentCount === n
|
||||
? "border-warm-amber bg-warm-amber text-white font-bold shadow-lg shadow-warm-amber/25"
|
||||
? "border-warm-amber bg-warm-amber text-white font-bold"
|
||||
: "border-gray-200 bg-white hover:border-warm-amber/40"
|
||||
}`}
|
||||
>
|
||||
@@ -359,7 +359,7 @@ export function ScheduleStep({ amount, onSelect }: Props) {
|
||||
|
||||
{/* Breakdown */}
|
||||
<Card className="overflow-hidden">
|
||||
<div className="h-1 bg-gradient-to-r from-warm-amber to-orange-400" />
|
||||
<div className="h-1 bg-generosity-gold" />
|
||||
<CardContent className="pt-4">
|
||||
<div className="text-center mb-4">
|
||||
<p className="text-xs text-muted-foreground">Monthly payment</p>
|
||||
@@ -392,7 +392,7 @@ export function ScheduleStep({ amount, onSelect }: Props) {
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<div className="rounded-xl bg-warm-amber/5 border border-warm-amber/20 p-3 text-center">
|
||||
<div className="rounded-lg bg-warm-amber/5 border border-warm-amber/20 p-3 text-center">
|
||||
<p className="text-xs text-muted-foreground">
|
||||
We'll send you a reminder with payment details before each installment date.
|
||||
You can pay via bank transfer, card, or Direct Debit.
|
||||
@@ -401,7 +401,7 @@ export function ScheduleStep({ amount, onSelect }: Props) {
|
||||
|
||||
<Button
|
||||
size="xl"
|
||||
className="w-full bg-warm-amber hover:bg-warm-amber/90 shadow-lg shadow-warm-amber/25"
|
||||
className="w-full bg-warm-amber hover:bg-warm-amber/90"
|
||||
onClick={handleInstallmentConfirm}
|
||||
>
|
||||
<Check className="h-5 w-5 mr-2" />
|
||||
|
||||
@@ -45,7 +45,7 @@ function SuccessContent() {
|
||||
if (cancelled) {
|
||||
return (
|
||||
<div className="max-w-md mx-auto text-center space-y-6">
|
||||
<div className="inline-flex items-center justify-center w-20 h-20 rounded-full bg-amber-100">
|
||||
<div className="inline-flex items-center justify-center w-16 h-16 bg-amber-100">
|
||||
<X className="h-10 w-10 text-amber-600" />
|
||||
</div>
|
||||
<h1 className="text-2xl font-extrabold text-gray-900">Payment Cancelled</h1>
|
||||
@@ -89,7 +89,7 @@ function SuccessContent() {
|
||||
|
||||
return (
|
||||
<div className="max-w-md mx-auto text-center space-y-6">
|
||||
<div className="inline-flex items-center justify-center w-20 h-20 rounded-full bg-success-green/10">
|
||||
<div className="inline-flex items-center justify-center w-16 h-16 bg-success-green/10">
|
||||
<Check className="h-10 w-10 text-success-green" />
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
@@ -125,7 +125,7 @@ function SuccessContent() {
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
<div className="rounded-2xl bg-trust-blue/5 border border-trust-blue/20 p-4 space-y-2">
|
||||
<div className="rounded-lg bg-trust-blue/5 border border-trust-blue/20 p-4 space-y-2">
|
||||
<p className="text-sm font-medium text-trust-blue">What happens next?</p>
|
||||
<p className="text-sm text-muted-foreground">{nextStepMessages[rail] || nextStepMessages.card}</p>
|
||||
</div>
|
||||
@@ -138,7 +138,7 @@ function SuccessContent() {
|
||||
|
||||
export default function SuccessPage() {
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center bg-gradient-to-br from-trust-blue/5 via-white to-warm-amber/5 p-4">
|
||||
<div className="min-h-screen flex items-center justify-center bg-paper p-4">
|
||||
<Suspense fallback={
|
||||
<div className="text-center space-y-4">
|
||||
<Loader2 className="h-10 w-10 text-trust-blue animate-spin mx-auto" />
|
||||
|
||||
@@ -89,7 +89,7 @@ export default function VolunteerPage() {
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center bg-gradient-to-br from-trust-blue/5 via-white to-warm-amber/5">
|
||||
<div className="min-h-screen flex items-center justify-center bg-paper">
|
||||
<Loader2 className="h-8 w-8 text-trust-blue animate-spin" />
|
||||
</div>
|
||||
)
|
||||
@@ -97,7 +97,7 @@ export default function VolunteerPage() {
|
||||
|
||||
if (error || !data) {
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center bg-gradient-to-br from-trust-blue/5 via-white to-warm-amber/5 p-4">
|
||||
<div className="min-h-screen flex items-center justify-center bg-paper p-4">
|
||||
<div className="text-center space-y-4">
|
||||
<QrCode className="h-12 w-12 text-muted-foreground mx-auto" />
|
||||
<h1 className="text-xl font-bold">QR code not found</h1>
|
||||
@@ -108,7 +108,7 @@ export default function VolunteerPage() {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gradient-to-br from-trust-blue/5 via-white to-warm-amber/5">
|
||||
<div className="min-h-screen bg-paper">
|
||||
<div className="max-w-lg mx-auto px-4 py-8 space-y-6">
|
||||
{/* Header */}
|
||||
<div className="text-center space-y-2">
|
||||
@@ -153,7 +153,7 @@ export default function VolunteerPage() {
|
||||
</div>
|
||||
<div className="h-3 rounded-full bg-gray-100 overflow-hidden">
|
||||
<div
|
||||
className="h-full rounded-full bg-gradient-to-r from-trust-blue to-success-green transition-all duration-1000"
|
||||
className="h-full rounded-full bg-promise-blue transition-all duration-1000"
|
||||
style={{ width: `${Math.round((data.stats.totalPaidPence / data.stats.totalPledgedPence) * 100)}%` }}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -57,7 +57,7 @@ export function LiveTicker({ eventId }: LiveTickerProps) {
|
||||
const totalToday = pledges.reduce((s, p) => s + p.amountPence, 0)
|
||||
|
||||
return (
|
||||
<div className="rounded-2xl border bg-white p-4 space-y-3">
|
||||
<div className="rounded-lg border bg-white p-4 space-y-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<h3 className="font-bold text-sm flex items-center gap-2">
|
||||
<TrendingUp className="h-4 w-4 text-trust-blue" /> Live Feed
|
||||
|
||||
@@ -3,24 +3,25 @@ import { cva, type VariantProps } from "class-variance-authority"
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const buttonVariants = cva(
|
||||
"inline-flex items-center justify-center whitespace-nowrap rounded-xl text-sm font-semibold ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 active:scale-[0.98]",
|
||||
"inline-flex items-center justify-center whitespace-nowrap text-sm font-semibold ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default: "bg-trust-blue text-white hover:bg-trust-blue/90 shadow-lg shadow-trust-blue/25",
|
||||
destructive: "bg-danger-red text-white hover:bg-danger-red/90",
|
||||
outline: "border-2 border-input bg-background hover:bg-accent hover:text-accent-foreground",
|
||||
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
||||
ghost: "hover:bg-accent hover:text-accent-foreground",
|
||||
link: "text-trust-blue underline-offset-4 hover:underline",
|
||||
success: "bg-success-green text-white hover:bg-success-green/90 shadow-lg shadow-success-green/25",
|
||||
amber: "bg-warm-amber text-white hover:bg-warm-amber/90 shadow-lg shadow-warm-amber/25",
|
||||
default: "bg-midnight text-white hover:bg-gray-800",
|
||||
destructive: "bg-alert-red text-white hover:bg-alert-red/90",
|
||||
outline: "border border-gray-200 bg-white hover:bg-gray-50 text-midnight",
|
||||
secondary: "bg-gray-100 text-midnight hover:bg-gray-200",
|
||||
ghost: "hover:bg-gray-100 text-midnight",
|
||||
link: "text-promise-blue underline-offset-4 hover:underline",
|
||||
success: "bg-fulfilled-green text-white hover:bg-fulfilled-green/90",
|
||||
amber: "bg-generosity-gold text-white hover:bg-generosity-gold/90",
|
||||
blue: "bg-promise-blue text-white hover:bg-promise-blue/90",
|
||||
},
|
||||
size: {
|
||||
default: "h-11 px-6 py-2",
|
||||
sm: "h-9 rounded-lg px-4",
|
||||
lg: "h-14 rounded-2xl px-8 text-base",
|
||||
xl: "h-16 rounded-2xl px-10 text-lg",
|
||||
sm: "h-9 px-4",
|
||||
lg: "h-14 px-8 text-base",
|
||||
xl: "h-16 px-10 text-lg",
|
||||
icon: "h-10 w-10",
|
||||
},
|
||||
},
|
||||
|
||||
@@ -2,7 +2,7 @@ import * as React from "react"
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const Card = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(({ className, ...props }, ref) => (
|
||||
<div ref={ref} className={cn("rounded-2xl border bg-card text-card-foreground shadow-sm", className)} {...props} />
|
||||
<div ref={ref} className={cn("rounded-lg border bg-card text-card-foreground shadow-sm", className)} {...props} />
|
||||
))
|
||||
Card.displayName = "Card"
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ export function Dialog({ open, onOpenChange, children }: DialogProps) {
|
||||
<div className="fixed inset-0 z-50">
|
||||
<div className="fixed inset-0 bg-black/50 backdrop-blur-sm" onClick={() => onOpenChange(false)} />
|
||||
<div className="fixed inset-0 flex items-center justify-center p-4">
|
||||
<div className="relative bg-background rounded-2xl shadow-xl max-w-lg w-full max-h-[90vh] overflow-y-auto p-6 animate-in fade-in-0 zoom-in-95">
|
||||
<div className="relative bg-background rounded-lg shadow-xl max-w-lg w-full max-h-[90vh] overflow-y-auto p-6 animate-in fade-in-0 zoom-in-95">
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -40,7 +40,7 @@ DropdownMenuTrigger.displayName = "DropdownMenuTrigger"
|
||||
function DropdownMenuContent({ children, className, align = "end" }: { children: React.ReactNode; className?: string; align?: "start" | "end" }) {
|
||||
return (
|
||||
<div className={cn(
|
||||
"absolute z-50 min-w-[180px] overflow-hidden rounded-xl border bg-white p-1.5 shadow-lg animate-scale-in",
|
||||
"absolute z-50 min-w-[180px] overflow-hidden rounded-lg border bg-white p-1.5 shadow-lg animate-scale-in",
|
||||
align === "end" ? "right-0" : "left-0",
|
||||
"top-full mt-1",
|
||||
className
|
||||
|
||||
@@ -5,7 +5,7 @@ const Input = React.forwardRef<HTMLInputElement, React.InputHTMLAttributes<HTMLI
|
||||
<input
|
||||
type={type}
|
||||
className={cn(
|
||||
"flex h-11 w-full rounded-xl border border-input bg-background px-4 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-trust-blue focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
|
||||
"flex h-11 w-full rounded-lg border border-input bg-background px-4 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-trust-blue focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
|
||||
className
|
||||
)}
|
||||
ref={ref}
|
||||
|
||||
@@ -5,7 +5,7 @@ const Select = React.forwardRef<HTMLSelectElement, React.SelectHTMLAttributes<HT
|
||||
<select
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"flex h-11 w-full rounded-xl border border-input bg-background px-4 py-2 text-sm ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-trust-blue focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
|
||||
"flex h-11 w-full rounded-lg border border-input bg-background px-4 py-2 text-sm ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-trust-blue focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
function Skeleton({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
|
||||
return <div className={cn("animate-pulse rounded-xl bg-muted", className)} {...props} />
|
||||
return <div className={cn("animate-pulse rounded-lg bg-muted", className)} {...props} />
|
||||
}
|
||||
|
||||
export { Skeleton }
|
||||
|
||||
@@ -16,7 +16,7 @@ function Tabs({ value, onValueChange, children, className }: { value: string; on
|
||||
|
||||
function TabsList({ children, className }: { children: React.ReactNode; className?: string }) {
|
||||
return (
|
||||
<div className={cn("inline-flex h-10 items-center justify-center rounded-xl bg-muted p-1 text-muted-foreground", className)}>
|
||||
<div className={cn("inline-flex h-10 items-center justify-center rounded-lg bg-muted p-1 text-muted-foreground", className)}>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -5,7 +5,7 @@ const Textarea = React.forwardRef<HTMLTextAreaElement, React.TextareaHTMLAttribu
|
||||
({ className, ...props }, ref) => (
|
||||
<textarea
|
||||
className={cn(
|
||||
"flex min-h-[80px] w-full rounded-xl border border-input bg-background px-4 py-3 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-trust-blue focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
|
||||
"flex min-h-[80px] w-full rounded-lg border border-input bg-background px-4 py-3 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-trust-blue focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
|
||||
className
|
||||
)}
|
||||
ref={ref}
|
||||
|
||||
@@ -33,7 +33,7 @@ export function ToastProvider({ children }: { children: React.ReactNode }) {
|
||||
<div
|
||||
key={t.id}
|
||||
className={cn(
|
||||
"rounded-xl px-4 py-3 text-sm font-medium text-white shadow-lg animate-in slide-in-from-right",
|
||||
"rounded-lg px-4 py-3 text-sm font-medium text-white shadow-lg animate-in slide-in-from-right",
|
||||
t.type === 'success' && 'bg-success-green',
|
||||
t.type === 'error' && 'bg-danger-red',
|
||||
t.type === 'info' && 'bg-trust-blue',
|
||||
|
||||
@@ -42,6 +42,14 @@ const config: Config = {
|
||||
border: "hsl(var(--border))",
|
||||
input: "hsl(var(--input))",
|
||||
ring: "hsl(var(--ring))",
|
||||
// Brand colors — named by psychology, not appearance
|
||||
midnight: "#111827",
|
||||
"promise-blue": "#1e40af",
|
||||
"generosity-gold": "#f59e0b",
|
||||
"fulfilled-green": "#16a34a",
|
||||
"alert-red": "#dc2626",
|
||||
paper: "#f9fafb",
|
||||
// Legacy aliases (used across codebase)
|
||||
"trust-blue": "#1e40af",
|
||||
"warm-amber": "#f59e0b",
|
||||
"success-green": "#16a34a",
|
||||
|
||||
Reference in New Issue
Block a user