import React, { useState, useEffect, useCallback } from 'react'; import { Check, AlertCircle, ArrowLeft, Loader2, Globe2, TreePine, Waves, Factory, Wind, X, User, Mail, Phone, Building, Flame, Snowflake, Mountain, Sprout, Package, Droplet, Leaf, Zap } from 'lucide-react'; import { motion, AnimatePresence } from 'framer-motion'; import { getPortfolios } from '../api/wrenClient'; import { createCheckoutSession } from '../api/checkoutClient'; import type { OffsetOrder as OffsetOrderType, Portfolio, OffsetProject } from '../types'; import { currencies, formatCurrency } from '../utils/currencies'; import { config } from '../utils/config'; import { sendFormspreeEmail } from '../utils/email'; import { logger } from '../utils/logger'; import { FormInput } from './forms/FormInput'; import { FormTextarea } from './forms/FormTextarea'; import { RadialProgress } from './RadialProgress'; import { PortfolioDonutChart } from './PortfolioDonutChart'; import { getProjectColor } from '../utils/portfolioColors'; import { CertificationBadge } from './CertificationBadge'; import { CarbonImpactComparison } from './CarbonImpactComparison'; import { useCalculatorState } from '../hooks/useCalculatorState'; interface Props { tons: number; monetaryAmount?: number; onBack: () => void; calculatorType: 'trip' | 'annual'; } interface ProjectTypeIconProps { project: OffsetProject; } const ProjectTypeIcon = ({ project }: ProjectTypeIconProps) => { // Safely check if project exists if (!project || !project.name) { return ; } const name = project.name.toLowerCase(); // Match on project name to determine appropriate icon if (name.includes('rainforest') || name.includes('forest')) { return ; } else if (name.includes('biochar') && name.includes('carbon removal')) { return ; } else if (name.includes('refrigerant')) { return ; } else if (name.includes('rock weathering') || name.includes('enhanced weathering')) { return ; } else if (name.includes('rice paddies') || name.includes('paddies')) { return ; } else if (name.includes('mangrove') || name.includes('blue carbon')) { return ; } else if (name.includes('biomass storage') || name.includes('underground')) { return ; } else if (name.includes('bio-oil') || name.includes('oil')) { return ; } else if (name.includes('adipic') || name.includes('nitrous oxide') || name.includes('factory')) { return ; } else if (name.includes('renewable') || name.includes('wind') || name.includes('solar')) { return ; } else if (name.includes('direct air capture') || name.includes('dac')) { return ; } else { return ; } }; export function OffsetOrder({ tons, monetaryAmount, onBack, calculatorType }: Props) { const { state: savedState, saveState } = useCalculatorState(); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const [success, setSuccess] = useState(false); const [order, setOrder] = useState(null); const [portfolio, setPortfolio] = useState(null); const [loadingPortfolio, setLoadingPortfolio] = useState(true); const [offsetPercentage, setOffsetPercentage] = useState( savedState?.offsetPercentage ?? 100 // Default to 100% or use saved value ); // Calculate price per ton (rounded up) const roundedPricePerTon = portfolio ? Math.ceil(portfolio.pricePerTon || 18) : 18; // Calculate the actual tons to offset based on percentage // If monetaryAmount is provided (custom amount), calculate tons from the monetary amount const baseTons = monetaryAmount ? (monetaryAmount / roundedPricePerTon) : tons; const actualOffsetTons = (baseTons * offsetPercentage) / 100; // Format tons for display const formatTons = (tons: number): string => { const fixed = tons.toFixed(2); const parts = fixed.split('.'); parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ','); return parts.join('.'); }; const [formData, setFormData] = useState({ name: '', email: '', phone: '', company: '', message: `I would like to offset ${formatTons(actualOffsetTons)} tons of CO2 from my yacht's ${calculatorType} emissions.` }); // Update form message when percentage changes useEffect(() => { setFormData(prev => ({ ...prev, message: `I would like to offset ${formatTons(actualOffsetTons)} tons of CO2 (${offsetPercentage}% of ${formatTons(tons)} tons) from my yacht's ${calculatorType} emissions.` })); }, [offsetPercentage, actualOffsetTons, tons, calculatorType]); // Save offset percentage and portfolio ID to localStorage useEffect(() => { if (savedState) { saveState({ ...savedState, offsetPercentage, portfolioId: portfolio?.id, }); } }, [offsetPercentage, portfolio, savedState, saveState]); useEffect(() => { if (!config.wrenApiKey) { setError('Carbon offset service is currently unavailable. Please use our contact form to request offsetting.'); setLoadingPortfolio(false); return; } fetchPortfolio(); }, []); const fetchPortfolio = async () => { try { const allPortfolios = await getPortfolios(); // Check if portfolios were returned if (!allPortfolios || allPortfolios.length === 0) { throw new Error('No portfolios available'); } // Only get the puffin portfolio, no selection allowed const puffinPortfolio = allPortfolios.find(p => p.name.toLowerCase().includes('puffin') || p.name.toLowerCase().includes('maritime') ); if (puffinPortfolio) { logger.log('[OffsetOrder] Found Puffin portfolio with ID:', puffinPortfolio.id); setPortfolio(puffinPortfolio); } else { // Default to first portfolio if no puffin portfolio found logger.log('[OffsetOrder] No Puffin portfolio found, using first available portfolio with ID:', allPortfolios[0].id); setPortfolio(allPortfolios[0]); } } catch (err) { setError('Failed to fetch portfolio information. Please try again.'); } finally { setLoadingPortfolio(false); } }; const handleOffsetOrder = async () => { if (!portfolio) return; setLoading(true); setError(null); try { logger.info('[OffsetOrder] Creating checkout session for', actualOffsetTons, 'tons with portfolio', portfolio.id); // Create Stripe Checkout Session const checkoutSession = await createCheckoutSession({ tons: actualOffsetTons, portfolioId: portfolio.id, pricePerTon: roundedPricePerTon, // Pass the rounded-up price that matches calculator display }); logger.info('[OffsetOrder] Checkout session created:', checkoutSession.sessionId); // Redirect to Stripe Checkout window.location.href = checkoutSession.url; } catch (err) { logger.error('[OffsetOrder] Failed to create checkout session:', err); setError('Failed to create checkout session. Please try again.'); setLoading(false); } }; const renderPortfolioPrice = (portfolio: Portfolio) => { try { // Get the price per ton from the portfolio and round UP to next whole number const pricePerTon = Math.ceil(portfolio.pricePerTon || 18); return formatCurrency(pricePerTon, currencies.USD); } catch (err) { console.error('Error formatting portfolio price:', err); return formatCurrency(18, currencies.USD); } }; // Calculate offset cost using the portfolio price (rounded UP to match display) const offsetCost = monetaryAmount || (portfolio ? actualOffsetTons * roundedPricePerTon : 0); return ( Back to Calculator

Offset Your Impact

You're about to offset {formatTons(tons)} tons of CO₂

{error && !config.wrenApiKey ? (

Contact Us for Offsetting

Our automated offsetting service is temporarily unavailable. Please fill out the form below and our team will help you offset your emissions.

{ e.preventDefault(); setLoading(true); try { await sendFormspreeEmail(formData, 'offset'); setSuccess(true); } catch (err) { setError('Failed to send request. Please try again.'); } finally { setLoading(false); } }} className="space-y-6"> } type="text" required value={formData.name} onChange={(e) => setFormData(prev => ({ ...prev, name: e.target.value }))} /> } type="email" required value={formData.email} onChange={(e) => setFormData(prev => ({ ...prev, email: e.target.value }))} /> } type="tel" value={formData.phone} onChange={(e) => setFormData(prev => ({ ...prev, phone: e.target.value }))} /> } type="text" value={formData.company} onChange={(e) => setFormData(prev => ({ ...prev, company: e.target.value }))} /> setFormData(prev => ({ ...prev, message: e.target.value }))} />
) : error ? (

{error}

) : success && order ? (

Offset Order Successful!

Your order has been processed successfully. You'll receive a confirmation email shortly.

Order Summary

Order ID: {order.id}
Amount: {formatCurrency(order.amountCharged / 100, currencies[order.currency])}
CO₂ Offset: {order.tons} tons
Portfolio: {order.portfolio.name}
) : loadingPortfolio ? (
Loading portfolio information...
) : portfolio ? ( <>

{portfolio.name}

{portfolio.description}

{/* Portfolio Allocation Visualization */} {portfolio.projects && portfolio.projects.length > 0 && (

Portfolio Distribution

)} {/* Project Cards */} {portfolio.projects && portfolio.projects.length > 0 && ( {portfolio.projects.map((project, index) => ( {/* Header with title and radial progress */}

{project.name}

{project.percentage && ( )}
{/* Project image */} {project.imageUrl && (
{project.name}
)} {/* Description - This will grow to push price and button to bottom */}

{project.shortDescription || project.description}

))} )} Portfolio Price per Ton: {renderPortfolioPrice(portfolio)}
{/* Offset Percentage Slider */} {!monetaryAmount && (

Choose Your Offset Amount

Offset Percentage: {offsetPercentage}%
{/* Tick marks - visible notches */}
{[0, 25, 50, 75, 100].map((tick) => (
))}
{/* Slider */} setOffsetPercentage(Number(e.target.value))} className="relative z-10 w-full h-3 bg-gray-200 rounded-lg appearance-none cursor-pointer slider" style={{ background: `linear-gradient(to right, #3b82f6 0%, #3b82f6 ${offsetPercentage}%, #e5e7eb ${offsetPercentage}%, #e5e7eb 100%)` }} />
{/* Percentage labels aligned with tick marks */}
0% 25% 50% 75% 100%
CO₂ to Offset: {formatTons(actualOffsetTons)} tons
{offsetPercentage}% of {formatTons(tons)} tons total emissions
)}

Order Summary

Amount to Offset: {formatTons(actualOffsetTons)} tons CO₂
Portfolio Distribution: Automatically optimized
Cost per Ton: {renderPortfolioPrice(portfolio)}
Total Cost: {formatCurrency(offsetCost, currencies.USD)}
{loading ? (
Redirecting to checkout...
) : ( 'Proceed to Checkout' )}
) : null}
); }