From 3d31dc1b5540972d67ef03522ba3a02573e1eedc Mon Sep 17 00:00:00 2001 From: Neeldhara Misra Date: Mon, 20 Jul 2026 14:22:11 +0530 Subject: [PATCH] Add Forgejo CI and Dokploy deployment --- .dockerignore | 8 ++++ .forgejo/workflows/ci.yml | 41 +++++++++++++++++++ Dockerfile | 19 +++++++++ README.md | 6 +++ nginx.conf | 22 ++++++++++ src/components/InteractiveGallery.tsx | 4 +- src/components/InteractiveIndex.tsx | 4 +- src/components/LadybugClockPuzzle.tsx | 4 +- src/components/ZeckendorfGame.tsx | 4 +- .../guessing-game/QuestionGuessingMode.tsx | 3 +- src/components/ui/command.tsx | 2 +- src/components/ui/textarea.tsx | 3 +- tailwind.config.ts | 3 +- 13 files changed, 110 insertions(+), 13 deletions(-) create mode 100644 .dockerignore create mode 100644 .forgejo/workflows/ci.yml create mode 100644 Dockerfile create mode 100644 nginx.conf diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..e9a45b4 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,8 @@ +.git +.github +.forgejo +dist +node_modules +npm-debug.log* +Dockerfile* +README.md diff --git a/.forgejo/workflows/ci.yml b/.forgejo/workflows/ci.yml new file mode 100644 index 0000000..40a10b9 --- /dev/null +++ b/.forgejo/workflows/ci.yml @@ -0,0 +1,41 @@ +name: CI + +on: + push: + branches: + - main + pull_request: + branches: + - main + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: ci-${{ forgejo.ref }} + cancel-in-progress: true + +jobs: + verify: + runs-on: ubuntu-latest + timeout-minutes: 15 + + steps: + - name: Check out repository + uses: actions/checkout@v4 + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: 22 + cache: npm + + - name: Install dependencies + run: npm ci + + - name: Lint + run: npm run lint + + - name: Build + run: npm run build diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..ff67f4c --- /dev/null +++ b/Dockerfile @@ -0,0 +1,19 @@ +FROM node:22-alpine AS build + +WORKDIR /app + +COPY package.json package-lock.json ./ +RUN npm ci + +COPY . . +RUN npm run build + +FROM nginx:1.27-alpine AS runtime + +COPY nginx.conf /etc/nginx/conf.d/default.conf +COPY --from=build /app/dist /usr/share/nginx/html + +EXPOSE 80 + +HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ + CMD wget -q --spider http://127.0.0.1/ || exit 1 diff --git a/README.md b/README.md index e0eb78a..672b6b0 100644 --- a/README.md +++ b/README.md @@ -64,6 +64,12 @@ This project is built with: Simply open [Lovable](https://lovable.dev/projects/5ee9d1e7-af25-47e9-ab4c-bdbca4554e32) and click on Share -> Publish. +### Forgejo and Dokploy + +Pushes and pull requests targeting `main` are verified by the Forgejo Actions workflow in `.forgejo/workflows/ci.yml`. The production container is built from `Dockerfile` and serves the Vite output with NGINX, including React Router fallback handling. + +Dokploy is connected to the `Websites/interactives` Forgejo repository with autodeploy enabled. A successful push to `main` triggers a fresh Dockerfile build and replaces the running service only after the image has built successfully. + ## Can I connect a custom domain to my Lovable project? Yes, you can! diff --git a/nginx.conf b/nginx.conf new file mode 100644 index 0000000..aca9d71 --- /dev/null +++ b/nginx.conf @@ -0,0 +1,22 @@ +server { + listen 80; + listen [::]:80; + server_name _; + + root /usr/share/nginx/html; + index index.html; + + location / { + try_files $uri $uri/ /index.html; + } + + location = /index.html { + add_header Cache-Control "no-cache"; + } + + location ~* \.(?:css|js|mjs|jpg|jpeg|png|gif|ico|svg|webp|woff|woff2)$ { + expires 1y; + add_header Cache-Control "public, immutable"; + try_files $uri =404; + } +} diff --git a/src/components/InteractiveGallery.tsx b/src/components/InteractiveGallery.tsx index 1086712..24a3a03 100644 --- a/src/components/InteractiveGallery.tsx +++ b/src/components/InteractiveGallery.tsx @@ -67,7 +67,7 @@ const InteractiveGallery = () => {

Data Structures and Algorithms

-

Interactive explorations of elementary data structures and algorithms.

+

Interactive explorations of elementary data structures and algorithms.

{/* Search bar */} @@ -103,4 +103,4 @@ const InteractiveGallery = () => { ); }; -export default InteractiveGallery; \ No newline at end of file +export default InteractiveGallery; diff --git a/src/components/InteractiveIndex.tsx b/src/components/InteractiveIndex.tsx index 3017d22..4223abe 100644 --- a/src/components/InteractiveIndex.tsx +++ b/src/components/InteractiveIndex.tsx @@ -323,7 +323,7 @@ const InteractiveIndex = () => { // Filter and sort interactives const filteredAndSortedInteractives = useMemo(() => { - let filtered = allInteractives.filter(interactive => { + const filtered = allInteractives.filter(interactive => { const matchesSearch = interactive.title.toLowerCase().includes(searchTerm.toLowerCase()) || interactive.description.toLowerCase().includes(searchTerm.toLowerCase()) || interactive.tags.some(tag => tag.toLowerCase().includes(searchTerm.toLowerCase())); @@ -549,4 +549,4 @@ const InteractiveIndex = () => { ); }; -export default InteractiveIndex; \ No newline at end of file +export default InteractiveIndex; diff --git a/src/components/LadybugClockPuzzle.tsx b/src/components/LadybugClockPuzzle.tsx index 4ad2044..7de133f 100644 --- a/src/components/LadybugClockPuzzle.tsx +++ b/src/components/LadybugClockPuzzle.tsx @@ -144,7 +144,7 @@ const LadybugClockPuzzle: React.FC = ({ // Run to completion const runToCompletion = useCallback(() => { setIsPlaying(false); - let currentSim = simulation; + const currentSim = simulation; let position = currentSim.position; const visited = new Set(currentSim.visited); let moveCount = currentSim.moveCount; @@ -545,4 +545,4 @@ const LadybugClockPuzzle: React.FC = ({ {showSocialShare && }
; }; -export default LadybugClockPuzzle; \ No newline at end of file +export default LadybugClockPuzzle; diff --git a/src/components/ZeckendorfGame.tsx b/src/components/ZeckendorfGame.tsx index 439b0d0..1655e46 100644 --- a/src/components/ZeckendorfGame.tsx +++ b/src/components/ZeckendorfGame.tsx @@ -34,7 +34,7 @@ const ZeckendorfGame: React.FC = ({ showSocialShare = true // Generate a random combination of Fibonacci numbers const numTerms = Math.floor(Math.random() * 4) + 2; // 2-5 terms let target = 0; - let usedIndices = new Set(); + const usedIndices = new Set(); for (let i = 0; i < numTerms; i++) { let index; @@ -235,4 +235,4 @@ const ZeckendorfGame: React.FC = ({ showSocialShare = true ); }; -export default ZeckendorfGame; \ No newline at end of file +export default ZeckendorfGame; diff --git a/src/components/guessing-game/QuestionGuessingMode.tsx b/src/components/guessing-game/QuestionGuessingMode.tsx index d30855a..d76a11a 100644 --- a/src/components/guessing-game/QuestionGuessingMode.tsx +++ b/src/components/guessing-game/QuestionGuessingMode.tsx @@ -217,7 +217,7 @@ const QuestionGuessingMode: React.FC = ({ case 'odd': baseCondition = (num % 2 !== 0); break; - case 'prime': + case 'prime': { const isPrimeNum = (n: number): boolean => { if (n < 2) return false; for (let i = 2; i <= Math.sqrt(n); i++) { @@ -227,6 +227,7 @@ const QuestionGuessingMode: React.FC = ({ }; baseCondition = isPrimeNum(num); break; + } case 'perfectSquare': baseCondition = Math.sqrt(num) % 1 === 0; break; diff --git a/src/components/ui/command.tsx b/src/components/ui/command.tsx index 56a0979..69d63a4 100644 --- a/src/components/ui/command.tsx +++ b/src/components/ui/command.tsx @@ -21,7 +21,7 @@ const Command = React.forwardRef< )) Command.displayName = CommandPrimitive.displayName -interface CommandDialogProps extends DialogProps {} +type CommandDialogProps = DialogProps const CommandDialog = ({ children, ...props }: CommandDialogProps) => { return ( diff --git a/src/components/ui/textarea.tsx b/src/components/ui/textarea.tsx index 9f9a6dc..12c9136 100644 --- a/src/components/ui/textarea.tsx +++ b/src/components/ui/textarea.tsx @@ -2,8 +2,7 @@ import * as React from "react" import { cn } from "@/lib/utils" -export interface TextareaProps - extends React.TextareaHTMLAttributes {} +export type TextareaProps = React.TextareaHTMLAttributes const Textarea = React.forwardRef( ({ className, ...props }, ref) => { diff --git a/tailwind.config.ts b/tailwind.config.ts index a4b00a3..a934352 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -1,4 +1,5 @@ import type { Config } from "tailwindcss"; +import tailwindcssAnimate from "tailwindcss-animate"; export default { darkMode: ["class"], @@ -122,5 +123,5 @@ export default { } } }, - plugins: [require("tailwindcss-animate")], + plugins: [tailwindcssAnimate], } satisfies Config;