diff --git a/frontend/public/icon-192.svg b/frontend/public/icon-192.svg
new file mode 100644
index 0000000..8aff20c
--- /dev/null
+++ b/frontend/public/icon-192.svg
@@ -0,0 +1,4 @@
+
diff --git a/frontend/public/manifest.json b/frontend/public/manifest.json
new file mode 100644
index 0000000..294b22e
--- /dev/null
+++ b/frontend/public/manifest.json
@@ -0,0 +1,31 @@
+{
+ "name": "WrestleDesk",
+ "short_name": "WrestleDesk",
+ "description": "Wrestling Club Management System",
+ "start_url": "/",
+ "display": "standalone",
+ "background_color": "#070F2B",
+ "theme_color": "#1B1A55",
+ "orientation": "portrait",
+ "scope": "/",
+ "icons": [
+ {
+ "src": "/icon-192.png",
+ "sizes": "192x192",
+ "type": "image/png",
+ "purpose": "any"
+ },
+ {
+ "src": "/icon-512.png",
+ "sizes": "512x512",
+ "type": "image/png",
+ "purpose": "any"
+ },
+ {
+ "src": "/icon-maskable.png",
+ "sizes": "512x512",
+ "type": "image/png",
+ "purpose": "maskable"
+ }
+ ]
+}
diff --git a/frontend/src/app/(dashboard)/layout.tsx b/frontend/src/app/(dashboard)/layout.tsx
index 23115f7..fd9ece0 100644
--- a/frontend/src/app/(dashboard)/layout.tsx
+++ b/frontend/src/app/(dashboard)/layout.tsx
@@ -5,6 +5,7 @@ import { useRouter, usePathname } from "next/navigation"
import { useAuth } from "@/lib/auth"
import { Loader2 } from "lucide-react"
import { Sidebar } from "@/components/layout/Sidebar"
+import { InstallPrompt } from "@/components/ui/install-prompt"
export default function DashboardLayout({
children,
@@ -46,6 +47,7 @@ export default function DashboardLayout({
{children}
+
)
}
diff --git a/frontend/src/app/globals.css b/frontend/src/app/globals.css
index faa4546..b229f58 100644
--- a/frontend/src/app/globals.css
+++ b/frontend/src/app/globals.css
@@ -126,23 +126,80 @@
}
html {
@apply font-sans;
-}
+ }
-::-webkit-scrollbar {
+ ::-webkit-scrollbar {
width: 6px;
height: 6px;
-}
+ }
-::-webkit-scrollbar-track {
+ ::-webkit-scrollbar-track {
background: transparent;
-}
+ }
-::-webkit-scrollbar-thumb {
+ ::-webkit-scrollbar-thumb {
background: oklch(0.5 0 0 / 20%);
border-radius: 3px;
-}
+ }
-::-webkit-scrollbar-thumb:hover {
+ ::-webkit-scrollbar-thumb:hover {
background: oklch(0.5 0 0 / 35%);
-}
+ }
+
+ /* Mobile/PWA Optimierungen */
+ html {
+ -webkit-tap-highlight-color: transparent;
+ touch-action: manipulation;
+ }
+
+ /* iOS Safe Areas */
+ body {
+ padding-top: env(safe-area-inset-top);
+ padding-bottom: env(safe-area-inset-bottom);
+ padding-left: env(safe-area-inset-left);
+ padding-right: env(safe-area-inset-right);
+ }
+
+ /* Kein Zoom bei Input-Fokus auf iOS */
+ input, select, textarea {
+ font-size: 16px;
+ }
+
+ /* Scrollbar auf Mobile ausblenden */
+ @media (max-width: 768px) {
+ ::-webkit-scrollbar {
+ display: none;
+ }
+ body {
+ scrollbar-width: none;
+ }
+ }
+
+ /* Minimum Touch Target 44x44px */
+ button, a, input, select, textarea, [role="button"] {
+ min-height: 44px;
+ min-width: 44px;
+ }
+
+ /* Kein Hover-Effekt auf Touch-Geräten */
+ @media (hover: none) {
+ *:hover {
+ -webkit-transform: none !important;
+ transform: none !important;
+ }
+ }
+
+ /* PWA App-Look im Standalone-Modus */
+ @media (display-mode: standalone) {
+ html {
+ height: 100vh;
+ height: 100dvh;
+ }
+ body {
+ overflow: hidden;
+ position: fixed;
+ width: 100%;
+ height: 100%;
+ }
+ }
}
\ No newline at end of file
diff --git a/frontend/src/app/layout.tsx b/frontend/src/app/layout.tsx
index cc90595..e97ad78 100644
--- a/frontend/src/app/layout.tsx
+++ b/frontend/src/app/layout.tsx
@@ -1,4 +1,4 @@
-import type { Metadata } from "next"
+import type { Metadata, Viewport } from "next"
import { Syne, DM_Sans } from "next/font/google"
import "./globals.css"
import { Providers } from "./providers"
@@ -15,9 +15,33 @@ const dmSans = DM_Sans({
weight: ["400", "500", "600", "700"],
})
+export const viewport: Viewport = {
+ width: "device-width",
+ initialScale: 1,
+ maximumScale: 1,
+ userScalable: false,
+ themeColor: "#1B1A55",
+ viewportFit: "cover",
+}
+
export const metadata: Metadata = {
title: "WrestleDesk",
description: "Wrestling Club Management System",
+ manifest: "/manifest.json",
+ appleWebApp: {
+ capable: true,
+ statusBarStyle: "black-translucent",
+ title: "WrestleDesk",
+ },
+ icons: {
+ icon: [
+ { url: "/icon-192.png", sizes: "192x192", type: "image/png" },
+ { url: "/icon-512.png", sizes: "512x512", type: "image/png" },
+ ],
+ apple: [
+ { url: "/icon-192.png", sizes: "192x192", type: "image/png" },
+ ],
+ },
}
export default function RootLayout({
diff --git a/frontend/src/components/ui/install-prompt.tsx b/frontend/src/components/ui/install-prompt.tsx
new file mode 100644
index 0000000..008a8b1
--- /dev/null
+++ b/frontend/src/components/ui/install-prompt.tsx
@@ -0,0 +1,63 @@
+"use client"
+
+import { useState, useEffect } from "react"
+import { Button } from "@/components/ui/button"
+import { X } from "lucide-react"
+
+export function InstallPrompt() {
+ const [show, setShow] = useState(false)
+ const [isIOS, setIsIOS] = useState(false)
+ const [isStandalone, setIsStandalone] = useState(false)
+
+ useEffect(() => {
+ // Prüfe ob bereits als PWA installiert
+ const standalone = window.matchMedia('(display-mode: standalone)').matches ||
+ (window.navigator as any).standalone ||
+ document.referrer.includes('android-app://')
+ setIsStandalone(standalone)
+
+ // Prüfe iOS
+ const isIOSDevice = /iPad|iPhone|iPod/.test(navigator.userAgent) && !(window as any).MSStream
+ setIsIOS(isIOSDevice)
+
+ // Zeige Prompt nur wenn nicht bereits installiert und nicht bereits geschlossen
+ const dismissed = localStorage.getItem('install-prompt-dismissed')
+ if (!standalone && !dismissed) {
+ // Verzögert anzeigen
+ setTimeout(() => setShow(true), 3000)
+ }
+ }, [])
+
+ const handleDismiss = () => {
+ setShow(false)
+ localStorage.setItem('install-prompt-dismissed', 'true')
+ }
+
+ if (!show || isStandalone) return null
+
+ return (
+
+
+
+
+ WrestleDesk als App installieren
+
+ {isIOS ? (
+
+ Tippe auf Teilen → "Zum Home Screen hinzufügen"
+
+ ) : (
+
+ Installieren für schnellen Zugriff
+
+ )}
+
+
+
+
+
+
+ )
+}