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:
2025-08-11 18:29:06 +02:00
parent 38b02c186f
commit ba62beb90d
12 changed files with 322 additions and 10 deletions

View File

@@ -9,6 +9,8 @@
"version": "1.0.0", "version": "1.0.0",
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"cors": "^2.8.5",
"dotenv": "^17.2.1",
"express": "^5.1.0" "express": "^5.1.0"
} }
}, },
@@ -122,6 +124,19 @@
"node": ">=6.6.0" "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": { "node_modules/debug": {
"version": "4.4.1", "version": "4.4.1",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz",
@@ -148,6 +163,18 @@
"node": ">= 0.8" "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": { "node_modules/dunder-proto": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
@@ -505,6 +532,15 @@
"node": ">= 0.6" "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": { "node_modules/object-inspect": {
"version": "1.13.4", "version": "1.13.4",
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",

View File

@@ -11,6 +11,8 @@
"license": "ISC", "license": "ISC",
"description": "", "description": "",
"dependencies": { "dependencies": {
"cors": "^2.8.5",
"dotenv": "^17.2.1",
"express": "^5.1.0" "express": "^5.1.0"
} }
} }

View File

@@ -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
View 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>

View File

@@ -9,8 +9,10 @@
"version": "0.0.0", "version": "0.0.0",
"dependencies": { "dependencies": {
"@tailwindcss/vite": "^4.1.11", "@tailwindcss/vite": "^4.1.11",
"lucide-react": "^0.539.0",
"react": "^19.1.1", "react": "^19.1.1",
"react-dom": "^19.1.1", "react-dom": "^19.1.1",
"react-router-dom": "^7.8.0",
"tailwind-merge": "^3.3.1", "tailwind-merge": "^3.3.1",
"tailwindcss": "^4.1.11", "tailwindcss": "^4.1.11",
"tailwindcss-animate": "^1.0.7", "tailwindcss-animate": "^1.0.7",
@@ -2402,6 +2404,15 @@
"integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
"license": "MIT" "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": { "node_modules/cosmiconfig": {
"version": "8.3.6", "version": "8.3.6",
"resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz",
@@ -3436,6 +3447,15 @@
"yallist": "^3.0.2" "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": { "node_modules/magic-string": {
"version": "0.30.17", "version": "0.30.17",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz",
@@ -3793,6 +3813,44 @@
"node": ">=0.10.0" "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": { "node_modules/resolve-from": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
@@ -3891,6 +3949,12 @@
"semver": "bin/semver.js" "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": { "node_modules/shebang-command": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",

View File

@@ -11,8 +11,10 @@
}, },
"dependencies": { "dependencies": {
"@tailwindcss/vite": "^4.1.11", "@tailwindcss/vite": "^4.1.11",
"lucide-react": "^0.539.0",
"react": "^19.1.1", "react": "^19.1.1",
"react-dom": "^19.1.1", "react-dom": "^19.1.1",
"react-router-dom": "^7.8.0",
"tailwind-merge": "^3.3.1", "tailwind-merge": "^3.3.1",
"tailwindcss": "^4.1.11", "tailwindcss": "^4.1.11",
"tailwindcss-animate": "^1.0.7", "tailwindcss-animate": "^1.0.7",

View File

@@ -1,14 +1,16 @@
import "./App.css"; import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import Layout from "./layout/Layout"; import Admin from './components/Admin'; // Beispiel-Komponente
import Home from './components/Home'; // Beispiel-Komponente
function App() { function App() {
return ( return (
<> <Router>
<Layout> <Routes>
<h1>Hello World</h1> <Route path="/" element={<Home />} />
</Layout> <Route path="/admin" element={<Admin />} />
</> </Routes>
</Router>
); );
} }
export default App; export default App;

View File

@@ -0,0 +1,11 @@
import React from "react";
const Admin: React.FC = () => {
return (
<>
<h1>Admin</h1>
</>
);
};
export default Admin;

View File

@@ -1,9 +1,26 @@
import React from "react"; import React from "react";
import { Award, RectangleEllipsis } from "lucide-react";
const Header: React.FC = () => { const Header: React.FC = () => {
return ( return (
<header> <header className="w-full border-b border-black/10 bg-gray-100/95 shadow-sm">
<h1>Header</h1> <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> </header>
); );
}; };

View 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;

View 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;

View File

@@ -1,5 +1,6 @@
import React from "react"; import React from "react";
import Header from "../components/Header"; import Header from "../components/Header";
import "../App.css";
type LayoutProps = { type LayoutProps = {
children: React.ReactNode; children: React.ReactNode;