From 0cbec0bb192a5973847274a3aa7df8adecfc146c Mon Sep 17 00:00:00 2001 From: denshooter <44590296+denshooter@users.noreply.github.com> Date: Mon, 17 Feb 2025 09:58:58 +0100 Subject: [PATCH] Dev (#38) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ✨ chore: update CI workflow to include testing and multi-arch build (#29) * ✨ chore: remove unused dependencies from package-lock.json and updated to a better local dev environment (#30) * ✨ test: add unit tests * ✨ test: add unit tests for whole project * ✨ feat: add whatwg-fetch for improved fetch support * ✨ chore: update Node.js version to 22 in workflow * ✨ refactor: update types and improve email handling tests * ✨ refactor: remove unused imports * ✨ fix: normalize image name to lowercase in workflows * ✨ fix: ensure Docker image names are consistently lowercase * ✨ chore: update * ✨ chore: update base URL to use secret variable * ✨ chore: update to login to ghcr * ✨ fix: add missing 'fi' to close if statement in workflow * D branch 1 (#32) * full upgrade (#31) * ✨ chore: update CI workflow to include testing and multi-arch build (#29) * ✨ chore: remove unused dependencies from package-lock.json and updated to a better local dev environment (#30) * ✨ test: add unit tests * ✨ test: add unit tests for whole project * ✨ feat: add whatwg-fetch for improved fetch support * ✨ chore: update Node.js version to 22 in workflow * ✨ refactor: update types and improve email handling tests * ✨ refactor: remove unused imports * ✨ fix: normalize image name to lowercase in workflows * ✨ fix: ensure Docker image names are consistently lowercase * ✨ chore: update * ✨ chore: update base URL to use secret variable * ✨ chore: update to login to ghcr * ✨ fix: add missing 'fi' to close if statement in workflow * 🚀 fix: update Docker run commands to use specific network * D branch 1 (#34) * full upgrade (#31) * ✨ chore: update CI workflow to include testing and multi-arch build (#29) * ✨ chore: remove unused dependencies from package-lock.json and updated to a better local dev environment (#30) * ✨ test: add unit tests * ✨ test: add unit tests for whole project * ✨ feat: add whatwg-fetch for improved fetch support * ✨ chore: update Node.js version to 22 in workflow * ✨ refactor: update types and improve email handling tests * ✨ refactor: remove unused imports * ✨ fix: normalize image name to lowercase in workflows * ✨ fix: ensure Docker image names are consistently lowercase * ✨ chore: update * ✨ chore: update base URL to use secret variable * ✨ chore: update to login to ghcr * ✨ fix: add missing 'fi' to close if statement in workflow * 🚀 fix: update Docker run commands to use specific network * ✨ fix: add error handling for invalid project data * D branch 2 (#35) * full upgrade (#31) * ✨ chore: update CI workflow to include testing and multi-arch build (#29) * ✨ chore: remove unused dependencies from package-lock.json and updated to a better local dev environment (#30) * ✨ test: add unit tests * ✨ test: add unit tests for whole project * ✨ feat: add whatwg-fetch for improved fetch support * ✨ chore: update Node.js version to 22 in workflow * ✨ refactor: update types and improve email handling tests * ✨ refactor: remove unused imports * ✨ fix: normalize image name to lowercase in workflows * ✨ fix: ensure Docker image names are consistently lowercase * ✨ chore: update * ✨ chore: update base URL to use secret variable * ✨ chore: update to login to ghcr * ✨ fix: add missing 'fi' to close if statement in workflow * ✨ fix: format code for better readability in Contact and Footer components * D branch 2 (#36) * full upgrade (#31) * ✨ chore: update CI workflow to include testing and multi-arch build (#29) * ✨ chore: remove unused dependencies from package-lock.json and updated to a better local dev environment (#30) * ✨ test: add unit tests * ✨ test: add unit tests for whole project * ✨ feat: add whatwg-fetch for improved fetch support * ✨ chore: update Node.js version to 22 in workflow * ✨ refactor: update types and improve email handling tests * ✨ refactor: remove unused imports * ✨ fix: normalize image name to lowercase in workflows * ✨ fix: ensure Docker image names are consistently lowercase * ✨ chore: update * ✨ chore: update base URL to use secret variable * ✨ chore: update to login to ghcr * ✨ fix: add missing 'fi' to close if statement in workflow * ✨ fix: format code for better readability in Contact and Footer components * 🚀 fix: update Docker commands and remove hardcoded API URL * Update main.yml * Update main.yml * Update main.yml * D branch 1 (#37) * full upgrade (#31) * ✨ chore: update CI workflow to include testing and multi-arch build (#29) * ✨ chore: remove unused dependencies from package-lock.json and updated to a better local dev environment (#30) * ✨ test: add unit tests * ✨ test: add unit tests for whole project * ✨ feat: add whatwg-fetch for improved fetch support * ✨ chore: update Node.js version to 22 in workflow * ✨ refactor: update types and improve email handling tests * ✨ refactor: remove unused imports * ✨ fix: normalize image name to lowercase in workflows * ✨ fix: ensure Docker image names are consistently lowercase * ✨ chore: update * ✨ chore: update base URL to use secret variable * ✨ chore: update to login to ghcr * ✨ fix: add missing 'fi' to close if statement in workflow * ✨ feat: display base URL in Hero component * Update main.yml * Update next.config.ts * next.config.ts aktualisieren * Update main.yml * ✨ chore: refactor environment variable handling in workflow * ✨ chore: update GitHub Actions workflow for improved security and caching * 🚀 chore: update Trivy action version and enhance config * ✨ chore: update GitHub Actions workflows and add linter * 🚫 chore: remove Docker image vulnerability scan step * ✨ chore: update environment variable logging in workflow * ✨ chore: add dynamic environment for deployment jobs * 🚀 chore: set deployment environment to GitHub ref name * 🎉 chore: remove environment variable exposure in CI/CD * ✨ chore: remove sensitive environment variable logging and update variable references * ✨ chore: log environment variables for debugging purposes * ✨ chore: create .env file for environment variables setup * ✨ feat: copy .env file to Docker image for config * ✨ refactor: update environment variables to public scope * ✨ chore: remove environment variable from Hero component * ✨ fix: update environment variable references in workflow * ✨ chore: add folder structure display to workflow steps * ✨ chore: reorder CI steps for improved workflow clarity * ✨ fix: remove unnecessary console logs and correct base URL variable --- .github/workflows/linter.yml | 55 ++++ .github/workflows/main.yml | 62 +++-- Dockerfile | 3 + app/__tests__/api/email.test.tsx | 8 +- app/__tests__/api/fetchAllProjects.test.tsx | 4 +- app/__tests__/api/fetchProject.test.tsx | 5 +- app/__tests__/api/sitemap.test.tsx | 4 +- app/__tests__/components/Projects.test.tsx | 4 +- app/__tests__/projects/[slug]/page.test.tsx | 4 +- app/api/email/route.tsx | 4 +- app/api/fetchAllProjects/route.tsx | 9 +- app/api/fetchProject/route.tsx | 4 +- app/api/sitemap/route.tsx | 6 +- app/components/Contact.tsx | 1 - app/components/Footer.tsx | 154 ++++++----- app/components/Footer_Back.tsx | 140 +++++----- app/components/Header.tsx | 244 +++++++++-------- app/components/Hero.tsx | 15 +- app/components/Projects.tsx | 144 +++++----- app/projects/[slug]/page.tsx | 284 ++++++++++---------- app/sitemap.xml/route.tsx | 2 +- next.config.ts | 12 +- 22 files changed, 642 insertions(+), 526 deletions(-) create mode 100644 .github/workflows/linter.yml diff --git a/.github/workflows/linter.yml b/.github/workflows/linter.yml new file mode 100644 index 0000000..b84608d --- /dev/null +++ b/.github/workflows/linter.yml @@ -0,0 +1,55 @@ +name: Lint Code Base + +on: + push: + branches: + - dev + - preview + - production + paths: + - 'app/**' + - 'public/**' + - 'styles/**' + - 'Dockerfile' + - 'docker-compose.yml' + - '.github/workflows/**' + - 'next.config.ts' + - 'package.json' + - 'package-lock.json' + - 'tsconfig.json' + - 'tailwind.config.ts' + pull_request: + branches: + - dev + - preview + - production + paths: + - 'app/**' + - 'public/**' + - 'styles/**' + - 'Dockerfile' + - 'docker-compose.yml' + - '.github/workflows/**' + - 'next.config.ts' + - 'package.json' + - 'package-lock.json' + - 'tsconfig.json' + - 'tailwind.config.ts' + +jobs: + build: + name: Check and Lint Code Base + runs-on: ubuntu-latest + + steps: + - name: Check Out Code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Lint Code Base + uses: github/super-linter@v4 + env: + VALIDATE_ALL_CODEBASE: false + DEFAULT_BRANCH: production + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 096fec0..c452141 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -6,6 +6,18 @@ on: - production - dev - preview + paths: + - 'app/**' + - 'public/**' + - 'styles/**' + - 'Dockerfile' + - 'docker-compose.yml' + - '.github/workflows/main.yml' + - 'next.config.ts' + - 'package.json' + - 'package-lock.json' + - 'tsconfig.json' + - 'tailwind.config.ts' jobs: test_and_build: @@ -15,10 +27,32 @@ jobs: uses: actions/checkout@v4 - name: Set up Node - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version: '22' + - name: Cache Node.js modules + uses: actions/cache@v4 + with: + path: ~/.npm + key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} + restore-keys: | + ${{ runner.os }}-node- + + - name: Create env file + run: | + touch .env + echo "NEXT_PUBLIC_BASE_URL=${{ vars.NEXT_PUBLIC_BASE_URL }}" >> .env + echo "NEXT_PUBLIC_GHOST_API_URL=${{ vars.NEXT_PUBLIC_GHOST_API_URL }}" >> .env + echo "NEXT_PUBLIC_GHOST_API_KEY=${{ secrets.NEXT_PUBLIC_GHOST_API_KEY }}" >> .env + echo "NEXT_PUBLIC_MY_EMAIL=${{ vars.NEXT_PUBLIC_MY_EMAIL }}" >> .env + echo "NEXT_PUBLIC_MY_PASSWORD=${{ secrets.NEXT_PUBLIC_MY_PASSWORD }}" >> .env + cat .env + + - name: Show folder structure + run: | + ls -la + - name: Install Dependencies run: npm install @@ -32,14 +66,12 @@ jobs: - name: Build and Push Multi-Arch Docker Image run: | IMAGE_NAME="ghcr.io/${{ github.repository_owner }}/my-nextjs-app:${{ github.ref_name }}" - IMAGE_NAME=$(echo "$IMAGE_NAME" | tr '[:upper:]' '[:lower:]') docker buildx create --use docker buildx build \ - --platform linux/arm64,linux/amd64 \ + --platform linux/arm64 \ -t "$IMAGE_NAME" \ --push \ . - deploy: runs-on: self-hosted needs: test_and_build @@ -82,19 +114,19 @@ jobs: docker rm -f "$NEW_CONTAINER_NAME" || true fi + echo "Deploying $CONTAINER_NAME with $IMAGE_NAME" + # Start new container on a temporary internal port - docker run -d --name "$NEW_CONTAINER_NAME" -p 40000:3000 \ - -e GHOST_API_KEY="${{ secrets.GHOST_API_KEY }}" \ - -e NEXT_PUBLIC_BASE_URL="${{ secrets.NEXT_PUBLIC_BASE_URL }}" \ - -e MY_EMAIL="${{ secrets.MY_EMAIL }}" \ - -e MY_PASSWORD="${{ secrets.MY_PASSWORD }}" \ - -e GHOST_API_URL="${{ secrets.GHOST_API_URL }}" \ + docker run -d --name "$NEW_CONTAINER_NAME" --network big-bear-ghost_ghost-network -p 40000:3000 \ "$IMAGE_NAME" # Wait for the new container to start sleep 10 - if [ "$(docker inspect --format='{{.State.Running}}' $NEW_CONTAINER_NAME)" = "true" ]; then + # Debugging: Check if the environment variables are set correctly + docker exec "$NEW_CONTAINER_NAME" printenv + + if [ "$(docker inspect --format='{{.State.Running}}' "$NEW_CONTAINER_NAME")" = "true" ]; then # Stop/remove the old container if [ "$(docker ps -aq -f name=$CONTAINER_NAME)" ]; then docker stop "$CONTAINER_NAME" || true @@ -104,12 +136,8 @@ jobs: # Replace the new container with final name/port docker stop "$NEW_CONTAINER_NAME" || true docker rm "$NEW_CONTAINER_NAME" || true - docker run -d --name "$CONTAINER_NAME" -p $PORT:3000 \ - -e GHOST_API_KEY="${{ secrets.GHOST_API_KEY }}" \ - -e NEXT_PUBLIC_BASE_URL="${{ secrets.NEXT_PUBLIC_BASE_URL }}" \ - -e MY_EMAIL="${{ secrets.MY_EMAIL }}" \ - -e MY_PASSWORD="${{ secrets.MY_PASSWORD }}" \ - -e GHOST_API_URL="${{ secrets.GHOST_API_URL }}" \ + + docker run -d --name "$CONTAINER_NAME" --network big-bear-ghost_ghost-network -p $PORT:3000 \ "$IMAGE_NAME" else echo "New container failed to start." diff --git a/Dockerfile b/Dockerfile index 9fd3e6e..24ba3e3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -13,6 +13,9 @@ RUN npm install # Copy the application code COPY . . +# Copy the .env file +COPY .env .env + # Build the Next.js application RUN npm run build diff --git a/app/__tests__/api/email.test.tsx b/app/__tests__/api/email.test.tsx index 5ecbd75..b09c9fe 100644 --- a/app/__tests__/api/email.test.tsx +++ b/app/__tests__/api/email.test.tsx @@ -10,8 +10,8 @@ jest.mock('next/server', () => ({ beforeEach(() => { nodemailermock.mock.reset(); - process.env.MY_EMAIL = 'test@dki.one'; - process.env.MY_PASSWORD = 'test-password'; + process.env.NEXT_PUBLIC_MY_EMAIL = 'test@dki.one'; + process.env.NEXT_PUBLIC_MY_PASSWORD = 'test-password'; }); describe('POST /api/email', () => { @@ -35,8 +35,8 @@ describe('POST /api/email', () => { }); it('should return an error if EMAIL or PASSWORD is missing', async () => { - delete process.env.MY_EMAIL; - delete process.env.MY_PASSWORD; + delete process.env.NEXT_PUBLIC_MY_EMAIL; + delete process.env.NEXT_PUBLIC_MY_PASSWORD; const mockRequest = { json: jest.fn().mockResolvedValue({ diff --git a/app/__tests__/api/fetchAllProjects.test.tsx b/app/__tests__/api/fetchAllProjects.test.tsx index 9824748..dec7974 100644 --- a/app/__tests__/api/fetchAllProjects.test.tsx +++ b/app/__tests__/api/fetchAllProjects.test.tsx @@ -10,8 +10,8 @@ jest.mock('next/server', () => ({ describe('GET /api/fetchAllProjects', () => { beforeAll(() => { - process.env.GHOST_API_URL = 'http://localhost:2368'; - process.env.GHOST_API_KEY = 'some-key'; + process.env.NEXT_PUBLIC_GHOST_API_URL = 'http://localhost:2368'; + process.env.NEXT_PUBLIC_GHOST_API_KEY = 'some-key'; global.fetch = mockFetch({ posts: [ { diff --git a/app/__tests__/api/fetchProject.test.tsx b/app/__tests__/api/fetchProject.test.tsx index 57d74f8..da7e83a 100644 --- a/app/__tests__/api/fetchProject.test.tsx +++ b/app/__tests__/api/fetchProject.test.tsx @@ -10,8 +10,9 @@ jest.mock('next/server', () => ({ describe('GET /api/fetchProject', () => { beforeAll(() => { - process.env.GHOST_API_URL = 'http://localhost:2368'; - process.env.GHOST_API_KEY = 'some-key'; + process.env.NEXT_PUBLIC_GHOST_API_URL = 'http://localhost:2368'; + process.env.NEXT_PUBLIC_GHOST_API_KEY = 'some-key'; + global.fetch = mockFetch({ posts: [ { diff --git a/app/__tests__/api/sitemap.test.tsx b/app/__tests__/api/sitemap.test.tsx index f97a5ed..12e144d 100644 --- a/app/__tests__/api/sitemap.test.tsx +++ b/app/__tests__/api/sitemap.test.tsx @@ -7,8 +7,8 @@ jest.mock('next/server', () => ({ describe('GET /api/sitemap', () => { beforeAll(() => { - process.env.GHOST_API_URL = 'http://localhost:2368'; - process.env.GHOST_API_KEY = 'test-api-key'; + process.env.NEXT_PUBLIC_GHOST_API_URL = 'http://localhost:2368'; + process.env.NEXT_PUBLIC_GHOST_API_KEY = 'test-api-key'; process.env.NEXT_PUBLIC_BASE_URL = 'https://dki.one'; global.fetch = mockFetch({ posts: [ diff --git a/app/__tests__/components/Projects.test.tsx b/app/__tests__/components/Projects.test.tsx index cfee4ec..a4836d8 100644 --- a/app/__tests__/components/Projects.test.tsx +++ b/app/__tests__/components/Projects.test.tsx @@ -6,8 +6,8 @@ import { mockFetch } from '@/app/__tests__/__mocks__/mock-fetch'; describe('Projects', () => { beforeAll(() => { - process.env.GHOST_API_URL = 'http://localhost:2368'; - process.env.GHOST_API_KEY = 'some-key'; + process.env.NEXT_PUBLIC_GHOST_API_URL = 'http://localhost:2368'; + process.env.NEXT_PUBLIC_GHOST_API_KEY = 'some-key'; global.fetch = mockFetch({ posts: [ { diff --git a/app/__tests__/projects/[slug]/page.test.tsx b/app/__tests__/projects/[slug]/page.test.tsx index 1cd7127..182c4b8 100644 --- a/app/__tests__/projects/[slug]/page.test.tsx +++ b/app/__tests__/projects/[slug]/page.test.tsx @@ -13,8 +13,8 @@ jest.mock('next/navigation', () => ({ describe('ProjectDetails', () => { beforeAll(() => { - process.env.GHOST_API_URL = 'http://localhost:2368'; - process.env.GHOST_API_KEY = 'some-key'; + process.env.NEXT_PUBLIC_GHOST_API_URL = 'http://localhost:2368'; + process.env.NEXT_PUBLIC_GHOST_API_KEY = 'some-key'; global.fetch = mockFetch({ posts: [ { diff --git a/app/api/email/route.tsx b/app/api/email/route.tsx index db61600..76a1168 100644 --- a/app/api/email/route.tsx +++ b/app/api/email/route.tsx @@ -11,8 +11,8 @@ export async function POST(request: NextRequest) { }; const { email, name, message } = body; - const user = process.env.MY_EMAIL ?? ""; - const pass = process.env.MY_PASSWORD ?? ""; + const user = process.env.NEXT_PUBLIC_MY_EMAIL ?? ""; + const pass = process.env.NEXT_PUBLIC_MY_PASSWORD ?? ""; if (!user || !pass) { console.error("Missing email/password environment variables"); diff --git a/app/api/fetchAllProjects/route.tsx b/app/api/fetchAllProjects/route.tsx index 2a73645..8cfd2ff 100644 --- a/app/api/fetchAllProjects/route.tsx +++ b/app/api/fetchAllProjects/route.tsx @@ -2,8 +2,8 @@ import { NextResponse } from "next/server"; export const runtime = "nodejs"; // Force Node runtime -const GHOST_API_URL = process.env.GHOST_API_URL || "http://192.168.179.31:2368"; -const GHOST_API_KEY = process.env.GHOST_API_KEY; +const GHOST_API_URL = process.env.NEXT_PUBLIC_GHOST_API_URL; +const GHOST_API_KEY = process.env.NEXT_PUBLIC_GHOST_API_KEY; export async function GET() { try { @@ -15,6 +15,11 @@ export async function GET() { return NextResponse.json([]); } const posts = await response.json(); + + if (!posts || !posts.posts) { + console.error("Invalid posts data"); + return NextResponse.json([]); + } return NextResponse.json(posts); } catch (error) { console.error("Failed to fetch posts from Ghost:", error); diff --git a/app/api/fetchProject/route.tsx b/app/api/fetchProject/route.tsx index 9627cc8..eb0abbf 100644 --- a/app/api/fetchProject/route.tsx +++ b/app/api/fetchProject/route.tsx @@ -2,8 +2,8 @@ import { NextResponse } from "next/server"; export const runtime = "nodejs"; // Force Node runtime -const GHOST_API_URL = process.env.GHOST_API_URL || "http://192.168.179.31:2368"; -const GHOST_API_KEY = process.env.GHOST_API_KEY; +const GHOST_API_URL = process.env.NEXT_PUBLIC_GHOST_API_URL; +const GHOST_API_KEY = process.env.NEXT_PUBLIC_GHOST_API_KEY; export async function GET(request: Request) { const { searchParams } = new URL(request.url); diff --git a/app/api/sitemap/route.tsx b/app/api/sitemap/route.tsx index 9629c13..411833b 100644 --- a/app/api/sitemap/route.tsx +++ b/app/api/sitemap/route.tsx @@ -12,8 +12,8 @@ interface ProjectsData { export const dynamic = "force-dynamic"; export const runtime = "nodejs"; // Force Node runtime -const GHOST_API_URL = process.env.GHOST_API_URL || "http://192.168.179.31:2368"; -const GHOST_API_KEY = process.env.GHOST_API_KEY; +const GHOST_API_URL = process.env.NEXT_PUBLIC_GHOST_API_URL; +const GHOST_API_KEY = process.env.NEXT_PUBLIC_GHOST_API_KEY; // Funktion, um die XML für die Sitemap zu generieren function generateXml(sitemapRoutes: { url: string; lastModified: string }[]) { @@ -38,7 +38,7 @@ function generateXml(sitemapRoutes: { url: string; lastModified: string }[]) { } export async function GET() { - const baseUrl = process.env.NEXT_PUBLIC_BASE_URL || "https://dki.one"; + const baseUrl = process.env.NEXT_PUBLIC_BASE_URL; // Statische Routen const staticRoutes = [ diff --git a/app/components/Contact.tsx b/app/components/Contact.tsx index 1ea0f99..3898dc8 100644 --- a/app/components/Contact.tsx +++ b/app/components/Contact.tsx @@ -54,7 +54,6 @@ export default function Contact() { setBanner((prev) => ({ ...prev, show: false })); }, 3000); } - return (
{ - setTimeout(() => { - setIsVisible(true); - }, 450); // Delay to start the animation - }, []); + useEffect(() => { + setTimeout(() => { + setIsVisible(true); + }, 450); // Delay to start the animation + }, []); - const scrollToSection = (id: string) => { - const element = document.getElementById(id); - if (element) { - element.scrollIntoView({behavior: "smooth"}); - } - }; + const scrollToSection = (id: string) => { + const element = document.getElementById(id); + if (element) { + element.scrollIntoView({ behavior: "smooth" }); + } + }; - return ( - + ); } diff --git a/app/components/Footer_Back.tsx b/app/components/Footer_Back.tsx index 1436296..3ce816c 100644 --- a/app/components/Footer_Back.tsx +++ b/app/components/Footer_Back.tsx @@ -1,73 +1,79 @@ import Link from "next/link"; -import {useEffect, useState} from "react"; +import { useEffect, useState } from "react"; export default function Footer_Back() { - const [isVisible, setIsVisible] = useState(false); + const [isVisible, setIsVisible] = useState(false); - useEffect(() => { - setTimeout(() => { - setIsVisible(true); - }, 450); // Delay to start the animation - }, []); + useEffect(() => { + setTimeout(() => { + setIsVisible(true); + }, 450); // Delay to start the animation + }, []); - return ( - - ); + return ( + + ); } diff --git a/app/components/Header.tsx b/app/components/Header.tsx index d652b72..c28a5af 100644 --- a/app/components/Header.tsx +++ b/app/components/Header.tsx @@ -1,132 +1,138 @@ "use client"; -import {useEffect, useState} from "react"; +import { useEffect, useState } from "react"; import Link from "next/link"; export default function Header() { + const [isVisible, setIsVisible] = useState(false); - const [isVisible, setIsVisible] = useState(false); + useEffect(() => { + setTimeout(() => { + setIsVisible(true); + }, 50); // Delay to start the animation after Projects + }, []); - useEffect(() => { - setTimeout(() => { - setIsVisible(true); - }, 50); // Delay to start the animation after Projects - }, []); + const [isSidebarOpen, setIsSidebarOpen] = useState(false); - const [isSidebarOpen, setIsSidebarOpen] = useState(false); + const toggleSidebar = () => { + setIsSidebarOpen(!isSidebarOpen); + }; - const toggleSidebar = () => { - setIsSidebarOpen(!isSidebarOpen); - }; + const scrollToSection = (id: string) => { + const element = document.getElementById(id); + if (element) { + element.scrollIntoView({ behavior: "smooth" }); + } else { + /*go to main page and scroll*/ + window.location.href = `/#${id}`; + } + }; - const scrollToSection = (id: string) => { - const element = document.getElementById(id); - if (element) { - element.scrollIntoView({behavior: "smooth"}); - } else { - /*go to main page and scroll*/ - window.location.href = `/#${id}`; - } - }; - - return ( -
-
-
- -
-
- -
- -
+
+
+ +
+
+ +
+ +
+ +
+ + +
- ); -} \ No newline at end of file +

© 2025 Dennis

+
+
+ ); +} diff --git a/app/components/Hero.tsx b/app/components/Hero.tsx index e4a4765..1173d95 100644 --- a/app/components/Hero.tsx +++ b/app/components/Hero.tsx @@ -1,15 +1,14 @@ -// app/components/Hero.tsx -import React, {useEffect, useState} from "react"; +import React, { useEffect, useState } from "react"; import Image from "next/image"; export default function Hero() { - const [isVisible, setIsVisible] = useState(false); + const [isVisible, setIsVisible] = useState(false); - useEffect(() => { - setTimeout(() => { - setIsVisible(true); - }, 150); // Delay to start the animation - }, []); + useEffect(() => { + setTimeout(() => { + setIsVisible(true); + }, 150); // Delay to start the animation + }, []); return (
([]); - const [isVisible, setIsVisible] = useState(false); + const [projects, setProjects] = useState([]); + const [isVisible, setIsVisible] = useState(false); - useEffect(() => { - const fetchProjects = async () => { - try { - const response = await fetch("/api/fetchAllProjects"); - if (!response.ok) { - console.error(`Failed to fetch projects: ${response.statusText}`); - return []; - } - const projectsData = (await response.json()) as ProjectsData; - setProjects(projectsData.posts); + useEffect(() => { + const fetchProjects = async () => { + try { + const response = await fetch("/api/fetchAllProjects"); + if (!response.ok) { + console.error(`Failed to fetch projects: ${response.statusText}`); + return []; + } + const projectsData = (await response.json()) as ProjectsData; + if (!projectsData || !projectsData.posts) { + console.error("Invalid projects data"); + return; + } + setProjects(projectsData.posts); - setTimeout(() => { - setIsVisible(true); - }, 250); // Delay to start the animation after Hero - } catch (error) { - console.error("Failed to fetch projects:", error); - } - }; - fetchProjects(); - }, []); + setTimeout(() => { + setIsVisible(true); + }, 250); // Delay to start the animation after Hero + } catch (error) { + console.error("Failed to fetch projects:", error); + } + }; + fetchProjects(); + }, []); - const numberOfProjects = projects.length; - return ( -
-

Projects

-
- {projects.map((project, index) => ( - -
-

- {project.title} -

-

{project.meta_description}

-
- - ))} -
-

More to come

-

...

-
+ const numberOfProjects = projects.length; + return ( +
+

Projects

+
+ {projects.map((project, index) => ( + +
+

+ {project.title} +

+

{project.meta_description}

-
- ); + + ))} +
+

More to come

+

...

+
+
+
+ ); } diff --git a/app/projects/[slug]/page.tsx b/app/projects/[slug]/page.tsx index 13dddee..ec202e0 100644 --- a/app/projects/[slug]/page.tsx +++ b/app/projects/[slug]/page.tsx @@ -1,12 +1,12 @@ "use client"; import { - useRouter, - useSearchParams, - useParams, - usePathname, + useRouter, + useSearchParams, + useParams, + usePathname, } from "next/navigation"; -import {useEffect, useState} from "react"; +import { useEffect, useState } from "react"; import Link from "next/link"; @@ -16,154 +16,156 @@ import Image from "next/image"; import "@/app/styles/ghostContent.css"; // Import the global styles interface Project { - slug: string; - id: string; - title: string; - feature_image: string; - visibility: string; - published_at: string; - updated_at: string; - html: string; - reading_time: number; - meta_description: string; + slug: string; + id: string; + title: string; + feature_image: string; + visibility: string; + published_at: string; + updated_at: string; + html: string; + reading_time: number; + meta_description: string; } const ProjectDetails = () => { - const router = useRouter(); - const searchParams = useSearchParams(); - const params = useParams(); - const pathname = usePathname(); - const [project, setProject] = useState(null); - const [isVisible, setIsVisible] = useState(false); - const [error, setError] = useState(null); + const router = useRouter(); + const searchParams = useSearchParams(); + const params = useParams(); + const pathname = usePathname(); + const [project, setProject] = useState(null); + const [isVisible, setIsVisible] = useState(false); + const [error, setError] = useState(null); - useEffect(() => { - setTimeout(() => { - setIsVisible(true); - }, 150); // Delay to start the animation - }, []); + useEffect(() => { + setTimeout(() => { + setIsVisible(true); + }, 150); // Delay to start the animation + }, []); - useEffect(() => { - const projectData = searchParams.get("project"); - if (projectData) { - setProject(JSON.parse(projectData as string)); - // Remove the project data from the URL without reloading the page - if (typeof window !== "undefined") { - const url = new URL(window.location.href); - url.searchParams.delete("project"); - window.history.replaceState({}, "", url.toString()); - } - } else { - // Fetch project data based on slug from URL - const slug = params.slug as string; - try { - fetchProjectData(slug); - } catch (error) { - console.error(error); - setError("Failed to fetch project data"); - } - } - }, [searchParams, router, params, pathname]); - - const fetchProjectData = async (slug: string) => { - try { - const response = await fetch(`/api/fetchProject?slug=${slug}`); - if (!response.ok) { - setError("Failed to fetch project Data"); - } - const projectData = (await response.json()) as { posts: Project[] }; - if (projectData.posts.length === 0) { - setError("Project not found"); - } - setProject(projectData.posts[0]); - } catch (error) { - console.error("Failed to fetch project data:", error); - setError("Project not found"); - } - }; - - if (error) { - return ( -
-
-
-
-

- 404 -

-

- {error} -

- - Go Back Home - -
-
- -
- ); + useEffect(() => { + const projectData = searchParams.get("project"); + if (projectData) { + setProject(JSON.parse(projectData as string)); + // Remove the project data from the URL without reloading the page + if (typeof window !== "undefined") { + const url = new URL(window.location.href); + url.searchParams.delete("project"); + window.history.replaceState({}, "", url.toString()); + } + } else { + // Fetch project data based on slug from URL + const slug = params.slug as string; + try { + fetchProjectData(slug); + } catch (error) { + console.error(error); + setError("Failed to fetch project data"); + } } + }, [searchParams, router, params, pathname]); - if (!project) { - return ( -
-
-
-
-
- -
- ); + const fetchProjectData = async (slug: string) => { + try { + const response = await fetch(`/api/fetchProject?slug=${slug}`); + if (!response.ok) { + setError("Failed to fetch project Data"); + } + const projectData = (await response.json()) as { posts: Project[] }; + if ( + !projectData || + !projectData.posts || + projectData.posts.length === 0 + ) { + setError("Project not found"); + } + setProject(projectData.posts[0]); + } catch (error) { + console.error("Failed to fetch project data:", error); + setError("Project not found"); } + }; - const featureImageUrl = project.feature_image - ? `/api/fetchImage?url=${encodeURIComponent(project.feature_image)}` - : ""; - + if (error) { return ( -
-
-
-
- {featureImageUrl && ( -
- {project.title} -
- )} -
-
-

- {project.title} -

-
- - {/* Project Content */} -
-
-
-
-
-
- +
+
+
+
+

+ 404 +

+

+ {error} +

+ + Go Back Home + +
+ +
); + } + + if (!project) { + return ( +
+
+
+
+
+ +
+ ); + } + + const featureImageUrl = project.feature_image + ? `/api/fetchImage?url=${encodeURIComponent(project.feature_image)}` + : ""; + + return ( +
+
+
+
+ {featureImageUrl && ( +
+ {project.title} +
+ )} +
+
+

+ {project.title} +

+
+ + {/* Project Content */} +
+
+
+
+
+
+ +
+ ); }; export default ProjectDetails; diff --git a/app/sitemap.xml/route.tsx b/app/sitemap.xml/route.tsx index b9da8ff..2ca03f5 100644 --- a/app/sitemap.xml/route.tsx +++ b/app/sitemap.xml/route.tsx @@ -3,7 +3,7 @@ import {NextResponse} from "next/server"; export const dynamic = 'force-dynamic'; export async function GET() { - const baseUrl = process.env.NEXT_PUBLIC_BASE_URL || "https://dki.one"; + const baseUrl = process.env.NEXT_PUBLIC_BASE_URL; const apiUrl = `${baseUrl}/api/sitemap`; // Verwende die vollständige URL zur API try { diff --git a/next.config.ts b/next.config.ts index fe2f713..6332ec9 100644 --- a/next.config.ts +++ b/next.config.ts @@ -1,14 +1,16 @@ import type { NextConfig } from "next"; import dotenv from "dotenv"; +import path from "path"; -dotenv.config(); +// Lade die .env Datei aus dem Arbeitsverzeichnis +dotenv.config({ path: path.resolve(__dirname, '.env') }); const nextConfig: NextConfig = { env: { - GHOST_API_KEY: process.env.GHOST_API_KEY, - GHOST_API_URL: process.env.GHOST_API_URL, - MY_EMAIL: process.env.MY_EMAIL, - MY_PASSWORD: process.env.MY_PASSWORD, + NEXT_PUBLIC_GHOST_API_KEY: process.env.NEXT_PUBLIC_GHOST_API_KEY, + NEXT_PUBLIC_GHOST_API_URL: process.env.NEXT_PUBLIC_GHOST_API_URL, + NEXT_PUBLIC_MY_EMAIL: process.env.NEXT_PUBLIC_MY_EMAIL, + NEXT_PUBLIC_MY_PASSWORD: process.env.NEXT_PUBLIC_MY_PASSWORD, NEXT_PUBLIC_BASE_URL: process.env.NEXT_PUBLIC_BASE_URL, }, };