Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
NEXT_PUBLIC_SUPABASE_URL='your-supabase-project-url'
NEXT_PUBLIC_SUPABASE_ANON_KEY='your-supabase-anon-key'
GOOGLE_SITE_VERIFICATION='your-google-site-verification-code'

# Notion Integration (Fork Dashboard)
NOTION_API_KEY='secret_your-notion-integration-token'
NOTION_FORKS_DB_ID='your-forks-database-id'
NOTION_EVENTS_DB_ID='your-events-database-id'
NOTION_MEMBERS_DB_ID='your-members-database-id'
NOTION_REPORTS_DB_ID='your-reports-database-id'
NOTION_USERS_DB_ID='your-users-database-id'
137 changes: 135 additions & 2 deletions TECHNICAL_DOCUMENTATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,12 @@
│ │ │ ├── feedback/ # Chat feedback collection
│ │ │ ├── image/ # Image generation trigger
│ │ │ └── voice/ # Voice input (placeholder)
│ │ ├── forks/ # Fork Dashboard API
│ │ │ ├── route.ts # GET forks list + leaderboard
│ │ │ └── [id]/route.ts # GET single fork details
│ │ ├── events/route.ts # Fork events CRUD
│ │ ├── reports/route.ts # Fork reports CRUD
│ │ ├── team/route.ts # Team members list
│ │ ├── join/route.ts # Submit join requests → Supabase
│ │ ├── contact/ # Contact form submissions
│ │ └── discord/ # Discord OAuth integration
Expand All @@ -55,7 +61,11 @@
│ ├── sentiment.ts # Frustration detection regex
│ ├── team-data.ts # Team members + role matching logic
│ ├── theme.ts # Theme utilities (dark mode)
│ └── utils.ts # cn() classname merger
│ ├── utils.ts # cn() classname merger
│ ├── notion.ts # Notion API wrapper (Fork Dashboard)
│ ├── gamification.ts # Points & levels system (Fork Dashboard)
│ ├── healthScore.ts # Health score calculations (Fork Dashboard)
│ └── onboarding.ts # Onboarding tracking (Fork Dashboard)
├── public/
│ ├── images/ # Optimized AVIF/WebP
│ ├── partners/ # Sponsor logos
Expand Down Expand Up @@ -725,12 +735,135 @@ Next.js 16

---

## 16. Future Roadmap Notes
## 16. Fork Dashboard Integration

### 16.1 Overview

The Fork Dashboard is a Notion-powered management system for Bits&Bytes regional chapters (forks). It provides real-time tracking of fork health, events, team members, reports, and gamification through a web dashboard.

**Architecture:** Single database (Notion) shared between Discord bot and website, ensuring real-time sync without additional infrastructure.

### 16.2 Notion Database Schema

| Database | Purpose | Key Properties |
|----------|---------|----------------|
| **Forks** | Regional chapters | Name, City, Status, Points, Health Score, Level, Team Size, Events Count |
| **Events** | Fork events | Name, Fork (relation), Status, Type, Date, Points, Sponsors |
| **Team Members** | Fork membership | Name, Fork (relation), Role, Discord ID, Status, Onboarding Complete |
| **Reports** | Weekly/monthly reports | Title, Fork (relation), Type, Status, Content, Late flag |
| **Users** | Website authentication | Name, Email, Password Hash, Role, Fork (relation) |

### 16.3 Environment Variables

```bash
# Fork Dashboard (Notion Integration)
NOTION_API_KEY='secret_your-notion-integration-token'
NOTION_FORKS_DB_ID='your-forks-database-id'
NOTION_EVENTS_DB_ID='your-events-database-id'
NOTION_MEMBERS_DB_ID='your-members-database-id'
NOTION_REPORTS_DB_ID='your-reports-database-id'
NOTION_USERS_DB_ID='your-users-database-id'
```

### 16.4 Library Modules (`lib/`)

| Module | Purpose | Key Functions |
|--------|---------|---------------|
| `notion.ts` | Notion API wrapper | `getForks()`, `getFork()`, `getEvents()`, `createEvent()`, `getTeamMembers()`, `getReports()`, `createReport()` |
| `gamification.ts` | Points & levels | `addPoints()`, `getLevelFromPoints()`, `getLeaderboard()`, `POINTS` constants |
| `healthScore.ts` | Health calculations | `calculateHealthScore()`, `getHealthStatus()`, `getHealthRecommendations()` |
| `onboarding.ts` | Onboarding tracking | `calculateOnboardingProgress()`, `getNextStep()`, `ONBOARDING_STEPS` |

### 16.5 API Routes (`app/api/`)

| Route | Methods | Purpose |
|-------|---------|---------|
| `/api/forks` | GET | List all forks with health scores and leaderboard |
| `/api/forks/[id]` | GET | Single fork details with events, members, reports |
| `/api/events` | GET, POST | List/create events (auto-awards points) |
| `/api/reports` | GET, POST | List/submit reports (on-time bonus logic) |
| `/api/team` | GET | List team members by fork |

### 16.6 Gamification System

**Points Allocation:**
```typescript
const POINTS = {
EVENT_CREATED: 10,
EVENT_APPROVED: 20,
EVENT_COMPLETED: 50,
PER_SPONSOR_SECURED: 10,
REPORT_SUBMITTED: 15,
WEEKLY_PULSE_UPDATE: 10,
ON_TIME_REPORT_BONUS: 10,
MISSED_REPORT_DEADLINE: -15,
INACTIVE_TWO_WEEKS: -25,
}
```

**Level Progression:**
| Level | Points Range | Badge | Color |
|-------|--------------|-------|-------|
| Seed Fork | 0-99 | 🌱 | #81ECEC |
| Active Fork | 100-299 | 🌿 | #00FF95 |
| High Impact Fork | 300-699 | 🌳 | #00F2FF |
| Elite Fork | 700+ | 🏆 | #FFD700 |

### 16.7 Health Score Calculation

**Components (each 0-20 points):**
1. **Pulse Recency** - Days since last weekly update
2. **Events Conducted** - Total events count
3. **Team Completeness** - Team size
4. **Report Submission** - Regular reporting
5. **Partnerships** - Sponsor/partner engagement

**Status Thresholds:**
- Excellent: 80-100 (green)
- Good: 60-79 (yellow)
- Fair: 40-59 (orange)
- At Risk: 0-39 (red)

### 16.8 Dashboard UI (`app/fork/dashboard/`)

**Features:**
- Fork selector with status badges
- Stats overview (Total Forks, Active Forks, Events, Members)
- Health score visualization with progress bar
- Events/Team/Reports tabs
- Live leaderboard sidebar
- Configuration error handling (graceful degradation)

**Access:** `/fork/dashboard`

### 16.9 Error Handling

The dashboard gracefully handles missing Notion configuration:
- Displays configuration required message with missing env vars
- Shows actionable instructions for setup
- Returns 503 status for API calls when not configured

**Example Check:**
```typescript
const configStatus = getNotionConfigStatus()
if (!configStatus.configured) {
return NextResponse.json(
{ error: "Notion integration not configured", missing: configStatus.missing },
{ status: 503 }
)
}
```

---

## 17. Future Roadmap Notes

- [ ] Global rate limiter (Redis integration)
- [ ] Voice input support (Whisper API)
- [ ] Discord bot commands via `/api/discord` webhook
- [ ] Multi-language RAG (Hindi embeddings)
- [ ] Analytics dashboard (Supabase dashboards)
- [ ] Email notifications for leads (Resend.dev)
- [ ] Fork Dashboard authentication (Supabase Auth)
- [ ] Website-to-Discord sync webhooks

89 changes: 89 additions & 0 deletions app/api/events/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import { NextRequest, NextResponse } from "next/server"
import { getEvents, createEvent, getNotionConfigStatus, NotionConfigError, NotionDatabaseError } from "@/lib/notion"
import { POINTS, addPoints } from "@/lib/gamification"

export async function GET(req: NextRequest) {
const configStatus = getNotionConfigStatus()
if (!configStatus.configured) {
return NextResponse.json(
{ error: "Notion integration not configured", missing: configStatus.missing },
{ status: 503 }
)
}

try {
const { searchParams } = new URL(req.url)
const forkId = searchParams.get("forkId") || undefined
const status = searchParams.get("status") as any
const limit = searchParams.get("limit") ? parseInt(searchParams.get("limit")!) : undefined

const events = await getEvents({ forkId, status, limit })

return NextResponse.json({ events, total: events.length })
} catch (error) {
console.error("[API /events] Error:", error)

if (error instanceof NotionConfigError) {
return NextResponse.json({ error: error.message }, { status: 503 })
}

if (error instanceof NotionDatabaseError) {
return NextResponse.json({ error: error.message }, { status: 500 })
}

return NextResponse.json(
{ error: "Failed to fetch events. Please try again later." },
{ status: 500 }
)
}
}

export async function POST(req: NextRequest) {
const configStatus = getNotionConfigStatus()
if (!configStatus.configured) {
return NextResponse.json(
{ error: "Notion integration not configured", missing: configStatus.missing },
{ status: 503 }
)
}

try {
const body = await req.json()
const { name, forkId, type, date, description } = body

if (!name || !forkId || !type) {
return NextResponse.json(
{ error: "Missing required fields: name, forkId, type" },
{ status: 400 }
)
}

const event = await createEvent({
name,
forkId,
type,
date: date ? new Date(date) : undefined,
description,
})

// Award points for event creation
await addPoints(forkId, POINTS.EVENT_CREATED, "Event Created")

return NextResponse.json({ event, message: "Event created successfully" }, { status: 201 })
} catch (error) {
console.error("[API /events POST] Error:", error)

if (error instanceof NotionConfigError) {
return NextResponse.json({ error: error.message }, { status: 503 })
}

if (error instanceof NotionDatabaseError) {
return NextResponse.json({ error: error.message }, { status: 500 })
}

return NextResponse.json(
{ error: "Failed to create event. Please try again later." },
{ status: 500 }
)
}
}
75 changes: 75 additions & 0 deletions app/api/forks/[id]/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { NextRequest, NextResponse } from "next/server"
import { getFork, getEvents, getTeamMembers, getReports, getNotionConfigStatus, NotionConfigError, NotionDatabaseError } from "@/lib/notion"
import { calculateHealthScore, getHealthRecommendations } from "@/lib/healthScore"
import { getLevelInfo, getPointsToNextLevel } from "@/lib/gamification"

export async function GET(
req: NextRequest,
{ params }: { params: Promise<{ id: string }> }
) {
const configStatus = getNotionConfigStatus()
if (!configStatus.configured) {
return NextResponse.json(
{ error: "Notion integration not configured", missing: configStatus.missing },
{ status: 503 }
)
}

try {
const { id } = await params
const fork = await getFork(id)

if (!fork) {
return NextResponse.json({ error: "Fork not found" }, { status: 404 })
}

// Fetch related data
const [events, members, reports] = await Promise.all([
getEvents({ forkId: id }),
getTeamMembers({ forkId: id }),
getReports({ forkId: id, limit: 5 }),
])

// Calculate health score
const healthResult = calculateHealthScore(fork)
const recommendations = getHealthRecommendations(healthResult.breakdown)

// Get level info
const levelInfo = getLevelInfo(fork.level)
const nextLevel = getPointsToNextLevel(fork.points)

return NextResponse.json({
fork: {
...fork,
healthScore: healthResult.score,
healthStatus: healthResult.status,
healthBreakdown: healthResult.breakdown,
},
level: {
current: fork.level,
...levelInfo,
pointsToNextLevel: nextLevel.pointsNeeded,
nextLevel: nextLevel.next,
},
events,
members,
reports,
recommendations,
})
} catch (error) {
console.error("[API /forks/[id]] Error:", error)

if (error instanceof NotionConfigError) {
return NextResponse.json({ error: error.message }, { status: 503 })
}

if (error instanceof NotionDatabaseError) {
return NextResponse.json({ error: error.message }, { status: 500 })
}

return NextResponse.json(
{ error: "Failed to fetch fork details. Please try again later." },
{ status: 500 }
)
}
}
Loading