Implement QR code auto-fill and auto-navigation for calculator
Some checks failed
Build and Push Docker Images / docker (push) Failing after 2m13s

- 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 <noreply@anthropic.com>
This commit is contained in:
Matt 2025-11-03 21:30:41 +01:00
parent e76a650d4e
commit f57ceb7b8f
3 changed files with 59 additions and 13 deletions

View File

@ -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<number | undefined>();
@ -59,10 +62,17 @@ export function CalculatorClient() {
</div>
<div className="flex flex-col items-center w-full max-w-4xl space-y-8">
{hasQRData ? (
<QRCalculatorLoader
vesselData={sampleVessel}
onOffsetClick={handleOffsetClick}
/>
) : (
<TripCalculator
vesselData={sampleVessel}
onOffsetClick={handleOffsetClick}
/>
)}
</div>
</div>
);

View File

@ -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();

View File

@ -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<QRCalculatorData | null>(null);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState<string | null>(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;
}