A modern Progressive Web Application (PWA) for barcode scanning grocery shopping built with Next.js 15, React 18, and TypeScript. Features real-time barcode detection, shopping basket management, and responsive design optimized for mobile-first grocery shopping experiences.
This frontend application implements a component-based architecture using Next.js App Router with server-side rendering capabilities. The system follows modern React patterns with TypeScript for type safety, custom hooks for state management, and a service layer for API communication.
| Component | Technology | Version | Purpose |
|---|---|---|---|
| Framework | Next.js | 15.3.3 | Full-stack React framework with App Router |
| Runtime | React | 18.2.0 | Component-based UI library |
| Language | TypeScript | 5.0+ | Static type checking and development experience |
| Styling | TailwindCSS | 4.0.0 | Utility-first CSS framework |
| Icons | Phosphor Icons | 2.1.10 | Comprehensive icon library |
| Icons Alternative | Lucide React | 0.263.1 | Additional icon components |
| Build System | Next.js Compiler | Built-in | Rust-based SWC compiler |
| Type Checking | TypeScript Compiler | 5.0+ | Static analysis and type validation |
src/
├── app/ # Next.js App Router
│ ├── layout.tsx # Root layout with metadata and viewport config
│ ├── page.tsx # Main application page with state management
│ └── globals.css # Global styles and custom CSS properties
├── components/ # Reusable React components
│ ├── GroceryBasket.tsx # Shopping basket management component
│ └── QRScanner.tsx # Barcode scanning interface component
├── services/ # External service integrations
│ └── api.ts # HTTP client with retry logic and error handling
├── types/ # TypeScript type definitions
│ └── index.ts # Shared interfaces and type definitions
├── constants/ # Application constants and configuration
│ └── index.ts # API endpoints, UI constants, and app configuration
└── utils/ # Utility functions and helpers
└── index.ts # Data formatting, validation, and browser utilities
- Camera Access: Utilizes
navigator.mediaDevices.getUserMedia()for camera stream - Barcode Detection: Implements Web APIs
BarcodeDetectorfor real-time scanning - Frame Processing: Uses
ImageCapture.grabFrame()for frame-by-frame analysis - Error Handling: Comprehensive error states for camera access, detection failures, and network issues
const SUPPORTED_FORMATS = [
'code_128', 'code_39', 'code_93', 'codabar',
'ean_13', 'ean_8', 'itf', 'pdf417',
'upc_a', 'upc_e', 'qr_code'
];interface ScannerState {
isScanning: boolean; // Camera active state
isLoading: boolean; // Processing state
scannedData: string; // Last scanned barcode
status: string; // User feedback message
statusType: 'info' | 'success' | 'error';
}- Duplicate scan prevention with 3-second cooldown
- Automatic product lookup via API integration
- Real-time status feedback with visual indicators
- Camera permission handling and fallback states
- Performance-optimized scanning with 100ms intervals
- State Management: React hooks for cart state with localStorage persistence
- Product Management: Add, remove, and quantity update operations
- Price Calculations: Real-time total calculation with quantity-based pricing
- Persistence: Browser storage for cart continuity across sessions
interface BasketItem {
id: string; // Unique product identifier
name: string; // Product display name
price: number; // Unit price in currency
image: string; // Product image URL
discount?: string; // Discount information
category: string; // Product category
quantity: number; // Item quantity in basket
}- Add Product: Increment quantity if exists, create new item if not
- Update Quantity: Modify item quantities with validation
- Remove Item: Delete items from basket with confirmation
- Calculate Totals: Real-time price computation with formatting
- Clear Basket: Reset all items with state cleanup
class ApiClient {
private baseURL: string;
private timeout: number = 10000;
private retryAttempts: number = 3;
private retryDelay: number = 1000;
}- Automatic Retries: Exponential backoff for failed requests
- Timeout Handling: Request timeout with configurable limits
- Error Transformation: Structured error responses with status codes
- Environment Detection: Dynamic API endpoint selection
class ProductService extends ApiClient {
async getProducts(filters): Promise<ApiResponse<Product[]>>
async getProductById(id): Promise<ApiResponse<Product>>
async searchByBarcode(barcode): Promise<Product | null>
async createProduct(data): Promise<ApiResponse<Product>>
async updateProduct(id, data): Promise<ApiResponse<Product>>
async deleteProduct(id): Promise<ApiResponse<void>>
}interface Product {
productId: string; // Unique identifier (format: P001, P002, etc.)
name: string; // Product display name
mrpPrice: number; // Maximum Retail Price
image: string; // Product image URL
discounts?: string; // Discount description
category: ProductCategory; // Enum-based category
stock?: number; // Available inventory
expiryDate?: string; // Expiration date (YYYY-MM-DD)
}
interface ApiResponse<T = any> {
success: boolean; // Operation success status
data?: T; // Response payload
error?: string; // Error message
message?: string; // Success message
pagination?: PaginationInfo; // Pagination metadata
timestamp?: string; // Response timestamp
}
enum ProductCategory {
DAIRY = 'Dairy',
FRUITS = 'Fruits',
VEGETABLES = 'Vegetables',
GROCERY = 'Grocery',
BAKERY = 'Bakery',
BEVERAGES = 'Beverages',
SNACKS = 'Snacks',
OTHER = 'Other'
}The application leverages the modern Web API for barcode detection:
interface BarcodeDetector {
detect(image: ImageBitmap): Promise<Barcode[]>;
}
interface Barcode {
rawValue: string; // Detected barcode value
format: string; // Barcode format type
boundingBox?: DOMRectReadOnly; // Detection coordinates
}- Chrome/Chromium: ≥90 (BarcodeDetector API support)
- Firefox: ≥88 (MediaDevices API support)
- Safari: ≥14 (ImageCapture API support)
- Edge: ≥90 (Full API compatibility)
const browserCapabilities = {
supportsBarcodeDetection: () => 'BarcodeDetector' in window,
supportsCamera: () => 'mediaDevices' in navigator,
supportsImageCapture: () => 'ImageCapture' in window,
supportsLocalStorage: () => typeof Storage !== 'undefined'
};const API_CONFIG = {
BASE_URL: typeof window !== 'undefined'
? window.location.hostname === 'localhost'
? 'http://localhost:5001'
: 'https://smart-grocery-basket-backend.onrender.com'
: 'http://localhost:5001',
TIMEOUT: 10000,
RETRY_ATTEMPTS: 3,
RETRY_DELAY: 1000,
};const SCANNER_CONFIG = {
VIDEO_CONSTRAINTS: {
facingMode: 'environment', // Rear camera preference
width: { ideal: 1280 }, // Optimal resolution
height: { ideal: 720 },
},
SCAN_INTERVAL: 100, // Frame processing frequency (ms)
DUPLICATE_SCAN_TIMEOUT: 3000, // Cooldown period (ms)
DETECTION_TIMEOUT: 5000, // Max detection time (ms)
};- Color Scheme: Dark mode with gray-950 primary background
- Typography: System font stack with optimal readability
- Spacing: Consistent 4px grid system
- Border Radius: Modern rounded corners (8px, 12px, 16px)
- Animations: Subtle transitions with reduced motion support
- Mobile-First: Optimized for smartphone usage
- Breakpoints: Standard TailwindCSS breakpoints (sm: 640px, md: 768px, lg: 1024px)
- Grid System: Flexible grid layout with responsive columns
- Touch Targets: Minimum 44px touch targets for accessibility
- Focus Management: Visible focus indicators with 2px outline
- Screen Reader Support: Semantic HTML and ARIA labels
- Motion Preferences: Respects
prefers-reduced-motion - Color Contrast: WCAG AA compliant contrast ratios
- Keyboard Navigation: Full keyboard accessibility
- Component State: React
useStatefor component-specific data - Effect Management:
useEffectfor side effects and cleanup - Callback Optimization:
useCallbackfor performance optimization - Reference Management:
useReffor DOM access and mutable values
const storage = {
setItem: (key: string, value: any): void => {
localStorage.setItem(key, JSON.stringify(value));
},
getItem: <T>(key: string, defaultValue: T | null = null): T | null => {
const item = localStorage.getItem(key);
return item ? JSON.parse(item) : defaultValue;
},
removeItem: (key: string): void => {
localStorage.removeItem(key);
}
};- Parent-Child Communication: Props-based data flow
- Event Handling: Callback functions for state updates
- Side Effect Management: Proper cleanup in useEffect hooks
- Route-Based Splitting: Next.js automatic code splitting
- Component Lazy Loading: Dynamic imports for heavy components
- Bundle Analysis: Built-in bundle analyzer support
- Next.js Image Component: Automatic optimization and lazy loading
- Format Selection: WebP support with fallbacks
- Responsive Images: Multiple size variants
- Debounced Scanning: Prevents excessive API calls
- Memoization: React.memo and useMemo for expensive operations
- Efficient Re-renders: Optimized dependency arrays
Node.js ≥18.0.0
npm ≥8.0.0
Modern browser with camera support
Backend API running (see backend repository)# Repository setup
git clone https://github.com/Intel-IoT-Club/smart_grocery_basket_frontend.git
cd smart_grocery_basket_frontend
# Dependency installation
npm install
# Development server with Turbopack
npm run dev
# Build optimization
npm run build
# Production server
npm startnpm run dev # Next.js development server with hot reload
npm run build # Production build with optimizations
npm start # Production server
npm run lint # ESLint code analysis
npm run type-check # TypeScript compilation check// next.config.ts
const nextConfig: NextConfig = {
images: {
domains: ['localhost'], // External image domains
},
experimental: {
turbopack: true, // Rust-based bundler
},
};const sanitizeString = (input: string): string => {
return input
.trim()
.replace(/[<>]/g, '') // Remove HTML tags
.replace(/javascript:/gi, '') // Remove JavaScript URLs
.replace(/on\w+=/gi, ''); // Remove event handlers
};- HTTPS Enforcement: Production HTTPS requirements
- CORS Handling: Proper cross-origin request handling
- Input Validation: Client-side validation with server-side verification
- Error Information: Sanitized error messages to prevent information leakage
export const metadata: Metadata = {
title: "Smart Grocery Basket",
description: "Scan products to add them to your basket with ease",
keywords: ["grocery", "shopping", "barcode", "scanner", "mobile"],
manifest: "/manifest.json",
};
export const viewport: Viewport = {
width: "device-width",
initialScale: 1,
maximumScale: 1,
userScalable: false,
themeColor: "#111827",
};- App-like Experience: Full-screen mobile web app
- Status Bar Styling: Translucent status bar for iOS
- Viewport Management: Optimal mobile viewport configuration
- Touch Optimization: Touch-friendly interface design
- Component-Level Errors: Graceful component error handling
- API Error Handling: Structured error responses with user-friendly messages
- Network Error Recovery: Automatic retry mechanisms with exponential backoff
const STATUS_MESSAGES = {
SCANNER: {
READY: 'Scanner ready. Tap to start scanning',
INITIALIZING: 'Initializing camera...',
SCANNING: 'Hold barcode steady in the frame',
ERROR: 'Unable to access camera',
NOT_SUPPORTED: 'Barcode scanning not supported in this browser'
},
BASKET: {
EMPTY: 'Basket is Empty',
ITEM_ADDED: 'Item added to basket',
CLEARED: 'Basket cleared'
}
};- Compile-Time Checking: TypeScript compilation validation
- Interface Compliance: Strict type checking across components
- Runtime Validation: Input validation with type guards
- Hot Reload: Instant feedback during development
- Error Overlay: Detailed error information in development mode
- Console Logging: Structured logging for debugging
- Tree Shaking: Unused code elimination
- Minification: JavaScript and CSS minification
- Asset Optimization: Image and font optimization
- Bundle Splitting: Optimal chunk sizing
- Core Web Vitals: Optimized for Google's performance metrics
- Lighthouse Score: High performance, accessibility, and SEO scores
- Bundle Size: Optimized bundle size for fast loading
- Development: Hot reload with detailed error information
- Production: Optimized builds with error boundary protection
- Environment Detection: Automatic API endpoint selection
The application requires modern browser features for optimal functionality:
| Feature | Requirement | Fallback |
|---|---|---|
| BarcodeDetector API | Chrome ≥90, Edge ≥90 | Feature detection with graceful degradation |
| MediaDevices API | All modern browsers | Camera access error handling |
| ImageCapture API | Chrome ≥59, Edge ≥90 | Frame capture alternatives |
| Local Storage | Universal support | Memory-based fallback |
useEffect(() => {
if ("BarcodeDetector" in window) {
updateStatus("Scanner ready. Tap to start scanning", 'info');
} else {
updateStatus("Barcode scanning not supported in this browser", 'error');
}
}, []);MIT License - Open source project by Intel IoT Club
- Backend API: smart_grocery_basket_backend
- Documentation: Comprehensive API documentation available in backend repository }
### Environment Configuration
```bash
# .env.local
NEXT_PUBLIC_API_URL=http://localhost:5001/api
NEXT_PUBLIC_ENVIRONMENT=development
ANALYZE=false
# Start development with file watching
npm run dev
# Type checking in separate terminal
npm run type-check -- --watch
# Linting
npm run lint
# Build for production testing
npm run build && npm start# Create optimized production build
npm run build
# Analyze bundle size
ANALYZE=true npm run build
# Start production server
npm start// next.config.ts
const nextConfig: NextConfig = {
experimental: {
turbo: {
rules: {
'*.svg': {
loaders: ['@svgr/webpack'],
as: '*.js',
},
},
},
},
images: {
domains: ['via.placeholder.com', 'example.com'],
formats: ['image/webp', 'image/avif'],
},
compress: true,
poweredByHeader: false,
generateEtags: false,
assetPrefix: process.env.NODE_ENV === 'production' ? '/grocery-app' : '',
};// Web Vitals reporting
export function reportWebVitals(metric: NextWebVitalsMetric) {
switch (metric.name) {
case 'CLS':
case 'FID':
case 'FCP':
case 'LCP':
case 'TTFB':
console.log(metric);
// Send to analytics service
break;
default:
break;
}
}# Install testing dependencies
npm install --save-dev @testing-library/react @testing-library/jest-dom jest jest-environment-jsdom// components/__tests__/GroceryBasket.test.tsx
import { render, screen, fireEvent } from '@testing-library/react';
import GroceryBasket from '../GroceryBasket';
describe('GroceryBasket', () => {
test('renders empty basket state', () => {
render(<GroceryBasket />);
expect(screen.getByText('Basket is Empty')).toBeInTheDocument();
});
test('adds product to basket', () => {
const mockProduct = {
productId: 'P001',
name: 'Test Product',
mrpPrice: 100,
image: 'test.jpg',
category: 'Test'
};
render(<GroceryBasket />);
// Test product addition logic
});
});// services/__tests__/api.test.ts
import { ProductService } from '../api';
// Mock fetch
global.fetch = jest.fn();
describe('ProductService', () => {
beforeEach(() => {
(fetch as jest.Mock).mockClear();
});
test('fetches product successfully', async () => {
const mockProduct = { productId: 'P001', name: 'Test' };
(fetch as jest.Mock).mockResolvedValueOnce({
ok: true,
json: async () => ({ success: true, data: mockProduct })
});
const result = await ProductService.getProduct('P001');
expect(result.success).toBe(true);
expect(result.data).toEqual(mockProduct);
});
});# Install Playwright for E2E testing
npm install --save-dev @playwright/test
# Run E2E tests
npx playwright test// jest.config.js
const nextJest = require('next/jest');
const createJestConfig = nextJest({
dir: './',
});
const customJestConfig = {
setupFilesAfterEnv: ['<rootDir>/jest.setup.js'],
testEnvironment: 'jest-environment-jsdom',
moduleDirectories: ['node_modules', '<rootDir>/'],
testPathIgnorePatterns: ['<rootDir>/.next/', '<rootDir>/node_modules/'],
};
module.exports = createJestConfig(customJestConfig);- Chrome/Chromium ≥90
- Firefox ≥88
- Safari ≥14
- Edge ≥90
- Mobile Safari ≥14
- Chrome Android ≥90
// Feature detection
const checkBrowserSupport = () => {
const features = {
camera: 'mediaDevices' in navigator && 'getUserMedia' in navigator.mediaDevices,
barcodeDetector: 'BarcodeDetector' in window,
serviceWorker: 'serviceWorker' in navigator,
notifications: 'Notification' in window
};
return features;
};// Barcode detection fallback
const initializeBarcodeScanning = async () => {
if ('BarcodeDetector' in window) {
return new BarcodeDetector();
} else {
// Fallback to library-based detection
const { BarcodeDetector } = await import('barcode-detector/pure');
return new BarcodeDetector();
}
};- Follow TypeScript strict mode requirements
- Use semantic commit messages (feat, fix, docs, style, refactor, test, chore)
- Ensure all tests pass before submitting PR
- Update documentation for new features
- Follow accessibility guidelines (WCAG 2.1 AA)
- Optimize for performance and bundle size
// ESLint configuration
module.exports = {
extends: [
'next/core-web-vitals',
'@typescript-eslint/recommended',
'@typescript-eslint/recommended-requiring-type-checking'
],
rules: {
'@typescript-eslint/no-unused-vars': 'error',
'@typescript-eslint/explicit-function-return-type': 'warn',
'react-hooks/exhaustive-deps': 'error'
}
};- Create feature branch from main
- Implement changes with tests
- Run full test suite and linting
- Update documentation if needed
- Submit PR with detailed description
- Address code review feedback
- Ensure CI/CD checks pass
- Use provided issue templates
- Include browser information and steps to reproduce
- Attach screenshots for UI issues
- Provide minimal reproduction example
MIT License - see LICENSE file for details
For technical support or questions:
- Create an issue on GitHub
- Review the troubleshooting guide
- Check browser console for error details
- Ensure camera permissions are granted
See CHANGELOG.md for version history and updates