A modern full-stack web app for digitizing and managing business card contacts — with OCR scanning, duplicate detection, and LinkedIn integration.
- Frontend: Next.js 14 (App Router) + React 18 + Tailwind CSS
- Backend: Next.js API Routes
- Database: Firebase Firestore
- Auth: Firebase Authentication (email/password)
- Storage: Firebase Storage
- OCR: Tesseract.js (client-side + server-side)
cd constara
npm install-
Go to Firebase Console
-
Create a new project (or use existing)
-
Enable these services:
- Authentication → Sign-in method → Email/Password ✓
- Firestore Database → Create database (start in test mode for dev)
- Storage → Get started
-
Get your config:
- Project Settings → General → Your apps → Add web app
- Copy the
firebaseConfigvalues
Edit .env.local with your Firebase credentials:
NEXT_PUBLIC_FIREBASE_API_KEY=AIza...
NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN=your-project.firebaseapp.com
NEXT_PUBLIC_FIREBASE_PROJECT_ID=your-project-id
NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET=your-project.appspot.com
NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID=123456789
NEXT_PUBLIC_FIREBASE_APP_ID=1:123:web:abcIn Firebase Console → Firestore → Rules:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /users/{userId} {
allow read, write: if request.auth != null && request.auth.uid == userId;
}
match /contacts/{contactId} {
allow read, write: if request.auth != null && request.auth.uid == resource.data.userId;
allow create: if request.auth != null && request.auth.uid == request.resource.data.userId;
}
}
}In Firebase Console → Storage → Rules:
rules_version = '2';
service firebase.storage {
match /b/{bucket}/o {
match /cards/{userId}/{allPaths=**} {
allow read, write: if request.auth != null && request.auth.uid == userId;
}
}
}The app needs a composite index for contacts. Firebase will prompt you to create it automatically when you first query — or create it manually:
- Collection:
contacts - Fields:
userId(Ascending),isDeleted(Ascending),createdAt(Descending)
npm run dev- Upload PNG/JPG/WEBP images
- Auto-extract: name, email, phone, company, title, location
- Review and edit before saving
- Checks email, phone, name+company
- Shows modal to update existing or create new
- Real-time search across name, company, email, notes
- Grid and list view toggle
- Save LinkedIn URLs manually
- One-click Google search:
site:linkedin.com/in "Name" "Company"
- Create, read, update, soft-delete
- Manual entry or OCR upload
constara/
├── app/
│ ├── layout.tsx # Root layout + Auth context
│ ├── page.tsx # Root redirect
│ ├── dashboard/ # Main contacts dashboard
│ ├── login/ # Login page
│ ├── signup/ # Sign up page
│ ├── upload/ # Business card upload + OCR
│ ├── contacts/
│ │ ├── page.tsx # Contacts list
│ │ └── new/page.tsx # Manual contact creation
│ ├── contact/[id]/ # Contact detail + edit
│ ├── settings/ # User settings
│ └── api/
│ ├── ocr/route.ts # OCR endpoint
│ └── contacts/ # CRUD endpoints
│
├── components/
│ ├── ContactCard.tsx # Grid card component
│ ├── ContactForm.tsx # Create/edit form
│ ├── UploadCard.tsx # Drag-drop upload
│ ├── Navbar.tsx # Top navigation
│ ├── Sidebar.tsx # Side navigation
│ ├── SearchBar.tsx # Search input
│ └── DuplicateModal.tsx # Duplicate warning
│
├── lib/
│ ├── firebase.ts # Firebase initialization
│ ├── auth.ts # Auth functions
│ ├── db.ts # Firestore CRUD
│ ├── storage.ts # Image uploads
│ ├── ocr.ts # OCR (client-side)
│ └── duplicateCheck.ts # Duplicate logic
│
└── types/index.ts # TypeScript types
For best results:
- Use clear, well-lit photos
- Avoid glare and shadows
- Keep the card flat and centered
- Higher resolution = better accuracy
The OCR uses Tesseract.js which runs in the browser (no external API needed). Processing takes 3-8 seconds depending on image quality.