Skip to main content

VideoBlast Landing Page

VideoBlast Landing Page

Component File: src/features/fundraising/components/VideoBlastLandingPage.tsx Route: /vb/:publicUrl Access Level: Public (No authentication required) Last Updated: April 21, 2026 Status: ✅ Fully Implemented with Stripe Integration

Overview

The VideoBlast Landing Page is a public-facing video donation page that displays a personalized video message alongside a donation form. It's designed for video-based fundraising campaigns where supporters can watch a video and then make a donation. The page fetches video blast data from the database, tracks views (deduped per session), and processes donations through Stripe Checkout.

> Apr 21 2026: stripped the legacy mocked "Our Impact" / fake mission / Unsplash hero block — every donor was seeing the same fake nonprofit narrative regardless of org. The page now renders only the real video, the real org name, and the donation form. Re-introduce mission / impact stats only when there is a real per-org data source.

Implementation Status

Backend Integration

  • ✅ Fetches video bomb data (plus org name) via fetchVideoBlastPublic() SECURITY DEFINER RPC
  • ✅ Fetches organization name for display
  • ✅ Increments view count on page load via incrementVideoBlastViews()
  • ✅ Uses `createCheckoutSession()` from `src/lib/db/stripe.ts` (wraps the `create-checkout-session` Edge Function). Amount is in dollars (same contract as Donations Manager / donor portal)—not cents.
  • ✅ Passes videoBlastId into checkout metadata for webhook donation attribution
  • ✅ Handles success/cancel redirects from Stripe Checkout
  • ✅ Checkout errors surface via `getPaymentInitErrorMessage` (e.g. `connect_not_ready`, `charges_not_enabled`, `demo_org_blocked`)
  • ✅ Loading and error states for better UX

UI Features

Header

  • Organization name display (fetched from database)
  • Clean, minimal branding

Title

  • Campaign title (from video_bombs.title)
  • Subtitle: Shared by {organization name}

Video Section

  • Video player with controls
  • Video loaded from Supabase Storage URL (public bucket)

Donation Form

  • Preset Amounts: $25, $50, $100, $250, $500
  • Custom Amount: Text input for other amounts
  • Donation Type: One-time or monthly recurring
  • Donor Info:
  • Name (required)
  • Email (required)
  • Personal message (optional)
  • Donate Button: Redirects to Stripe Checkout

Data Flow

Props Interface

interface VideoBlastLandingPageProps {
  publicUrl?: string; // Can be passed as prop or from URL params
}

State Management

  • videoBlast - Video Blast data from database (camelCase to avoid shadowing the VideoBlast type)
  • organizationName - Organization name for display
  • isLoading - Loading state
  • error - Error message if video bomb not found
  • isProcessing - Processing state during checkout
  • amount - Selected preset amount or 'custom'
  • customAmount - Custom amount input value
  • donationType - 'one-time' or 'monthly'
  • donorInfo - Name, email, message

Donation Flow

1. Visitor arrives via shared VideoBlast link (/vb/:publicUrl) 2. Page fetches video bomb data from database 3. View count is incremented (fire and forget) 4. Visitor watches video content 5. Selects donation amount (preset or custom) 6. Chooses one-time or monthly recurring 7. Enters contact information 8. Clicks "Complete Donation" 9. Redirected to Stripe Checkout with:

10. After payment, redirected back to landing page with success/cancel param 11. Toast notification shown based on result

  • Amount in dollars (preset or custom; passed through to the Edge Function as `amount`, which expects USD whole/currency units — not cents)
  • Organization ID and optional organization name
  • Donor info (name, email, optional note as `donorNote` in metadata)
  • Video Blast attribution (`videoBlastId`video_blast_id metadata)
  • Recurring flag (monthly Checkout subscription when enabled)
  • Success/cancel URLs

Stripe Integration

Checkout session parameters (client → createCheckoutSession)

Matches `src/lib/db/stripe.ts` / Edge Function contract:

{
  amount: number,              // Dollars (e.g. 50 for $50) — NOT cents
  organizationId: string,
  organizationName?: string,
  donorName: string,
  donorEmail: string,
  donorNote?: string,          // Optional message → checkout metadata
  videoBlastId?: string,       // Video Blast attribution (webhook updates stats)
  isRecurring: boolean,        // Monthly when true
  successUrl: string,
  cancelUrl: string,
}

Routing: The Edge Function uses `resolvePaymentTargetForOrg` (Connect on ancestor chain, else `legacy_direct` under `DIRECT_STRIPE_PAYOUT_ORG_ID`). If the org cannot accept card payments, the invoke fails with `PaymentInitError` and a user-safe message from `getPaymentInitErrorMessage`.

Error Handling

| Scenario | Behavior |
|----------|----------|
| Video bomb not found | Error card: "Video Not Found" |
| Loading | Spinner with "Loading..." text |
| No amount selected | Toast: "Please select or enter a valid amount" |
| Missing contact info | Toast: "Please fill in your contact information" |
| Checkout fails (e.g. Connect not ready) | Toast: **`getPaymentInitErrorMessage`** result, or generic fallback |
| Payment success | Toast: "Thank you for your donation!" |
| Payment canceled | Toast: "Donation was canceled." |

Validation

Frontend Validations

  • Amount must be a finite number, `>= $1` (Stripe rejects sub-$0.50 charges) and `<= $100,000` (fat-finger guard).
  • Email must match ^[^\s@]+@[^\s@]+\.[^\s@]+$ (HTML5 alone is bypassed when Stripe metadata reaches the receipt).
  • Name normalized via normalizePersonName and required non-empty.
  • Video Blast row must exist and be status = 'active'.

View Counter Dedup

View increments are deduped per tab session per slug via sessionStorage[vb-viewed:{slug}]. Refreshing the page or returning from Stripe checkout in the same tab does not double-count. Cross-tab and cross-IP dedup is intentionally out of scope.

Related Documentation


Synced from IFMmvp-Frontend documentation: pages/fundraising/04-VIDEOBLAST-LANDING-PAGE.md

Ready to Get Started?

See how Alignmint can simplify your nonprofit's operations. Schedule a free demo with our team and we'll walk you through everything.

Questions? Email us at steven@getalignmint.org

Ready to get started?Start Plus Trial