Initial commit

This commit is contained in:
michael
2026-01-22 13:55:55 -08:00
commit d05c02f556
31 changed files with 17832 additions and 0 deletions

186
src/hooks/useScan.ts Normal file
View File

@@ -0,0 +1,186 @@
/**
* useScan Hook
* React hook for managing NFC scan state and operations
*/
import {useState, useCallback, useEffect, useRef} from 'react';
import {nfcManager} from '../services/nfc';
import type {ScanState, RawTagData, ScanError, NFCStatus} from '../types/nfc';
export interface UseScanResult {
/** Current scan state */
state: ScanState;
/** Scanned tag data (when state is 'success') */
tag: RawTagData | null;
/** Scan error (when state is 'error') */
error: ScanError | null;
/** NFC status (supported and enabled) */
nfcStatus: NFCStatus;
/** Start scanning for a tag */
startScan: () => Promise<void>;
/** Cancel ongoing scan */
cancelScan: () => Promise<void>;
/** Reset state to idle */
reset: () => void;
/** Open device NFC settings */
openSettings: () => Promise<void>;
}
/**
* Hook for managing NFC scanning
*/
export function useScan(): UseScanResult {
const [state, setState] = useState<ScanState>('idle');
const [tag, setTag] = useState<RawTagData | null>(null);
const [error, setError] = useState<ScanError | null>(null);
const [nfcStatus, setNfcStatus] = useState<NFCStatus>({
isSupported: false,
isEnabled: false,
});
// Track if component is mounted to prevent state updates after unmount
const isMounted = useRef(true);
// Track if a scan is in progress
const scanInProgress = useRef(false);
// Initialize NFC and check status
useEffect(() => {
let mounted = true;
async function initialize() {
await nfcManager.init();
if (mounted) {
const status = await nfcManager.getStatus();
setNfcStatus(status);
}
}
initialize();
return () => {
mounted = false;
isMounted.current = false;
// Cleanup on unmount
nfcManager.cancelScan();
};
}, []);
// Start scanning for a tag
const startScan = useCallback(async () => {
// Prevent multiple concurrent scans
if (scanInProgress.current) {
return;
}
scanInProgress.current = true;
setState('scanning');
setTag(null);
setError(null);
try {
// Check NFC status first
const status = await nfcManager.getStatus();
if (isMounted.current) {
setNfcStatus(status);
}
if (!status.isSupported) {
if (isMounted.current) {
setState('error');
setError({
type: 'NFC_NOT_SUPPORTED',
message: 'NFC is not supported on this device',
});
}
return;
}
if (!status.isEnabled) {
if (isMounted.current) {
setState('error');
setError({
type: 'NFC_NOT_ENABLED',
message: 'Please enable NFC in your device settings',
});
}
return;
}
// Perform the scan
const result = await nfcManager.scanTag();
if (!isMounted.current) {
return;
}
if (result.error) {
// Don't show error for cancellation
if (result.error.type === 'SCAN_CANCELLED') {
setState('idle');
} else {
setState('error');
setError(result.error);
}
} else if (result.tag) {
setState('success');
setTag(result.tag);
} else {
setState('error');
setError({
type: 'UNKNOWN',
message: 'No tag data received',
});
}
} catch (err) {
if (isMounted.current) {
setState('error');
setError({
type: 'UNKNOWN',
message: err instanceof Error ? err.message : 'An unknown error occurred',
originalError: err,
});
}
} finally {
scanInProgress.current = false;
}
}, []);
// Cancel ongoing scan
const cancelScan = useCallback(async () => {
await nfcManager.cancelScan();
scanInProgress.current = false;
if (isMounted.current) {
setState('idle');
setError(null);
}
}, []);
// Reset state to idle
const reset = useCallback(() => {
setState('idle');
setTag(null);
setError(null);
}, []);
// Open device NFC settings
const openSettings = useCallback(async () => {
await nfcManager.openNFCSettings();
// Re-check status after returning from settings
const status = await nfcManager.getStatus();
if (isMounted.current) {
setNfcStatus(status);
}
}, []);
return {
state,
tag,
error,
nfcStatus,
startScan,
cancelScan,
reset,
openSettings,
};
}