95 Commits

Author SHA1 Message Date
Matt
f0337101cf Show project name and percentage on same line in pie chart
All checks were successful
Build and Push Docker Images / docker (push) Successful in 58s
- Combine project name and percentage into single label line
- Format: "Project Name (25.0%)"
- Reduces vertical space and improves readability
- Cleaner, more compact label design

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-31 18:10:41 +01:00
Matt
3273d4af2a Make receipt page responsive and add print page breaks
All checks were successful
Build and Push Docker Images / docker (push) Successful in 58s
- Increase max width from max-w-3xl to max-w-4xl/5xl for better screen fit
- Add responsive breakpoints: full width on mobile, constrained on larger screens
- Add print-page-break class for automatic page breaks between sections
- Each major section now prints on separate page:
  - Page 1: Order summary with payment details
  - Page 2: Portfolio distribution chart
  - Page 3: Carbon impact comparisons
- Improve print styling: remove shadows, borders, rounded corners
- Set print page size to standard letter format
- Reduce padding on mobile for better space utilization

Fixes receipt layout to be responsive on all screen sizes while
maintaining excellent print quality with proper page breaks.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-31 18:06:23 +01:00
Matt
69e1465f69 Remove tonnage from pie chart labels for cleaner display
All checks were successful
Build and Push Docker Images / docker (push) Successful in 58s
- Remove middle tonnage line from chart labels
- Keep only project name and percentage
- Tonnage still visible in legend below chart
- Reduces label clutter and improves readability

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-31 18:01:27 +01:00
Matt
4dfc420535 Make pie chart responsive and reduce label text size
All checks were successful
Build and Push Docker Images / docker (push) Successful in 59s
- Reduce label font sizes: 10px name, 9px details (was 14px/12px)
- Add print-friendly smaller sizes: 7px/6px
- Make container fully responsive with max-width constraints
- Reduce label extension distance from ±50px to ±30px
- Reduce label radius from outerRadius+60 to outerRadius+35
- Use fixed pie dimensions (80/50) instead of calculated sizes
- Reduce connector line width from 2px to 1.5px
- Add print-specific max-width and height constraints

Fixes text overflow and makes chart fit properly on page while
maintaining readability and print quality.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-31 17:57:32 +01:00
Matt
ec0c8a2fbc Fix Docker build cache size issue by using mode=min
All checks were successful
Build and Push Docker Images / docker (push) Successful in 1m1s
Change buildcache from mode=max to mode=min to reduce cache layer size
and fix "413 Request Entity Too Large" error when pushing to Gitea registry.

mode=min only caches layers likely to be reused, significantly reducing
the cache size while maintaining build performance.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-31 17:51:43 +01:00
Matt
f21048b0d6 Replace custom pie chart with Recharts for better label collision handling
Some checks failed
Build and Push Docker Images / docker (push) Failing after 1m10s
- Add Recharts dependency for professional chart rendering
- Create RechartsPortfolioPieChart component with built-in label collision detection
- Replace StaticPortfolioPieChart usage in CheckoutSuccess page
- Remove old StaticPortfolioPieChart component with problematic label overlap
- Fixes jumbled/overlapping labels on portfolio distribution chart

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-31 17:48:20 +01:00
Matt
6787ccd2d8 Add portfolio distribution chart to receipt and update ID display
All checks were successful
Build and Push Docker Images / docker (push) Successful in 49s
Backend:
- Add getWrenPortfolios() to wrenClient.js with detailed logging including certification status
- Modify checkout endpoint to fetch and include portfolio data in order response
- Add stripeSessionId to order response for Payment ID display

Frontend:
- Create new StaticPortfolioPieChart component for printable receipt
- Add portfolio distribution visualization to CheckoutSuccess page
- Update receipt to show Payment ID (Stripe) and Offsetting Order ID (Wren)
- Implement responsive grid layout for order IDs
- Add print-friendly styling with Tailwind print classes

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-31 17:24:51 +01:00
Matt
837411699f Fix Wren API request body to match official API specification
All checks were successful
Build and Push Docker Images / docker (push) Successful in 45s
Changed request parameters to match Wren API docs:
- 'portfolio' → 'portfolioId' (correct parameter name)
- 'dry_run' → 'dryRun' (camelCase as per API)
- Removed 'currency' and 'amount_charged' (API calculates amount)
- Removed 'source' object (not in API spec)
- Added 'note' with customer email for record keeping

API will now calculate and return the amount charged based on tons and portfolio.
Reference: https://www.wren.co/api/offset-orders documentation
2025-10-31 12:58:48 +01:00
Matt
a6e655c4a9 Fix Wren API endpoint URL
All checks were successful
Build and Push Docker Images / docker (push) Successful in 47s
Changed from non-existent api.wren.co to correct www.wren.co
Changed endpoint path from offset_orders to offset-orders (hyphen not underscore)

Correct endpoints:
- POST https://www.wren.co/api/offset-orders
- GET https://www.wren.co/api/offset-orders/:id
2025-10-31 12:55:45 +01:00
Matt
9fcc458760 Add DNS servers to backend container to resolve external API calls
All checks were successful
Build and Push Docker Images / docker (push) Successful in 49s
Fixes ENOTFOUND api.wren.co error by adding Google DNS servers (8.8.8.8, 8.8.4.4)
to the backend container configuration.
2025-10-31 12:41:07 +01:00
Matt
0192c9b3e5 Fix nginx backend proxy port from 3001 to 3801
Backend container is exposed on host port 3801, not 3001.
This was preventing webhooks and API calls from reaching the backend.
2025-10-31 12:36:49 +01:00
Matt
e5dced48d2 Add comprehensive debugging logs for state restoration troubleshooting
All checks were successful
Build and Push Docker Images / docker (push) Successful in 48s
2025-10-30 16:56:22 +01:00
Matt
cb9c9f8291 Fix calculator state persistence after browser back from Stripe
All checks were successful
Build and Push Docker Images / docker (push) Successful in 48s
Root causes fixed:
1. State only initialized on mount, not on navigation - Added useEffect to restore offset state when navigating to calculator page
2. CheckoutCancel auto-redirected too quickly (100ms) - Removed auto-redirect, user manually clicks Try Again
3. No restoration logic existed - New useEffect watches currentPage and savedState, restores when showOffsetOrder=true

Changes:
- App.tsx: Added restoration useEffect that triggers when currentPage='calculator' and savedState has offset state
- App.tsx: Added console logs for debugging state restoration
- CheckoutCancel.tsx: Removed auto-redirect useEffect and useEffect import
- CheckoutSuccess.tsx: Already correctly clears state on successful payment (verified)

User flow now:
1. Fill calculator → proceeds to offset order (state saved)
2. Click Proceed to Payment → Stripe checkout
3. Click browser back → lands on cancel page
4. Click Try Again → navigates to calculator
5. App detects savedState.showOffsetOrder=true → restores offset order screen with preserved data
2025-10-30 16:48:53 +01:00
Matt
569cf84cde Add comprehensive server-side logging for Wren API calls
All checks were successful
Build and Push Docker Images / docker (push) Successful in 49s
- Added detailed request/response logging for createWrenOffsetOrder
- Added detailed request/response logging for getWrenOffsetOrder
- Logs include timestamp, duration, status, parameters, and full responses
- API token is masked for security (shows first 8 and last 4 chars)
- Logs will appear in Docker container logs with [WREN API SERVER] prefix
- Makes it easier to track and debug Wren API calls from webhooks
2025-10-30 16:40:27 +01:00
Matt
f35e82c72a Enable automatic Stripe receipt emails after payment
All checks were successful
Build and Push Docker Images / docker (push) Successful in 51s
- Added payment_intent_data.receipt_email to checkout session
- Stripe will now automatically send receipt emails to customers after successful payment
2025-10-30 16:32:50 +01:00
Matt
09bbb804e5 Persist offset order state for browser back navigation from Stripe
All checks were successful
Build and Push Docker Images / docker (push) Successful in 50s
- Extended CalculatorState interface to include offset order fields (showOffsetOrder, offsetTons, monetaryAmount, calculatorTypeUsed)
- App.tsx now saves offset state to localStorage when user proceeds to offset order
- App.tsx restores offset state from localStorage on mount
- App.tsx clears offset state when user navigates back from offset order
- Fixes issue where browser back button from Stripe returns to calculator input instead of offset order screen
2025-10-30 16:31:30 +01:00
Matt
b725e7240a Round tons to 2 decimal places in Stripe checkout display
All checks were successful
Build and Push Docker Images / docker (push) Successful in 50s
2025-10-30 16:23:34 +01:00
Matt
e21756d7f0 Fix custom amount tons calculation and payment link failure
All checks were successful
Build and Push Docker Images / docker (push) Successful in 47s
Root Cause:
When users entered a custom monetary amount, the tons calculation was broken:
- actualOffsetTons was calculated as (tons * offsetPercentage) / 100
- When monetaryAmount was provided, tons was 0, so actualOffsetTons = 0
- This caused tons display to show 0 and payment link to fail (0 tons invalid)

Fix Applied:
1. Moved roundedPricePerTon calculation earlier in component lifecycle
2. Calculate baseTons from monetaryAmount when provided:
   baseTons = monetaryAmount / roundedPricePerTon
3. Then apply offset percentage: actualOffsetTons = (baseTons * offsetPercentage) / 100

Example:
- User enters $1,000 custom amount
- Portfolio price = $238/ton (rounded up)
- baseTons = 1000 / 238 = 4.20 tons
- With 100% offset: actualOffsetTons = 4.20 tons
- Payment link now passes 4.20 tons to Stripe ✓

Files Modified:
- src/components/OffsetOrder.tsx
- src/components/MobileOffsetOrder.tsx

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-30 15:40:49 +01:00
Matt
bda4a84bce Auto-redirect to calculator when checkout is cancelled
All checks were successful
Build and Push Docker Images / docker (push) Successful in 49s
Updated CheckoutCancel component to automatically redirect to the calculator
page after 100ms when the component mounts. This ensures:

- Users are immediately taken back to the calculator with their saved state
- No need to manually click "Try Again" button
- Calculator state is preserved from localStorage
- URL properly updates to /calculator via existing handleNavigate logic

User experience:
1. User cancels Stripe checkout
2. Stripe redirects to /checkout/cancel
3. CheckoutCancel component mounts and immediately redirects to calculator
4. Calculator displays with all previously entered values restored

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-30 15:31:34 +01:00
Matt
319db3627c Update receipt logo to match homepage branding
All checks were successful
Build and Push Docker Images / docker (push) Successful in 44s
Changed receipt page logo from /puffin-logo.svg to /puffinOffset.webp
to match the logo used on the homepage for consistent branding.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-30 15:06:23 +01:00
Matt
b5847b573b Enable business payments and bank transfers in Stripe checkout
All checks were successful
Build and Push Docker Images / docker (push) Successful in 45s
Payment Methods Added:
- ACH bank transfers (us_bank_account) for US customers
- Stripe Link for saved payment methods including bank accounts
- Kept credit/debit card payments

Business Payment Features:
- customer_creation: 'always' - Creates Stripe customer for each payment
- billing_address_collection: 'required' - Collects full billing address
- phone_number_collection: Enabled for contact information
- tax_id_collection: Enabled for business tax ID (VAT, EIN, etc.)

Benefits:
- Businesses can now enter their tax ID for invoicing
- Customers can pay via bank transfer (ACH) as well as cards
- Full billing information collected for business records
- Stripe Link provides one-click checkout for returning customers

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-30 15:05:04 +01:00
Matt
a0bc75493d Update Stripe checkout processing fee display to 3%
All checks were successful
Build and Push Docker Images / docker (push) Successful in 45s
- Changed line item name from "Processing Fee (5%)" to "Processing Fee (3%)"
- This updates what customers see on the Stripe checkout page
- Backend constant was already updated to 0.03 in previous commit
- Frontend receipt page was already showing 3%
- All processing fee displays now consistently show 3%

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-30 15:02:35 +01:00
Matt
6753337135 Add numerical formatting to custom amount field in calculators
All checks were successful
Build and Push Docker Images / docker (push) Successful in 52s
- Updated TripCalculator custom amount field to use formatNumber helper
- Updated MobileCalculator custom amount field to use formatNumber helper
- Changed input type from "number" to "text" to display formatted values
- Strip commas when converting to numbers for calculations
- Custom amount inputs now display thousand separators for better readability

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-30 14:58:10 +01:00
Matt
0b2d8685d8 Add comprehensive console logging for Wren API calls
All checks were successful
Build and Push Docker Images / docker (push) Successful in 48s
- Added detailed request/response logging for getPortfolios()
- Added detailed request/response logging for createOffsetOrder()
- Logs include timestamps, durations, request parameters, and response details
- Visual indicators: 🔵 for info,  for success,  for errors, ⚠️ for warnings
- Helps track API calls and debug issues in production console logs

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-30 14:49:15 +01:00
Matt
8b92e1673c Change processing fee from 5% to 3%
All checks were successful
Build and Push Docker Images / docker (push) Successful in 49s
## Changes Made:

### Backend (server/routes/checkout.js):
- Changed PROCESSING_FEE_PERCENT from 0.05 to 0.03
- Now calculates 3% fee instead of 5%

### Frontend (src/pages/CheckoutSuccess.tsx):
- Updated display text from "Processing Fee (5%)" to "Processing Fee (3%)"
- Receipt now shows correct percentage

## Impact:
- All new orders will have 3% processing fee
- Calculation, display, and Stripe charges all consistent at 3%

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-30 14:38:01 +01:00
Matt
d6896fa591 Fix receipt page display issues
All checks were successful
Build and Push Docker Images / docker (push) Successful in 47s
## Issues Fixed:

1. **Better Icon**: Replaced bell icon with leaf/plant icon for carbon offset
   - More appropriate visual representation

2. **Currency Formatting**: Added thousand separators to all amounts
   - Added formatCurrency() helper function
   - Total now shows "$13,414.12" instead of "$13414.12"
   - Applied to base amount, processing fee, and total

3. **Print Functionality**: Fixed empty page printing
   - Removed "no-print" class from outer wrapper
   - Only interactive elements (buttons) hidden when printing
   - Receipt content now prints properly

4. **Status Display**: Shows "Confirmed" immediately after payment
   - Uses Stripe session.paymentStatus for just-completed payments
   - Falls back to order.status for older orders
   - Prevents showing "Processing" when payment is already complete

## Technical Changes:
- CheckoutSuccess.tsx (line 27-32): Added formatCurrency helper
- CheckoutSuccess.tsx (line 120-121): Use Stripe payment status
- CheckoutSuccess.tsx (line 145): Removed no-print from wrapper
- CheckoutSuccess.tsx (line 208-210): Changed to leaf SVG icon
- CheckoutSuccess.tsx (lines 228, 234, 241): Use formatCurrency()

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-30 14:21:41 +01:00
Matt
e8d47f0fb3 Fix pricing discrepancy: use rounded-up price in Stripe checkout
All checks were successful
Build and Push Docker Images / docker (push) Successful in 48s
## Problem
- Calculator displayed rounded-up price per ton (e.g., $238/ton)
- Stripe checkout used exact Wren API price (e.g., $237.20/ton)
- This caused confusion when checkout amount differed from calculator

Example with 20,000 liters (53.9 tons):
- Calculator showed: 53.9 × $238 = $12,819
- Stripe charged: 53.9 × $237.20 = $12,775.35

## Solution
Modified both OffsetOrder.tsx and MobileOffsetOrder.tsx to:
- Keep displaying rounded-up price (Math.ceil) to users
- Pass roundedPricePerTon to Stripe checkout session
- Ensures calculator and Stripe always match exactly

## Changes
- OffsetOrder.tsx (line 170): Pass roundedPricePerTon to createCheckoutSession
- MobileOffsetOrder.tsx (lines 207-218): Add rounding to price display and calculations

Now the price users see is exactly what they pay.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-30 14:17:14 +01:00
Matt
deb4351e21 Enhance UX: Remove calculator comparisons and redesign receipt page
All checks were successful
Build and Push Docker Images / docker (push) Successful in 51s
## Changes Made:

### 1. Remove Carbon Impact Comparisons from Calculator Pages
- Removed from OffsetOrder.tsx (lines 532-542)
- Removed from MobileOffsetOrder.tsx (lines 429-432)
- Keep comparisons ONLY on CheckoutSuccess receipt page per user request

### 2. Comprehensive CheckoutSuccess Page Redesign
- Add Puffin logo prominently at top of receipt
- Implement status mapping: paid/fulfilled → "Confirmed", pending → "Processing"
- Add comprehensive print CSS (@media print rules)
- Hide interactive elements (buttons) when printing
- Optimize layout and spacing for printed receipt
- Professional receipt aesthetics with enhanced design:
  * Beautiful gradient header with logo
  * Highlighted carbon offset display with icon
  * Enhanced pricing breakdown section
  * Better typography and spacing throughout
  * Professional metadata section with date
  * Improved button styling with gradients and hover effects
  * Email confirmation notice with icon
  * Footer with contact information

### Benefits:
- Cleaner calculator UX (comparisons only on success)
- Professional printable receipt
- Clear "Confirmed" status (not "PENDING")
- Beautiful modern design that matches brand

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-30 14:09:00 +01:00
Matt
043cdf07b3 Fix state persistence: save state before navigation
All checks were successful
Build and Push Docker Images / docker (push) Successful in 47s
Issue: Calculator inputs were not being saved before navigating to checkout
because navigation happened synchronously before useEffect could run.

Solution: Call saveState() directly in handleCalculate before navigation
in both TripCalculator and MobileCalculator components.

This ensures calculator inputs (fuel amount, distance, etc.) are properly
preserved when users click "Calculate Impact" and then cancel checkout.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-30 14:02:28 +01:00
Matt
5e642794d8 Implement calculator state persistence and fix checkout navigation
All checks were successful
Build and Push Docker Images / docker (push) Successful in 49s
Features:
- Add useCalculatorState hook with localStorage persistence and 1-hour expiry
- State persists through page reloads and Stripe checkout redirects
- Automatically clears state on successful payment (paid/fulfilled status)

Navigation fixes:
- Fix white page issues on checkout success/cancel pages
- Replace <a> links with button handlers for proper state-based routing
- Pass navigation handlers from App.tsx to checkout pages

State persistence integration:
- TripCalculator: Save/restore calculator inputs (fuel, distance, custom)
- MobileCalculator: Full state persistence for mobile app route
- OffsetOrder: Persist offset percentage and portfolio selection
- MobileOffsetOrder: Persist offset percentage for mobile flow

Carbon impact comparisons:
- Add varied carbon impact comparisons with random selection
- Display 3 comparisons in preview mode, 5 in success mode
- Categories: cars, flights, trees, streaming, homes

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-30 13:55:51 +01:00
Matt
f7196881d2 Change processing fee from 3% to 5%
All checks were successful
Build and Push Docker Images / docker (push) Successful in 47s
Updated PROCESSING_FEE_PERCENT from 0.03 to 0.05 and updated the
line item description in Stripe checkout from "Processing Fee (3%)"
to "Processing Fee (5%)".

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-30 13:16:05 +01:00
Matt
9c7e65b894 Round prices UP and change portfolio name to "Puffin Portfolio"
All checks were successful
Build and Push Docker Images / docker (push) Successful in 49s
Changes:
- Changed portfolio description from "Portfolio {ID}" to "Puffin Portfolio"
- Round pricePerTon UP using Math.ceil() instead of exact decimals
- Round total cost UP using Math.ceil()

Example: $237.19 per ton → $238 per ton

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-30 13:05:52 +01:00
Matt
09e3c13eaf Fix pricing calculation by passing pricePerTon from frontend to backend
All checks were successful
Build and Push Docker Images / docker (push) Successful in 51s
The backend was using a hardcoded PORTFOLIO_PRICING map that only had prices
for portfolios 1, 2, 3 (defaulting to $18/ton for others). When Wren API
returns Portfolio ID 37 at $237/ton, the backend was calculating with $18/ton
instead, causing massive pricing discrepancies.

Changes:
- Frontend now passes pricePerTon (from Wren API) in checkout request
- Backend uses the received pricePerTon instead of lookup table
- Removed dependency on hardcoded PORTFOLIO_PRICING map
- Added validation for pricePerTon parameter

Example fix:
- Before: 0.03 tons × $18/ton = $0.54 (wrong!)
- After: 0.03 tons × $237/ton = $7.11 (correct!)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-30 13:00:13 +01:00
Matt
0177707921 Fix portfolio ID validation to accept Wren API portfolio IDs
All checks were successful
Build and Push Docker Images / docker (push) Successful in 42s
Changed backend validation from hardcoded [1, 2, 3] to accept any positive
integer portfolio ID. The Wren API returns portfolios with their real IDs
(not limited to 1-3), so the backend needs to accept dynamic portfolio IDs.

- Changed validation to check for positive integers instead of [1, 2, 3]
- Added logging to show which portfolio ID is being processed
- Pricing calculation already has fallback to $18/ton for unknown IDs

This fixes the "Invalid portfolio ID" error when trying to checkout.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-30 12:54:45 +01:00
Matt
5ec24af338 Fix service worker cache preventing code updates and change fallback URL
All checks were successful
Build and Push Docker Images / docker (push) Successful in 44s
- Bumped service worker cache version to v2 to force cache invalidation
- Added activate event to explicitly clear old caches
- Enhanced service worker logging with update checking
- Changed API fallback URL from localhost:3001 to https://puffinoffset.com/api

This fixes the issue where new code deployments were not being used due to
service worker caching the old JavaScript with the timing bug.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-30 12:50:15 +01:00
Matt
8d9f65868a CRITICAL FIX: Lazy evaluate API URLs at request time, not module load time
All checks were successful
Build and Push Docker Images / docker (push) Successful in 43s
ROOT CAUSE (Zen Debug Investigation):
The API_BASE_URL constant was being evaluated when the module loaded,
BEFORE window.env was populated by env-config.js. This caused the app
to permanently use the fallback localhost:3001 instead of reading the
production URL from the .env file.

TIMING ISSUE:
1. Browser loads index.html
2. env-config.js loads (creates window.env)
3. main.tsx loads as ES module (DEFERRED)
4. Module imports checkoutClient → const API_BASE_URL executes
5. window.env NOT READY YET → falls back to localhost:3001
6. API_BASE_URL locked to wrong value forever

THE FIX:
Changed from module-level constants to request-time lazy evaluation.
Now getApiBaseUrl() is called WHEN THE API REQUEST HAPPENS, not when
the module loads. At that point window.env is guaranteed to exist.

FILES CHANGED:
- src/api/checkoutClient.ts:
  * Removed constant API_BASE_URL
  * All 3 functions now call getApiBaseUrl() at request time
  * Added logging to show which URL is being used

- src/api/aisClient.ts:
  * Removed constant API_KEY
  * Added getApiKey() function with lazy evaluation
  * Same pattern for consistency and future-proofing

This fixes: "FetchEvent for http://localhost:3001 resulted in network error"
Now uses: https://puffinoffset.com/api (from .env file)

🔒 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-30 12:43:16 +01:00
Matt
3aac87de50 Fix API_BASE_URL to use runtime configuration
All checks were successful
Build and Push Docker Images / docker (push) Successful in 45s
CRITICAL FIX: Checkout was failing because API calls went to localhost

The checkoutClient was hardcoded to use import.meta.env.VITE_API_BASE_URL
which is build-time only. In production, this defaulted to localhost:3001
instead of the actual backend URL.

Changed to read from window.env.API_BASE_URL (runtime config) first,
then fall back to build-time config. This matches the pattern used
in src/utils/config.ts.

Frontend will now correctly connect to:
- Development: http://localhost:3001
- Production: https://puffinoffset.com/api (from .env file)

Fixes network error: "Failed to fetch" when creating checkout session

🔒 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-30 12:36:57 +01:00
Matt
0d7ac4b1de Add missing logger.info() and logger.debug() methods
All checks were successful
Build and Push Docker Images / docker (push) Successful in 43s
FIX: TypeError: z.info is not a function in production

The logger object was missing .info() and .debug() methods that were
being called in OffsetOrder.tsx and other components. This caused
checkout to fail in production with "z.info is not a function" error.

Added:
- logger.info() - Info level logging (dev only)
- logger.debug() - Debug level logging (dev only)

All logger methods now follow the same pattern:
- log, info, warn, debug: Only log in development
- error: Always log (production + development)

🔒 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-30 12:34:07 +01:00
Matt
04bfef4391 Fix frontend runtime environment configuration
All checks were successful
Build and Push Docker Images / docker (push) Successful in 46s
CRITICAL FIX:
- Frontend now loads environment variables at runtime instead of build time
- This allows changing configuration without rebuilding the Docker image

CHANGES:
- Add env-config.js script loader to index.html
- Update env.sh to use correct path for serve (/app/dist)
- Update Dockerfile to run env.sh before starting serve
- Add VITE_STRIPE_PUBLISHABLE_KEY to docker-compose environment

HOW IT WORKS:
1. env.sh reads VITE_* variables from container environment
2. Generates /app/dist/env-config.js with window.env object
3. index.html loads env-config.js before React app
4. src/utils/config.ts reads from window.env at runtime

This fixes the "Missing required environment variable: WREN_API_TOKEN" error
because the frontend can now access environment variables passed to the container.

🔒 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-30 12:30:29 +01:00
Matt
bc9e2d3782 Implement comprehensive Stripe security fixes and production deployment
All checks were successful
Build and Push Docker Images / docker (push) Successful in 1m22s
CRITICAL SECURITY FIXES:
- Add webhook secret validation to prevent signature bypass
- Implement idempotency protection across all webhook handlers
- Add atomic database updates to prevent race conditions
- Improve CORS security with origin validation and logging
- Remove .env from git tracking to protect secrets

STRIPE INTEGRATION:
- Add support for checkout.session.expired webhook event
- Add Stripe publishable key to environment configuration
- Fix webhook handlers with proper idempotency checks
- Update Order model with atomic updatePaymentAndStatus method
- Add comprehensive logging for webhook processing

DEPLOYMENT ARCHITECTURE:
- Split into two Docker images (frontend-latest, backend-latest)
- Update CI/CD to build separate frontend and backend images
- Configure backend on port 3801 (internal 3001)
- Add production-ready docker-compose.yml
- Remove redundant docker-compose.portainer.yml
- Update nginx configuration for both frontend and backend

DOCUMENTATION:
- Add PRODUCTION-SETUP.md with complete deployment guide
- Add docs/stripe-security-fixes.md with security audit details
- Add docs/stripe-checkout-sessions.md with integration docs
- Add docs/stripe-webhooks.md with webhook configuration
- Update .env.example with all required variables including Stripe publishable key

CONFIGURATION:
- Consolidate to single .env.example template
- Update .gitignore to protect all .env variants
- Add server/Dockerfile for backend container
- Update DEPLOYMENT.md with new architecture

🔒 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-30 12:18:57 +01:00
Matt
97919cd4ac Update Stripe API version to 2025-10-29.clover
All checks were successful
Build and Push Docker Image / docker (push) Successful in 48s
Updated the Stripe API version from 2024-12-18.acacia to the latest
version 2025-10-29.clover as requested.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-30 11:22:19 +01:00
Matt
9e621042db Add WREN_DRY_RUN environment variable for safe testing
All checks were successful
Build and Push Docker Image / docker (push) Successful in 42s
Prevent accidental creation of real carbon offsets during development:
- Add WREN_DRY_RUN environment variable (default: true for dev)
- Update webhook fulfillment to use env variable instead of hardcoded value
- Log warning when in dry run mode for visibility
- Production deployments should set WREN_DRY_RUN=false

This allows safe testing with Stripe test cards without creating real Wren offset orders.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-29 22:07:06 +01:00
Matt
06733cb2cb Integrate Stripe Checkout and add comprehensive UI enhancements
All checks were successful
Build and Push Docker Image / docker (push) Successful in 42s
## Stripe Payment Integration
- Add Express.js backend server with Stripe Checkout Sessions
- Create SQLite database for order tracking
- Implement Stripe webhook handlers for payment events
- Integrate with Wren Climate API for carbon offset fulfillment
- Add CheckoutSuccess and CheckoutCancel pages
- Create checkout API client for frontend
- Update OffsetOrder component to redirect to Stripe Checkout
- Add processing fee calculation (3% of base amount)
- Implement order status tracking (pending → paid → fulfilled)

Backend (server/):
- Express server with CORS and middleware
- SQLite database with Order schema
- Stripe configuration and client
- Order CRUD operations model
- Checkout session creation endpoint
- Webhook handler for payment confirmation
- Wren API client for offset fulfillment

Frontend:
- CheckoutSuccess page with order details display
- CheckoutCancel page with retry encouragement
- Updated OffsetOrder to use Stripe checkout flow
- Added checkout routes to App.tsx
- TypeScript interfaces for checkout flow

## Visual & UX Enhancements
- Add CertificationBadge component for project verification status
- Create PortfolioDonutChart for visual portfolio allocation
- Implement RadialProgress for percentage displays
- Add reusable form components (FormInput, FormTextarea, FormSelect, FormFieldWrapper)
- Refactor OffsetOrder with improved layout and animations
- Add offset percentage slider with visual feedback
- Enhance MobileOffsetOrder with better responsive design
- Improve TripCalculator with cleaner UI structure
- Update CurrencySelect with better styling
- Add portfolio distribution visualization
- Enhance project cards with hover effects and animations
- Improve color palette and gradient usage throughout

## Configuration
- Add VITE_API_BASE_URL environment variable
- Create backend .env.example template
- Update frontend .env.example with API URL
- Add Stripe documentation references

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-29 21:45:14 +01:00
Matt
3a33221130 Hide debug logs in production
All checks were successful
Build and Push Docker Image / docker (push) Successful in 48s
- Create logger utility that only logs in development mode
- Update wrenClient.ts to use logger instead of console.log/warn
- Update OffsetOrder.tsx to use logger for debug messages
- Update config.ts to only log environment loading in dev mode
- Keeps console.error for actual errors (always shown)

Fixes: Console clutter in production deployment

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-29 14:58:22 +01:00
Matt
17c7a8f580 Support environment variables in env.sh for Portainer deployment
All checks were successful
Build and Push Docker Image / docker (push) Successful in 50s
- Add fallback to read from container environment variables
- Maintains backward compatibility with .env file approach
- Allows setting VITE_* vars directly in Portainer UI

This enables flexible deployment:
- File-based: Mount .env file (docker-compose)
- Env-based: Set env vars in Portainer container settings

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-29 14:52:39 +01:00
Matt
077deb4194 Remove container specification - use runner's default ubuntu-latest
All checks were successful
Build and Push Docker Image / docker (push) Successful in 3m31s
Match working workflow pattern from other server.
Let runner provide Docker environment directly.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-29 14:28:21 +01:00
Matt
b9c03fa87e Remove driver specification from Buildx setup
Some checks failed
Build and Push Docker Image / docker (push) Failing after 11s
Let Buildx use default driver with --privileged container.
The 'docker' driver caused endpoint property errors.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-29 14:27:34 +01:00
Matt
aec179ec49 Remove duplicate Docker socket mount from workflow
Some checks failed
Build and Push Docker Image / docker (push) Failing after 12s
Runner already mounts /var/run/docker.sock automatically.
Keep only --privileged flag to allow socket access.

Fixes: Error response from daemon: Duplicate mount point

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-29 14:26:49 +01:00
Matt
41f8fc1c76 Fix Docker socket permission issue in CI/CD workflow
Some checks failed
Build and Push Docker Image / docker (push) Failing after 1s
- Add --privileged flag and explicit Docker socket mount to container
- Configure Buildx to use 'docker' driver instead of 'docker-container'
- This avoids nested container permission issues while using full Ubuntu image

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-29 14:25:30 +01:00
Matt
189255a7d8 Use repository variables for registry configuration
Some checks failed
Build and Push Docker Image / docker (push) Failing after 14s
Match the working workflow pattern from other server by using
repository variables instead of hardcoded values:

- REGISTRY_HOST (code.puffinoffset.com)
- REGISTRY_USERNAME (matt)
- IMAGE_NAME (puffin-app)
- REGISTRY_TOKEN (secret with write:package permission)

This makes the workflow portable and matches the proven
working configuration.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-29 14:22:32 +01:00