From 5cc1188c5e9a945ded985cfe8f850ecfbac0b1e1 Mon Sep 17 00:00:00 2001 From: Matt Date: Mon, 28 Apr 2025 00:31:32 +0200 Subject: [PATCH] Made Calculator into Wordpress Plugin --- puffin-calculator-integrated.php | 996 +++++++++++++++++++++++++++++++ 1 file changed, 996 insertions(+) create mode 100644 puffin-calculator-integrated.php diff --git a/puffin-calculator-integrated.php b/puffin-calculator-integrated.php new file mode 100644 index 0000000..7024fcf --- /dev/null +++ b/puffin-calculator-integrated.php @@ -0,0 +1,996 @@ + '100%', + 'height' => 'auto', + 'theme' => 'light', + ), $atts); + + // Generate unique ID for this instance + $calculator_id = 'puffin-calculator-' . uniqid(); + + // Start output buffer + ob_start(); + ?> +
+
+
+ + + get_calculator_styles()); + + // Register and enqueue the calculator script + wp_register_script('puffin-calculator', '', array('react', 'react-dom'), '1.0.0', true); + wp_enqueue_script('puffin-calculator'); + + // Add the calculator script as inline JS + wp_add_inline_script('puffin-calculator', $this->get_calculator_script()); + } + + /** + * Get the inline calculator styles + */ + private function get_calculator_styles() { + return " + .puffin-calculator-wrapper { + margin: 20px 0; + width: 100%; + max-width: 100%; + border-radius: 8px; + overflow: hidden; + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); + } + + .puffin-calculator { + border: none; + width: 100%; + min-height: 600px; + background-color: #f9fafb; + } + + @media (max-width: 768px) { + .puffin-calculator-wrapper { + margin: 15px 0; + } + + .puffin-calculator { + min-height: 800px; + } + } + "; + } + + /** + * Get the inline calculator script + */ + private function get_calculator_script() { + return " + // Initialize the Puffin Calculator + function initPuffinCalculator(element) { + 'use strict'; + + // React and ReactDOM are loaded from CDN + const React = window.React; + const ReactDOM = window.ReactDOM; + const { useState, useEffect, useCallback } = React; + + // ==================== TYPE DEFINITIONS ==================== + + // VesselData interface + // imo: string; + // vesselName: string; + // type: string; + // length: number; + // width: number; + // estimatedEnginePower: number; + + // TripEstimate interface + // distance: number; // nautical miles + // duration: number; // hours + // fuelConsumption: number; // liters + // co2Emissions: number; // tons + + // Currency interface + // code: string; + // symbol: string; + // rate: number; // Exchange rate relative to USD + + // ==================== UTILITIES ==================== + + // Mock vessel data + const sampleVessel = { + imo: '1234567', + vesselName: 'Sample Yacht', + type: 'Yacht', + length: 50, + width: 9, + estimatedEnginePower: 2250 + }; + + // Constants for carbon calculations + const EMISSION_FACTOR = 3.206; // tons of CO₂ per ton of fuel + const FUEL_DENSITY = 0.85; // tons per m³ (or metric tons per kiloliter) + const GALLONS_TO_LITERS = 3.78541; // 1 US gallon = 3.78541 liters + const LITERS_TO_CUBIC_METERS = 0.001; // 1 liter = 0.001 m³ + + // Available currencies + const currencies = { + USD: { code: 'USD', symbol: '$', rate: 1 }, + EUR: { code: 'EUR', symbol: '€', rate: 0.92 }, + GBP: { code: 'GBP', symbol: '£', rate: 0.79 }, + CHF: { code: 'CHF', symbol: 'CHF', rate: 0.88 }, + }; + + // Format currency + function formatCurrency(amountUSD, currency) { + if (!currency) { + currency = currencies.USD; + } + + // Convert USD amount to target currency + const convertedAmount = amountUSD * currency.rate; + + return `${currency.symbol}${convertedAmount.toLocaleString(undefined, { + minimumFractionDigits: 0, + maximumFractionDigits: 0, + })}`; + } + + // Calculate trip carbon + function calculateTripCarbon(vesselData, distance, speed, fuelRateLitersPerHour) { + const tripHours = distance / speed; + + // Calculate total fuel consumption in liters + const fuelConsumptionLiters = fuelRateLitersPerHour * tripHours; + + // Convert liters to tons for CO₂ calculation + const fuelConsumptionTons = (fuelConsumptionLiters * LITERS_TO_CUBIC_METERS) * FUEL_DENSITY; + + // Calculate CO₂ emissions + const fuelRateTonsPerHour = (fuelRateLitersPerHour * LITERS_TO_CUBIC_METERS) * FUEL_DENSITY; + const emissionsPerNM = (fuelRateTonsPerHour * EMISSION_FACTOR) / speed; + const totalEmissions = emissionsPerNM * distance; + + return { + distance, + duration: tripHours, + fuelConsumption: Math.round(fuelConsumptionLiters), + co2Emissions: Number(totalEmissions.toFixed(2)) + }; + } + + // Calculate carbon from fuel + function calculateCarbonFromFuel(fuelAmount, isGallons = false) { + // Convert to liters if input is in gallons + const liters = isGallons ? fuelAmount * GALLONS_TO_LITERS : fuelAmount; + + // Convert liters to cubic meters (m³) + const cubicMeters = liters * LITERS_TO_CUBIC_METERS; + + // Convert volume to mass (tons) + const fuelTons = cubicMeters * FUEL_DENSITY; + + // Calculate CO₂ emissions + const co2Emissions = fuelTons * EMISSION_FACTOR; + + return Number(co2Emissions.toFixed(2)); + } + + // Analytics object (simplified) + const analytics = { + pageView: (path) => { + // Simplified analytics - would integrate with WordPress analytics in a real implementation + console.log(`Page view: ${path}`); + }, + event: (category, action, label) => { + console.log(`Event: ${category} - ${action} - ${label}`); + } + }; + + // ==================== COMPONENTS ==================== + + // CurrencySelect Component + function CurrencySelect({ value, onChange }) { + return React.createElement('select', { + value: value, + onChange: (e) => onChange(e.target.value), + className: 'block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring focus:ring-blue-200 focus:ring-opacity-50 p-2' + }, + Object.entries(currencies).map(([code, currency]) => ( + React.createElement('option', { + key: code, + value: code + }, `${currency.symbol} ${code}`) + ))); + } + + // TripCalculator Component + function TripCalculator({ vesselData, onOffsetClick }) { + const [calculationType, setCalculationType] = useState('fuel'); + const [distance, setDistance] = useState(''); + const [speed, setSpeed] = useState('12'); + const [fuelRate, setFuelRate] = useState('100'); + const [fuelAmount, setFuelAmount] = useState(''); + const [fuelUnit, setFuelUnit] = useState('liters'); + const [tripEstimate, setTripEstimate] = useState(null); + const [currency, setCurrency] = useState('USD'); + const [offsetPercentage, setOffsetPercentage] = useState(100); + const [customPercentage, setCustomPercentage] = useState(''); + const [customAmount, setCustomAmount] = useState(''); + + const handleCalculate = useCallback((e) => { + e.preventDefault(); + if (calculationType === 'distance') { + const estimate = calculateTripCarbon( + vesselData, + Number(distance), + Number(speed), + Number(fuelRate) + ); + setTripEstimate(estimate); + } else if (calculationType === 'fuel') { + const co2Emissions = calculateCarbonFromFuel(Number(fuelAmount), fuelUnit === 'gallons'); + setTripEstimate({ + distance: 0, + duration: 0, + fuelConsumption: Number(fuelAmount), + co2Emissions + }); + } + }, [calculationType, distance, speed, fuelRate, fuelAmount, fuelUnit, vesselData]); + + const handleCustomPercentageChange = useCallback((e) => { + const value = e.target.value; + if (value === '' || (Number(value) >= 0 && Number(value) <= 100)) { + setCustomPercentage(value); + if (value !== '') { + setOffsetPercentage(Number(value)); + } + } + }, []); + + const handlePresetPercentage = useCallback((percentage) => { + setOffsetPercentage(percentage); + setCustomPercentage(''); + }, []); + + const calculateOffsetAmount = useCallback((emissions, percentage) => { + return (emissions * percentage) / 100; + }, []); + + const handleCustomAmountChange = useCallback((e) => { + const value = e.target.value; + if (value === '' || Number(value) >= 0) { + setCustomAmount(value); + } + }, []); + + return React.createElement('div', { + className: 'bg-white rounded-lg shadow-xl p-6 max-w-2xl w-full mt-8' + }, [ + // Header + React.createElement('div', { + key: 'header', + className: 'flex items-center justify-between mb-6' + }, [ + React.createElement('h2', { + key: 'title', + className: 'text-2xl font-bold text-gray-800' + }, 'Carbon Offset Calculator'), + // Route icon would be here + ]), + + // Calculation Method + React.createElement('div', { + key: 'calc-method', + className: 'mb-6' + }, [ + React.createElement('label', { + key: 'method-label', + className: 'block text-sm font-medium text-gray-700 mb-2' + }, 'Calculation Method'), + React.createElement('div', { + key: 'method-buttons', + className: 'flex flex-wrap gap-3' + }, [ + React.createElement('button', { + key: 'fuel-btn', + type: 'button', + onClick: () => setCalculationType('fuel'), + className: `px-4 py-2 rounded-lg transition-colors ${ + calculationType === 'fuel' + ? 'bg-blue-500 text-white' + : 'bg-gray-100 text-gray-700 hover:bg-gray-200' + }` + }, 'Fuel Based'), + React.createElement('button', { + key: 'distance-btn', + type: 'button', + onClick: () => setCalculationType('distance'), + className: `px-4 py-2 rounded-lg transition-colors ${ + calculationType === 'distance' + ? 'bg-blue-500 text-white' + : 'bg-gray-100 text-gray-700 hover:bg-gray-200' + }` + }, 'Distance Based'), + React.createElement('button', { + key: 'custom-btn', + type: 'button', + onClick: () => setCalculationType('custom'), + className: `px-4 py-2 rounded-lg transition-colors ${ + calculationType === 'custom' + ? 'bg-blue-500 text-white' + : 'bg-gray-100 text-gray-700 hover:bg-gray-200' + }` + }, 'Custom Amount') + ]) + ]), + + // Custom Amount Form + calculationType === 'custom' ? React.createElement('div', { + key: 'custom-form', + className: 'space-y-4' + }, [ + // Currency Select + React.createElement('div', { + key: 'currency-select', + }, [ + React.createElement('label', { + className: 'block text-sm font-medium text-gray-700 mb-2' + }, 'Select Currency'), + React.createElement('div', { + className: 'max-w-xs' + }, [ + React.createElement(CurrencySelect, { + value: currency, + onChange: setCurrency + }) + ]) + ]), + + // Amount Input + React.createElement('div', { + key: 'amount-input', + }, [ + React.createElement('label', { + className: 'block text-sm font-medium text-gray-700 mb-2' + }, 'Enter Amount to Offset'), + React.createElement('div', { + className: 'relative rounded-md shadow-sm' + }, [ + React.createElement('div', { + className: 'pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3' + }, [ + React.createElement('span', { + className: 'text-gray-500 sm:text-sm' + }, currencies[currency].symbol) + ]), + React.createElement('input', { + type: 'number', + value: customAmount, + onChange: handleCustomAmountChange, + placeholder: 'Enter amount', + min: '0', + className: 'mt-1 block w-full rounded-md border-gray-300 pl-7 pr-12 focus:border-blue-500 focus:ring-blue-500 sm:text-sm p-2', + required: true + }), + React.createElement('div', { + className: 'pointer-events-none absolute inset-y-0 right-0 flex items-center pr-3' + }, [ + React.createElement('span', { + className: 'text-gray-500 sm:text-sm' + }, currency) + ]) + ]) + ]), + + // Offset Button + customAmount && Number(customAmount) > 0 ? React.createElement('button', { + key: 'offset-btn', + onClick: () => onOffsetClick(0, Number(customAmount)), + className: 'w-full bg-blue-500 text-white py-2 px-4 rounded-lg hover:bg-blue-600 transition-colors mt-6' + }, 'Offset Your Impact') : null + ]) : + // Trip Calculator Form + React.createElement('form', { + key: 'trip-form', + onSubmit: handleCalculate, + className: 'space-y-4' + }, [ + // Fuel Based Form + calculationType === 'fuel' ? React.createElement('div', { + key: 'fuel-form' + }, [ + React.createElement('label', { + className: 'block text-sm font-medium text-gray-700' + }, 'Fuel Consumption'), + React.createElement('div', { + className: 'flex space-x-4' + }, [ + React.createElement('div', { + className: 'flex-1' + }, [ + React.createElement('input', { + type: 'number', + min: '1', + value: fuelAmount, + onChange: (e) => setFuelAmount(e.target.value), + placeholder: 'Enter amount', + className: 'mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring focus:ring-blue-200 p-2', + required: true + }) + ]), + React.createElement('div', {}, [ + React.createElement('select', { + value: fuelUnit, + onChange: (e) => setFuelUnit(e.target.value), + className: 'mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring focus:ring-blue-200 p-2' + }, [ + React.createElement('option', { + key: 'liters', + value: 'liters' + }, 'Liters'), + React.createElement('option', { + key: 'gallons', + value: 'gallons' + }, 'Gallons') + ]) + ]) + ]) + ]) : null, + + // Distance Based Form + calculationType === 'distance' ? React.createElement(React.Fragment, { + key: 'distance-form' + }, [ + React.createElement('div', { + key: 'distance-input' + }, [ + React.createElement('label', { + className: 'block text-sm font-medium text-gray-700' + }, 'Distance (nautical miles)'), + React.createElement('input', { + type: 'number', + min: '1', + value: distance, + onChange: (e) => setDistance(e.target.value), + className: 'mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring focus:ring-blue-200 p-2', + required: true + }) + ]), + + React.createElement('div', { + key: 'speed-input' + }, [ + React.createElement('label', { + className: 'block text-sm font-medium text-gray-700' + }, 'Average Speed (knots)'), + React.createElement('input', { + type: 'number', + min: '1', + max: '50', + value: speed, + onChange: (e) => setSpeed(e.target.value), + className: 'mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring focus:ring-blue-200 p-2', + required: true + }) + ]), + + React.createElement('div', { + key: 'fuel-rate-input' + }, [ + React.createElement('label', { + className: 'block text-sm font-medium text-gray-700' + }, 'Fuel Consumption Rate (liters per hour)'), + React.createElement('input', { + type: 'number', + min: '1', + step: '1', + value: fuelRate, + onChange: (e) => setFuelRate(e.target.value), + className: 'mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring focus:ring-blue-200 p-2', + required: true + }), + React.createElement('p', { + className: 'mt-1 text-sm text-gray-500' + }, 'Typical range: 50 - 500 liters per hour for most yachts') + ]) + ]) : null, + + // Common Form Elements + React.createElement('div', { + key: 'currency-select' + }, [ + React.createElement('label', { + className: 'block text-sm font-medium text-gray-700' + }, 'Select Currency'), + React.createElement('div', { + className: 'max-w-xs' + }, [ + React.createElement(CurrencySelect, { + value: currency, + onChange: setCurrency + }) + ]) + ]), + + React.createElement('button', { + key: 'calculate-btn', + type: 'submit', + className: 'w-full bg-blue-500 text-white py-2 px-4 rounded-lg hover:bg-blue-600 transition-colors' + }, 'Calculate Impact') + ]), + + // Results Section + tripEstimate && calculationType !== 'custom' ? React.createElement('div', { + key: 'results', + className: 'mt-6 space-y-6' + }, [ + // Trip Details + React.createElement('div', { + key: 'trip-details', + className: 'grid grid-cols-2 gap-4' + }, [ + // Trip Duration (only for distance calculation) + calculationType === 'distance' ? React.createElement('div', { + key: 'duration', + className: 'bg-gray-50 p-4 rounded-lg' + }, [ + React.createElement('p', { + className: 'text-sm text-gray-600' + }, 'Trip Duration'), + React.createElement('p', { + className: 'text-xl font-bold text-gray-900' + }, `${tripEstimate.duration.toFixed(1)} hours`) + ]) : null, + + // Fuel Consumption + React.createElement('div', { + key: 'fuel', + className: 'bg-gray-50 p-4 rounded-lg' + }, [ + React.createElement('p', { + className: 'text-sm text-gray-600' + }, 'Fuel Consumption'), + React.createElement('p', { + className: 'text-xl font-bold text-gray-900' + }, `${tripEstimate.fuelConsumption.toLocaleString()} ${fuelUnit}`) + ]) + ]), + + // Offset Percentage Selection + React.createElement('div', { + key: 'offset-percent', + }, [ + React.createElement('label', { + className: 'block text-sm font-medium text-gray-700 mb-2' + }, 'Offset Percentage'), + React.createElement('div', { + className: 'flex flex-wrap gap-3 mb-3' + }, [ + // Preset percentage buttons + [100, 75, 50, 25].map((percent) => ( + React.createElement('button', { + key: `percent-${percent}`, + type: 'button', + onClick: () => handlePresetPercentage(percent), + className: `px-4 py-2 rounded-lg transition-colors ${ + offsetPercentage === percent && customPercentage === '' + ? 'bg-blue-500 text-white' + : 'bg-gray-100 text-gray-700 hover:bg-gray-200' + }` + }, `${percent}%`) + )), + + // Custom percentage input + React.createElement('div', { + className: 'flex items-center space-x-2' + }, [ + React.createElement('input', { + type: 'number', + value: customPercentage, + onChange: handleCustomPercentageChange, + placeholder: 'Custom %', + min: '0', + max: '100', + className: 'w-24 px-3 py-2 border rounded-lg focus:ring-blue-500 focus:border-blue-500' + }), + React.createElement('span', { + className: 'text-gray-600' + }, '%') + ]) + ]) + ]), + + // Selected CO₂ Offset + React.createElement('div', { + key: 'co2-offset', + className: 'bg-blue-50 p-4 rounded-lg' + }, [ + React.createElement('p', { + className: 'text-sm text-gray-600' + }, 'Selected CO₂ Offset'), + React.createElement('p', { + className: 'text-2xl font-bold text-blue-900' + }, `${calculateOffsetAmount(tripEstimate.co2Emissions, offsetPercentage).toFixed(2)} tons`), + React.createElement('p', { + className: 'text-sm text-blue-600 mt-1' + }, `${offsetPercentage}% of ${tripEstimate.co2Emissions.toFixed(2)} tons`) + ]), + + // Offset Button + React.createElement('button', { + key: 'offset-btn', + onClick: () => onOffsetClick(calculateOffsetAmount(tripEstimate.co2Emissions, offsetPercentage)), + className: 'w-full bg-blue-500 text-white py-2 px-4 rounded-lg hover:bg-blue-600 transition-colors' + }, 'Offset Your Impact') + ]) : null + ]); + } + + // OffsetOrder Component + function OffsetOrder({ tons, monetaryAmount, onBack, calculatorType }) { + const [name, setName] = useState(''); + const [email, setEmail] = useState(''); + const [currency, setCurrency] = useState('USD'); + const [isSubmitting, setIsSubmitting] = useState(false); + const [isSuccess, setIsSuccess] = useState(false); + const [error, setError] = useState(''); + + const handleSubmit = (e) => { + e.preventDefault(); + setIsSubmitting(true); + setError(''); + + // Simulate API call + setTimeout(() => { + // Send analytics event + analytics.event('offset', 'purchase', `${tons} tons`); + + setIsSubmitting(false); + setIsSuccess(true); + }, 1500); + }; + + if (isSuccess) { + return React.createElement('div', { + className: 'bg-white rounded-lg shadow-xl p-6 max-w-2xl w-full' + }, [ + React.createElement('div', { + key: 'success-icon', + className: 'flex justify-center mb-6' + }, [ + // Success checkmark icon (simplified) + React.createElement('div', { + className: 'w-16 h-16 bg-green-100 rounded-full flex items-center justify-center' + }, [ + React.createElement('span', { + className: 'text-green-500 text-2xl' + }, '✓') + ]) + ]), + React.createElement('h2', { + key: 'success-title', + className: 'text-2xl font-bold text-gray-800 text-center mb-4' + }, 'Thank You for Your Contribution!'), + React.createElement('p', { + key: 'success-message', + className: 'text-gray-600 text-center mb-6' + }, monetaryAmount + ? `Your contribution of ${formatCurrency(monetaryAmount, currencies[currency])} has been received.` + : `Your offset of ${tons.toFixed(2)} tons of CO₂ has been successfully processed.`), + React.createElement('p', { + key: 'success-email', + className: 'text-gray-600 text-center mb-8' + }, `A confirmation email has been sent to ${email}.`), + React.createElement('button', { + key: 'back-btn', + onClick: onBack, + className: 'block w-full bg-blue-500 text-white py-2 px-4 rounded-lg hover:bg-blue-600 transition-colors' + }, 'Return to Calculator') + ]); + } + + return React.createElement('div', { + className: 'bg-white rounded-lg shadow-xl p-6 max-w-2xl w-full' + }, [ + // Back button + React.createElement('button', { + key: 'back-button', + onClick: onBack, + className: 'text-sm text-blue-500 mb-6 flex items-center' + }, '← Back to Calculator'), + + React.createElement('h2', { + key: 'title', + className: 'text-2xl font-bold text-gray-800 mb-6' + }, 'Complete Your Carbon Offset'), + + // Description + React.createElement('div', { + key: 'description', + className: 'mb-6' + }, [ + React.createElement('div', { + className: 'p-4 bg-blue-50 rounded-lg mb-4' + }, [ + React.createElement('p', { + className: 'font-semibold text-blue-800' + }, monetaryAmount + ? `You're contributing: ${formatCurrency(monetaryAmount, currencies[currency])}` + : `You're offsetting: ${tons.toFixed(2)} tons of CO₂`) + ]), + React.createElement('p', { + className: 'text-gray-600' + }, 'Your offset will be invested in certified projects that reduce greenhouse gas emissions and support sustainable development.') + ]), + + // Form + React.createElement('form', { + key: 'form', + onSubmit: handleSubmit, + className: 'space-y-4' + }, [ + // Name field + React.createElement('div', {}, [ + React.createElement('label', { + htmlFor: 'name', + className: 'block text-sm font-medium text-gray-700 mb-1' + }, 'Full Name'), + React.createElement('input', { + id: 'name', + type: 'text', + value: name, + onChange: (e) => setName(e.target.value), + className: 'w-full p-2 border border-gray-300 rounded-md focus:ring-blue-500 focus:border-blue-500', + required: true + }) + ]), + + // Email field + React.createElement('div', {}, [ + React.createElement('label', { + htmlFor: 'email', + className: 'block text-sm font-medium text-gray-700 mb-1' + }, 'Email Address'), + React.createElement('input', { + id: 'email', + type: 'email', + value: email, + onChange: (e) => setEmail(e.target.value), + className: 'w-full p-2 border border-gray-300 rounded-md focus:ring-blue-500 focus:border-blue-500', + required: true + }) + ]), + + // Currency field + React.createElement('div', {}, [ + React.createElement('label', { + htmlFor: 'currency', + className: 'block text-sm font-medium text-gray-700 mb-1' + }, 'Currency'), + React.createElement('div', { + className: 'max-w-xs' + }, [ + React.createElement(CurrencySelect, { + value: currency, + onChange: setCurrency + }) + ]) + ]), + + // Error message + error ? React.createElement('div', { + className: 'p-3 bg-red-50 text-red-700 rounded-md' + }, error) : null, + + // Submit button + React.createElement('button', { + type: 'submit', + disabled: isSubmitting, + className: `w-full bg-blue-500 text-white py-2 px-4 rounded-lg hover:bg-blue-600 transition-colors ${ + isSubmitting ? 'opacity-70 cursor-not-allowed' : '' + }` + }, isSubmitting ? 'Processing...' : 'Complete Offset') + ]) + ]); + } + + // Main App Component + function App() { + const [currentPage, setCurrentPage] = useState('calculator'); + const [showOffsetOrder, setShowOffsetOrder] = useState(false); + const [offsetTons, setOffsetTons] = useState(0); + const [monetaryAmount, setMonetaryAmount] = useState(undefined); + const [calculatorType, setCalculatorType] = useState('trip'); + + useEffect(() => { + analytics.pageView(currentPage); + }, [currentPage]); + + const handleOffsetClick = (tons, monetaryAmount) => { + setOffsetTons(tons); + setMonetaryAmount(monetaryAmount); + setShowOffsetOrder(true); + }; + + const renderContent = () => { + if (showOffsetOrder) { + return React.createElement(OffsetOrder, { + tons: offsetTons, + monetaryAmount: monetaryAmount, + onBack: () => setShowOffsetOrder(false), + calculatorType: calculatorType + }); + } + + return React.createElement(TripCalculator, { + vesselData: sampleVessel, + onOffsetClick: handleOffsetClick + }); + }; + + return React.createElement('div', { + className: 'min-h-[600px] bg-gradient-to-b from-blue-50 to-green-50' + }, [ + // Header + React.createElement('div', { + key: 'header', + className: 'bg-white shadow-sm py-4 px-6 mb-8' + }, [ + React.createElement('div', { + className: 'flex items-center space-x-2' + }, [ + // Bird icon would be here in a real implementation + React.createElement('h1', { + className: 'text-xl font-bold text-gray-900' + }, 'Puffin Offset') + ]) + ]), + + // Main content + React.createElement('div', { + key: 'main', + className: 'px-4 sm:px-6 flex justify-center' + }, renderContent()), + + // Footer + React.createElement('div', { + key: 'footer', + className: 'bg-white mt-16 py-4 px-6 text-center text-gray-500' + }, 'Powered by Verified Carbon Offset Projects') + ]); + } + + // Initialize React application + ReactDOM.createRoot(element).render(React.createElement(App, {})); + } + "; + } +} + +/** + * Widget class for the calculator + */ +class Puffin_Calculator_Widget_Class extends WP_Widget { + + /** + * Initialize the widget + */ + public function __construct() { + parent::__construct( + 'puffin_calculator_widget', + 'Puffin Offset Calculator', + array('description' => 'Adds the Puffin Offset Calculator to a widget area') + ); + } + + /** + * Front-end display of the widget + */ + public function widget($args, $instance) { + echo $args['before_widget']; + + if (!empty($instance['title'])) { + echo $args['before_title'] . apply_filters('widget_title', $instance['title']) . $args['after_title']; + } + + echo do_shortcode('[puffin_calculator]'); + + echo $args['after_widget']; + } + + /** + * Back-end widget form + */ + public function form($instance) { + $title = isset($instance['title']) ? $instance['title'] : 'Carbon Offset Calculator'; + ?> +

+ + +

+