This commit is contained in:
Matt 2025-05-13 20:09:23 +02:00
parent fc47823714
commit 2f7f26e4fd
4 changed files with 191 additions and 22 deletions

View File

@ -28,7 +28,65 @@ server {
include /etc/letsencrypt/options-ssl-nginx.conf; # from certbot
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # from certbot
# === Proxy all traffic to your Node app ===
# === Proxy for direct image requests from Wren API ===
location ~* ^/images/(.*)$ {
proxy_pass https://www.wren.co/images/$1;
proxy_ssl_server_name on;
proxy_set_header Host www.wren.co;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_buffers 16 4k;
proxy_buffer_size 2k;
# Add CORS headers for images
add_header Access-Control-Allow-Origin '*' always;
add_header Access-Control-Allow-Methods 'GET, OPTIONS' always;
add_header Access-Control-Allow-Headers 'Origin, X-Requested-With, Content-Type, Accept, Authorization' always;
# Cache control for images
expires 7d;
add_header Cache-Control "public, max-age=604800";
# Handle OPTIONS requests for CORS preflight
if ($request_method = 'OPTIONS') {
add_header Access-Control-Allow-Origin '*';
add_header Access-Control-Allow-Methods 'GET, OPTIONS';
add_header Access-Control-Allow-Headers 'Origin, X-Requested-With, Content-Type, Accept, Authorization';
add_header Access-Control-Max-Age 1728000;
add_header Content-Type 'text/plain charset=UTF-8';
add_header Content-Length 0;
return 204;
}
}
# === Proxy for Wren API requests ===
location /api/wren/ {
proxy_pass https://www.wren.co/api/;
proxy_ssl_server_name on;
proxy_set_header Host www.wren.co;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# Add CORS headers for API requests
add_header Access-Control-Allow-Origin '*' always;
add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS' always;
add_header Access-Control-Allow-Headers 'Origin, X-Requested-With, Content-Type, Accept, Authorization' always;
# Handle OPTIONS requests for CORS preflight
if ($request_method = 'OPTIONS') {
add_header Access-Control-Allow-Origin '*';
add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS';
add_header Access-Control-Allow-Headers 'Origin, X-Requested-With, Content-Type, Accept, Authorization';
add_header Access-Control-Max-Age 1728000;
add_header Content-Type 'text/plain charset=UTF-8';
add_header Content-Length 0;
return 204;
}
}
# === Proxy all other traffic to your Node app ===
location / {
proxy_pass http://127.0.0.1:3800;
proxy_http_version 1.1;
@ -38,10 +96,55 @@ server {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# increase timeouts if your app sometimes takes longer to respond:
proxy_read_timeout 90;
# Add CORS headers for all responses
add_header Access-Control-Allow-Origin '*' always;
add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS' always;
add_header Access-Control-Allow-Headers 'Origin, X-Requested-With, Content-Type, Accept, Authorization' always;
# Increase timeouts for potentially slow API calls
proxy_read_timeout 120;
proxy_connect_timeout 120;
proxy_send_timeout 120;
}
# === Additional common settings ===
# Increase client body size for file uploads if needed
client_max_body_size 10M;
# Enable compression for better performance
gzip on;
gzip_comp_level 5;
gzip_min_length 256;
gzip_proxied any;
gzip_vary on;
gzip_types
application/atom+xml
application/javascript
application/json
application/ld+json
application/manifest+json
application/rss+xml
application/vnd.geo+json
application/vnd.ms-fontobject
application/x-font-ttf
application/x-web-app-manifest+json
application/xhtml+xml
application/xml
font/opentype
image/bmp
image/svg+xml
image/x-icon
text/cache-manifest
text/css
text/plain
text/vcard
text/vnd.rim.location.xloc
text/vtt
text/x-component
text/x-cross-domain-policy;
# Optional: serve static assets directly if you ever add any here
# location /static/ {
# root /var/www/puffinoffset.com;

View File

@ -13,15 +13,38 @@ server {
gzip_proxied expired no-cache no-store private auth;
gzip_types text/plain text/css application/json application/javascript application/x-javascript text/xml application/xml application/xml+rss text/javascript;
# Forward all requests to index.html for SPA routing
location / {
try_files $uri $uri/ /index.html;
}
# Cache static assets
# Add CORS headers for static assets including images
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ {
expires 30d;
add_header Cache-Control "public, no-transform";
add_header Access-Control-Allow-Origin * always;
add_header Access-Control-Allow-Methods 'GET, OPTIONS' always;
add_header Access-Control-Allow-Headers 'Origin, X-Requested-With, Content-Type, Accept, Authorization' always;
try_files $uri =404;
}
# Forward all requests to index.html for SPA routing
location / {
try_files $uri $uri/ /index.html;
# Add CORS headers for all responses
add_header Access-Control-Allow-Origin * always;
add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS' always;
add_header Access-Control-Allow-Headers 'Origin, X-Requested-With, Content-Type, Accept, Authorization' always;
}
# Respond to preflighted CORS requests
location /api/ {
if ($request_method = 'OPTIONS') {
add_header Access-Control-Allow-Origin * always;
add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS' always;
add_header Access-Control-Allow-Headers 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization' always;
add_header Access-Control-Max-Age 1728000;
add_header Content-Type 'text/plain charset=UTF-8';
add_header Content-Length 0;
return 204;
}
try_files $uri $uri/ /index.html;
}
# Don't cache HTML

View File

@ -160,19 +160,16 @@ export async function createOffsetOrder(
console.error('[wrenClient] Cannot create order - missing API token');
throw new Error('Carbon offset service is currently unavailable. Please contact support.');
}
// Always use portfolio ID 2 for Community Tree Planting as seen in the tutorial
const actualPortfolioId = 2;
console.log(`[wrenClient] Creating offset order: portfolio=${actualPortfolioId}, tons=${tons}, dryRun=${dryRun}`);
console.log(`[wrenClient] Creating offset order: portfolio=${portfolioId}, tons=${tons}, dryRun=${dryRun}`);
const api = createApiClient();
// Removed the /api prefix to match the working example
const response = await api.post('/offset-orders', {
// Using exactly the format shown in the API tutorial
portfolioId: actualPortfolioId, // Force using ID 2 as in the tutorial
portfolioId, // Use the provided portfolio ID instead of hardcoding
tons,
dryRun: true // Always use dryRun mode for testing
dryRun // Use the provided dryRun parameter
});
// Add detailed response logging

View File

@ -64,19 +64,34 @@ export function OffsetOrder({ tons, monetaryAmount, onBack, calculatorType }: Pr
fetchPortfolio();
}, []);
const [portfolios, setPortfolios] = useState<Portfolio[]>([]);
const [selectedPortfolioId, setSelectedPortfolioId] = useState<number | null>(null);
const fetchPortfolio = async () => {
try {
const portfolios = await getPortfolios();
const puffinPortfolio = portfolios.find(p =>
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 =>
p.name.toLowerCase().includes('puffin') ||
p.name.toLowerCase().includes('maritime')
);
if (!puffinPortfolio) {
throw new Error('Portfolio not found');
if (puffinPortfolio) {
setPortfolio(puffinPortfolio);
setSelectedPortfolioId(puffinPortfolio.id);
} else {
// Default to first portfolio if no puffin portfolio found
setPortfolio(allPortfolios[0]);
setSelectedPortfolioId(allPortfolios[0].id);
}
setPortfolio(puffinPortfolio);
} catch (err) {
setError('Failed to fetch portfolio information. Please try again.');
} finally {
@ -84,6 +99,15 @@ export function OffsetOrder({ tons, monetaryAmount, onBack, calculatorType }: Pr
}
};
// Handle portfolio selection change
const handlePortfolioChange = (portfolioId: number) => {
const selected = portfolios.find(p => p.id === portfolioId);
if (selected) {
setPortfolio(selected);
setSelectedPortfolioId(portfolioId);
}
};
const handleOffsetOrder = async () => {
if (!portfolio) return;
@ -287,6 +311,28 @@ export function OffsetOrder({ tons, monetaryAmount, onBack, calculatorType }: Pr
</div>
) : portfolio ? (
<>
{portfolios.length > 1 && (
<div className="mb-8">
<label className="block text-sm font-medium text-gray-700 mb-2">
Select Carbon Offset Portfolio
</label>
<select
value={selectedPortfolioId || ''}
onChange={(e) => handlePortfolioChange(Number(e.target.value))}
className="block w-full pl-3 pr-10 py-2 text-base border-gray-300 focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm rounded-md"
>
{portfolios.map((p) => (
<option key={p.id} value={p.id}>
{p.name} - {formatCurrency(p.pricePerTon, getCurrencyByCode(currency))} per ton
</option>
))}
</select>
<p className="mt-2 text-sm text-gray-500">
Select which portfolio of climate projects you'd like to support.
</p>
</div>
)}
<div className="bg-white border rounded-lg p-6 mb-8">
<h3 className="text-xl font-semibold text-gray-900 mb-4">
{portfolio.name}
@ -396,4 +442,4 @@ export function OffsetOrder({ tons, monetaryAmount, onBack, calculatorType }: Pr
) : null}
</div>
);
}
}