CardCade is a React-based customer card management application with barcode scanning capabilities and location-based shop selection. The app allows users to store and manage customer loyalty cards with smart features for finding the closest relevant shop.
- οΏ½ Authentication: Sign in securely with Google
- π± Barcode & QR Scanning: Scan loyalty cards using your device's camera (EAN-13, Code128, Code39, QR)
- π° Magic Click: Click the logo to automatically select the card for the closest shop based on your GPS location
- πΊοΈ Location-Based Selection: Attach GPS coordinates to cards so the closest shop is always surfaced first
- π€ Card Sharing: Share all your cards with other users by email β bidirectional access, no Cloud Functions required
- π€ User Profiles: Set a custom username visible across the app
- π Smart Sorting: Cards are sorted by open count so your most-used cards appear first
- οΏ½ Quick-Access Shortcut: Deep-link any card to your home screen β tapping its icon opens that card instantly
- οΏ½π₯ Firebase Integration: Cloud storage with per-user Firestore collections
- π± Responsive Design: Works on mobile and desktop devices
- π¨ Material Design: Clean, modern UI with Material-UI v7 components
- πΎ Progressive Web App (PWA): Install on Android and iOS devices, works offline with service worker caching
CardCade is a fully-featured Progressive Web App that can be installed on both Android and iOS devices:
Android (Chrome/Edge):
- Open the app in Chrome or Edge
- Tap the menu (three dots) and select "Install app" or "Add to Home Screen"
- The app will be installed and can be launched from your home screen
iOS (Safari):
- Open the app in Safari
- Tap the Share button (square with arrow)
- Scroll down and tap "Add to Home Screen"
- Tap "Add" to confirm
- The app will appear on your home screen
The PWA includes a service worker that caches resources for offline use:
- All app assets (JS, CSS, images) are cached after first visit
- Cards and data are stored in Firebase, which has its own offline persistence
- Google Fonts are cached for consistent styling offline
- Fast Loading: Cached assets load instantly
- Offline Access: Core functionality works without internet
- Native-like Experience: Runs in standalone mode without browser UI
- Auto-updates: New versions are automatically downloaded and activated
- Responsive: Adapts to any screen size and orientation
The "Magic Click" feature uses your device's GPS location to:
- Get your current position
- Calculate distances to all shop locations stored with your cards
- Find the closest shop location
- Automatically select and display the card for the closest shop
- Fall back to random selection if no shop locations are available
Every card can be pinned to your device's home screen as a one-tap shortcut:
- Open any card from the grid
- Tap Add shortcut at the bottom of the card detail
- Mobile: the native Web Share sheet appears β tap Add to Home Screen (Android) or Add Bookmark (iOS) and set the URL as a home-screen shortcut
- Desktop: the shortcut link is copied to your clipboard; you can add it as a browser bookmark or a desktop shortcut
- Launch the app via that shortcut β CardCade reads the
?card=<id>URL parameter and automatically opens the correct card modal
- Deep-link format:
https://<your-domain>/?card=<cardId> - Works for both your own cards and shared cards
- Implemented in
src/utils/cardShortcut.ts:buildShortcutUrl(cardId)β constructs the URLgetCardIdFromUrl()β reads the param on launchshareOrCopyShortcut()β callsnavigator.share()on mobile, falls back toclipboard.writeText()on desktop
CardListauto-opens the target card via auseEffectthat fires once cards are loaded
- Node.js 20.19+ or 22.12+
- A Firebase project
-
Copy the example environment file:
cp .env.example .env
-
Configure your environment variables in
.env:
VITE_FIREBASE_API_KEY=your_firebase_api_key
VITE_FIREBASE_AUTH_DOMAIN=your-project.firebaseapp.com
VITE_FIREBASE_PROJECT_ID=your-project-id
VITE_FIREBASE_STORAGE_BUCKET=your-project.appspot.com
VITE_FIREBASE_MESSAGING_SENDER_ID=123456789
VITE_FIREBASE_APP_ID=1:123456789:web:abcdef123456
VITE_FIRESTORE_COLLECTION=customer_cards
VITE_APP_NAME=CardCade-
Install dependencies:
npm install
-
Start the development server:
npm run dev
-
Build for production:
npm run build
-
Deploy to Firebase Hosting:
firebase deploy --only hosting
-
Sign In: Log in with your Google account on first launch.
-
Add Cards: Click + Card in the toolbar to open the add-card form.
- Enter the store name and barcode/QR value manually, or tap Scan to use your camera.
- Select the barcode type (EAN-13, Code128, Code39, or QR Code).
- Click Add Card to save to Firestore.
-
Attach Shop Locations: Open any card, then click the location pin icon to open the Location Dialog.
- Tap Use Current Location to capture your GPS position as a shop coordinate.
- Add multiple coordinates for chain stores with multiple branches.
- Remove all coordinates with Clear All Locations.
-
Magic Click: Click the CardCade logo on the main screen.
- The app requests your GPS position, calculates distances to all stored shop coordinates, and opens the card for the closest match.
- Falls back to a random card if no shop coordinates exist.
-
View Cards: Cards are displayed in a responsive grid sorted by open count (most-used first). Click any card to view its barcode/QR code in full.
-
Share Cards: Open the user menu (top-right avatar) β Share Cards.
- Enter the email address of another CardCade user.
- Their email must match an existing account profile.
- All your cards will be visible to them immediately; shared cards show your email as the owner.
- Remove a user to revoke access instantly.
-
User Profile: Open the user menu β Profile to set a custom username.
-
Quick-Access Shortcut: Open any card and tap Add shortcut at the bottom of the detail view.
- On mobile (Android/iOS), the native share sheet opens β tap "Add to Home Screen" to pin that card as a shortcut.
- On desktop, the deep-link URL is copied to your clipboard.
- Launching the app from that shortcut URL automatically opens the card immediately (no navigation needed).
- Frontend: React 19 + TypeScript + Vite 7
- UI Library: Material-UI (MUI) v7
- Backend: Firebase 12 (Firestore + Auth)
- Authentication: Firebase Google Sign-In
- Barcode Scanning: Quagga2 (Code128, EAN-13, Code39)
- Barcode Rendering: JsBarcode + qr-code-styling (QR codes)
- Location Services: Browser Geolocation API
- PWA: vite-plugin-pwa with Workbox
- Service Worker: Auto-updating with runtime caching
- Testing: Vitest
- Deployment: Firebase Hosting
src/
βββ components/
β βββ AddCardForm.tsx # Card input form with live barcode scanning
β βββ CardList.tsx # Card display grid with modal, barcode/QR rendering, and location editing
β βββ LoginPage.tsx # Google Sign-In landing page
β βββ LocationDialog.tsx # Dialog for attaching/removing GPS shop coordinates from a card
β βββ SharedUsersManager.tsx # Manage users you share all your cards with
β βββ UserProfileManager.tsx # Edit display username
βββ contexts/
β βββ AuthContext.tsx # Firebase Auth context (Google provider)
βββ services/
β βββ cardService.ts # Firestore CRUD: fetch own cards, fetch shared cards, add, update locations, increment openCount
βββ utils/
β βββ magicClick.ts # Magic-click: GPS β closest shop β open card modal
β βββ cardShortcut.ts # Deep-link helpers: build /?card=id URL, read on launch, share or copy
β βββ geolocation.ts # getCurrentLocation(), calculateDistance(), duplicate detection
β βββ firebasePaths.ts # Centralized Firestore path helpers
β βββ errorHandler.ts # Error extraction, validation helpers (email, username)
β βββ logger.ts # Dev/prod logging wrapper
βββ constants/
β βββ index.ts # Animation, geolocation, UI, validation, scanner constants
βββ types.ts # TypeScript interfaces: CardContent, ShopLocation, SharedUser, UserProfile
βββ firebase.ts # Firebase app + Firestore initialization
βββ App.tsx # Root: auth gate, AppBar, card fetching, magic-click handler
interface CardContent {
id: string;
store_name: string;
code: string;
barcode_type?: BarcodeType; // 'EAN13' | 'EAN8' | 'CODE128' | 'CODE39' | 'QRCODE'
shop_locations: ShopLocation[] | null;
openCount?: number; // Atomic counter incremented each time the card is opened
ownerEmail?: string; // Set on shared cards β shows who owns them
ownerId?: string; // UID of the owner; present on shared cards
}
interface ShopLocation {
lat: number;
lng: number;
}
interface SharedUser {
id: string; // Firebase UID of the user being shared with
email: string;
addedAt: Date;
}
interface UserProfile {
email: string;
username?: string;
createdAt?: Date;
updatedAt?: Date;
}users/
{userId}/
profile/
info: { email, username?, createdAt, updatedAt }
{VITE_FIRESTORE_COLLECTION}/ # e.g. customer_cards
{cardId}: { store_name, code, barcode_type, shop_locations, openCount }
shared_with/
{targetUserId}: { email, userId, addedAt } # Who I share with
sharing_with_me/
{sharerUserId}: { email, userId, addedAt } # Who shares with me (reverse index)
- Fork the repository
- Create a feature branch:
git checkout -b feature-name - Commit your changes:
git commit -am 'Add feature' - Push to the branch:
git push origin feature-name - Submit a pull request
- Never commit your
.envfile to version control - Configure Firebase security rules appropriately
- Use HTTPS in production for geolocation access
- Be mindful of location privacy when sharing cards