puffinOffsetCalculator/puffin-calculator-iframe/js/components.js

1040 lines
36 KiB
JavaScript

// Icon Components
function Route() {
return React.createElement('svg', {
xmlns: 'http://www.w3.org/2000/svg',
width: '24',
height: '24',
viewBox: '0 0 24 24',
fill: 'none',
stroke: 'currentColor',
strokeWidth: '2',
strokeLinecap: 'round',
strokeLinejoin: 'round',
className: 'text-blue-500'
}, [
React.createElement('path', { key: 'path1', d: 'M9 15a16 16 0 0 0 8 0' }),
React.createElement('path', { key: 'path2', d: 'M18 6a16 16 0 0 0-12 0' }),
React.createElement('circle', { key: 'circle1', cx: '12', cy: '3', r: '1' }),
React.createElement('circle', { key: 'circle2', cx: '19', cy: '6', r: '1' }),
React.createElement('circle', { key: 'circle3', cx: '5', cy: '6', r: '1' }),
React.createElement('circle', { key: 'circle4', cx: '12', cy: '21', r: '1' }),
React.createElement('circle', { key: 'circle5', cx: '19', cy: '18', r: '1' }),
React.createElement('circle', { key: 'circle6', cx: '5', cy: '18', r: '1' }),
React.createElement('path', { key: 'path3', d: 'M9 9a116 116 0 0 0 6 0' }),
React.createElement('path', { key: 'path4', d: 'M9 12a116 116 0 0 0 6 0' })
]);
}
function ArrowLeft() {
return React.createElement('svg', {
xmlns: 'http://www.w3.org/2000/svg',
width: '20',
height: '20',
viewBox: '0 0 24 24',
fill: 'none',
stroke: 'currentColor',
strokeWidth: '2',
strokeLinecap: 'round',
strokeLinejoin: 'round',
className: 'mr-2'
}, [
React.createElement('path', { key: 'path1', d: 'M19 12H5' }),
React.createElement('path', { key: 'path2', d: 'M12 19l-7-7 7-7' })
]);
}
function CheckIcon() {
return React.createElement('svg', {
xmlns: 'http://www.w3.org/2000/svg',
width: '32',
height: '32',
viewBox: '0 0 24 24',
fill: 'none',
stroke: 'currentColor',
strokeWidth: '2',
strokeLinecap: 'round',
strokeLinejoin: 'round',
className: 'text-green-500'
}, [
React.createElement('path', { key: 'path1', d: 'M20 6L9 17l-5-5' })
]);
}
function LoaderIcon() {
return React.createElement('svg', {
xmlns: 'http://www.w3.org/2000/svg',
width: '20',
height: '20',
viewBox: '0 0 24 24',
fill: 'none',
stroke: 'currentColor',
strokeWidth: '2',
strokeLinecap: 'round',
strokeLinejoin: 'round',
className: 'animate-spin mr-2'
}, [
React.createElement('path', { key: 'path1', d: 'M21 12a9 9 0 1 1-6.219-8.56' })
]);
}
// 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}`)
)));
}
// ProjectTypeIcon Component
function ProjectTypeIcon({ project }) {
// Safely check if project and type exist
if (!project || !project.type) {
return React.createElement('span', { className: 'text-blue-500' }, '🌎');
}
const type = project.type.toLowerCase();
switch (type) {
case 'direct air capture':
return React.createElement('span', { className: 'text-purple-500' }, '🏭');
case 'blue carbon':
return React.createElement('span', { className: 'text-blue-500' }, '🌊');
case 'renewable energy':
return React.createElement('span', { className: 'text-green-500' }, '💨');
case 'forestry':
return React.createElement('span', { className: 'text-green-500' }, '🌲');
default:
return React.createElement('span', { className: 'text-blue-500' }, '🌎');
}
}
// TripCalculator Component
function TripCalculator({ vesselData, onOffsetClick }) {
const [calculationType, setCalculationType] = React.useState('fuel');
const [distance, setDistance] = React.useState('');
const [speed, setSpeed] = React.useState('12');
const [fuelRate, setFuelRate] = React.useState('100');
const [fuelAmount, setFuelAmount] = React.useState('');
const [fuelUnit, setFuelUnit] = React.useState('liters');
const [tripEstimate, setTripEstimate] = React.useState(null);
const [currency, setCurrency] = React.useState('USD');
const [offsetPercentage, setOffsetPercentage] = React.useState(100);
const [customPercentage, setCustomPercentage] = React.useState('');
const [customAmount, setCustomAmount] = React.useState('');
const handleCalculate = React.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 = React.useCallback((e) => {
const value = e.target.value;
if (value === '' || (Number(value) >= 0 && Number(value) <= 100)) {
setCustomPercentage(value);
if (value !== '') {
setOffsetPercentage(Number(value));
}
}
}, []);
const handlePresetPercentage = React.useCallback((percentage) => {
setOffsetPercentage(percentage);
setCustomPercentage('');
}, []);
const calculateOffsetAmount = React.useCallback((emissions, percentage) => {
return (emissions * percentage) / 100;
}, []);
const handleCustomAmountChange = React.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'),
React.createElement(Route, { key: 'icon' })
]),
// 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 = 'trip' }) {
const [loading, setLoading] = React.useState(false);
const [error, setError] = React.useState(null);
const [success, setSuccess] = React.useState(false);
const [order, setOrder] = React.useState(null);
const [currency, setCurrency] = React.useState('USD');
const [portfolio, setPortfolio] = React.useState(null);
const [loadingPortfolio, setLoadingPortfolio] = React.useState(true);
const [formData, setFormData] = React.useState({
name: '',
email: '',
phone: '',
company: '',
message: `I would like to offset ${tons.toFixed(2)} tons of CO2 from my yacht's ${calculatorType} emissions.`
});
React.useEffect(() => {
fetchPortfolio();
}, []);
const fetchPortfolio = async () => {
try {
const portfolios = await getPortfolios();
const puffinPortfolio = portfolios.find(p =>
p.name.toLowerCase().includes('puffin') ||
p.name.toLowerCase().includes('maritime')
);
if (!puffinPortfolio) {
throw new Error('Portfolio not found');
}
setPortfolio(puffinPortfolio);
} 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 {
const newOrder = await createOffsetOrder(portfolio.id, tons);
setOrder(newOrder);
setSuccess(true);
} catch (err) {
setError('Failed to create offset order. Please try again.');
} finally {
setLoading(false);
}
};
const handleFormSubmit = async (e) => {
e.preventDefault();
setLoading(true);
try {
await sendFormSubmission(formData);
setSuccess(true);
} catch (err) {
setError('Failed to send request. Please try again.');
} finally {
setLoading(false);
}
};
const renderPortfolioPrice = (portfolio) => {
try {
// Get the price per ton from the portfolio
const pricePerTon = portfolio.pricePerTon || 200; // Default to 200 if not set
const targetCurrency = getCurrencyByCode(currency);
return formatCurrency(pricePerTon, targetCurrency);
} catch (err) {
console.error('Error formatting portfolio price:', err);
return formatCurrency(200, currencies.USD); // Default fallback
}
};
// Calculate offset cost using the portfolio price
const offsetCost = monetaryAmount || (portfolio ? tons * (portfolio.pricePerTon || 200) : 0);
if (success && order) {
return React.createElement('div', {
className: 'bg-white rounded-lg shadow-xl p-8 max-w-4xl w-full'
}, [
React.createElement('div', {
key: 'success-icon',
className: 'text-center py-8'
}, [
React.createElement('div', {
className: 'inline-flex items-center justify-center w-16 h-16 bg-green-100 rounded-full mb-6'
}, [
React.createElement(CheckIcon, {})
]),
React.createElement('h3', {
className: 'text-2xl font-bold text-gray-900 mb-4'
}, 'Offset Order Successful!'),
React.createElement('p', {
className: 'text-gray-600 mb-6'
}, 'Your order has been processed successfully. You\'ll receive a confirmation email shortly.'),
React.createElement('div', {
className: 'bg-gray-50 rounded-lg p-6 mb-6'
}, [
React.createElement('h4', {
className: 'text-lg font-semibold text-gray-900 mb-4'
}, 'Order Summary'),
React.createElement('div', {
className: 'space-y-2'
}, [
React.createElement('div', {
className: 'flex justify-between'
}, [
React.createElement('span', {
className: 'text-gray-600'
}, 'Order ID:'),
React.createElement('span', {
className: 'font-medium'
}, order.id)
]),
React.createElement('div', {
className: 'flex justify-between'
}, [
React.createElement('span', {
className: 'text-gray-600'
}, 'Amount:'),
React.createElement('span', {
className: 'font-medium'
}, formatCurrency(order.amountCharged / 100, currencies[order.currency]))
]),
React.createElement('div', {
className: 'flex justify-between'
}, [
React.createElement('span', {
className: 'text-gray-600'
}, 'CO₂ Offset:'),
React.createElement('span', {
className: 'font-medium'
}, `${order.tons} tons`)
]),
React.createElement('div', {
className: 'flex justify-between'
}, [
React.createElement('span', {
className: 'text-gray-600'
}, 'Portfolio:'),
React.createElement('span', {
className: 'font-medium'
}, order.portfolio.name)
])
])
]),
React.createElement('button', {
onClick: onBack,
className: 'bg-blue-500 text-white px-6 py-2 rounded-lg hover:bg-blue-600 transition-colors'
}, 'Back to Calculator')
])
]);
}
if (success) {
return React.createElement('div', {
className: 'bg-white rounded-lg shadow-xl p-8 max-w-4xl w-full'
}, [
React.createElement('div', {
key: 'success-icon',
className: 'text-center py-8'
}, [
React.createElement('div', {
className: 'inline-flex items-center justify-center w-16 h-16 bg-green-100 rounded-full mb-6'
}, [
React.createElement(CheckIcon, {})
]),
React.createElement('h3', {
className: 'text-2xl font-bold text-gray-900 mb-4'
}, 'Request Submitted Successfully!'),
React.createElement('p', {
className: 'text-gray-600 mb-6'
}, 'Your offset request has been submitted. Our team will contact you shortly to complete the process.'),
React.createElement('button', {
onClick: onBack,
className: 'bg-blue-500 text-white px-6 py-2 rounded-lg hover:bg-blue-600 transition-colors'
}, 'Back to Calculator')
])
]);
}
return React.createElement('div', {
className: 'bg-white rounded-lg shadow-xl p-8 max-w-4xl w-full'
}, [
React.createElement('button', {
key: 'back-button',
onClick: onBack,
className: 'flex items-center text-gray-600 hover:text-gray-900 mb-6'
}, [
React.createElement(ArrowLeft, {}),
'Back to Calculator'
]),
React.createElement('div', {
key: 'header',
className: 'text-center mb-8'
}, [
React.createElement('h2', {
className: 'text-3xl font-bold text-gray-900 mb-4'
}, 'Offset Your Impact'),
React.createElement('p', {
className: 'text-lg text-gray-600'
}, `You're about to offset ${tons.toFixed(2)} tons of CO₂`)
]),
error ? React.createElement('div', {
key: 'error',
className: 'bg-red-50 border border-red-200 rounded-lg p-4 mb-6'
}, [
React.createElement('div', {
className: 'flex items-center space-x-2'
}, [
React.createElement('span', {
className: 'text-red-500'
}, '⚠️'),
React.createElement('p', {
className: 'text-red-700'
}, error)
])
]) : null,
loadingPortfolio ? React.createElement('div', {
key: 'loading',
className: 'flex justify-center items-center py-12'
}, [
React.createElement(LoaderIcon, {}),
React.createElement('span', {
className: 'ml-2 text-gray-600'
}, 'Loading portfolio information...')
]) : portfolio ? React.createElement(React.Fragment, {
key: 'portfolio'
}, [
// Portfolio description
React.createElement('div', {
className: 'bg-white border rounded-lg p-6 mb-8'
}, [
React.createElement('h3', {
className: 'text-xl font-semibold text-gray-900 mb-4'
}, portfolio.name),
React.createElement('p', {
className: 'text-gray-600 mb-6'
}, portfolio.description),
// Projects
portfolio.projects && portfolio.projects.length > 0 ?
React.createElement('div', {
className: 'grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 mb-6'
}, portfolio.projects.map((project) =>
React.createElement('div', {
key: project.id,
className: 'bg-gray-50 rounded-lg p-4 hover:shadow-md transition-shadow'
}, [
React.createElement('div', {
className: 'flex items-center space-x-2 mb-3'
}, [
React.createElement(ProjectTypeIcon, { project }),
React.createElement('h4', {
className: 'font-semibold text-gray-900'
}, project.name)
]),
project.imageUrl ? React.createElement('div', {
className: 'relative h-32 mb-3 rounded-lg overflow-hidden'
}, [
React.createElement('img', {
src: project.imageUrl,
alt: project.name,
className: 'absolute inset-0 w-full h-full object-cover'
})
]) : null,
React.createElement('p', {
className: 'text-sm text-gray-600 mb-3'
}, project.shortDescription || project.description),
React.createElement('div', {
className: 'space-y-1 text-sm'
}, [
project.location ? React.createElement('div', {
className: 'flex justify-between'
}, [
React.createElement('span', {
className: 'text-gray-500'
}, 'Location:'),
React.createElement('span', {
className: 'text-gray-900'
}, project.location)
]) : null,
project.type ? React.createElement('div', {
className: 'flex justify-between'
}, [
React.createElement('span', {
className: 'text-gray-500'
}, 'Type:'),
React.createElement('span', {
className: 'text-gray-900'
}, project.type)
]) : null,
project.verificationStandard ? React.createElement('div', {
className: 'flex justify-between'
}, [
React.createElement('span', {
className: 'text-gray-500'
}, 'Standard:'),
React.createElement('span', {
className: 'text-gray-900'
}, project.verificationStandard)
]) : null,
project.impactMetrics?.co2Reduced ? React.createElement('div', {
className: 'flex justify-between'
}, [
React.createElement('span', {
className: 'text-gray-500'
}, 'Impact:'),
React.createElement('span', {
className: 'text-gray-900'
}, `${project.impactMetrics.co2Reduced.toLocaleString()} tons CO₂`)
]) : null
])
])
)) : null,
// Price per ton
React.createElement('div', {
className: 'flex items-center justify-between bg-blue-50 p-4 rounded-lg'
}, [
React.createElement('span', {
className: 'text-blue-900 font-medium'
}, 'Portfolio Price per Ton:'),
React.createElement('span', {
className: 'text-blue-900 font-bold text-lg'
}, renderPortfolioPrice(portfolio))
])
]),
// Order summary
React.createElement('div', {
className: 'bg-gray-50 rounded-lg p-6 mb-6'
}, [
React.createElement('h3', {
className: 'text-lg font-semibold text-gray-900 mb-4'
}, 'Order Summary'),
React.createElement('div', {
className: 'space-y-4'
}, [
React.createElement('div', {
className: 'flex justify-between'
}, [
React.createElement('span', {
className: 'text-gray-600'
}, 'Amount to Offset:'),
React.createElement('span', {
className: 'font-medium'
}, `${tons.toFixed(2)} tons CO₂`)
]),
React.createElement('div', {
className: 'flex justify-between'
}, [
React.createElement('span', {
className: 'text-gray-600'
}, 'Portfolio Distribution:'),
React.createElement('span', {
className: 'font-medium'
}, 'Automatically optimized')
]),
React.createElement('div', {
className: 'border-t pt-4'
}, [
React.createElement('div', {
className: 'flex justify-between'
}, [
React.createElement('span', {
className: 'text-gray-900 font-semibold'
}, 'Total Cost:'),
React.createElement('span', {
className: 'text-gray-900 font-semibold'
}, formatCurrency(offsetCost, getCurrencyByCode(portfolio.currency)))
])
])
])
]),
// Submit button
React.createElement('button', {
onClick: handleOffsetOrder,
disabled: loading,
className: `w-full bg-blue-500 text-white py-3 px-4 rounded-lg transition-colors ${
loading ? 'opacity-50 cursor-not-allowed' : 'hover:bg-blue-600'
}`
}, loading ?
React.createElement('div', {
className: 'flex items-center justify-center'
}, [
React.createElement(LoaderIcon, {}),
'Processing...'
]) :
'Confirm Offset Order'
)
]) : React.createElement('div', {
key: 'contact-form',
className: 'max-w-2xl mx-auto'
}, [
React.createElement('div', {
className: 'bg-blue-50 border border-blue-200 rounded-lg p-6 mb-8'
}, [
React.createElement('h3', {
className: 'text-xl font-semibold text-blue-900 mb-4'
}, 'Contact Us for Offsetting'),
React.createElement('p', {
className: 'text-blue-700 mb-4'
}, 'Our automated offsetting service is temporarily unavailable. Please fill out the form below and our team will help you offset your emissions.'),
React.createElement('form', {
onSubmit: handleFormSubmit,
className: 'space-y-6'
}, [
React.createElement('div', {}, [
React.createElement('label', {
className: 'block text-sm font-medium text-gray-700 mb-1'
}, 'Name *'),
React.createElement('input', {
type: 'text',
required: true,
value: formData.name,
onChange: (e) => setFormData(prev => ({ ...prev, 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'
})
]),
React.createElement('div', {}, [
React.createElement('label', {
className: 'block text-sm font-medium text-gray-700 mb-1'
}, 'Email *'),
React.createElement('input', {
type: 'email',
required: true,
value: formData.email,
onChange: (e) => setFormData(prev => ({ ...prev, 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'
})
]),
React.createElement('div', {}, [
React.createElement('label', {
className: 'block text-sm font-medium text-gray-700 mb-1'
}, 'Phone'),
React.createElement('input', {
type: 'tel',
value: formData.phone,
onChange: (e) => setFormData(prev => ({ ...prev, 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'
})
]),
React.createElement('div', {}, [
React.createElement('label', {
className: 'block text-sm font-medium text-gray-700 mb-1'
}, 'Company'),
React.createElement('input', {
type: 'text',
value: formData.company,
onChange: (e) => setFormData(prev => ({ ...prev, company: e.target.value })),
className: 'w-full px-4 py-2 border rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500'
})
]),
React.createElement('div', {}, [
React.createElement('label', {
className: 'block text-sm font-medium text-gray-700 mb-1'
}, 'Message'),
React.createElement('textarea', {
rows: 4,
value: formData.message,
onChange: (e) => setFormData(prev => ({ ...prev, message: e.target.value })),
className: 'w-full px-4 py-2 border rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500'
})
]),
React.createElement('button', {
type: 'submit',
disabled: loading,
className: `w-full flex items-center justify-center bg-blue-500 text-white py-3 rounded-lg transition-colors ${
loading ? 'opacity-50 cursor-not-allowed' : 'hover:bg-blue-600'
}`
}, loading ?
React.createElement(React.Fragment, {}, [
React.createElement(LoaderIcon, {}),
'Sending Request...'
]) :
'Send Offset Request'
)
])
])
])
]);
}