From 0d9c7d726ecf561ef673f5fd685bac9250d02a51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Blicharz?= Date: Tue, 23 Sep 2025 21:34:00 +0200 Subject: [PATCH 01/18] feat: make a development plan feat: Add basic json data storage and build subpages feat: Add mongodb connection feat: Add basic admin UI and db authentication --- .gitignore | 4 +- @DEVELOPMENT_ROADMAP.md | 217 ++ docker-compose.yml | 6 +- frontend/Dockerfile | 32 +- frontend/app/(home)/_components/board.tsx | 82 +- frontend/app/(home)/_components/projects.tsx | 60 +- frontend/app/(home)/page.tsx | 2 - frontend/app/admin/board/page.tsx | 275 +++ frontend/app/admin/layout.tsx | 118 + frontend/app/admin/page.tsx | 312 +++ frontend/app/admin/projects/page.tsx | 227 ++ frontend/app/admin/settings/page.tsx | 133 ++ frontend/app/api/admin/auth/route.ts | 46 + frontend/app/api/admin/projects/[id]/route.ts | 83 + frontend/app/api/admin/projects/route.ts | 57 + frontend/app/api/board/route.ts | 15 + frontend/app/api/projects/route.ts | 15 + frontend/app/board/page.tsx | 207 ++ frontend/app/layout.tsx | 24 + frontend/app/projects/[slug]/page.tsx | 223 ++ frontend/app/projects/page.tsx | 194 ++ frontend/components/plug.tsx | 14 - frontend/components/ui/badge.tsx | 36 + frontend/components/ui/dropdown-menu.tsx | 200 ++ frontend/components/ui/input.tsx | 22 + frontend/data/.gitkeep | 1 + frontend/data/board_members.json | 74 + frontend/data/projects.json | 54 + frontend/data/seed.ts | 252 +++ frontend/env.example | 3 + frontend/lib/auth.ts | 45 + frontend/lib/db.ts | 7 + frontend/lib/models/AdminUser.ts | 67 + frontend/lib/models/BoardMember.ts | 56 + frontend/lib/models/Project.ts | 65 + frontend/lib/mongodb.ts | 56 + frontend/lib/repositories/adminAuth.ts | 122 + frontend/lib/repositories/boardMembers.ts | 167 ++ frontend/lib/repositories/index.ts | 6 + frontend/lib/repositories/projects.ts | 166 ++ frontend/lib/types.ts | 67 + frontend/middleware.ts | 45 + frontend/next.config.js | 13 +- frontend/package-lock.json | 1979 ++++++++++++++--- frontend/package.json | 14 +- frontend/scripts/create-admin.ts | 54 + 46 files changed, 5590 insertions(+), 327 deletions(-) create mode 100644 @DEVELOPMENT_ROADMAP.md create mode 100644 frontend/app/admin/board/page.tsx create mode 100644 frontend/app/admin/layout.tsx create mode 100644 frontend/app/admin/page.tsx create mode 100644 frontend/app/admin/projects/page.tsx create mode 100644 frontend/app/admin/settings/page.tsx create mode 100644 frontend/app/api/admin/auth/route.ts create mode 100644 frontend/app/api/admin/projects/[id]/route.ts create mode 100644 frontend/app/api/admin/projects/route.ts create mode 100644 frontend/app/api/board/route.ts create mode 100644 frontend/app/api/projects/route.ts create mode 100644 frontend/app/board/page.tsx create mode 100644 frontend/app/layout.tsx create mode 100644 frontend/app/projects/[slug]/page.tsx create mode 100644 frontend/app/projects/page.tsx delete mode 100644 frontend/components/plug.tsx create mode 100644 frontend/components/ui/badge.tsx create mode 100644 frontend/components/ui/dropdown-menu.tsx create mode 100644 frontend/components/ui/input.tsx create mode 100644 frontend/data/.gitkeep create mode 100644 frontend/data/board_members.json create mode 100644 frontend/data/projects.json create mode 100644 frontend/data/seed.ts create mode 100644 frontend/env.example create mode 100644 frontend/lib/auth.ts create mode 100644 frontend/lib/db.ts create mode 100644 frontend/lib/models/AdminUser.ts create mode 100644 frontend/lib/models/BoardMember.ts create mode 100644 frontend/lib/models/Project.ts create mode 100644 frontend/lib/mongodb.ts create mode 100644 frontend/lib/repositories/adminAuth.ts create mode 100644 frontend/lib/repositories/boardMembers.ts create mode 100644 frontend/lib/repositories/index.ts create mode 100644 frontend/lib/repositories/projects.ts create mode 100644 frontend/lib/types.ts create mode 100644 frontend/middleware.ts create mode 100644 frontend/scripts/create-admin.ts diff --git a/.gitignore b/.gitignore index ad25d06..602c85b 100644 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,6 @@ .Spotlight-V100 .Trashes ehthumbs.db -Thumbs.db \ No newline at end of file +Thumbs.db +/frontend/.env + diff --git a/@DEVELOPMENT_ROADMAP.md b/@DEVELOPMENT_ROADMAP.md new file mode 100644 index 0000000..c811883 --- /dev/null +++ b/@DEVELOPMENT_ROADMAP.md @@ -0,0 +1,217 @@ +### Gradient Science Club Website — Development Roadmap + +This roadmap will guide the build of a small, maintainable website for a science club. It prioritizes simplicity, low maintenance, and a minimal CMS for updating Projects and Board Members. We'll update this document as we progress. + +--- + +### Goals +- **Showcase club initiatives and projects**: Simple pages with project listings and details. +- **Introduce the club**: Board members page with roles and bios. +- **Minimal CMS**: Single-admin, basic CRUD for Projects and Board Members. +- **Keep it simple**: Prefer boring, proven tools new members can maintain. + +### Non‑Goals +- **No complex auth**: Only one admin account; no multi-user, roles, or OAuth. +- **No advanced CMS features**: No media library, workflows, or versioning (beyond Git). +- **No external databases**: Use a single local SQLite file; no managed DB. +- **No heavy backend**: Keep everything inside the existing Next.js app. + +--- + +### Proposed Architecture (simple by design) +- **Frontend and CMS**: Next.js (App Router) + TypeScript + Tailwind CSS (already present in `frontend/`). +- **Data storage**: + - **Current (Phase 1-2)**: JSON files for development and prototyping (`projects.json`, `board_members.json`) + - **Target (Phase 3+)**: MongoDB Atlas free cluster (cloud-hosted, managed database) +- **Data access**: Repository pattern with simple data-access layer. Currently using direct JSON file operations, will migrate to MongoDB with `mongoose` for CMS functionality. +- **Admin access**: HTTP Basic Auth via Next.js `middleware` using env variables (`ADMIN_USER`, `ADMIN_PASS`). Protects `/admin` routes and admin API endpoints. +- **Images**: Use external image URLs to avoid upload/storage complexity for now. Can add uploads later if needed. +- **Deployment**: Single Docker service with a bind-mounted `data/` volume. Serve a production build (`npm run build` + `npm run start`). + +Why not a headless CMS (Strapi, Sanity) or Git-based CMS (Decap)? They add setup, auth, hosting, or learning overhead. For a tiny site with one admin, an in-app minimal CMS is easier to own and hand over. + +**Note**: The current JSON-based storage is a temporary solution for development. The CMS (Phase 3) will migrate to MongoDB Atlas for professional database features, cloud backup, scalability, and admin interface reliability. + +--- + +### Tech Stack +- **Runtime**: Node.js 20 (LTS) +- **Web**: Next.js 14 (App Router), TypeScript, Tailwind CSS +- **DB**: + - **Current**: JSON files (`frontend/data/*.json`) + - **Target**: MongoDB Atlas (free tier, cloud-hosted) +- **DB Access**: + - **Current**: Direct JSON file operations via `fs` + - **Target**: `mongoose` ODM (for CMS) +- **Auth**: HTTP Basic Auth (env-configured) +- **Container**: Docker (multi-stage build) + Docker Compose (single service, volume for `data/`) + +--- + +### Data Model (initial) +- **Project** + - `id` (integer, PK) + - `title` (text) + - `slug` (text, unique) + - `description` (text) + - `imageUrl` (text) + - `tags` (text, comma/JSON) + - `status` (text; e.g., planned/active/completed) + - `links` (text, JSON-encoded) + - `displayOrder` (integer) + - `createdAt` (datetime) + - `updatedAt` (datetime) +- **BoardMember** + - `id` (integer, PK) + - `name` (text) + - `role` (text) + - `photoUrl` (text) + - `bio` (text) + - `socials` (text, JSON-encoded) + - `displayOrder` (integer) + - `active` (boolean) + - `createdAt` (datetime) + - `updatedAt` (datetime) + +--- + +### Routing Plan +- **Public** + - `/` — Home: brief intro, featured projects + - `/projects` — List all projects + - `/projects/[slug]` — Project detail + - `/board` — Board members + - `/about` — Club description (optional) +- **Admin (protected)** + - `/admin` — Dashboard + - `/admin/projects` — List + create/edit/delete projects + - `/admin/board` — List + create/edit/delete board members + +--- + +### Security +- **HTTP Basic Auth** for `/admin` and `/api/admin/*` via `middleware.ts`. +- **HTTPS in production** via hosting/proxy (out of scope for this repo). +- **CSRF**: Keep admin within same origin; use POST for mutations; no cross-origin. +- **Secrets**: `ADMIN_USER`, `ADMIN_PASS` stored in environment (not committed). + +--- + +### Deployment +- **Compose**: One service (Next.js app), bind-mount `./frontend/data/` to persist `site.db`. +- **Prod run**: Build once, run with `npm run start` (not dev server). +- **Backups**: Copy `frontend/data/site.db` periodically. + +--- + +### Project Structure (planned) +- `frontend/app/(public)/*` — Public pages +- `frontend/app/(admin)/*` — Admin UI +- `frontend/app/api/*` — API routes (public read + admin CRUD under `/api/admin/*`) +- `frontend/lib/db.ts` — SQLite connection helper +- `frontend/lib/repositories/*` — Data-access functions +- `frontend/data/` — SQLite file (`site.db`), schema and seed scripts +- `frontend/middleware.ts` — Basic Auth protection + +--- + +### Phased Task Breakdown + +- **Phase 0: Repo & Build Hardening** + - [x] Switch Docker to production build/run: `npm ci && npm run build` then `npm run start`. + - [x] Add `frontend/data/` directory and ensure it is persisted via Docker volume. + - [x] Document `.env` with `ADMIN_USER` and `ADMIN_PASS`. + +- **Phase 1: Data Layer** ✅ COMPLETED (JSON-based interim solution) + - [x] Add schema SQL for `projects` and `board_members` tables (for future SQLite migration). + - [x] Implement `frontend/lib/db.ts` using JSON file operations (temporary solution). + - [x] Implement repositories: `projectsRepo`, `boardMembersRepo` with CRUD. + - [x] Seed script to create JSON files and seed sample data. + +- **Phase 2: Public Pages** ✅ COMPLETED + - [x] `/projects` list page (static + revalidate) using DB reads. + - [x] `/projects/[slug]` detail page. + - [x] `/board` page with member cards. + - [x] Home page with featured projects (updated existing components). + +- **Phase 3: Admin CMS** + - **Phase 3.1: Database Migration & Auth Foundation** ✅ COMPLETED + - [x] Set up MongoDB Atlas free cluster and obtain connection string (requires user setup) + - [x] Install and configure `mongoose` package + - [x] Create MongoDB connection and database models + - [x] Migrate repository layer from JSON to MongoDB operations + - [x] Create data migration script (JSON → MongoDB) + - [x] Test database operations and ensure data integrity (requires MongoDB setup) + - [x] Implement HTTP Basic Auth middleware for `/admin` routes + - [x] Create admin environment variables and configuration + - **Phase 3.2: Admin UI Foundation** ✅ COMPLETED + - [x] Create admin layout component with navigation + - [x] Set up admin routing structure (`/admin`, `/admin/projects`, `/admin/board`) + - [x] Create admin dashboard with overview stats + - [x] Implement auth-protected admin pages + - [x] Add admin-specific styling and components + - **Phase 3.3: Projects Management** + - [ ] Admin API routes for projects CRUD (`/api/admin/projects/*`) + - [ ] Projects list page with table view and actions + - [ ] Create project form with validation + - [ ] Edit project form with pre-populated data + - [ ] Delete project with confirmation modal + - [ ] Bulk actions (delete multiple, change status) + - **Phase 3.4: Board Members Management** + - [ ] Admin API routes for board members CRUD (`/api/admin/board/*`) + - [ ] Board members list page with table view + - [ ] Create board member form with validation + - [ ] Edit board member form with pre-populated data + - [ ] Delete board member with confirmation + - [ ] Reorder members with drag-and-drop functionality + - **Phase 3.5: Form Validation & UX** + - [ ] Client-side form validation with error messages + - [ ] Server-side validation and error handling + - [ ] Success/error toast notifications + - [ ] Loading states and form submission feedback + - [ ] Auto-save drafts functionality + - [ ] Form field helpers (slug generation, etc.) + +- **Phase 4: Polish & Docs** + - [ ] SEO: `` tags, OpenGraph, sitemap, robots. + - [ ] Error boundaries and not-found pages. + - [ ] README for onboarding and operations (backups, env, updating content). + +- **Phase 5: Deploy** + - [ ] Update `docker-compose.yml` to mount `./frontend/data:/app/data` (or equivalent internal path). + - [ ] Run migrations/seed in entrypoint if DB is missing. + - [ ] Verify admin protection and content updates in prod. + +--- + +### Hand‑over Notes (for future club members) +- **Update content** via `/admin` using the single admin account. +- **Back up** the `site.db` file; that’s where all content lives. +- **No need** to learn a big CMS; this is intentionally small. +- **If needs grow**, consider a hosted headless CMS and swap repositories later. + +--- + +### Open Questions +- Do we need image uploads, or are URLs sufficient for now? +- Any must-have fields for projects or board members beyond the current model? +- Hosting environment and HTTPS termination preferences? + +--- + +### Change Log +- 2025-09-23: Initial roadmap created. Chose in-app minimal CMS with SQLite and Basic Auth for simplicity. +- 2025-09-24: **Phase 1 & 2 Completed**: + - Implemented data layer with JSON-based storage (interim solution for development) + - Created repository pattern for CRUD operations with file-based storage + - Developed comprehensive seed script with sample data + - Created public pages: `/projects`, `/projects/[slug]`, `/board` + - Updated existing home page components to use database + - Added responsive UI with Tailwind CSS and shadcn/ui components + - Integrated Lucide React icons for modern iconography + - Implemented proper TypeScript typing throughout + - Added SEO metadata and OpenGraph support + - Built error handling and graceful fallbacks + - Fixed build compatibility issues and ensured production-ready deployment + - Fixed navigation between home and projects pages + - **Note**: JSON storage is temporary; Phase 3 will migrate to MongoDB Atlas for CMS functionality \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index e8bd819..ab4391a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -2,10 +2,12 @@ version: "3.7" services: gradient: - command: npm run dev + command: npm run start image: gradientpg/website:2.1 build: context: ./frontend dockerfile: Dockerfile ports: - - 6007:3000 \ No newline at end of file + - 6007:3000 + volumes: + - ./frontend/data:/app/data \ No newline at end of file diff --git a/frontend/Dockerfile b/frontend/Dockerfile index 62eab41..e412952 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -1,13 +1,31 @@ -FROM node:18-alpine +# Stage 1: build +FROM node:20-alpine AS builder +WORKDIR /app +COPY package*.json ./ +RUN npm ci +COPY . . +RUN npm run build -WORKDIR /frontend +# Stage 2: run +FROM node:20-alpine AS runner +WORKDIR /app +ENV NODE_ENV=production -COPY package*.json ./ +# Create non-root user for safety +RUN addgroup -S nextjs && adduser -S nextjs -G nextjs -RUN npm install +# Copy package files for production dependencies +COPY --from=builder /app/package*.json ./ +RUN npm ci --omit=dev && npm cache clean --force -COPY . . +# Copy built application +COPY --from=builder /app/.next ./.next +COPY --from=builder /app/public ./public +COPY --from=builder /app/next.config.js ./next.config.js -EXPOSE 3000 +# Create data directory for SQLite +RUN mkdir -p /app/data && chown -R nextjs:nextjs /app -CMD npm run dev \ No newline at end of file +USER nextjs +EXPOSE 3000 +CMD ["npm", "run", "start"] \ No newline at end of file diff --git a/frontend/app/(home)/_components/board.tsx b/frontend/app/(home)/_components/board.tsx index 666f967..6eba6ad 100644 --- a/frontend/app/(home)/_components/board.tsx +++ b/frontend/app/(home)/_components/board.tsx @@ -1,13 +1,37 @@ import React from "react"; +import Link from "next/link"; import { cn } from "@/lib/utils"; -import boardInfo from "@/public/data/board.json"; +import { boardMembersRepo } from "@/lib/repositories"; +import type { BoardMember, MemberSocials } from "@/lib/types"; import Image from "next/image"; +import { Button } from "@/components/ui/button"; interface BoardProps extends React.HTMLProps {} -const Board: React.FC = ({ ...props }) => { - let icons_path = "/images/icons/"; +// Server component to fetch active board members +async function getActiveBoardMembers(): Promise { + try { + return await boardMembersRepo.findActive(); + } catch (error) { + console.error('Failed to fetch board members:', error); + return []; + } +} + +// Helper function to parse JSON socials +function parseSocials(socials?: string): MemberSocials { + if (!socials) return {}; + try { + return JSON.parse(socials); + } catch { + return {}; + } +} + +const Board: React.FC = async ({ ...props }) => { + const boardMembers = await getActiveBoardMembers(); + return (
= ({ ...props }) => {

Our Board

- {boardInfo.members.map((member, index) => { + {boardMembers.map((member) => { + const socials = parseSocials(member.socials); return (
-
- icons +
+ {member.photoUrl ? ( + {`${member.name} + ) : ( +
+ {member.name.charAt(0)} +
+ )}
-
+

- {member.name} {member.surname} + {member.name}

- {member.position} + {member.role}

+ {member.bio && ( +

+ {member.bio} +

+ )}
); })}
+ {boardMembers.length === 0 && ( +
+

No board members information available.

+
+ )} + + {/* View All Board Members Button */} + {boardMembers.length > 0 && ( +
+ +
+ )}
); }; diff --git a/frontend/app/(home)/_components/projects.tsx b/frontend/app/(home)/_components/projects.tsx index e3c2352..b290ac0 100644 --- a/frontend/app/(home)/_components/projects.tsx +++ b/frontend/app/(home)/_components/projects.tsx @@ -3,14 +3,25 @@ import React from "react"; import { Button } from "@/components/ui/button"; import { cn } from "@/lib/utils"; -import projectsFile from "@/public/data/projects.json"; +import { projectsRepo } from "@/lib/repositories"; +import type { Project } from "@/lib/types"; import Image from "next/image"; import Link from "next/link"; interface ProjectsProps extends React.HTMLProps {} -const Projects: React.FC = ({ ...props }) => { - let projects_path = "/images/projects/"; +// Server component to fetch featured projects +async function getFeauturedProjects(): Promise { + try { + return await projectsRepo.findFeatured(6); // Get up to 6 featured projects + } catch (error) { + console.error('Failed to fetch featured projects:', error); + return []; + } +} + +const Projects: React.FC = async ({ ...props }) => { + const projects = await getFeauturedProjects(); return (
= ({ ...props }) => { "mt-12 flex flex-col flex-wrap gap-4 p-4 md:grid md:grid-cols-3", )} > - {projectsFile.projects.map((project, index) => { + {projects.map((project) => { return ( - +
{project.name}
- {project.name} + {project.title} +

+ {project.description} +

- + +
+ + {project.status} + +
); })}
+ {projects.length === 0 && ( +
+

No projects available at the moment.

+
+ )} + + {/* View All Projects Button */} + {projects.length > 0 && ( +
+ +
+ )}
); }; diff --git a/frontend/app/(home)/page.tsx b/frontend/app/(home)/page.tsx index 720053a..72042de 100644 --- a/frontend/app/(home)/page.tsx +++ b/frontend/app/(home)/page.tsx @@ -1,5 +1,4 @@ import Footer from "@/components/footer"; -import Plug from "@/components/plug"; import Board from "./_components/board"; import Cards from "./_components/cards"; import Hero from "./_components/hero"; @@ -13,7 +12,6 @@ export default function Home() {