From eb999a70c6e4a0bb633aba8dd4a7d6bb66c3a8db Mon Sep 17 00:00:00 2001 From: Denshooter Date: Tue, 18 Feb 2025 14:35:33 +0100 Subject: [PATCH 01/21] =?UTF-8?q?=F0=9F=9A=80=20refactor:=20simplify=20dep?= =?UTF-8?q?loyment=20process=20in=20workflow=20file?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy.yml | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index efc95c5..d797da8 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -43,28 +43,17 @@ jobs: - name: Zero-Downtime Deployment run: | CONTAINER_NAME="nextjs-$DEPLOY_ENV" - NEW_CONTAINER_NAME="$CONTAINER_NAME-new" - - # Entferne vorhandenen temporären Container, falls vorhanden - docker rm -f "$NEW_CONTAINER_NAME" || true echo "Deploying $CONTAINER_NAME with $IMAGE_NAME" - # Starte neuen Container auf einem temporären Port - docker run -d --name "$NEW_CONTAINER_NAME" --network big-bear-ghost_ghost-network -p 40000:3000 \ - -e NODE_ENV=production \ - "$IMAGE_NAME" - - sleep 10 - if [ "$(docker inspect --format='{{.State.Running}}' "$NEW_CONTAINER_NAME")" = "true" ]; then docker stop "$CONTAINER_NAME" || true docker rm "$CONTAINER_NAME" || true - docker rename "$NEW_CONTAINER_NAME" "$CONTAINER_NAME" - docker network connect big-bear-ghost_ghost-network "$CONTAINER_NAME" + + docker run -d --name "$CONTAINER_NAME" -p $PORT:3000 "$IMAGE_NAME" echo "Deployment erfolgreich!" else echo "Neuer Container konnte nicht gestartet werden!" - docker logs "$NEW_CONTAINER_NAME" + docker logs "$CONTAINER_NAME" exit 1 fi From 78c26320023e08d928158f6e2f7a65e351fbc45b Mon Sep 17 00:00:00 2001 From: Denshooter Date: Tue, 18 Feb 2025 18:35:24 +0100 Subject: [PATCH 02/21] =?UTF-8?q?=F0=9F=9A=80=20chore:=20add=20IMAGE=5FNAM?= =?UTF-8?q?E=20to=20GITHUB=5FENV=20for=20deployment=20workflow?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index d797da8..477090e 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -39,6 +39,7 @@ jobs: IMAGE_NAME="ghcr.io/${{ github.repository_owner }}/my-nextjs-app:${{ github.event.workflow_run.head_branch }}" IMAGE_NAME=$(echo "$IMAGE_NAME" | tr '[:upper:]' '[:lower:]') docker pull "$IMAGE_NAME" + echo "$IMAGE_NAME" >> $GITHUB_ENV - name: Zero-Downtime Deployment run: | From aaf15aedd54bb9914bfc59f07b408c968f1d34e6 Mon Sep 17 00:00:00 2001 From: Denshooter Date: Tue, 18 Feb 2025 18:45:10 +0100 Subject: [PATCH 03/21] =?UTF-8?q?=E2=9C=A8=20chore:=20simplify=20deploymen?= =?UTF-8?q?t=20logging=20in=20workflow=20file?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 477090e..b20b829 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -39,13 +39,12 @@ jobs: IMAGE_NAME="ghcr.io/${{ github.repository_owner }}/my-nextjs-app:${{ github.event.workflow_run.head_branch }}" IMAGE_NAME=$(echo "$IMAGE_NAME" | tr '[:upper:]' '[:lower:]') docker pull "$IMAGE_NAME" - echo "$IMAGE_NAME" >> $GITHUB_ENV - name: Zero-Downtime Deployment run: | CONTAINER_NAME="nextjs-$DEPLOY_ENV" - echo "Deploying $CONTAINER_NAME with $IMAGE_NAME" + echo "Deploying $CONTAINER_NAME" if [ "$(docker inspect --format='{{.State.Running}}' "$NEW_CONTAINER_NAME")" = "true" ]; then docker stop "$CONTAINER_NAME" || true From afce84dde0fbab08b95d9b8bede964245ebd4362 Mon Sep 17 00:00:00 2001 From: Denshooter Date: Fri, 21 Feb 2025 16:03:02 +0100 Subject: [PATCH 04/21] =?UTF-8?q?=F0=9F=9A=80=20fix:=20correct=20container?= =?UTF-8?q?=20name=20in=20deployment=20script=20logic?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index f70058d..ef428fb 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -46,11 +46,13 @@ jobs: echo "Deploying $CONTAINER_NAME" - if [ "$(docker inspect --format='{{.State.Running}}' "$NEW_CONTAINER_NAME")" = "true" ]; then + if [ "$(docker inspect --format='{{.State.Running}}' "$CONTAINER_NAME")" = "true" ]; then docker stop "$CONTAINER_NAME" || true docker rm "$CONTAINER_NAME" || true + fi - docker run -d --name "$CONTAINER_NAME" -p $PORT:3000 "$IMAGE_NAME" + docker run -d --name "$CONTAINER_NAME" -p $PORT:3000 "$IMAGE_NAME" + if [ "$(docker inspect --format='{{.State.Running}}' "$CONTAINER_NAME")" = "true" ]; then echo "Deployment erfolgreich!" else echo "Neuer Container konnte nicht gestartet werden!" From 10a27ec91f1c305d92b2cc2b99c1ebd7044cec3e Mon Sep 17 00:00:00 2001 From: Denshooter Date: Fri, 21 Feb 2025 16:51:25 +0100 Subject: [PATCH 05/21] =?UTF-8?q?=F0=9F=9A=80=20refactor:=20rename=20job?= =?UTF-8?q?=20and=20streamline=20deployment=20steps?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy.yml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index ef428fb..5d5d227 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -34,14 +34,11 @@ jobs: - name: Log in to GHCR run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.repository_owner }} --password-stdin - - name: Pull Docker Image + - name: Pull & Deploy Docker Image run: | IMAGE_NAME="ghcr.io/${{ github.repository_owner }}/my-nextjs-app:${{ github.event.workflow_run.head_branch }}" IMAGE_NAME=$(echo "$IMAGE_NAME" | tr '[:upper:]' '[:lower:]') docker pull "$IMAGE_NAME" - - - name: Zero-Downtime Deployment - run: | CONTAINER_NAME="nextjs-$DEPLOY_ENV" echo "Deploying $CONTAINER_NAME" From b0f2533710cf179e6ec91ee197da6bbc156f1bbe Mon Sep 17 00:00:00 2001 From: denshooter <44590296+denshooter@users.noreply.github.com> Date: Fri, 21 Feb 2025 17:08:11 +0100 Subject: [PATCH 06/21] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 99336a5..9370f93 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Dennis Konkol's Portfolio +# Dennis Konkol's Portfolio Website This is a [Next.js](https://nextjs.org) project bootstrapped with [ `create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app). From a00e8241d262acefb4d4423250633b9b7ae2564a Mon Sep 17 00:00:00 2001 From: Denshooter Date: Sat, 22 Feb 2025 17:15:05 +0100 Subject: [PATCH 07/21] =?UTF-8?q?=E2=9C=A8=20fix:=20prevent=20multiple=20f?= =?UTF-8?q?orm=20submissions=20in=20Contact=20component?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 3 --- app/components/Contact.tsx | 35 +++++++++++++++++++++++------------ 2 files changed, 23 insertions(+), 15 deletions(-) diff --git a/Dockerfile b/Dockerfile index 24ba3e3..8ffb440 100644 --- a/Dockerfile +++ b/Dockerfile @@ -19,9 +19,6 @@ COPY .env .env # Build the Next.js application RUN npm run build -# Set environmental variable for production mode -ENV NODE_ENV=production - # Expose the port the app runs on EXPOSE 3000 diff --git a/app/components/Contact.tsx b/app/components/Contact.tsx index 3898dc8..28cfd2f 100644 --- a/app/components/Contact.tsx +++ b/app/components/Contact.tsx @@ -40,19 +40,30 @@ export default function Contact() { // Convert FormData to a plain object const jsonData = JSON.stringify(data); - const response = await sendEmail(jsonData); - if (response.success) { - form.reset(); - } + //prevent multiple submissions + const submitButton = form.querySelector("button[type='submit']"); + if (submitButton) { + submitButton.setAttribute("disabled", "true"); + submitButton.textContent = "Sending..."; - setBanner({ - show: true, - message: response.message, - type: response.success ? "success" : "error", - }); - setTimeout(() => { - setBanner((prev) => ({ ...prev, show: false })); - }, 3000); + const response = await sendEmail(jsonData); + if (response.success) { + form.reset(); + submitButton.textContent = "Sent!"; + setTimeout(() => { + submitButton.removeAttribute("disabled"); + submitButton.textContent = "Send"; + }, 2000); + } + setBanner({ + show: true, + message: response.message, + type: response.success ? "success" : "error", + }); + setTimeout(() => { + setBanner((prev) => ({ ...prev, show: false })); + }, 3000); + } } return (
Date: Sat, 22 Feb 2025 21:34:45 +0100 Subject: [PATCH 08/21] =?UTF-8?q?=E2=9C=A8=20feat:=20honeypot=20and=20time?= =?UTF-8?q?stamp=20checks=20to=20form=20submission?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/components/Contact.tsx | 55 +++++++++++++++++++++++++++++++++++--- 1 file changed, 51 insertions(+), 4 deletions(-) diff --git a/app/components/Contact.tsx b/app/components/Contact.tsx index 28cfd2f..442b032 100644 --- a/app/components/Contact.tsx +++ b/app/components/Contact.tsx @@ -18,11 +18,14 @@ export default function Contact() { message: "", type: "success", }); + // Record the time when the form is rendered + const [formLoadedTimestamp, setFormLoadedTimestamp] = useState(Date.now()); useEffect(() => { + setFormLoadedTimestamp(Date.now()); setTimeout(() => { setIsVisible(true); - }, 350); // Delay to start the animation after Projects + }, 350); }, []); async function onSubmit(e: React.FormEvent) { @@ -31,16 +34,44 @@ export default function Contact() { const form = e.currentTarget as HTMLFormElement; const formData = new FormData(form); + // Honeypot check: if the hidden field has a value, it's likely a bot. + const honeypot = formData.get("hp-field"); + if (honeypot) { + setBanner({ + show: true, + message: "Bot detected", + type: "error", + }); + setTimeout(() => { + setBanner((prev) => ({ ...prev, show: false })); + }, 3000); + return; + } + + // Time based anti-bot check: + // Read the timestamp from the hidden field and ensure at least 3 seconds have passed. + const timestampStr = formData.get("timestamp") as string; + const timestamp = parseInt(timestampStr, 10); + if (Date.now() - timestamp < 3000) { + setBanner({ + show: true, + message: "Please take your time filling out the form.", + type: "error", + }); + setTimeout(() => { + setBanner((prev) => ({ ...prev, show: false })); + }, 3000); + return; + } + const data: ContactFormData = { name: formData.get("name") as string, email: formData.get("email") as string, message: formData.get("message") as string, }; - // Convert FormData to a plain object const jsonData = JSON.stringify(data); - //prevent multiple submissions const submitButton = form.querySelector("button[type='submit']"); if (submitButton) { submitButton.setAttribute("disabled", "true"); @@ -65,6 +96,7 @@ export default function Contact() { }, 3000); } } + return (
{banner.show && (
{banner.message}
)}
+ {/* Honeypot field: should remain empty */} + + {/* Hidden timestamp field to check how fast the form was filled */} + Date: Sat, 22 Feb 2025 21:53:57 +0100 Subject: [PATCH 09/21] =?UTF-8?q?=E2=9C=A8=20refactor:=20simplify=20contac?= =?UTF-8?q?t=20form=20and=20improve=20UI=20elements?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/__tests__/components/Contact.test.tsx | 32 ++++-- app/components/Contact.tsx | 132 ++++++++++++++-------- 2 files changed, 111 insertions(+), 53 deletions(-) diff --git a/app/__tests__/components/Contact.test.tsx b/app/__tests__/components/Contact.test.tsx index a853957..5c13912 100644 --- a/app/__tests__/components/Contact.test.tsx +++ b/app/__tests__/components/Contact.test.tsx @@ -1,4 +1,4 @@ -import { render, screen, fireEvent } from '@testing-library/react'; +import { render, screen, fireEvent, waitFor } from '@testing-library/react'; import Contact from '@/app/components/Contact'; import '@testing-library/jest-dom'; @@ -10,20 +10,34 @@ global.fetch = jest.fn(() => ) as jest.Mock; describe('Contact', () => { + beforeAll(() => { + jest.useFakeTimers('modern'); + }); + + afterAll(() => { + jest.useRealTimers(); + }); + it('renders the contact form', () => { render(); - expect(screen.getByPlaceholderText('Name')).toBeInTheDocument(); - expect(screen.getByPlaceholderText('Email')).toBeInTheDocument(); - expect(screen.getByPlaceholderText('Message')).toBeInTheDocument(); + expect(screen.getByPlaceholderText('Your Name')).toBeInTheDocument(); + expect(screen.getByPlaceholderText('you@example.com')).toBeInTheDocument(); + expect(screen.getByPlaceholderText('Your Message...')).toBeInTheDocument(); + expect(screen.getByLabelText('I accept the privacy policy.')).toBeInTheDocument(); }); it('submits the form', async () => { render(); - fireEvent.change(screen.getByPlaceholderText('Name'), { target: { value: 'John Doe' } }); - fireEvent.change(screen.getByPlaceholderText('Email'), { target: { value: 'john@example.com' } }); - fireEvent.change(screen.getByPlaceholderText('Message'), { target: { value: 'Hello!' } }); - fireEvent.click(screen.getByText('Send')); - expect(await screen.findByText('Email sent')).toBeInTheDocument(); + // Fast forward time to ensure the timestamp check passes + jest.advanceTimersByTime(3000); + + fireEvent.change(screen.getByPlaceholderText('Your Name'), { target: { value: 'John Doe' } }); + fireEvent.change(screen.getByPlaceholderText('you@example.com'), { target: { value: 'john@example.com' } }); + fireEvent.change(screen.getByPlaceholderText('Your Message...'), { target: { value: 'Hello!' } }); + fireEvent.click(screen.getByLabelText('I accept the privacy policy.')); + fireEvent.click(screen.getByText('Send Message')); + + await waitFor(() => expect(screen.getByText('Email sent')).toBeInTheDocument()); }); }); \ No newline at end of file diff --git a/app/components/Contact.tsx b/app/components/Contact.tsx index 442b032..b7a7109 100644 --- a/app/components/Contact.tsx +++ b/app/components/Contact.tsx @@ -1,5 +1,6 @@ import React, { useEffect, useState } from "react"; import { sendEmail } from "@/app/utils/send-email"; +import Link from "next/link"; export type ContactFormData = { name: string; @@ -34,7 +35,7 @@ export default function Contact() { const form = e.currentTarget as HTMLFormElement; const formData = new FormData(form); - // Honeypot check: if the hidden field has a value, it's likely a bot. + // Honeypot check const honeypot = formData.get("hp-field"); if (honeypot) { setBanner({ @@ -42,14 +43,11 @@ export default function Contact() { message: "Bot detected", type: "error", }); - setTimeout(() => { - setBanner((prev) => ({ ...prev, show: false })); - }, 3000); + setTimeout(() => setBanner((prev) => ({ ...prev, show: false })), 3000); return; } - // Time based anti-bot check: - // Read the timestamp from the hidden field and ensure at least 3 seconds have passed. + // Time-based anti-bot check const timestampStr = formData.get("timestamp") as string; const timestamp = parseInt(timestampStr, 10); if (Date.now() - timestamp < 3000) { @@ -58,9 +56,7 @@ export default function Contact() { message: "Please take your time filling out the form.", type: "error", }); - setTimeout(() => { - setBanner((prev) => ({ ...prev, show: false })); - }, 3000); + setTimeout(() => setBanner((prev) => ({ ...prev, show: false })), 3000); return; } @@ -83,7 +79,7 @@ export default function Contact() { submitButton.textContent = "Sent!"; setTimeout(() => { submitButton.removeAttribute("disabled"); - submitButton.textContent = "Send"; + submitButton.textContent = "Send Message"; }, 2000); } setBanner({ @@ -91,70 +87,118 @@ export default function Contact() { message: response.message, type: response.success ? "success" : "error", }); - setTimeout(() => { - setBanner((prev) => ({ ...prev, show: false })); - }, 3000); + setTimeout(() => setBanner((prev) => ({ ...prev, show: false })), 3000); } } return (
-

- Contact Me +

+ Get in Touch

-
+
{banner.show && (
{banner.message}
)} - - {/* Honeypot field: should remain empty */} + + {/* Honeypot field */} - {/* Hidden timestamp field to check how fast the form was filled */} + {/* Hidden timestamp field */} - - - + +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+
From a36cec04c79425b2c3f5e16aa04dc45c5129f183 Mon Sep 17 00:00:00 2001 From: Denshooter Date: Sat, 22 Feb 2025 23:16:36 +0100 Subject: [PATCH 10/21] =?UTF-8?q?=E2=9C=A8=20feat:=20add=20responsive=20ma?= =?UTF-8?q?sonry=20layout=20for=20projects=20display?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/components/Projects.tsx | 59 +++++++++++++++++++------------------ app/globals.css | 19 +++++++++++- package-lock.json | 9 +++++- package.json | 3 +- 4 files changed, 58 insertions(+), 32 deletions(-) diff --git a/app/components/Projects.tsx b/app/components/Projects.tsx index 5fec402..e084a34 100644 --- a/app/components/Projects.tsx +++ b/app/components/Projects.tsx @@ -1,5 +1,6 @@ import React, { useEffect, useState } from "react"; import Link from "next/link"; +import Masonry, { ResponsiveMasonry } from "react-responsive-masonry"; interface Project { slug: string; @@ -47,41 +48,41 @@ export default function Projects() { fetchProjects(); }, []); - const numberOfProjects = projects.length; return (

Projects

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

- {project.title} -

-

{project.meta_description}

-
- - ))} -
-

More to come

-

...

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

+ {project.title} +

+

+ {project.meta_description} +

+
+ + ))} +
+
); diff --git a/app/globals.css b/app/globals.css index c2b3259..c59bd8e 100644 --- a/app/globals.css +++ b/app/globals.css @@ -196,4 +196,21 @@ body { .animate-fade-out { animation: fadeOut 3s forwards; -} \ No newline at end of file +} + +.project-card { + display: flex; + flex-direction: column; + justify-content: space-between; + background: rgba(255, 255, 255, 0.45); + border-radius: 16px; + padding: 16px; + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); + transition: transform 0.3s ease; + width: 100%; + height: auto; +} + +.project-card:hover { + transform: translateY(-5px); +} diff --git a/package-lock.json b/package-lock.json index 59346b2..79c241a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,7 +18,8 @@ "node-fetch": "^3.3.2", "nodemailer": "^6.10.0", "react": "^19.0.0", - "react-dom": "^19.0.0" + "react-dom": "^19.0.0", + "react-responsive-masonry": "^2.7.1" }, "devDependencies": { "@eslint/eslintrc": "^3", @@ -9249,6 +9250,12 @@ "dev": true, "license": "MIT" }, + "node_modules/react-responsive-masonry": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/react-responsive-masonry/-/react-responsive-masonry-2.7.1.tgz", + "integrity": "sha512-Q+u+nOH87PzjqGFd2PgTcmLpHPZnCmUPREHYoNBc8dwJv6fi51p9U6hqwG8g/T8MN86HrFjrU+uQU6yvETU7cA==", + "license": "MIT" + }, "node_modules/read-cache": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", diff --git a/package.json b/package.json index e6b8cab..7ec56f7 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,8 @@ "node-fetch": "^3.3.2", "nodemailer": "^6.10.0", "react": "^19.0.0", - "react-dom": "^19.0.0" + "react-dom": "^19.0.0", + "react-responsive-masonry": "^2.7.1" }, "devDependencies": { "@eslint/eslintrc": "^3", From 725bbe5d50788890c663d8d459a5d6a511db9205 Mon Sep 17 00:00:00 2001 From: Denshooter Date: Sat, 22 Feb 2025 23:32:52 +0100 Subject: [PATCH 11/21] =?UTF-8?q?=E2=9C=A8=20style:=20Update=20project=20t?= =?UTF-8?q?itle=20size=20and=20improve=20layout=20visibility?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/components/Contact.tsx | 4 +-- app/components/Projects.tsx | 58 +++++++++++++++++++------------------ 2 files changed, 32 insertions(+), 30 deletions(-) diff --git a/app/components/Contact.tsx b/app/components/Contact.tsx index b7a7109..aeb5e9c 100644 --- a/app/components/Contact.tsx +++ b/app/components/Contact.tsx @@ -96,10 +96,10 @@ export default function Contact() { id="contact" className={`p-10 ${isVisible ? "animate-fade-in" : "opacity-0"}`} > -

+

Get in Touch

-
+
{banner.show && (
-

Projects

+

Projects

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

- {project.title} -

-

- {project.meta_description} -

-
- - ))} -
-
+
+

+ {project.title} +

+

+ {project.meta_description} +

+
+ + ))} + + + )}
); From 5affec766f1fe1aabb105dc4eae2e75131321e65 Mon Sep 17 00:00:00 2001 From: Denshooter Date: Sat, 22 Feb 2025 23:58:34 +0100 Subject: [PATCH 12/21] =?UTF-8?q?=E2=9C=A8=20fix:=20remove=20unnecessary?= =?UTF-8?q?=20test=20assertions=20and=20improve=20act=20usage?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/__tests__/api/email.test.tsx | 8 ++++++ app/__tests__/components/Contact.test.tsx | 32 ++++++++++++++++------ app/__tests__/components/Projects.test.tsx | 1 - app/api/email/route.tsx | 1 - jest.setup.ts | 17 +++++++++++- 5 files changed, 47 insertions(+), 12 deletions(-) diff --git a/app/__tests__/api/email.test.tsx b/app/__tests__/api/email.test.tsx index b09c9fe..6d59b0c 100644 --- a/app/__tests__/api/email.test.tsx +++ b/app/__tests__/api/email.test.tsx @@ -8,6 +8,14 @@ jest.mock('next/server', () => ({ }, })); +beforeAll(() => { + jest.spyOn(console, 'error').mockImplementation(() => {}); +}); + +afterAll(() => { + (console.error as jest.Mock).mockRestore(); +}); + beforeEach(() => { nodemailermock.mock.reset(); process.env.NEXT_PUBLIC_MY_EMAIL = 'test@dki.one'; diff --git a/app/__tests__/components/Contact.test.tsx b/app/__tests__/components/Contact.test.tsx index 5c13912..520c5af 100644 --- a/app/__tests__/components/Contact.test.tsx +++ b/app/__tests__/components/Contact.test.tsx @@ -1,4 +1,4 @@ -import { render, screen, fireEvent, waitFor } from '@testing-library/react'; +import { render, screen, fireEvent, waitFor, act } from '@testing-library/react'; import Contact from '@/app/components/Contact'; import '@testing-library/jest-dom'; @@ -29,15 +29,29 @@ describe('Contact', () => { it('submits the form', async () => { render(); - // Fast forward time to ensure the timestamp check passes - jest.advanceTimersByTime(3000); + // Wrap timer advancement in act + await act(async () => { + jest.advanceTimersByTime(3000); + }); - fireEvent.change(screen.getByPlaceholderText('Your Name'), { target: { value: 'John Doe' } }); - fireEvent.change(screen.getByPlaceholderText('you@example.com'), { target: { value: 'john@example.com' } }); - fireEvent.change(screen.getByPlaceholderText('Your Message...'), { target: { value: 'Hello!' } }); - fireEvent.click(screen.getByLabelText('I accept the privacy policy.')); - fireEvent.click(screen.getByText('Send Message')); + // Fire events inside act if needed + act(() => { + fireEvent.change(screen.getByPlaceholderText('Your Name'), { + target: { value: 'John Doe' }, + }); + fireEvent.change(screen.getByPlaceholderText('you@example.com'), { + target: { value: 'john@example.com' }, + }); + fireEvent.change(screen.getByPlaceholderText('Your Message...'), { + target: { value: 'Hello!' }, + }); + fireEvent.click(screen.getByLabelText('I accept the privacy policy.')); + fireEvent.click(screen.getByText('Send Message')); + }); - await waitFor(() => expect(screen.getByText('Email sent')).toBeInTheDocument()); + // Wait for the result + await waitFor(() => + expect(screen.getByText('Email sent')).toBeInTheDocument() + ); }); }); \ No newline at end of file diff --git a/app/__tests__/components/Projects.test.tsx b/app/__tests__/components/Projects.test.tsx index a4836d8..d50f1c7 100644 --- a/app/__tests__/components/Projects.test.tsx +++ b/app/__tests__/components/Projects.test.tsx @@ -38,7 +38,6 @@ describe('Projects', () => { expect(screen.getByText('Hello bla bla bla bla')).toBeInTheDocument(); expect(screen.getByText('Blockchain Based Voting System')).toBeInTheDocument(); expect(screen.getByText('This project aims to revolutionize voting systems by leveraging blockchain to ensure security, transparency, and immutability.')).toBeInTheDocument(); - expect(screen.getByText('More to come')).toBeInTheDocument(); }); }); }); \ No newline at end of file diff --git a/app/api/email/route.tsx b/app/api/email/route.tsx index 76a1168..3d286ae 100644 --- a/app/api/email/route.tsx +++ b/app/api/email/route.tsx @@ -55,7 +55,6 @@ export async function POST(request: NextRequest) { new Promise((resolve, reject) => { transport.sendMail(mailOptions, function (err, info) { if (!err) { - console.log("Email sent"); resolve(info.response); } else { console.error("Error sending email:", err); diff --git a/jest.setup.ts b/jest.setup.ts index bf9f2b6..9f5a3ea 100644 --- a/jest.setup.ts +++ b/jest.setup.ts @@ -1 +1,16 @@ -import 'whatwg-fetch'; \ No newline at end of file +import 'whatwg-fetch'; +import React from "react"; + +jest.mock("react-responsive-masonry", () => ({ + __esModule: true, + default: ({ children }: { children: React.ReactNode }) => + React.createElement("div", null, children), + get ResponsiveMasonry() { + return ({ children }: { children: React.ReactNode }) => + React.createElement("div", null, children); + }, +})); + +jest.mock('next/link', () => { + return ({ children }: { children: React.ReactNode }) => children; +}); \ No newline at end of file From 83c5cd1b7522584bb2a31d3b7243c4d4a565bb9f Mon Sep 17 00:00:00 2001 From: Denshooter Date: Sun, 23 Feb 2025 11:07:58 +0100 Subject: [PATCH 13/21] =?UTF-8?q?=E2=9C=A8=20chore:=20add=20@types/react-r?= =?UTF-8?q?esponsive-masonry=20package?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fixing with this import error on building --- package-lock.json | 11 +++++++++++ package.json | 4 ++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 79c241a..0e017b0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -31,6 +31,7 @@ "@types/nodemailer": "^6.4.17", "@types/react": "^19", "@types/react-dom": "^19", + "@types/react-responsive-masonry": "^2.6.0", "cross-env": "^7.0.3", "eslint": "^9", "eslint-config-next": "15.1.7", @@ -2543,6 +2544,16 @@ "@types/react": "^19.0.0" } }, + "node_modules/@types/react-responsive-masonry": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@types/react-responsive-masonry/-/react-responsive-masonry-2.6.0.tgz", + "integrity": "sha512-MF2ql1CjzOoL9fLWp6L3ABoyzBUP/YV71wyb3Fx+cViYNj7+tq3gDCllZHbLg1LQfGOQOEGbV2P7TOcUeGiR6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/react": "*" + } + }, "node_modules/@types/stack-utils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", diff --git a/package.json b/package.json index 7ec56f7..a3e02f3 100644 --- a/package.json +++ b/package.json @@ -21,8 +21,7 @@ "node-fetch": "^3.3.2", "nodemailer": "^6.10.0", "react": "^19.0.0", - "react-dom": "^19.0.0", - "react-responsive-masonry": "^2.7.1" + "react-dom": "^19.0.0" }, "devDependencies": { "@eslint/eslintrc": "^3", @@ -34,6 +33,7 @@ "@types/nodemailer": "^6.4.17", "@types/react": "^19", "@types/react-dom": "^19", + "@types/react-responsive-masonry": "^2.6.0", "cross-env": "^7.0.3", "eslint": "^9", "eslint-config-next": "15.1.7", From bdc2b42f27bc393fcd7582f8a65b7dd87569a0fa Mon Sep 17 00:00:00 2001 From: Denshooter Date: Sun, 23 Feb 2025 11:17:34 +0100 Subject: [PATCH 14/21] =?UTF-8?q?=E2=9C=A8=20chore:=20remove=20unused=20de?= =?UTF-8?q?v=20dependencies=20from=20package-lock.json?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 1345 ++++++++++++++++----------------------------- package.json | 3 +- 2 files changed, 491 insertions(+), 857 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0e017b0..726455a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -136,19 +136,6 @@ "url": "https://opencollective.com/babel" } }, - "node_modules/@babel/core/node_modules/json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dev": true, - "license": "MIT", - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/@babel/core/node_modules/semver": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", @@ -193,16 +180,6 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/helper-compilation-targets/node_modules/lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^3.0.2" - } - }, "node_modules/@babel/helper-compilation-targets/node_modules/semver": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", @@ -733,9 +710,9 @@ } }, "node_modules/@eslint/core": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.10.0.tgz", - "integrity": "sha512-gFHJ+xBOo4G3WRlR1e/3G8A6/KZAH6zcE/hkLRCZTi/B9avAG365QhFA8uOGzTMqgTghpn7/fSnscW++dpMSAw==", + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.12.0.tgz", + "integrity": "sha512-cmrR6pytBuSMTaBweKoGMwu3EiHiEC+DoyupPmlZ0HxBJBtIxwe+j/E4XPIKNx+Q74c8lXKPwYawBf5glsTkHg==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -746,9 +723,9 @@ } }, "node_modules/@eslint/eslintrc": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.2.0.tgz", - "integrity": "sha512-grOjVNN8P3hjJn/eIETF1wwd12DdnwFDoyceUJLYYdkpbwq3nLi+4fqrTAONx7XDALqlL220wC/RHSC/QTI/0w==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.0.tgz", + "integrity": "sha512-yaVPAiNAalnCZedKLdR21GOGILMLKPyqSLWaAjQFvYA2i/ciDi8ArYVr69Anohb6cH2Ukhqti4aFnYyPm8wdwQ==", "dev": true, "license": "MIT", "dependencies": { @@ -770,9 +747,9 @@ } }, "node_modules/@eslint/js": { - "version": "9.19.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.19.0.tgz", - "integrity": "sha512-rbq9/g38qjfqFLOVPvwjIvFFdNziEC5S65jmjPw5r6A//QH+W91akh9irMwjDN8zKUTak6W9EsAv4m/7Wnw0UQ==", + "version": "9.21.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.21.0.tgz", + "integrity": "sha512-BqStZ3HX8Yz6LvsF5ByXYrtigrV5AXADWLAGc7PH/1SxOb7/FIYYMszZZWiUou/GB9P2lXWk2SV4d+Z8h0nknw==", "dev": true, "license": "MIT", "engines": { @@ -790,13 +767,13 @@ } }, "node_modules/@eslint/plugin-kit": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.5.tgz", - "integrity": "sha512-lB05FkqEdUg2AA0xEbUz0SnkXT1LcCTa438W4IWTUh4hdOnVbQyOJ81OrDXsJk/LSiJHubgGEFoR5EHq1NsH1A==", + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.7.tgz", + "integrity": "sha512-JubJ5B2pJ4k4yGxaNLdbjrnk9d/iDz6/q8wOilpIowd6PJPgaxCuHBnBszq7Ce2TyMrywm5r4PnKm6V3iiZF+g==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@eslint/core": "^0.10.0", + "@eslint/core": "^0.12.0", "levn": "^0.4.1" }, "engines": { @@ -856,9 +833,9 @@ } }, "node_modules/@humanwhocodes/retry": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.1.tgz", - "integrity": "sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA==", + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.2.tgz", + "integrity": "sha512-xeO57FpIu4p1Ri3Jq/EXq4ClRm86dVF2z/+kvFnyqVYRavTZmaFaUBbWCOuuTh0o/g7DSsk6kc2vrS4Vl5oPOQ==", "dev": true, "license": "Apache-2.0", "engines": { @@ -1248,6 +1225,84 @@ "node": ">=12" } }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/@istanbuljs/load-nyc-config": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", @@ -1431,16 +1486,6 @@ } } }, - "node_modules/@jest/core/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/@jest/core/node_modules/ansi-styles": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", @@ -1476,19 +1521,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@jest/core/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/@jest/environment": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", @@ -1610,51 +1642,6 @@ } } }, - "node_modules/@jest/reporters/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/reporters/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", - "dev": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@jest/reporters/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/@jest/schemas": { "version": "29.6.3", "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", @@ -2032,9 +2019,9 @@ "license": "MIT" }, "node_modules/@prisma/client": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/@prisma/client/-/client-6.3.1.tgz", - "integrity": "sha512-ARAJaPs+eBkemdky/XU3cvGRl+mIPHCN2lCXsl5Vlb0E2gV+R6IN7aCI8CisRGszEZondwIsW9Iz8EJkTdykyA==", + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/@prisma/client/-/client-6.4.1.tgz", + "integrity": "sha512-A7Mwx44+GVZVexT5e2GF/WcKkEkNNKbgr059xpr5mn+oUm2ZW1svhe+0TRNBwCdzhfIZ+q23jEgsNPvKD9u+6g==", "hasInstallScript": true, "license": "Apache-2.0", "engines": { @@ -2053,61 +2040,6 @@ } } }, - "node_modules/@prisma/debug": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-6.3.1.tgz", - "integrity": "sha512-RrEBkd+HLZx+ydfmYT0jUj7wjLiS95wfTOSQ+8FQbvb6vHh5AeKfEPt/XUQ5+Buljj8hltEfOslEW57/wQIVeA==", - "license": "Apache-2.0", - "optional": true, - "peer": true - }, - "node_modules/@prisma/engines": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-6.3.1.tgz", - "integrity": "sha512-sXdqEVLyGAJ5/iUoG/Ea5AdHMN71m6PzMBWRQnLmhhOejzqAaEr8rUd623ql6OJpED4s/U4vIn4dg1qkF7vGag==", - "hasInstallScript": true, - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "@prisma/debug": "6.3.1", - "@prisma/engines-version": "6.3.0-17.acc0b9dd43eb689cbd20c9470515d719db10d0b0", - "@prisma/fetch-engine": "6.3.1", - "@prisma/get-platform": "6.3.1" - } - }, - "node_modules/@prisma/engines-version": { - "version": "6.3.0-17.acc0b9dd43eb689cbd20c9470515d719db10d0b0", - "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-6.3.0-17.acc0b9dd43eb689cbd20c9470515d719db10d0b0.tgz", - "integrity": "sha512-R/ZcMuaWZT2UBmgX3Ko6PAV3f8//ZzsjRIG1eKqp3f2rqEqVtCv+mtzuH2rBPUC9ujJ5kCb9wwpxeyCkLcHVyA==", - "license": "Apache-2.0", - "optional": true, - "peer": true - }, - "node_modules/@prisma/fetch-engine": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-6.3.1.tgz", - "integrity": "sha512-HOf/0umOgt+/S2xtZze+FHKoxpVg4YpVxROr6g2YG09VsI3Ipyb+rGvD6QGbCqkq5NTWAAZoOGNL+oy7t+IhaQ==", - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "@prisma/debug": "6.3.1", - "@prisma/engines-version": "6.3.0-17.acc0b9dd43eb689cbd20c9470515d719db10d0b0", - "@prisma/get-platform": "6.3.1" - } - }, - "node_modules/@prisma/get-platform": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-6.3.1.tgz", - "integrity": "sha512-AYLq6Hk9xG73JdLWJ3Ip9Wg/vlP7xPvftGBalsPzKDOHr/ImhwJ09eS8xC2vNT12DlzGxhfk8BkL0ve2OriNhQ==", - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "@prisma/debug": "6.3.1" - } - }, "node_modules/@resvg/resvg-wasm": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/@resvg/resvg-wasm/-/resvg-wasm-2.4.0.tgz", @@ -2209,16 +2141,6 @@ "node": ">=18" } }, - "node_modules/@testing-library/dom/node_modules/aria-query": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", - "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "dequal": "^2.0.3" - } - }, "node_modules/@testing-library/jest-dom": { "version": "6.6.3", "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.6.3.tgz", @@ -2505,9 +2427,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "22.13.4", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.4.tgz", - "integrity": "sha512-ywP2X0DYtX3y08eFVx5fNIw7/uIv8hYUKgXoK8oayJlLnKcRfEYCxWMVE1XagUdVtCJlZT1AU4LXEABW+L1Peg==", + "version": "22.13.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.5.tgz", + "integrity": "sha512-+lTU0PxZXn0Dr1NBtC7Y8cR21AJr87dLLU953CWA6pMxxv/UDc7jYAY90upcrie1nRcD6XNG5HOYEDtgW5TxAg==", "dev": true, "license": "MIT", "dependencies": { @@ -2525,9 +2447,9 @@ } }, "node_modules/@types/react": { - "version": "19.0.8", - "resolved": "https://registry.npmjs.org/@types/react/-/react-19.0.8.tgz", - "integrity": "sha512-9P/o1IGdfmQxrujGbIMDyYaaCykhLKc0NGCtYcECNUr9UAaDe4gwvV9bR6tvd5Br1SG0j+PBpbKr2UYY8CwqSw==", + "version": "19.0.10", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.0.10.tgz", + "integrity": "sha512-JuRQ9KXLEjaUNjTWpzuR231Z2WpIwczOkBEIvbHNCzQefFIT0L8IqE6NV6ULLyC1SI/i234JnDoMkfg+RjQj2g==", "dev": true, "license": "MIT", "dependencies": { @@ -2535,9 +2457,9 @@ } }, "node_modules/@types/react-dom": { - "version": "19.0.3", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.0.3.tgz", - "integrity": "sha512-0Knk+HJiMP/qOZgMyNFamlIjw9OFCsyC2ZbigmEEyXXixgre6IQpm/4V+r3qH4GC1JPvRJKInw+on2rV6YZLeA==", + "version": "19.0.4", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.0.4.tgz", + "integrity": "sha512-4fSQ8vWFkg+TGhePfUzVmat3eC14TXYSsiiDSLI0dVLsrm9gZFABjPy/Qu6TKgl1tq1Bu1yDsuQgY3A3DOjCcg==", "dev": true, "license": "MIT", "peerDependencies": { @@ -2586,21 +2508,21 @@ "license": "MIT" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.22.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.22.0.tgz", - "integrity": "sha512-4Uta6REnz/xEJMvwf72wdUnC3rr4jAQf5jnTkeRQ9b6soxLxhDEbS/pfMPoJLDfFPNVRdryqWUIV/2GZzDJFZw==", + "version": "8.24.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.24.1.tgz", + "integrity": "sha512-ll1StnKtBigWIGqvYDVuDmXJHVH4zLVot1yQ4fJtLpL7qacwkxJc1T0bptqw+miBQ/QfUbhl1TcQ4accW5KUyA==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.22.0", - "@typescript-eslint/type-utils": "8.22.0", - "@typescript-eslint/utils": "8.22.0", - "@typescript-eslint/visitor-keys": "8.22.0", + "@typescript-eslint/scope-manager": "8.24.1", + "@typescript-eslint/type-utils": "8.24.1", + "@typescript-eslint/utils": "8.24.1", + "@typescript-eslint/visitor-keys": "8.24.1", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", - "ts-api-utils": "^2.0.0" + "ts-api-utils": "^2.0.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2616,16 +2538,16 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.22.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.22.0.tgz", - "integrity": "sha512-MqtmbdNEdoNxTPzpWiWnqNac54h8JDAmkWtJExBVVnSrSmi9z+sZUt0LfKqk9rjqmKOIeRhO4fHHJ1nQIjduIQ==", + "version": "8.24.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.24.1.tgz", + "integrity": "sha512-Tqoa05bu+t5s8CTZFaGpCH2ub3QeT9YDkXbPd3uQ4SfsLoh1/vv2GEYAioPoxCWJJNsenXlC88tRjwoHNts1oQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.22.0", - "@typescript-eslint/types": "8.22.0", - "@typescript-eslint/typescript-estree": "8.22.0", - "@typescript-eslint/visitor-keys": "8.22.0", + "@typescript-eslint/scope-manager": "8.24.1", + "@typescript-eslint/types": "8.24.1", + "@typescript-eslint/typescript-estree": "8.24.1", + "@typescript-eslint/visitor-keys": "8.24.1", "debug": "^4.3.4" }, "engines": { @@ -2641,14 +2563,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.22.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.22.0.tgz", - "integrity": "sha512-/lwVV0UYgkj7wPSw0o8URy6YI64QmcOdwHuGuxWIYznO6d45ER0wXUbksr9pYdViAofpUCNJx/tAzNukgvaaiQ==", + "version": "8.24.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.24.1.tgz", + "integrity": "sha512-OdQr6BNBzwRjNEXMQyaGyZzgg7wzjYKfX2ZBV3E04hUCBDv3GQCHiz9RpqdUIiVrMgJGkXm3tcEh4vFSHreS2Q==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.22.0", - "@typescript-eslint/visitor-keys": "8.22.0" + "@typescript-eslint/types": "8.24.1", + "@typescript-eslint/visitor-keys": "8.24.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2659,16 +2581,16 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.22.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.22.0.tgz", - "integrity": "sha512-NzE3aB62fDEaGjaAYZE4LH7I1MUwHooQ98Byq0G0y3kkibPJQIXVUspzlFOmOfHhiDLwKzMlWxaNv+/qcZurJA==", + "version": "8.24.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.24.1.tgz", + "integrity": "sha512-/Do9fmNgCsQ+K4rCz0STI7lYB4phTtEXqqCAs3gZW0pnK7lWNkvWd5iW545GSmApm4AzmQXmSqXPO565B4WVrw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/typescript-estree": "8.22.0", - "@typescript-eslint/utils": "8.22.0", + "@typescript-eslint/typescript-estree": "8.24.1", + "@typescript-eslint/utils": "8.24.1", "debug": "^4.3.4", - "ts-api-utils": "^2.0.0" + "ts-api-utils": "^2.0.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2683,9 +2605,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.22.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.22.0.tgz", - "integrity": "sha512-0S4M4baNzp612zwpD4YOieP3VowOARgK2EkN/GBn95hpyF8E2fbMT55sRHWBq+Huaqk3b3XK+rxxlM8sPgGM6A==", + "version": "8.24.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.24.1.tgz", + "integrity": "sha512-9kqJ+2DkUXiuhoiYIUvIYjGcwle8pcPpdlfkemGvTObzgmYfJ5d0Qm6jwb4NBXP9W1I5tss0VIAnWFumz3mC5A==", "dev": true, "license": "MIT", "engines": { @@ -2697,20 +2619,20 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.22.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.22.0.tgz", - "integrity": "sha512-SJX99NAS2ugGOzpyhMza/tX+zDwjvwAtQFLsBo3GQxiGcvaKlqGBkmZ+Y1IdiSi9h4Q0Lr5ey+Cp9CGWNY/F/w==", + "version": "8.24.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.24.1.tgz", + "integrity": "sha512-UPyy4MJ/0RE648DSKQe9g0VDSehPINiejjA6ElqnFaFIhI6ZEiZAkUI0D5MCk0bQcTf/LVqZStvQ6K4lPn/BRg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.22.0", - "@typescript-eslint/visitor-keys": "8.22.0", + "@typescript-eslint/types": "8.24.1", + "@typescript-eslint/visitor-keys": "8.24.1", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", - "ts-api-utils": "^2.0.0" + "ts-api-utils": "^2.0.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2780,16 +2702,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.22.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.22.0.tgz", - "integrity": "sha512-T8oc1MbF8L+Bk2msAvCUzjxVB2Z2f+vXYfcucE2wOmYs7ZUwco5Ep0fYZw8quNwOiw9K8GYVL+Kgc2pETNTLOg==", + "version": "8.24.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.24.1.tgz", + "integrity": "sha512-OOcg3PMMQx9EXspId5iktsI3eMaXVwlhC8BvNnX6B5w9a4dVgpkQZuU8Hy67TolKcl+iFWq0XX+jbDGN4xWxjQ==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "8.22.0", - "@typescript-eslint/types": "8.22.0", - "@typescript-eslint/typescript-estree": "8.22.0" + "@typescript-eslint/scope-manager": "8.24.1", + "@typescript-eslint/types": "8.24.1", + "@typescript-eslint/typescript-estree": "8.24.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2804,13 +2726,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.22.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.22.0.tgz", - "integrity": "sha512-AWpYAXnUgvLNabGTy3uBylkgZoosva/miNd1I8Bz3SjotmQPbVqhO4Cczo8AsZ44XVErEBPr/CRSgaj8sG7g0w==", + "version": "8.24.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.24.1.tgz", + "integrity": "sha512-EwVHlp5l+2vp8CoqJm9KikPZgi3gbdZAtabKT9KPShGeOcJhsv4Zdo3oc8T8I0uKEmYoU4ItyxbptjF08enaxg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.22.0", + "@typescript-eslint/types": "8.24.1", "eslint-visitor-keys": "^4.2.0" }, "engines": { @@ -2935,16 +2857,13 @@ } }, "node_modules/ansi-regex": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", - "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true, "license": "MIT", "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" + "node": ">=8" } }, "node_modules/ansi-styles": { @@ -2999,13 +2918,13 @@ "license": "Python-2.0" }, "node_modules/aria-query": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", - "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", + "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", "dev": true, "license": "Apache-2.0", - "engines": { - "node": ">= 0.4" + "dependencies": { + "dequal": "^2.0.3" } }, "node_modules/array-buffer-byte-length": { @@ -3515,10 +3434,9 @@ } }, "node_modules/call-bind-apply-helpers": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.1.tgz", - "integrity": "sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g==", - "dev": true, + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -3585,9 +3503,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001696", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001696.tgz", - "integrity": "sha512-pDCPkvzfa39ehJtJ+OwGT/2yvT2SbjfHhiIW2LWOAcMQ7BzwxT/XuyUp4OTOd0XFWA6BKw0JalnBHgSi5DGJBQ==", + "version": "1.0.30001700", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001700.tgz", + "integrity": "sha512-2S6XIXwaE7K7erT8dY+kLQcpa5ms63XlRkMkReXjle+kf6c5g38vyMl+Z5y8dSxOFDhcFe+nxnn261PLxBSQsQ==", "funding": [ { "type": "opencollective", @@ -3713,69 +3631,6 @@ "node": ">=12" } }, - "node_modules/cliui/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/cliui/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/cliui/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cliui/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cliui/node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, "node_modules/co": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", @@ -4339,7 +4194,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.1", @@ -4380,9 +4234,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.101", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.101.tgz", - "integrity": "sha512-L0ISiQrP/56Acgu4/i/kfPwWSgrzYZUnQrC0+QPFuhqlLP1Ir7qzPPDVS9BcKIyWTRU8+o6CC8dKw38tSWhYIA==", + "version": "1.5.103", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.103.tgz", + "integrity": "sha512-P6+XzIkfndgsrjROJWfSvVEgNHtPgbhVyTkwLjUM2HU/h7pZRORgaTlHqfAikqxKmdJMLW8fftrdGWbd/Ds0FA==", "dev": true, "license": "ISC" }, @@ -4407,9 +4261,9 @@ "license": "MIT" }, "node_modules/enhanced-resolve": { - "version": "5.18.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.0.tgz", - "integrity": "sha512-0/r0MySGYG8YqlayBZ6MuCfECmHFdJ5qyPh8s8wa5Hnm6SaFLSK1VYCbj+NKp090Nm1caZhD+QTnmxO7esYGyQ==", + "version": "5.18.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.1.tgz", + "integrity": "sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==", "dev": true, "license": "MIT", "dependencies": { @@ -4443,13 +4297,6 @@ "is-arrayish": "^0.2.1" } }, - "node_modules/error-ex/node_modules/is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", - "dev": true, - "license": "MIT" - }, "node_modules/es-abstract": { "version": "1.23.9", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.9.tgz", @@ -4520,7 +4367,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -4530,7 +4376,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -4568,7 +4413,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", - "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0" @@ -4581,7 +4425,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", - "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -4594,13 +4437,16 @@ } }, "node_modules/es-shim-unscopables": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.2.tgz", - "integrity": "sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.1.0.tgz", + "integrity": "sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==", "dev": true, "license": "MIT", "dependencies": { - "hasown": "^2.0.0" + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" } }, "node_modules/es-to-primitive": { @@ -4672,22 +4518,22 @@ } }, "node_modules/eslint": { - "version": "9.19.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.19.0.tgz", - "integrity": "sha512-ug92j0LepKlbbEv6hD911THhoRHmbdXt2gX+VDABAW/Ir7D3nqKdv5Pf5vtlyY6HQMTEP2skXY43ueqTCWssEA==", + "version": "9.21.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.21.0.tgz", + "integrity": "sha512-KjeihdFqTPhOMXTt7StsDxriV4n66ueuF/jfPNC3j/lduHwr/ijDwJMsF+wyMJethgiKi5wniIE243vi07d3pg==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", - "@eslint/config-array": "^0.19.0", - "@eslint/core": "^0.10.0", - "@eslint/eslintrc": "^3.2.0", - "@eslint/js": "9.19.0", - "@eslint/plugin-kit": "^0.2.5", + "@eslint/config-array": "^0.19.2", + "@eslint/core": "^0.12.0", + "@eslint/eslintrc": "^3.3.0", + "@eslint/js": "9.21.0", + "@eslint/plugin-kit": "^0.2.7", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", - "@humanwhocodes/retry": "^0.4.1", + "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "@types/json-schema": "^7.0.15", "ajv": "^6.12.4", @@ -4782,20 +4628,19 @@ } }, "node_modules/eslint-import-resolver-typescript": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-3.7.0.tgz", - "integrity": "sha512-Vrwyi8HHxY97K5ebydMtffsWAn1SCR9eol49eCd5fJS4O1WV7PaAjbcjmbfJJSMz/t4Mal212Uz/fQZrOB8mow==", + "version": "3.8.3", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-3.8.3.tgz", + "integrity": "sha512-A0bu4Ks2QqDWNpeEgTQMPTngaMhuDu4yv6xpftBMAf+1ziXnpx+eSR1WRfoPTe2BAiAjHFZ7kSNx1fvr5g5pmQ==", "dev": true, "license": "ISC", "dependencies": { "@nolyfill/is-core-module": "1.0.39", "debug": "^4.3.7", "enhanced-resolve": "^5.15.0", - "fast-glob": "^3.3.2", - "get-tsconfig": "^4.7.5", + "get-tsconfig": "^4.10.0", "is-bun-module": "^1.0.2", - "is-glob": "^4.0.3", - "stable-hash": "^0.0.4" + "stable-hash": "^0.0.4", + "tinyglobby": "^0.2.12" }, "engines": { "node": "^14.18.0 || >=16.0.0" @@ -4817,36 +4662,6 @@ } } }, - "node_modules/eslint-import-resolver-typescript/node_modules/fast-glob": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", - "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.8" - }, - "engines": { - "node": ">=8.6.0" - } - }, - "node_modules/eslint-import-resolver-typescript/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/eslint-module-utils": { "version": "2.12.0", "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.12.0.tgz", @@ -4959,6 +4774,16 @@ "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9" } }, + "node_modules/eslint-plugin-jsx-a11y/node_modules/aria-query": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", + "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/eslint-plugin-react": { "version": "7.37.4", "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.4.tgz", @@ -5164,13 +4989,6 @@ "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, - "node_modules/execa/node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true, - "license": "ISC" - }, "node_modules/exit": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", @@ -5400,9 +5218,9 @@ } }, "node_modules/flatted": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.2.tgz", - "integrity": "sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", "dev": true, "license": "ISC" }, @@ -5427,9 +5245,9 @@ } }, "node_modules/for-each": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.4.tgz", - "integrity": "sha512-kKaIINnFpzW6ffJNDjjyjrk21BkDx38c0xa/klsT8VzLCaMEefv4ZTacrcVR4DmgTeBra++jMDAfS/tS799YDw==", + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", "dev": true, "license": "MIT", "dependencies": { @@ -5459,14 +5277,28 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/foreground-child/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/form-data": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz", - "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz", + "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==", "license": "MIT", "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", "mime-types": "^2.1.12" }, "engines": { @@ -5496,6 +5328,7 @@ "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, "hasInstallScript": true, "license": "MIT", "optional": true, @@ -5510,7 +5343,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" @@ -5568,18 +5400,17 @@ } }, "node_modules/get-intrinsic": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.7.tgz", - "integrity": "sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA==", - "dev": true, + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", "license": "MIT", "dependencies": { - "call-bind-apply-helpers": "^1.0.1", + "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0", + "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", - "get-proto": "^1.0.0", + "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", @@ -5606,7 +5437,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "dev": true, "license": "MIT", "dependencies": { "dunder-proto": "^1.0.1", @@ -5661,21 +5491,22 @@ } }, "node_modules/glob": { - "version": "10.4.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", - "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", "dev": true, "license": "ISC", "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" }, - "bin": { - "glob": "dist/esm/bin.mjs" + "engines": { + "node": "*" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -5694,32 +5525,6 @@ "node": ">=10.13.0" } }, - "node_modules/glob/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/glob/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/globals": { "version": "14.0.0", "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", @@ -5754,7 +5559,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -5885,7 +5689,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -5898,7 +5701,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", - "dev": true, "license": "MIT", "dependencies": { "has-symbols": "^1.0.3" @@ -5914,7 +5716,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dev": true, "license": "MIT", "dependencies": { "function-bind": "^1.1.2" @@ -6126,11 +5927,11 @@ } }, "node_modules/is-arrayish": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", - "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==", - "license": "MIT", - "optional": true + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true, + "license": "MIT" }, "node_modules/is-async-function": { "version": "2.1.1", @@ -6182,13 +5983,13 @@ } }, "node_modules/is-boolean-object": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.1.tgz", - "integrity": "sha512-l9qO6eFlUETHtuihLcYOaLKByJ1f+N4kthcU9YjHy3N+B3hWv0y/2Nd0mu/7lTFnRQHTrSdXF50HQ3bl5fEnng==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz", + "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", "dev": true, "license": "MIT", "dependencies": { - "call-bound": "^1.0.2", + "call-bound": "^1.0.3", "has-tostringtag": "^1.0.2" }, "engines": { @@ -6541,13 +6342,13 @@ } }, "node_modules/is-weakref": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.0.tgz", - "integrity": "sha512-SXM8Nwyys6nT5WP6pltOwKytLV7FqQ4UiibxVmW+EIosHcmCqkkjViTb5SNssDlkCiEYRP1/pdWUKVvZBmsR2Q==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.1.tgz", + "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==", "dev": true, "license": "MIT", "dependencies": { - "call-bound": "^1.0.2" + "call-bound": "^1.0.3" }, "engines": { "node": ">= 0.4" @@ -6913,28 +6714,6 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/jest-config/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", - "dev": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/jest-config/node_modules/pretty-format": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", @@ -7456,38 +7235,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-runtime/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", - "dev": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/jest-runtime/node_modules/strip-bom": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", - "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/jest-snapshot": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz", @@ -7767,28 +7514,6 @@ } } }, - "node_modules/jsdom/node_modules/ws": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", - "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, "node_modules/jsesc": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", @@ -7831,16 +7556,16 @@ "license": "MIT" }, "node_modules/json5": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", - "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "dev": true, "license": "MIT", - "dependencies": { - "minimist": "^1.2.0" - }, "bin": { "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" } }, "node_modules/jsx-ast-utils": { @@ -8013,11 +7738,14 @@ } }, "node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", "dev": true, - "license": "ISC" + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } }, "node_modules/lz-string": { "version": "1.5.0", @@ -8066,7 +7794,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -8178,9 +7905,9 @@ } }, "node_modules/mrmime": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.0.tgz", - "integrity": "sha512-eu38+hdgojoyq63s+yTpN4XMBdt5l8HhMhc4VKLO9KM5caLIBvUm4thi7fFaxyTmCKeNnXZ5pAlBwCUnhA09uw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", + "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==", "license": "MIT", "engines": { "node": ">=10" @@ -8436,9 +8163,9 @@ } }, "node_modules/object-inspect": { - "version": "1.13.3", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.3.tgz", - "integrity": "sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA==", + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", "dev": true, "license": "MIT", "engines": { @@ -8782,6 +8509,13 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -8891,9 +8625,9 @@ } }, "node_modules/possible-typed-array-names": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", - "integrity": "sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", "dev": true, "license": "MIT", "engines": { @@ -8901,9 +8635,9 @@ } }, "node_modules/postcss": { - "version": "8.5.1", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.1.tgz", - "integrity": "sha512-6oz2beyjc5VMn/KV1pPw8fliQkhBXrVn1Z3TVyqZxU8kZpzEKhBdmCFqI6ZbmGtamQvQGuU1sgPTk8ZrXDD7jQ==", + "version": "8.5.3", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz", + "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==", "dev": true, "funding": [ { @@ -9074,16 +8808,6 @@ "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" } }, - "node_modules/pretty-format/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/pretty-format/node_modules/ansi-styles": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", @@ -9097,42 +8821,6 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/pretty-format/node_modules/react-is": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", - "dev": true, - "license": "MIT" - }, - "node_modules/prisma": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/prisma/-/prisma-6.3.1.tgz", - "integrity": "sha512-JKCZWvBC3enxk51tY4TWzS4b5iRt4sSU1uHn2I183giZTvonXaQonzVtjLzpOHE7qu9MxY510kAtFGJwryKe3Q==", - "hasInstallScript": true, - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "@prisma/engines": "6.3.1" - }, - "bin": { - "prisma": "build/index.js" - }, - "engines": { - "node": ">=18.18" - }, - "optionalDependencies": { - "fsevents": "2.3.3" - }, - "peerDependencies": { - "typescript": ">=5.1.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, "node_modules/prompts": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", @@ -9159,6 +8847,13 @@ "react-is": "^16.13.1" } }, + "node_modules/prop-types/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "dev": true, + "license": "MIT" + }, "node_modules/proxy-from-env": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", @@ -9255,9 +8950,9 @@ } }, "node_modules/react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", "dev": true, "license": "MIT" }, @@ -9604,9 +9299,9 @@ } }, "node_modules/semver": { - "version": "7.7.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.0.tgz", - "integrity": "sha512-DrfFnPzblFmNrIZzg5RzHegbiRWg7KMR7btwi2yjHwx06zsUbO5g613sVwEV7FTwmzJu+Io0lJe2GJ3LxqpvBQ==", + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", "devOptional": true, "license": "ISC", "bin": { @@ -9805,17 +9500,11 @@ } }, "node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", "dev": true, - "license": "ISC", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } + "license": "ISC" }, "node_modules/simple-swizzle": { "version": "0.2.2", @@ -9827,6 +9516,13 @@ "is-arrayish": "^0.3.1" } }, + "node_modules/simple-swizzle/node_modules/is-arrayish": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", + "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==", + "license": "MIT", + "optional": true + }, "node_modules/sirv": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/sirv/-/sirv-2.0.4.tgz", @@ -9946,45 +9642,19 @@ "node": ">=10" } }, - "node_modules/string-length/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/string-length/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, "license": "MIT", "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" }, "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=8" } }, "node_modules/string-width-cjs": { @@ -10003,16 +9673,6 @@ "node": ">=8" } }, - "node_modules/string-width-cjs/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/string-width-cjs/node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", @@ -10020,18 +9680,12 @@ "dev": true, "license": "MIT" }, - "node_modules/string-width-cjs/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "node_modules/string-width/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } + "license": "MIT" }, "node_modules/string.prototype.codepointat": { "version": "0.2.1", @@ -10153,19 +9807,16 @@ } }, "node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "license": "MIT", "dependencies": { - "ansi-regex": "^6.0.1" + "ansi-regex": "^5.0.1" }, "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" + "node": ">=8" } }, "node_modules/strip-ansi-cjs": { @@ -10182,26 +9833,16 @@ "node": ">=8" } }, - "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "node_modules/strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", "dev": true, "license": "MIT", "engines": { "node": ">=8" } }, - "node_modules/strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, "node_modules/strip-bom-string": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/strip-bom-string/-/strip-bom-string-1.0.0.tgz", @@ -10293,6 +9934,53 @@ "node": ">=16 || 14 >=14.17" } }, + "node_modules/sucrase/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/sucrase/node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/sucrase/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -10419,28 +10107,6 @@ "node": ">=8" } }, - "node_modules/test-exclude/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", - "dev": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/thenify": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", @@ -10470,6 +10136,51 @@ "integrity": "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==", "license": "MIT" }, + "node_modules/tinyglobby": { + "version": "0.2.12", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.12.tgz", + "integrity": "sha512-qkf4trmKSIiMTs/E63cxH+ojC2unam7rJ0WrauAzpT3ECNTxGRMlaXxVbfxMUC/w0LaYk6jQ4y/nGR9uBO3tww==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.4.3", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.4.3", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.3.tgz", + "integrity": "sha512-PMXmW2y1hDDfTSRc9gaXIuCCRpuoz3Kaz8cUelp3smouvfT632ozg2vrT6lJsHKKOF59YLbOGfAWGUcKEfRMQw==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/tmpl": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", @@ -10549,9 +10260,9 @@ "license": "Apache-2.0" }, "node_modules/ts-jest": { - "version": "29.2.5", - "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.2.5.tgz", - "integrity": "sha512-KD8zB2aAZrcKIdGk4OwpJggeLcH1FgrICqDSROWqlnJXGCXK4Mn6FcdK2B6670Xr73lHMG1kHw8R87A0ecZ+vA==", + "version": "29.2.6", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.2.6.tgz", + "integrity": "sha512-yTNZVZqc8lSixm+QGVFcPe6+yj7+TWZwIesuOWvfcn4B9bz5x4NDzVCQQjOs7Hfouu36aEqfEbo9Qpo+gq8dDg==", "dev": true, "license": "MIT", "dependencies": { @@ -10562,7 +10273,7 @@ "json5": "^2.2.3", "lodash.memoize": "^4.1.2", "make-error": "^1.3.6", - "semver": "^7.6.3", + "semver": "^7.7.1", "yargs-parser": "^21.1.1" }, "bin": { @@ -10597,19 +10308,6 @@ } } }, - "node_modules/ts-jest/node_modules/json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dev": true, - "license": "MIT", - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/ts-node": { "version": "10.9.2", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", @@ -10674,6 +10372,29 @@ "strip-bom": "^3.0.0" } }, + "node_modules/tsconfig-paths/node_modules/json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/tsconfig-paths/node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", @@ -11013,6 +10734,27 @@ "node": ">= 10" } }, + "node_modules/webpack-bundle-analyzer/node_modules/ws": { + "version": "7.5.10", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", + "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", + "license": "MIT", + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/whatwg-encoding": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz", @@ -11172,18 +10914,18 @@ } }, "node_modules/wrap-ansi": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", - "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, "license": "MIT", "dependencies": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" }, "engines": { - "node": ">=12" + "node": ">=10" }, "funding": { "url": "https://github.com/chalk/wrap-ansi?sponsor=1" @@ -11208,64 +10950,6 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/wrap-ansi-cjs/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi/node_modules/ansi-styles": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", - "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", @@ -11287,24 +10971,18 @@ "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, - "node_modules/write-file-atomic/node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true, - "license": "ISC" - }, "node_modules/ws": { - "version": "7.5.10", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", - "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w==", + "dev": true, "license": "MIT", "engines": { - "node": ">=8.3.0" + "node": ">=10.0.0" }, "peerDependencies": { "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" + "utf-8-validate": ">=5.0.2" }, "peerDependenciesMeta": { "bufferutil": { @@ -11391,51 +11069,6 @@ "node": ">=12" } }, - "node_modules/yargs/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/yargs/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/yargs/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/yargs/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/yn": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", diff --git a/package.json b/package.json index a3e02f3..316b9e5 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,8 @@ "node-fetch": "^3.3.2", "nodemailer": "^6.10.0", "react": "^19.0.0", - "react-dom": "^19.0.0" + "react-dom": "^19.0.0", + "react-responsive-masonry": "^2.7.1" }, "devDependencies": { "@eslint/eslintrc": "^3", From 23a145b37e2c3449701540db87b82c523f2628eb Mon Sep 17 00:00:00 2001 From: Denshooter Date: Sun, 23 Feb 2025 14:20:23 +0100 Subject: [PATCH 15/21] =?UTF-8?q?=E2=9C=A8=20refactor:=20update=20environm?= =?UTF-8?q?ent=20variable=20usage=20and=20add=20caching?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/api/email/route.tsx | 4 +-- app/api/fetchAllProjects/route.tsx | 43 +++++++++++++++++++++++++----- app/api/fetchProject/route.tsx | 4 +-- next.config.ts | 14 ++++++---- package-lock.json | 22 +++++++++++++++ package.json | 1 + 6 files changed, 72 insertions(+), 16 deletions(-) diff --git a/app/api/email/route.tsx b/app/api/email/route.tsx index 3d286ae..691d679 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.NEXT_PUBLIC_MY_EMAIL ?? ""; - const pass = process.env.NEXT_PUBLIC_MY_PASSWORD ?? ""; + const user = process.env.MY_EMAIL ?? ""; + const pass = process.env.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 8cfd2ff..cbed346 100644 --- a/app/api/fetchAllProjects/route.tsx +++ b/app/api/fetchAllProjects/route.tsx @@ -1,25 +1,54 @@ import { NextResponse } from "next/server"; +import http from "http"; +import fetch from "node-fetch"; +import NodeCache from "node-cache"; export const runtime = "nodejs"; // Force Node runtime -const GHOST_API_URL = process.env.NEXT_PUBLIC_GHOST_API_URL; -const GHOST_API_KEY = process.env.NEXT_PUBLIC_GHOST_API_KEY; +const GHOST_API_URL = process.env.GHOST_API_URL; +const GHOST_API_KEY = process.env.GHOST_API_KEY; +const cache = new NodeCache({ stdTTL: 300 }); // Cache für 5 Minuten + +type GhostPost = { + 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; +}; + +type GhostPostsResponse = { + posts: Array; +}; export async function GET() { + const cacheKey = "ghostPosts"; + const cachedPosts = cache.get(cacheKey); + + if (cachedPosts) { + return NextResponse.json(cachedPosts); + } + try { + const agent = new http.Agent({ keepAlive: true }); const response = await fetch( `${GHOST_API_URL}/ghost/api/content/posts/?key=${GHOST_API_KEY}&limit=all`, + { agent: agent as unknown as undefined } ); - if (!response.ok) { - console.error(`Failed to fetch posts: ${response.statusText}`); - return NextResponse.json([]); - } - const posts = await response.json(); + const posts: GhostPostsResponse = await response.json() as GhostPostsResponse; if (!posts || !posts.posts) { console.error("Invalid posts data"); return NextResponse.json([]); } + + cache.set(cacheKey, posts); // Daten im Cache speichern + 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 eb0abbf..372b1bf 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.NEXT_PUBLIC_GHOST_API_URL; -const GHOST_API_KEY = process.env.NEXT_PUBLIC_GHOST_API_KEY; +const GHOST_API_URL = process.env.GHOST_API_URL; +const GHOST_API_KEY = process.env.GHOST_API_KEY; export async function GET(request: Request) { const { searchParams } = new URL(request.url); diff --git a/next.config.ts b/next.config.ts index 6332ec9..d200128 100644 --- a/next.config.ts +++ b/next.config.ts @@ -7,11 +7,15 @@ dotenv.config({ path: path.resolve(__dirname, '.env') }); const nextConfig: NextConfig = { env: { - 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, + NEXT_PUBLIC_BASE_URL: process.env.NEXT_PUBLIC_BASE_URL + }, + serverRuntimeConfig: { + GHOST_API_URL: process.env.GHOST_API_URL, + GHOST_API_KEY: process.env.GHOST_API_KEY, + MY_EMAIL: process.env.MY_EMAIL, + MY_INFO_EMAIL: process.env.MY_INFO_EMAIL, + MY_PASSWORD: process.env.MY_PASSWORD, + MY_INFO_PASSWORD: process.env.MY_INFO_PASSWORD }, }; diff --git a/package-lock.json b/package-lock.json index 726455a..57dac94 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,6 +15,7 @@ "dotenv": "^16.4.7", "gray-matter": "^4.0.3", "next": "15.1.7", + "node-cache": "^5.1.2", "node-fetch": "^3.3.2", "nodemailer": "^6.10.0", "react": "^19.0.0", @@ -3631,6 +3632,15 @@ "node": ">=12" } }, + "node_modules/clone": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==", + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, "node_modules/co": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", @@ -8039,6 +8049,18 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/node-cache": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/node-cache/-/node-cache-5.1.2.tgz", + "integrity": "sha512-t1QzWwnk4sjLWaQAS8CHgOJ+RAfmHpxFWmc36IWTiWHQfs0w5JDMBS1b1ZxQteo0vVVuWJvIUKHDkkeK7vIGCg==", + "license": "MIT", + "dependencies": { + "clone": "2.x" + }, + "engines": { + "node": ">= 8.0.0" + } + }, "node_modules/node-domexception": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", diff --git a/package.json b/package.json index 316b9e5..48f1882 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "dotenv": "^16.4.7", "gray-matter": "^4.0.3", "next": "15.1.7", + "node-cache": "^5.1.2", "node-fetch": "^3.3.2", "nodemailer": "^6.10.0", "react": "^19.0.0", From 69f254c7915e9c6f9174911f6dbcaa916e305103 Mon Sep 17 00:00:00 2001 From: Denshooter Date: Sun, 23 Feb 2025 14:42:06 +0100 Subject: [PATCH 16/21] =?UTF-8?q?=E2=9C=A8=20refactor:=20update=20environm?= =?UTF-8?q?ent=20variables=20and=20dependencies?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/build.yml | 13 ++- .github/workflows/lint.yml | 2 +- .github/workflows/test.yml | 10 +- app/__tests__/api/email.test.tsx | 8 +- app/__tests__/api/fetchAllProjects.test.tsx | 86 ++++++++------ app/__tests__/api/fetchProject.test.tsx | 4 +- app/__tests__/api/sitemap.test.tsx | 4 +- app/__tests__/components/Projects.test.tsx | 4 +- app/__tests__/projects/[slug]/page.test.tsx | 4 +- app/api/sitemap/route.tsx | 4 +- package-lock.json | 118 ++++++-------------- package.json | 2 +- 12 files changed, 116 insertions(+), 143 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 066dd72..a7d7163 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -25,13 +25,18 @@ jobs: run: | cat > .env < .env < { beforeEach(() => { nodemailermock.mock.reset(); - process.env.NEXT_PUBLIC_MY_EMAIL = 'test@dki.one'; - process.env.NEXT_PUBLIC_MY_PASSWORD = 'test-password'; + process.env.MY_EMAIL = 'test@dki.one'; + process.env.MY_PASSWORD = 'test-password'; }); describe('POST /api/email', () => { @@ -43,8 +43,8 @@ describe('POST /api/email', () => { }); it('should return an error if EMAIL or PASSWORD is missing', async () => { - delete process.env.NEXT_PUBLIC_MY_EMAIL; - delete process.env.NEXT_PUBLIC_MY_PASSWORD; + delete process.env.MY_EMAIL; + delete process.env.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 dec7974..13046e3 100644 --- a/app/__tests__/api/fetchAllProjects.test.tsx +++ b/app/__tests__/api/fetchAllProjects.test.tsx @@ -1,6 +1,43 @@ import { GET } from '@/app/api/fetchAllProjects/route'; import { NextResponse } from 'next/server'; -import { mockFetch } from '@/app/__tests__/__mocks__/mock-fetch'; + +// Wir mocken node-fetch direkt +jest.mock('node-fetch', () => { + return jest.fn(() => + Promise.resolve({ + json: () => + Promise.resolve({ + posts: [ + { + id: '67ac8dfa709c60000117d312', + title: 'Just Doing Some Testing', + meta_description: 'Hello bla bla bla bla', + slug: 'just-doing-some-testing', + updated_at: '2025-02-13T14:25:38.000+00:00', + }, + { + id: '67aaffc3709c60000117d2d9', + title: 'Blockchain Based Voting System', + meta_description: + 'This project aims to revolutionize voting systems by leveraging blockchain to ensure security, transparency, and immutability.', + slug: 'blockchain-based-voting-system', + updated_at: '2025-02-13T16:54:42.000+00:00', + }, + ], + meta: { + pagination: { + limit: 'all', + next: null, + page: 1, + pages: 1, + prev: null, + total: 2, + }, + }, + }), + }) + ); +}); jest.mock('next/server', () => ({ NextResponse: { @@ -10,48 +47,27 @@ jest.mock('next/server', () => ({ describe('GET /api/fetchAllProjects', () => { beforeAll(() => { - process.env.NEXT_PUBLIC_GHOST_API_URL = 'http://localhost:2368'; - process.env.NEXT_PUBLIC_GHOST_API_KEY = 'some-key'; - global.fetch = mockFetch({ - posts: [ - { - id: '67ac8dfa709c60000117d312', - title: 'Just Doing Some Testing', - meta_description: 'Hello bla bla bla bla', - slug: 'just-doing-some-testing', - updated_at: '2025-02-13T14:25:38.000+00:00', - }, - { - id: '67aaffc3709c60000117d2d9', - title: 'Blockchain Based Voting System', - meta_description: 'This project aims to revolutionize voting systems by leveraging blockchain to ensure security, transparency, and immutability.', - slug: 'blockchain-based-voting-system', - updated_at: '2025-02-13T16:54:42.000+00:00', - }, - ], - }); + process.env.GHOST_API_URL = 'http://localhost:2368'; + process.env.GHOST_API_KEY = 'some-key'; }); - it('should return a list of projects', async () => { + it('should return a list of projects (partial match)', async () => { await GET(); - expect(NextResponse.json).toHaveBeenCalledWith({ - posts: [ - { + // Den tatsächlichen Argumentwert extrahieren + const responseArg = (NextResponse.json as jest.Mock).mock.calls[0][0]; + + expect(responseArg).toMatchObject({ + posts: expect.arrayContaining([ + expect.objectContaining({ id: '67ac8dfa709c60000117d312', title: 'Just Doing Some Testing', - meta_description: 'Hello bla bla bla bla', - slug: 'just-doing-some-testing', - updated_at: '2025-02-13T14:25:38.000+00:00', - }, - { + }), + expect.objectContaining({ id: '67aaffc3709c60000117d2d9', title: 'Blockchain Based Voting System', - meta_description: 'This project aims to revolutionize voting systems by leveraging blockchain to ensure security, transparency, and immutability.', - slug: 'blockchain-based-voting-system', - updated_at: '2025-02-13T16:54:42.000+00:00', - }, - ], + }), + ]), }); }); }); \ No newline at end of file diff --git a/app/__tests__/api/fetchProject.test.tsx b/app/__tests__/api/fetchProject.test.tsx index da7e83a..eedc4f6 100644 --- a/app/__tests__/api/fetchProject.test.tsx +++ b/app/__tests__/api/fetchProject.test.tsx @@ -10,8 +10,8 @@ jest.mock('next/server', () => ({ describe('GET /api/fetchProject', () => { beforeAll(() => { - process.env.NEXT_PUBLIC_GHOST_API_URL = 'http://localhost:2368'; - process.env.NEXT_PUBLIC_GHOST_API_KEY = 'some-key'; + process.env.GHOST_API_URL = 'http://localhost:2368'; + process.env.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 12e144d..f97a5ed 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.NEXT_PUBLIC_GHOST_API_URL = 'http://localhost:2368'; - process.env.NEXT_PUBLIC_GHOST_API_KEY = 'test-api-key'; + process.env.GHOST_API_URL = 'http://localhost:2368'; + process.env.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 d50f1c7..0621555 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.NEXT_PUBLIC_GHOST_API_URL = 'http://localhost:2368'; - process.env.NEXT_PUBLIC_GHOST_API_KEY = 'some-key'; + process.env.GHOST_API_URL = 'http://localhost:2368'; + process.env.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 182c4b8..1cd7127 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.NEXT_PUBLIC_GHOST_API_URL = 'http://localhost:2368'; - process.env.NEXT_PUBLIC_GHOST_API_KEY = 'some-key'; + process.env.GHOST_API_URL = 'http://localhost:2368'; + process.env.GHOST_API_KEY = 'some-key'; global.fetch = mockFetch({ posts: [ { diff --git a/app/api/sitemap/route.tsx b/app/api/sitemap/route.tsx index 411833b..cc359b9 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.NEXT_PUBLIC_GHOST_API_URL; -const GHOST_API_KEY = process.env.NEXT_PUBLIC_GHOST_API_KEY; +const GHOST_API_URL = process.env.GHOST_API_URL; +const GHOST_API_KEY = process.env.GHOST_API_KEY; // Funktion, um die XML für die Sitemap zu generieren function generateXml(sitemapRoutes: { url: string; lastModified: string }[]) { diff --git a/package-lock.json b/package-lock.json index 57dac94..e0d0a8e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,7 +16,7 @@ "gray-matter": "^4.0.3", "next": "15.1.7", "node-cache": "^5.1.2", - "node-fetch": "^3.3.2", + "node-fetch": "^2.7.0", "nodemailer": "^6.10.0", "react": "^19.0.0", "react-dom": "^19.0.0", @@ -3904,15 +3904,6 @@ "dev": true, "license": "BSD-2-Clause" }, - "node_modules/data-uri-to-buffer": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", - "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", - "license": "MIT", - "engines": { - "node": ">= 12" - } - }, "node_modules/data-urls": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-3.0.2.tgz", @@ -5108,29 +5099,6 @@ "bser": "2.1.1" } }, - "node_modules/fetch-blob": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", - "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/jimmywarting" - }, - { - "type": "paypal", - "url": "https://paypal.me/jimmywarting" - } - ], - "license": "MIT", - "dependencies": { - "node-domexception": "^1.0.0", - "web-streams-polyfill": "^3.0.3" - }, - "engines": { - "node": "^12.20 || >= 14.13" - } - }, "node_modules/fflate": { "version": "0.7.4", "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.7.4.tgz", @@ -5315,18 +5283,6 @@ "node": ">= 6" } }, - "node_modules/formdata-polyfill": { - "version": "4.0.10", - "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", - "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", - "license": "MIT", - "dependencies": { - "fetch-blob": "^3.1.2" - }, - "engines": { - "node": ">=12.20.0" - } - }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -8061,41 +8017,46 @@ "node": ">= 8.0.0" } }, - "node_modules/node-domexception": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", - "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/jimmywarting" - }, - { - "type": "github", - "url": "https://paypal.me/jimmywarting" - } - ], - "license": "MIT", - "engines": { - "node": ">=10.5.0" - } - }, "node_modules/node-fetch": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", - "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", "license": "MIT", "dependencies": { - "data-uri-to-buffer": "^4.0.0", - "fetch-blob": "^3.1.4", - "formdata-polyfill": "^4.0.10" + "whatwg-url": "^5.0.0" }, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": "4.x || >=6.0.0" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/node-fetch" + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-fetch/node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" + }, + "node_modules/node-fetch/node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" + }, + "node_modules/node-fetch/node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" } }, "node_modules/node-int64": { @@ -10701,15 +10662,6 @@ "makeerror": "1.0.12" } }, - "node_modules/web-streams-polyfill": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", - "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, "node_modules/webidl-conversions": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", diff --git a/package.json b/package.json index 48f1882..7127d80 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,7 @@ "gray-matter": "^4.0.3", "next": "15.1.7", "node-cache": "^5.1.2", - "node-fetch": "^3.3.2", + "node-fetch": "^2.7.0", "nodemailer": "^6.10.0", "react": "^19.0.0", "react-dom": "^19.0.0", From efcaccc0c2edb0d2bc638b47ee075f3d47967385 Mon Sep 17 00:00:00 2001 From: Denshooter Date: Sun, 23 Feb 2025 17:38:14 +0100 Subject: [PATCH 17/21] =?UTF-8?q?=E2=9C=A8=20chore:=20streamline=20Dockerf?= =?UTF-8?q?ile=20and=20remove=20redundant=20steps?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/build.yml | 5 ----- Dockerfile | 27 +++++++++++++++++++++------ 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a7d7163..db3cdfd 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -32,11 +32,6 @@ jobs: EOF echo "Created .env file:" && cat .env - - name: Install dependencies - run: npm install - - - - name: Build & Push Docker Image run: | # Nutzt den Branch-Namen aus dem auslösenden Workflow diff --git a/Dockerfile b/Dockerfile index 8ffb440..45aa815 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ -# Use Node.js LTS image as the base -FROM node:current-alpine +# Stage 1: Build +FROM node:current-alpine AS builder # Set working directory WORKDIR /app @@ -7,20 +7,35 @@ WORKDIR /app # Copy package.json and package-lock.json COPY package*.json ./ -# Install dependencies +# Install dependencies including development dependencies RUN npm install # Copy the application code COPY . . -# Copy the .env file -COPY .env .env +# Install type definitions for react-responsive-masonry +RUN npm install --save-dev @types/react-responsive-masonry # Build the Next.js application RUN npm run build +# Stage 2: Production +FROM node:current-alpine + +# Set working directory +WORKDIR /app + +# Copy only the necessary files from the build stage +COPY --from=builder /app/package*.json ./ +COPY --from=builder /app/.next ./.next +COPY --from=builder /app/public ./public +COPY --from=builder /app/.env .env + +# Install only production dependencies +RUN npm install --only=production + # Expose the port the app runs on EXPOSE 3000 # Run the app with the start script -CMD ["npm", "start"] +ENTRYPOINT [ "npm", "run", "start" ] \ No newline at end of file From 5f2a7c2dd9611e25c0edf96b0e373d903f7aa9be Mon Sep 17 00:00:00 2001 From: Denshooter Date: Sun, 23 Feb 2025 17:53:04 +0100 Subject: [PATCH 18/21] =?UTF-8?q?=E2=9C=A8=20chore:=20add=20type=20definit?= =?UTF-8?q?ions=20for=20node-fetch=20in=20Dockerfile?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 45aa815..a9aeaec 100644 --- a/Dockerfile +++ b/Dockerfile @@ -13,8 +13,8 @@ RUN npm install # Copy the application code COPY . . -# Install type definitions for react-responsive-masonry -RUN npm install --save-dev @types/react-responsive-masonry +# Install type definitions for react-responsive-masonry and node-fetch +RUN npm install --save-dev @types/react-responsive-masonry @types/node-fetch # Build the Next.js application RUN npm run build From ded873e6b47da539f28b68e72ea10f3bd6d92e95 Mon Sep 17 00:00:00 2001 From: Dennis Konkol Date: Mon, 1 Sep 2025 23:29:58 +0000 Subject: [PATCH 19/21] =?UTF-8?q?=F0=9F=8E=A8=20Complete=20Portfolio=20Red?= =?UTF-8?q?esign:=20Modern=20Dark=20Theme=20+=20Admin=20Dashboard=20+=20En?= =?UTF-8?q?hanced=20Markdown=20Editor?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ✨ New Features: - Complete dark theme redesign with glassmorphism effects - Responsive admin dashboard with collapsible projects list - Enhanced markdown editor with live preview - Project image upload functionality - Improved project management (create, edit, delete, publish/unpublish) - Slug-based project URLs - Legal pages (Impressum, Privacy Policy) - Modern animations with Framer Motion 🔧 Improvements: - Fixed hydration errors with mounted state - Enhanced UI/UX with better spacing and proportions - Improved markdown rendering with custom components - Better project image placeholders with initials - Conditional rendering for GitHub/Live Demo links - Enhanced toolbar with categorized quick actions - Responsive grid layout for admin dashboard 📱 Technical: - Next.js 15 + TypeScript + Tailwind CSS - Local storage for project persistence - Optimized performance and responsive design --- README.md | 258 +----- app/admin/page.tsx | 858 +++++++++++++++++++ app/components/Contact.tsx | 421 +++++----- app/components/Footer.tsx | 225 +++-- app/components/Header.tsx | 279 ++++--- app/components/Hero.tsx | 214 +++-- app/components/Projects.tsx | 239 ++++-- app/globals.css | 462 +++++++---- app/layout.tsx | 98 ++- app/legal-notice/page.tsx | 118 +-- app/page.tsx | 6 +- app/privacy-policy/page.tsx | 102 ++- app/projects/[slug]/page.tsx | 287 +++---- app/projects/page.tsx | 207 +++++ package-lock.json | 1513 ++++++++++++++++++++++++++++++++-- package.json | 11 +- 16 files changed, 4050 insertions(+), 1248 deletions(-) create mode 100644 app/admin/page.tsx create mode 100644 app/projects/page.tsx diff --git a/README.md b/README.md index 9370f93..4732bc0 100644 --- a/README.md +++ b/README.md @@ -1,248 +1,30 @@ -# Dennis Konkol's Portfolio Website +# Dennis Konkol Portfolio - Modern Dark Theme -This is a [Next.js](https://nextjs.org) project bootstrapped with [ -`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app). - -## Getting Started - -First, clone the repository: - -```bash -git clone https://github.com/Denshooter/my_portfolio.git -cd my_portfolio -``` - -Then, install the dependencies: - -```bash -npm install -# or -yarn install -# or -pnpm install -# or -bun install -``` - -### Development - -To run the development server: - -```bash -npm run dev -# or -yarn dev -# or -pnpm dev -# or -bun dev -``` - -Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. - -You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. - -### Build - -To build the application for production: - -```bash -npm run build -# or -yarn build -# or -pnpm build -# or -bun build -``` - -### Start - -To start the application in production mode: - -```bash -npm run start -# or -yarn start -# or -pnpm start -# or -bun start -``` - -### Lint - -To run the linter: - -```bash -npm run lint -# or -yarn lint -# or -pnpm lint -# or -bun lint -``` - -## Project Structure - -```plaintext -my_portfolio/ -├── .github/ -│ └── workflows/ -│ └── main.yml -├── app/ -│ ├── api/ -│ │ ├── email/ -│ │ │ └── route.tsx -│ │ ├── fetchAllProjects/ -│ │ │ └── route.tsx -│ │ ├── fetchImage/ -│ │ │ └── route.tsx -│ │ ├── fetchProject/ -│ │ │ └── route.tsx -│ │ - -i mean to give more examples how it was built what it features and how it works not how to run it, but more explanations about the project and how its deployed etc. - -Got it! Here's an updated README.md that provides more detailed explanations about the project, its features, how it was built, and how it is deployed: - -```md -# Dennis Konkol's Portfolio - -This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app). It showcases my projects, provides a way to contact me, and includes various other features. +Ein modernes, responsives Portfolio mit dunklem Design, coolen Animationen und einem integrierten Markdown-Editor. ## Features -- **Responsive Design**: The portfolio is fully responsive and works on all devices. -- **Project Showcase**: Displays a list of projects fetched from a Ghost CMS. -- **Contact Form**: Allows visitors to send me messages via email. -- **SEO Optimized**: Includes metadata and Open Graph tags for better SEO. -- **Dynamic Sitemap**: Automatically generates a sitemap for better search engine indexing. -- **Cookie Consent**: Includes a cookie consent banner to comply with GDPR. -- **Zero-Downtime Deployment**: Uses Docker and GitHub Actions for seamless deployments to a Raspberry Pi. +- **Dunkles Theme** mit Glassmorphism-Effekten +- **Responsive Design** für alle Geräte +- **Smooth Animationen** mit Framer Motion +- **Markdown-Editor** für Projekte +- **Admin Dashboard** für Content-Management -## Project Structure +## Technologien -```plaintext -my_portfolio/ -├── .github/ -│ └── workflows/ -│ └── main.yml -├── app/ -│ ├── api/ -│ │ ├── email/ -│ │ │ └── route.tsx -│ │ ├── fetchAllProjects/ -│ │ │ └── route.tsx -│ │ ├── fetchImage/ -│ │ │ └── route.tsx -│ │ ├── fetchProject/ -│ │ │ └── route.tsx -│ │ ├── og/ -│ │ │ └── route.tsx -│ │ ├── projects/ -│ │ │ └── route.tsx -│ │ ├── sitemap/ -│ │ │ └── route.tsx -│ ├── components/ -│ │ ├── ClientCookieConsentBanner.tsx -│ │ ├── Contact.tsx -│ │ ├── CookieConsentBanner.tsx -│ │ ├── Footer.tsx -│ │ ├── Footer_Back.tsx -│ │ ├── Header.tsx -│ │ ├── Hero.tsx -│ │ ├── Projects.tsx -│ ├── styles/ -│ │ └── ghostContent.css -│ ├── globals.css -│ ├── layout.tsx -│ ├── metadata.tsx -│ ├── not-found.tsx -│ ├── page.tsx -│ ├── privacy-policy/ -│ │ └── page.tsx -│ ├── legal-notice/ -│ │ └── page.tsx -│ ├── projects/ -│ │ └── [slug]/ -│ │ └── page.tsx -│ ├── sitemap.xml/ -│ │ └── route.tsx -│ ├── utils/ -│ │ └── send-email.tsx -├── public/ -│ ├── icons/ -│ │ ├── github.svg -│ │ ├── linkedin.svg -│ ├── images/ -│ ├── robots.txt -├── Dockerfile -├── README.md -├── next.config.ts -├── package.json -├── tailwind.config.ts -├── tsconfig.json -└── eslint.config.mjs -``` +- Next.js 15 mit App Router +- TypeScript für Type Safety +- Tailwind CSS für Styling +- Framer Motion für Animationen +- React Markdown für Content -## How It Works +## Installation -### Project Showcase +npm install +npm run dev -Projects are fetched from a Ghost CMS using the Ghost Content API. The API routes in the `app/api` directory handle -fetching all projects, fetching a single project by slug, and fetching images. +## Verwendung -### Contact Form - -The contact form allows visitors to send me messages via email. It uses the `nodemailer` package to send emails through -an SMTP server. The API route `app/api/email/route.tsx` handles the email sending logic. - -### SEO and Open Graph - -The project includes metadata and Open Graph tags to improve SEO. The `app/metadata.tsx` file defines the metadata for -the site. The `app/api/og/route.tsx` file generates dynamic Open Graph images. - -### Dynamic Sitemap - -A dynamic sitemap is generated to help search engines index the site. The `app/api/sitemap/route.tsx` file generates the -sitemap, and the `app/sitemap.xml/route.tsx` file serves it. - -### Cookie Consent - -A cookie consent banner is included to comply with GDPR. The `app/components/CookieConsentBanner.tsx` and -`app/components/ClientCookieConsentBanner.tsx` components handle the display and logic of the cookie consent banner. - -### Zero-Downtime Deployment - -The project uses Docker and GitHub Actions for zero-downtime deployments to a Raspberry Pi. The -`.github/workflows/main.yml` file defines the GitHub Actions workflow for deploying the project. The `Dockerfile` -defines the Docker image for the project. - -## Deployment - -The project is deployed using Docker and GitHub Actions. The GitHub Actions workflow is defined in -`.github/workflows/main.yml`. It builds the Docker image and deploys it to a Raspberry Pi with zero downtime. - -### Steps to Deploy - -1. **Set Up Raspberry Pi**: Ensure Docker is installed on your Raspberry Pi. -2. **Configure GitHub Secrets**: Add the necessary secrets (e.g., `GHOST_API_KEY`, `MY_EMAIL`, `MY_PASSWORD`) to your - GitHub repository. -3. **Push to GitHub**: Push your changes to the `production`, `dev`, or `preview` branches to trigger the deployment - workflow. - -## Learn More - -To learn more about Next.js, take a look at the following resources: - -- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. -- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. - -You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions -are welcome! - -## Author - -- **Dennis Konkol** - [GitHub](https://github.com/Denshooter) | [LinkedIn](https://linkedin.com/in/dkonkol) +- `/` - Homepage +- `/projects` - Alle Projekte +- `/admin` - Admin Dashboard mit Markdown-Editor diff --git a/app/admin/page.tsx b/app/admin/page.tsx new file mode 100644 index 0000000..944e7d1 --- /dev/null +++ b/app/admin/page.tsx @@ -0,0 +1,858 @@ +"use client"; + +import { useState, useEffect } from 'react'; +import { motion } from 'framer-motion'; +import { + Save, + Eye, + Plus, + Edit, + Trash2, + Upload, + Bold, + Italic, + List, + Link as LinkIcon, + Image as ImageIcon, + Code, + Quote, + ArrowLeft, + ChevronDown, + ChevronRight, + Palette, + Smile, + FileText, + Download, + Upload as UploadIcon, + Settings, + Smartphone +} from 'lucide-react'; +import Link from 'next/link'; +import ReactMarkdown from 'react-markdown'; + +interface Project { + id: number; + title: string; + description: string; + content: string; + tags: string[]; + featured: boolean; + category: string; + date: string; + github?: string; + live?: string; + published: boolean; + imageUrl?: string; + metaDescription?: string; + keywords?: string; + ogImage?: string; + schema?: any; +} + +const AdminPage = () => { + const [mounted, setMounted] = useState(false); + + useEffect(() => { + setMounted(true); + }, []); + + const [projects, setProjects] = useState([]); + + // Load projects from localStorage on mount + useEffect(() => { + const savedProjects = localStorage.getItem('portfolio-projects'); + if (savedProjects) { + setProjects(JSON.parse(savedProjects)); + } else { + // Default projects if none exist + const defaultProjects: Project[] = [ + { + id: 1, + title: "Portfolio Website", + description: "A modern, responsive portfolio website built with Next.js, TypeScript, and Tailwind CSS.", + content: "# Portfolio Website\n\nThis is my personal portfolio website built with modern web technologies. The site features a dark theme with glassmorphism effects and smooth animations.\n\n## Features\n\n- **Responsive Design**: Works perfectly on all devices\n- **Dark Theme**: Modern dark mode with glassmorphism effects\n- **Animations**: Smooth animations powered by Framer Motion\n- **Markdown Support**: Projects are written in Markdown for easy editing\n- **Performance**: Optimized for speed and SEO\n\n## Technologies Used\n\n- Next.js 15\n- TypeScript\n- Tailwind CSS\n- Framer Motion\n- React Markdown\n\n## Development Process\n\nThe website was designed with a focus on user experience and performance. I used modern CSS techniques like CSS Grid, Flexbox, and custom properties to create a responsive layout.\n\n## Future Improvements\n\n- Add blog functionality\n- Implement project filtering\n- Add more interactive elements\n- Optimize for Core Web Vitals\n\n## Links\n\n- [Live Demo](https://dki.one)\n- [GitHub Repository](https://github.com/Denshooter/portfolio)", + tags: ["Next.js", "TypeScript", "Tailwind CSS", "Framer Motion"], + featured: true, + category: "Web Development", + date: "2024" + } + ]; + setProjects(defaultProjects); + localStorage.setItem('portfolio-projects', JSON.stringify(defaultProjects)); + } + }, []); + + const [selectedProject, setSelectedProject] = useState(null); + + const [isPreview, setIsPreview] = useState(false); + const [isProjectsCollapsed, setIsProjectsCollapsed] = useState(false); + const [formData, setFormData] = useState({ + title: '', + description: '', + content: '', + tags: '', + category: '', + featured: false, + github: '', + live: '', + published: true, + imageUrl: '' + }); + + const [markdownContent, setMarkdownContent] = useState(''); + + const categories = [ + "Web Development", + "Full-Stack", + "Web Application", + "Mobile App", + "Desktop App", + "API Development", + "Database Design", + "DevOps", + "UI/UX Design", + "Game Development", + "Machine Learning", + "Data Science", + "Blockchain", + "IoT", + "Cybersecurity" + ]; + + if (!mounted) { + return null; + } + + const handleSave = () => { + if (!formData.title || !formData.description || !markdownContent || !formData.category) { + alert('Please fill in all required fields!'); + return; + } + + try { + if (selectedProject) { + // Update existing project + const updatedProjects = projects.map(p => + p.id === selectedProject.id + ? { + ...p, + title: formData.title, + description: formData.description, + content: markdownContent, + tags: formData.tags ? formData.tags.split(',').map(tag => tag.trim()).filter(tag => tag) : [], + category: formData.category, + featured: formData.featured, + github: formData.github || undefined, + live: formData.live || undefined, + published: formData.published, + imageUrl: formData.imageUrl || undefined + } + : p + ); + setProjects(updatedProjects); + localStorage.setItem('portfolio-projects', JSON.stringify(updatedProjects)); + console.log('Project updated successfully:', selectedProject.id); + } else { + // Create new project + const newProject: Project = { + id: Math.floor(Math.random() * 1000000), + title: formData.title, + description: formData.description, + content: markdownContent, + tags: formData.tags ? formData.tags.split(',').map(tag => tag.trim()).filter(tag => tag) : [], + category: formData.category, + featured: formData.featured, + github: formData.github || undefined, + live: formData.live || undefined, + published: formData.published, + imageUrl: formData.imageUrl || undefined, + date: new Date().getFullYear().toString() + }; + const updatedProjects = [...projects, newProject]; + setProjects(updatedProjects); + localStorage.setItem('portfolio-projects', JSON.stringify(updatedProjects)); + console.log('New project created successfully:', newProject.id); + } + + resetForm(); + alert('Project saved successfully!'); + } catch (error) { + console.error('Error saving project:', error); + alert('Error saving project. Please try again.'); + } + }; + + const handleEdit = (project: Project) => { + console.log('Editing project:', project); + setSelectedProject(project); + setFormData({ + title: project.title, + description: project.description, + content: project.content, + tags: project.tags.join(', '), + category: project.category, + featured: project.featured, + github: project.github || '', + live: project.live || '', + published: project.published !== undefined ? project.published : true, + imageUrl: project.imageUrl || '' + }); + setMarkdownContent(project.content); + setIsPreview(false); + }; + + const handleDelete = (projectId: number) => { + if (confirm('Are you sure you want to delete this project?')) { + const updatedProjects = projects.filter(p => p.id !== projectId); + setProjects(updatedProjects); + localStorage.setItem('portfolio-projects', JSON.stringify(updatedProjects)); + } + }; + + const resetForm = () => { + console.log('Resetting form'); + setSelectedProject(null); + setFormData({ + title: '', + description: '', + content: '', + tags: '', + category: '', + featured: false, + github: '', + live: '', + published: true, + imageUrl: '' + }); + setMarkdownContent(''); + setIsPreview(false); + }; + + const insertMarkdown = (type: string) => { + const textarea = document.getElementById('markdown-editor') as HTMLTextAreaElement; + if (!textarea) return; + + const start = textarea.selectionStart; + const end = textarea.selectionEnd; + const text = textarea.value; + + let insertion = ''; + let cursorOffset = 0; + + switch (type) { + case 'h1': + insertion = `# ${text.substring(start, end) || 'Heading'}`; + cursorOffset = 2; + break; + case 'h2': + insertion = `## ${text.substring(start, end) || 'Heading'}`; + cursorOffset = 3; + break; + case 'bold': + insertion = `**${text.substring(start, end) || 'bold text'}**`; + cursorOffset = 2; + break; + case 'italic': + insertion = `*${text.substring(start, end) || 'italic text'}*`; + cursorOffset = 1; + break; + case 'list': + insertion = `- ${text.substring(start, end) || 'list item'}`; + cursorOffset = 2; + break; + case 'link': + insertion = `[${text.substring(start, end) || 'link text'}](url)`; + cursorOffset = 3; + break; + case 'image': + insertion = `![alt text](image-url)`; + cursorOffset = 9; + break; + case 'code': + insertion = `\`${text.substring(start, end) || 'code'}\``; + cursorOffset = 1; + break; + case 'quote': + insertion = `> ${text.substring(start, end) || 'quote text'}`; + cursorOffset = 2; + break; + case 'table': + insertion = `| Header 1 | Header 2 | Header 3 |\n|----------|----------|----------|\n| Cell 1 | Cell 2 | Cell 3 |\n| Cell 4 | Cell 5 | Cell 6 |`; + cursorOffset = 0; + break; + } + + const newText = text.substring(0, start) + insertion + text.substring(end); + setMarkdownContent(newText); + + // Set cursor position and select the placeholder text for easy editing + setTimeout(() => { + textarea.focus(); + if (type === 'h1' || type === 'h2') { + // For headings, select the placeholder text so user can type directly + const placeholderStart = start + (type === 'h1' ? 2 : 3); + const placeholderEnd = start + insertion.length; + textarea.setSelectionRange(placeholderStart, placeholderEnd); + } else { + // For other elements, position cursor appropriately + textarea.setSelectionRange(start + insertion.length - cursorOffset, start + insertion.length - cursorOffset); + } + }, 0); + }; + + const handleProjectImageUpload = (e: React.ChangeEvent) => { + const files = e.target.files; + if (!files || files.length === 0) return; + + const file = files[0]; + if (file) { + // Simulate image upload - in production you'd upload to a real service + const imageUrl = URL.createObjectURL(file); + setFormData(prev => ({ ...prev, imageUrl })); + } + }; + + const handleImageUpload = (e: React.ChangeEvent) => { + const files = e.target.files; + if (!files || files.length === 0) return; + + const file = files[0]; + if (file) { + // Create a more descriptive image URL for better organization + const imageName = file.name.replace(/\.[^/.]+$/, ""); // Remove file extension + const imageUrl = URL.createObjectURL(file); + + const textarea = document.getElementById('markdown-editor') as HTMLTextAreaElement; + if (!textarea) return; + + const start = textarea.selectionStart; + const text = textarea.value; + + // Insert image with better alt text and a newline for spacing + const insertion = `\n![${imageName}](${imageUrl})\n`; + + const newText = text.substring(0, start) + insertion + text.substring(start); + setMarkdownContent(newText); + + // Focus back to textarea and position cursor after the image + setTimeout(() => { + textarea.focus(); + const newCursorPos = start + insertion.length; + textarea.setSelectionRange(newCursorPos, newCursorPos); + }, 0); + } + }; + + return ( +
+
+ {/* Header */} + + + + Back to Home + + +

+ Admin Dashboard +

+

+ Manage your projects with the built-in Markdown editor. Create, edit, and preview your content easily. +

+
+ + {/* Projects Toggle Button - Always Visible */} +
+ setIsProjectsCollapsed(!isProjectsCollapsed)} + className="flex items-center space-x-2 px-6 py-3 bg-gradient-to-r from-gray-700 to-gray-800 hover:from-gray-600 hover:to-gray-700 rounded-xl text-white transition-all duration-200 hover:scale-105 border border-gray-600/50 shadow-lg" + title={isProjectsCollapsed ? "Show Projects" : "Hide Projects"} + whileHover={{ scale: 1.05 }} + whileTap={{ scale: 0.95 }} + > + {isProjectsCollapsed ? ( + <> + + Show Projects + + ) : ( + <> + + Hide Projects + + )} + +
+ +
+ {/* Projects List */} +
+ +
+

Projects

+ +
+ +
+ {projects.map((project) => ( +
handleEdit(project)} + > +

{project.title}

+

{project.description}

+
+ {project.category} +
+ + +
+
+
+ ))} +
+
+
+ + {/* Editor */} +
+ +
+

+ {selectedProject ? 'Edit Project' : 'New Project'} +

+
+ + + {selectedProject && ( + + )} +
+
+ + {!isPreview ? ( +
+ {/* Basic Info */} +
+
+ + setFormData({...formData, title: e.target.value})} + className="w-full px-4 py-3 bg-gray-800/50 border border-gray-700 rounded-lg text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent" + placeholder="Project title" + /> +
+ +
+ + +
+
+ + {/* Links */} +
+
+ + setFormData({...formData, github: e.target.value})} + className="w-full px-4 py-3 bg-gray-800/50 border border-gray-700 rounded-lg text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent" + placeholder="https://github.com/username/repo" + /> +
+ +
+ + setFormData({...formData, live: e.target.value})} + className="w-full px-4 py-3 bg-gray-800/50 border border-gray-700 rounded-lg text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent" + placeholder="https://demo.example.com" + /> +
+
+ + {/* Project Image */} +
+ +
+
+ {formData.imageUrl ? ( + Project preview + ) : ( +
+ + {formData.title ? formData.title.split(' ').map(word => word[0]).join('').toUpperCase() : 'P'} + +
+ )} +
+
+ + +

Upload a project image or use auto-generated initials

+
+
+
+ +
+ + -
- -
- - -
- - - +

Send Message

+ +
+
+
+ + +
+ +
+ + +
+
+ +
+ + +
+ +
+ +