Compare commits
17 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 11d9267b2f | |||
| 6d0312ffd7 | |||
| bdcec35383 | |||
| 153b83c417 | |||
| ee96df11bf | |||
| fb43a8ee9c | |||
| 0721cbf879 | |||
| 7210d09821 | |||
| d6c95a232b | |||
| 8a6db1527b | |||
| ee07a817b0 | |||
| 1314982590 | |||
| 6edff1613c | |||
| 80897a7a6e | |||
| 830908d132 | |||
| 16c6b9789f | |||
| f96d727586 |
@@ -0,0 +1,6 @@
|
|||||||
|
# Backend Environment
|
||||||
|
SECRET_KEY=change-me-to-a-long-random-string-in-production
|
||||||
|
DB_PASSWORD=change-me-to-a-secure-password
|
||||||
|
|
||||||
|
# Optional: PostgreSQL (empfohlen für production)
|
||||||
|
# DATABASE_URL wird automatisch gesetzt: postgresql://wrestledesk:DB_PASSWORD@db:5432/wrestledesk
|
||||||
+202
@@ -0,0 +1,202 @@
|
|||||||
|
# Docker Deployment Guide für Unraid
|
||||||
|
|
||||||
|
Diese Anleitung beschreibt die Deployment von WrestleDesk auf einem Unraid-Server mit Docker Compose. **Nginx Proxy Manager läuft bereits zentral.**
|
||||||
|
|
||||||
|
## Voraussetzungen
|
||||||
|
|
||||||
|
- Unraid Server mit Docker Compose Plugin
|
||||||
|
- Domain (z.B. rce.playman.top) mit DNS A-Record auf Unraid-IP
|
||||||
|
- Nginx Proxy Manager läuft bereits (Port 81 für Admin-UI)
|
||||||
|
|
||||||
|
## Schnellstart
|
||||||
|
|
||||||
|
### 1. Repository klonen
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /mnt/user/appdata/
|
||||||
|
mkdir wrestledesk && cd wrestledesk
|
||||||
|
git clone http://192.168.101.42:3023/PlayMan/WrestleDesk.git .
|
||||||
|
git checkout feature/pwa # Wichtig: PWA Branch verwenden
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Umgebungsvariablen konfigurieren
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cp .env.example .env
|
||||||
|
nano .env # Oder dein bevorzugter Editor
|
||||||
|
```
|
||||||
|
|
||||||
|
**Wichtige Werte in .env ändern:**
|
||||||
|
|
||||||
|
```env
|
||||||
|
# Backend
|
||||||
|
SECRET_KEY=dein-sehr-langer-zufälliger-schlüssel-mindestens-50-zeichen
|
||||||
|
DATABASE_URL=sqlite:///db.sqlite3
|
||||||
|
```
|
||||||
|
|
||||||
|
**Tipp für SECRET_KEY:**
|
||||||
|
```bash
|
||||||
|
openssl rand -base64 50
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Docker Container starten
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker-compose up -d --build
|
||||||
|
```
|
||||||
|
|
||||||
|
Das erste Bauen kann 5-10 Minuten dauern.
|
||||||
|
|
||||||
|
### 4. Im Nginx Proxy Manager konfigurieren
|
||||||
|
|
||||||
|
1. Öffne deinen Nginx Proxy Manager (normalerweise `http://deine-unraid-ip:81`)
|
||||||
|
2. **Proxy Hosts** → **Add Proxy Host**
|
||||||
|
|
||||||
|
**Einstellungen für Frontend:**
|
||||||
|
- **Domain Names:** `rce.playman.top`
|
||||||
|
- **Scheme:** `http`
|
||||||
|
- **Forward Hostname / IP:** `deine-unraid-ip`
|
||||||
|
- **Forward Port:** `3000`
|
||||||
|
- **Block Common Exploits:** ✅ Aktivieren
|
||||||
|
|
||||||
|
**Einstellungen für Backend (API):**
|
||||||
|
- **Domain Names:** `rce.playman.top`
|
||||||
|
- **Scheme:** `http`
|
||||||
|
- **Forward Hostname / IP:** `deine-unraid-ip`
|
||||||
|
- **Forward Port:** `8000`
|
||||||
|
- **Advanced Tab** → **Custom Locations**:
|
||||||
|
- Location: `/api/v1`
|
||||||
|
- Forward Hostname: `deine-unraid-ip`
|
||||||
|
- Forward Port: `8000`
|
||||||
|
|
||||||
|
**SSL Tab:**
|
||||||
|
- **SSL Certificate:** `Request a new SSL Certificate`
|
||||||
|
- **Force SSL:** ✅ Aktivieren
|
||||||
|
- **HTTP/2 Support:** ✅ Aktivieren
|
||||||
|
- **Save**
|
||||||
|
|
||||||
|
### 5. DNS einrichten
|
||||||
|
|
||||||
|
In deinem Domain-Provider:
|
||||||
|
- **Type:** A
|
||||||
|
- **Name:** rce (oder @ für Root)
|
||||||
|
- **Value:** Deine Unraid Server IP
|
||||||
|
- **TTL:** 300
|
||||||
|
|
||||||
|
### 6. Fertig!
|
||||||
|
|
||||||
|
Nach wenigen Minuten sollte WrestleDesk erreichbar sein unter:
|
||||||
|
- **https://rce.playman.top**
|
||||||
|
|
||||||
|
## Wartung
|
||||||
|
|
||||||
|
### Updates durchführen
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /mnt/user/appdata/wrestledesk
|
||||||
|
git pull origin feature/pwa # Oder main
|
||||||
|
docker-compose down
|
||||||
|
docker-compose up -d --build
|
||||||
|
```
|
||||||
|
|
||||||
|
### Logs ansehen
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Alle Services
|
||||||
|
docker-compose logs -f
|
||||||
|
|
||||||
|
# Nur Frontend
|
||||||
|
docker-compose logs -f frontend
|
||||||
|
|
||||||
|
# Nur Backend
|
||||||
|
docker-compose logs -f backend
|
||||||
|
```
|
||||||
|
|
||||||
|
### Backup
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Datenbank + Media Backup erstellen
|
||||||
|
cd /mnt/user/appdata/wrestledesk
|
||||||
|
tar czf backup-$(date +%Y%m%d).tar.gz backend/db.sqlite3 backend/media/
|
||||||
|
|
||||||
|
# Backup zu Unraid Array verschieben
|
||||||
|
mv backup-*.tar.gz /mnt/user/backups/wrestledesk/
|
||||||
|
```
|
||||||
|
|
||||||
|
### Container stoppen
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker-compose down
|
||||||
|
```
|
||||||
|
|
||||||
|
## Port-Belegung
|
||||||
|
|
||||||
|
| Service | Port | Beschreibung |
|
||||||
|
|---------|------|--------------|
|
||||||
|
| Frontend | 3000 | Next.js App |
|
||||||
|
| Backend | 8000 | Django API |
|
||||||
|
|
||||||
|
**Wichtig:** Diese Ports müssen von außen NICHT erreichbar sein - nur über Nginx Proxy Manager!
|
||||||
|
|
||||||
|
## Fehlerbehebung
|
||||||
|
|
||||||
|
### Frontend zeigt "Backend not reachable"
|
||||||
|
|
||||||
|
Prüfe ob die API-URL korrekt ist:
|
||||||
|
```bash
|
||||||
|
docker-compose exec frontend env | grep API_URL
|
||||||
|
# Sollte zeigen: NEXT_PUBLIC_API_URL=https://rce.playman.top/api/v1
|
||||||
|
```
|
||||||
|
|
||||||
|
### CORS Fehler im Browser
|
||||||
|
|
||||||
|
Backend .env prüfen:
|
||||||
|
```bash
|
||||||
|
docker-compose exec backend env | grep CORS
|
||||||
|
# Sollte zeigen: CORS_ALLOWED_ORIGINS=https://rce.playman.top
|
||||||
|
```
|
||||||
|
|
||||||
|
### Container starten nicht
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Prüfen was den Port blockiert
|
||||||
|
netstat -tlnp | grep -E '(:3000|:8000)'
|
||||||
|
|
||||||
|
# Oder: Container logs prüfen
|
||||||
|
docker-compose logs --tail 50
|
||||||
|
```
|
||||||
|
|
||||||
|
### SSL Zertifikat wird nicht erstellt
|
||||||
|
|
||||||
|
- Prüfe ob Port 80 und 443 von außen erreichbar sind
|
||||||
|
- Prüfe DNS A-Record (sollte auf Unraid-IP zeigen)
|
||||||
|
- Warte 24h nach DNS-Änderungen
|
||||||
|
- Versuche "Renew" im Nginx Proxy Manager
|
||||||
|
|
||||||
|
## Sicherheitshinweise
|
||||||
|
|
||||||
|
1. **Ändere das Nginx Proxy Manager Passwort** nach dem ersten Login
|
||||||
|
2. **Verwende ein starkes SECRET_KEY** in .env (min. 50 Zeichen)
|
||||||
|
3. **Aktiviere "Block Common Exploits"** im Nginx Proxy Manager
|
||||||
|
4. **Halte Docker Images aktuell:** `docker-compose pull && docker-compose up -d`
|
||||||
|
5. **Backup regelmäßig durchführen**
|
||||||
|
|
||||||
|
## Architektur
|
||||||
|
|
||||||
|
```
|
||||||
|
Internet
|
||||||
|
↓
|
||||||
|
Nginx Proxy Manager (auf Unraid, zentral)
|
||||||
|
↓ (Reverse Proxy)
|
||||||
|
┌──────────────┐ ┌──────────────┐
|
||||||
|
│ Frontend │←──→│ Backend │
|
||||||
|
│ Port 3000 │ │ Port 8000 │
|
||||||
|
└──────────────┘ └──────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
## Support
|
||||||
|
|
||||||
|
Bei Problemen:
|
||||||
|
1. Logs prüfen: `docker-compose logs`
|
||||||
|
2. Container Status: `docker-compose ps`
|
||||||
|
3. Netzwerk prüfen: `docker network ls`
|
||||||
@@ -0,0 +1,121 @@
|
|||||||
|
# Nginx Proxy Manager Konfiguration für WrestleDesk
|
||||||
|
|
||||||
|
Diese Anleitung beschreibt die Einrichtung von Nginx Proxy Manager, damit das Frontend öffentlich erreichbar ist, das Backend aber intern bleibt.
|
||||||
|
|
||||||
|
## Architektur
|
||||||
|
|
||||||
|
```
|
||||||
|
Internet
|
||||||
|
↓ (HTTPS)
|
||||||
|
rce.playman.top (Nginx Proxy Manager)
|
||||||
|
├── / → 192.168.101.42:10001 (Frontend)
|
||||||
|
└── /api/v1 → 192.168.101.42:10002 (Backend intern)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Schritt 1: Proxy Host für Frontend erstellen
|
||||||
|
|
||||||
|
1. Nginx Proxy Manager öffnen (http://deine-unraid-ip:81)
|
||||||
|
2. **Proxy Hosts** → **Add Proxy Host**
|
||||||
|
|
||||||
|
**Details Tab:**
|
||||||
|
- **Domain Names:** `rce.playman.top`
|
||||||
|
- **Scheme:** `http`
|
||||||
|
- **Forward Hostname / IP:** `192.168.101.42`
|
||||||
|
- **Forward Port:** `10001`
|
||||||
|
- **Cache Assets:** ❌ (optional)
|
||||||
|
- **Block Common Exploits:** ✅ (empfohlen)
|
||||||
|
|
||||||
|
**SSL Tab:**
|
||||||
|
- **SSL Certificate:** `Request a new SSL Certificate`
|
||||||
|
- **Force SSL:** ✅
|
||||||
|
- **HTTP/2 Support:** ✅
|
||||||
|
- **Accept Terms:** ✅
|
||||||
|
- **Save**
|
||||||
|
|
||||||
|
## Schritt 2: API Weiterleitung (Location) hinzufügen
|
||||||
|
|
||||||
|
1. Auf den gerade erstellten Host klicken (**Edit**)
|
||||||
|
2. **Advanced** Tab öffnen
|
||||||
|
|
||||||
|
**Custom Locations einfügen:**
|
||||||
|
|
||||||
|
```nginx
|
||||||
|
location /api/v1 {
|
||||||
|
proxy_pass http://192.168.101.42:10002/api/v1;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
|
||||||
|
# CORS Headers für API
|
||||||
|
add_header 'Access-Control-Allow-Origin' 'https://rce.playman.top' always;
|
||||||
|
add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, PATCH, DELETE, OPTIONS' always;
|
||||||
|
add_header 'Access-Control-Allow-Headers' 'Authorization, Content-Type' always;
|
||||||
|
|
||||||
|
# Preflight Requests
|
||||||
|
if ($request_method = 'OPTIONS') {
|
||||||
|
return 204;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Save**
|
||||||
|
|
||||||
|
## Schritt 3: DNS einrichten
|
||||||
|
|
||||||
|
In deinem Domain-Provider:
|
||||||
|
- **Type:** A
|
||||||
|
- **Name:** `rce` (oder @ für Root)
|
||||||
|
- **Value:** `192.168.101.42` (deine Unraid IP)
|
||||||
|
- **TTL:** 300
|
||||||
|
|
||||||
|
## Schritt 4: Testen
|
||||||
|
|
||||||
|
Warte 1-2 Minuten für DNS, dann teste:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Frontend erreichbar?
|
||||||
|
curl https://rce.playman.top
|
||||||
|
|
||||||
|
# API erreichbar?
|
||||||
|
curl https://rce.playman.top/api/v1/
|
||||||
|
# Sollte zurückgeben: {"detail":"Authentication credentials were not provided."}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Sicherheitshinweise
|
||||||
|
|
||||||
|
1. **Backend Port 10002** ist nur intern erreichbar (Unraid Firewall)
|
||||||
|
2. **Niemals** Port 10002 im Router öffnen!
|
||||||
|
3. Nur Port 80 und 443 (für Nginx Proxy Manager) sollten vom Internet erreichbar sein
|
||||||
|
|
||||||
|
## Fehlerbehebung
|
||||||
|
|
||||||
|
### "Mixed Content" Fehler im Browser
|
||||||
|
- Prüfe ob SSL aktiv ist (https://)
|
||||||
|
- Frontend .env.local muss `https://` enthalten
|
||||||
|
|
||||||
|
### CORS Fehler
|
||||||
|
- Custom Locations müssen korrekt sein
|
||||||
|
- Backend CORS_ALLOWED_ORIGINS muss `https://rce.playman.top` enthalten
|
||||||
|
|
||||||
|
### API nicht erreichbar
|
||||||
|
```bash
|
||||||
|
# Teste direkten Backend-Zugriff (nur intern)
|
||||||
|
curl http://192.168.101.42:10002/api/v1/
|
||||||
|
|
||||||
|
# Teste über Proxy
|
||||||
|
curl https://rce.playman.top/api/v1/
|
||||||
|
```
|
||||||
|
|
||||||
|
## Wichtige Ports
|
||||||
|
|
||||||
|
| Port | Service | Öffentlich | Intern |
|
||||||
|
|------|---------|------------|--------|
|
||||||
|
| 80 | Nginx HTTP | ✅ | ✅ |
|
||||||
|
| 443 | Nginx HTTPS | ✅ | ✅ |
|
||||||
|
| 10001 | Frontend | ❌ | ✅ |
|
||||||
|
| 10002 | Backend | ❌ | ✅ |
|
||||||
|
|
||||||
|
**Backend ist NUR über Nginx Proxy erreichbar, niemals direkt!**
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
# Nginx Proxy Manager Konfiguration (Einfach)
|
||||||
|
|
||||||
|
## Schritt 1: Proxy Host erstellen
|
||||||
|
|
||||||
|
1. Nginx Proxy Manager öffnen (http://deine-unraid-ip:81)
|
||||||
|
2. **Proxy Hosts** → **Add Proxy Host**
|
||||||
|
|
||||||
|
**Details:**
|
||||||
|
- Domain Names: `rce.playman.top`
|
||||||
|
- Scheme: `http`
|
||||||
|
- Forward Hostname: `192.168.101.42`
|
||||||
|
- Forward Port: `10001`
|
||||||
|
- Block Common Exploits: ✅
|
||||||
|
|
||||||
|
**SSL:**
|
||||||
|
- SSL Certificate: Request a new SSL Certificate
|
||||||
|
- Force SSL: ✅
|
||||||
|
- HTTP/2 Support: ✅
|
||||||
|
- Save
|
||||||
|
|
||||||
|
## Schritt 2: API Weiterleitung
|
||||||
|
|
||||||
|
Auf den Host klicken (Edit) → **Advanced** Tab:
|
||||||
|
|
||||||
|
**Custom Locations:**
|
||||||
|
|
||||||
|
```nginx
|
||||||
|
location /api {
|
||||||
|
proxy_pass http://192.168.101.42:10002;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Save**
|
||||||
|
|
||||||
|
## Fertig!
|
||||||
|
|
||||||
|
- Frontend: https://rce.playman.top
|
||||||
|
- Backend intern: https://rce.playman.top/api/v1
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
FROM python:3.11-slim
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
ENV PYTHONDONTWRITEBYTECODE=1
|
||||||
|
ENV PYTHONUNBUFFERED=1
|
||||||
|
ENV DEBIAN_FRONTEND=noninteractive
|
||||||
|
|
||||||
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
|
build-essential \
|
||||||
|
libpq-dev \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
COPY requirements.txt .
|
||||||
|
RUN pip install --no-cache-dir -r requirements.txt
|
||||||
|
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
RUN mkdir -p /app/media /app/staticfiles
|
||||||
|
|
||||||
|
EXPOSE 8000
|
||||||
|
|
||||||
|
CMD ["gunicorn", "--bind", "0.0.0.0:8000", "--workers", "4", "--threads", "2", "--timeout", "60", "wrestleDesk.wsgi:application"]
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
version: '3.8'
|
||||||
|
|
||||||
|
services:
|
||||||
|
db:
|
||||||
|
image: postgres:15-alpine
|
||||||
|
container_name: wrestledesk-db
|
||||||
|
restart: unless-stopped
|
||||||
|
volumes:
|
||||||
|
- ./postgres-data:/var/lib/postgresql/data
|
||||||
|
environment:
|
||||||
|
- POSTGRES_DB=wrestledesk
|
||||||
|
- POSTGRES_USER=wrestledesk
|
||||||
|
- POSTGRES_PASSWORD=${DB_PASSWORD}
|
||||||
|
networks:
|
||||||
|
- wrestledesk-network
|
||||||
|
|
||||||
|
backend:
|
||||||
|
build: ./backend
|
||||||
|
container_name: wrestledesk-backend
|
||||||
|
restart: unless-stopped
|
||||||
|
depends_on:
|
||||||
|
- db
|
||||||
|
ports:
|
||||||
|
- '10002:8000'
|
||||||
|
volumes:
|
||||||
|
- ./backend/media:/app/media
|
||||||
|
- ./backend/staticfiles:/app/staticfiles
|
||||||
|
environment:
|
||||||
|
- SECRET_KEY=${SECRET_KEY}
|
||||||
|
- DEBUG=False
|
||||||
|
- ALLOWED_HOSTS=localhost,127.0.0.1,rce.playman.top,192.168.101.42,nginx-proxy-manager
|
||||||
|
- CORS_ALLOWED_ORIGINS=https://rce.playman.top,http://192.168.101.42:10001,http://192.168.101.42:10002
|
||||||
|
- DATABASE_URL=postgresql://wrestledesk:${DB_PASSWORD}@db:5432/wrestledesk
|
||||||
|
networks:
|
||||||
|
- wrestledesk-network
|
||||||
|
|
||||||
|
frontend:
|
||||||
|
build: ./frontend
|
||||||
|
container_name: wrestledesk-frontend
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- '10001:3000'
|
||||||
|
environment:
|
||||||
|
- NEXT_PUBLIC_API_URL=https://rce.playman.top/api/v1
|
||||||
|
- NODE_ENV=production
|
||||||
|
networks:
|
||||||
|
- wrestledesk-network
|
||||||
|
|
||||||
|
networks:
|
||||||
|
wrestledesk-network:
|
||||||
|
driver: bridge
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
NEXT_PUBLIC_API_URL=https://rce.playman.top/api/v1
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
FROM node:20-alpine AS builder
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY package*.json ./
|
||||||
|
RUN npm ci
|
||||||
|
|
||||||
|
COPY . .
|
||||||
|
RUN npm run build
|
||||||
|
|
||||||
|
FROM node:20-alpine AS runner
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
ENV NODE_ENV=production
|
||||||
|
|
||||||
|
COPY --from=builder /app/.next/standalone ./
|
||||||
|
COPY --from=builder /app/.next/static ./.next/static
|
||||||
|
COPY --from=builder /app/public ./public
|
||||||
|
|
||||||
|
EXPOSE 3000
|
||||||
|
|
||||||
|
ENV PORT=3000
|
||||||
|
ENV HOSTNAME="0.0.0.0"
|
||||||
|
|
||||||
|
CMD ["node", "server.js"]
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
const sharp = require('sharp');
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
const svgPath = path.join(__dirname, 'public/icon-192.svg');
|
||||||
|
if (!fs.existsSync(svgPath)) {
|
||||||
|
console.error('SVG template not found at', svgPath);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
const svgBuffer = fs.readFileSync(svgPath);
|
||||||
|
|
||||||
|
Promise.all([
|
||||||
|
sharp(svgBuffer).resize(192, 192).png().toFile(path.join(__dirname, 'public/icon-192.png')),
|
||||||
|
sharp(svgBuffer).resize(512, 512).png().toFile(path.join(__dirname, 'public/icon-512.png')),
|
||||||
|
sharp(svgBuffer).resize(180, 180).png().toFile(path.join(__dirname, 'public/apple-touch-icon.png')),
|
||||||
|
sharp(svgBuffer).resize(384, 384).extend({ top: 64, bottom: 64, left: 64, right: 64, background: { r: 27, g: 26, b: 85, alpha: 1 } }).png().toFile(path.join(__dirname, 'public/icon-maskable.png'))
|
||||||
|
]).then(() => {
|
||||||
|
console.log('Icons created successfully');
|
||||||
|
}).catch((err) => {
|
||||||
|
console.error('Icon generation failed:', err);
|
||||||
|
});
|
||||||
@@ -1,7 +1,10 @@
|
|||||||
import type { NextConfig } from "next";
|
import type { NextConfig } from "next";
|
||||||
|
|
||||||
const nextConfig: NextConfig = {
|
const nextConfig: NextConfig = {
|
||||||
/* config options here */
|
output: 'standalone',
|
||||||
|
images: {
|
||||||
|
unoptimized: true,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default nextConfig;
|
export default nextConfig;
|
||||||
|
|||||||
Generated
+1
-4
@@ -23,6 +23,7 @@
|
|||||||
"react-big-calendar": "^1.19.4",
|
"react-big-calendar": "^1.19.4",
|
||||||
"react-dom": "19.2.4",
|
"react-dom": "19.2.4",
|
||||||
"shadcn": "^4.1.0",
|
"shadcn": "^4.1.0",
|
||||||
|
"sharp": "^0.34.5",
|
||||||
"sonner": "^2.0.7",
|
"sonner": "^2.0.7",
|
||||||
"tailwind-merge": "^3.5.0",
|
"tailwind-merge": "^3.5.0",
|
||||||
"tw-animate-css": "^1.4.0",
|
"tw-animate-css": "^1.4.0",
|
||||||
@@ -1055,7 +1056,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz",
|
||||||
"integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==",
|
"integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
@@ -4192,7 +4192,6 @@
|
|||||||
"version": "2.1.2",
|
"version": "2.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
|
||||||
"integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
|
"integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
|
||||||
"devOptional": true,
|
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
@@ -8787,7 +8786,6 @@
|
|||||||
"integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==",
|
"integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"optional": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@img/colour": "^1.0.0",
|
"@img/colour": "^1.0.0",
|
||||||
"detect-libc": "^2.1.2",
|
"detect-libc": "^2.1.2",
|
||||||
@@ -8831,7 +8829,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
|
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
|
||||||
"integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
|
"integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"optional": true,
|
|
||||||
"bin": {
|
"bin": {
|
||||||
"semver": "bin/semver.js"
|
"semver": "bin/semver.js"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "next dev",
|
"dev": "next dev",
|
||||||
|
"dev:host": "next dev --hostname 192.168.101.111",
|
||||||
"build": "next build",
|
"build": "next build",
|
||||||
"start": "next start",
|
"start": "next start",
|
||||||
"lint": "eslint"
|
"lint": "eslint"
|
||||||
@@ -24,6 +25,7 @@
|
|||||||
"react-big-calendar": "^1.19.4",
|
"react-big-calendar": "^1.19.4",
|
||||||
"react-dom": "19.2.4",
|
"react-dom": "19.2.4",
|
||||||
"shadcn": "^4.1.0",
|
"shadcn": "^4.1.0",
|
||||||
|
"sharp": "^0.34.5",
|
||||||
"sonner": "^2.0.7",
|
"sonner": "^2.0.7",
|
||||||
"tailwind-merge": "^3.5.0",
|
"tailwind-merge": "^3.5.0",
|
||||||
"tw-animate-css": "^1.4.0",
|
"tw-animate-css": "^1.4.0",
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 3.4 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 3.7 KiB |
@@ -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 |
Binary file not shown.
|
After Width: | Height: | Size: 14 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
@@ -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,30 @@
|
|||||||
::-webkit-scrollbar-thumb:hover {
|
::-webkit-scrollbar-thumb:hover {
|
||||||
background: oklch(0.5 0 0 / 35%);
|
background: oklch(0.5 0 0 / 35%);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/* Mobile/PWA Optimizations */
|
||||||
|
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); }
|
||||||
|
|
||||||
|
/* Prevent zoom on input focus */
|
||||||
|
input, select, textarea { font-size: 16px; }
|
||||||
|
|
||||||
|
/* Hide scrollbar on mobile */
|
||||||
|
@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; }
|
||||||
|
|
||||||
|
/* Disable hover effects on touch devices */
|
||||||
|
@media (hover: none) { *:hover { transform: none !important; } }
|
||||||
|
|
||||||
|
/* PWA standalone mode styles */
|
||||||
|
@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,55 @@
|
|||||||
|
"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(() => {
|
||||||
|
const standalone = window.matchMedia('(display-mode: standalone)').matches ||
|
||||||
|
(window.navigator as any).standalone ||
|
||||||
|
document.referrer.includes('android-app://')
|
||||||
|
setIsStandalone(standalone)
|
||||||
|
|
||||||
|
const isIOSDevice = /iPad|iPhone|iPod/.test(navigator.userAgent) && !(window as any).MSStream
|
||||||
|
setIsIOS(isIOSDevice)
|
||||||
|
|
||||||
|
const dismissed = localStorage.getItem('install-prompt-dismissed')
|
||||||
|
if (!standalone && !dismissed) {
|
||||||
|
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">
|
||||||
|
<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">
|
||||||
|
Tippe auf Teilen → "Zum Home Screen hinzufügen"
|
||||||
|
</p>
|
||||||
|
) : (
|
||||||
|
<p className="text-xs text-muted-foreground">
|
||||||
|
Installieren für schnellen Zugriff
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<Button size="sm" variant="ghost" onClick={handleDismiss}>
|
||||||
|
<X className="w-4 h-4" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user