diff --git a/.github/workflows/security-checks.yml b/.github/workflows/security-checks.yml new file mode 100644 index 0000000..11ac5ef --- /dev/null +++ b/.github/workflows/security-checks.yml @@ -0,0 +1,162 @@ +name: Security Checks + +on: + pull_request: + branches: [ "develop" ] + +jobs: + security: + runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: write # Needed to comment on PR + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: npm + + - name: Install dependencies + run: npm ci + + # ------------------------------- + # 1️⃣ TypeScript Type Checking + # ------------------------------- + - name: TypeScript Check + id: tsc + run: | + npx tsc --noEmit > ts-report.txt 2>&1 || true + cat ts-report.txt + continue-on-error: true + - uses: actions/upload-artifact@v4 + with: + name: typescript-report + path: ts-report.txt + + # ------------------------------- + # 2️⃣ ESLint with Security Plugin + # ------------------------------- + - name: ESLint Security Lint + id: eslint + run: | + npx eslint . --ext .ts,.tsx -f json -o eslint-report.json || true + cat eslint-report.json + continue-on-error: true + - uses: actions/upload-artifact@v4 + with: + name: eslint-report + path: eslint-report.json + + # ------------------------------- + # 3️⃣ NPM Audit + # ------------------------------- + - name: NPM Audit + id: npm_audit + run: | + npm audit --json > npm-audit-report.json || true + cat npm-audit-report.json + continue-on-error: true + - uses: actions/upload-artifact@v4 + with: + name: npm-audit-report + path: npm-audit-report.json + + # ------------------------------- + # 4️⃣ Semgrep SAST + # ------------------------------- + - name: Semgrep Scan + id: semgrep + uses: returntocorp/semgrep-action@v1 + with: + config: "p/ci" + continue-on-error: true + + # ------------------------------- + # 5️⃣ Secret Scanning with Gitleaks + # ------------------------------- + - name: Gitleaks Scan + id: gitleaks + uses: zricethezav/gitleaks-action@v2 + with: + report_path: gitleaks-report.json + fail: false # Continue even if leaks are found + + - uses: actions/upload-artifact@v4 + with: + name: gitleaks-report + path: gitleaks-report.json + + # ------------------------------- + # 6️⃣ Post Summary as PR Comment + # ------------------------------- + - name: Generate and Post Security Summary + if: always() # Always run this step to post a comment + uses: actions/github-script@v7 + with: + script: | + const fs = require('fs'); + + function readJsonReport(file) { + if (!fs.existsSync(file)) return '⚠️ No report'; + const content = fs.readFileSync(file, 'utf8'); + if (!content.trim()) return '✅ No issues'; + try { + const data = JSON.parse(content); + return (Array.isArray(data) && data.length > 0) || (typeof data === 'object' && Object.keys(data).length > 0 && data.vulnerabilities) ? '❌ Issues found' : '✅ No issues'; + } catch (e) { + return '⚠️ Report invalid'; + } + } + + const tscReport = fs.existsSync('ts-report.txt') && fs.readFileSync('ts-report.txt', 'utf8').trim(); + + const body = ` + ## 🔐 Security Check Summary + + | Check Type | Result | + | --- | --- | + | **TypeScript** | ${{ steps.tsc.outcome == 'success' ? '✅ No issues' : '❌ Issues found' }} | + | **ESLint Security** | ${{ steps.eslint.outcome == 'success' ? '✅ No issues' : '❌ Issues found' }} | + | **NPM Audit** | ${{ steps.npm_audit.outcome == 'success' ? '✅ No issues' : '❌ Issues found' }} | + | **Semgrep SAST** | ${{ steps.semgrep.outcome == 'success' ? '✅ No issues' : '❌ Issues found' }} | + | **Gitleaks Secrets** | ${{ steps.gitleaks.outcome == 'success' ? '✅ No issues' : '❌ Issues found' }} | + + ${tscReport ? ` +
+ TypeScript Details + + ``` + ${tscReport} + ``` + +
+ ` : ''} + + > 📄 Detailed reports are in workflow artifacts. + `; + + github.rest.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: body + }); + + # ------------------------------- + # 7️⃣ Fail job if any check failed + # ------------------------------- + - name: Fail if issues found + if: > + steps.tsc.outcome == 'failure' || + steps.eslint.outcome == 'failure' || + steps.npm_audit.outcome == 'failure' || + steps.semgrep.outcome == 'failure' || + steps.gitleaks.outcome == 'failure' + run: | + echo "Security checks failed. Please review the PR comment and artifacts." + exit 1 diff --git a/.github/workflows/stagingdeploy.yml b/.github/workflows/stagingdeploy.yml new file mode 100644 index 0000000..f28dbda --- /dev/null +++ b/.github/workflows/stagingdeploy.yml @@ -0,0 +1,50 @@ +name: Sync GitHub develop to GitLab main + +on: + push: + branches: + - develop + +jobs: + sync: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Set up Git identity + run: | + git config --global user.email "jumalawrence98@gmail.com" + git config --global user.name "Jumalaw98" + + - name: Add GitLab remote + run: | + git remote add gitlab https://oauth2:${{ secrets.GITLAB_TOKEN_STAGE }}@gitlab.com/jumalaw98/ndc-website-test.git + + - name: Sync with GitLab main and push + id: sync + run: | + set -e # Stop if any command fails + git fetch gitlab main + + if ! git rebase gitlab/main; then + echo "MERGE_CONFLICT=true" >> $GITHUB_ENV + exit 1 + fi + + git push gitlab HEAD:main + + - name: Comment on failure + if: failure() && env.MERGE_CONFLICT == 'true' + uses: actions/github-script@v7 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + github.rest.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: "⚠️ Sync to GitLab failed due to merge/rebase conflicts. Please resolve them manually before retrying." + }) diff --git a/README.md b/README.md new file mode 100644 index 0000000..3c7af06 --- /dev/null +++ b/README.md @@ -0,0 +1,305 @@ +# Nairobi DevOps Community + +A modern, responsive web application for the Nairobi DevOps Community. Built with React, TypeScript, Vite, and Tailwind CSS. + +--- + +## Table of Contents + +- [Nairobi DevOps Community](#nairobi-devops-community) + - [Table of Contents](#table-of-contents) + - [About](#about) + - [Features](#features) + - [Tech Stack](#tech-stack) + - [Project Structure](#project-structure) + - [Data Folder](#data-folder) + - [Getting Started](#getting-started) + - [Prerequisites](#prerequisites) + - [Installation](#installation) + - [Scripts](#scripts) + - [Contributing](#contributing) + - [How to Contribute](#how-to-contribute) + - [License](#license) + +--- + +## About + +This platform empowers developers, automation experts, and tech enthusiasts to learn, network, and advance DevOps practices in Kenya's vibrant technology ecosystem. It features event management, community resources, and collaborative tools. + +--- + +## Features + +- Modern, responsive UI with dark mode +- Event and gallery management (static/demo only) +- Community and partner sections +- Modular, scalable codebase + +--- + +## Tech Stack + +- React (with hooks) +- TypeScript +- Vite +- Tailwind CSS +- Radix UI, Lucide Icons, Framer Motion, Wouter, React Query + +--- + +## Project Structure + +``` +NairobiDevOps-1/ + client/ # React frontend (src/, components/, pages/, contexts/, hooks/, etc.) + client/src/data/ # Static data files (testimonials, gallery images, partners, etc.) + components.json # UI config + tailwind.config.ts # Tailwind CSS config + tsconfig.json # TypeScript config + vite.config.ts # Vite config + package.json # Project dependencies and scripts +``` + +--- + +## Data Folder + +The `client/src/data/` directory contains static data used throughout the application. This includes: + +- **testimonialsData.ts**: Member testimonials displayed on the site +- **galleryData.ts**: Image URLs and metadata for the gallery section +- **partnersData.ts**: Information about community partners and sponsors +- **whatWeDoData.ts**: Details about the community's activities and offerings +--- + +## Getting Started + +### Prerequisites + +- Node.js (v18+ recommended) +- npm or yarn + +### Installation + +1. **Clone the repository:** + ```bash + git clone + cd NDCsite + ``` + +2. **Install dependencies:** + ```bash + npm install + ``` + +3. **Start the development server:** + ```bash + npm run dev + ``` + - The app will be available at `http://localhost:5173` or `http://localhost:4000` (default Vite port). + +4. **Build for production:** + ```bash + npm run build + ``` + +5. **Preview the production build:** + ```bash + npm run preview + ``` + +--- + +## Scripts + +- `npm run dev` — Start the app in development mode (with Vite and hot reload) +- `npm run build` — Build the client for production +- `npm run preview` — Preview the production build locally + + +--- + +## Contributing +We welcome contributions to enhance the Nairobi DevOps Community Platform! Whether it's fixing bugs, adding features, or improving documentation, your help is appreciated. +### How to Contribute +To contribute, please follow these steps: + 1. Clone the repository: + ```bash + git clone + cd NDCsite + ``` + 2. Create a new feature branch: + ```bash + git checkout -b feature/YourFeature + ``` + 3. Make your changes and commit: + ```bash + git commit -am 'Describe your update' + ``` + 4. Push your branch: + ```bash + git push origin feature/YourFeature + ``` + 5. Notify the repository maintainer to review your changes and merge them. + +- Please follow the code style and commit message guidelines used in this project. + +--- + +## License + +This project is licensed under the MIT License. + + + + +width: 327; +height: 160; +angle: 0 deg; +opacity: 1; +top: 322px; +left: 99px; +padding-top: 10px; +padding-right: 21px; +padding-bottom: 10px; +padding-left: 21px; +gap: 8px;" + + +/* Floating detail Frame */ + +/* Auto layout */ +display: flex; +flex-direction: column; +justify-content: center; +align-items: flex-start; +padding: 10px 21px; +gap: 8px; + +position: absolute; +width: 327px; +height: 160px; +left: 99px; +top: 322px; + +background: #D1E2F2; + + +/* member name */ + +width: 285px; +height: 41px; +position: absolute; +width: 198px; +height: 41px; +left: 0px; +top: 0px; + +font-family: 'Poppins'; +font-style: normal; +font-weight: 700; +font-size: 24px; +line-height: 41px; + + +/* Inside auto layout */ +flex: none; +order: 0; +align-self: stretch; +flex-grow: 0; + + +/* identical to box height, or 171% */ + +color: #000000; + + + +/* Member title */ + +width: 99px; +height: 41px; +position: absolute; +width: 99px; +height: 41px; +left: 0px; +top: 0px; + +font-family: 'Poppins'; +font-style: normal; +font-weight: 400; +font-size: 24px; +line-height: 41px; +/* identical to box height, or 171% */ + +color: #000000; + +/* Inside auto layout */ +flex: none; +order: 1; +flex-grow: 0; + +/* team social icons */ + +width: 140px; +height: 31px; + + +/* Inside auto layout */ +flex: none; +order: 2; +flex-grow: 0; + + +/* X */ + +position: absolute; +width: 30px; +height: 31px; +left: 21px; +top: 113.5px; + + +/* LinkedIn */ + +position: absolute; +width: 30px; +height: 31px; +left: 76px; +top: 113.5px; + + +/* Instagram Circle */ + +position: absolute; +width: 30px; +height: 31px; +left: 131px; +top: 113.5px; + + +
+
+

+ {member.name} +

+

+ {member.title} +

+
+ + + + + + + + + +
+
+
+ + + help me update the floating section to have bg color of, "bg-[#D1E2F2]" reduce the widith to be flexible to increase or reduce depending on inside content lenghth \ No newline at end of file diff --git a/client/index.html b/client/index.html new file mode 100644 index 0000000..9ed452f --- /dev/null +++ b/client/index.html @@ -0,0 +1,61 @@ + + + + + + Nairobi DevOps Community | DevOps in Kenya + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + diff --git a/client/src/App.tsx b/client/src/App.tsx new file mode 100644 index 0000000..fd27861 --- /dev/null +++ b/client/src/App.tsx @@ -0,0 +1,42 @@ +import { Switch, Route } from "wouter"; +import { queryClient } from "./lib/queryClient"; +import { QueryClientProvider } from "@tanstack/react-query"; +import { Toaster } from "@/components/ui/toaster"; +import { TooltipProvider } from "@/components/ui/tooltip"; +import { ThemeProvider } from "@/contexts/ThemeContext"; +import Home from "@/pages/Home"; +import AboutUs from "@/pages/AboutUs"; +import FAQPage from "@/pages/FAQPage"; + +import NotFound from "@/pages/not-found"; + +import { Analytics } from '@vercel/analytics/react'; + +function Router() { + return ( + + + + + + + + + ); +} + +function App() { + return ( + + + + + + + + + + ); +} + +export default App; diff --git a/client/src/assets/faq.png b/client/src/assets/faq.png new file mode 100644 index 0000000..892dbc8 Binary files /dev/null and b/client/src/assets/faq.png differ diff --git a/client/src/assets/images/IMG_8326 2.png b/client/src/assets/images/IMG_8326 2.png new file mode 100644 index 0000000..6bd5da8 Binary files /dev/null and b/client/src/assets/images/IMG_8326 2.png differ diff --git a/client/src/assets/images/joinus.jpg b/client/src/assets/images/joinus.jpg new file mode 100644 index 0000000..afffeab Binary files /dev/null and b/client/src/assets/images/joinus.jpg differ diff --git a/client/src/components/Footer.tsx b/client/src/components/Footer.tsx new file mode 100644 index 0000000..3794656 --- /dev/null +++ b/client/src/components/Footer.tsx @@ -0,0 +1,88 @@ +import { Code, Linkedin, Instagram, Send, MapPin, Phone, Mail, X } from "lucide-react"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; + +export default function Footer() { + const currentYear = new Date().getFullYear(); + return ( + + ); +} diff --git a/client/src/components/Navbar.tsx b/client/src/components/Navbar.tsx new file mode 100644 index 0000000..80a2610 --- /dev/null +++ b/client/src/components/Navbar.tsx @@ -0,0 +1,110 @@ +import { useState } from "react"; +import { useTheme } from "@/contexts/ThemeContext"; +import { Code, Menu, X, Sun, Moon, Handshake } from "lucide-react"; +import { Button } from "@/components/ui/button"; +import { Sheet, SheetContent, SheetTrigger } from "@/components/ui/sheet"; + +export default function Navbar() { + const { theme, toggleTheme } = useTheme(); + const [isOpen, setIsOpen] = useState(false); + + const navLinks = [ + { href: "/", label: "Home" }, + { href: "/about", label: "About Us" }, + { href: "#events", label: "Events" }, + { href: "#blog", label: "Blog" }, + { href: "#community", label: "Community" }, + ]; + + const handleNavClick = (href: string) => { + if (href.startsWith('/')) { + window.location.href = href; + } else { + const element = document.querySelector(href); + if (element) { + element.scrollIntoView({ behavior: 'smooth' }); + } + } + setIsOpen(false); + }; + + return ( +
+
+
+
+
+ + Nairobi DevOps + +
+ +
+ + + +
+ + + + + + + + + + + +
+ Menu + +
+ +
+
+
+
+
+
+ ); +} diff --git a/client/src/components/landing/AboutSection.tsx b/client/src/components/landing/AboutSection.tsx new file mode 100644 index 0000000..fd15963 --- /dev/null +++ b/client/src/components/landing/AboutSection.tsx @@ -0,0 +1,65 @@ +import { Button } from "@/components/ui/button"; +import { Users, Calendar, Handshake } from "lucide-react"; + +export default function AboutSection() { + const stats = [ + { value: "3000+", label: "Community Member", color: "text-foreground", icon: Users }, + { value: "100+", label: "Events", color: "text-foreground", icon: Calendar }, + { value: "20+", label: "Partners", color: "text-foreground", icon: Handshake }, + ]; + return ( +
+
+
+

About Us

+
+ +
+ {/* Video Player on the Left */} +
+
+