Login integriert

This commit is contained in:
Marc Wieland
2025-04-21 17:46:44 +02:00
parent 8f9cd73e50
commit 8a92201b74
10 changed files with 500 additions and 102 deletions

View File

@@ -1,54 +1,28 @@
import { useEffect, useState } from "react";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { useState } from "react";
import { Button } from "@/components/ui/button";
import { Select, SelectItem } from "@/components/ui/select"; // Falls du ein Select UI-Element hast
const news = [
{
id: 1,
title: "Saisonstart 2023/24",
date: "2023-09-15",
description: "Die neue Saison beginnt mit einem Heimspiel gegen VfB Mosbach. Kommt vorbei und unterstützt unser Team!",
image: "https://images.unsplash.com/photo-1612872087720-bb876e2e67d1?ixlib=rb-4.0.3&auto=format&fit=crop&w=500&q=80",
team: "Herren I"
},
{
id: 2,
title: "Beachvolleyball-Turnier",
date: "2023-07-03",
description: "Unser jährliches Beachvolleyball-Turnier war ein voller Erfolg! 24 Teams kämpften um den Sieg.",
image: "https://images.unsplash.com/photo-1583514555852-6f77e7c9e081?ixlib=rb-4.0.3&auto=format&fit=crop&w=500&q=80",
team: "Mixed-Team"
},
{
id: 3,
title: "Neuer Trainer für die Jugendmannschaft",
date: "2023-05-21",
description: "Wir freuen uns, Marc Schneider als neuen Trainer für unsere U16-Mannschaft begrüßen zu dürfen.",
image: "https://images.unsplash.com/photo-1547347298-4074fc3086f0?ixlib=rb-4.0.3&auto=format&fit=crop&w=500&q=80",
team: "Jugend U16"
},
];
type NewsItem = {
id: number;
title: string;
description: string;
image_url: string;
team: string;
created_at: string;
};
const AlleNeuigkeitenPage = () => {
const [selectedTeam, setSelectedTeam] = useState("Alle");
const [news, setNews] = useState<NewsItem[]>([]);
// Teams dynamisch auslesen
const teams = ["Alle", ...Array.from(new Set(news.map((item) => item.team)))];
// Nach Datum absteigend sortieren
const sortedNews = [...news].sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime());
// Nach Team filtern
const filteredNews = sortedNews.filter((item) =>
selectedTeam === "Alle" ? true : item.team === selectedTeam
);
useEffect(() => {
fetch("http://192.168.50.65:3000/api/news")
.then((res) => res.json())
.then((data) => setNews(data))
.catch((err) => console.error("Fehler beim Laden der News:", err));
}, []);
// Gruppieren nach Jahren
const groupedNews = filteredNews.reduce((acc: any, item) => {
const year = new Date(item.date).getFullYear();
const groupedNews = news.reduce((acc: Record<string, NewsItem[]>, item) => {
const year = new Date(item.created_at).getFullYear().toString();
if (!acc[year]) acc[year] = [];
acc[year].push(item);
return acc;
@@ -58,34 +32,23 @@ const AlleNeuigkeitenPage = () => {
<div className="max-w-5xl mx-auto px-4 py-12">
<h1 className="text-3xl font-bold text-center mb-8 text-frog-600">Alle Neuigkeiten</h1>
{/* Team-Filter */}
<div className="flex justify-center mb-8">
<select
value={selectedTeam}
onChange={(e) => setSelectedTeam(e.target.value)}
className="border border-gray-300 rounded-md p-2"
>
{teams.map((team) => (
<option key={team} value={team}>
{team}
</option>
))}
</select>
</div>
{/* News nach Jahren gruppiert */}
{Object.entries(groupedNews).map(([year, items]) => (
<div key={year} className="mb-12">
<h2 className="text-2xl font-bold text-frog-500 mb-4">{year}</h2>
<div className="grid md:grid-cols-2 gap-6">
{(items as typeof news).map((item) => (
{items.map((item) => (
<Card key={item.id} className="overflow-hidden">
<div className="h-48 overflow-hidden">
<img src={item.image} alt={item.title} className="w-full h-full object-cover" />
<img
src={item.image_url || "https://via.placeholder.com/400x300?text=Kein+Bild"}
alt={item.title}
className="w-full h-full object-cover"
/>
</div>
<CardHeader>
<CardTitle>{item.title}</CardTitle>
<CardDescription>{new Date(item.date).toLocaleDateString("de-DE")}</CardDescription>
<CardDescription>{new Date(item.created_at).toLocaleDateString("de-DE")}</CardDescription>
</CardHeader>
<CardContent>
<p>{item.description}</p>
@@ -95,7 +58,6 @@ const AlleNeuigkeitenPage = () => {
</div>
</div>
))}
</div>
);
};

View File

@@ -1,8 +1,40 @@
import { useState } from "react";
import { useNavigate } from "react-router-dom";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
const LoginPage = () => {
const [email, setEmail] = useState(""); // Wird eigentlich Username sein!
const [password, setPassword] = useState("");
const [error, setError] = useState("");
const navigate = useNavigate();
const handleLogin = async (e: React.FormEvent) => {
e.preventDefault();
setError("");
try {
const res = await fetch("http://192.168.50.65:3000/api/login", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ username: email, password }),
});
if (!res.ok) {
throw new Error("Login fehlgeschlagen");
}
const data = await res.json();
localStorage.setItem("token", data.token);
navigate("/admin");
} catch (err) {
console.error(err);
setError("Login fehlgeschlagen. Bitte prüfe Benutzername und Passwort.");
}
};
return (
<div className="flex justify-center items-center min-h-screen bg-gray-50">
<Card className="w-full max-w-md">
@@ -10,9 +42,22 @@ const LoginPage = () => {
<CardTitle className="text-center text-frog-600">Login</CardTitle>
</CardHeader>
<CardContent>
<form className="space-y-4">
<Input placeholder="E-Mail-Adresse" type="email" required />
<Input placeholder="Passwort" type="password" required />
<form className="space-y-4" onSubmit={handleLogin}>
<Input
placeholder="Benutzername"
type="text"
value={email}
onChange={(e) => setEmail(e.target.value)}
required
/>
<Input
placeholder="Passwort"
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
required
/>
{error && <p className="text-red-500 text-sm">{error}</p>}
<Button type="submit" className="w-full bg-frog-500 hover:bg-frog-600 text-white">
Einloggen
</Button>