2025-05-13 18:50:30 +02:00
|
|
|
import React, { useState, useEffect } from 'react';
|
|
|
|
|
import { Check, AlertCircle, ArrowLeft, Loader2, Globe2, TreePine, Waves, Factory, Wind } from 'lucide-react';
|
|
|
|
|
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;
|
|
|
|
|
monetaryAmount?: number;
|
|
|
|
|
onBack: () => void;
|
|
|
|
|
calculatorType: 'trip' | 'annual';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
interface ProjectTypeIconProps {
|
|
|
|
|
project: OffsetProject;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const ProjectTypeIcon = ({ project }: ProjectTypeIconProps) => {
|
|
|
|
|
// Safely check if project and type exist
|
|
|
|
|
if (!project || !project.type) {
|
|
|
|
|
return <Globe2 className="text-blue-500" />;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const type = project.type.toLowerCase();
|
|
|
|
|
|
|
|
|
|
switch (type) {
|
|
|
|
|
case 'direct air capture':
|
|
|
|
|
return <Factory className="text-purple-500" />;
|
|
|
|
|
case 'blue carbon':
|
|
|
|
|
return <Waves className="text-blue-500" />;
|
|
|
|
|
case 'renewable energy':
|
|
|
|
|
return <Wind className="text-green-500" />;
|
|
|
|
|
case 'forestry':
|
|
|
|
|
return <TreePine className="text-green-500" />;
|
|
|
|
|
default:
|
|
|
|
|
return <Globe2 className="text-blue-500" />;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export function OffsetOrder({ tons, monetaryAmount, onBack, calculatorType }: Props) {
|
|
|
|
|
const [loading, setLoading] = useState(false);
|
|
|
|
|
const [error, setError] = useState<string | null>(null);
|
|
|
|
|
const [success, setSuccess] = useState(false);
|
|
|
|
|
const [order, setOrder] = useState<OffsetOrderType | null>(null);
|
|
|
|
|
const [currency, setCurrency] = useState<CurrencyCode>('USD');
|
|
|
|
|
const [portfolio, setPortfolio] = useState<Portfolio | null>(null);
|
|
|
|
|
const [loadingPortfolio, setLoadingPortfolio] = useState(true);
|
|
|
|
|
const [formData, setFormData] = useState({
|
|
|
|
|
name: '',
|
|
|
|
|
email: '',
|
|
|
|
|
phone: '',
|
|
|
|
|
company: '',
|
|
|
|
|
message: `I would like to offset ${tons.toFixed(2)} tons of CO2 from my yacht's ${calculatorType} emissions.`
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
if (!config.wrenApiKey) {
|
|
|
|
|
setError('Carbon offset service is currently unavailable. Please use our contact form to request offsetting.');
|
|
|
|
|
setLoadingPortfolio(false);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
fetchPortfolio();
|
|
|
|
|
}, []);
|
|
|
|
|
|
2025-05-13 20:09:23 +02:00
|
|
|
const [portfolios, setPortfolios] = useState<Portfolio[]>([]);
|
|
|
|
|
const [selectedPortfolioId, setSelectedPortfolioId] = useState<number | null>(null);
|
|
|
|
|
|
2025-05-13 18:50:30 +02:00
|
|
|
const fetchPortfolio = async () => {
|
|
|
|
|
try {
|
2025-05-13 20:09:23 +02:00
|
|
|
const allPortfolios = await getPortfolios();
|
|
|
|
|
|
|
|
|
|
// Check if portfolios were returned
|
|
|
|
|
if (!allPortfolios || allPortfolios.length === 0) {
|
|
|
|
|
throw new Error('No portfolios available');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
setPortfolios(allPortfolios);
|
|
|
|
|
|
|
|
|
|
// Set default portfolio - prefer one with "puffin" in the name, otherwise first one
|
|
|
|
|
const puffinPortfolio = allPortfolios.find(p =>
|
2025-05-13 18:50:30 +02:00
|
|
|
p.name.toLowerCase().includes('puffin') ||
|
|
|
|
|
p.name.toLowerCase().includes('maritime')
|
|
|
|
|
);
|
|
|
|
|
|
2025-05-13 20:09:23 +02:00
|
|
|
if (puffinPortfolio) {
|
|
|
|
|
setPortfolio(puffinPortfolio);
|
|
|
|
|
setSelectedPortfolioId(puffinPortfolio.id);
|
|
|
|
|
} else {
|
|
|
|
|
// Default to first portfolio if no puffin portfolio found
|
|
|
|
|
setPortfolio(allPortfolios[0]);
|
|
|
|
|
setSelectedPortfolioId(allPortfolios[0].id);
|
2025-05-13 18:50:30 +02:00
|
|
|
}
|
|
|
|
|
} catch (err) {
|
|
|
|
|
setError('Failed to fetch portfolio information. Please try again.');
|
|
|
|
|
} finally {
|
|
|
|
|
setLoadingPortfolio(false);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2025-05-13 20:09:23 +02:00
|
|
|
// Handle portfolio selection change
|
|
|
|
|
const handlePortfolioChange = (portfolioId: number) => {
|
|
|
|
|
const selected = portfolios.find(p => p.id === portfolioId);
|
|
|
|
|
if (selected) {
|
|
|
|
|
setPortfolio(selected);
|
|
|
|
|
setSelectedPortfolioId(portfolioId);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2025-05-13 18:50:30 +02:00
|
|
|
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 renderPortfolioPrice = (portfolio: 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);
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div className="bg-white rounded-lg shadow-xl p-8 max-w-4xl w-full">
|
|
|
|
|
<button
|
|
|
|
|
onClick={onBack}
|
|
|
|
|
className="flex items-center text-gray-600 hover:text-gray-900 mb-6"
|
|
|
|
|
>
|
|
|
|
|
<ArrowLeft className="mr-2" size={20} />
|
|
|
|
|
Back to Calculator
|
|
|
|
|
</button>
|
|
|
|
|
|
|
|
|
|
<div className="text-center mb-8">
|
|
|
|
|
<h2 className="text-3xl font-bold text-gray-900 mb-4">
|
|
|
|
|
Offset Your Impact
|
|
|
|
|
</h2>
|
|
|
|
|
<p className="text-lg text-gray-600">
|
|
|
|
|
You're about to offset {tons.toFixed(2)} tons of CO₂
|
|
|
|
|
</p>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{error && !config.wrenApiKey ? (
|
|
|
|
|
<div className="max-w-2xl mx-auto">
|
|
|
|
|
<div className="bg-blue-50 border border-blue-200 rounded-lg p-6 mb-8">
|
|
|
|
|
<h3 className="text-xl font-semibold text-blue-900 mb-4">
|
|
|
|
|
Contact Us for Offsetting
|
|
|
|
|
</h3>
|
|
|
|
|
<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.
|
|
|
|
|
</p>
|
|
|
|
|
<form onSubmit={async (e) => {
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
setLoading(true);
|
|
|
|
|
try {
|
|
|
|
|
await sendFormspreeEmail(formData, 'offset');
|
|
|
|
|
setSuccess(true);
|
|
|
|
|
} catch (err) {
|
|
|
|
|
setError('Failed to send request. Please try again.');
|
|
|
|
|
} finally {
|
|
|
|
|
setLoading(false);
|
|
|
|
|
}
|
|
|
|
|
}} className="space-y-6">
|
|
|
|
|
<div>
|
|
|
|
|
<label className="block text-sm font-medium text-gray-700 mb-1">
|
|
|
|
|
Name *
|
|
|
|
|
</label>
|
|
|
|
|
<input
|
|
|
|
|
type="text"
|
|
|
|
|
required
|
|
|
|
|
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"
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
<div>
|
|
|
|
|
<label className="block text-sm font-medium text-gray-700 mb-1">
|
|
|
|
|
Email *
|
|
|
|
|
</label>
|
|
|
|
|
<input
|
|
|
|
|
type="email"
|
|
|
|
|
required
|
|
|
|
|
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"
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
<div>
|
|
|
|
|
<label className="block text-sm font-medium text-gray-700 mb-1">
|
|
|
|
|
Phone
|
|
|
|
|
</label>
|
|
|
|
|
<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"
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
<div>
|
|
|
|
|
<label className="block text-sm font-medium text-gray-700 mb-1">
|
|
|
|
|
Company
|
|
|
|
|
</label>
|
|
|
|
|
<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"
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
<div>
|
|
|
|
|
<label className="block text-sm font-medium text-gray-700 mb-1">
|
|
|
|
|
Message
|
|
|
|
|
</label>
|
|
|
|
|
<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"
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
<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 ? (
|
|
|
|
|
<>
|
|
|
|
|
<Loader2 className="animate-spin mr-2" size={20} />
|
|
|
|
|
Sending Request...
|
|
|
|
|
</>
|
|
|
|
|
) : (
|
|
|
|
|
'Send Offset Request'
|
|
|
|
|
)}
|
|
|
|
|
</button>
|
|
|
|
|
</form>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
) : error ? (
|
|
|
|
|
<div className="bg-red-50 border border-red-200 rounded-lg p-4 mb-6">
|
|
|
|
|
<div className="flex items-center space-x-2">
|
|
|
|
|
<AlertCircle className="text-red-500" size={20} />
|
|
|
|
|
<p className="text-red-700">{error}</p>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
) : success && order ? (
|
|
|
|
|
<div className="text-center py-8">
|
|
|
|
|
<div className="inline-flex items-center justify-center w-16 h-16 bg-green-100 rounded-full mb-6">
|
|
|
|
|
<Check className="text-green-500" size={32} />
|
|
|
|
|
</div>
|
|
|
|
|
<h3 className="text-2xl font-bold text-gray-900 mb-4">
|
|
|
|
|
Offset Order Successful!
|
|
|
|
|
</h3>
|
|
|
|
|
<p className="text-gray-600 mb-6">
|
|
|
|
|
Your order has been processed successfully. You'll receive a confirmation email shortly.
|
|
|
|
|
</p>
|
|
|
|
|
<div className="bg-gray-50 rounded-lg p-6 mb-6">
|
|
|
|
|
<h4 className="text-lg font-semibold text-gray-900 mb-4">Order Summary</h4>
|
|
|
|
|
<div className="space-y-2">
|
|
|
|
|
<div className="flex justify-between">
|
|
|
|
|
<span className="text-gray-600">Order ID:</span>
|
|
|
|
|
<span className="font-medium">{order.id}</span>
|
|
|
|
|
</div>
|
|
|
|
|
<div className="flex justify-between">
|
|
|
|
|
<span className="text-gray-600">Amount:</span>
|
|
|
|
|
<span className="font-medium">
|
|
|
|
|
{formatCurrency(order.amountCharged / 100, currencies[order.currency])}
|
|
|
|
|
</span>
|
|
|
|
|
</div>
|
|
|
|
|
<div className="flex justify-between">
|
|
|
|
|
<span className="text-gray-600">CO₂ Offset:</span>
|
|
|
|
|
<span className="font-medium">{order.tons} tons</span>
|
|
|
|
|
</div>
|
|
|
|
|
<div className="flex justify-between">
|
|
|
|
|
<span className="text-gray-600">Portfolio:</span>
|
|
|
|
|
<span className="font-medium">{order.portfolio.name}</span>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<button
|
|
|
|
|
onClick={onBack}
|
|
|
|
|
className="bg-blue-500 text-white px-6 py-2 rounded-lg hover:bg-blue-600 transition-colors"
|
|
|
|
|
>
|
|
|
|
|
Back to Calculator
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
) : loadingPortfolio ? (
|
|
|
|
|
<div className="flex justify-center items-center py-12">
|
|
|
|
|
<Loader2 className="animate-spin text-blue-500" size={32} />
|
|
|
|
|
<span className="ml-2 text-gray-600">Loading portfolio information...</span>
|
|
|
|
|
</div>
|
|
|
|
|
) : portfolio ? (
|
|
|
|
|
<>
|
2025-05-13 20:12:41 +02:00
|
|
|
{portfolios.length > 0 && (
|
2025-05-13 20:09:23 +02:00
|
|
|
<div className="mb-8">
|
2025-05-13 20:12:41 +02:00
|
|
|
<h3 className="text-lg font-semibold text-gray-900 mb-4">
|
|
|
|
|
Select A Carbon Offset Portfolio
|
|
|
|
|
</h3>
|
|
|
|
|
|
|
|
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
2025-05-13 20:09:23 +02:00
|
|
|
{portfolios.map((p) => (
|
2025-05-13 20:12:41 +02:00
|
|
|
<div
|
|
|
|
|
key={p.id}
|
|
|
|
|
onClick={() => handlePortfolioChange(p.id)}
|
|
|
|
|
className={`cursor-pointer border rounded-lg p-4 transition-all ${
|
|
|
|
|
selectedPortfolioId === p.id
|
|
|
|
|
? 'border-blue-500 bg-blue-50 shadow-md'
|
|
|
|
|
: 'border-gray-200 hover:border-blue-300 hover:bg-blue-50'
|
|
|
|
|
}`}
|
|
|
|
|
>
|
|
|
|
|
<div className="flex justify-between items-start mb-2">
|
|
|
|
|
<h4 className="text-lg font-medium text-gray-900">{p.name}</h4>
|
|
|
|
|
{selectedPortfolioId === p.id && (
|
|
|
|
|
<div className="bg-blue-500 text-white rounded-full p-1">
|
|
|
|
|
<Check size={16} />
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
<p className="text-sm text-gray-600 mb-3 line-clamp-2">
|
|
|
|
|
{p.description}
|
|
|
|
|
</p>
|
|
|
|
|
<div className="flex justify-between items-center">
|
|
|
|
|
<div className="text-xs text-gray-500">
|
|
|
|
|
{p.projects?.length || 0} project{p.projects?.length !== 1 ? 's' : ''}
|
|
|
|
|
</div>
|
|
|
|
|
<div className="text-blue-600 font-semibold">
|
|
|
|
|
{formatCurrency(p.pricePerTon, getCurrencyByCode(currency))}/ton
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
2025-05-13 20:09:23 +02:00
|
|
|
))}
|
2025-05-13 20:12:41 +02:00
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<p className="mt-4 text-sm text-gray-500">
|
|
|
|
|
Click on a portfolio to select which climate projects you'd like to support.
|
2025-05-13 20:09:23 +02:00
|
|
|
</p>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
|
2025-05-13 18:50:30 +02:00
|
|
|
<div className="bg-white border rounded-lg p-6 mb-8">
|
|
|
|
|
<h3 className="text-xl font-semibold text-gray-900 mb-4">
|
|
|
|
|
{portfolio.name}
|
|
|
|
|
</h3>
|
|
|
|
|
<p className="text-gray-600 mb-6">
|
|
|
|
|
{portfolio.description}
|
|
|
|
|
</p>
|
|
|
|
|
|
|
|
|
|
{portfolio.projects && portfolio.projects.length > 0 && (
|
|
|
|
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 mb-6">
|
|
|
|
|
{portfolio.projects.map((project) => (
|
|
|
|
|
<div key={project.id} className="bg-gray-50 rounded-lg p-4 hover:shadow-md transition-shadow">
|
|
|
|
|
<div className="flex items-center space-x-2 mb-3">
|
|
|
|
|
<ProjectTypeIcon project={project} />
|
|
|
|
|
<h4 className="font-semibold text-gray-900">{project.name}</h4>
|
|
|
|
|
</div>
|
|
|
|
|
{project.imageUrl && (
|
|
|
|
|
<div className="relative h-32 mb-3 rounded-lg overflow-hidden">
|
|
|
|
|
<img
|
|
|
|
|
src={project.imageUrl}
|
|
|
|
|
alt={project.name}
|
|
|
|
|
className="absolute inset-0 w-full h-full object-cover"
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
<p className="text-sm text-gray-600 mb-3">
|
|
|
|
|
{project.shortDescription || project.description}
|
|
|
|
|
</p>
|
|
|
|
|
<div className="space-y-1 text-sm">
|
|
|
|
|
{project.location && (
|
|
|
|
|
<div className="flex justify-between">
|
|
|
|
|
<span className="text-gray-500">Location:</span>
|
|
|
|
|
<span className="text-gray-900">{project.location}</span>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
{project.type && (
|
|
|
|
|
<div className="flex justify-between">
|
|
|
|
|
<span className="text-gray-500">Type:</span>
|
|
|
|
|
<span className="text-gray-900">{project.type}</span>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
{project.verificationStandard && (
|
|
|
|
|
<div className="flex justify-between">
|
|
|
|
|
<span className="text-gray-500">Standard:</span>
|
|
|
|
|
<span className="text-gray-900">{project.verificationStandard}</span>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
{project.impactMetrics?.co2Reduced && (
|
|
|
|
|
<div className="flex justify-between">
|
|
|
|
|
<span className="text-gray-500">Impact:</span>
|
|
|
|
|
<span className="text-gray-900">{project.impactMetrics.co2Reduced.toLocaleString()} tons CO₂</span>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
<div className="flex items-center justify-between bg-blue-50 p-4 rounded-lg">
|
|
|
|
|
<span className="text-blue-900 font-medium">Portfolio Price per Ton:</span>
|
|
|
|
|
<span className="text-blue-900 font-bold text-lg">
|
|
|
|
|
{renderPortfolioPrice(portfolio)}
|
|
|
|
|
</span>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div className="bg-gray-50 rounded-lg p-6 mb-6">
|
|
|
|
|
<h3 className="text-lg font-semibold text-gray-900 mb-4">Order Summary</h3>
|
|
|
|
|
<div className="space-y-4">
|
|
|
|
|
<div className="flex justify-between">
|
|
|
|
|
<span className="text-gray-600">Amount to Offset:</span>
|
|
|
|
|
<span className="font-medium">{tons.toFixed(2)} tons CO₂</span>
|
|
|
|
|
</div>
|
|
|
|
|
<div className="flex justify-between">
|
|
|
|
|
<span className="text-gray-600">Portfolio Distribution:</span>
|
|
|
|
|
<span className="font-medium">Automatically optimized</span>
|
|
|
|
|
</div>
|
|
|
|
|
<div className="border-t pt-4">
|
|
|
|
|
<div className="flex justify-between">
|
|
|
|
|
<span className="text-gray-900 font-semibold">Total Cost:</span>
|
|
|
|
|
<span className="text-gray-900 font-semibold">
|
|
|
|
|
{formatCurrency(offsetCost, getCurrencyByCode(portfolio.currency))}
|
|
|
|
|
</span>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<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 ? (
|
|
|
|
|
<div className="flex items-center justify-center">
|
|
|
|
|
<Loader2 className="animate-spin mr-2" size={20} />
|
|
|
|
|
Processing...
|
|
|
|
|
</div>
|
|
|
|
|
) : (
|
|
|
|
|
'Confirm Offset Order'
|
|
|
|
|
)}
|
|
|
|
|
</button>
|
|
|
|
|
</>
|
|
|
|
|
) : null}
|
|
|
|
|
</div>
|
|
|
|
|
);
|
2025-05-13 20:09:23 +02:00
|
|
|
}
|