fix(frontend): Vite-Build reparieren und Unterpfad-Deployment korrigieren
Der Build läuft über Vite ("build": "vite build"), der Code las
Umgebungsvariablen aber über process.env – das gibt es im Browser-Bundle
nicht. Damit war der Build-Arg REACT_APP_API_URL wirkungslos und der
Zugriff selbst lief in "process is not defined".
- constants.js nutzt import.meta.env und leitet den Deployment-Basispfad
aus import.meta.env.BASE_URL ab (Fallback: Laufzeit-Erkennung).
REACT_APP_* heißt überall VITE_* (Dockerfile, Compose, .env.example,
Doku).
- /verwaltung und /passwort-zuruecksetzen wurden gegen
window.location.pathname ohne Basispfad verglichen und trafen unter
/nachsuche/ nie zu – die Passwort-Reset-Seite war produktiv nicht
erreichbar. Neue Helfer withBase()/normalizePath().
- Der Service Worker wurde als '/sw.js' registriert und war damit der des
Portals mit Scope '/'. Registrierung läuft jetzt über BASE_URL, inkl.
passendem Scope.
- index.html verlinkte '/manifest.json' absolut, also das Portal-Manifest:
die App hätte sich als Portal installiert. Jetzt %BASE_URL%.
- public/index.html (CRA-Rest) kollidierte im Vite-Build mit der
index.html im Projektwurzelverzeichnis und wurde entfernt.
- manifest.json verwies auf ein favicon.ico, das es nicht gibt.
- package.json: Scripts auf Vite umgestellt. react-scripts,
@testing-library/* und web-vitals entfernt – nichts davon wird
importiert, und react-scripts stand im Widerspruch zum Build.
- Die ungenutzten Konstanten USER_TYPES/USER_TYPE_LABELS/RULES sind
entfallen (die echten Werte kommen aus der Datenbank).
- Header bekam eine Prop onXLogin, die er gar nicht entgegennimmt und die
den Logout-Handler durchreichte.
Verifiziert: alle drei Frontends bauen, im Bundle kein process.env mehr,
alle Pfade korrekt auf den jeweiligen Basispfad.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
718d84a545
commit
63f6d97318
|
|
@ -139,7 +139,7 @@ Im Frontend wird die API-URL zur **Build-Zeit** gesetzt. Für Production:
|
|||
frontend:
|
||||
build:
|
||||
args:
|
||||
- REACT_APP_API_URL=https://api.yourdomain.com
|
||||
- VITE_API_URL=https://api.yourdomain.com
|
||||
```
|
||||
|
||||
2. Rebuild erforderlich:
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ services:
|
|||
context: ./frontend
|
||||
args:
|
||||
- PUBLIC_URL=/drohnenfuehrer/
|
||||
- REACT_APP_API_URL=${REACT_APP_API_URL:-}
|
||||
- VITE_API_URL=${VITE_API_URL:-}
|
||||
container_name: drohnenfuehrer-frontend
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
|
|
|
|||
|
|
@ -183,4 +183,4 @@ frontend/src/
|
|||
- `CORS_ORIGIN`: Erlaubter CORS-Origin
|
||||
|
||||
### Frontend (.env)
|
||||
- `REACT_APP_API_URL`: Backend-API-URL (Standard: http://localhost:5000)
|
||||
- `VITE_API_URL`: Backend-API-URL (Standard: http://localhost:5000)
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
# API Configuration
|
||||
# For local development
|
||||
REACT_APP_API_URL=http://localhost:5000
|
||||
VITE_API_URL=http://localhost:5000
|
||||
|
||||
# For production, use your actual backend URL
|
||||
# REACT_APP_API_URL=https://api.yourdomain.com
|
||||
# VITE_API_URL=https://api.yourdomain.com
|
||||
|
||||
# Admin path (optional, defaults to /verwaltung)
|
||||
# REACT_APP_ADMIN_PATH=/verwaltung
|
||||
# VITE_ADMIN_PATH=/verwaltung
|
||||
|
|
|
|||
|
|
@ -10,8 +10,8 @@ COPY . .
|
|||
|
||||
ARG PUBLIC_URL=/drohnenfuehrer/
|
||||
ENV PUBLIC_URL=$PUBLIC_URL
|
||||
ARG REACT_APP_API_URL=
|
||||
ENV REACT_APP_API_URL=$REACT_APP_API_URL
|
||||
ARG VITE_API_URL=
|
||||
ENV VITE_API_URL=$VITE_API_URL
|
||||
# Limit Node.js heap during build to prevent OOM kills
|
||||
ENV NODE_OPTIONS="--max_old_space_size=512"
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="%BASE_URL%favicon.ico" />
|
||||
<link rel="icon" href="%BASE_URL%icons/icon-192.png" />
|
||||
<link rel="apple-touch-icon" href="%BASE_URL%icons/icon-192.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#2d6a2d" />
|
||||
|
|
@ -10,7 +10,9 @@
|
|||
<meta name="apple-mobile-web-app-status-bar-style" content="default" />
|
||||
<meta name="apple-mobile-web-app-title" content="Drohnenführer" />
|
||||
<meta name="mobile-web-app-capable" content="yes" />
|
||||
<link rel="manifest" href="/manifest.json" />
|
||||
<!-- Pfad relativ zur Deployment-Basis: ein absolutes "/manifest.json" wuerde
|
||||
das Portal-Manifest laden und die App als Portal installieren. -->
|
||||
<link rel="manifest" href="%BASE_URL%manifest.json" />
|
||||
<meta
|
||||
name="description"
|
||||
content="Drohnenführer Heidekreis – Übersicht der Drohnenführer"
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -3,28 +3,16 @@
|
|||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@testing-library/jest-dom": "^5.16.4",
|
||||
"@testing-library/react": "^13.3.0",
|
||||
"@testing-library/user-event": "^13.5.0",
|
||||
"axios": "^1.13.5",
|
||||
"leaflet": "^1.9.4",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-leaflet": "^4.2.1",
|
||||
"react-scripts": "5.0.1",
|
||||
"web-vitals": "^2.1.4"
|
||||
"react-leaflet": "^4.2.1"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
"start": "vite",
|
||||
"build": "vite build",
|
||||
"test": "react-scripts test",
|
||||
"eject": "react-scripts eject"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": [
|
||||
"react-app",
|
||||
"react-app/jest"
|
||||
]
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
|
|
|
|||
|
|
@ -1,24 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
||||
<link rel="apple-touch-icon" href="%PUBLIC_URL%/icons/icon-192.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#2d6a2d" />
|
||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="default" />
|
||||
<meta name="apple-mobile-web-app-title" content="Drohnenführer Heidekreis" />
|
||||
<meta name="mobile-web-app-capable" content="yes" />
|
||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
||||
<meta
|
||||
name="description"
|
||||
content="Drohnenführer Heidekreis – Übersicht der Drohnenführer"
|
||||
/>
|
||||
<title>Drohnenführer Heidekreis</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -2,11 +2,6 @@
|
|||
"short_name": "Drohnenführer",
|
||||
"name": "Drohnenführer Heidekreis",
|
||||
"icons": [
|
||||
{
|
||||
"src": "favicon.ico",
|
||||
"sizes": "64x64 32x32 24x24 16x16",
|
||||
"type": "image/x-icon"
|
||||
},
|
||||
{
|
||||
"src": "icons/icon-192.png",
|
||||
"sizes": "192x192",
|
||||
|
|
@ -27,4 +22,4 @@
|
|||
"theme_color": "#2d6a2d",
|
||||
"background_color": "#ffffff",
|
||||
"description": "Drohnenführer Heidekreis – Übersicht der Drohnenführer"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,13 +11,15 @@ import Admin from './pages/Admin';
|
|||
import DrohnenfuehrerLogin from './components/drohnenfuehrer/DrohnenfuehrerLogin';
|
||||
import DrohnenfuehrerDashboard from './components/drohnenfuehrer/DrohnenfuehrerDashboard';
|
||||
import InstallBanner from './components/common/InstallBanner';
|
||||
import { ADMIN_PATH, RESET_PASSWORD_PATH, withBase, normalizePath } from './utils/constants';
|
||||
import './App.css';
|
||||
|
||||
function App() {
|
||||
const adminPath = process.env.REACT_APP_ADMIN_PATH || '/verwaltung';
|
||||
const resetPasswordPath = '/passwort-zuruecksetzen';
|
||||
const isAdminRoute = window.location.pathname === adminPath;
|
||||
const isResetPasswordRoute = window.location.pathname === resetPasswordPath;
|
||||
// Die Pfade muessen den Deployment-Basispfad enthalten: produktiv laeuft die
|
||||
// App unter /<app>/, ein Vergleich gegen '/verwaltung' traefe dort nie zu.
|
||||
const currentPath = normalizePath(window.location.pathname);
|
||||
const isAdminRoute = currentPath === normalizePath(withBase(ADMIN_PATH));
|
||||
const isResetPasswordRoute = currentPath === normalizePath(withBase(RESET_PASSWORD_PATH));
|
||||
const [view, setView] = useState('public');
|
||||
const [drohnenfuehrerUser, setDrohnenfuehrerUser] = useState(null);
|
||||
const { isAuthenticated, loading: authLoading, login, logout } = useAuth();
|
||||
|
|
@ -97,7 +99,6 @@ function App() {
|
|||
isAdmin={false}
|
||||
currentView={view}
|
||||
onViewChange={handleViewChange}
|
||||
onDrohnenfuehrerLogin={handleDrohnenfuehrerLogout}
|
||||
/>
|
||||
<main className="app-main">
|
||||
{isAdminRoute || view === 'login' ? (
|
||||
|
|
|
|||
|
|
@ -10,10 +10,14 @@ root.render(
|
|||
</React.StrictMode>
|
||||
);
|
||||
|
||||
// Register service worker
|
||||
// Register service worker.
|
||||
// BASE_URL endet immer auf '/' und ist bei Unterpfad-Deployments '/nachsuche/'.
|
||||
// Ein absolutes '/sw.js' würde stattdessen den Service Worker des Portals
|
||||
// registrieren und dessen Scope übernehmen.
|
||||
if ('serviceWorker' in navigator) {
|
||||
const base = import.meta.env.BASE_URL || '/';
|
||||
window.addEventListener('load', () => {
|
||||
navigator.serviceWorker.register('./sw.js')
|
||||
navigator.serviceWorker.register(`${base}sw.js`, { scope: base })
|
||||
.then(registration => {
|
||||
console.log('SW registered: ', registration);
|
||||
})
|
||||
|
|
@ -21,4 +25,4 @@ if ('serviceWorker' in navigator) {
|
|||
console.log('SW registration failed: ', registrationError);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,34 +1,34 @@
|
|||
// Use configured API URL when set, otherwise auto-detect common subpath deployment (/drohnenfuehrer)
|
||||
// Der Build läuft über Vite: Umgebungsvariablen kommen aus import.meta.env und
|
||||
// müssen mit VITE_ beginnen. `process.env` existiert im Browser-Bundle NICHT –
|
||||
// ein Zugriff darauf wirft "process is not defined" und die App bleibt weiß.
|
||||
const env = import.meta.env;
|
||||
|
||||
// Fallback, falls ein Build ohne PUBLIC_URL/VITE_BASE_PATH unter einem Unterpfad
|
||||
// ausgeliefert wird: dann steht in BASE_URL nur '/'.
|
||||
const detectRuntimeBasePath = () => {
|
||||
if (typeof window === 'undefined') return '';
|
||||
const path = window.location.pathname || '';
|
||||
if (path === '/drohnenfuehrer' || path.startsWith('/drohnenfuehrer/')) {
|
||||
return '/drohnenfuehrer';
|
||||
}
|
||||
return '';
|
||||
const knownBasePaths = ['/nachsuche', '/drohnenfuehrer', '/stoeberhunde'];
|
||||
const match = knownBasePaths.find(basePath => path === basePath || path.startsWith(`${basePath}/`));
|
||||
return match || '';
|
||||
};
|
||||
|
||||
const configuredApiBaseUrl = process.env.REACT_APP_API_URL;
|
||||
export const API_BASE_URL = (typeof configuredApiBaseUrl === 'string' && configuredApiBaseUrl.trim().length > 0)
|
||||
? configuredApiBaseUrl
|
||||
: detectRuntimeBasePath();
|
||||
const stripTrailingSlash = (value) => String(value || '').replace(/\/+$/, '');
|
||||
|
||||
export const USER_TYPES = {
|
||||
DF: 'DF',
|
||||
WK: 'WK',
|
||||
RGB: 'RGB'
|
||||
};
|
||||
// Basis-Pfad des Deployments ohne abschließenden Slash: '/nachsuche' bzw. '' im Root.
|
||||
export const BASE_PATH = stripTrailingSlash(env.BASE_URL) || detectRuntimeBasePath();
|
||||
|
||||
export const USER_TYPE_LABELS = {
|
||||
[USER_TYPES.DF]: 'Drohnenführer',
|
||||
[USER_TYPES.WK]: 'Wärmebildkamera',
|
||||
[USER_TYPES.RGB]: 'RGB-Kamera'
|
||||
};
|
||||
// Vollständige API-Basis. VITE_API_URL überschreibt (z. B. eigene API-Domain).
|
||||
export const API_BASE_URL = (typeof env.VITE_API_URL === 'string' && env.VITE_API_URL.trim())
|
||||
? stripTrailingSlash(env.VITE_API_URL.trim())
|
||||
: BASE_PATH;
|
||||
|
||||
export const RULES = [
|
||||
"Drohnenflug nur mit gültigem Drohnenführerschein (A1/A3 oder A2).",
|
||||
"Informieren Sie den zuständigen Revierinhaber vor jedem Einsatz.",
|
||||
"Halten Sie die Datenschutzbestimmungen beim Einsatz von Wärmebildkameras ein.",
|
||||
"Geben Sie keine Aufnahmen ohne Zustimmung des Revierinhabers weiter.",
|
||||
"Melden Sie Ihren Einsatz unverzüglich an die koordinierende Stelle."
|
||||
];
|
||||
export const ADMIN_PATH = env.VITE_ADMIN_PATH || '/verwaltung';
|
||||
export const RESET_PASSWORD_PATH = '/passwort-zuruecksetzen';
|
||||
|
||||
// Hängt den Deployment-Basispfad vor einen App-Pfad: '/nachsuche/verwaltung'.
|
||||
export const withBase = (appPath) =>
|
||||
`${BASE_PATH}${appPath.startsWith('/') ? appPath : `/${appPath}`}`;
|
||||
|
||||
// Vergleichbare Normalform eines Pfads (ohne abschließenden Slash).
|
||||
export const normalizePath = (pathname) => stripTrailingSlash(pathname) || '/';
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ services:
|
|||
build:
|
||||
context: ./frontend
|
||||
args:
|
||||
- REACT_APP_API_URL=http://localhost:5000
|
||||
- VITE_API_URL=http://localhost:5000
|
||||
container_name: drohnenfuehrer-frontend
|
||||
ports:
|
||||
- "8080:80"
|
||||
|
|
|
|||
|
|
@ -139,7 +139,7 @@ Im Frontend wird die API-URL zur **Build-Zeit** gesetzt. Für Production:
|
|||
frontend:
|
||||
build:
|
||||
args:
|
||||
- REACT_APP_API_URL=https://api.yourdomain.com
|
||||
- VITE_API_URL=https://api.yourdomain.com
|
||||
```
|
||||
|
||||
2. Rebuild erforderlich:
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ services:
|
|||
context: ./frontend
|
||||
args:
|
||||
- PUBLIC_URL=/nachsuche/
|
||||
- REACT_APP_API_URL=${REACT_APP_API_URL:-}
|
||||
- VITE_API_URL=${VITE_API_URL:-}
|
||||
container_name: nachsuche-frontend
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
|
|
|
|||
|
|
@ -183,4 +183,4 @@ frontend/src/
|
|||
- `CORS_ORIGIN`: Erlaubter CORS-Origin
|
||||
|
||||
### Frontend (.env)
|
||||
- `REACT_APP_API_URL`: Backend-API-URL (Standard: http://localhost:5000)
|
||||
- `VITE_API_URL`: Backend-API-URL (Standard: http://localhost:5000)
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
# API Configuration
|
||||
# For local development
|
||||
REACT_APP_API_URL=http://localhost:5000
|
||||
VITE_API_URL=http://localhost:5000
|
||||
|
||||
# For production, use your actual backend URL
|
||||
# REACT_APP_API_URL=https://api.yourdomain.com
|
||||
# VITE_API_URL=https://api.yourdomain.com
|
||||
|
||||
# Admin path (optional, defaults to /verwaltung)
|
||||
# REACT_APP_ADMIN_PATH=/verwaltung
|
||||
# VITE_ADMIN_PATH=/verwaltung
|
||||
|
|
|
|||
|
|
@ -10,8 +10,8 @@ COPY . .
|
|||
|
||||
ARG PUBLIC_URL=/nachsuche/
|
||||
ENV PUBLIC_URL=$PUBLIC_URL
|
||||
ARG REACT_APP_API_URL=
|
||||
ENV REACT_APP_API_URL=$REACT_APP_API_URL
|
||||
ARG VITE_API_URL=
|
||||
ENV VITE_API_URL=$VITE_API_URL
|
||||
# Limit Node.js heap during build to prevent OOM kills
|
||||
ENV NODE_OPTIONS="--max_old_space_size=512"
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="%BASE_URL%favicon.ico" />
|
||||
<link rel="icon" href="%BASE_URL%icons/icon-192.png" />
|
||||
<link rel="apple-touch-icon" href="%BASE_URL%icons/icon-192.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#2d6a2d" />
|
||||
|
|
@ -10,7 +10,9 @@
|
|||
<meta name="apple-mobile-web-app-status-bar-style" content="default" />
|
||||
<meta name="apple-mobile-web-app-title" content="NSS Heidekreis" />
|
||||
<meta name="mobile-web-app-capable" content="yes" />
|
||||
<link rel="manifest" href="/manifest.json" />
|
||||
<!-- Pfad relativ zur Deployment-Basis: ein absolutes "/manifest.json" wuerde
|
||||
das Portal-Manifest laden und die App als Portal installieren. -->
|
||||
<link rel="manifest" href="%BASE_URL%manifest.json" />
|
||||
<meta
|
||||
name="description"
|
||||
content="Nachsuchenstation Heidekreis – Übersicht der Nachsuchenführer"
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -3,28 +3,16 @@
|
|||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@testing-library/jest-dom": "^5.16.4",
|
||||
"@testing-library/react": "^13.3.0",
|
||||
"@testing-library/user-event": "^13.5.0",
|
||||
"axios": "^1.13.5",
|
||||
"leaflet": "^1.9.4",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-leaflet": "^4.2.1",
|
||||
"react-scripts": "5.0.1",
|
||||
"web-vitals": "^2.1.4"
|
||||
"react-leaflet": "^4.2.1"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
"start": "vite",
|
||||
"build": "vite build",
|
||||
"test": "react-scripts test",
|
||||
"eject": "react-scripts eject"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": [
|
||||
"react-app",
|
||||
"react-app/jest"
|
||||
]
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
|
|
|
|||
|
|
@ -1,24 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
||||
<link rel="apple-touch-icon" href="%PUBLIC_URL%/icons/icon-192.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#2d6a2d" />
|
||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="default" />
|
||||
<meta name="apple-mobile-web-app-title" content="NSS Heidekreis" />
|
||||
<meta name="mobile-web-app-capable" content="yes" />
|
||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
||||
<meta
|
||||
name="description"
|
||||
content="Nachsuchenstation Heidekreis – Übersicht der Nachsuchenführer"
|
||||
/>
|
||||
<title>NSS Heidekreis</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -2,11 +2,6 @@
|
|||
"short_name": "NSS Heidekreis",
|
||||
"name": "Nachsuchenstation Heidekreis",
|
||||
"icons": [
|
||||
{
|
||||
"src": "favicon.ico",
|
||||
"sizes": "64x64 32x32 24x24 16x16",
|
||||
"type": "image/x-icon"
|
||||
},
|
||||
{
|
||||
"src": "icons/icon-192.png",
|
||||
"sizes": "192x192",
|
||||
|
|
@ -27,4 +22,4 @@
|
|||
"theme_color": "#2d6a2d",
|
||||
"background_color": "#ffffff",
|
||||
"description": "Nachsuchenstation Heidekreis – Übersicht der Nachsuchenführer"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,13 +11,15 @@ import Admin from './pages/Admin';
|
|||
import HandlerLogin from './components/handler/HandlerLogin';
|
||||
import HandlerDashboard from './components/handler/HandlerDashboard';
|
||||
import InstallBanner from './components/common/InstallBanner';
|
||||
import { ADMIN_PATH, RESET_PASSWORD_PATH, withBase, normalizePath } from './utils/constants';
|
||||
import './App.css';
|
||||
|
||||
function App() {
|
||||
const adminPath = process.env.REACT_APP_ADMIN_PATH || '/verwaltung';
|
||||
const resetPasswordPath = '/passwort-zuruecksetzen';
|
||||
const isAdminRoute = window.location.pathname === adminPath;
|
||||
const isResetPasswordRoute = window.location.pathname === resetPasswordPath;
|
||||
// Die Pfade muessen den Deployment-Basispfad enthalten: produktiv laeuft die
|
||||
// App unter /<app>/, ein Vergleich gegen '/verwaltung' traefe dort nie zu.
|
||||
const currentPath = normalizePath(window.location.pathname);
|
||||
const isAdminRoute = currentPath === normalizePath(withBase(ADMIN_PATH));
|
||||
const isResetPasswordRoute = currentPath === normalizePath(withBase(RESET_PASSWORD_PATH));
|
||||
const [view, setView] = useState('public');
|
||||
const [handlerUser, setHandlerUser] = useState(null);
|
||||
const { isAuthenticated, loading: authLoading, login, logout } = useAuth();
|
||||
|
|
@ -97,7 +99,6 @@ function App() {
|
|||
isAdmin={false}
|
||||
currentView={view}
|
||||
onViewChange={handleViewChange}
|
||||
onHandlerLogin={handleHandlerLogout}
|
||||
/>
|
||||
<main className="app-main">
|
||||
{isAdminRoute || view === 'login' ? (
|
||||
|
|
|
|||
|
|
@ -10,10 +10,14 @@ root.render(
|
|||
</React.StrictMode>
|
||||
);
|
||||
|
||||
// Register service worker
|
||||
// Register service worker.
|
||||
// BASE_URL endet immer auf '/' und ist bei Unterpfad-Deployments '/nachsuche/'.
|
||||
// Ein absolutes '/sw.js' würde stattdessen den Service Worker des Portals
|
||||
// registrieren und dessen Scope übernehmen.
|
||||
if ('serviceWorker' in navigator) {
|
||||
const base = import.meta.env.BASE_URL || '/';
|
||||
window.addEventListener('load', () => {
|
||||
navigator.serviceWorker.register('/sw.js')
|
||||
navigator.serviceWorker.register(`${base}sw.js`, { scope: base })
|
||||
.then(registration => {
|
||||
console.log('SW registered: ', registration);
|
||||
})
|
||||
|
|
@ -21,4 +25,4 @@ if ('serviceWorker' in navigator) {
|
|||
console.log('SW registration failed: ', registrationError);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,10 @@
|
|||
// Use configured API URL when set, otherwise auto-detect common subpath deployment
|
||||
// Der Build läuft über Vite: Umgebungsvariablen kommen aus import.meta.env und
|
||||
// müssen mit VITE_ beginnen. `process.env` existiert im Browser-Bundle NICHT –
|
||||
// ein Zugriff darauf wirft "process is not defined" und die App bleibt weiß.
|
||||
const env = import.meta.env;
|
||||
|
||||
// Fallback, falls ein Build ohne PUBLIC_URL/VITE_BASE_PATH unter einem Unterpfad
|
||||
// ausgeliefert wird: dann steht in BASE_URL nur '/'.
|
||||
const detectRuntimeBasePath = () => {
|
||||
if (typeof window === 'undefined') return '';
|
||||
const path = window.location.pathname || '';
|
||||
|
|
@ -7,32 +13,22 @@ const detectRuntimeBasePath = () => {
|
|||
return match || '';
|
||||
};
|
||||
|
||||
const configuredApiBaseUrl = process.env.REACT_APP_API_URL;
|
||||
export const API_BASE_URL = (typeof configuredApiBaseUrl === 'string' && configuredApiBaseUrl.trim().length > 0)
|
||||
? configuredApiBaseUrl
|
||||
: detectRuntimeBasePath();
|
||||
const stripTrailingSlash = (value) => String(value || '').replace(/\/+$/, '');
|
||||
|
||||
export const USER_TYPES = {
|
||||
HS: 'HS',
|
||||
BGS: 'BGS',
|
||||
SB: 'SB',
|
||||
LAB: 'LAB'
|
||||
};
|
||||
// Basis-Pfad des Deployments ohne abschließenden Slash: '/nachsuche' bzw. '' im Root.
|
||||
export const BASE_PATH = stripTrailingSlash(env.BASE_URL) || detectRuntimeBasePath();
|
||||
|
||||
export const USER_TYPE_LABELS = {
|
||||
[USER_TYPES.HS]: 'Hannoverscher Schweißhund',
|
||||
[USER_TYPES.BGS]: 'Bayerischer Gebirgsschweißhund',
|
||||
[USER_TYPES.SB]: 'Schweißhund (allgemein)',
|
||||
[USER_TYPES.LAB]: 'Labrador'
|
||||
};
|
||||
// Vollständige API-Basis. VITE_API_URL überschreibt (z. B. eigene API-Domain).
|
||||
export const API_BASE_URL = (typeof env.VITE_API_URL === 'string' && env.VITE_API_URL.trim())
|
||||
? stripTrailingSlash(env.VITE_API_URL.trim())
|
||||
: BASE_PATH;
|
||||
|
||||
export const RULES = [
|
||||
"1. Verbrechen Sie den Standort und den Anschuss.",
|
||||
"2. Vertreten Sie keine Pirschzeichen.",
|
||||
"3. Versuchen Sie die Nachsuche möglichst nicht erst mit ungeübten Hunden.",
|
||||
"4. Benachrichtigen Sie unverzüglich den Nachsuchenführer und die evtl. betroffenen Revierinhaber der Nachbarjagdbezirke.",
|
||||
"5. Der Hundeführer gibt den Fangschuss auf das gestellte Stück ab.",
|
||||
"6. Bei der Nachsuche hat der Hundeführer die Stellung eines Jagdleiters.",
|
||||
"7. Der anfordernde Revierinhaber muss die betroffenen Nachbarreviere verständigen.",
|
||||
"Auf Empfehlung der Jägerschaften Soltau und Fallingbostel ist bei Inanspruchnahme eine pauschale Aufwandsentschädigung an den Nachsuchenführer von 50,00 € zu entrichten."
|
||||
];
|
||||
export const ADMIN_PATH = env.VITE_ADMIN_PATH || '/verwaltung';
|
||||
export const RESET_PASSWORD_PATH = '/passwort-zuruecksetzen';
|
||||
|
||||
// Hängt den Deployment-Basispfad vor einen App-Pfad: '/nachsuche/verwaltung'.
|
||||
export const withBase = (appPath) =>
|
||||
`${BASE_PATH}${appPath.startsWith('/') ? appPath : `/${appPath}`}`;
|
||||
|
||||
// Vergleichbare Normalform eines Pfads (ohne abschließenden Slash).
|
||||
export const normalizePath = (pathname) => stripTrailingSlash(pathname) || '/';
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ services:
|
|||
build:
|
||||
context: ./frontend
|
||||
args:
|
||||
- REACT_APP_API_URL=http://localhost:5000
|
||||
- VITE_API_URL=http://localhost:5000
|
||||
container_name: tracking-leaders-frontend
|
||||
ports:
|
||||
- "8080:80"
|
||||
|
|
|
|||
|
|
@ -139,7 +139,7 @@ Im Frontend wird die API-URL zur **Build-Zeit** gesetzt. Für Production:
|
|||
frontend:
|
||||
build:
|
||||
args:
|
||||
- REACT_APP_API_URL=https://api.yourdomain.com
|
||||
- VITE_API_URL=https://api.yourdomain.com
|
||||
```
|
||||
|
||||
2. Rebuild erforderlich:
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ services:
|
|||
context: ./frontend
|
||||
args:
|
||||
- PUBLIC_URL=/stoeberhunde/
|
||||
- REACT_APP_API_URL=${REACT_APP_API_URL:-}
|
||||
- VITE_API_URL=${VITE_API_URL:-}
|
||||
container_name: stoeberhunde-frontend
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
|
|
|
|||
|
|
@ -183,4 +183,4 @@ frontend/src/
|
|||
- `CORS_ORIGIN`: Erlaubter CORS-Origin
|
||||
|
||||
### Frontend (.env)
|
||||
- `REACT_APP_API_URL`: Backend-API-URL (Standard: http://localhost:5000)
|
||||
- `VITE_API_URL`: Backend-API-URL (Standard: http://localhost:5000)
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
# API Configuration
|
||||
# For local development
|
||||
REACT_APP_API_URL=http://localhost:5000
|
||||
VITE_API_URL=http://localhost:5000
|
||||
|
||||
# For production, use your actual backend URL
|
||||
# REACT_APP_API_URL=https://api.yourdomain.com
|
||||
# VITE_API_URL=https://api.yourdomain.com
|
||||
|
||||
# Admin path (optional, defaults to /verwaltung)
|
||||
# REACT_APP_ADMIN_PATH=/verwaltung
|
||||
# VITE_ADMIN_PATH=/verwaltung
|
||||
|
|
|
|||
|
|
@ -10,8 +10,8 @@ COPY . .
|
|||
|
||||
ARG PUBLIC_URL=/stoeberhunde/
|
||||
ENV PUBLIC_URL=$PUBLIC_URL
|
||||
ARG REACT_APP_API_URL=
|
||||
ENV REACT_APP_API_URL=$REACT_APP_API_URL
|
||||
ARG VITE_API_URL=
|
||||
ENV VITE_API_URL=$VITE_API_URL
|
||||
# Limit Node.js heap during build to prevent OOM kills
|
||||
ENV NODE_OPTIONS="--max_old_space_size=512"
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="%BASE_URL%favicon.ico" />
|
||||
<link rel="icon" href="%BASE_URL%icons/icon-192.png" />
|
||||
<link rel="apple-touch-icon" href="%BASE_URL%icons/icon-192.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#2d6a2d" />
|
||||
|
|
@ -10,7 +10,9 @@
|
|||
<meta name="apple-mobile-web-app-status-bar-style" content="default" />
|
||||
<meta name="apple-mobile-web-app-title" content="Stöberhunde" />
|
||||
<meta name="mobile-web-app-capable" content="yes" />
|
||||
<link rel="manifest" href="/manifest.json" />
|
||||
<!-- Pfad relativ zur Deployment-Basis: ein absolutes "/manifest.json" wuerde
|
||||
das Portal-Manifest laden und die App als Portal installieren. -->
|
||||
<link rel="manifest" href="%BASE_URL%manifest.json" />
|
||||
<meta
|
||||
name="description"
|
||||
content="Stöberhunde Heidekreis – Übersicht der Stöberhundleiter"
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -3,28 +3,16 @@
|
|||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@testing-library/jest-dom": "^5.16.4",
|
||||
"@testing-library/react": "^13.3.0",
|
||||
"@testing-library/user-event": "^13.5.0",
|
||||
"axios": "^1.13.5",
|
||||
"leaflet": "^1.9.4",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-leaflet": "^4.2.1",
|
||||
"react-scripts": "5.0.1",
|
||||
"web-vitals": "^2.1.4"
|
||||
"react-leaflet": "^4.2.1"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
"start": "vite",
|
||||
"build": "vite build",
|
||||
"test": "react-scripts test",
|
||||
"eject": "react-scripts eject"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": [
|
||||
"react-app",
|
||||
"react-app/jest"
|
||||
]
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
|
|
|
|||
|
|
@ -1,24 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
||||
<link rel="apple-touch-icon" href="%PUBLIC_URL%/icons/icon-192.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#2d6a2d" />
|
||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="default" />
|
||||
<meta name="apple-mobile-web-app-title" content="Stöberhunde Heidekreis" />
|
||||
<meta name="mobile-web-app-capable" content="yes" />
|
||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
||||
<meta
|
||||
name="description"
|
||||
content="Stöberhunde Heidekreis – Übersicht der Stöberhundeführer"
|
||||
/>
|
||||
<title>Stöberhunde Heidekreis</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -2,11 +2,6 @@
|
|||
"short_name": "Stöberhunde",
|
||||
"name": "Stöberhunde Heidekreis",
|
||||
"icons": [
|
||||
{
|
||||
"src": "favicon.ico",
|
||||
"sizes": "64x64 32x32 24x24 16x16",
|
||||
"type": "image/x-icon"
|
||||
},
|
||||
{
|
||||
"src": "icons/icon-192.png",
|
||||
"sizes": "192x192",
|
||||
|
|
@ -27,4 +22,4 @@
|
|||
"theme_color": "#2d6a2d",
|
||||
"background_color": "#ffffff",
|
||||
"description": "Stöberhunde Heidekreis – Übersicht der Stöberhundleiter"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,13 +11,15 @@ import Admin from './pages/Admin';
|
|||
import StoeberhundefuehrerLogin from './components/stoeberhundefuehrer/StoeberhundefuehrerLogin';
|
||||
import StoeberhundefuehrerDashboard from './components/stoeberhundefuehrer/StoeberhundefuehrerDashboard';
|
||||
import InstallBanner from './components/common/InstallBanner';
|
||||
import { ADMIN_PATH, RESET_PASSWORD_PATH, withBase, normalizePath } from './utils/constants';
|
||||
import './App.css';
|
||||
|
||||
function App() {
|
||||
const adminPath = process.env.REACT_APP_ADMIN_PATH || '/verwaltung';
|
||||
const resetPasswordPath = '/passwort-zuruecksetzen';
|
||||
const isAdminRoute = window.location.pathname === adminPath;
|
||||
const isResetPasswordRoute = window.location.pathname === resetPasswordPath;
|
||||
// Die Pfade muessen den Deployment-Basispfad enthalten: produktiv laeuft die
|
||||
// App unter /<app>/, ein Vergleich gegen '/verwaltung' traefe dort nie zu.
|
||||
const currentPath = normalizePath(window.location.pathname);
|
||||
const isAdminRoute = currentPath === normalizePath(withBase(ADMIN_PATH));
|
||||
const isResetPasswordRoute = currentPath === normalizePath(withBase(RESET_PASSWORD_PATH));
|
||||
const [view, setView] = useState('public');
|
||||
const [stoeberhundefuehrerUser, setStoeberhundefuehrerUser] = useState(null);
|
||||
const { isAuthenticated, loading: authLoading, login, logout } = useAuth();
|
||||
|
|
@ -97,7 +99,6 @@ function App() {
|
|||
isAdmin={false}
|
||||
currentView={view}
|
||||
onViewChange={handleViewChange}
|
||||
onStoeberhundefuehrerLogin={handleStoeberhundefuehrerLogout}
|
||||
/>
|
||||
<main className="app-main">
|
||||
{isAdminRoute || view === 'login' ? (
|
||||
|
|
|
|||
|
|
@ -10,10 +10,14 @@ root.render(
|
|||
</React.StrictMode>
|
||||
);
|
||||
|
||||
// Register service worker
|
||||
// Register service worker.
|
||||
// BASE_URL endet immer auf '/' und ist bei Unterpfad-Deployments '/nachsuche/'.
|
||||
// Ein absolutes '/sw.js' würde stattdessen den Service Worker des Portals
|
||||
// registrieren und dessen Scope übernehmen.
|
||||
if ('serviceWorker' in navigator) {
|
||||
const base = import.meta.env.BASE_URL || '/';
|
||||
window.addEventListener('load', () => {
|
||||
navigator.serviceWorker.register('./sw.js')
|
||||
navigator.serviceWorker.register(`${base}sw.js`, { scope: base })
|
||||
.then(registration => {
|
||||
console.log('SW registered: ', registration);
|
||||
})
|
||||
|
|
@ -21,4 +25,4 @@ if ('serviceWorker' in navigator) {
|
|||
console.log('SW registration failed: ', registrationError);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,34 +1,34 @@
|
|||
// Use configured API URL when set, otherwise auto-detect common subpath deployment (/stoeberhunde)
|
||||
// Der Build läuft über Vite: Umgebungsvariablen kommen aus import.meta.env und
|
||||
// müssen mit VITE_ beginnen. `process.env` existiert im Browser-Bundle NICHT –
|
||||
// ein Zugriff darauf wirft "process is not defined" und die App bleibt weiß.
|
||||
const env = import.meta.env;
|
||||
|
||||
// Fallback, falls ein Build ohne PUBLIC_URL/VITE_BASE_PATH unter einem Unterpfad
|
||||
// ausgeliefert wird: dann steht in BASE_URL nur '/'.
|
||||
const detectRuntimeBasePath = () => {
|
||||
if (typeof window === 'undefined') return '';
|
||||
const path = window.location.pathname || '';
|
||||
if (path === '/stoeberhunde' || path.startsWith('/stoeberhunde/')) {
|
||||
return '/stoeberhunde';
|
||||
}
|
||||
return '';
|
||||
const knownBasePaths = ['/nachsuche', '/drohnenfuehrer', '/stoeberhunde'];
|
||||
const match = knownBasePaths.find(basePath => path === basePath || path.startsWith(`${basePath}/`));
|
||||
return match || '';
|
||||
};
|
||||
|
||||
const configuredApiBaseUrl = process.env.REACT_APP_API_URL;
|
||||
export const API_BASE_URL = (typeof configuredApiBaseUrl === 'string' && configuredApiBaseUrl.trim().length > 0)
|
||||
? configuredApiBaseUrl
|
||||
: detectRuntimeBasePath();
|
||||
const stripTrailingSlash = (value) => String(value || '').replace(/\/+$/, '');
|
||||
|
||||
export const USER_TYPES = {
|
||||
SH: 'SH',
|
||||
BSH: 'BSH',
|
||||
SHK: 'SHK'
|
||||
};
|
||||
// Basis-Pfad des Deployments ohne abschließenden Slash: '/nachsuche' bzw. '' im Root.
|
||||
export const BASE_PATH = stripTrailingSlash(env.BASE_URL) || detectRuntimeBasePath();
|
||||
|
||||
export const USER_TYPE_LABELS = {
|
||||
[USER_TYPES.SH]: 'Stöberhundleiter',
|
||||
[USER_TYPES.BSH]: 'Begleithund',
|
||||
[USER_TYPES.SHK]: 'Stöberhund kombiniert'
|
||||
};
|
||||
// Vollständige API-Basis. VITE_API_URL überschreibt (z. B. eigene API-Domain).
|
||||
export const API_BASE_URL = (typeof env.VITE_API_URL === 'string' && env.VITE_API_URL.trim())
|
||||
? stripTrailingSlash(env.VITE_API_URL.trim())
|
||||
: BASE_PATH;
|
||||
|
||||
export const RULES = [
|
||||
"Informieren Sie den Revierinhaber vor jedem Einsatz.",
|
||||
"Der Stöberhundleiter ist verantwortlich für den sicheren Einsatz seines Hundes.",
|
||||
"Halten Sie die vereinbarten Gebietsabgrenzungen ein.",
|
||||
"Melden Sie das Ergebnis unverzüglich an den Revierinhaber.",
|
||||
"Bei Inanspruchnahme ist eine pauschale Aufwandsentschädigung zu entrichten."
|
||||
];
|
||||
export const ADMIN_PATH = env.VITE_ADMIN_PATH || '/verwaltung';
|
||||
export const RESET_PASSWORD_PATH = '/passwort-zuruecksetzen';
|
||||
|
||||
// Hängt den Deployment-Basispfad vor einen App-Pfad: '/nachsuche/verwaltung'.
|
||||
export const withBase = (appPath) =>
|
||||
`${BASE_PATH}${appPath.startsWith('/') ? appPath : `/${appPath}`}`;
|
||||
|
||||
// Vergleichbare Normalform eines Pfads (ohne abschließenden Slash).
|
||||
export const normalizePath = (pathname) => stripTrailingSlash(pathname) || '/';
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ services:
|
|||
build:
|
||||
context: ./frontend
|
||||
args:
|
||||
- REACT_APP_API_URL=http://localhost:5000
|
||||
- VITE_API_URL=http://localhost:5000
|
||||
container_name: stoeberhunde-frontend
|
||||
ports:
|
||||
- "8080:80"
|
||||
|
|
|
|||
Loading…
Reference in New Issue