Venue Closure Reporting
Feature: User-submitted closure reports with admin review workflow
Users can report venues as closed directly from the venue detail view. Reports are queued for admin/dev review before the venue is actually marked as closed in the system.
User Flow
User views venue details
- Navigates to a venue → "Learn more about this venue"
- Sees "Report this place as closed" button at bottom of details
User submits closure report
- Clicks "Report this place as closed"
- Modal opens with form:
- Reason (required dropdown):
- Permanently closed
- Temporarily closed
- Relocated to new address
- Never existed / Wrong information
- Other
- Additional details (optional text, max 500 chars)
- Reason (required dropdown):
- Submits report
- Success message shows: "Report submitted! We'll review this soon."
Report stored for review
- Report saved to
venueClosureReportscollection - Status:
pending - Venue remains active and visible
- Report saved to
Admin reviews report (via npm script)
- Admin runs:
npm run review:closures --env=dev(or--env=prod) - Tool shows pending reports with venue details
- Admin chooses: Approve, Reject, or Skip
- If approved → Venue status updated to
closed, hidden from app - If rejected → Report marked as rejected, venue remains active
- Admin runs:
Technical Architecture
Frontend Components
Location: src/components/dashboard/VenueView.jsx
- "Report this place as closed" button in venue details
- Opens
ClosureReportModalon click - Shows success message after submission
Modal: src/components/dashboard/ClosureReportModal.jsx
- Dropdown for closure reason
- Textarea for additional details (optional)
- Submit/Cancel actions
- Loading states and error handling
Backend Service
Service: src/lib/venueClosureReportService.js
Functions:
createClosureReport(venueId, userId, reason, details)- Submit new reportgetPendingClosureReports()- Fetch all pending reports (admin)getVenueClosureReports(venueId)- Get all reports for specific venueapproveClosureReport(reportId, adminId, notes)- Approve reportrejectClosureReport(reportId, adminId, notes)- Reject reportgetClosureReportStats()- Get stats (total, pending, approved, rejected)
Firestore Schema
Collection: venueClosureReports
Document structure:
{
venueId: string, // ID of reported venue
venueName: string, // Venue name (for display)
venueAddress: string|null, // Venue address
venueCategory: string|null,// Venue category
reportedBy: string, // User ID who submitted report
reason: string, // Closure reason (enum)
details: string|null, // Optional additional details
status: string, // 'pending' | 'approved' | 'rejected'
createdAt: timestamp, // When report was created
updatedAt: timestamp, // Last update
reviewedAt: timestamp|null,// When admin reviewed
reviewedBy: string|null, // Admin ID who reviewed
reviewNotes: string|null // Admin notes (optional)
}Security Rules: firestore.rules
- Create: Authenticated users only, status must be 'pending'
- Read: Authenticated users (admins only in practice)
- Update: Blocked (use admin script only)
- Delete: Blocked (audit trail)
Admin Review Tool
Script: scripts/review-closure-reports.mjs
Usage:
# Review dev reports
npm run review:closures -- --env=dev
# Review prod reports
npm run review:closures -- --env=prodWorkflow:
- Admin runs script with environment flag
- Prompted for admin ID (for audit trail)
- Script shows stats: total, pending, approved, rejected
- For each pending report:
- Displays venue info, reason, details, reporter, date
- Prompts for action:
[A]pprove | [R]eject | [S]kip | [Q]uit - If approved: Venue status → 'closed' (hidden from app)
- If rejected: Report status → 'rejected' (venue stays active)
- Changes committed to Firestore in batches
Requirements:
- Firebase Admin SDK
- Service account at:
~/repos/secrets/firebase/lantern-app-{env}-firebase-adminsdk.json
Workflow Examples
Example: False Report (Venue Still Open)
- User reports "Coffee Lab" as permanently closed
- Admin reviews report
- Admin verifies venue is still open (checks Google Maps, social media)
- Admin rejects report with note: "Verified open via Instagram post today"
- Report marked as rejected, venue remains active
- Venue continues to show in app
Example: Confirmed Closure
- User reports "Books & Brews" as permanently closed
- Admin reviews report
- Admin confirms closure (Google shows "Permanently closed")
- Admin approves report
- Venue status updated to 'closed'
- Venue hidden from app (no longer shows in search/map)
- Existing lanterns at venue auto-expire per normal TTL
Example: Duplicate Reports
- Multiple users report same venue
- All reports stored separately with
pendingstatus - Admin approves first report → venue marked closed
- Subsequent reports can be bulk-approved or skipped (venue already closed)
Data Flow Diagram
┌─────────────────────────────────────────────────────────────┐
│ USER REPORTS CLOSURE │
└─────────────────────────────────────────────────────────────┘
│
▼
┌──────────────────────┐
│ VenueView.jsx │
│ (Detail screen) │
└──────────────────────┘
│
▼
┌──────────────────────┐
│ ClosureReportModal │
│ (Reason + details) │
└──────────────────────┘
│
▼
┌────────────────────────────────────┐
│ venueClosureReportService.js │
│ createClosureReport() │
└────────────────────────────────────┘
│
▼
┌────────────────────────────────────┐
│ Firestore Collection │
│ 'venueClosureReports' │
│ status: 'pending' │
└────────────────────────────────────┘
│
│ (Waits for admin review)
▼
┌─────────────────────────────────────────────────────────────┐
│ ADMIN REVIEWS VIA NPM SCRIPT │
└─────────────────────────────────────────────────────────────┘
│
▼
┌────────────────────────────────────┐
│ npm run review:closures --env=dev │
│ (Interactive CLI tool) │
└────────────────────────────────────┘
│
┌───────┴───────┐
│ │
▼ ▼
[Approve] [Reject]
│ │
▼ ▼
┌──────────────────┐ ┌──────────────────┐
│ Venue status → │ │ Report status → │
│ 'closed' │ │ 'rejected' │
│ │ │ │
│ Hidden from app │ │ Venue stays │
│ │ │ active │
└──────────────────┘ └──────────────────┘Testing
Manual Testing Steps
Test report submission:
- Sign in as a user
- Navigate to any venue details
- Click "Report this place as closed"
- Select reason, add optional details
- Submit and verify success message
Verify Firestore data:
bash# In Firebase console, check venueClosureReports collection # Should see new document with: # - status: 'pending' # - reportedBy: {your user ID} # - reason, details, timestampsTest admin review (dev):
bashnpm run review:closures --env=dev # Enter your admin ID # Approve a report # Verify venue status changed to 'closed' in venues collectionTest rejection:
bashnpm run review:closures --env=dev # Reject a report # Verify venue status remains 'open' # Verify report status changed to 'rejected'
Future Enhancements
V2 Features
- Admin web dashboard - Replace CLI tool with web UI
- Bulk actions - Approve/reject multiple reports at once
- Auto-flagging - Auto-approve if X users report same venue
- OSM sync - Cross-check reports against OSM data
- Notification system - Notify reporters when their report is reviewed
- Report analytics - Track false positive rates, reporter accuracy
Production Considerations
- Admin role management - Use Firebase custom claims for admin roles
- Rate limiting - Prevent spam reports from single user
- Report validation - Check if user has actually been to venue
- Appeal process - Allow venue owners to dispute closures
- Automated verification - Integrate with Google Places API to auto-verify closures
Related Documentation
- Venue Status Service - Core venue status management
- VenueView Component - Venue detail UI
- Firestore Security Rules - Data access controls
- Venue Import Guide - OSM import workflow
Changelog
2026-01-23 - Initial implementation
- User-facing closure report button in venue details
- Modal for submitting reports with reason + details
- Firestore collection for pending reports
- Interactive npm script for admin review
- Firestore security rules for closure reports
- Complete documentation