Add backend and frontend structure with routing, components, and styling
- Implemented Express server with CORS and dotenv support - Created basic routing with React Router in frontend - Added Admin and Home components with navigation - Integrated MainForm component for user input - Updated package.json and package-lock.json with new dependencies - Styled components using Tailwind CSS and Lucide icons - Added error handling in server - Created initial EJS view for backend
This commit is contained in:
36
backend/package-lock.json
generated
36
backend/package-lock.json
generated
@@ -9,6 +9,8 @@
|
||||
"version": "1.0.0",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"cors": "^2.8.5",
|
||||
"dotenv": "^17.2.1",
|
||||
"express": "^5.1.0"
|
||||
}
|
||||
},
|
||||
@@ -122,6 +124,19 @@
|
||||
"node": ">=6.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/cors": {
|
||||
"version": "2.8.5",
|
||||
"resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
|
||||
"integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"object-assign": "^4",
|
||||
"vary": "^1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/debug": {
|
||||
"version": "4.4.1",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz",
|
||||
@@ -148,6 +163,18 @@
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/dotenv": {
|
||||
"version": "17.2.1",
|
||||
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.1.tgz",
|
||||
"integrity": "sha512-kQhDYKZecqnM0fCnzI5eIv5L4cAe/iRI+HqMbO/hbRdTAeXDG+M9FjipUxNfbARuEg4iHIbhnhs78BCHNbSxEQ==",
|
||||
"license": "BSD-2-Clause",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://dotenvx.com"
|
||||
}
|
||||
},
|
||||
"node_modules/dunder-proto": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
||||
@@ -505,6 +532,15 @@
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/object-assign": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/object-inspect": {
|
||||
"version": "1.13.4",
|
||||
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
|
||||
|
@@ -11,6 +11,8 @@
|
||||
"license": "ISC",
|
||||
"description": "",
|
||||
"dependencies": {
|
||||
"cors": "^2.8.5",
|
||||
"dotenv": "^17.2.1",
|
||||
"express": "^5.1.0"
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,26 @@
|
||||
import express from "express";
|
||||
import cors from "cors";
|
||||
import env from "dotenv";
|
||||
env.config();
|
||||
const app = express();
|
||||
const port = 10001;
|
||||
|
||||
app.use(cors());
|
||||
app.use(express.urlencoded({ extended: true }));
|
||||
app.set("view engine", "ejs");
|
||||
app.use(express.json());
|
||||
|
||||
app.get("/", (req, res) => {
|
||||
res.render("index.ejs", { title: port });
|
||||
});
|
||||
|
||||
app.listen(port, () => {
|
||||
console.log(`Server is running on port: ${port}`);
|
||||
});
|
||||
|
||||
// error handling code
|
||||
app.use((err, req, res, next) => {
|
||||
// Log the error stack and send a generic error response
|
||||
console.error(err.stack);
|
||||
res.status(500).send("Something broke!");
|
||||
});
|
||||
|
11
backend/views/index.ejs
Normal file
11
backend/views/index.ejs
Normal file
@@ -0,0 +1,11 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Backend</title>
|
||||
</head>
|
||||
<body>
|
||||
Backend
|
||||
</body>
|
||||
</html>
|
64
frontend/package-lock.json
generated
64
frontend/package-lock.json
generated
@@ -9,8 +9,10 @@
|
||||
"version": "0.0.0",
|
||||
"dependencies": {
|
||||
"@tailwindcss/vite": "^4.1.11",
|
||||
"lucide-react": "^0.539.0",
|
||||
"react": "^19.1.1",
|
||||
"react-dom": "^19.1.1",
|
||||
"react-router-dom": "^7.8.0",
|
||||
"tailwind-merge": "^3.3.1",
|
||||
"tailwindcss": "^4.1.11",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
@@ -2402,6 +2404,15 @@
|
||||
"integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/cookie": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz",
|
||||
"integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/cosmiconfig": {
|
||||
"version": "8.3.6",
|
||||
"resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz",
|
||||
@@ -3436,6 +3447,15 @@
|
||||
"yallist": "^3.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/lucide-react": {
|
||||
"version": "0.539.0",
|
||||
"resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.539.0.tgz",
|
||||
"integrity": "sha512-VVISr+VF2krO91FeuCrm1rSOLACQUYVy7NQkzrOty52Y8TlTPcXcMdQFj9bYzBgXbWCiywlwSZ3Z8u6a+6bMlg==",
|
||||
"license": "ISC",
|
||||
"peerDependencies": {
|
||||
"react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/magic-string": {
|
||||
"version": "0.30.17",
|
||||
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz",
|
||||
@@ -3793,6 +3813,44 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-router": {
|
||||
"version": "7.8.0",
|
||||
"resolved": "https://registry.npmjs.org/react-router/-/react-router-7.8.0.tgz",
|
||||
"integrity": "sha512-r15M3+LHKgM4SOapNmsH3smAizWds1vJ0Z9C4mWaKnT9/wD7+d/0jYcj6LmOvonkrO4Rgdyp4KQ/29gWN2i1eg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"cookie": "^1.0.1",
|
||||
"set-cookie-parser": "^2.6.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=18",
|
||||
"react-dom": ">=18"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/react-router-dom": {
|
||||
"version": "7.8.0",
|
||||
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.8.0.tgz",
|
||||
"integrity": "sha512-ntInsnDVnVRdtSu6ODmTQ41cbluak/ENeTif7GBce0L6eztFg6/e1hXAysFQI8X25C8ipKmT9cClbJwxx3Kaqw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"react-router": "7.8.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=18",
|
||||
"react-dom": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/resolve-from": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
|
||||
@@ -3891,6 +3949,12 @@
|
||||
"semver": "bin/semver.js"
|
||||
}
|
||||
},
|
||||
"node_modules/set-cookie-parser": {
|
||||
"version": "2.7.1",
|
||||
"resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz",
|
||||
"integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/shebang-command": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
||||
|
@@ -11,8 +11,10 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@tailwindcss/vite": "^4.1.11",
|
||||
"lucide-react": "^0.539.0",
|
||||
"react": "^19.1.1",
|
||||
"react-dom": "^19.1.1",
|
||||
"react-router-dom": "^7.8.0",
|
||||
"tailwind-merge": "^3.3.1",
|
||||
"tailwindcss": "^4.1.11",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
|
@@ -1,13 +1,15 @@
|
||||
import "./App.css";
|
||||
import Layout from "./layout/Layout";
|
||||
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
|
||||
import Admin from './components/Admin'; // Beispiel-Komponente
|
||||
import Home from './components/Home'; // Beispiel-Komponente
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<>
|
||||
<Layout>
|
||||
<h1>Hello World</h1>
|
||||
</Layout>
|
||||
</>
|
||||
<Router>
|
||||
<Routes>
|
||||
<Route path="/" element={<Home />} />
|
||||
<Route path="/admin" element={<Admin />} />
|
||||
</Routes>
|
||||
</Router>
|
||||
);
|
||||
}
|
||||
|
||||
|
11
frontend/src/components/Admin.tsx
Normal file
11
frontend/src/components/Admin.tsx
Normal file
@@ -0,0 +1,11 @@
|
||||
import React from "react";
|
||||
|
||||
const Admin: React.FC = () => {
|
||||
return (
|
||||
<>
|
||||
<h1>Admin</h1>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Admin;
|
@@ -1,9 +1,26 @@
|
||||
import React from "react";
|
||||
import { Award, RectangleEllipsis } from "lucide-react";
|
||||
|
||||
const Header: React.FC = () => {
|
||||
return (
|
||||
<header>
|
||||
<h1>Header</h1>
|
||||
<header className="w-full border-b border-black/10 bg-gray-100/95 shadow-sm">
|
||||
<div className="mx-auto flex max-w-7xl items-center justify-between px-4 py-3 md:px-6">
|
||||
<div className="flex items-center gap-3">
|
||||
<Award className="h-8 w-8 text-black" strokeWidth={2.6} />
|
||||
<h1 className="text-2xl font-black tracking-tight text-neutral-900 md:text-3xl">
|
||||
MCS Lose
|
||||
</h1>
|
||||
</div>
|
||||
<a href="/admin">
|
||||
<button
|
||||
type="button"
|
||||
className="inline-flex items-center gap-2 rounded-xl border border-black/10 bg-gray-200/90 px-4 py-2 font-semibold text-neutral-900 shadow-inner transition hover:bg-gray-300/90"
|
||||
>
|
||||
<RectangleEllipsis className="h-5 w-5" />
|
||||
Login
|
||||
</button>
|
||||
</a>
|
||||
</div>
|
||||
</header>
|
||||
);
|
||||
};
|
||||
|
15
frontend/src/components/Home.tsx
Normal file
15
frontend/src/components/Home.tsx
Normal file
@@ -0,0 +1,15 @@
|
||||
import "../App.css";
|
||||
import Layout from "../layout/Layout";
|
||||
import MainForm from "./MainForm";
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<>
|
||||
<Layout>
|
||||
<MainForm />
|
||||
</Layout>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
125
frontend/src/components/MainForm.tsx
Normal file
125
frontend/src/components/MainForm.tsx
Normal file
@@ -0,0 +1,125 @@
|
||||
import React from "react";
|
||||
|
||||
const MainForm: React.FC = () => {
|
||||
return (
|
||||
<section className="mx-auto mt-10 w-full max-w-lg rounded-3xl border border-black/10 bg-zinc-200/90 p-6 shadow-lg backdrop-blur-sm">
|
||||
<h2 className="text-2xl font-black text-zinc-900">Los registrieren</h2>
|
||||
<p className="mt-1 text-xs text-zinc-500">
|
||||
* alle Informationen werden benötigt
|
||||
</p>
|
||||
|
||||
<form className="mt-5 space-y-4">
|
||||
<div className="space-y-1">
|
||||
<label
|
||||
htmlFor="losnummer"
|
||||
className="text-sm font-medium text-zinc-800"
|
||||
>
|
||||
Losnummer:
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
id="losnummer"
|
||||
placeholder="XXXX-XXXX-XXXX-XXXX"
|
||||
required
|
||||
className="w-full rounded-xl border border-black/25 bg-white/80 px-4 py-2.5 text-sm text-zinc-800 placeholder-zinc-400 shadow-inner outline-none ring-0 transition focus:border-black/40 focus:ring-2 focus:ring-black/10"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
|
||||
<div className="space-y-1">
|
||||
<label
|
||||
htmlFor="vorname"
|
||||
className="text-sm font-medium text-zinc-800"
|
||||
>
|
||||
Vorname:
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
id="vorname"
|
||||
placeholder="Max"
|
||||
required
|
||||
className="w-full rounded-xl border border-black/25 bg-white/80 px-4 py-2.5 text-sm text-zinc-800 placeholder-zinc-400 shadow-inner outline-none focus:border-black/40 focus:ring-2 focus:ring-black/10"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-1">
|
||||
<label
|
||||
htmlFor="nachname"
|
||||
className="text-sm font-medium text-zinc-800"
|
||||
>
|
||||
Nachname:
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
id="nachname"
|
||||
placeholder="Mustermann"
|
||||
required
|
||||
className="w-full rounded-xl border border-black/25 bg-white/80 px-4 py-2.5 text-sm text-zinc-800 placeholder-zinc-400 shadow-inner outline-none focus:border-black/40 focus:ring-2 focus:ring-black/10"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-1">
|
||||
<label
|
||||
htmlFor="strasse"
|
||||
className="text-sm font-medium text-zinc-800"
|
||||
>
|
||||
Straße + Haus Nr.:
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
id="strasse"
|
||||
placeholder="Musterstraße 1"
|
||||
required
|
||||
className="w-full rounded-xl border border-black/25 bg-white/80 px-4 py-2.5 text-sm text-zinc-800 placeholder-zinc-400 shadow-inner outline-none focus:border-black/40 focus:ring-2 focus:ring-black/10"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-1">
|
||||
<label htmlFor="plz" className="text-sm font-medium text-zinc-800">
|
||||
Postleitzahl + Ort:
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
id="plz"
|
||||
placeholder="12345 Musterstadt"
|
||||
required
|
||||
className="w-full rounded-xl border border-black/25 bg-white/80 px-4 py-2.5 text-sm text-zinc-800 placeholder-zinc-400 shadow-inner outline-none focus:border-black/40 focus:ring-2 focus:ring-black/10"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-1">
|
||||
<label htmlFor="email" className="text-sm font-medium text-zinc-800">
|
||||
E-Mail:
|
||||
</label>
|
||||
<input
|
||||
type="email"
|
||||
id="email"
|
||||
placeholder="max@mustermann.de"
|
||||
required
|
||||
className="w-full rounded-xl border border-black/25 bg-white/80 px-4 py-2.5 text-sm text-zinc-800 placeholder-zinc-400 shadow-inner outline-none focus:border-black/40 focus:ring-2 focus:ring-black/10"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
className="mt-2 w-full rounded-xl bg-blue-600 px-4 py-3 text-base font-bold text-white shadow transition hover:bg-blue-700 active:bg-blue-800"
|
||||
>
|
||||
Los Registrieren
|
||||
</button>
|
||||
<p className="mt-1 text-xs text-zinc-500">
|
||||
Wenn Sie die Daten eines Loses bearbeiten möchten,{" "}
|
||||
<a
|
||||
className="text-blue-600 underline"
|
||||
href="mailto:example@example.com"
|
||||
>
|
||||
kontaktieren Sie uns
|
||||
</a>
|
||||
.
|
||||
</p>
|
||||
</form>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export default MainForm;
|
@@ -1,5 +1,6 @@
|
||||
import React from "react";
|
||||
import Header from "../components/Header";
|
||||
import "../App.css";
|
||||
|
||||
type LayoutProps = {
|
||||
children: React.ReactNode;
|
||||
|
Reference in New Issue
Block a user