Implement QR code auto-fill and auto-navigation for calculator
Some checks failed
Build and Push Docker Images / docker (push) Failing after 2m13s
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:
parent
e76a650d4e
commit
f57ceb7b8f
@ -2,7 +2,9 @@
|
|||||||
|
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { TripCalculator } from '../src/components/TripCalculator';
|
import { TripCalculator } from '../src/components/TripCalculator';
|
||||||
|
import { QRCalculatorLoader } from '../src/components/QRCalculatorLoader';
|
||||||
import { OffsetOrder } from '../src/components/OffsetOrder';
|
import { OffsetOrder } from '../src/components/OffsetOrder';
|
||||||
|
import { useHasQRData } from '../src/hooks/useQRDecoder';
|
||||||
import type { VesselData } from '../src/types';
|
import type { VesselData } from '../src/types';
|
||||||
|
|
||||||
// Sample vessel data (same as in old App.tsx)
|
// Sample vessel data (same as in old App.tsx)
|
||||||
@ -16,6 +18,7 @@ const sampleVessel: VesselData = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export function CalculatorClient() {
|
export function CalculatorClient() {
|
||||||
|
const hasQRData = useHasQRData(); // Check if URL contains QR code data
|
||||||
const [showOffsetOrder, setShowOffsetOrder] = useState(false);
|
const [showOffsetOrder, setShowOffsetOrder] = useState(false);
|
||||||
const [offsetTons, setOffsetTons] = useState(0);
|
const [offsetTons, setOffsetTons] = useState(0);
|
||||||
const [monetaryAmount, setMonetaryAmount] = useState<number | undefined>();
|
const [monetaryAmount, setMonetaryAmount] = useState<number | undefined>();
|
||||||
@ -59,10 +62,17 @@ export function CalculatorClient() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-col items-center w-full max-w-4xl space-y-8">
|
<div className="flex flex-col items-center w-full max-w-4xl space-y-8">
|
||||||
<TripCalculator
|
{hasQRData ? (
|
||||||
vesselData={sampleVessel}
|
<QRCalculatorLoader
|
||||||
onOffsetClick={handleOffsetClick}
|
vesselData={sampleVessel}
|
||||||
/>
|
onOffsetClick={handleOffsetClick}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<TripCalculator
|
||||||
|
vesselData={sampleVessel}
|
||||||
|
onOffsetClick={handleOffsetClick}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
import { useState, useCallback, useEffect } from 'react';
|
import { useState, useCallback, useEffect } from 'react';
|
||||||
import { Leaf, Droplet } from 'lucide-react';
|
import { Leaf, Droplet } from 'lucide-react';
|
||||||
import { motion, AnimatePresence } from 'framer-motion';
|
import { motion, AnimatePresence } from 'framer-motion';
|
||||||
@ -75,6 +77,37 @@ export function TripCalculator({ vesselData, onOffsetClick, initialData }: Props
|
|||||||
initialData?.customAmount?.toString() || savedState?.customAmount || ''
|
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) => {
|
const handleCalculate = useCallback((e: React.FormEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
|
|||||||
@ -3,7 +3,10 @@
|
|||||||
* Custom React hook for decoding QR calculator data from URL parameters
|
* Custom React hook for decoding QR calculator data from URL parameters
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
'use client';
|
||||||
|
|
||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
|
import { useSearchParams } from 'next/navigation';
|
||||||
import { QRCalculatorData } from '../types';
|
import { QRCalculatorData } from '../types';
|
||||||
import { extractQRDataFromUrl } from '../utils/qrDataEncoder';
|
import { extractQRDataFromUrl } from '../utils/qrDataEncoder';
|
||||||
import { validateQRData } from '../utils/qrDataValidator';
|
import { validateQRData } from '../utils/qrDataValidator';
|
||||||
@ -49,6 +52,7 @@ export interface QRDecoderResult {
|
|||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
export function useQRDecoder(): QRDecoderResult {
|
export function useQRDecoder(): QRDecoderResult {
|
||||||
|
const searchParams = useSearchParams();
|
||||||
const [data, setData] = useState<QRCalculatorData | null>(null);
|
const [data, setData] = useState<QRCalculatorData | null>(null);
|
||||||
const [isLoading, setIsLoading] = useState(true);
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
@ -57,11 +61,8 @@ export function useQRDecoder(): QRDecoderResult {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const decodeQRData = () => {
|
const decodeQRData = () => {
|
||||||
try {
|
try {
|
||||||
// Get URL search params
|
|
||||||
const searchParams = new URLSearchParams(window.location.search);
|
|
||||||
|
|
||||||
// Check if QR parameter exists
|
// Check if QR parameter exists
|
||||||
const qrParam = searchParams.get('qr');
|
const qrParam = searchParams?.get('qr');
|
||||||
if (!qrParam) {
|
if (!qrParam) {
|
||||||
setHasQRData(false);
|
setHasQRData(false);
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
@ -71,7 +72,9 @@ export function useQRDecoder(): QRDecoderResult {
|
|||||||
setHasQRData(true);
|
setHasQRData(true);
|
||||||
|
|
||||||
// Extract and decode QR data
|
// 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) {
|
if (!decodedData) {
|
||||||
setError('Failed to decode QR data');
|
setError('Failed to decode QR data');
|
||||||
@ -100,7 +103,7 @@ export function useQRDecoder(): QRDecoderResult {
|
|||||||
};
|
};
|
||||||
|
|
||||||
decodeQRData();
|
decodeQRData();
|
||||||
}, []); // Run only once on mount
|
}, [searchParams]); // Re-run when search params change
|
||||||
|
|
||||||
return {
|
return {
|
||||||
data,
|
data,
|
||||||
@ -128,12 +131,12 @@ export function useQRDecoder(): QRDecoderResult {
|
|||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
export function useHasQRData(): boolean {
|
export function useHasQRData(): boolean {
|
||||||
|
const searchParams = useSearchParams();
|
||||||
const [hasQRData, setHasQRData] = useState(false);
|
const [hasQRData, setHasQRData] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const searchParams = new URLSearchParams(window.location.search);
|
setHasQRData(searchParams?.has('qr') || false);
|
||||||
setHasQRData(searchParams.has('qr'));
|
}, [searchParams]);
|
||||||
}, []);
|
|
||||||
|
|
||||||
return hasQRData;
|
return hasQRData;
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user