feat: Implement Next.js middleware for subdomain-based tenant routing and authentication, create the admin application's main page, and add Google site verification.

This commit is contained in:
Timo Knuth 2026-03-02 23:01:21 +01:00
parent 9d71c16883
commit 873c5e53af
4 changed files with 155 additions and 9 deletions

View File

@ -4,7 +4,7 @@ import { useState, useEffect } from 'react'
import Link from 'next/link'
import Image from 'next/image'
import { Syne } from 'next/font/google'
import { ArrowRight, ArrowUpRight, Sun, Moon } from 'lucide-react'
import { ArrowRight, ArrowUpRight, Sun, Moon, Menu, X } from 'lucide-react'
const syne = Syne({ subsets: ['latin'], weight: ['400', '500', '600', '700', '800'] })
@ -57,6 +57,7 @@ export default function RootPage() {
const [openFaq, setOpenFaq] = useState<number | null>(null);
const [cookieConsent, setCookieConsent] = useState<CookieConsentState>('loading')
const [showMailFallback, setShowMailFallback] = useState(false)
const [mobileMenuOpen, setMobileMenuOpen] = useState(false)
useEffect(() => {
setMounted(true);
@ -625,10 +626,67 @@ export default function RootPage() {
color: var(--gold); font-weight: 500; margin-bottom: 20px;
}
/* Mobile Navigation */
.nav-mobile-toggle { display: none; }
@media (max-width: 767px) {
.nav-mobile-toggle { display: flex; align-items: center; cursor: pointer; }
.nav-menu-btn {
background: none;
border: none;
cursor: pointer;
color: var(--ink);
padding: 8px;
display: flex;
align-items: center;
justify-content: center;
}
.nav-links {
display: none;
position: fixed;
top: 64px;
left: 0;
right: 0;
background: var(--bg);
border-bottom: 1px solid var(--ink-faint);
padding: 24px 32px;
flex-direction: column;
gap: 12px;
z-index: 40;
}
.nav-links-mobile-open { display: flex !important; }
.nav-link { display: block; }
}
/* Improved Mobile Responsiveness */
@media (max-width: 639px) {
.nav-links .nav-link { display: none; }
.stat { border-right: none; padding-right: 0; }
.stat:nth-child(odd) { border-right: 1px solid var(--ink-faint); padding-right: 16px; }
.nav-inner { padding: 0 16px; }
.hero { padding: 120px 16px 60px; }
.hero-h1 { font-size: clamp(2rem, 6vw, 3.25rem); }
.hero-body { gap: 24px; }
.hero-image-wrapper { aspect-ratio: 1; }
.stats { grid-template-columns: repeat(2, 1fr); margin-top: 60px; }
.stat { padding: 20px 16px; border-right: none; border-bottom: 1px solid var(--ink-faint); }
.stat:nth-child(2n) { border-right: 1px solid var(--ink-faint); }
.stat:nth-last-child(-n+2) { border-bottom: none; }
.challenges-section { padding: 60px 0; }
.challenges-inner { padding: 0 16px; }
.challenges-grid { gap: 20px; }
.challenge-card { padding: 20px; }
.features { padding: 60px 0; }
.features-inner { padding: 0 16px; gap: 40px; }
.feature-item { padding: 20px 16px; gap: 16px; flex-direction: column; }
.feature-num { font-size: 2rem; min-width: auto; }
.comparison-section { padding: 60px 0; }
.comparison-inner { padding: 0 16px; }
.comp-card { padding: 20px; }
.cta-section { padding: 60px 0 80px; }
.cta-inner { padding: 0 16px; }
.aeo-section { padding: 60px 0; }
.aeo-inner { padding: 0 16px; }
.faq-section { padding: 60px 0; }
.faq-inner { padding: 0 16px; }
.footer { padding: 24px 0; }
.footer-inner { padding: 0 16px; }
}
`}</style>
@ -639,8 +697,17 @@ export default function RootPage() {
<div className="logo">
Innungs<span className="logo-accent">App</span> <span className="logo-pro">PRO</span>
</div>
<div className="nav-links">
<a href="#leistungen" className="nav-link">Leistungen</a>
<div className="nav-mobile-toggle">
<button
className="nav-menu-btn"
onClick={() => setMobileMenuOpen(!mobileMenuOpen)}
aria-label="Navigation toggle"
>
{mobileMenuOpen ? <X size={24} /> : <Menu size={24} />}
</button>
</div>
<div className={`nav-links ${mobileMenuOpen ? 'nav-links-mobile-open' : ''}`}>
<a href="#leistungen" className="nav-link" onClick={() => setMobileMenuOpen(false)}>Leistungen</a>
<button
onClick={toggleTheme}
@ -651,11 +718,14 @@ export default function RootPage() {
{theme === 'theme-dark' ? <Sun size={18} /> : <Moon size={18} />}
</button>
<Link href="/login" className="nav-link">Login</Link>
<Link href="/login" className="nav-link" onClick={() => setMobileMenuOpen(false)}>Login</Link>
<a
href={CONTACT_WEBMAIL_HREF}
className="btn-primary"
onClick={() => handleContactCtaClick('nav')}
onClick={() => {
handleContactCtaClick('nav')
setMobileMenuOpen(false)
}}
target="_blank"
rel="noreferrer"
>

View File

@ -13,7 +13,11 @@ const PUBLIC_PREFIXES = [
const PUBLIC_EXACT_PATHS = ['/']
// Reserved subdomains that shouldn't be treated as tenant slugs
const RESERVED_SUBDOMAINS = ['www', 'app', 'admin', 'localhost', 'superadmin', 'api']
const RESERVED_SUBDOMAINS = [
'www', 'app', 'admin', 'localhost', 'superadmin', 'api',
'logo.png', 'favicon.ico', 'robots.txt', 'sitemap.xml',
'apple-touch-icon', 'android-chrome', 'manifest'
]
export function middleware(request: NextRequest) {
const url = request.nextUrl

View File

@ -0,0 +1 @@
google-site-verification: googleccd5315437d68a49.html

View File

@ -0,0 +1,71 @@
> build
> turbo build
Turborepo did not find the correct binary for your platform.
We will attempt to install it now.
Installation has succeeded.
Attention:
Turborepo now collects completely anonymous telemetry regarding usage.
This information is used to shape the Turborepo roadmap and prioritize features.
You can learn more, including how to opt-out if you'd not like to participate in this anonymous program, by visiting the following URL:
https://turborepo.dev/docs/telemetry
• turbo 2.8.10
• Packages in scope: @innungsapp/admin, @innungsapp/mobile, @innungsapp/shared
• Running build in 3 packages
• Remote caching disabled
@innungsapp/admin:build: cache miss, executing 680b5ccc77f9c5aa
@innungsapp/admin:build:
@innungsapp/admin:build: > @innungsapp/admin@0.1.0 build /mnt/c/Users/a931627/Documents/stadtwerke-saas-analysis/innungsapp/apps/admin
@innungsapp/admin:build: > next build
@innungsapp/admin:build:
@innungsapp/admin:build: Downloading swc package @next/swc-linux-x64-gnu... to /home/tknuth/.cache/next-swc
@innungsapp/admin:build: Downloading swc package @next/swc-linux-x64-musl... to /home/tknuth/.cache/next-swc
@innungsapp/admin:build: ▲ Next.js 15.3.4
@innungsapp/admin:build: - Environments: .env.local
@innungsapp/admin:build:
@innungsapp/admin:build: Creating an optimized production build ...
@innungsapp/admin:build: glob error [Error: EIO: i/o error, scandir '/mnt/c/Users/a931627/Documents/stadtwerke-saas-analysis/innungsapp/apps/admin/node_modules/next/dist/esm/client/dev/error-overlay'] {
@innungsapp/admin:build: errno: -5,
@innungsapp/admin:build: code: 'EIO',
@innungsapp/admin:build: syscall: 'scandir',
@innungsapp/admin:build: path: '/mnt/c/Users/a931627/Documents/stadtwerke-saas-analysis/innungsapp/apps/admin/node_modules/next/dist/esm/client/dev/error-overlay'
@innungsapp/admin:build: }
@innungsapp/admin:build: <w> [webpack.cache.PackFileCacheStrategy] Restoring pack from /mnt/c/Users/a931627/Documents/stadtwerke-saas-analysis/innungsapp/apps/admin/.next/cache/webpack/server-production.pack failed: Error: No such label 'restore cache container' for WebpackLogger.timeEnd()
@innungsapp/admin:build: Failed to compile.
@innungsapp/admin:build:
@innungsapp/admin:build: Error: EIO: i/o error, scandir '/mnt/c/Users/a931627/Documents/stadtwerke-saas-analysis/innungsapp/apps/admin/node_modules/next/dist/esm/client/dev/error-overlay'
@innungsapp/admin:build:
@innungsapp/admin:build: HookWebpackError: Cannot read properties of undefined (reading 'server')
@innungsapp/admin:build: at makeWebpackError (/mnt/c/Users/a931627/Documents/stadtwerke-saas-analysis/innungsapp/node_modules/.pnpm/next@15.3.4_babel-plugin-react-compiler@1.0.0_react-dom@19.0.0_react@19.0.0__react@19.0.0/node_modules/next/dist/compiled/webpack/bundle5.js:29:315788)
@innungsapp/admin:build: at /mnt/c/Users/a931627/Documents/stadtwerke-saas-analysis/innungsapp/node_modules/.pnpm/next@15.3.4_babel-plugin-react-compiler@1.0.0_react-dom@19.0.0_react@19.0.0__react@19.0.0/node_modules/next/dist/compiled/webpack/bundle5.js:29:106487
@innungsapp/admin:build: at eval (eval at create (/mnt/c/Users/a931627/Documents/stadtwerke-saas-analysis/innungsapp/node_modules/.pnpm/next@15.3.4_babel-plugin-react-compiler@1.0.0_react-dom@19.0.0_react@19.0.0__react@19.0.0/node_modules/next/dist/compiled/webpack/bundle5.js:14:9224), <anonymous>:20:1)
@innungsapp/admin:build: -- inner error --
@innungsapp/admin:build: TypeError: Cannot read properties of undefined (reading 'server')
@innungsapp/admin:build: at FlightClientEntryPlugin.createActionAssets (/mnt/c/Users/a931627/Documents/stadtwerke-saas-analysis/innungsapp/node_modules/.pnpm/next@15.3.4_babel-plugin-react-compiler@1.0.0_react-dom@19.0.0_react@19.0.0__react@19.0.0/node_modules/next/dist/build/webpack/plugins/flight-client-entry-plugin.js:665:68)
@innungsapp/admin:build: at /mnt/c/Users/a931627/Documents/stadtwerke-saas-analysis/innungsapp/node_modules/.pnpm/next@15.3.4_babel-plugin-react-compiler@1.0.0_react-dom@19.0.0_react@19.0.0__react@19.0.0/node_modules/next/dist/build/webpack/plugins/flight-client-entry-plugin.js:170:25
@innungsapp/admin:build: at fn (/mnt/c/Users/a931627/Documents/stadtwerke-saas-analysis/innungsapp/node_modules/.pnpm/next@15.3.4_babel-plugin-react-compiler@1.0.0_react-dom@19.0.0_react@19.0.0__react@19.0.0/node_modules/next/dist/compiled/webpack/bundle5.js:29:70201)
@innungsapp/admin:build: at _next7 (eval at create (/mnt/c/Users/a931627/Documents/stadtwerke-saas-analysis/innungsapp/node_modules/.pnpm/next@15.3.4_babel-plugin-react-compiler@1.0.0_react-dom@19.0.0_react@19.0.0__react@19.0.0/node_modules/next/dist/compiled/webpack/bundle5.js:14:9224), <anonymous>:12:17)
@innungsapp/admin:build: at eval (eval at create (/mnt/c/Users/a931627/Documents/stadtwerke-saas-analysis/innungsapp/node_modules/.pnpm/next@15.3.4_babel-plugin-react-compiler@1.0.0_react-dom@19.0.0_react@19.0.0__react@19.0.0/node_modules/next/dist/compiled/webpack/bundle5.js:14:9224), <anonymous>:31:1)
@innungsapp/admin:build: caused by plugins in Compilation.hooks.processAssets
@innungsapp/admin:build: TypeError: Cannot read properties of undefined (reading 'server')
@innungsapp/admin:build: at FlightClientEntryPlugin.createActionAssets (/mnt/c/Users/a931627/Documents/stadtwerke-saas-analysis/innungsapp/node_modules/.pnpm/next@15.3.4_babel-plugin-react-compiler@1.0.0_react-dom@19.0.0_react@19.0.0__react@19.0.0/node_modules/next/dist/build/webpack/plugins/flight-client-entry-plugin.js:665:68)
@innungsapp/admin:build: at /mnt/c/Users/a931627/Documents/stadtwerke-saas-analysis/innungsapp/node_modules/.pnpm/next@15.3.4_babel-plugin-react-compiler@1.0.0_react-dom@19.0.0_react@19.0.0__react@19.0.0/node_modules/next/dist/build/webpack/plugins/flight-client-entry-plugin.js:170:25
@innungsapp/admin:build: at fn (/mnt/c/Users/a931627/Documents/stadtwerke-saas-analysis/innungsapp/node_modules/.pnpm/next@15.3.4_babel-plugin-react-compiler@1.0.0_react-dom@19.0.0_react@19.0.0__react@19.0.0/node_modules/next/dist/compiled/webpack/bundle5.js:29:70201)
@innungsapp/admin:build: at _next7 (eval at create (/mnt/c/Users/a931627/Documents/stadtwerke-saas-analysis/innungsapp/node_modules/.pnpm/next@15.3.4_babel-plugin-react-compiler@1.0.0_react-dom@19.0.0_react@19.0.0__react@19.0.0/node_modules/next/dist/compiled/webpack/bundle5.js:14:9224), <anonymous>:12:17)
@innungsapp/admin:build: at eval (eval at create (/mnt/c/Users/a931627/Documents/stadtwerke-saas-analysis/innungsapp/node_modules/.pnpm/next@15.3.4_babel-plugin-react-compiler@1.0.0_react-dom@19.0.0_react@19.0.0__react@19.0.0/node_modules/next/dist/compiled/webpack/bundle5.js:14:9224), <anonymous>:31:1)
@innungsapp/admin:build:
@innungsapp/admin:build:
@innungsapp/admin:build: > Build failed because of webpack errors
@innungsapp/admin:build: ELIFECYCLE Command failed with exit code 1.
@innungsapp/admin:build: ERROR: command finished with error: command (/mnt/c/Users/a931627/Documents/stadtwerke-saas-analysis/innungsapp/apps/admin) /mnt/c/Users/a931627/AppData/Roaming/npm/pnpm run build exited (1)
@innungsapp/admin#build: command (/mnt/c/Users/a931627/Documents/stadtwerke-saas-analysis/innungsapp/apps/admin) /mnt/c/Users/a931627/AppData/Roaming/npm/pnpm run build exited (1)
Tasks: 0 successful, 1 total
Cached: 0 cached, 1 total
Time: 5m33.353s
Failed: @innungsapp/admin#build
ERROR run failed: command exited (1)