diff --git a/.env.example b/.env.example
index b360eb5..1cae9c2 100644
--- a/.env.example
+++ b/.env.example
@@ -7,8 +7,8 @@
# ═══ Server ═══
PORT=8787
# Basis-URL, unter der RAPPORT-HOST erreichbar ist (für Stripe-Redirects).
-# Lokal: http://localhost:5273 · Prod: https://host.rapport.studio
-PUBLIC_BASE_URL=http://localhost:5273
+# Lokal: http://localhost:8787 · Prod: https://host.rapport.studio
+PUBLIC_BASE_URL=http://localhost:8787
# ═══ Auth ═══
# JWT-Signatur-Secret für HOST-Kundenkonten. openssl rand -hex 32
diff --git a/package.json b/package.json
index 29b19fa..b5cf6fd 100644
--- a/package.json
+++ b/package.json
@@ -3,21 +3,12 @@
"version": "0.1.0",
"type": "module",
"private": true,
- "description": "RAPPORT-HOST — kommerzielle Hosting-/Abo-Plattform für Rapport-Instanzen (proprietär)",
+ "description": "RAPPORT-HOST — Backend für die kommerzielle Hosting-/Abo-Plattform (proprietär). Frontend lebt in RAPPORT-WEBSITE.",
"scripts": {
- "dev": "vite",
- "build": "vite build",
- "preview": "vite preview",
"server": "node --watch server/index.js",
- "server:migrate": "node server/migrate.js",
- "dev:all": "echo 'In zwei Terminals: npm run server und npm run dev'"
- },
- "dependencies": {
- "react": "^18.3.1",
- "react-dom": "^18.3.1"
- },
- "devDependencies": {
- "@vitejs/plugin-react": "^4.3.1",
- "vite": "^5.4.0"
+ "start": "node server/index.js",
+ "migrate": "node server/migrate.js",
+ "build:website": "cd ../RAPPORT-WEBSITE && hugo --quiet --baseURL / --destination public",
+ "dev": "npm run build:website && npm run server"
}
}
diff --git a/public/fonts/Krungthep.ttf b/public/fonts/Krungthep.ttf
deleted file mode 100644
index 9415853..0000000
Binary files a/public/fonts/Krungthep.ttf and /dev/null differ
diff --git a/server/env.js b/server/env.js
index e4099cd..49eb4fb 100644
--- a/server/env.js
+++ b/server/env.js
@@ -23,9 +23,12 @@ const e = process.env;
export const env = {
port: parseInt(e.PORT || "8787", 10),
- publicBaseUrl: (e.PUBLIC_BASE_URL || "http://localhost:5273").replace(/\/+$/, ""),
+ publicBaseUrl: (e.PUBLIC_BASE_URL || "http://localhost:8787").replace(/\/+$/, ""),
jwtSecret: e.JWT_SECRET || "dev-insecure-secret-change-me",
databaseUrl: e.DATABASE_URL || "postgres://rapport_host:rapport_host@localhost:55432/rapport_host",
+ // Gebautes public/ der RAPPORT-WEBSITE (Hugo). Default: Schwester-Repo lokal.
+ websitePublicDir: e.WEBSITE_PUBLIC_DIR ||
+ new URL("../../RAPPORT-WEBSITE/public", import.meta.url).pathname,
stripe: {
secretKey: e.STRIPE_SECRET_KEY || "",
diff --git a/server/index.js b/server/index.js
index a03eb25..8a63bfb 100644
--- a/server/index.js
+++ b/server/index.js
@@ -21,12 +21,20 @@ app.use("/api/auth", authRouter);
app.use("/api/billing", billingRouter);
app.use("/api/account", accountRouter);
-// In Produktion liefert dasselbe Backend das gebaute Frontend aus (dist/).
-const dist = path.resolve(__dirname, "..", "dist");
-app.use(express.static(dist));
+// ── Statische Auslieferung der RAPPORT-WEBSITE (Hugo-Build) ──────────────────
+// Die komplette Frontend-Oberfläche (Marketing + Hosting + Login/Konto als
+// Vanilla-JS-Seiten) kommt aus dem AGPL-Repo RAPPORT-WEBSITE. Dieses Backend
+// liefert dessen gebautes public/ aus und stellt darunter /api bereit.
+// Saubere Trennung: die Website enthält keinen Backend-Code, nur fetch('/api').
+// Pfad per WEBSITE_PUBLIC_DIR überschreibbar (Default: Schwester-Repo).
+const websitePublic = env.websitePublicDir;
+
+app.use(express.static(websitePublic));
app.get("*", (req, res, next) => {
if (req.path.startsWith("/api/")) return next();
- res.sendFile(path.join(dist, "index.html"), (err) => err && next());
+ // Hugo erzeugt pro Seite eine eigene index.html → Clean-URLs funktionieren
+ // direkt über express.static. Unbekannte Pfade → 404-Seite.
+ res.status(404).sendFile(path.join(websitePublic, "404.html"), (err) => err && next());
});
// Express-Fehlerhandler: fängt synchron geworfene Fehler aus Routen → 500
diff --git a/server/routes/billing.js b/server/routes/billing.js
index 0e6ff44..448d174 100644
--- a/server/routes/billing.js
+++ b/server/routes/billing.js
@@ -61,7 +61,7 @@ billingRouter.post("/checkout", requireAuth, async (req, res) => {
status: "active",
periodEnd: new Date(Date.now() + 30 * 864e5),
});
- return res.json({ mock: true, url: `${env.publicBaseUrl}/dashboard?provisioned=1` });
+ return res.json({ mock: true, url: `${env.publicBaseUrl}/konto/?provisioned=1` });
}
// ── Stripe-Checkout-Session ──────────────────────────────────────────────
@@ -72,8 +72,8 @@ billingRouter.post("/checkout", requireAuth, async (req, res) => {
customer_email: req.account.email,
client_reference_id: req.account.id,
metadata: { accountId: req.account.id, planId: plan.id },
- success_url: `${env.publicBaseUrl}/dashboard?provisioned=1`,
- cancel_url: `${env.publicBaseUrl}/plans?canceled=1`,
+ success_url: `${env.publicBaseUrl}/konto/?provisioned=1`,
+ cancel_url: `${env.publicBaseUrl}/hosting-preise/?canceled=1`,
});
res.json({ url: session.url });
} catch (err) {
diff --git a/src/App.jsx b/src/App.jsx
index 99d3bb0..09cae2e 100644
--- a/src/App.jsx
+++ b/src/App.jsx
@@ -1,40 +1,53 @@
import React, { useState, useEffect, useCallback } from "react";
import { auth } from "./api.js";
-import Landing from "./views/Landing.jsx";
import Register from "./views/Register.jsx";
import Login from "./views/Login.jsx";
import Plans from "./views/Plans.jsx";
import Dashboard from "./views/Dashboard.jsx";
-// Minimaler Pfad-Router (kein react-router nötig für 5 Seiten).
-function usePath() {
- const [path, setPath] = useState(window.location.pathname);
+// Die App wird unter /app/ ausgeliefert (Hugo-Marketing liegt unter /).
+// Der Mini-Router rechnet intern in App-relativen Pfaden ("/login"),
+// schreibt aber immer mit BASE-Präfix in die URL ("/app/login").
+const BASE = "/app";
+// Interne App-Routen. Alles andere, was navigate() bekommt (z.B. "/preise/"),
+// ist ein Marketing-Link und wird als echter Seitenwechsel behandelt.
+const ROUTES = ["/login", "/register", "/plans", "/dashboard"];
+
+function toRel(pathname) {
+ const p = pathname.startsWith(BASE) ? pathname.slice(BASE.length) : pathname;
+ const clean = (p.replace(/\/$/, "") || "/");
+ return ROUTES.includes(clean) ? clean : "/";
+}
+
+function useRoute() {
+ const [route, setRoute] = useState(toRel(window.location.pathname));
useEffect(() => {
- const onPop = () => setPath(window.location.pathname);
+ const onPop = () => setRoute(toRel(window.location.pathname));
window.addEventListener("popstate", onPop);
return () => window.removeEventListener("popstate", onPop);
}, []);
const navigate = useCallback((to) => {
- window.history.pushState({}, "", to);
- setPath(to);
+ if (!ROUTES.includes(to)) { window.location.href = to; return; } // Marketing/extern
+ window.history.pushState({}, "", BASE + to);
+ setRoute(to);
window.scrollTo(0, 0);
}, []);
- return [path, navigate];
+ return [route, navigate];
}
export default function App() {
- const [path, navigate] = usePath();
+ const [route, navigate] = useRoute();
const loggedIn = auth.isLoggedIn;
- // Geschützte Seiten ohne Login → Login. Login/Register mit Login → Dashboard.
+ // Geschützte/öffentliche Weiterleitungen. "/" (App-Einstieg) → je nach Login.
useEffect(() => {
- if (path === "/dashboard" && !loggedIn) navigate("/login");
- if ((path === "/login" || path === "/register") && loggedIn) navigate("/dashboard");
- }, [path, loggedIn, navigate]);
+ if (route === "/dashboard" && !loggedIn) navigate("/login");
+ else if ((route === "/login" || route === "/register" || route === "/") && loggedIn) navigate("/dashboard");
+ else if (route === "/" && !loggedIn) navigate("/login");
+ }, [route, loggedIn, navigate]);
- if (path === "/register") return ;
- if (path === "/login") return ;
- if (path === "/plans") return ;
- if (path === "/dashboard") return ;
- return ;
+ if (route === "/register") return ;
+ if (route === "/plans") return ;
+ if (route === "/dashboard") return ;
+ return ;
}
diff --git a/vite.config.js b/vite.config.js
index dbf5a6c..b8154ad 100644
--- a/vite.config.js
+++ b/vite.config.js
@@ -1,9 +1,11 @@
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
-// Frontend läuft auf :5273, Backend auf :8787 — /api wird durchgeproxt,
-// damit im Dev kein CORS nötig ist und Prod-URLs identisch bleiben (/api/...).
+// Die React-App wird in Produktion unter /app/ ausgeliefert (base), die
+// Hugo-Marketing-Site liegt unter /. Im Dev läuft Vite auf :5273 und proxt
+// /api → Backend, damit kein CORS nötig ist und die URLs identisch bleiben.
export default defineConfig({
+ base: "/app/",
plugins: [react()],
server: {
port: 5273,