first commit

This commit is contained in:
sandhiya-hepl
2026-07-14 15:20:26 +05:30
commit 4b8af23f5f
125 changed files with 15124 additions and 0 deletions
@@ -0,0 +1,398 @@
# Dynamic CMS with Admin Panel — Design Spec
**Date:** 2026-07-09
**Status:** Approved (architecture)
**Goal:** Convert the static CITPL marketing site into a dynamic site with instant content updates via a password-protected admin panel hosted on a VPS.
---
## Requirements
| Requirement | Decision |
|-------------|----------|
| Content updates | Instant — no rebuild required |
| Data model | Latest data only (no versioning/drafts) |
| Hosting | Self-hosted VPS |
| Admin users | Single admin login |
| Editable content | Text, links, and image/video uploads |
| Public site | Existing React SPA, same visual design |
---
## Architecture
```
┌──────────────────────────────────────────────────────────┐
│ VPS (Ubuntu + Nginx) │
│ │
│ citpl.com/ citpl.com/admin │
│ │ │ │
│ ▼ ▼ │
│ React SPA (dist/) Admin SPA (dist/admin/) │
│ │ │ │
│ └────────────┬────────────┘ │
│ ▼ │
│ Express API :3001 │
│ GET /api/content (public) │
│ POST /api/auth/login (public) │
│ PUT /api/content/:section (auth required) │
│ POST /api/upload (auth required) │
│ │ │
│ ┌──────────┴──────────┐ │
│ ▼ ▼ │
│ SQLite (content.db) /var/www/citpl/uploads/ │
│ - admin user images, videos, logos │
│ - content sections │
└──────────────────────────────────────────────────────────┘
```
### Tech Stack
| Layer | Technology |
|-------|------------|
| Public frontend | React 19 + Vite 8 (existing) |
| Admin frontend | React 19 + Vite 8 (new entry point) |
| API | Express.js 4 |
| Database | better-sqlite3 (SQLite) |
| Auth | bcrypt + jsonwebtoken (JWT, 24h expiry) |
| File uploads | multer (max 50MB, images + video) |
| Process manager | PM2 |
| Reverse proxy | Nginx |
---
## Content Schema
All site content is stored as JSON documents in a single `content` table, keyed by section ID. On first boot, the API seeds the database from current hardcoded values.
### Sections
```typescript
type SiteContent = {
site: {
title: string
logo: string // URL path e.g. /uploads/logo.svg
copyright: string
}
navigation: Array<{
label: string
href: string
type: 'hash' | 'modal'
}>
hero: {
video: string
badge: string
headline: string
subheadline: string
}
metrics: Array<{
value: string
label: string
}>
about: {
heading: string
badgeImage: string
badgeAlt: string
backgroundImage: string
}
partners: {
heading: string
subheading: string
body: string
logos: Array<{ name: string; image: string }>
}
certifications: {
heading: string
items: Array<{ image: string; alt: string }>
}
services: {
eyebrow: string
title: string
intro: string
items: Array<{
id: string
number: string
title: string
boldStatement: string
description: string
capabilities: string[]
cta: string
glowColor: string
borderColor: string
}>
}
whyUs: {
eyebrow: string
title: string
cards: Array<{ title: string; description: string }>
images: Array<{ src: string; alt: string }>
}
gallery: {
eyebrow: string
title: string
subtitle: string
items: Array<{
id: number
title: string
category: string
excerpt: string
image: string
readTime: string
}>
}
products: {
eyebrow: string
title: string
subtitle: string
items: Array<{
name: string
description: string
image: string
stats: Array<{ value: string; label: string }>
}>
}
insights: {
title: string
subtitle: string
items: Array<{
category: string
title: string
date: string
image: string
excerpt: string
}>
}
footer: {
cta: {
headline: string
body: string
image: string
primaryButton: string
secondaryButton: string
}
quickLinks: Array<{ label: string; href: string }>
productLinks: Array<{ label: string; href: string }>
social: Array<{ platform: string; url: string }>
legal: Array<{ label: string; href: string }>
}
}
```
---
## API Design
### Public Endpoints
| Method | Path | Description |
|--------|------|-------------|
| GET | `/api/content` | Returns full `SiteContent` object |
| GET | `/api/health` | Health check for PM2/Nginx |
### Auth Endpoints
| Method | Path | Body | Response |
|--------|------|------|----------|
| POST | `/api/auth/login` | `{ username, password }` | `{ token, expiresAt }` |
| GET | `/api/auth/me` | Bearer token | `{ username }` |
### Protected Endpoints (require `Authorization: Bearer <token>`)
| Method | Path | Body | Description |
|--------|------|------|-------------|
| PUT | `/api/content/:section` | Section JSON | Update one section (e.g. `hero`, `services`) |
| PUT | `/api/content` | Full `SiteContent` | Bulk update all sections |
| POST | `/api/upload` | `multipart/form-data` field `file` | Upload image/video, returns `{ url }` |
| DELETE | `/api/upload/:filename` | — | Remove uploaded file |
### Error Responses
```json
{ "error": "Human-readable message", "code": "UNAUTHORIZED" }
```
HTTP codes: 400 (validation), 401 (auth), 404 (not found), 413 (file too large), 500 (server).
---
## Admin Panel
**URL:** `/admin`
**Login:** Single username/password set via environment variable on first deploy.
### Pages
| Page | Edits |
|------|-------|
| Dashboard | Quick links to all sections, last-updated timestamp |
| Hero & Metrics | Headline, video upload, badge text, stat values |
| About & Partners | Headings, partner logos (upload + reorder) |
| Certifications | Upload/replace badge images |
| Services | CRUD for all 6 service cards |
| Why Us | 4 value cards + 3 image uploads |
| Gallery | CRUD gallery items with image upload |
| Products | 3 product cards with stats and screenshots |
| Insights | CRUD blog cards with image upload |
| Footer & Links | CTA text, social URLs, quick links |
| Settings | Change admin password |
### Admin UX
- Sidebar navigation matching section list above
- Inline form fields with save button per section
- Image fields show current preview + upload/replace button
- Toast notifications on save success/failure
- Redirect to `/admin/login` if token expired
---
## Frontend Changes (Public Site)
1. **Content hook**`useContent()` fetches `/api/content` on mount, shows loading skeleton, caches in React context
2. **Refactor `App.jsx`** — Replace hardcoded strings/arrays with `content.hero`, `content.services`, etc.
3. **Refactor `FeaturedGallery.jsx`** — Accept `content.gallery` as prop instead of inline data
4. **Image URLs** — All image paths become absolute URLs from API (`/uploads/...` or external)
5. **Fallback** — If API unreachable, show cached content or graceful error banner (not blank page)
### New Files
```
server/
index.js # Express app entry
db.js # SQLite setup + seed
auth.js # JWT middleware
routes/
content.js
auth.js
upload.js
seed/
default-content.json # Current site content extracted from App.jsx
src/
context/ContentContext.jsx
hooks/useContent.js
admin/
main.jsx
App.jsx
pages/
Login.jsx
Dashboard.jsx
HeroEditor.jsx
ServicesEditor.jsx
... (one per section)
components/
ImageUpload.jsx
SectionForm.jsx
AdminLayout.jsx
```
---
## Security
| Concern | Mitigation |
|---------|------------|
| Brute-force login | Rate limit `/api/auth/login` (5 attempts/min per IP) |
| JWT theft | HttpOnly not possible cross-origin; use short 24h expiry + secure flag in production |
| File uploads | Whitelist MIME types: `image/*`, `video/mp4`; max 50MB; sanitize filenames |
| SQL injection | Parameterized queries only (better-sqlite3 prepared statements) |
| Admin password | bcrypt hash (cost 12); initial password via `ADMIN_PASSWORD` env var |
| CORS | Restrict to production domain only |
---
## Deployment (VPS)
### Environment Variables
```env
PORT=3001
JWT_SECRET=<random-64-char-string>
ADMIN_USERNAME=admin
ADMIN_PASSWORD=<set-on-first-deploy>
UPLOAD_DIR=/var/www/citpl/uploads
DB_PATH=/var/www/citpl/data/content.db
NODE_ENV=production
CORS_ORIGIN=https://citpl.com
```
### Nginx Config (summary)
```nginx
server {
listen 80;
server_name citpl.com;
# Public site
location / {
root /var/www/citpl/dist;
try_files $uri $uri/ /index.html;
}
# Admin panel
location /admin {
alias /var/www/citpl/dist-admin;
try_files $uri $uri/ /admin/index.html;
}
# API
location /api {
proxy_pass http://127.0.0.1:3001;
}
# Uploaded media
location /uploads {
alias /var/www/citpl/uploads;
expires 30d;
}
}
```
### PM2
```bash
pm2 start server/index.js --name citpl-api
pm2 save
```
### Deploy Script
```bash
npm run build # public site → dist/
npm run build:admin # admin panel → dist-admin/
rsync dist/ dist-admin/ server/ → VPS
pm2 restart citpl-api
```
---
## Out of Scope (v1)
- Multiple admin users / roles
- Content versioning or drafts
- Careers / Strategy Call form backend (keep EmailJS as-is)
- Blog detail pages (insights remain cards only)
- SSL setup (assumed handled separately via Certbot)
- CI/CD pipeline (manual deploy script provided)
---
## Success Criteria
1. Admin can log in at `/admin` with username/password
2. Admin can edit any section and save — changes appear on public site within seconds (page refresh)
3. Admin can upload/replace images and hero video
4. Public site loads all content from API with no hardcoded copy remaining
5. API + site run stably on a 1GB RAM VPS via PM2