1040 lines
36 KiB
JavaScript
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'
|
|
)
|
|
])
|
|
])
|
|
])
|
|
]);
|
|
}
|