From e67e64947c435100774331b0e3ee5afcbee2bbf1 Mon Sep 17 00:00:00 2001 From: Matt Date: Thu, 5 Jun 2025 01:52:03 +0200 Subject: [PATCH] Refactor MobileOffsetOrder component for improved structure and clarity --- src/components/MobileOffsetOrder.tsx | 845 +++++++++++++++------------ 1 file changed, 485 insertions(+), 360 deletions(-) diff --git a/src/components/MobileOffsetOrder.tsx b/src/components/MobileOffsetOrder.tsx index 5f053d6..cb29624 100644 --- a/src/components/MobileOffsetOrder.tsx +++ b/src/components/MobileOffsetOrder.tsx @@ -1,8 +1,11 @@ -import React, { useState } from 'react'; -import { Check, ArrowLeft, Loader2, CreditCard, User, Mail, Phone } from 'lucide-react'; +import React, { useState, useEffect, useCallback } from 'react'; +import { Check, ArrowLeft, Loader2, User, Mail, Phone, Globe2, TreePine, Waves, Factory, Wind, X, AlertCircle } from 'lucide-react'; import { motion, AnimatePresence } from 'framer-motion'; -import type { CurrencyCode } from '../types'; +import { createOffsetOrder, getPortfolios } from '../api/wrenClient'; +import type { CurrencyCode, OffsetOrder as OffsetOrderType, Portfolio, OffsetProject } from '../types'; import { currencies, formatCurrency, getCurrencyByCode } from '../utils/currencies'; +import { config } from '../utils/config'; +import { sendFormspreeEmail } from '../utils/email'; interface Props { tons: number; @@ -11,89 +14,130 @@ interface Props { onBack: () => void; } -interface Project { - id: string; - name: string; - type: string; - description: string; - location: string; - pricePerTon: number; - imageUrl: string; - percentage: number; +interface ProjectTypeIconProps { + project: OffsetProject; } +const ProjectTypeIcon = ({ project }: ProjectTypeIconProps) => { + if (!project || !project.type) { + return ; + } + + const type = project.type.toLowerCase(); + + switch (type) { + case 'direct air capture': + return ; + case 'blue carbon': + return ; + case 'renewable energy': + return ; + case 'forestry': + return ; + default: + return ; + } +}; + export function MobileOffsetOrder({ tons, monetaryAmount, currency, onBack }: Props) { - const [currentStep, setCurrentStep] = useState<'summary' | 'projects' | 'payment' | 'confirmation'>('summary'); + const [currentStep, setCurrentStep] = useState<'summary' | 'projects' | 'confirmation'>('summary'); const [loading, setLoading] = useState(false); - const [orderData, setOrderData] = useState({ + 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 [selectedProject, setSelectedProject] = useState(null); + const [formData, setFormData] = useState({ name: '', email: '', - phone: '' + phone: '', + company: '', + message: `I would like to offset ${tons.toFixed(2)} tons of CO2 from my yacht's emissions.` }); - // Dummy projects data - const projects: Project[] = [ - { - id: '1', - name: 'Coastal Blue Carbon', - type: 'Blue Carbon', - description: 'Protecting and restoring coastal wetlands that capture carbon 10x faster than forests.', - location: 'Indonesia', - pricePerTon: 25, - imageUrl: 'https://images.unsplash.com/photo-1559827260-dc66d52bef19?auto=format&fit=crop&q=80&w=800', - percentage: 0.35 - }, - { - id: '2', - name: 'Wind Power Initiative', - type: 'Renewable Energy', - description: 'Supporting offshore wind farms to replace fossil fuel energy generation.', - location: 'North Sea', - pricePerTon: 15, - imageUrl: 'https://images.unsplash.com/photo-1548337138-e87d889cc369?auto=format&fit=crop&q=80&w=800', - percentage: 0.30 - }, - { - id: '3', - name: 'Rainforest Conservation', - type: 'Forestry', - description: 'Protecting critical rainforest habitats from deforestation.', - location: 'Amazon Basin', - pricePerTon: 12, - imageUrl: 'https://images.unsplash.com/photo-1511884642898-4c92249e20b6?auto=format&fit=crop&q=80&w=800', - percentage: 0.35 + useEffect(() => { + if (!config.wrenApiKey) { + setError('Carbon offset service is currently unavailable. Please use our contact form to request offsetting.'); + setLoadingPortfolio(false); + return; } - ]; + fetchPortfolio(); + }, []); - // Calculate cost (using dummy pricing) - const pricePerTon = 18; // $18 per ton - const totalCost = monetaryAmount || (tons * pricePerTon); - const targetCurrency = getCurrencyByCode(currency); - - const handleInputChange = (field: keyof typeof orderData, value: string) => { - setOrderData(prev => ({ ...prev, [field]: value })); + const fetchPortfolio = async () => { + try { + const allPortfolios = await getPortfolios(); + + if (!allPortfolios || allPortfolios.length === 0) { + throw new Error('No portfolios available'); + } + + const puffinPortfolio = allPortfolios.find(p => + p.name.toLowerCase().includes('puffin') || + p.name.toLowerCase().includes('maritime') + ); + + if (puffinPortfolio) { + console.log('[MobileOffsetOrder] Found Puffin portfolio with ID:', puffinPortfolio.id); + setPortfolio(puffinPortfolio); + } else { + console.log('[MobileOffsetOrder] 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 handleProceedToPayment = () => { + const handleOffsetOrder = async () => { + if (!portfolio) return; + + setLoading(true); + setError(null); + + try { + const newOrder = await createOffsetOrder(portfolio.id, tons); + setOrder(newOrder); + setSuccess(true); + setCurrentStep('confirmation'); + } catch (err) { + setError('Failed to create offset order. Please try again.'); + } finally { + setLoading(false); + } + }; + + const renderPortfolioPrice = (portfolio: Portfolio) => { + try { + const pricePerTon = portfolio.pricePerTon || 18; + const targetCurrency = getCurrencyByCode(currency); + return formatCurrency(pricePerTon, targetCurrency); + } catch (err) { + console.error('Error formatting portfolio price:', err); + return formatCurrency(18, currencies.USD); + } + }; + + const offsetCost = monetaryAmount || (portfolio ? tons * (portfolio.pricePerTon || 18) : 0); + const targetCurrency = getCurrencyByCode(currency); + + const handleInputChange = (field: keyof typeof formData, value: string) => { + setFormData(prev => ({ ...prev, [field]: value })); + }; + + const handleProceedToProjects = () => { setCurrentStep('projects'); }; - const handleProceedFromProjects = () => { - setCurrentStep('payment'); - }; + const handleProjectClick = useCallback((project: OffsetProject) => { + setSelectedProject(project); + }, []); - const handlePlaceOrder = async () => { - setLoading(true); - - // Simulate API call - await new Promise(resolve => setTimeout(resolve, 2000)); - - setLoading(false); - setCurrentStep('confirmation'); - }; - - const generateOrderId = () => { - return 'PO-' + Date.now().toString().slice(-8); + const handleCloseLightbox = () => { + setSelectedProject(null); }; const renderSummaryStep = () => ( @@ -103,98 +147,154 @@ export function MobileOffsetOrder({ tons, monetaryAmount, currency, onBack }: Pr animate={{ opacity: 1, x: 0 }} exit={{ opacity: 0, x: -20 }} > -
-

Offset Summary

- -
-
+ {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); + setCurrentStep('confirmation'); + } catch (err) { + setError('Failed to send request. Please try again.'); + } finally { + setLoading(false); + } + }} className="space-y-4">
-
CO₂ to Offset
-
From your yacht emissions
+ + handleInputChange('name', e.target.value)} + className="w-full px-4 py-2 border rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500" + />
-
- {tons.toFixed(2)} tons -
-
- -
-
Price per Ton
-
Verified carbon credits
+ + handleInputChange('email', e.target.value)} + className="w-full px-4 py-2 border rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500" + />
-
- {formatCurrency(pricePerTon, targetCurrency)} +
+ + handleInputChange('phone', e.target.value)} + className="w-full px-4 py-2 border rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500" + />
-
- -
-
- Total Cost - - {formatCurrency(totalCost, targetCurrency)} - +
+ +