Merge pull request #116 from Since-3/feature/added-glassmorphism

added subtil glassmorphism
This commit is contained in:
Rajpreet0 2026-06-09 19:03:33 +02:00 • committed by GitHub
commit 5354a90a8d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 200 additions and 177 deletions

View file

@ -1,5 +1,5 @@
import SettignsPersonalPageView from "@/src/modules/settings/ui/views/settings-personal-page-view";
import SettingsPersonalPageView from "@/src/modules/settings/ui/views/settings-personal-page-view";
export default async function SettingsPersonalPage() {
return <SettignsPersonalPageView />;
return <SettingsPersonalPageView />;
}

View file

@ -10,6 +10,7 @@ import AbstractTooltip from "@/src/components/layout/AbstractTooltip";
import { Avatar, AvatarFallback, AvatarImage } from "@/src/components/ui/avatar";
import { Pen, Trash2, Link } from "lucide-react";
import { toast } from "sonner";
import Image from "next/image";
/**
* Props für die CourseListItem Komponente
@ -31,6 +32,7 @@ interface CourseListItemProps {
status?: string; // Status des Kurses ("draft" oder "published")
isPast?: boolean; // Ob der Kurs in der Vergangenheit liegt
showDeleteIcon?: boolean; // Ob das Löschen-Icon angezeigt werden soll
coverImage?: string; // Optionales Cover-Bild
}
/**
@ -54,6 +56,7 @@ const CourseListItem: React.FC<CourseListItemProps> = ({
status,
isPast,
showDeleteIcon,
coverImage,
}) => {
// Prüfen, ob der Kurs voll belegt ist (für visuelle Hervorhebung)
const isFull = currentParticipants >= maxParticipants;
@ -87,109 +90,117 @@ const CourseListItem: React.FC<CourseListItemProps> = ({
return (
<div
className={`bg-white border border-gray-200 rounded-xl p-5 shadow-sm hover:shadow-md transition flex flex-col gap-4 relative ${
isPast ? "opacity-60 bg-gray-50" : ""
className={`border border-white/60 bg-white/55 backdrop-blur-xl rounded-xl shadow-sm hover:shadow-md hover:bg-white/70 transition flex flex-col relative overflow-hidden ${
isPast ? "opacity-60" : ""
}`}
>
<div className="flex items-center w-full">
{trainers.map((trainerId, index) => {
const trainerInfo = trainersMap[trainerId];
const displayName = trainerInfo?.label ?? "Ehemaliger Trainer";
const avatarSrc = trainerInfo?.pbSrc;
return (
<AbstractTooltip key={`${trainerId}-${index}`} tooltipText={displayName}>
<div
className={`relative ${index !== 0 ? "-ml-3" : ""}`}
style={{ zIndex: trainers.length - index }}
>
<Avatar className="rounded-full border-2 border-white">
{avatarSrc && <AvatarImage src={avatarSrc} alt={displayName} />}
<AvatarFallback>{getInitials(displayName)}</AvatarFallback>
</Avatar>
</div>
</AbstractTooltip>
);
})}
{/* Time */}
<div className="w-full flex justify-end">
<p className="text-gray-500 text-lg">
{timeFrom} - {timeTo}
</p>
{coverImage && (
<div className="relative w-full h-36 shrink-0">
<Image src={coverImage} alt={courseName} fill className="object-cover" />
</div>
</div>
)}
{/* Course Name */}
<h2 className="text-xl font-semibold text-gray-900">{courseName}</h2>
<div className="flex items-center justify-between w-full">
{/* Participant Number */}
<span className={isFull ? "text-red-600 font-semibold" : "text-gray-800"}>
{currentParticipants}/{maxParticipants} Teilnehmer
</span>
{/* Price */}
<h2 className="text-lg font-semibold">
{price.toLocaleString("de-DE", {
style: "currency",
currency: "EUR",
<div className="flex flex-col gap-4 p-5">
<div className="flex items-center w-full">
{trainers.map((trainerId, index) => {
const trainerInfo = trainersMap[trainerId];
const displayName = trainerInfo?.label ?? "Ehemaliger Trainer";
const avatarSrc = trainerInfo?.pbSrc;
return (
<AbstractTooltip key={`${trainerId}-${index}`} tooltipText={displayName}>
<div
className={`relative ${index !== 0 ? "-ml-3" : ""}`}
style={{ zIndex: trainers.length - index }}
>
<Avatar className="rounded-full border-2 border-white">
{avatarSrc && <AvatarImage src={avatarSrc} alt={displayName} />}
<AvatarFallback>{getInitials(displayName)}</AvatarFallback>
</Avatar>
</div>
</AbstractTooltip>
);
})}
</h2>
</div>
{/* Time */}
<div className="w-full flex justify-end">
<p className="text-gray-500 text-lg">
{timeFrom} - {timeTo}
</p>
</div>
</div>
{/* Participant Bar */}
<div className="w-full h-2 bg-gray-200 rounded-full overflow-hidden">
<div
className={`h-full ${isFull ? "bg-red-500" : "bg-blue"}`}
style={{
width: `${maxParticipants > 0 ? (currentParticipants / maxParticipants) * 100 : 0}%`,
}}
/>
</div>
{/* Course Name */}
<h2 className="text-xl font-semibold text-gray-900">{courseName}</h2>
<div className="flex items-center justify-between w-full">
<span
className={`w-fit text-xs font-semibold px-2 py-1 rounded-lg ${currentLevel.bg} ${currentLevel.text}`}
>
{currentLevel.label}
</span>
{/* Room */}
<p className="text-gray-500">{room}</p>
</div>
<hr />
<div className="flex items-center justify-between w-full">
{/* Status */}
{status && (
<span
className={`w-fit top-3 left-3 text-xs font-semibold px-2 py-1 rounded ${
status === "draft" ? "bg-yellow-100 text-yellow-700" : "bg-green-100 text-green-700"
}`}
>
{status === "draft" ? "Entwurf" : "Veröffentlicht"}
<div className="flex items-center justify-between w-full">
{/* Participant Number */}
<span className={isFull ? "text-red-600 font-semibold" : "text-gray-800"}>
{currentParticipants}/{maxParticipants} Teilnehmer
</span>
)}
{/* Actions */}
<div className="flex gap-2">
{courseId && status === "published" && (
<button
onClick={handleCopyLink}
className="p-2 rounded-md hover:bg-blue-100 transition"
title="Buchungslink kopieren"
{/* Price */}
<h2 className="text-lg font-semibold">
{price.toLocaleString("de-DE", {
style: "currency",
currency: "EUR",
})}
</h2>
</div>
{/* Participant Bar */}
<div className="w-full h-2 bg-white/40 rounded-full overflow-hidden">
<div
className={`h-full ${isFull ? "bg-red-500" : "bg-blue"}`}
style={{
width: `${maxParticipants > 0 ? (currentParticipants / maxParticipants) * 100 : 0}%`,
}}
/>
</div>
<div className="flex items-center justify-between w-full">
<span
className={`w-fit text-xs font-semibold px-2 py-1 rounded-lg ${currentLevel.bg} ${currentLevel.text}`}
>
{currentLevel.label}
</span>
{/* Room */}
<p className="text-gray-500">{room}</p>
</div>
<hr />
<div className="flex items-center justify-between w-full">
{/* Status */}
{status && (
<span
className={`w-fit top-3 left-3 text-xs font-semibold px-2 py-1 rounded ${
status === "draft" ? "bg-yellow-100 text-yellow-700" : "bg-green-100 text-green-700"
}`}
>
<Link size={20} className="text-blue-600" />
</button>
)}
{showDeleteIcon && onDelete && (
<button onClick={onDelete} className="p-2 rounded-md hover:bg-red-100 transition">
<Trash2 size={20} className="text-red-600" />
</button>
)}
{onEdit && (
<button onClick={onEdit} className="p-2 rounded-md hover:bg-gray-100 transition">
<Pen size={20} className="text-gray-700" />
</button>
{status === "draft" ? "Entwurf" : "Veröffentlicht"}
</span>
)}
{/* Actions */}
<div className="flex gap-2">
{courseId && status === "published" && (
<button
onClick={handleCopyLink}
className="p-2 rounded-md hover:bg-blue-100 transition"
title="Buchungslink kopieren"
>
<Link size={20} className="text-blue-600" />
</button>
)}
{showDeleteIcon && onDelete && (
<button onClick={onDelete} className="p-2 rounded-md hover:bg-red-500/10 transition">
<Trash2 size={20} className="text-red-600" />
</button>
)}
{onEdit && (
<button onClick={onEdit} className="p-2 rounded-md hover:bg-white/50 transition">
<Pen size={20} className="text-gray-700" />
</button>
)}
</div>
</div>
</div>
</div>

View file

@ -414,67 +414,77 @@ const PublicCourseCard = ({ course, business, defaultOpen = false }: PublicCours
<>
<div
onClick={() => setDialogOpen(true)}
className="bg-white border border-gray-200 rounded-xl p-5 shadow-sm hover:shadow-md transition flex flex-col gap-4"
className="border border-white/60 bg-white/55 backdrop-blur-xl rounded-xl shadow-sm hover:shadow-md hover:bg-white/70 transition flex flex-col cursor-pointer overflow-hidden"
>
<div className="flex items-center justify-between">
<p className="text-gray-500 text-sm">{formattedDate}</p>
<p className="text-gray-500 text-sm">
{course.timeFrom} – {course.timeTo}
</p>
</div>
{course.trainerProfiles && course.trainerProfiles.length > 0 && (
<div className="flex items-center gap-1.5">
{course.trainerProfiles.map((trainer) => {
const fullName =
[trainer.firstName, trainer.lastName].filter(Boolean).join(" ") || "Trainer";
return (
<Tooltip key={trainer.id}>
<TooltipTrigger asChild>
<div>
<TrainerAvatar trainer={trainer} size="sm" />
</div>
</TooltipTrigger>
<TooltipContent side="top">{fullName}</TooltipContent>
</Tooltip>
);
})}
{course.coverImage && (
<div className="relative w-full h-36 shrink-0">
<Image src={course.coverImage} alt={course.name} fill className="object-cover" />
</div>
)}
<h2 className="text-xl font-semibold text-gray-900">{course.name}</h2>
<div className="flex flex-col gap-4 p-5">
<div className="flex items-center justify-between">
<p className="text-gray-500 text-sm">{formattedDate}</p>
<p className="text-gray-500 text-sm">
{course.timeFrom} – {course.timeTo}
</p>
</div>
<div className="flex items-center justify-between">
<span
className={`text-xs font-semibold px-2 py-1 rounded-lg ${currentLevel.bg} ${currentLevel.text}`}
>
{currentLevel.label}
</span>
<span className="text-lg font-semibold">{formattedPrice}</span>
{course.trainerProfiles && course.trainerProfiles.length > 0 && (
<div className="flex items-center gap-1.5">
{course.trainerProfiles.map((trainer) => {
const fullName =
[trainer.firstName, trainer.lastName].filter(Boolean).join(" ") || "Trainer";
return (
<Tooltip key={trainer.id}>
<TooltipTrigger asChild>
<div>
<TrainerAvatar trainer={trainer} size="sm" />
</div>
</TooltipTrigger>
<TooltipContent side="top">{fullName}</TooltipContent>
</Tooltip>
);
})}
</div>
)}
<h2 className="text-xl font-semibold text-gray-900">{course.name}</h2>
<div className="flex items-center justify-between">
<span
className={`text-xs font-semibold px-2 py-1 rounded-lg ${currentLevel.bg} ${currentLevel.text}`}
>
{currentLevel.label}
</span>
<span className="text-lg font-semibold">{formattedPrice}</span>
</div>
<div className="flex items-center justify-between">
<span
className={isFull ? "text-red-600 font-semibold text-sm" : "text-gray-700 text-sm"}
>
{course.currentParticipants}/{course.maxParticipants} Teilnehmer
</span>
</div>
<div className="w-full h-2 bg-white/40 rounded-full overflow-hidden">
<div
className={`h-full ${isFull ? "bg-red-500" : "bg-blue-500"}`}
style={{ width: `${fillPercent}%` }}
/>
</div>
{isFull ? (
<Button disabled variant="outline" className="w-full">
Ausgebucht
</Button>
) : (
<Button className="w-full" onClick={() => setDialogOpen(true)}>
Jetzt anmelden
</Button>
)}
</div>
<div className="flex items-center justify-between">
<span className={isFull ? "text-red-600 font-semibold text-sm" : "text-gray-700 text-sm"}>
{course.currentParticipants}/{course.maxParticipants} Teilnehmer
</span>
</div>
<div className="w-full h-2 bg-gray-200 rounded-full overflow-hidden">
<div
className={`h-full ${isFull ? "bg-red-500" : "bg-blue-500"}`}
style={{ width: `${fillPercent}%` }}
/>
</div>
{isFull ? (
<Button disabled variant="outline" className="w-full">
Ausgebucht
</Button>
) : (
<Button className="w-full" onClick={() => setDialogOpen(true)}>
Jetzt anmelden
</Button>
)}
</div>
<BookingDialog

View file

@ -145,7 +145,7 @@ const BusinessPublicView = ({
return (
<div className="min-h-screen bg-gray-50">
{/* Header */}
<div className="bg-white border-b border-gray-200">
<div className="border-b border-white/60 bg-white/55 backdrop-blur-xl">
<div className="max-w-6xl mx-auto px-6 py-10">
<h1 className="text-4xl font-bold text-gray-900">{business.name}</h1>
{business.title && <p className="text-xl text-gray-500 mt-1">{business.title}</p>}
@ -169,7 +169,7 @@ const BusinessPublicView = ({
{/* Left: sticky calendar */}
<div className="lg:sticky lg:top-6 shrink-0 w-full lg:w-auto">
<h2 className="text-2xl font-semibold text-gray-900 mb-3">{formattedSelectedDate}</h2>
<div className="bg-white border border-gray-200 rounded-xl shadow-sm p-4 flex justify-center">
<div className="border border-white/60 bg-white/55 backdrop-blur-xl rounded-xl shadow-sm p-4 flex justify-center">
<Calendar
mode="single"
selected={selectedDate}
@ -194,7 +194,7 @@ const BusinessPublicView = ({
className={`px-4 py-1.5 rounded-full text-sm font-medium transition-colors ${
resolvedSport === sport
? "bg-gray-900 text-white"
: "bg-white border border-gray-200 text-gray-600 hover:border-gray-400"
: "border border-white/60 bg-white/55 backdrop-blur-sm text-gray-600 hover:bg-white/70"
}`}
>
{sport === "Alle" ? "Alle" : capitalize(sport)}

View file

@ -32,6 +32,7 @@ type Course = {
createdBy: string;
createdAt: Date;
updatedAt: Date;
coverImage: string | null;
_count?: {
bookings: number;
};
@ -563,7 +564,7 @@ const CourseListView = ({ canCreate, canEdit, canDelete }: CourseListViewProps)
)}
{filteredCourses.length === 0 ? (
<div className="text-center py-12 bg-gray-50 rounded-lg">
<div className="text-center py-12 border border-white/60 bg-white/55 backdrop-blur-xl rounded-2xl">
<p className="text-xl text-gray-600">
{courses.length === 0
? "Sie haben noch keine Kurse erstellt"
@ -609,6 +610,7 @@ const CourseListView = ({ canCreate, canEdit, canDelete }: CourseListViewProps)
status={course.status}
isPast={isPastCourse(course.date)}
showDeleteIcon={deleteMode && canDelete}
coverImage={course.coverImage ?? undefined}
onDelete={
canDelete ? () => handleDeleteClick(course.id, course.name) : undefined
}

View file

@ -12,7 +12,7 @@ const DashboardView = async () => {
return (
<div className="space-y-6">
<div className="bg-white rounded-lg shadow p-6">
<div className="border border-white/60 bg-white/55 backdrop-blur-xl rounded-2xl shadow-sm p-6">
<h1 className="text-3xl font-bold text-gray-900 mb-4">Willkommen zurück!</h1>
<p className="text-gray-600">Schön, dich wiederzusehen, {displayName}</p>
{isManager(userData) && (
@ -28,13 +28,13 @@ const DashboardView = async () => {
</div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
<div className="bg-white rounded-lg shadow p-6">
<div className="border border-white/60 bg-white/55 backdrop-blur-xl rounded-2xl shadow-sm p-6">
<h2 className="text-lg font-semibold mb-2">Profil</h2>
<p className="text-gray-600 text-sm">Verwalte deine persönlichen Daten</p>
</div>
{isManager(userData) ? (
<div className="bg-white rounded-lg shadow p-6">
<div className="border border-white/60 bg-white/55 backdrop-blur-xl rounded-2xl shadow-sm p-6">
<h2 className="text-lg font-semibold mb-2">Meine Businesses</h2>
<p className="text-gray-600 text-sm">
{userData.businesses.length}{" "}
@ -42,7 +42,7 @@ const DashboardView = async () => {
</p>
</div>
) : isEmployee(userData) ? (
<div className="bg-white rounded-lg shadow p-6">
<div className="border border-white/60 bg-white/55 backdrop-blur-xl rounded-2xl shadow-sm p-6">
<h2 className="text-lg font-semibold mb-2">Meine Berechtigungen</h2>
{(() => {
const p = userData.permissions;
@ -79,20 +79,20 @@ const DashboardView = async () => {
})()}
</div>
) : (
<div className="bg-white rounded-lg shadow p-6">
<div className="border border-white/60 bg-white/55 backdrop-blur-xl rounded-2xl shadow-sm p-6">
<h2 className="text-lg font-semibold mb-2">Trainings</h2>
<p className="text-gray-600 text-sm">Deine gebuchten Trainingseinheiten</p>
</div>
)}
<div className="bg-white rounded-lg shadow p-6">
<div className="border border-white/60 bg-white/55 backdrop-blur-xl rounded-2xl shadow-sm p-6">
<h2 className="text-lg font-semibold mb-2">Einstellungen</h2>
<p className="text-gray-600 text-sm">Passe deine Einstellungen an</p>
</div>
</div>
{isUser(userData) && (
<div className="bg-white rounded-lg shadow p-6">
<div className="border border-white/60 bg-white/55 backdrop-blur-xl rounded-2xl shadow-sm p-6">
<h2 className="text-xl font-semibold mb-4">Benutzerdaten</h2>
<dl className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
@ -132,7 +132,7 @@ const DashboardView = async () => {
)}
{isManager(userData) && (
<div className="bg-white rounded-lg shadow p-6">
<div className="border border-white/60 bg-white/55 backdrop-blur-xl rounded-2xl shadow-sm p-6">
<h2 className="text-xl font-semibold mb-4">Manager-Daten</h2>
<dl className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
@ -164,7 +164,7 @@ const DashboardView = async () => {
)}
{isManager(userData) && userData.businesses.length > 0 && (
<div className="bg-white rounded-lg shadow p-6">
<div className="border border-white/60 bg-white/55 backdrop-blur-xl rounded-2xl shadow-sm p-6">
<h2 className="text-xl font-semibold mb-4">Meine Businesses</h2>
<div className="space-y-4">
{userData.businesses.map((business) => (
@ -179,7 +179,7 @@ const DashboardView = async () => {
)}
{isEmployee(userData) && (
<div className="bg-white rounded-lg shadow p-6">
<div className="border border-white/60 bg-white/55 backdrop-blur-xl rounded-2xl shadow-sm p-6">
<h2 className="text-xl font-semibold mb-4">Mitarbeiter-Daten</h2>
<dl className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>

View file

@ -46,7 +46,7 @@ const EmployeeListItem: React.FC<EmployeeListItemProps> = ({
showDeleteIcon,
}) => {
return (
<div className="bg-white border border-gray-200 rounded-xl p-5 shadow-sm hover:shadow-md transition flex flex-col gap-4 relative">
<div className="border border-white/60 bg-white/55 backdrop-blur-xl rounded-xl p-5 shadow-sm hover:shadow-md hover:bg-white/70 transition flex flex-col gap-4 relative">
{/* Status Badge */}
<div className="flex justify-between items-start">
<div className="flex gap-2">
@ -159,15 +159,15 @@ const EmployeeListItem: React.FC<EmployeeListItemProps> = ({
})()}
{/* Action Buttons */}
<div className="flex justify-end items-center pt-3 border-t border-gray-200">
<div className="flex justify-end items-center pt-3 border-t border-white/40">
<div className="flex gap-2">
{showDeleteIcon && onDelete && (
<button onClick={onDelete} className="p-2 rounded-md hover:bg-red-100 transition">
<button onClick={onDelete} className="p-2 rounded-md hover:bg-red-500/10 transition">
<Trash2 size={20} className="text-red-600" />
</button>
)}
{onEdit && (
<button onClick={onEdit} className="p-2 rounded-md hover:bg-gray-100 transition">
<button onClick={onEdit} className="p-2 rounded-md hover:bg-white/50 transition">
<Pen size={20} className="text-gray-700" />
</button>
)}

View file

@ -94,7 +94,7 @@ const EmployeeListView = ({ canCreate, canEdit, canDelete }: EmployeeListViewPro
</div>
{employees.length === 0 ? (
<div className="text-center py-12 bg-gray-50 rounded-lg">
<div className="text-center py-12 border border-white/60 bg-white/55 backdrop-blur-xl rounded-2xl">
<p className="text-xl text-gray-600">Keine Mitarbeiter vorhanden</p>
{canCreate && (
<>

View file

@ -17,7 +17,7 @@ const RoomsListItem: React.FC<RoomsListItemProps> = ({
showDeleteIcon,
}) => {
return (
<div className="bg-white border border-gray-200 rounded-xl p-5 shadow-sm hover:shadow-md transition flex flex-col gap-4 w-full max-w-xs justify-self-center">
<div className="border border-white/60 bg-white/55 backdrop-blur-xl rounded-xl p-5 shadow-sm hover:shadow-md hover:bg-white/70 transition flex flex-col gap-4 w-full max-w-xs justify-self-center">
<div className="w-full flex items-center justify-between">
<div className="flex-1">
<h2 className="text-xl text-gray-900">{roomName}</h2>
@ -27,7 +27,7 @@ const RoomsListItem: React.FC<RoomsListItemProps> = ({
<button
aria-label={`Raum ${roomName} löschen`}
onClick={onDelete}
className="p-2 rounded-md hover:bg-red-100 transition"
className="p-2 rounded-md hover:bg-red-500/10 transition"
>
<Trash2 size={20} className="text-red-600" />
</button>
@ -36,7 +36,7 @@ const RoomsListItem: React.FC<RoomsListItemProps> = ({
<button
aria-label={`Raum ${roomName} bearbeiten`}
onClick={onEdit}
className="p-2 rounded-md hover:bg-gray-100 transition"
className="p-2 rounded-md hover:bg-white/50 transition"
>
<Pen size={20} className="text-gray-700" />
</button>

View file

@ -126,7 +126,7 @@ const SettingsPaymentsView = () => {
</div>
{businesses.length === 0 ? (
<div className="border border-gray-200 rounded-xl p-6 bg-white text-center">
<div className="border border-white/60 bg-white/55 backdrop-blur-xl rounded-xl p-6 text-center">
<p className="text-sm text-gray-500">Du hast noch keine Businesses angelegt.</p>
</div>
) : (
@ -139,7 +139,7 @@ const SettingsPaymentsView = () => {
return (
<div
key={business.id}
className="border border-gray-200 rounded-xl p-5 bg-white space-y-3"
className="border border-white/60 bg-white/55 backdrop-blur-xl rounded-xl p-5 space-y-3"
>
<div className="flex items-start justify-between gap-3">
<div>
@ -190,7 +190,7 @@ const SettingsPaymentsView = () => {
</div>
)}
<div className="border border-blue-100 bg-blue-50/50 rounded-xl p-4 text-xs text-gray-600">
<div className="border border-white/60 bg-white/40 backdrop-blur-xl rounded-xl p-4 text-xs text-gray-600">
<p className="font-medium text-gray-900 mb-1">So funktioniert es:</p>
<ul className="list-disc list-inside space-y-1">
<li>Klicke auf &quot;Stripe-Konto verbinden&quot; - du wirst zu Stripe weitergeleitet</li>

View file

@ -1,5 +1,5 @@
const SettignsPersonalPageView = () => {
const SettingsPersonalPageView = () => {
return <div></div>;
};
export default SettignsPersonalPageView;
export default SettingsPersonalPageView;