Installation & Setup

Get started with CUSS2 React in your project.

1. Install the Package

bash
npm install cuss2-react

2. Import Hooks

import {
  Cuss2Connector,
  useCuss2,
  useBarcodeReader,
  useCardReader,
  useCamera,
  useAnnouncement
} from '@elevationai/cuss2-react';

3. Basic Component Setup

import { Cuss2Connector, Cuss2ConnectionOptions, useCuss2 } from '@elevationai/cuss2-react';

const options: Cuss2ConnectionOptions = {
  clientId: 'your-client-id',
  clientSecret: 'your-client-secret',
  cussUrl: 'cuss-url'
};

function App() {
  return (
    <Cuss2Connector
      options={options}
      onConnect={() => console.log('Connected to CUSS2')}
      onDisconnect={() => console.log('Disconnected from CUSS2')}
      onError={(error) => console.error('Connection error:', error)}
    >
      <KioskApp />
    </Cuss2Connector>
  );
}

function KioskApp() {
  const { state, connected } = useCuss2();

  if (!connected) {
    return (
      <div className="loading">
        <i className="fas fa-spinner fa-spin"></i>
        Connecting to CUSS2 platform...
      </div>
    );
  }

  return (
    <div>
      <h1>CUSS2 Platform Ready (State: {state})</h1>
      {/* Your app content */}
    </div>
  );
}

Basic Connection Management

Learn how to manage CUSS2 platform connections effectively.

Connection with Error Handling

import { useEffect, useCallback } from 'react';
import { useCuss2 } from '@elevationai/cuss2-react';

function ConnectionManager() {
  const { cuss2, connected, state } = useCuss2();

  const handleConnectionError = useCallback((error: any) => {
    // Implement retry logic or offline mode
    setTimeout(() => {
      console.log('Retrying connection...');
      // Retry connection logic would go here
    }, 5000);
  }, []);

  // Monitor connection status
  useEffect(() => {
    if (connected) {
      console.log('Successfully connected to CUSS2 platform');
      // enableKioskFeatures();
    } else {
      // disableKioskFeatures();
      // showOfflineMessage();
    }
  }, [connected]);

  // Monitor platform state changes
  useEffect(() => {
    console.log('CUSS2 Platform State:', state);
  }, [state]);

  const handleRequestState = async (targetState: 'UNAVAILABLE' | 'AVAILABLE' | 'ACTIVE') => {
    if (!cuss2) return;
    try {
      switch (targetState) {
        case 'UNAVAILABLE':
          await cuss2.requestUnavailableState();
          break;
        case 'AVAILABLE':
          await cuss2.requestAvailableState();
          break;
        case 'ACTIVE':
          await cuss2.requestActiveState();
          break;
      }
    } catch (error) {
      console.error(`Failed to request ${targetState} state:`, error);
      setErrors(prev => [`Failed to request ${targetState} state: ${error}`, ...prev].slice(0, 20));
    }
  };

  return (
    <div>
      {connected ? (
        <div>Kiosk State { state } </div>
        <div className="btn-group mb-3" role="group">
          <button
            className="btn btn-outline-warning"
            onClick={() => handleRequestState('UNAVAILABLE')}
            disabled={!connected}
          >
            Request UNAVAILABLE
          </button>
          <button
            className="btn btn-outline-success"
            onClick={() => handleRequestState('AVAILABLE')}
            disabled={!connected}
          >
            Request AVAILABLE
          </button>
          <button
            className="btn btn-outline-primary"
            onClick={() => handleRequestState('ACTIVE')}
            disabled={!connected || state !== 'AVAILABLE'}
          >
            Request ACTIVE
          </button>
        </div>
      ) : (
        <div>Connecting to platform...</div>
      )}
    </div>
  );
}

Component Status Monitoring

Monitor and manage the status of CUSS2 components.

import { useEffect, useMemo } from 'react';
import {
  useCuss2,
  useBarcodeReader,
  useCardReader,
  useBoardingPassPrinter,
  useScale
} from '@elevationai/cuss2-react';

function ComponentStatusMonitor() {
  const { connected } = useCuss2();
  const { device: barcodeReader, enabled: barcodeEnabled } = useBarcodeReader();
  const { device: cardReader, enabled: cardEnabled } = useCardReader();
  const { device: printer, enabled: printerEnabled } = useBoardingPassPrinter();
  const { device: scale, enabled: scaleEnabled } = useScale();

  const componentStatus = useMemo(() => ({
    barcodeReader: { device: barcodeReader, enabled: barcodeEnabled },
    cardReader: { device: cardReader, enabled: cardEnabled },
    printer: { device: printer, enabled: printerEnabled },
    scale: { device: scale, enabled: scaleEnabled }
  }), [barcodeReader, barcodeEnabled, cardReader, cardEnabled, printer, printerEnabled, scale, scaleEnabled]);

  useEffect(() => {
    console.log('Component Status:', componentStatus);

    // Enable features based on available components
    if (componentStatus.barcodeReader.enabled) {
      // enableBarcodeScanning();
    }

    if (componentStatus.cardReader.enabled) {
      // enablePaymentProcessing();
    }

    if (!componentStatus.printer.enabled && componentStatus.printer.device) {
      // showPrinterOfflineWarning();
    }
  }, [componentStatus]);

  // Auto-enable disabled components
  useEffect(() => {
    const componentsToEnable = Object.values(componentStatus).filter(
      c => c.device && !c.enabled
    );

    componentsToEnable.forEach(async ({ device }) => {
      if (device) {
        try {
          await device.enable();
          console.log(`Enabled ${(device as any).deviceType}`);
        } catch (err) {
          console.error(`Failed to enable ${(device as any).deviceType}:`, err);
        }
      }
    });
  }, [componentStatus]);

  return (
    <div>
      <h3>Component Status</h3>
      <ul>
        <li>Barcode Reader: {barcodeEnabled ? 'Enabled' : 'Disabled'}</li>
        <li>Card Reader: {cardEnabled ? 'Enabled' : 'Disabled'}</li>
        <li>Printer: {printerEnabled ? 'Enabled' : 'Disabled'}</li>
        <li>Scale: {scaleEnabled ? 'Enabled' : 'Disabled'}</li>
      </ul>
    </div>
  );
}

Complete Check-In Flow

Build a complete passenger check-in flow with barcode scanning and boarding pass printing.

import { useState, useEffect, useCallback } from 'react';
import {
  useBarcodeReader,
  useDocumentReader,
  useBoardingPassPrinter
} from '@elevationai/cuss2-react';

type CheckInStep = 'welcome' | 'scan' | 'processing' | 'print' | 'complete';

function CheckInFlow() {
  const { device: barcodeReader, data: barcodeData } = useBarcodeReader();
  const { device: documentReader, data: documentData } = useDocumentReader();
  const { device: printer } = useBoardingPassPrinter();

  const [currentStep, setCurrentStep] = useState<CheckInStep>('welcome');
  const [passengerData, setPassengerData] = useState<any>(null);

  const startCheckInFlow = useCallback(() => {
    setCurrentStep('scan');
    console.log('Starting check-in flow');

    // Enable scanning devices
    barcodeReader?.enable().catch(console.error);
    documentReader?.enable().catch(console.error);
  }, [barcodeReader, documentReader]);

  const resetFlow = useCallback(() => {
    setCurrentStep('welcome');
    setPassengerData(null);
    setTimeout(() => startCheckInFlow(), 3000);
  }, [startCheckInFlow]);

  const processBoardingPass = useCallback(async (barcode: string) => {
    setCurrentStep('processing');
    console.log('Processing boarding pass...');

    try {
      // Parse boarding pass data (implementation depends on format)
      const passengerInfo = parseBoardingPass(barcode);

      // Validate with airline systems
      const validatedData = await validatePassenger(passengerInfo);

      setPassengerData(validatedData);
      setCurrentStep('print');

      await printBoardingPass(validatedData);

    } catch (error) {
      console.error('Check-in processing failed:', error);
      resetFlow();
    }
  }, [resetFlow]);

  const processPassport = useCallback(async (documentLines: string[]) => {
    setCurrentStep('processing');
    console.log('Processing passport...');

    try {
      const mrzLines = documentLines.filter(line => isMRZLine(line));
      const passportData = parseMRZ(mrzLines);

      // Create check-in record
      const passengerInfo = await createCheckInFromPassport(passportData);

      setPassengerData(passengerInfo);
      setCurrentStep('print');

      await printBoardingPass(passengerInfo);

    } catch (error) {
      console.error('Passport processing failed:', error);
      resetFlow();
    }
  }, [resetFlow]);

  const printBoardingPass = async (passengerInfo: any) => {
    try {
      // Setup printer with airline assets
      await printer?.setupITPS([
        'PT##$S6A#@;#TICK#CHEC#BOAR#0101110112011301210122012301C#0201A34#03BRB061661#0430G25F'
      ]);

      // Print boarding pass with passenger data
      await printer?.sendITPS([
        'CP#A#01S#CP#C01#02@@01#03M1THIS IS A BARCODE#04THIS IS A BOARDING PASS#'
      ]);

      setCurrentStep('complete');
      console.log('Boarding pass printed successfully');

      // Auto-reset after 10 seconds
      setTimeout(() => resetFlow(), 10000);

    } catch (error) {
      console.error('Printing failed:', error);
      resetFlow();
    }
  };

  // Handle barcode data
  useEffect(() => {
    if (barcodeData && currentStep === 'scan') {
      const barcodes = barcodeData.payload?.dataRecords?.map((dr: any) => dr.data) || [];
      if (barcodes[0]) {
        processBoardingPass(barcodes[0]);
      }
    }
  }, [barcodeData, currentStep, processBoardingPass]);

  // Handle document data
  useEffect(() => {
    if (documentData && currentStep === 'scan') {
      const documentLines = documentData.payload?.dataRecords?.map((dr: any) => dr.data) || [];
      if (documentLines.length > 0) {
        processPassport(documentLines);
      }
    }
  }, [documentData, currentStep, processPassport]);

  // Start flow on mount
  useEffect(() => {
    startCheckInFlow();
  }, [startCheckInFlow]);

  return (
    <div>
      <h2>Check-In: {currentStep}</h2>
      {passengerData && <div>Passenger: {passengerData.name}</div>}
    </div>
  );
}

// Helper functions
function parseBoardingPass(barcode: string) {
  // Implementation depends on barcode format (IATA BCBP, etc.)
  // Return passenger information
  return { name: 'Passenger', flight: 'AA123', gate: 'A1', departureTime: '14:30' };
}

function parseMRZ(mrzLines: string[]) {
  // Parse Machine Readable Zone from passport
  // Return passport data
  return {};
}

function isMRZLine(line: string): boolean {
  // Check if line is part of MRZ
  return line.length > 0;
}

async function validatePassenger(data: any) {
  // Validate with airline systems
  return data;
}

async function createCheckInFromPassport(passportData: any) {
  // Create check-in record
  return { name: 'Passenger', flight: 'AA123', gate: 'A1', departureTime: '14:30' };
}

Payment Processing

Implement secure payment card processing with the CardReader.

import { useState, useEffect, useCallback } from 'react';
import { useCardReader } from '@elevationai/cuss2-react';

type PaymentState = 'idle' | 'waiting' | 'processing' | 'success' | 'failed';

function PaymentProcessor() {
  const { device: cardReader, data: cardData } = useCardReader();

  const [paymentState, setPaymentState] = useState<PaymentState>('idle');
  const [amount, setAmount] = useState<number>(0);

  const paymentFailed = useCallback((message: string) => {
    setPaymentState('failed');
    console.error('Payment failed:', message);

    // Reset after error message
    setTimeout(() => {
      setPaymentState('idle');
    }, 3000);
  }, []);

  const paymentSuccess = useCallback((result: any) => {
    setPaymentState('success');
    console.log('Payment approved:', result.transactionId);

    // Auto-reset after showing success
    setTimeout(() => {
      setPaymentState('idle');
    }, 5000);
  }, []);

  const processPaymentCard = useCallback(async (data: any[], paymentAmount: number) => {
    setPaymentState('processing');
    console.log('Processing payment...');

    try {
      // Extract card information
      const cardInfo = parseCardData(data.map(d => d.data));

      if (!cardInfo.isValid) {
        throw new Error('Invalid card data');
      }

      // Process payment through payment gateway
      const paymentResult = await submitPayment(cardInfo, paymentAmount);

      if (paymentResult.approved) {
        paymentSuccess(paymentResult);
      } else {
        paymentFailed(paymentResult.message || 'Payment declined');
      }

    } catch (error) {
      console.error('Payment processing error:', error);
      paymentFailed('Payment processing failed. Please try again.');
    } finally {
      await cardReader?.disable();
    }
  }, [cardReader, paymentSuccess, paymentFailed]);

  const processPayment = useCallback(async (paymentAmount: number) => {
    setAmount(paymentAmount);
    setPaymentState('waiting');
    console.log(`Initiating payment for $${paymentAmount}`);

    try {
      // Enable card reader
      await cardReader?.enable();

      // Start payment read with 30 second timeout
      await cardReader?.read(30000);

    } catch (error) {
      console.error('Payment initiation failed:', error);
      paymentFailed('Unable to initialize payment. Please try again.');
    }
  }, [cardReader, paymentFailed]);

  // Handle card data
  useEffect(() => {
    if (cardData && paymentState === 'waiting') {
      if (cardData.length) {
        processPaymentCard(cardData, amount);
      }
    }
  }, [cardData, paymentState, amount, processPaymentCard]);

  return (
    <div>
      <h2>Payment: {paymentState}</h2>
      {paymentState === 'idle' && (
        <button onClick={() => processPayment(100)}>Process $100 Payment</button>
      )}
    </div>
  );
}

// Helper functions
function parseCardData(data: string[]) {
  // parse and return card information
}

async function submitPaymen(cardInfo: any, amount: number) {
  // Implement payment api call
}

Baggage Handling

Implement baggage weighing and tag printing with weight validation.

import { useState, useEffect, useCallback, useRef } from 'react';
import { useScale, useBagTagPrinter } from '@elevationai/cuss2-react';

type BaggageState = 'idle' | 'weighing' | 'overweight' | 'printing' | 'complete';

function BaggageHandler() {
  const { device: scale, data: scaleData } = useScale();
  const { device: printer } = useBagTagPrinter();

  const [baggageState, setBaggageState] = useState<BaggageState>('idle');
  const [currentWeight, setCurrentWeight] = useState<number>(0);
  const [passenger, setPassenger] = useState<any>(null);
  const maxWeight = 50; // kg

  const timeoutRef = useRef<NodeJS.Timeout>();

  const resetBaggageProcess = useCallback(() => {
    setBaggageState('idle');
    setCurrentWeight(0);
    setPassenger(null);
  }, []);

  const saveBaggageInfo = async (tagNumber: string, weight: number) => {
    // Save baggage information to backend
  };

  const printBagTag = useCallback(async (weight: number) => {
    setBaggageState('printing');
    console.log(`Printing bag tag for weight: ${weight}kg`);

    try {
      // Setup bag tag printer with assets
      const companyLogo = 'LT0146940A020101000000001D01630064006400000000000000000000' +
          '000000000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF...';
      const assets = 'BTT0801~J 500262=#01C0M5493450304#02C0M5493450304#03B1MA020250541=06' +
          '#04B1MK200464141=06#05L0 A258250000#\n' + companyLogo;

      await device.setupITPS([assets]);

      // Print bag tag with passenger and weight information
      const coupon = 'BTP080101#01THIS IS A#02BAG TAG#03123#04456#0501#';
      await printer?.sendITPS([coupon]);

      setBaggageState('complete');
      console.log('Bag tag printed successfully:', bagTagNumber);

      // Save baggage information
      await saveBaggageInfo(bagTagNumber, weight);

      // Auto-reset after success
      setTimeout(() => resetBaggageProcess(), 8000);

    } catch (error) {
      console.error('Bag tag printing failed:', error);
      resetBaggageProcess();
    }
  }, [printer, passenger, generateBagTagNumber, resetBaggageProcess]);

  const handleOverweight = useCallback((weight: number) => {
    setBaggageState('overweight');
    const excessWeight = weight - maxWeight;
    console.log(`Bag is overweight: ${weight}kg (${excessWeight.toFixed(1)}kg over limit)`);

    // Allow passenger to adjust and try again
    setTimeout(() => {
      console.log('Waiting for adjusted bag weight');
      setBaggageState('weighing');
      stableCountRef.current = 0;
      lastWeightRef.current = 0;
    }, 10000);
  }, []);

  const startBaggageProcess = useCallback(async (passengerData: any) => {
    setPassenger(passengerData);
    setBaggageState('weighing');
    console.log('Starting baggage process for:', passengerData.name);

    // Enable scale
    await scale?.enable();

    // Set timeout for stable weight
    timeoutRef.current = setTimeout(() => {
      if (baggageState === 'weighing') {
        console.error('Unable to get stable weight reading');
        resetBaggageProcess();
      }
    }, 30000);
  }, [scale, baggageState, resetBaggageProcess]);

  // Monitor scale data and process weight
  useEffect(() => {
    if (scaleData && baggageState === 'weighing') {
      const weight = scaleData.data;

      if (weight > 0) {
        setCurrentWeight(weight);        

        if (weight > maxWeight) {
          handleOverweight(weight);
        } else {
          await printBagTag(weight);
        }
      }
    }
  }, [scaleData, baggageState]);

  // Cleanup timeout on unmount
  useEffect(() => {
    return () => {
      if (timeoutRef.current) clearTimeout(timeoutRef.current);
    };
  }, []);

  return (
    <div>
      <h2>Baggage Handling: {baggageState}</h2>
      <div className={`weight-display ${
        currentWeight > maxWeight ? 'overweight' :
        currentWeight > maxWeight * 0.9 ? 'warning' : 'normal'
      }`}>
        {currentWeight.toFixed(1)} kg
      </div>
      {baggageState === 'idle' && (
        <button onClick={() => startBaggageProcess({ name: 'John Doe', airline: 'AA' })}>
          Start Baggage Process
        </button>
      )}
    </div>
  );
}

Camera Capture

Implement passenger photo capture for documentation and verification purposes.

import { useState, useEffect, useCallback } from 'react';
import { useCamera } from '@elevationai/cuss2-react';

type CaptureState = 'idle' | 'preparing' | 'ready' | 'capturing' | 'complete';

function PhotoCapture() {
  const { device: camera, ready, data: cameraData } = useCamera();

  const [captureState, setCaptureState] = useState<CaptureState>('idle');
  const [capturedPhoto, setCapturedPhoto] = useState<string | null>(null);

  // Monitor camera ready status
  useEffect(() => {
    if (ready && captureState === 'preparing') {
      setCaptureState('ready');
      console.log('Camera is ready');
    }
  }, [ready, captureState]);

  // Monitor captured photos
  useEffect(() => {
    if (cameraData && captureState === 'capturing') {
      const photos = cameraData.payload?.dataRecords?.map((dr: any) => dr.data) || [];
      if (photos.length > 0 && photos[0]) {
        setCapturedPhoto(photos[0]);
        setCaptureState('complete');
        console.log('Photo captured successfully');
        savePhoto(photos[0]);
      }
    }
  }, [cameraData, captureState]);

  const countdownToCapture = async () => {
    const countdown = [3, 2, 1];
    for (const count of countdown) {
      console.log(`Countdown: ${count}`);
      await new Promise(resolve => setTimeout(resolve, 1000));
    }
    console.log('Capturing photo...');
  };

  const savePhoto = async (photoData: any) => {
    // save photo to backend
  };

  const startPhotoCapture = useCallback(async () => {
    setCaptureState('preparing');
    console.log('Preparing camera...');

    try {
      // Enable camera
      await camera?.enable();

      // Wait for camera to be ready
      setCaptureState('ready');
      console.log('Camera ready, starting countdown');

      // Countdown before capture
      await countdownToCapture();

      // Capture photo with 10 second timeout
      setCaptureState('capturing');
      await camera?.read(10000);

    } catch (error) {
      console.error('Photo capture failed:', error);
      setCaptureState('idle');
    }
  }, [camera]);

  const retakePhoto = useCallback(() => {
    setCaptureState('idle');
    setCapturedPhoto(null);
    startPhotoCapture();
  }, [startPhotoCapture]);

  return (
    <div>
      <h2>Photo Capture: {captureState}</h2>
      {captureState === 'idle' && (
        <button onClick={startPhotoCapture}>Start Photo Capture</button>
      )}
      {captureState === 'complete' && capturedPhoto && (
        <div>
          <p>Photo captured successfully!</p>
          <button onClick={retakePhoto}>Retake Photo</button>
        </div>
      )}
    </div>
  );
}

Accessibility Support

Implement comprehensive accessibility features with headset and keypad navigation.

import { useState, useEffect, useCallback, useMemo } from 'react';
import {
  useHeadset,
  useKeypad,
  useAnnouncement,
  useBarcodeReader,
  useCardReader,
  useBoardingPassPrinter
} from '@elevationai/cuss2-react';

interface MenuItem {
  id: string;
  label: string;
  action: () => void;
}

function AccessibleKiosk() {
  const { device: headset, enabled: headsetEnabled } = useHeadset();
  const { device: keypad, keypadData } = useKeypad();
  const { device: announcement } = useAnnouncement();
  const { enabled: barcodeEnabled } = useBarcodeReader();
  const { enabled: cardEnabled } = useCardReader();
  const { enabled: printerEnabled } = useBoardingPassPrinter();

  const [accessibilityMode, setAccessibilityMode] = useState(false);
  const [currentFocus, setCurrentFocus] = useState(0);

  const startCheckIn = useCallback(() => {
    announcement?.say(
      'Starting check-in process. You can scan your boarding pass, ' +
      'scan a QR code from your phone, or place your passport on the document reader.'
    );
    // Navigate to check-in component
  }, [announcement]);

  const showFlightInfo = useCallback(() => {
    announcement?.say('Loading flight information');
    // Navigate to flight info component
  }, [announcement]);

  const getHelp = useCallback(() => {
    announcement?.say(
      'For additional assistance, please approach the service desk ' +
      'or press the call button to speak with an agent.'
    );
  }, [announcement]);

  const changeLanguage = useCallback(() => {
    announcement?.say('Language options: English, Spanish, French. Use arrow keys to select.');
  }, [announcement]);

  const menuItems: MenuItem[] = useMemo(() => [
    { id: 'check-in', label: 'Check In', action: startCheckIn },
    { id: 'flight-info', label: 'Flight Information', action: showFlightInfo },
    { id: 'help', label: 'Help and Assistance', action: getHelp },
    { id: 'language', label: 'Change Language', action: changeLanguage }
  ], [startCheckIn, showFlightInfo, getHelp, changeLanguage]);

  const announceCurrentOption = useCallback(() => {
    const currentItem = menuItems[currentFocus];
    if (currentItem) {
      const position = `Option ${currentFocus + 1} of ${menuItems.length}`;
      announcement?.say(`${position}: ${currentItem.label}`);
    }
  }, [currentFocus, menuItems, announcement]);

  const enableAccessibilityMode = useCallback(async () => {
    console.log('Enabling accessibility mode');

    try {
      // Enable headset
      await headset?.enable();

      // Enable keypad for navigation
      await keypad?.enable();

      setAccessibilityMode(true);

      // Provide welcome message
      await announcement?.say(
        'Accessibility mode enabled. Audio guidance is now available. ' +
        'Use the up and down arrow keys to navigate options. ' +
        'Press enter to select. Press the help key at any time for assistance.'
      );

      // Start with first menu item
      setCurrentFocus(0);
      announceCurrentOption();

    } catch (error) {
      console.error('Failed to enable accessibility mode:', error);
    }
  }, [headset, keypad, announcement, announceCurrentOption]);

  const disableAccessibilityMode = useCallback(() => {
    console.log('Disabling accessibility mode');
    setAccessibilityMode(false);
    // Switch to visual-only interface
  }, []);

  // Monitor headset connection
  useEffect(() => {
    if (headsetEnabled) {
      enableAccessibilityMode();
    } else {
      disableAccessibilityMode();
    }
  }, [headsetEnabled, enableAccessibilityMode, disableAccessibilityMode]);

  const navigateUp = useCallback(() => {
    setCurrentFocus(prev => {
      const newFocus = prev > 0 ? prev - 1 : menuItems.length - 1;
      return newFocus;
    });
  }, [menuItems.length]);

  const navigateDown = useCallback(() => {
    setCurrentFocus(prev => {
      const newFocus = prev < menuItems.length - 1 ? prev + 1 : 0;
      return newFocus;
    });
  }, [menuItems.length]);

  const selectCurrentOption = useCallback(async () => {
    const selectedItem = menuItems[currentFocus];
    if (selectedItem) {
      await announcement?.say(`Selected: ${selectedItem.label}`);
      selectedItem.action();
    }
  }, [currentFocus, menuItems, announcement]);

  const provideHelp = useCallback(() => {
    const helpText =
      'You are using the accessible kiosk interface. ' +
      'Current options: Check in, Flight information, Help, and Language settings. ' +
      'Use up and down arrows to navigate between options. ' +
      'Press enter to select an option. ' +
      'Press home to return to the main menu. ' +
      'Press volume up or down to adjust audio volume. ' +
      'Press help at any time to hear this message again.';

    announcement?.say(helpText);
  }, [announcement]);

  const goToMainMenu = useCallback(async () => {
    setCurrentFocus(0);
    await announcement?.say('Returning to main menu');
    setTimeout(() => announceCurrentOption(), 500);
  }, [announcement, announceCurrentOption]);

  const adjustVolume = useCallback((direction: number) => {
    const action = direction > 0 ? 'increased' : 'decreased';
    announcement?.say(`Volume ${action}`);
  }, [announcement]);

  // Handle keypad input
  useEffect(() => {
    if (keypadData && accessibilityMode) {
      if (keypadData.UP) {
        navigateUp();
      } else if (keypadData.DOWN) {
        navigateDown();
      } else if (keypadData.ENTER) {
        selectCurrentOption();
      } else if (keypadData.HOME) {
        goToMainMenu();
      } else if (keypadData.HELP) {
        provideHelp();
      } else if (keypadData.VOLUMEUP) {
        adjustVolume(1);
      } else if (keypadData.VOLUMEDOWN) {
        adjustVolume(-1);
      }
    }
  }, [keypadData, accessibilityMode, navigateUp, navigateDown, selectCurrentOption,
        goToMainMenu, provideHelp, adjustVolume]);

  // Announce current option when focus changes
  useEffect(() => {
    if (accessibilityMode) {
      announceCurrentOption();
    }
  }, [currentFocus, accessibilityMode, announceCurrentOption]);

  // Announce available services
  useEffect(() => {
    if (accessibilityMode) {
      const availables = [];
      if (barcodeEnabled) availables.push('barcode scanning');
      if (cardEnabled) availables.push('payment processing');
      if (printerEnabled) availables.push('boarding pass printing');

      if (availables.length > 0) {
        const servicesText = availables.join(', ');
        announcement?.say(`Available services: ${servicesText}`);
      }
    }
  }, [accessibilityMode, barcodeEnabled, cardEnabled, printerEnabled, announcement]);

  return (
    <div>
      <h2>Accessible Kiosk</h2>
      <p>Accessibility Mode: {accessibilityMode ? 'Enabled' : 'Disabled'}</p>
      {accessibilityMode && (
        <ul>
          {menuItems.map((item, index) => (
            <li key={item.id} className={index === currentFocus ? 'focused' : ''}>
              {item.label}
            </li>
          ))}
        </ul>
      )}
    </div>
  );
}

Error Handling Best Practices

Implement robust error handling for production CUSS2 applications.

import { useCallback } from 'react';
import { useCuss2 } from '@elevationai/cuss2-react';

function useErrorHandling() {
  const { connected } = useCuss2();

  // Centralized error handling with retry logic
  const handleError = useCallback(async <T,>(
    operation: () => Promise<T>,
    serviceName: string,
    maxRetries: number = 3,
    retryDelay: number = 2000
  ): Promise<T> => {
    let lastError: any;

    for (let attempt = 1; attempt <= maxRetries; attempt++) {
      try {
        return await operation();
      } catch (error) {
        lastError = error;
        console.warn(`${serviceName} failed (attempt ${attempt}/${maxRetries}):`, error);

        if (attempt < maxRetries) {
          // Exponential backoff
          const delay = retryDelay * Math.pow(2, attempt - 1);
          await new Promise(resolve => setTimeout(resolve, delay));
        }
      }
    }

    // All retries failed
    console.error(`${serviceName} failed after ${maxRetries} retries:`, lastError);
    handleFinalError(lastError, serviceName);
    throw lastError;
  }, []);

  const handleFinalError = useCallback((error: any, serviceName: string) => {
    let userMessage = 'A technical error occurred. Please try again.';

    // Provide specific error messages based on error type
    if (error.code === 'TIMEOUT') {
      userMessage = `${serviceName} timeout. Please ensure the device is ready and try again.`;
    } else if (error.code === 'DEVICE_NOT_FOUND') {
      userMessage = `${serviceName} is not available. Please see an agent for assistance.`;
    } else if (error.code === 'CONNECTION_LOST') {
      userMessage = 'Connection to the platform was lost. Reconnecting...';
      attemptReconnection();
    }

    // Display error to user
    console.error(userMessage);
  }, []);

  const attemptReconnection = useCallback(async () => {
    if (!connected) {
      // Wait 3 seconds before reconnecting
      await new Promise(resolve => setTimeout(resolve, 3000));

      try {
        // In a real implementation, reconnection would be handled by the Cuss2Connector
        console.log('Connection restored');
      } catch (error) {
        console.error('Unable to reconnect. Please see an agent.');
      }
    }
  }, [connected]);

  // Device-specific error handling
  const handleBarcodeReaderError = useCallback((error: any) => {
    if (error.message?.includes('timeout')) {
      console.error('Barcode scanning timed out');
    } else if (error.message?.includes('invalid')) {
      console.error('Unable to read barcode');
    } else {
      console.error('Barcode scanner error');
    }
  }, []);

  const handleCardReaderError = useCallback((error: any) => {
    if (error.message?.includes('payment')) {
      console.error('Payment card could not be processed');
    } else if (error.message?.includes('track')) {
      console.error('Card could not be read');
    } else {
      console.error('Card reader error');
    }
  }, []);

  const handlePrinterError = useCallback((error: any) => {
    if (error.message?.includes('paper')) {
      console.error('Printer is out of paper');
    } else if (error.message?.includes('jam')) {
      console.error('Printer jam detected');
    } else {
      console.error('Printing failed');
    }
  }, []);

  return {
    handleError,
    handleBarcodeReaderError,
    handleCardReaderError,
    handlePrinterError
  };
}

// Example usage in a component
function ExampleComponent() {
  const { handleError, handleBarcodeReaderError } = useErrorHandling();
  const { device: barcodeReader } = useBarcodeReader();

  const scanBarcode = async () => {
    try {
      await handleError(
        () => barcodeReader!.read(10000),
        'Barcode Reader',
        3,
        2000
      );
    } catch (error) {
      handleBarcodeReaderError(error);
    }
  };

  return <button onClick={scanBarcode}>Scan Barcode</button>;
}

Performance Optimization

Tips for optimizing performance in CUSS2 React applications.

1. Lazy Load Components

Use React.lazy and Suspense to reduce initial bundle size.

import { lazy, Suspense } from 'react';

// Lazy load device components
const BarcodeScanner = lazy(() => import('./components/BarcodeScanner'));
const PaymentProcessor = lazy(() => import('./components/PaymentProcessor'));

function KioskApp() {
  return (
    <Suspense fallback={<div>Loading...</div>}>
      <BarcodeScanner />
      <PaymentProcessor />
    </Suspense>
  );
}

2. Memoize Expensive Computations

Use useMemo to optimize expensive calculations.

import { useMemo } from 'react';
import { useBarcodeReader } from '@elevationai/cuss2-react';

function ScannerComponent() {
  const { data: barcodeData } = useBarcodeReader();

  const scanResult = useMemo(() => {
    if (!barcodeData?.payload?.dataRecords) return null;
    return barcodeData.payload.dataRecords.map(dr => dr.data)[0];
  }, [barcodeData]);

  return <div>{scanResult}</div>;
}

3. Cleanup Effects Properly

Always cleanup effects to prevent memory leaks.

import { useEffect } from 'react';
import { useBarcodeReader } from '@elevationai/cuss2-react';

function BaseKioskComponent() {
  const { device: barcodeReader, data } = useBarcodeReader();

  useEffect(() => {
    // Subscribe to data
    console.log('Barcode data:', data);

    // Cleanup when component unmounts
    return () => {
      barcodeReader?.disable();
    };
  }, [barcodeReader, data]);

  return <div>Scanner Component</div>;
}

4. Debounce Rapid Events

Use debouncing to prevent UI flooding from rapid device events.

import { useState, useEffect } from 'react';
import { useScale } from '@elevationai/cuss2-react';

function WeightDisplay() {
  const { data: scaleData } = useScale();
  const [displayWeight, setDisplayWeight] = useState(0);

  useEffect(() => {
    // Debounce weight updates
    const timer = setTimeout(() => {
      if (scaleData?.payload?.weight) {
        const weight = scaleData.payload.weight.grossWeight.weight;
        // Only update if weight changed significantly
        if (Math.abs(weight - displayWeight) > 0.1) {
          setDisplayWeight(weight);
        }
      }
    }, 500);

    return () => clearTimeout(timer);
  }, [scaleData, displayWeight]);

  return <div>{displayWeight.toFixed(1)} kg</div>;
}

Testing Strategies

Comprehensive testing approaches for CUSS2 React applications.

Unit Testing with React Testing Library

import { render, screen, waitFor } from '@testing-library/react';
import { useBarcodeReader } from '@elevationai/cuss2-react';
import BarcodeScanner from './BarcodeScanner';

// Mock the hook
jest.mock('@elevationai/cuss2-react');

describe('BarcodeScanner Component', () => {
  const mockUseBarcodeReader = useBarcodeReader as jest.MockedFunction<typeof useBarcodeReader>;

  beforeEach(() => {
    mockUseBarcodeReader.mockReturnValue({
      device: {
        enable: jest.fn().mockResolvedValue(undefined),
        disable: jest.fn().mockResolvedValue(undefined),
        read: jest.fn().mockResolvedValue(undefined)
      } as any,
      ready: true,
      enabled: true,
      data: null,
      status: 'OK',
      pending: false
    });
  });

  it('should render scanner component', () => {
    render(<BarcodeScanner />);
    expect(screen.getByText(/barcode/i)).toBeInTheDocument();
  });

  it('should display barcode data when scanned', async () => {
    const mockData = {
      meta: { messageCode: 'DATA_PRESENT' },
      payload: {
        dataRecords: [{ data: 'TEST123456789' }]
      }
    };

    mockUseBarcodeReader.mockReturnValue({
      device: {} as any,
      ready: true,
      enabled: true,
      data: mockData,
      status: 'OK',
      pending: false
    });

    render(<BarcodeScanner />);

    await waitFor(() => {
      expect(screen.getByText(/TEST123456789/)).toBeInTheDocument();
    });
  });
});

Integration Testing

import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import { Cuss2Connector, Cuss2ConnectionOptions } from '@elevationai/cuss2-react';
import CheckInFlow from './CheckInFlow';

const testOptions: Cuss2ConnectionOptions = {
  clientId: 'test-client',
  clientSecret: 'test-secret',
  cussUrl: 'ws://localhost:8080'
};

describe('CheckInFlow Integration', () => {
  it('should complete check-in flow when barcode is scanned', async () => {
    render(
      <Cuss2Connector options={testOptions}>
        <CheckInFlow />
      </Cuss2Connector>
    );

    // Wait for component to load
    await waitFor(() => {
      expect(screen.getByText(/check-in/i)).toBeInTheDocument();
    });

    // Start check-in flow
    const startButton = screen.getByRole('button', { name: /start/i });
    fireEvent.click(startButton);

    // Simulate barcode scan by updating the hook's data
    // In a real test, you would mock the platform response

    await waitFor(() => {
      expect(screen.getByText(/processing/i)).toBeInTheDocument();
    });
  });
});

E2E Testing with Playwright

import { test, expect } from '@playwright/test';

test.describe('Kiosk E2E Tests', () => {
  test.beforeEach(async ({ page }) => {
    await page.goto('http://localhost:3000');
  });

  test('should complete passenger check-in', async ({ page }) => {
    // Wait for connection
    await expect(page.locator('[data-testid="connection-status"]')).toHaveText('Connected');

    // Start check-in
    await page.click('button:has-text("Start Check-In")');

    // Simulate barcode scan via mock platform
    await page.evaluate(() => {
      // Trigger mock barcode scan event
      window.postMessage({
        type: 'MOCK_BARCODE_SCAN',
        data: 'M1DOE/JOHN       UA123 JFKLAX 001F003A0025'
      }, '*');
    });

    // Verify UI updates
    await expect(page.locator('[data-testid="check-in-status"]')).toHaveText('Processing');

    // Wait for completion
    await expect(page.locator('[data-testid="check-in-status"]')).toHaveText('Complete', {
      timeout: 10000
    });

    await expect(page.locator('[data-testid="completion-message"]')).toContainText('Check-in complete');
  });

  test('should handle barcode scanner errors', async ({ page }) => {
    await page.click('button:has-text("Start Check-In")');

    // Simulate error
    await page.evaluate(() => {
      window.postMessage({
        type: 'MOCK_BARCODE_ERROR',
        error: 'Scanner timeout'
      }, '*');
    });

    await expect(page.locator('[data-testid="error-message"]')).toContainText('timeout');
  });
});