From c501488322c908806f9501431c32a2f52d68c44e Mon Sep 17 00:00:00 2001 From: Ricardo Vega Date: Sun, 11 Oct 2026 12:56:56 -0500 Subject: [PATCH] fix(web): public home page without the stale Coming Soon popup Twilio rejected the FieldView.Live toll-free verification with 30491 ("Website Is Password Protected or Requires Login"): the home page opened with a "Coming Soon / launch Feb 15, 2026" popup over Owner Login and Admin links, and rendered client-side only because the popup hook read search params. - Remove ComingSoonModal and its useWelcomeModal hook (home page was the only user). - Add an "About FieldView.Live" section: what it is, operated by Noctusoft, Inc., support@fieldview.live, links to /sms, /privacy, /terms. - The home page is now a static server component, so the content is in the HTML a reviewer fetches. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_0183M8krzbhhBfqP9Efw7Pcb --- apps/web/app/__tests__/home-page.test.tsx | 55 ++++++ apps/web/app/page.tsx | 70 ++++--- apps/web/components/v2/ComingSoonModal.tsx | 212 --------------------- apps/web/hooks/useWelcomeModal.ts | 118 ------------ 4 files changed, 97 insertions(+), 358 deletions(-) create mode 100644 apps/web/app/__tests__/home-page.test.tsx delete mode 100644 apps/web/components/v2/ComingSoonModal.tsx delete mode 100644 apps/web/hooks/useWelcomeModal.ts diff --git a/apps/web/app/__tests__/home-page.test.tsx b/apps/web/app/__tests__/home-page.test.tsx new file mode 100644 index 0000000..1801d7b --- /dev/null +++ b/apps/web/app/__tests__/home-page.test.tsx @@ -0,0 +1,55 @@ +/** + * Home page must be fully public: carrier reviewers (Twilio toll-free + * verification) read it without logging in, so it names the business and a + * contact, and no popup covers it. + */ +import { render, screen } from '@testing-library/react'; +import type { AnchorHTMLAttributes, ReactNode } from 'react'; +import { describe, expect, it, vi } from 'vitest'; + +import HomePage from '../page'; + +vi.mock('next/link', () => ({ + default: ({ children, href, ...rest }: AnchorHTMLAttributes & { children: ReactNode }) => ( + + {children} + + ), +})); + +describe('HomePage', () => { + it('shows no coming-soon popup', () => { + render(); + + expect(screen.queryByText(/coming soon/i)).not.toBeInTheDocument(); + expect(screen.queryByText(/launch date/i)).not.toBeInTheDocument(); + expect(screen.queryByRole('dialog')).not.toBeInTheDocument(); + }); + + it('names the business and a support contact', () => { + render(); + + const about = screen.getByTestId('section-about-business'); + expect(about).toHaveTextContent('Noctusoft, Inc.'); + expect(about).toHaveTextContent('support@fieldview.live'); + expect(screen.getByTestId('link-about-support-email')).toHaveAttribute( + 'href', + 'mailto:support@fieldview.live' + ); + }); + + it('links to text messages, privacy and terms from the page body', () => { + render(); + + expect(screen.getByTestId('link-about-sms')).toHaveAttribute('href', '/sms'); + expect(screen.getByTestId('link-about-privacy')).toHaveAttribute('href', '/privacy'); + expect(screen.getByTestId('link-about-terms')).toHaveAttribute('href', '/terms'); + }); + + it('keeps owner login and get started', () => { + render(); + + expect(screen.getByTestId('link-owner-login')).toHaveAttribute('href', '/owners/login'); + expect(screen.getByTestId('link-owner-register')).toHaveAttribute('href', '/owners/register'); + }); +}); diff --git a/apps/web/app/page.tsx b/apps/web/app/page.tsx index 9cce724..5abed63 100644 --- a/apps/web/app/page.tsx +++ b/apps/web/app/page.tsx @@ -1,25 +1,8 @@ -'use client'; - import Link from 'next/link'; -import { Suspense } from 'react'; - -import { ComingSoonModal } from '@/components/v2/ComingSoonModal'; -import { useWelcomeModal } from '@/hooks/useWelcomeModal'; - -function HomePageContent() { - const { isOpen, closeModal, dontShowAgain, isVeoReferral } = useWelcomeModal(); +export default function HomePage() { return ( - <> - {/* Coming Soon Modal */} - - -
+
{/* Hero Section */}
@@ -96,6 +79,46 @@ function HomePageContent() {
+ {/* About the business: public for carrier reviewers, no login needed */} +
+

+ About FieldView.Live +

+

+ FieldView.Live streams youth sports games. Teams and event owners share one link; parents and fans watch + free or buy a game. FieldView.Live is operated by Noctusoft, Inc. +

+

+ Support:{' '} + + support@fieldview.live + +

+ +
+ {/* Quick Links */}
@@ -133,14 +156,5 @@ function HomePageContent() {
- - ); -} - -export default function HomePage() { - return ( - - - ); } diff --git a/apps/web/components/v2/ComingSoonModal.tsx b/apps/web/components/v2/ComingSoonModal.tsx deleted file mode 100644 index a7dba15..0000000 --- a/apps/web/components/v2/ComingSoonModal.tsx +++ /dev/null @@ -1,212 +0,0 @@ -'use client'; - -import { useState } from 'react'; -import { TouchButton } from './primitives/TouchButton'; -import { Input } from '@/components/ui/input'; -import { apiRequest } from '@/lib/api-client'; -import { InlineError } from '@/components/v2/InlineError'; - -export interface ComingSoonModalProps { - isOpen: boolean; - onClose: () => void; - onDontShowAgain: () => void; - isVeoReferral?: boolean; -} - -export function ComingSoonModal({ - isOpen, - onClose, - onDontShowAgain, - isVeoReferral = false, -}: ComingSoonModalProps) { - const [email, setEmail] = useState(''); - const [name, setName] = useState(''); - const [isSubmitting, setIsSubmitting] = useState(false); - const [isSuccess, setIsSuccess] = useState(false); - const [error, setError] = useState(''); - const [dontShow, setDontShow] = useState(false); - - const handleSubmit = async (e: React.FormEvent) => { - e.preventDefault(); - setError(''); - setIsSubmitting(true); - - try { - await apiRequest('/early-access/signup', { - method: 'POST', - body: JSON.stringify({ - email, - name: name || undefined, - source: isVeoReferral ? 'veo' : 'organic', - }), - }); - - setIsSuccess(true); - } catch (err) { - setError(err instanceof Error ? err.message : 'Failed to sign up'); - } finally { - setIsSubmitting(false); - } - }; - - const handleClose = () => { - if (dontShow) { - onDontShowAgain(); - } else { - onClose(); - } - }; - - if (!isOpen) return null; - - return ( - <> - {/* Backdrop with Gaussian Blur */} -
- - {/* Modal */} -
-
e.stopPropagation()} - data-testid={isSuccess ? "modal-coming-soon-success" : "modal-coming-soon"} - > - {isSuccess ? ( - // Success State -
-
🎉
-

- You're on the List! -

-

- We'll notify you as soon as we launch on{' '} - February 15, 2026. -

-

- Check your inbox for a confirmation email. -

- - Got It! - -
- ) : ( - // Main Form -
- {/* Header */} -
-
🚀
-

- Coming Soon! -

- {isVeoReferral && ( -

- Thanks for finding us through your Veo camera! 👋 -

- )} -
- - {/* Message */} -
-

- We're building something special for Veo camera owners – monetize your live streams with built-in paywalls and instant payments. -

-
- - {/* Launch Date */} -
-
- 🗓️ LAUNCH DATE -
-
- February 15, 2026 -
-
- - {/* Form */} -
-
-

- Get notified when we launch: -

- - setName(e.target.value)} - data-testid="input-name" - /> - - setEmail(e.target.value)} - required - data-testid="input-email" - /> -
- - {error && ( -
- -
- )} - - - {isSubmitting ? 'Signing Up...' : 'Notify Me'} - - - - Maybe Later - -
- - {/* Don't show again */} -
- setDontShow(e.target.checked)} - className="w-4 h-4 rounded border-[var(--fv-color-border)] text-[var(--fv-color-primary-500)] focus:ring-2 focus:ring-[var(--fv-color-primary-500)]" - data-testid="checkbox-dont-show" - /> - -
-
- )} -
-
- - ); -} diff --git a/apps/web/hooks/useWelcomeModal.ts b/apps/web/hooks/useWelcomeModal.ts deleted file mode 100644 index a0d115c..0000000 --- a/apps/web/hooks/useWelcomeModal.ts +++ /dev/null @@ -1,118 +0,0 @@ -/** - * useWelcomeModal Hook - * - * Manages the Veo Discovery welcome modal state. - * Shows on first visit or when ?ref=veo query param present. - * - * localStorage key: fv_welcome_modal_shown - */ - -'use client'; - -import { useCallback, useEffect, useState } from 'react'; -import { useSearchParams } from 'next/navigation'; - -const STORAGE_KEY = 'fv_welcome_modal_shown'; -const DONT_SHOW_AGAIN_KEY = 'fv_welcome_modal_dont_show'; - -export interface UseWelcomeModalResult { - isOpen: boolean; - openModal: () => void; - closeModal: () => void; - dontShowAgain: () => void; - hasSeenModal: boolean; - isVeoReferral: boolean; -} - -/** - * Hook to manage the welcome modal visibility - * - * Shows the modal when: - * 1. First time visitor (not seen before) - * 2. ?ref=veo query param present (Veo camera discovery) - * - * Does NOT show when: - * 1. User checked "Don't show again" - * 2. Already shown in this session (unless ?ref=veo) - */ -export function useWelcomeModal(): UseWelcomeModalResult { - const [isOpen, setIsOpen] = useState(false); - const [hasSeenModal, setHasSeenModal] = useState(true); - const [isVeoReferral, setIsVeoReferral] = useState(false); - - const searchParams = useSearchParams(); - - useEffect(() => { - // Check if running in browser - if (typeof window === 'undefined') return; - - // Check for ?ref=veo query param (force show) - const ref = searchParams?.get('ref'); - const isVeo = ref === 'veo'; - setIsVeoReferral(isVeo); - - // Check if user opted out - const dontShow = localStorage.getItem(DONT_SHOW_AGAIN_KEY) === 'true'; - if (dontShow && !isVeo) { - setHasSeenModal(true); - return; - } - - // Check if already seen (not first visit) - const alreadySeen = localStorage.getItem(STORAGE_KEY) === 'true'; - setHasSeenModal(alreadySeen); - - // Show modal for first visit or Veo referral - if (!alreadySeen || isVeo) { - // Small delay for better UX (let page render first) - const timer = setTimeout(() => { - setIsOpen(true); - }, 500); - - return () => clearTimeout(timer); - } - }, [searchParams]); - - const openModal = useCallback(() => { - setIsOpen(true); - }, []); - - const closeModal = useCallback(() => { - setIsOpen(false); - - // Mark as seen - if (typeof window !== 'undefined') { - localStorage.setItem(STORAGE_KEY, 'true'); - setHasSeenModal(true); - } - }, []); - - const dontShowAgain = useCallback(() => { - setIsOpen(false); - - if (typeof window !== 'undefined') { - localStorage.setItem(STORAGE_KEY, 'true'); - localStorage.setItem(DONT_SHOW_AGAIN_KEY, 'true'); - setHasSeenModal(true); - } - }, []); - - return { - isOpen, - openModal, - closeModal, - dontShowAgain, - hasSeenModal, - isVeoReferral, - }; -} - -/** - * Reset welcome modal state (for testing) - */ -export function resetWelcomeModal(): void { - if (typeof window === 'undefined') return; - - localStorage.removeItem(STORAGE_KEY); - localStorage.removeItem(DONT_SHOW_AGAIN_KEY); -}