# PunimTag Photo Viewer - Quick Start Guide

## 🎯 What Is This?

A **modern, fast, beautiful photo viewing website** that connects to your existing PunimTag PostgreSQL database. Perfect for sharing photos with family members without giving them admin access.

---

## 🚀 Quick Setup (5 Minutes)

### 1. Create the Project

```bash
# Navigate to your projects folder
cd /home/ladmin/Code

# Create Next.js project
npx create-next-app@latest punimtag-viewer --typescript --tailwind --app --no-src-dir

# Navigate into project
cd punimtag-viewer
```

When prompted, choose:
- TypeScript: **Yes**
- ESLint: **Yes**
- Tailwind CSS: **Yes**
- `src/` directory: **No** (we'll use app directory)
- App Router: **Yes**
- Import alias: **Yes** (@/*)

### 2. Install Core Dependencies

```bash
# UI Components & Styling
npx shadcn-ui@latest init

# Database
npm install @prisma/client
npm install -D prisma

# State Management & Data Fetching
npm install @tanstack/react-query

# Image Gallery
npm install react-photo-album yet-another-react-lightbox

# Icons & Animations
npm install lucide-react framer-motion

# Date Handling
npm install date-fns

# Authentication (Optional)
npm install next-auth
```

### 3. Set Up Database Connection

```bash
# Initialize Prisma
npx prisma init
```

Edit `.env`:
```bash
DATABASE_URL="postgresql://viewer_readonly:password@localhost:5432/punimtag"
```

### 4. Create Prisma Schema

Create `prisma/schema.prisma`:

```prisma
generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

model Photo {
  id         Int      @id @default(autoincrement())
  path       String   @unique
  filename   String
  dateAdded  DateTime @default(now()) @map("date_added")
  dateTaken  DateTime? @map("date_taken")
  processed  Boolean  @default(false)
  
  faces      Face[]
  photoTags  PhotoTagLinkage[]
  favorites  PhotoFavorite[]
  
  @@map("photos")
}

model Person {
  id             Int       @id @default(autoincrement())
  firstName      String    @map("first_name")
  lastName       String    @map("last_name")
  middleName     String?   @map("middle_name")
  maidenName     String?   @map("maiden_name")
  dateOfBirth    DateTime? @map("date_of_birth") @db.Date
  createdDate    DateTime  @default(now()) @map("created_date")
  
  faces          Face[]
  personEncodings PersonEncoding[]
  
  @@unique([firstName, lastName, middleName, maidenName, dateOfBirth], name: "uq_people_names_dob")
  @@map("people")
}

model Face {
  id               Int      @id @default(autoincrement())
  photoId          Int      @map("photo_id")
  personId         Int?     @map("person_id")
  encoding         Bytes
  location         String
  confidence       Decimal  @default(0) @db.Decimal
  qualityScore     Decimal  @default(0) @map("quality_score") @db.Decimal
  detectorBackend  String   @default("retinaface") @map("detector_backend")
  modelName        String   @default("ArcFace") @map("model_name")
  faceConfidence   Decimal  @default(0) @map("face_confidence") @db.Decimal
  poseMode         String   @default("frontal") @map("pose_mode")
  
  photo            Photo    @relation(fields: [photoId], references: [id])
  person           Person?  @relation(fields: [personId], references: [id])
  personEncodings  PersonEncoding[]
  
  @@index([photoId])
  @@index([personId])
  @@map("faces")
}

model PersonEncoding {
  id              Int      @id @default(autoincrement())
  personId        Int      @map("person_id")
  faceId          Int      @map("face_id")
  encoding        Bytes
  qualityScore    Decimal  @default(0) @map("quality_score") @db.Decimal
  detectorBackend String   @default("retinaface") @map("detector_backend")
  modelName       String   @default("ArcFace") @map("model_name")
  createdDate     DateTime @default(now()) @map("created_date")
  
  person          Person   @relation(fields: [personId], references: [id])
  face            Face     @relation(fields: [faceId], references: [id])
  
  @@index([personId])
  @@map("person_encodings")
}

model Tag {
  id          Int       @id @default(autoincrement())
  tagName     String    @unique @map("tag_name")
  createdDate DateTime  @default(now()) @map("created_date")
  
  photoTags   PhotoTagLinkage[]
  
  @@map("tags")
}

model PhotoTagLinkage {
  linkageId   Int      @id @default(autoincrement()) @map("linkage_id")
  photoId     Int      @map("photo_id")
  tagId       Int      @map("tag_id")
  linkageType Int      @default(0) @map("linkage_type")
  createdDate DateTime @default(now()) @map("created_date")
  
  photo       Photo    @relation(fields: [photoId], references: [id])
  tag         Tag      @relation(fields: [tagId], references: [id])
  
  @@unique([photoId, tagId], name: "uq_photo_tag")
  @@index([photoId])
  @@index([tagId])
  @@map("phototaglinkage")
}

model PhotoFavorite {
  id          Int      @id @default(autoincrement())
  username    String
  photoId     Int      @map("photo_id")
  createdDate DateTime @default(now()) @map("created_date")
  
  photo       Photo    @relation(fields: [photoId], references: [id])
  
  @@unique([username, photoId], name: "uq_user_photo_favorite")
  @@index([username])
  @@index([photoId])
  @@map("photo_favorites")
}
```

### 5. Generate Prisma Client

```bash
npx prisma generate
```

### 6. Test Database Connection

```bash
npx prisma db pull  # Verify schema matches database
npx prisma studio   # Open database browser (optional)
```

### 7. Create First Component

Create `app/page.tsx`:

```typescript
import { prisma } from '@/lib/db';
import { PhotoGrid } from '@/components/PhotoGrid';

export default async function HomePage() {
  const photos = await prisma.photo.findMany({
    where: { processed: true },
    orderBy: { dateTaken: 'desc' },
    take: 30,
  });

  return (
    <main className="container mx-auto px-4 py-8">
      <h1 className="text-4xl font-bold mb-8">Family Photos</h1>
      <PhotoGrid photos={photos} />
    </main>
  );
}
```

Create `lib/db.ts`:

```typescript
import { PrismaClient } from '@prisma/client';

const globalForPrisma = global as unknown as { prisma: PrismaClient };

export const prisma =
  globalForPrisma.prisma ||
  new PrismaClient({
    log: ['error'],
  });

if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma;
```

### 8. Run Development Server

```bash
npm run dev
```

Open http://localhost:3000

---

## 📁 Project Structure

```
punimtag-viewer/
├── app/                    # Next.js 14 App Router
│   ├── layout.tsx         # Root layout
│   ├── page.tsx           # Home page (photo grid)
│   ├── photo/[id]/        # Photo detail page
│   ├── people/            # People browser
│   ├── tags/              # Tags browser
│   └── search/            # Search page
├── components/            # React components
│   ├── ui/               # shadcn components
│   ├── PhotoGrid.tsx     # Photo grid
│   ├── PhotoCard.tsx     # Photo card
│   └── Lightbox.tsx      # Photo viewer
├── lib/                   # Utilities
│   ├── db.ts             # Prisma client
│   ├── queries.ts        # Database queries
│   └── utils.ts          # Helpers
├── prisma/
│   └── schema.prisma     # Database schema
├── .env                  # Environment variables
└── package.json
```

---

## 🎨 Install UI Components

```bash
# Install shadcn/ui components as needed
npx shadcn-ui@latest add button
npx shadcn-ui@latest add card
npx shadcn-ui@latest add input
npx shadcn-ui@latest add dialog
npx shadcn-ui@latest add dropdown-menu
npx shadcn-ui@latest add popover
npx shadcn-ui@latest add select
npx shadcn-ui@latest add calendar
npx shadcn-ui@latest add badge
npx shadcn-ui@latest add skeleton
```

---

## 🔐 Set Up Read-Only Database User

On your PostgreSQL server:

```sql
-- Create read-only user
CREATE USER viewer_readonly WITH PASSWORD 'your_secure_password';

-- Grant permissions
GRANT CONNECT ON DATABASE punimtag TO viewer_readonly;
GRANT USAGE ON SCHEMA public TO viewer_readonly;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO viewer_readonly;

-- Grant on future tables
ALTER DEFAULT PRIVILEGES IN SCHEMA public 
GRANT SELECT ON TABLES TO viewer_readonly;

-- Verify no write permissions
REVOKE INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public FROM viewer_readonly;
```

---

## 🚢 Deploy to Vercel (5 Minutes)

### 1. Push to GitHub

```bash
git init
git add .
git commit -m "Initial commit"
git remote add origin https://github.com/yourusername/punimtag-viewer.git
git push -u origin main
```

### 2. Deploy to Vercel

```bash
# Install Vercel CLI
npm i -g vercel

# Login
vercel login

# Deploy
vercel

# Set environment variables
vercel env add DATABASE_URL
```

Or use the Vercel dashboard:
1. Go to https://vercel.com
2. Import your GitHub repository
3. Add environment variable `DATABASE_URL`
4. Deploy!

---

## 🎯 Next Steps

### Essential Features (Week 1)
- [ ] Create photo grid with infinite scroll
- [ ] Add photo lightbox/viewer
- [ ] Implement search by people
- [ ] Add date range filter
- [ ] Create tag browser

### Nice-to-Have Features (Week 2-3)
- [ ] Add favorites system
- [ ] Implement timeline view
- [ ] Add photo sharing
- [ ] Create mobile navigation
- [ ] Add authentication (if needed)

### Polish (Week 4)
- [ ] Optimize performance (Lighthouse > 90)
- [ ] Add animations
- [ ] Test responsive design
- [ ] Add error handling
- [ ] Write documentation

---

## 📚 Key Documentation

- **Full Plan:** `docs/PHOTO_VIEWER_PLAN.md`
- **Next.js Docs:** https://nextjs.org/docs
- **Prisma Docs:** https://www.prisma.io/docs
- **shadcn/ui:** https://ui.shadcn.com/

---

## 🆘 Troubleshooting

### "Can't connect to database"
```bash
# Test connection manually
psql -U viewer_readonly -d punimtag -h localhost

# Check .env file has correct credentials
cat .env | grep DATABASE_URL
```

### "Module not found: @/..."
```bash
# Check tsconfig.json has paths configured
# Should have: "@/*": ["./*"]
```

### "Prisma Client not generated"
```bash
npx prisma generate
```

### "Images not loading"
```bash
# Check file paths in database
# Configure Next.js image domains in next.config.js
```

---

## 💡 Pro Tips

1. **Use React Server Components** - They're faster and simpler
2. **Enable Image Optimization** - Configure Next.js Image component
3. **Add Loading States** - Use Suspense and loading.tsx files
4. **Implement Infinite Scroll** - Use TanStack Query's useInfiniteQuery
5. **Cache Aggressively** - Photos don't change often
6. **Test Mobile First** - Most users will view on phones
7. **Add Dark Mode** - Use next-themes package
8. **Monitor Performance** - Use Vercel Analytics

---

## 🎨 Design Resources

**Color Schemes:**
- Modern Blue: `#3b82f6`
- Clean White: `#ffffff`
- Dark Mode: `#0f172a`

**Fonts:**
- Inter (Google Fonts)
- SF Pro (System font)

**Inspiration:**
- Google Photos
- iCloud Photos
- Unsplash

---

## 🤝 Need Help?

- Read the full plan: `docs/PHOTO_VIEWER_PLAN.md`
- Check Next.js docs: https://nextjs.org/docs
- Join Next.js Discord: https://nextjs.org/discord
- Check shadcn/ui examples: https://ui.shadcn.com/examples

---

**Ready to build something awesome!** 🚀

Start with Phase 1 in the full plan and iterate based on user feedback.

