Add PWA support and mobile optimizations
- Add manifest.json with PWA configuration - Add viewport settings for iOS (viewport-fit: cover) - Add meta tags for iOS Safari (apple-mobile-web-app-capable) - Add mobile CSS optimizations: * iOS Safe Area support * Minimum 44x44px touch targets * Disable zoom on input focus * Remove scrollbars on mobile * Disable hover effects on touch devices * Standalone mode styles - Add InstallPrompt component for Add to Home Screen - Add SVG icon (needs PNG conversion)
This commit is contained in:
@@ -0,0 +1,4 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="192" height="192" viewBox="0 0 192 192">
|
||||||
|
<rect width="192" height="192" rx="24" fill="#1B1A55"/>
|
||||||
|
<text x="96" y="100" font-family="Arial, sans-serif" font-size="80" font-weight="bold" fill="#9290C3" text-anchor="middle">W</text>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 287 B |
@@ -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"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@ import { useRouter, usePathname } from "next/navigation"
|
|||||||
import { useAuth } from "@/lib/auth"
|
import { useAuth } from "@/lib/auth"
|
||||||
import { Loader2 } from "lucide-react"
|
import { Loader2 } from "lucide-react"
|
||||||
import { Sidebar } from "@/components/layout/Sidebar"
|
import { Sidebar } from "@/components/layout/Sidebar"
|
||||||
|
import { InstallPrompt } from "@/components/ui/install-prompt"
|
||||||
|
|
||||||
export default function DashboardLayout({
|
export default function DashboardLayout({
|
||||||
children,
|
children,
|
||||||
@@ -46,6 +47,7 @@ export default function DashboardLayout({
|
|||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
<InstallPrompt />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -145,4 +145,61 @@
|
|||||||
::-webkit-scrollbar-thumb:hover {
|
::-webkit-scrollbar-thumb:hover {
|
||||||
background: oklch(0.5 0 0 / 35%);
|
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%;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { Metadata } from "next"
|
import type { Metadata, Viewport } from "next"
|
||||||
import { Syne, DM_Sans } from "next/font/google"
|
import { Syne, DM_Sans } from "next/font/google"
|
||||||
import "./globals.css"
|
import "./globals.css"
|
||||||
import { Providers } from "./providers"
|
import { Providers } from "./providers"
|
||||||
@@ -15,9 +15,33 @@ const dmSans = DM_Sans({
|
|||||||
weight: ["400", "500", "600", "700"],
|
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 = {
|
export const metadata: Metadata = {
|
||||||
title: "WrestleDesk",
|
title: "WrestleDesk",
|
||||||
description: "Wrestling Club Management System",
|
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({
|
export default function RootLayout({
|
||||||
|
|||||||
@@ -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 (
|
||||||
|
<div className="fixed bottom-0 left-0 right-0 z-50 p-4 bg-card border-t shadow-lg safe-area-bottom">
|
||||||
|
<div className="flex items-center justify-between max-w-lg mx-auto">
|
||||||
|
<div className="flex-1">
|
||||||
|
<p className="text-sm font-medium">
|
||||||
|
WrestleDesk als App installieren
|
||||||
|
</p>
|
||||||
|
{isIOS ? (
|
||||||
|
<p className="text-xs text-muted-foreground mt-0.5">
|
||||||
|
Tippe auf Teilen → "Zum Home Screen hinzufügen"
|
||||||
|
</p>
|
||||||
|
) : (
|
||||||
|
<p className="text-xs text-muted-foreground mt-0.5">
|
||||||
|
Installieren für schnellen Zugriff
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Button size="sm" variant="ghost" onClick={handleDismiss}>
|
||||||
|
<X className="w-4 h-4" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user