From f57ceb7b8f9ee524aa5a6f6884c297ce48b48b52 Mon Sep 17 00:00:00 2001 From: Matt Date: Mon, 3 Nov 2025 21:30:41 +0100 Subject: [PATCH] Implement QR code auto-fill and auto-navigation for calculator MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Update useQRDecoder hook to use Next.js useSearchParams() for SSR compatibility - Add auto-navigation logic to TripCalculator that automatically calculates and navigates to offset page when QR data is present - Add QR detection to CalculatorClient to conditionally render QRCalculatorLoader vs TripCalculator - Support all three calculation types: fuel, distance, and custom amount - Add 500ms delay before navigation to allow UI to render This completes the QR code flow where scanning a QR code now automatically: 1. Pre-fills calculator form with data 2. Calculates carbon footprint 3. Navigates to offset purchase page 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- components/CalculatorClient.tsx | 18 +++++++++++++---- src/components/TripCalculator.tsx | 33 +++++++++++++++++++++++++++++++ src/hooks/useQRDecoder.ts | 21 +++++++++++--------- 3 files changed, 59 insertions(+), 13 deletions(-) diff --git a/components/CalculatorClient.tsx b/components/CalculatorClient.tsx index d2d4549..a9fd8b3 100644 --- a/components/CalculatorClient.tsx +++ b/components/CalculatorClient.tsx @@ -2,7 +2,9 @@ import { useState } from 'react'; import { TripCalculator } from '../src/components/TripCalculator'; +import { QRCalculatorLoader } from '../src/components/QRCalculatorLoader'; import { OffsetOrder } from '../src/components/OffsetOrder'; +import { useHasQRData } from '../src/hooks/useQRDecoder'; import type { VesselData } from '../src/types'; // Sample vessel data (same as in old App.tsx) @@ -16,6 +18,7 @@ const sampleVessel: VesselData = { }; export function CalculatorClient() { + const hasQRData = useHasQRData(); // Check if URL contains QR code data const [showOffsetOrder, setShowOffsetOrder] = useState(false); const [offsetTons, setOffsetTons] = useState(0); const [monetaryAmount, setMonetaryAmount] = useState(); @@ -59,10 +62,17 @@ export function CalculatorClient() {
- + {hasQRData ? ( + + ) : ( + + )}
); diff --git a/src/components/TripCalculator.tsx b/src/components/TripCalculator.tsx index bddd11c..9f1df98 100644 --- a/src/components/TripCalculator.tsx +++ b/src/components/TripCalculator.tsx @@ -1,3 +1,5 @@ +'use client'; + import { useState, useCallback, useEffect } from 'react'; import { Leaf, Droplet } from 'lucide-react'; import { motion, AnimatePresence } from 'framer-motion'; @@ -75,6 +77,37 @@ export function TripCalculator({ vesselData, onOffsetClick, initialData }: Props initialData?.customAmount?.toString() || savedState?.customAmount || '' ); + // Auto-navigate when QR data is present + useEffect(() => { + if (initialData && onOffsetClick) { + // Automatically calculate and navigate based on calculation type + if (initialData.calculationType === 'distance' && initialData.distance && initialData.speed && initialData.fuelRate) { + const estimate = calculateTripCarbon( + initialData.distance, + initialData.speed, + initialData.fuelRate + ); + // Small delay to allow UI to render before navigating + setTimeout(() => { + onOffsetClick(estimate.co2Emissions); + }, 500); + } else if (initialData.calculationType === 'fuel' && initialData.fuelAmount && initialData.fuelUnit) { + const co2Emissions = calculateCarbonFromFuel( + initialData.fuelAmount, + initialData.fuelUnit === 'gallons' + ); + setTimeout(() => { + onOffsetClick(co2Emissions); + }, 500); + } else if (initialData.calculationType === 'custom' && initialData.customAmount) { + // For custom amount, pass the monetary value directly + setTimeout(() => { + onOffsetClick(0, initialData.customAmount); + }, 500); + } + } + }, [initialData, onOffsetClick]); + const handleCalculate = useCallback((e: React.FormEvent) => { e.preventDefault(); diff --git a/src/hooks/useQRDecoder.ts b/src/hooks/useQRDecoder.ts index 1bec2ed..39a15ba 100644 --- a/src/hooks/useQRDecoder.ts +++ b/src/hooks/useQRDecoder.ts @@ -3,7 +3,10 @@ * Custom React hook for decoding QR calculator data from URL parameters */ +'use client'; + import { useState, useEffect } from 'react'; +import { useSearchParams } from 'next/navigation'; import { QRCalculatorData } from '../types'; import { extractQRDataFromUrl } from '../utils/qrDataEncoder'; import { validateQRData } from '../utils/qrDataValidator'; @@ -49,6 +52,7 @@ export interface QRDecoderResult { * ``` */ export function useQRDecoder(): QRDecoderResult { + const searchParams = useSearchParams(); const [data, setData] = useState(null); const [isLoading, setIsLoading] = useState(true); const [error, setError] = useState(null); @@ -57,11 +61,8 @@ export function useQRDecoder(): QRDecoderResult { useEffect(() => { const decodeQRData = () => { try { - // Get URL search params - const searchParams = new URLSearchParams(window.location.search); - // Check if QR parameter exists - const qrParam = searchParams.get('qr'); + const qrParam = searchParams?.get('qr'); if (!qrParam) { setHasQRData(false); setIsLoading(false); @@ -71,7 +72,9 @@ export function useQRDecoder(): QRDecoderResult { setHasQRData(true); // Extract and decode QR data - const decodedData = extractQRDataFromUrl(searchParams); + // Need to convert ReadonlyURLSearchParams to URLSearchParams for compatibility + const urlSearchParams = new URLSearchParams(searchParams?.toString()); + const decodedData = extractQRDataFromUrl(urlSearchParams); if (!decodedData) { setError('Failed to decode QR data'); @@ -100,7 +103,7 @@ export function useQRDecoder(): QRDecoderResult { }; decodeQRData(); - }, []); // Run only once on mount + }, [searchParams]); // Re-run when search params change return { data, @@ -128,12 +131,12 @@ export function useQRDecoder(): QRDecoderResult { * ``` */ export function useHasQRData(): boolean { + const searchParams = useSearchParams(); const [hasQRData, setHasQRData] = useState(false); useEffect(() => { - const searchParams = new URLSearchParams(window.location.search); - setHasQRData(searchParams.has('qr')); - }, []); + setHasQRData(searchParams?.has('qr') || false); + }, [searchParams]); return hasQRData; }