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:
@@ -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"
|
||||
>
|
||||
|
||||
@@ -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
|
||||
|
||||
1
innungsapp/apps/admin/public/googleccd5315437d68a49.html
Normal file
1
innungsapp/apps/admin/public/googleccd5315437d68a49.html
Normal file
@@ -0,0 +1 @@
|
||||
google-site-verification: googleccd5315437d68a49.html
|
||||
Reference in New Issue
Block a user