diff --git a/.gitignore b/.gitignore index 3d70248ba2..635ca404d4 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ node_modules .env.production.local build +frontend/dist npm-debug.log* yarn-debug.log* diff --git a/Build_Guide.md b/Build_Guide.md new file mode 100644 index 0000000000..4627231d20 --- /dev/null +++ b/Build_Guide.md @@ -0,0 +1,1943 @@ +# Connect AFRICA — Web Application Build Guide + +## Complete Schema, Structure & Step-by-Step Cursor Instructions + +**Confidential — February 2026** +**Stack: Next.js (React) + Supabase + Stripe + WhatsApp Business API + OpenAI** + +--- + +## TABLE OF CONTENTS + +1. [Application Overview](#1-application-overview) +2. [Tech Stack & Dependencies](#2-tech-stack--dependencies) +3. [Project Folder Structure](#3-project-folder-structure) +4. [Supabase Database Schema](#4-supabase-database-schema) +5. [Authentication & Disclaimer Flow](#5-authentication--disclaimer-flow) +6. [Stripe Payment Architecture](#6-stripe-payment-architecture) +7. [Page-by-Page Breakdown](#7-page-by-page-breakdown) +8. [Step-by-Step Cursor Build Order](#8-step-by-step-cursor-build-order) +9. [API Routes & Edge Functions](#9-api-routes--edge-functions) +10. [Admin Panel](#10-admin-panel) +11. [AI Integration](#11-ai-integration) +12. [WhatsApp Business API Integration](#12-whatsapp-business-api-integration) +13. [Deployment & Environment Variables](#13-deployment--environment-variables) + +--- + +## 1. APPLICATION OVERVIEW + +Connect AFRICA is a LinkedIn-style investment platform connecting global investors with African entrepreneurs and verified service providers. The application has three distinct user types, each with unique flows, dashboards, and features. + +**Core Value Proposition:** Reduce deal friction and increase trust in cross-border African investments through structured listings, verification workflows, and controlled communication. + +**Three User Roles:** +- **Investor** — discovers ventures, filters by country/sector/stage, manages watchlists, requests introductions +- **Entrepreneur** — creates project listings (paid), uploads documents, receives investor interest +- **Service Provider** — subscribes for visibility, captures leads, earns verification badges + +--- + +## 2. TECH STACK & DEPENDENCIES + +### Core Framework +``` +next@14+ # React framework with App Router +react@18+ # UI library +typescript # Type safety +tailwindcss@3+ # Utility-first CSS +shadcn/ui # Component library (built on Radix) +``` + +### Backend & Database +``` +@supabase/supabase-js # Supabase client +@supabase/ssr # Supabase server-side rendering helpers +supabase CLI # Local dev, migrations, edge functions +``` + +### Payments +``` +stripe # Stripe Node.js SDK +@stripe/stripe-js # Stripe browser SDK +@stripe/react-stripe-js # Stripe React components +``` + +### AI & Messaging +``` +openai # OpenAI SDK for AI assistant features +whatsapp-business-api # WhatsApp Business API (or Meta Cloud API) +``` + +### Utilities +``` +zod # Schema validation +react-hook-form # Form management +@tanstack/react-query # Server state management +framer-motion # Animations (hero section, transitions) +lucide-react # Icon library +date-fns # Date utilities +react-player # Video player for hero section +sonner # Toast notifications +``` + +--- + +## 3. PROJECT FOLDER STRUCTURE + +``` +connect-africa/ +├── .env.local # Environment variables +├── supabase/ +│ ├── migrations/ # SQL migration files +│ │ ├── 001_create_profiles.sql +│ │ ├── 002_create_ventures.sql +│ │ ├── 003_create_providers.sql +│ │ ├── 004_create_investors.sql +│ │ ├── 005_create_payments.sql +│ │ ├── 006_create_messaging.sql +│ │ ├── 007_create_watchlists.sql +│ │ ├── 008_create_admin.sql +│ │ ├── 009_create_disclaimers.sql +│ │ └── 010_rls_policies.sql +│ ├── functions/ # Supabase Edge Functions +│ │ ├── stripe-webhook/ +│ │ ├── ai-assistant/ +│ │ ├── whatsapp-notify/ +│ │ └── admin-review/ +│ └── seed.sql # Seed data +├── public/ +│ ├── videos/ +│ │ └── hero-video.mp4 # Landing page hero video +│ ├── images/ +│ │ ├── logo.svg +│ │ ├── testimonials/ +│ │ └── icons/ +│ └── documents/ +│ ├── disclaimer-investor.pdf +│ ├── disclaimer-entrepreneur.pdf +│ └── disclaimer-provider.pdf +├── src/ +│ ├── app/ # Next.js App Router +│ │ ├── layout.tsx # Root layout +│ │ ├── page.tsx # Landing page +│ │ ├── globals.css +│ │ │ +│ │ ├── (auth)/ # Auth group +│ │ │ ├── login/page.tsx +│ │ │ ├── register/page.tsx # Role selection + signup +│ │ │ ├── register/disclaimer/page.tsx # Disclaimer signing +│ │ │ └── callback/route.ts # OAuth callback +│ │ │ +│ │ ├── (public)/ # Public pages +│ │ │ ├── about/page.tsx +│ │ │ ├── advice/ +│ │ │ │ ├── page.tsx # Advice hub +│ │ │ │ ├── investors/page.tsx +│ │ │ │ ├── entrepreneurs/page.tsx +│ │ │ │ └── providers/page.tsx +│ │ │ ├── rules/ +│ │ │ │ ├── page.tsx # Rules hub +│ │ │ │ ├── investors/page.tsx +│ │ │ │ ├── entrepreneurs/page.tsx +│ │ │ │ └── providers/page.tsx +│ │ │ └── terms/page.tsx +│ │ │ +│ │ ├── (dashboard)/ # Protected dashboard group +│ │ │ ├── layout.tsx # Dashboard layout with sidebar +│ │ │ ├── feed/page.tsx # Shared newsfeed +│ │ │ │ +│ │ │ ├── investor/ # Investor dashboard +│ │ │ │ ├── page.tsx # Investor home +│ │ │ │ ├── discover/page.tsx # Browse ventures +│ │ │ │ ├── watchlist/page.tsx +│ │ │ │ ├── deal-rooms/page.tsx +│ │ │ │ ├── deal-rooms/[id]/page.tsx +│ │ │ │ ├── messages/page.tsx +│ │ │ │ └── settings/page.tsx +│ │ │ │ +│ │ │ ├── entrepreneur/ # Entrepreneur dashboard +│ │ │ │ ├── page.tsx # Entrepreneur home +│ │ │ │ ├── project/new/page.tsx # Create listing +│ │ │ │ ├── project/[id]/page.tsx # Edit listing +│ │ │ │ ├── project/[id]/documents/page.tsx +│ │ │ │ ├── interest/page.tsx # Inbound interest +│ │ │ │ ├── providers/page.tsx # Browse providers +│ │ │ │ ├── messages/page.tsx +│ │ │ │ └── settings/page.tsx +│ │ │ │ +│ │ │ ├── provider/ # Service provider dashboard +│ │ │ │ ├── page.tsx # Provider home +│ │ │ │ ├── profile/page.tsx # Edit service profile +│ │ │ │ ├── leads/page.tsx # Incoming leads +│ │ │ │ ├── analytics/page.tsx +│ │ │ │ ├── subscription/page.tsx +│ │ │ │ ├── messages/page.tsx +│ │ │ │ └── settings/page.tsx +│ │ │ │ +│ │ │ └── admin/ # Admin panel +│ │ │ ├── page.tsx # Admin dashboard +│ │ │ ├── ventures/page.tsx # Review submissions +│ │ │ ├── providers/page.tsx # Provider verification +│ │ │ ├── users/page.tsx +│ │ │ ├── payments/page.tsx +│ │ │ ├── disputes/page.tsx +│ │ │ └── audit-log/page.tsx +│ │ │ +│ │ └── api/ # API routes +│ │ ├── stripe/ +│ │ │ ├── checkout/route.ts +│ │ │ ├── webhook/route.ts +│ │ │ ├── portal/route.ts +│ │ │ └── refund/route.ts +│ │ ├── ai/ +│ │ │ ├── profile-assist/route.ts +│ │ │ ├── memo-generate/route.ts +│ │ │ └── summarize/route.ts +│ │ └── whatsapp/ +│ │ ├── send/route.ts +│ │ └── webhook/route.ts +│ │ +│ ├── components/ +│ │ ├── ui/ # shadcn/ui components +│ │ ├── layout/ +│ │ │ ├── Navbar.tsx +│ │ │ ├── Footer.tsx +│ │ │ ├── DashboardSidebar.tsx +│ │ │ └── MobileNav.tsx +│ │ ├── landing/ +│ │ │ ├── HeroVideo.tsx # Full-page hero with video +│ │ │ ├── RoleButtons.tsx # 3 floating role buttons +│ │ │ ├── HowItWorks.tsx # Steps explanation +│ │ │ ├── Testimonials.tsx # Testimonials carousel +│ │ │ └── CTASection.tsx +│ │ ├── auth/ +│ │ │ ├── LoginForm.tsx +│ │ │ ├── RegisterForm.tsx +│ │ │ ├── RoleSelector.tsx +│ │ │ └── DisclaimerModal.tsx +│ │ ├── investor/ +│ │ │ ├── VentureCard.tsx +│ │ │ ├── FilterPanel.tsx +│ │ │ ├── WatchlistButton.tsx +│ │ │ ├── IntroRequestButton.tsx +│ │ │ └── DealRoomView.tsx +│ │ ├── entrepreneur/ +│ │ │ ├── ProjectForm.tsx +│ │ │ ├── DocumentChecklist.tsx +│ │ │ ├── MilestoneTracker.tsx +│ │ │ └── InterestCard.tsx +│ │ ├── provider/ +│ │ │ ├── ServiceProfileForm.tsx +│ │ │ ├── LeadCard.tsx +│ │ │ ├── SubscriptionManager.tsx +│ │ │ └── VerificationBadge.tsx +│ │ ├── messaging/ +│ │ │ ├── MessageThread.tsx +│ │ │ ├── MessageInput.tsx +│ │ │ └── IntroRequest.tsx +│ │ └── shared/ +│ │ ├── CountrySelector.tsx +│ │ ├── SectorSelector.tsx +│ │ ├── StageSelector.tsx +│ │ ├── FileUploader.tsx +│ │ ├── RiskBadge.tsx +│ │ └── ActivityFeed.tsx +│ │ +│ ├── lib/ +│ │ ├── supabase/ +│ │ │ ├── client.ts # Browser Supabase client +│ │ │ ├── server.ts # Server Supabase client +│ │ │ ├── admin.ts # Service role client +│ │ │ └── middleware.ts # Auth middleware +│ │ ├── stripe/ +│ │ │ ├── client.ts +│ │ │ ├── config.ts # Products & prices +│ │ │ └── helpers.ts +│ │ ├── ai/ +│ │ │ └── openai.ts +│ │ ├── whatsapp/ +│ │ │ └── client.ts +│ │ └── utils/ +│ │ ├── constants.ts # Countries, sectors, stages +│ │ ├── formatters.ts +│ │ └── validators.ts +│ │ +│ ├── hooks/ +│ │ ├── useUser.ts +│ │ ├── useRole.ts +│ │ ├── useVentures.ts +│ │ ├── useWatchlist.ts +│ │ ├── useMessages.ts +│ │ └── useSubscription.ts +│ │ +│ └── types/ +│ ├── database.ts # Generated Supabase types +│ ├── stripe.ts +│ └── index.ts +│ +├── middleware.ts # Next.js middleware (auth guard) +├── next.config.js +├── tailwind.config.ts +├── tsconfig.json +└── package.json +``` + +--- + +## 4. SUPABASE DATABASE SCHEMA + +### 4.1 Core Tables + +```sql +-- ============================================================ +-- MIGRATION 001: PROFILES (extends Supabase auth.users) +-- ============================================================ + +-- Enum types +CREATE TYPE user_role AS ENUM ('investor', 'entrepreneur', 'provider', 'admin'); +CREATE TYPE user_status AS ENUM ('pending_disclaimer', 'active', 'suspended', 'deactivated'); + +CREATE TABLE public.profiles ( + id UUID PRIMARY KEY REFERENCES auth.users(id) ON DELETE CASCADE, + role user_role NOT NULL, + status user_status DEFAULT 'pending_disclaimer', + full_name TEXT NOT NULL, + email TEXT NOT NULL, + phone TEXT, + avatar_url TEXT, + country TEXT, -- ISO 3166-1 alpha-2 + city TEXT, + bio TEXT, + linkedin_url TEXT, + whatsapp_optin BOOLEAN DEFAULT FALSE, + disclaimer_signed BOOLEAN DEFAULT FALSE, + disclaimer_signed_at TIMESTAMPTZ, + disclaimer_version TEXT, -- tracks which version was signed + onboarding_completed BOOLEAN DEFAULT FALSE, + created_at TIMESTAMPTZ DEFAULT NOW(), + updated_at TIMESTAMPTZ DEFAULT NOW() +); + +-- Auto-update timestamp +CREATE OR REPLACE FUNCTION update_updated_at() +RETURNS TRIGGER AS $$ +BEGIN + NEW.updated_at = NOW(); + RETURN NEW; +END; +$$ LANGUAGE plpgsql; + +CREATE TRIGGER profiles_updated_at + BEFORE UPDATE ON profiles + FOR EACH ROW EXECUTE FUNCTION update_updated_at(); + + +-- ============================================================ +-- MIGRATION 002: VENTURES (Entrepreneur projects) +-- ============================================================ + +CREATE TYPE venture_status AS ENUM ( + 'draft', 'submitted', 'under_review', 'approved', + 'rejected', 'suspended', 'archived' +); +CREATE TYPE venture_stage AS ENUM ( + 'pre_seed', 'seed', 'series_a', 'series_b', + 'growth', 'bridge', 'other' +); + +CREATE TABLE public.ventures ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + entrepreneur_id UUID NOT NULL REFERENCES profiles(id) ON DELETE CASCADE, + title TEXT NOT NULL, + slug TEXT UNIQUE, + tagline TEXT, + description TEXT, + country TEXT NOT NULL, -- primary country of operations + countries TEXT[], -- additional countries + sector TEXT NOT NULL, + sub_sector TEXT, + stage venture_stage NOT NULL, + funding_ask NUMERIC, -- amount seeking in EUR + currency TEXT DEFAULT 'EUR', + valuation NUMERIC, + revenue_ttm NUMERIC, -- trailing twelve months + team_size INTEGER, + founded_year INTEGER, + website TEXT, + pitch_deck_url TEXT, -- Supabase Storage path + logo_url TEXT, + milestones JSONB DEFAULT '[]', -- [{title, date, description}] + traction_data JSONB DEFAULT '{}', -- {users, revenue, growth_rate, etc.} + team_members JSONB DEFAULT '[]', -- [{name, role, linkedin, bio}] + risk_signals JSONB DEFAULT '{}', -- AI-generated or admin-set flags + status venture_status DEFAULT 'draft', + review_notes TEXT, -- admin notes (internal) + rejection_reason TEXT, + listing_fee_paid BOOLEAN DEFAULT FALSE, + listing_payment_id TEXT, -- Stripe payment intent ID + approved_at TIMESTAMPTZ, + approved_by UUID REFERENCES profiles(id), + created_at TIMESTAMPTZ DEFAULT NOW(), + updated_at TIMESTAMPTZ DEFAULT NOW() +); + +CREATE INDEX idx_ventures_status ON ventures(status); +CREATE INDEX idx_ventures_country ON ventures(country); +CREATE INDEX idx_ventures_sector ON ventures(sector); +CREATE INDEX idx_ventures_stage ON ventures(stage); + +CREATE TRIGGER ventures_updated_at + BEFORE UPDATE ON ventures + FOR EACH ROW EXECUTE FUNCTION update_updated_at(); + + +-- ============================================================ +-- MIGRATION 003: VENTURE DOCUMENTS +-- ============================================================ + +CREATE TYPE document_type AS ENUM ( + 'pitch_deck', 'financials', 'cap_table', 'legal_docs', + 'business_plan', 'due_diligence', 'team_cv', 'other' +); + +CREATE TABLE public.venture_documents ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + venture_id UUID NOT NULL REFERENCES ventures(id) ON DELETE CASCADE, + uploaded_by UUID NOT NULL REFERENCES profiles(id), + doc_type document_type NOT NULL, + file_name TEXT NOT NULL, + file_path TEXT NOT NULL, -- Supabase Storage path + file_size BIGINT, + mime_type TEXT, + is_public BOOLEAN DEFAULT FALSE, -- visible to all investors or only in deal room + created_at TIMESTAMPTZ DEFAULT NOW() +); + + +-- ============================================================ +-- MIGRATION 004: INVESTOR PROFILES +-- ============================================================ + +CREATE TYPE investor_type AS ENUM ( + 'angel', 'vc', 'pe', 'family_office', 'impact', + 'diaspora', 'institutional', 'corporate', 'other' +); + +CREATE TABLE public.investor_profiles ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + user_id UUID UNIQUE NOT NULL REFERENCES profiles(id) ON DELETE CASCADE, + investor_type investor_type, + firm_name TEXT, + thesis TEXT, -- investment thesis + ticket_min NUMERIC, -- minimum ticket size EUR + ticket_max NUMERIC, + preferred_countries TEXT[], -- ISO codes + preferred_sectors TEXT[], + preferred_stages venture_stage[], + portfolio_size INTEGER, + tier TEXT DEFAULT 'free', -- 'free', 'pro', 'team' + stripe_customer_id TEXT, + created_at TIMESTAMPTZ DEFAULT NOW(), + updated_at TIMESTAMPTZ DEFAULT NOW() +); + +CREATE TRIGGER investor_profiles_updated_at + BEFORE UPDATE ON investor_profiles + FOR EACH ROW EXECUTE FUNCTION update_updated_at(); + + +-- ============================================================ +-- MIGRATION 005: SERVICE PROVIDERS +-- ============================================================ + +CREATE TYPE provider_category AS ENUM ( + 'legal', 'accounting', 'tax', 'audit', 'consulting', + 'logistics', 'real_estate', 'hr', 'marketing', + 'technology', 'insurance', 'other' +); +CREATE TYPE provider_status AS ENUM ( + 'pending', 'active', 'suspended', 'expired' +); +CREATE TYPE verification_level AS ENUM ( + 'unverified', 'basic', 'verified', 'premium' +); + +CREATE TABLE public.service_providers ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + user_id UUID UNIQUE NOT NULL REFERENCES profiles(id) ON DELETE CASCADE, + company_name TEXT NOT NULL, + category provider_category NOT NULL, + sub_categories TEXT[], + description TEXT, + services_offered JSONB DEFAULT '[]', -- [{name, description, price_range}] + countries_covered TEXT[] NOT NULL, + website TEXT, + logo_url TEXT, + verification_level verification_level DEFAULT 'unverified', + verification_notes TEXT, + verified_at TIMESTAMPTZ, + verified_by UUID REFERENCES profiles(id), + partner_references JSONB DEFAULT '[]', -- [{name, contact, relationship}] + pricing_info TEXT, + availability TEXT, + status provider_status DEFAULT 'pending', + subscription_tier TEXT, -- 'monthly', 'annual' + stripe_customer_id TEXT, + stripe_subscription_id TEXT, + subscription_expires_at TIMESTAMPTZ, + lead_count INTEGER DEFAULT 0, + created_at TIMESTAMPTZ DEFAULT NOW(), + updated_at TIMESTAMPTZ DEFAULT NOW() +); + +CREATE INDEX idx_providers_category ON service_providers(category); +CREATE INDEX idx_providers_countries ON service_providers USING GIN(countries_covered); +CREATE INDEX idx_providers_status ON service_providers(status); + +CREATE TRIGGER service_providers_updated_at + BEFORE UPDATE ON service_providers + FOR EACH ROW EXECUTE FUNCTION update_updated_at(); + + +-- ============================================================ +-- MIGRATION 006: PAYMENTS & TRANSACTIONS +-- ============================================================ + +CREATE TYPE payment_type AS ENUM ( + 'listing_fee', 'provider_subscription', 'investor_upgrade', + 'refund' +); +CREATE TYPE payment_status AS ENUM ( + 'pending', 'completed', 'failed', 'refunded', 'partially_refunded' +); + +CREATE TABLE public.payments ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + user_id UUID NOT NULL REFERENCES profiles(id), + payment_type payment_type NOT NULL, + amount NUMERIC NOT NULL, + currency TEXT DEFAULT 'EUR', + stripe_payment_intent_id TEXT, + stripe_checkout_session_id TEXT, + stripe_subscription_id TEXT, + status payment_status DEFAULT 'pending', + refund_amount NUMERIC, + refund_reason TEXT, + metadata JSONB DEFAULT '{}', -- flexible extra data + created_at TIMESTAMPTZ DEFAULT NOW(), + updated_at TIMESTAMPTZ DEFAULT NOW() +); + +CREATE INDEX idx_payments_user ON payments(user_id); +CREATE INDEX idx_payments_stripe ON payments(stripe_payment_intent_id); + +CREATE TRIGGER payments_updated_at + BEFORE UPDATE ON payments + FOR EACH ROW EXECUTE FUNCTION update_updated_at(); + + +-- ============================================================ +-- MIGRATION 007: MESSAGING & INTRO REQUESTS +-- ============================================================ + +CREATE TYPE intro_status AS ENUM ( + 'pending', 'accepted', 'declined', 'expired' +); + +CREATE TABLE public.intro_requests ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + from_user_id UUID NOT NULL REFERENCES profiles(id), + to_user_id UUID NOT NULL REFERENCES profiles(id), + venture_id UUID REFERENCES ventures(id), + message TEXT, + status intro_status DEFAULT 'pending', + responded_at TIMESTAMPTZ, + created_at TIMESTAMPTZ DEFAULT NOW() +); + +CREATE INDEX idx_intro_from ON intro_requests(from_user_id); +CREATE INDEX idx_intro_to ON intro_requests(to_user_id); + +CREATE TABLE public.conversations ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + participant_ids UUID[] NOT NULL, -- array of user IDs + venture_id UUID REFERENCES ventures(id), + intro_request_id UUID REFERENCES intro_requests(id), + is_deal_room BOOLEAN DEFAULT FALSE, + title TEXT, + created_at TIMESTAMPTZ DEFAULT NOW(), + updated_at TIMESTAMPTZ DEFAULT NOW() +); + +CREATE TABLE public.messages ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + conversation_id UUID NOT NULL REFERENCES conversations(id) ON DELETE CASCADE, + sender_id UUID NOT NULL REFERENCES profiles(id), + content TEXT NOT NULL, + attachments JSONB DEFAULT '[]', -- [{file_name, file_path, mime_type}] + read_by UUID[] DEFAULT '{}', + created_at TIMESTAMPTZ DEFAULT NOW() +); + +CREATE INDEX idx_messages_conversation ON messages(conversation_id); +CREATE INDEX idx_messages_created ON messages(created_at DESC); + + +-- ============================================================ +-- MIGRATION 008: WATCHLISTS & SAVED ITEMS +-- ============================================================ + +CREATE TABLE public.watchlist_items ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + user_id UUID NOT NULL REFERENCES profiles(id) ON DELETE CASCADE, + venture_id UUID NOT NULL REFERENCES ventures(id) ON DELETE CASCADE, + notes TEXT, + alert_on_update BOOLEAN DEFAULT TRUE, + created_at TIMESTAMPTZ DEFAULT NOW(), + UNIQUE(user_id, venture_id) +); + +CREATE TABLE public.saved_providers ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + user_id UUID NOT NULL REFERENCES profiles(id) ON DELETE CASCADE, + provider_id UUID NOT NULL REFERENCES service_providers(id) ON DELETE CASCADE, + created_at TIMESTAMPTZ DEFAULT NOW(), + UNIQUE(user_id, provider_id) +); + + +-- ============================================================ +-- MIGRATION 009: DEAL ROOMS +-- ============================================================ + +CREATE TABLE public.deal_rooms ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + venture_id UUID NOT NULL REFERENCES ventures(id), + created_by UUID NOT NULL REFERENCES profiles(id), + title TEXT NOT NULL, + member_ids UUID[] NOT NULL, + status TEXT DEFAULT 'active', -- 'active', 'closed', 'archived' + notes JSONB DEFAULT '[]', -- [{user_id, content, timestamp}] + shared_documents UUID[], -- references to venture_documents + created_at TIMESTAMPTZ DEFAULT NOW(), + updated_at TIMESTAMPTZ DEFAULT NOW() +); + + +-- ============================================================ +-- MIGRATION 010: DISCLAIMERS & LEGAL +-- ============================================================ + +CREATE TABLE public.disclaimers ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + version TEXT NOT NULL UNIQUE, -- e.g., 'v1.0', 'v1.1' + role user_role NOT NULL, -- different disclaimer per role + title TEXT NOT NULL, + content TEXT NOT NULL, -- full legal text (markdown) + is_active BOOLEAN DEFAULT TRUE, + created_at TIMESTAMPTZ DEFAULT NOW() +); + +CREATE TABLE public.disclaimer_signatures ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + user_id UUID NOT NULL REFERENCES profiles(id) ON DELETE CASCADE, + disclaimer_id UUID NOT NULL REFERENCES disclaimers(id), + ip_address INET, + user_agent TEXT, + signature_data JSONB, -- {full_name, agreed: true, timestamp} + signed_at TIMESTAMPTZ DEFAULT NOW() +); + +CREATE INDEX idx_signatures_user ON disclaimer_signatures(user_id); + + +-- ============================================================ +-- MIGRATION 011: ADMIN & AUDIT +-- ============================================================ + +CREATE TYPE audit_action AS ENUM ( + 'venture_submitted', 'venture_approved', 'venture_rejected', + 'venture_suspended', 'provider_verified', 'provider_suspended', + 'user_suspended', 'user_activated', 'refund_issued', + 'payment_received', 'intro_sent', 'intro_accepted', + 'intro_declined', 'document_uploaded', 'profile_updated', + 'disclaimer_signed', 'deal_room_created', 'message_sent', + 'admin_action' +); + +CREATE TABLE public.audit_log ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + actor_id UUID REFERENCES profiles(id), + action audit_action NOT NULL, + target_type TEXT, -- 'venture', 'user', 'payment', etc. + target_id UUID, + details JSONB DEFAULT '{}', -- flexible context + ip_address INET, + created_at TIMESTAMPTZ DEFAULT NOW() +); + +CREATE INDEX idx_audit_actor ON audit_log(actor_id); +CREATE INDEX idx_audit_action ON audit_log(action); +CREATE INDEX idx_audit_target ON audit_log(target_type, target_id); +CREATE INDEX idx_audit_created ON audit_log(created_at DESC); + + +-- ============================================================ +-- MIGRATION 012: NEWSFEED / ACTIVITY +-- ============================================================ + +CREATE TABLE public.feed_posts ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + author_id UUID NOT NULL REFERENCES profiles(id), + content TEXT NOT NULL, + post_type TEXT DEFAULT 'update', -- 'update', 'milestone', 'announcement', 'article' + venture_id UUID REFERENCES ventures(id), + attachments JSONB DEFAULT '[]', + visibility TEXT DEFAULT 'public', -- 'public', 'investors_only', 'private' + likes_count INTEGER DEFAULT 0, + comments_count INTEGER DEFAULT 0, + created_at TIMESTAMPTZ DEFAULT NOW() +); + +CREATE TABLE public.feed_comments ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + post_id UUID NOT NULL REFERENCES feed_posts(id) ON DELETE CASCADE, + author_id UUID NOT NULL REFERENCES profiles(id), + content TEXT NOT NULL, + created_at TIMESTAMPTZ DEFAULT NOW() +); + +CREATE TABLE public.feed_likes ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + post_id UUID NOT NULL REFERENCES feed_posts(id) ON DELETE CASCADE, + user_id UUID NOT NULL REFERENCES profiles(id) ON DELETE CASCADE, + created_at TIMESTAMPTZ DEFAULT NOW(), + UNIQUE(post_id, user_id) +); + + +-- ============================================================ +-- MIGRATION 013: PROVIDER LEADS +-- ============================================================ + +CREATE TABLE public.provider_leads ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + provider_id UUID NOT NULL REFERENCES service_providers(id) ON DELETE CASCADE, + from_user_id UUID NOT NULL REFERENCES profiles(id), + venture_id UUID REFERENCES ventures(id), + message TEXT, + status TEXT DEFAULT 'new', -- 'new', 'contacted', 'converted', 'closed' + created_at TIMESTAMPTZ DEFAULT NOW() +); + + +-- ============================================================ +-- MIGRATION 014: ROW LEVEL SECURITY (RLS) +-- ============================================================ + +ALTER TABLE profiles ENABLE ROW LEVEL SECURITY; +ALTER TABLE ventures ENABLE ROW LEVEL SECURITY; +ALTER TABLE venture_documents ENABLE ROW LEVEL SECURITY; +ALTER TABLE investor_profiles ENABLE ROW LEVEL SECURITY; +ALTER TABLE service_providers ENABLE ROW LEVEL SECURITY; +ALTER TABLE payments ENABLE ROW LEVEL SECURITY; +ALTER TABLE intro_requests ENABLE ROW LEVEL SECURITY; +ALTER TABLE conversations ENABLE ROW LEVEL SECURITY; +ALTER TABLE messages ENABLE ROW LEVEL SECURITY; +ALTER TABLE watchlist_items ENABLE ROW LEVEL SECURITY; +ALTER TABLE saved_providers ENABLE ROW LEVEL SECURITY; +ALTER TABLE deal_rooms ENABLE ROW LEVEL SECURITY; +ALTER TABLE disclaimers ENABLE ROW LEVEL SECURITY; +ALTER TABLE disclaimer_signatures ENABLE ROW LEVEL SECURITY; +ALTER TABLE audit_log ENABLE ROW LEVEL SECURITY; +ALTER TABLE feed_posts ENABLE ROW LEVEL SECURITY; +ALTER TABLE feed_comments ENABLE ROW LEVEL SECURITY; +ALTER TABLE feed_likes ENABLE ROW LEVEL SECURITY; +ALTER TABLE provider_leads ENABLE ROW LEVEL SECURITY; + +-- PROFILES: users can read all active profiles, edit own +CREATE POLICY "Public profiles readable" + ON profiles FOR SELECT + USING (status = 'active' OR id = auth.uid()); + +CREATE POLICY "Users update own profile" + ON profiles FOR UPDATE + USING (id = auth.uid()); + +-- VENTURES: approved ventures public, own ventures always visible +CREATE POLICY "Approved ventures public" + ON ventures FOR SELECT + USING (status = 'approved' OR entrepreneur_id = auth.uid()); + +CREATE POLICY "Entrepreneurs manage own ventures" + ON ventures FOR ALL + USING (entrepreneur_id = auth.uid()); + +-- ADMIN: full access for admin role +CREATE POLICY "Admin full access profiles" + ON profiles FOR ALL + USING ( + EXISTS (SELECT 1 FROM profiles WHERE id = auth.uid() AND role = 'admin') + ); + +-- (Additional RLS policies follow same pattern for all tables) +-- Investors can see approved ventures +-- Providers can manage own profiles +-- Messages visible only to conversation participants +-- Audit log admin-only +-- Watchlists are private to the owner +``` + +### 4.2 Supabase Storage Buckets + +``` +Buckets to create: +├── avatars/ # Profile photos (public) +├── venture-logos/ # Venture logos (public) +├── venture-docs/ # Pitch decks, financials (private, RLS) +├── provider-logos/ # Provider logos (public) +├── feed-attachments/ # Newsfeed images/files (public) +└── legal-docs/ # Signed disclaimers (private, admin-only) +``` + +--- + +## 5. AUTHENTICATION & DISCLAIMER FLOW + +### Registration Flow (Critical — All Users Must Sign Disclaimer) + +``` +1. User lands on /register +2. User selects role: Investor | Entrepreneur | Service Provider +3. User enters: email, password, full name, country +4. Supabase Auth creates account → profile row created via trigger +5. Profile status set to 'pending_disclaimer' +6. Redirect to /register/disclaimer +7. Show role-specific disclaimer (full legal text, scrollable) +8. User must: + a. Scroll to bottom (or checkbox "I have read the above") + b. Type full name as electronic signature + c. Check "I agree to the terms and conditions" + d. Click "Sign & Continue" +9. Record in disclaimer_signatures table (with IP, user_agent, timestamp) +10. Update profile: disclaimer_signed = true, status = 'active' +11. Redirect to role-specific onboarding or dashboard +``` + +### Middleware Guard Logic + +```typescript +// middleware.ts — enforces disclaimer signing +// If user is logged in but disclaimer_signed = false, +// redirect to /register/disclaimer for ANY protected route +// If user is not logged in, redirect to /login for protected routes +// Admin routes require role = 'admin' +``` + +--- + +## 6. STRIPE PAYMENT ARCHITECTURE + +### 6.1 Products & Prices + +``` +Stripe Products: +├── Entrepreneur Listing Fee +│ └── Price: €1,000 one-time (payment_type: 'listing_fee') +│ └── Metadata: { refundable_portion: 800, non_refundable: 200 } +│ +├── Service Provider Subscription +│ ├── Monthly: €X/month (payment_type: 'provider_subscription') +│ └── Annual: €Y/year (payment_type: 'provider_subscription') +│ +└── Investor Premium + ├── Pro Tier: €X/month (advanced filters, deal rooms) + └── Team Tier: €X/month (multi-seat, internal approvals) +``` + +### 6.2 Payment Flows + +**Entrepreneur Listing Payment:** +``` +1. Entrepreneur completes project draft +2. Clicks "Submit for Review" → Stripe Checkout (€1,000) +3. Webhook: payment_intent.succeeded + → Create payment record + → Update venture: listing_fee_paid = true, status = 'submitted' + → Log audit event +4. Admin reviews → approve/reject +5. If rejected: + → Admin triggers partial refund (€800 refundable portion) + → Platform keeps €200 non-refundable review fee +``` + +**Provider Subscription:** +``` +1. Provider completes profile +2. Selects plan → Stripe Checkout (subscription) +3. Webhook: customer.subscription.created + → Update provider: subscription_tier, subscription_expires_at + → Activate provider listing +4. Webhook: customer.subscription.deleted + → Deactivate provider listing +``` + +**Investor Upgrade:** +``` +1. Investor on free tier → clicks upgrade +2. Stripe Checkout (subscription) +3. Webhook updates investor tier +``` + +--- + +## 7. PAGE-BY-PAGE BREAKDOWN + +### 7.1 Landing Page (`/` — `src/app/page.tsx`) + +``` +SECTION 1: HERO (full viewport height) +┌─────────────────────────────────────────────────┐ +│ │ +│ [Full-screen background video playing] │ +│ (autoplay, muted, loop) │ +│ │ +│ Overlay: semi-transparent dark gradient │ +│ │ +│ Center: │ +│ CONNECT AFRICA logo (large, white) │ +│ "The trust layer for cross-border │ +│ investment into Africa" │ +│ │ +│ Bottom floating buttons (3 columns): │ +│ ┌────────────┐ ┌────────────┐ ┌────────────┐ │ +│ │ INVESTOR │ │ENTREPRENEUR│ │ SERVICE │ │ +│ │ "Find │ │ "Get your │ │ PROVIDER │ │ +│ │ vetted │ │ venture │ │ "Connect │ │ +│ │ deals" │ │ funded" │ │ with │ │ +│ │ │ │ │ │ clients" │ │ +│ └────────────┘ └────────────┘ └────────────┘ │ +│ (Each button links to /register?role=X) │ +└─────────────────────────────────────────────────┘ + +SECTION 2: HOW IT WORKS (below the fold) +┌─────────────────────────────────────────────────┐ +│ "How Connect AFRICA Works" │ +│ │ +│ Three columns (responsive → stacked on mobile): │ +│ │ +│ FOR INVESTORS: FOR ENTREPRENEURS: │ +│ 1. Create profile 1. Pay listing fee │ +│ 2. Browse ventures 2. Build your profile │ +│ 3. Request intros 3. Get reviewed/approved │ +│ 4. Enter deal rooms 4. Receive investor │ +│ interest │ +│ │ +│ FOR SERVICE PROVIDERS: │ +│ 1. Subscribe │ +│ 2. Get verified │ +│ 3. Receive leads │ +│ 4. Close deals │ +│ │ +│ [CTA: "Join the Platform →"] │ +└─────────────────────────────────────────────────┘ + +SECTION 3: WHY CONNECT AFRICA +┌─────────────────────────────────────────────────┐ +│ Problem → Solution grid │ +│ "Information asymmetry" → "Standardized data" │ +│ "Trust deficits" → "Paid listings + verification"│ +│ "High deal friction" → "Structured workflows" │ +└─────────────────────────────────────────────────┘ + +SECTION 4: PLATFORM STATS / SOCIAL PROOF +┌─────────────────────────────────────────────────┐ +│ Animated counters: │ +│ "54 African Nations" | "X Ventures" | "X │ +│ Investors" | "X Service Providers" │ +└─────────────────────────────────────────────────┘ + +SECTION 5: TESTIMONIALS +┌─────────────────────────────────────────────────┐ +│ "What Our Users Say" │ +│ │ +│ Carousel of testimonial cards: │ +│ ┌──────────────────────┐ │ +│ │ Photo | Name | Role │ │ +│ │ "Quote text..." │ │ +│ │ Company / Country │ │ +│ └──────────────────────┘ │ +│ │ +│ Categories: Investor | Entrepreneur | Provider │ +│ (filter tabs above carousel) │ +└─────────────────────────────────────────────────┘ + +SECTION 6: CTA + FOOTER +┌─────────────────────────────────────────────────┐ +│ "Ready to connect?" │ +│ [Get Started] [Learn More] │ +│ │ +│ Footer: Links to Advice, Rules, Terms, About │ +└─────────────────────────────────────────────────┘ +``` + +### 7.2 Advice Pages (`/advice/*`) + +Each role gets a dedicated advice page with practical guidance: + +**`/advice/investors`** — Advice for Investors +- Understanding African markets: cultural context, relationship-first reality +- Due diligence checklist for cross-border deals +- Risk assessment framework (what "good" looks like per country/sector) +- Common failure patterns and how to avoid them +- Working with local service providers +- Timeline expectations vs. Western norms +- Post-investment governance best practices + +**`/advice/entrepreneurs`** — Advice for Entrepreneurs +- What investors actually look for (structured data, clear metrics) +- How to build an investor-ready profile +- Document preparation checklist by stage +- Pricing your round and setting realistic valuations +- Communicating traction effectively +- Working with service providers for compliance +- Managing investor relationships post-intro + +**`/advice/providers`** — Advice for Service Providers +- How to build a compelling provider profile +- Verification process and how to earn premium badges +- Lead conversion best practices +- Pricing transparency expectations +- Country coverage and specialization strategy +- Building reputation through platform references + +### 7.3 Rules Pages (`/rules/*`) + +**`/rules` — Platform Rules Hub** (links to all three below) + +**`/rules/investors`** — Investor Rules +- Account verification requirements +- Communication guidelines (no spam, no unsolicited bulk messages) +- Intro request limits and etiquette +- Confidentiality obligations for deal room documents +- Prohibited activities (front-running, sharing deal info externally) +- Dispute resolution process +- Account suspension/termination conditions + +**`/rules/entrepreneurs`** — Entrepreneur Rules +- Listing accuracy and truthfulness requirements +- Mandatory disclosures (conflicts, legal issues, material risks) +- Document authenticity standards +- Response time expectations for investor inquiries +- Prohibited listings (illegal activities, sanctions violations) +- Refund policy for rejected listings +- Profile maintenance and update obligations + +**`/rules/providers`** — Service Provider Rules +- Verification truthfulness requirements +- Service delivery standards +- Pricing transparency obligations +- Client confidentiality requirements +- Conflict of interest disclosure +- Subscription terms and cancellation +- Lead response time expectations +- Grounds for badge revocation + +### 7.4 Dashboard Pages (Protected) + +**Shared Newsfeed** — `/feed` +- Posts from ventures (milestones, updates) +- Platform announcements +- Provider highlights +- Like/comment functionality + +**Investor Dashboard** — `/investor` +- Overview: recent activity, watchlist updates, pending intros +- Discover: filterable venture grid (country, sector, stage, risk) +- Watchlist: saved ventures with alert preferences +- Deal Rooms: active rooms with document tracking +- Messages: conversations from accepted intros + +**Entrepreneur Dashboard** — `/entrepreneur` +- Overview: listing status, inbound interest count, profile completeness +- Project management: create/edit project, upload documents +- Interest: inbound intro requests (accept/decline) +- Providers: browse vetted service providers +- Messages: conversations with investors + +**Provider Dashboard** — `/provider` +- Overview: lead count, subscription status, analytics +- Profile: edit services, coverage, pricing +- Leads: incoming lead management +- Analytics: views, clicks, conversion rates +- Subscription: manage plan, billing + +--- + +## 8. STEP-BY-STEP CURSOR BUILD ORDER + +### PHASE 1: Project Scaffolding (Steps 1–5) + +**Step 1: Initialize Next.js Project** +``` +Cursor prompt: "Create a new Next.js 14 project with TypeScript, Tailwind CSS, +App Router, and src/ directory. Install shadcn/ui with the default config. +Add Supabase client packages (@supabase/supabase-js, @supabase/ssr). +Add Stripe packages (stripe, @stripe/stripe-js, @stripe/react-stripe-js). +Add react-player, framer-motion, zod, react-hook-form, @tanstack/react-query, +lucide-react, date-fns, sonner." +``` + +**Step 2: Set Up Environment Variables** +``` +Cursor prompt: "Create .env.local with placeholders for: +NEXT_PUBLIC_SUPABASE_URL, NEXT_PUBLIC_SUPABASE_ANON_KEY, +SUPABASE_SERVICE_ROLE_KEY, NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY, +STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET, OPENAI_API_KEY, +WHATSAPP_API_TOKEN, NEXT_PUBLIC_APP_URL. +Create a lib/env.ts that validates these with zod." +``` + +**Step 3: Initialize Supabase** +``` +Cursor prompt: "Set up Supabase clients in src/lib/supabase/: +- client.ts: browser client using createBrowserClient +- server.ts: server client using createServerClient with cookies +- admin.ts: service role client for admin operations +- middleware.ts: auth helper for Next.js middleware +Create the Next.js middleware.ts at root that checks auth status and +redirects unauthenticated users from protected routes." +``` + +**Step 4: Run Database Migrations** +``` +Cursor prompt: "Create all Supabase SQL migration files in supabase/migrations/ +using the complete database schema provided. Include all tables, types, indexes, +triggers, and RLS policies. Create a seed.sql with: +- 3 disclaimer records (one per role) +- An admin user profile +- Sample sector/country reference data." +``` + +**Step 5: Generate TypeScript Types** +``` +Cursor prompt: "Run supabase gen types typescript to generate database types. +Create src/types/database.ts with the generated types. Create src/types/index.ts +with application-level types extending the database types." +``` + +### PHASE 2: Layout & Shared Components (Steps 6–9) + +**Step 6: Create Root Layout & Global Styles** +``` +Cursor prompt: "Create the root layout.tsx with: +- Supabase auth provider +- React Query provider +- Sonner toaster +- Global metadata (title, description, OG tags for Connect AFRICA) +Set up globals.css with Tailwind config, custom CSS variables for brand colors +(Africa-inspired: gold #D4A843, deep green #1B5E20, warm brown #5D4037, +sky blue #0277BD). Configure tailwind.config.ts with these custom colors." +``` + +**Step 7: Build Navbar & Footer** +``` +Cursor prompt: "Create components/layout/Navbar.tsx: +- Logo left, nav links center, auth buttons right +- Mobile hamburger menu +- Conditional rendering based on auth state (Login/Register vs Dashboard link) +- Role-aware dashboard link + +Create components/layout/Footer.tsx: +- Site links: About, Advice, Rules, Terms +- Social links +- Copyright +- Contact info placeholder" +``` + +**Step 8: Build Dashboard Layout** +``` +Cursor prompt: "Create the (dashboard)/layout.tsx with: +- Left sidebar navigation (collapsible on mobile) +- Role-aware menu items (different nav for investor/entrepreneur/provider/admin) +- Top bar with user avatar, notifications bell, role badge +- Main content area +Use shadcn/ui Sheet for mobile sidebar." +``` + +**Step 9: Build Shared Components** +``` +Cursor prompt: "Create these shared components: +- CountrySelector: dropdown of 54 African nations with flags +- SectorSelector: multi-select for investment sectors +- StageSelector: venture stage picker +- FileUploader: drag-and-drop with Supabase Storage integration +- RiskBadge: colored badge showing risk level +- ActivityFeed: reusable activity stream component +Use shadcn/ui Select, MultiSelect, and Badge components." +``` + +### PHASE 3: Landing Page (Steps 10–13) + +**Step 10: Build Hero Video Section** +``` +Cursor prompt: "Create components/landing/HeroVideo.tsx: +- Full viewport height (100vh) container +- Background video using react-player or HTML5