From 7875ac9a0f7d49ef034adadea597551ea3af6fc8 Mon Sep 17 00:00:00 2001
From: TaxisTim-Luca <Tim-Luca.Taxis@Student.Reutlingen-University.DE>
Date: Mon, 10 Jun 2024 09:45:47 +0200
Subject: [PATCH] added two components -AddFruit -FruitList

---
 dmwt_session10/package-lock.json            |  25 +-
 dmwt_session10/package.json                 |  13 +-
 dmwt_session10/public/next.svg              |   1 -
 dmwt_session10/public/vercel.svg            |   1 -
 dmwt_session10/src/components/AddFruit.js   |  90 +++++++
 dmwt_session10/src/components/FruitList.js  |  35 +++
 dmwt_session10/src/pages/API/add-fruit.js   |   4 +-
 dmwt_session10/src/pages/API/fruit-table.js |   1 -
 dmwt_session10/src/pages/API/list-fruits.js |   4 +-
 dmwt_session10/src/pages/food.js            |   4 +-
 dmwt_session10/src/pages/index.js           |   4 +-
 dmwt_session10/src/pages/post.js            | 119 +++++++++
 package-lock.json                           | 257 ++++++++++++++++++++
 package.json                                |   8 +
 14 files changed, 549 insertions(+), 17 deletions(-)
 delete mode 100644 dmwt_session10/public/next.svg
 delete mode 100644 dmwt_session10/public/vercel.svg
 create mode 100644 dmwt_session10/src/components/AddFruit.js
 create mode 100644 dmwt_session10/src/components/FruitList.js
 create mode 100644 dmwt_session10/src/pages/post.js
 create mode 100644 package-lock.json
 create mode 100644 package.json

diff --git a/dmwt_session10/package-lock.json b/dmwt_session10/package-lock.json
index b2de4be..1d8c547 100644
--- a/dmwt_session10/package-lock.json
+++ b/dmwt_session10/package-lock.json
@@ -9,8 +9,9 @@
       "version": "0.1.0",
       "dependencies": {
         "next": "14.2.3",
-        "react": "^18",
-        "react-dom": "^18"
+        "react": "^18.3.1",
+        "react-dom": "^18.3.1",
+        "swr": "^2.2.5"
       },
       "devDependencies": {
         "eslint": "^8",
@@ -4272,6 +4273,18 @@
         "url": "https://github.com/sponsors/ljharb"
       }
     },
+    "node_modules/swr": {
+      "version": "2.2.5",
+      "resolved": "https://registry.npmjs.org/swr/-/swr-2.2.5.tgz",
+      "integrity": "sha512-QtxqyclFeAsxEUeZIYmsaQ0UjimSq1RZ9Un7I68/0ClKK/U3LoyQunwkQfJZr2fc22DfIXLNDc2wFyTEikCUpg==",
+      "dependencies": {
+        "client-only": "^0.0.1",
+        "use-sync-external-store": "^1.2.0"
+      },
+      "peerDependencies": {
+        "react": "^16.11.0 || ^17.0.0 || ^18.0.0"
+      }
+    },
     "node_modules/tailwindcss": {
       "version": "3.4.4",
       "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.4.tgz",
@@ -4527,6 +4540,14 @@
         "punycode": "^2.1.0"
       }
     },
+    "node_modules/use-sync-external-store": {
+      "version": "1.2.2",
+      "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.2.tgz",
+      "integrity": "sha512-PElTlVMwpblvbNqQ82d2n6RjStvdSoNe9FG28kNfz3WiXilJm4DdNkEzRhCZuIDwY8U08WVihhGR5iRqAwfDiw==",
+      "peerDependencies": {
+        "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
+      }
+    },
     "node_modules/util-deprecate": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
diff --git a/dmwt_session10/package.json b/dmwt_session10/package.json
index 3c92217..bb2e5d0 100644
--- a/dmwt_session10/package.json
+++ b/dmwt_session10/package.json
@@ -9,14 +9,15 @@
     "lint": "next lint"
   },
   "dependencies": {
-    "react": "^18",
-    "react-dom": "^18",
-    "next": "14.2.3"
+    "next": "14.2.3",
+    "react": "^18.3.1",
+    "react-dom": "^18.3.1",
+    "swr": "^2.2.5"
   },
   "devDependencies": {
-    "postcss": "^8",
-    "tailwindcss": "^3.4.1",
     "eslint": "^8",
-    "eslint-config-next": "14.2.3"
+    "eslint-config-next": "14.2.3",
+    "postcss": "^8",
+    "tailwindcss": "^3.4.1"
   }
 }
diff --git a/dmwt_session10/public/next.svg b/dmwt_session10/public/next.svg
deleted file mode 100644
index 5174b28..0000000
--- a/dmwt_session10/public/next.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.7zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.6h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z"/><path fill="#000" d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6v23.3c0 2.1-.4 4-1.3 5.5a9.1 9.1 0 0 1-3.8 3.5c-1.6.8-3.5 1.3-5.7 1.3-2 0-3.7-.4-5.3-1s-2.8-1.8-3.7-3.2c-.9-1.3-1.4-3-1.4-5h6c.1.8.3 1.6.7 2.2s1 1.2 1.6 1.5c.7.4 1.5.5 2.4.5 1 0 1.8-.2 2.4-.6a4 4 0 0 0 1.6-1.8c.3-.8.5-1.8.5-3V45.5zm30.9 9.1a4.4 4.4 0 0 0-2-3.3 7.5 7.5 0 0 0-4.3-1.1c-1.3 0-2.4.2-3.3.5-.9.4-1.6 1-2 1.6a3.5 3.5 0 0 0-.3 4c.3.5.7.9 1.3 1.2l1.8 1 2 .5 3.2.8c1.3.3 2.5.7 3.7 1.2a13 13 0 0 1 3.2 1.8 8.1 8.1 0 0 1 3 6.5c0 2-.5 3.7-1.5 5.1a10 10 0 0 1-4.4 3.5c-1.8.8-4.1 1.2-6.8 1.2-2.6 0-4.9-.4-6.8-1.2-2-.8-3.4-2-4.5-3.5a10 10 0 0 1-1.7-5.6h6a5 5 0 0 0 3.5 4.6c1 .4 2.2.6 3.4.6 1.3 0 2.5-.2 3.5-.6 1-.4 1.8-1 2.4-1.7a4 4 0 0 0 .8-2.4c0-.9-.2-1.6-.7-2.2a11 11 0 0 0-2.1-1.4l-3.2-1-3.8-1c-2.8-.7-5-1.7-6.6-3.2a7.2 7.2 0 0 1-2.4-5.7 8 8 0 0 1 1.7-5 10 10 0 0 1 4.3-3.5c2-.8 4-1.2 6.4-1.2 2.3 0 4.4.4 6.2 1.2 1.8.8 3.2 2 4.3 3.4 1 1.4 1.5 3 1.5 5h-5.8z"/></svg>
\ No newline at end of file
diff --git a/dmwt_session10/public/vercel.svg b/dmwt_session10/public/vercel.svg
deleted file mode 100644
index d2f8422..0000000
--- a/dmwt_session10/public/vercel.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 283 64"><path fill="black" d="M141 16c-11 0-19 7-19 18s9 18 20 18c7 0 13-3 16-7l-7-5c-2 3-6 4-9 4-5 0-9-3-10-7h28v-3c0-11-8-18-19-18zm-9 15c1-4 4-7 9-7s8 3 9 7h-18zm117-15c-11 0-19 7-19 18s9 18 20 18c6 0 12-3 16-7l-8-5c-2 3-5 4-8 4-5 0-9-3-11-7h28l1-3c0-11-8-18-19-18zm-10 15c2-4 5-7 10-7s8 3 9 7h-19zm-39 3c0 6 4 10 10 10 4 0 7-2 9-5l8 5c-3 5-9 8-17 8-11 0-19-7-19-18s8-18 19-18c8 0 14 3 17 8l-8 5c-2-3-5-5-9-5-6 0-10 4-10 10zm83-29v46h-9V5h9zM37 0l37 64H0L37 0zm92 5-27 48L74 5h10l18 30 17-30h10zm59 12v10l-3-1c-6 0-10 4-10 10v15h-9V17h9v9c0-5 6-9 13-9z"/></svg>
\ No newline at end of file
diff --git a/dmwt_session10/src/components/AddFruit.js b/dmwt_session10/src/components/AddFruit.js
new file mode 100644
index 0000000..4294ab2
--- /dev/null
+++ b/dmwt_session10/src/components/AddFruit.js
@@ -0,0 +1,90 @@
+import React, { useState } from 'react';
+
+const AddFruit = () => {
+  const [germanName, setGermanName] = useState('');
+  const [latinName, setLatinName] = useState('');
+  const [color, setColor] = useState('');
+  const [origin, setOrigin] = useState('');
+  const [calories, setCalories] = useState('');
+
+  const handleSubmit = async (e) => {
+    e.preventDefault();
+
+    const newFruit = {
+      germanName,
+      latinName,
+      color,
+      origin,
+      calories,
+    };
+
+    const response = await fetch('/api/add-fruit', {
+      method: 'POST',
+      headers: {
+        'Content-Type': 'application/json',
+      },
+      body: JSON.stringify(newFruit),
+    });
+
+    if (!response.ok) {
+      console.error('Failed to add fruit');
+      return;
+    }
+
+    const data = await response.json();
+    console.log(JSON.stringify(data));
+  };
+
+  return (
+    <form onSubmit={handleSubmit}>
+      <div>
+        <label htmlFor="germanName">German Name</label>
+        <input
+          id="germanName"
+          type="text"
+          value={germanName}
+          onChange={(e) => setGermanName(e.target.value)}
+        />
+      </div>
+      <div>
+        <label htmlFor="latinName">Latin Name</label>
+        <input
+          id="latinName"
+          type="text"
+          value={latinName}
+          onChange={(e) => setLatinName(e.target.value)}
+        />
+      </div>
+      <div>
+        <label htmlFor="color">Color</label>
+        <input
+          id="color"
+          type="text"
+          value={color}
+          onChange={(e) => setColor(e.target.value)}
+        />
+      </div>
+      <div>
+        <label htmlFor="origin">Origin</label>
+        <input
+          id="origin"
+          type="text"
+          value={origin}
+          onChange={(e) => setOrigin(e.target.value)}
+        />
+      </div>
+      <div>
+        <label htmlFor="calories">Calories</label>
+        <input
+          id="calories"
+          type="text"
+          value={calories}
+          onChange={(e) => setCalories(e.target.value)}
+        />
+      </div>
+      <button type="submit">Submit</button>
+    </form>
+  );
+};
+
+export default AddFruit;
diff --git a/dmwt_session10/src/components/FruitList.js b/dmwt_session10/src/components/FruitList.js
new file mode 100644
index 0000000..2cd0bed
--- /dev/null
+++ b/dmwt_session10/src/components/FruitList.js
@@ -0,0 +1,35 @@
+import React from 'react';
+import useSWR from 'swr';
+
+const fetcher = url => fetch(url).then(res => res.json());
+
+const FruitList = () => {
+  const { data: fruits, isLoading, error } = useSWR('/api/list-fruits', fetcher, {
+    revalidateOnFocus: false,
+    revalidateOnReconnect: false,
+  });
+
+  if (error) {
+    return <p>Failed to fetch</p>;
+  }
+
+  if (isLoading) {
+    return <p>Loading fruits...</p>;
+  }
+
+  return (
+    <ul>
+      {fruits && fruits.length > 0 ? (
+        fruits.map((fruit, index) => (
+          <li key={index}>
+            {fruit.germanName} ({fruit.latinName}), {fruit.color}, {fruit.origin}, {fruit.calories} kcal
+          </li>
+        ))
+      ) : (
+        <li>No fruits available</li>
+      )}
+    </ul>
+  );
+};
+
+export default FruitList;
diff --git a/dmwt_session10/src/pages/API/add-fruit.js b/dmwt_session10/src/pages/API/add-fruit.js
index 6483780..3e204d4 100644
--- a/dmwt_session10/src/pages/API/add-fruit.js
+++ b/dmwt_session10/src/pages/API/add-fruit.js
@@ -2,8 +2,8 @@ import { sql } from '@vercel/postgres';
 
 export default async function handler(request, response) {
     try {
-        const { id, DeutscherName, LateinischerName, Farbe, Herkunft, Kalorien} = JSON.parse(request.body);
-        if (!id || !DeutscherName || !LateinischerName || !Farbe || !Herkunft || !Kalorien) throw new Error ('Irgendwas muss hier stehen') ;
+        const {DeutscherName, LateinischerName, Farbe, Herkunft, Kalorien} = JSON.parse(request.body);
+        if (!DeutscherName || !LateinischerName || !Farbe || !Herkunft || !Kalorien) throw new Error ('Irgendwas muss hier stehen') ;
         await sql `INSERT INTO Obst (id, Deutscher Name ,Lateinischer Name, Farbe, Herkunft, Kalorien) VALUES (${id}, ${DeutscherName}, ${LateinischerName}, ${Farbe}, ${Herkunft}, ${Kalorien});`;
         const Obst = await sql `SELECT * FROM Obst;`;
         return response.status(200).json(Obst.rows);
diff --git a/dmwt_session10/src/pages/API/fruit-table.js b/dmwt_session10/src/pages/API/fruit-table.js
index d61bb8c..604a1f9 100644
--- a/dmwt_session10/src/pages/API/fruit-table.js
+++ b/dmwt_session10/src/pages/API/fruit-table.js
@@ -4,7 +4,6 @@ export default async function handler(request, response) {
         const result =
         await sql `CREATE TABLE Obst (
 
-            id int(11) NOT NULL,
             Deutscher Name varchar(50) NOT NULL,
             Lateinischer Name varchar(50) NOT NULL,
             Farbe varchar(50) NOT NULL,
diff --git a/dmwt_session10/src/pages/API/list-fruits.js b/dmwt_session10/src/pages/API/list-fruits.js
index 89d8696..ac6b751 100644
--- a/dmwt_session10/src/pages/API/list-fruits.js
+++ b/dmwt_session10/src/pages/API/list-fruits.js
@@ -2,8 +2,8 @@ import { sql } from '@vercel/postgres'
 
 export default async function handler(request, response) {
     try {
-        const pets = await sql `SELECT * FROM Obst;`;
-        return response.status(200).json(pets.rows);
+        const fruits = await sql `SELECT * FROM Obst;`;
+        return response.status(200).json(fruits.rows);
     } catch (error) {
         return response.status(500).json({ error })
     }
diff --git a/dmwt_session10/src/pages/food.js b/dmwt_session10/src/pages/food.js
index 11c3c3b..dc7d200 100644
--- a/dmwt_session10/src/pages/food.js
+++ b/dmwt_session10/src/pages/food.js
@@ -1,10 +1,12 @@
-
+import FruitList from '@/components/FruitList';
 import Navbar from '../components/Navbar';
 
 const Food = () => {
     return (
         <div>
             <Navbar />
+            <h1>Fruit List</h1>
+            <FruitList />
 
         </div>
     );
diff --git a/dmwt_session10/src/pages/index.js b/dmwt_session10/src/pages/index.js
index 7e50c71..20e9be7 100644
--- a/dmwt_session10/src/pages/index.js
+++ b/dmwt_session10/src/pages/index.js
@@ -1,10 +1,12 @@
+import AddFruit from '@/components/AddFruit';
 import Navbar from '../components/Navbar';
 
 const Home = () => {
     return (
       <div>
         <Navbar />
-        
+        <h1>Add a New Fruit</h1>
+        <AddFruit />
       </div>
     );
   };
diff --git a/dmwt_session10/src/pages/post.js b/dmwt_session10/src/pages/post.js
new file mode 100644
index 0000000..2d95c08
--- /dev/null
+++ b/dmwt_session10/src/pages/post.js
@@ -0,0 +1,119 @@
+import React, { useState } from 'react';
+import useSWR from 'swr';
+
+const fetcher = url => fetch(url).then(res => res.json());
+
+const Post = () => {
+  const [germanName, setGermanName] = useState('');
+  const [latinName, setLatinName] = useState('');
+  const [color, setColor] = useState('');
+  const [origin, setOrigin] = useState('');
+  const [calories, setCalories] = useState('');
+
+  const { data: fruits, isLoading, error } = useSWR('/api/list-fruits', fetcher, {
+    revalidateOnFocus: false,
+    revalidateOnReconnect: false,
+  });
+
+  if (error) {
+    return <p>Failed to fetch</p>;
+  }
+
+  if (isLoading) {
+    return <p>Loading fruits...</p>;
+  }
+
+  const handleSubmit = async (e) => {
+    e.preventDefault();
+
+    const newFruit = {
+      germanName,
+      latinName,
+      color,
+      origin,
+      calories,
+    };
+
+    const response = await fetch('/api/add-fruit', {
+      method: 'POST',
+      headers: {
+        'Content-Type': 'application/json',
+      },
+      body: JSON.stringify(newFruit),
+    });
+
+    if (!response.ok) {
+      console.error('Failed to add fruit');
+      return;
+    }
+
+    const data = await response.json();
+    console.log(JSON.stringify(data));
+  };
+
+  return (
+    <div>
+      <form onSubmit={handleSubmit}>
+        <div>
+          <label htmlFor="germanName">German Name</label>
+          <input
+            id="germanName"
+            type="text"
+            value={germanName}
+            onChange={(e) => setGermanName(e.target.value)}
+          />
+        </div>
+        <div>
+          <label htmlFor="latinName">Latin Name</label>
+          <input
+            id="latinName"
+            type="text"
+            value={latinName}
+            onChange={(e) => setLatinName(e.target.value)}
+          />
+        </div>
+        <div>
+          <label htmlFor="color">Color</label>
+          <input
+            id="color"
+            type="text"
+            value={color}
+            onChange={(e) => setColor(e.target.value)}
+          />
+        </div>
+        <div>
+          <label htmlFor="origin">Origin</label>
+          <input
+            id="origin"
+            type="text"
+            value={origin}
+            onChange={(e) => setOrigin(e.target.value)}
+          />
+        </div>
+        <div>
+          <label htmlFor="calories">Calories</label>
+          <input
+            id="calories"
+            type="text"
+            value={calories}
+            onChange={(e) => setCalories(e.target.value)}
+          />
+        </div>
+        <button type="submit">Submit</button>
+      </form>
+      <ul>
+        {fruits && fruits.length > 0 ? (
+          fruits.map((fruit, index) => (
+            <li key={index}>
+              {fruit.germanName} ({fruit.latinName}), {fruit.color}, {fruit.origin}, {fruit.calories} kcal
+            </li>
+          ))
+        ) : (
+          <li>No fruits available</li>
+        )}
+      </ul>
+    </div>
+  );
+};
+
+export default Post;
diff --git a/package-lock.json b/package-lock.json
new file mode 100644
index 0000000..3bb38a2
--- /dev/null
+++ b/package-lock.json
@@ -0,0 +1,257 @@
+{
+  "name": "dmwt_season10_group_2",
+  "lockfileVersion": 3,
+  "requires": true,
+  "packages": {
+    "": {
+      "dependencies": {
+        "@vercel/postgres": "^0.8.0",
+        "react": "^18.3.1",
+        "react-dom": "^18.3.1",
+        "swr": "^2.2.5"
+      }
+    },
+    "node_modules/@neondatabase/serverless": {
+      "version": "0.7.2",
+      "resolved": "https://registry.npmjs.org/@neondatabase/serverless/-/serverless-0.7.2.tgz",
+      "integrity": "sha512-wU3WA2uTyNO7wjPs3Mg0G01jztAxUxzd9/mskMmtPwPTjf7JKWi9AW5/puOGXLxmZ9PVgRFeBVRVYq5nBPhsCg==",
+      "dependencies": {
+        "@types/pg": "8.6.6"
+      }
+    },
+    "node_modules/@types/node": {
+      "version": "20.14.2",
+      "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.2.tgz",
+      "integrity": "sha512-xyu6WAMVwv6AKFLB+e/7ySZVr/0zLCzOa7rSpq6jNwpqOrUbcACDWC+53d4n2QHOnDou0fbIsg8wZu/sxrnI4Q==",
+      "dependencies": {
+        "undici-types": "~5.26.4"
+      }
+    },
+    "node_modules/@types/pg": {
+      "version": "8.6.6",
+      "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.6.6.tgz",
+      "integrity": "sha512-O2xNmXebtwVekJDD+02udOncjVcMZQuTEQEMpKJ0ZRf5E7/9JJX3izhKUcUifBkyKpljyUM6BTgy2trmviKlpw==",
+      "dependencies": {
+        "@types/node": "*",
+        "pg-protocol": "*",
+        "pg-types": "^2.2.0"
+      }
+    },
+    "node_modules/@vercel/postgres": {
+      "version": "0.8.0",
+      "resolved": "https://registry.npmjs.org/@vercel/postgres/-/postgres-0.8.0.tgz",
+      "integrity": "sha512-/QUV9ExwaNdKooRjOQqvrKNVnRvsaXeukPNI5DB1ovUTesglfR/fparw7ngo1KUWWKIVpEj2TRrA+ObRHRdaLg==",
+      "dependencies": {
+        "@neondatabase/serverless": "0.7.2",
+        "bufferutil": "4.0.8",
+        "utf-8-validate": "6.0.3",
+        "ws": "8.14.2"
+      },
+      "engines": {
+        "node": ">=14.6"
+      }
+    },
+    "node_modules/bufferutil": {
+      "version": "4.0.8",
+      "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.8.tgz",
+      "integrity": "sha512-4T53u4PdgsXqKaIctwF8ifXlRTTmEPJ8iEPWFdGZvcf7sbwYo6FKFEX9eNNAnzFZ7EzJAQ3CJeOtCRA4rDp7Pw==",
+      "hasInstallScript": true,
+      "dependencies": {
+        "node-gyp-build": "^4.3.0"
+      },
+      "engines": {
+        "node": ">=6.14.2"
+      }
+    },
+    "node_modules/client-only": {
+      "version": "0.0.1",
+      "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz",
+      "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA=="
+    },
+    "node_modules/js-tokens": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+      "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
+    },
+    "node_modules/loose-envify": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
+      "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
+      "dependencies": {
+        "js-tokens": "^3.0.0 || ^4.0.0"
+      },
+      "bin": {
+        "loose-envify": "cli.js"
+      }
+    },
+    "node_modules/node-gyp-build": {
+      "version": "4.8.1",
+      "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.1.tgz",
+      "integrity": "sha512-OSs33Z9yWr148JZcbZd5WiAXhh/n9z8TxQcdMhIOlpN9AhWpLfvVFO73+m77bBABQMaY9XSvIa+qk0jlI7Gcaw==",
+      "bin": {
+        "node-gyp-build": "bin.js",
+        "node-gyp-build-optional": "optional.js",
+        "node-gyp-build-test": "build-test.js"
+      }
+    },
+    "node_modules/pg-int8": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz",
+      "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==",
+      "engines": {
+        "node": ">=4.0.0"
+      }
+    },
+    "node_modules/pg-protocol": {
+      "version": "1.6.1",
+      "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.6.1.tgz",
+      "integrity": "sha512-jPIlvgoD63hrEuihvIg+tJhoGjUsLPn6poJY9N5CnlPd91c2T18T/9zBtLxZSb1EhYxBRoZJtzScCaWlYLtktg=="
+    },
+    "node_modules/pg-types": {
+      "version": "2.2.0",
+      "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz",
+      "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==",
+      "dependencies": {
+        "pg-int8": "1.0.1",
+        "postgres-array": "~2.0.0",
+        "postgres-bytea": "~1.0.0",
+        "postgres-date": "~1.0.4",
+        "postgres-interval": "^1.1.0"
+      },
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/postgres-array": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz",
+      "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==",
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/postgres-bytea": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz",
+      "integrity": "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/postgres-date": {
+      "version": "1.0.7",
+      "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz",
+      "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/postgres-interval": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz",
+      "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==",
+      "dependencies": {
+        "xtend": "^4.0.0"
+      },
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/react": {
+      "version": "18.3.1",
+      "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
+      "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
+      "dependencies": {
+        "loose-envify": "^1.1.0"
+      },
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/react-dom": {
+      "version": "18.3.1",
+      "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
+      "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
+      "dependencies": {
+        "loose-envify": "^1.1.0",
+        "scheduler": "^0.23.2"
+      },
+      "peerDependencies": {
+        "react": "^18.3.1"
+      }
+    },
+    "node_modules/scheduler": {
+      "version": "0.23.2",
+      "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
+      "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==",
+      "dependencies": {
+        "loose-envify": "^1.1.0"
+      }
+    },
+    "node_modules/swr": {
+      "version": "2.2.5",
+      "resolved": "https://registry.npmjs.org/swr/-/swr-2.2.5.tgz",
+      "integrity": "sha512-QtxqyclFeAsxEUeZIYmsaQ0UjimSq1RZ9Un7I68/0ClKK/U3LoyQunwkQfJZr2fc22DfIXLNDc2wFyTEikCUpg==",
+      "dependencies": {
+        "client-only": "^0.0.1",
+        "use-sync-external-store": "^1.2.0"
+      },
+      "peerDependencies": {
+        "react": "^16.11.0 || ^17.0.0 || ^18.0.0"
+      }
+    },
+    "node_modules/undici-types": {
+      "version": "5.26.5",
+      "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
+      "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="
+    },
+    "node_modules/use-sync-external-store": {
+      "version": "1.2.2",
+      "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.2.tgz",
+      "integrity": "sha512-PElTlVMwpblvbNqQ82d2n6RjStvdSoNe9FG28kNfz3WiXilJm4DdNkEzRhCZuIDwY8U08WVihhGR5iRqAwfDiw==",
+      "peerDependencies": {
+        "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
+      }
+    },
+    "node_modules/utf-8-validate": {
+      "version": "6.0.3",
+      "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-6.0.3.tgz",
+      "integrity": "sha512-uIuGf9TWQ/y+0Lp+KGZCMuJWc3N9BHA+l/UmHd/oUHwJJDeysyTRxNQVkbzsIWfGFbRe3OcgML/i0mvVRPOyDA==",
+      "hasInstallScript": true,
+      "dependencies": {
+        "node-gyp-build": "^4.3.0"
+      },
+      "engines": {
+        "node": ">=6.14.2"
+      }
+    },
+    "node_modules/ws": {
+      "version": "8.14.2",
+      "resolved": "https://registry.npmjs.org/ws/-/ws-8.14.2.tgz",
+      "integrity": "sha512-wEBG1ftX4jcglPxgFCMJmZ2PLtSbJ2Peg6TmpJFTbe9GZYOQCDPdMYu/Tm0/bGZkw8paZnJY45J4K2PZrLYq8g==",
+      "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/xtend": {
+      "version": "4.0.2",
+      "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
+      "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
+      "engines": {
+        "node": ">=0.4"
+      }
+    }
+  }
+}
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..baf5245
--- /dev/null
+++ b/package.json
@@ -0,0 +1,8 @@
+{
+  "dependencies": {
+    "@vercel/postgres": "^0.8.0",
+    "react": "^18.3.1",
+    "react-dom": "^18.3.1",
+    "swr": "^2.2.5"
+  }
+}
-- 
GitLab