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:
parent
9d71c16883
commit
873c5e53af
|
|
@ -4,7 +4,7 @@ import { useState, useEffect } from 'react'
|
||||||
import Link from 'next/link'
|
import Link from 'next/link'
|
||||||
import Image from 'next/image'
|
import Image from 'next/image'
|
||||||
import { Syne } from 'next/font/google'
|
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'] })
|
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 [openFaq, setOpenFaq] = useState<number | null>(null);
|
||||||
const [cookieConsent, setCookieConsent] = useState<CookieConsentState>('loading')
|
const [cookieConsent, setCookieConsent] = useState<CookieConsentState>('loading')
|
||||||
const [showMailFallback, setShowMailFallback] = useState(false)
|
const [showMailFallback, setShowMailFallback] = useState(false)
|
||||||
|
const [mobileMenuOpen, setMobileMenuOpen] = useState(false)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setMounted(true);
|
setMounted(true);
|
||||||
|
|
@ -625,10 +626,67 @@ export default function RootPage() {
|
||||||
color: var(--gold); font-weight: 500; margin-bottom: 20px;
|
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) {
|
@media (max-width: 639px) {
|
||||||
.nav-links .nav-link { display: none; }
|
.nav-inner { padding: 0 16px; }
|
||||||
.stat { border-right: none; padding-right: 0; }
|
.hero { padding: 120px 16px 60px; }
|
||||||
.stat:nth-child(odd) { border-right: 1px solid var(--ink-faint); padding-right: 16px; }
|
.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>
|
`}</style>
|
||||||
|
|
||||||
|
|
@ -639,8 +697,17 @@ export default function RootPage() {
|
||||||
<div className="logo">
|
<div className="logo">
|
||||||
Innungs<span className="logo-accent">App</span> <span className="logo-pro">PRO</span>
|
Innungs<span className="logo-accent">App</span> <span className="logo-pro">PRO</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="nav-links">
|
<div className="nav-mobile-toggle">
|
||||||
<a href="#leistungen" className="nav-link">Leistungen</a>
|
<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
|
<button
|
||||||
onClick={toggleTheme}
|
onClick={toggleTheme}
|
||||||
|
|
@ -651,11 +718,14 @@ export default function RootPage() {
|
||||||
{theme === 'theme-dark' ? <Sun size={18} /> : <Moon size={18} />}
|
{theme === 'theme-dark' ? <Sun size={18} /> : <Moon size={18} />}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<Link href="/login" className="nav-link">Login</Link>
|
<Link href="/login" className="nav-link" onClick={() => setMobileMenuOpen(false)}>Login</Link>
|
||||||
<a
|
<a
|
||||||
href={CONTACT_WEBMAIL_HREF}
|
href={CONTACT_WEBMAIL_HREF}
|
||||||
className="btn-primary"
|
className="btn-primary"
|
||||||
onClick={() => handleContactCtaClick('nav')}
|
onClick={() => {
|
||||||
|
handleContactCtaClick('nav')
|
||||||
|
setMobileMenuOpen(false)
|
||||||
|
}}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noreferrer"
|
rel="noreferrer"
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,11 @@ const PUBLIC_PREFIXES = [
|
||||||
const PUBLIC_EXACT_PATHS = ['/']
|
const PUBLIC_EXACT_PATHS = ['/']
|
||||||
|
|
||||||
// Reserved subdomains that shouldn't be treated as tenant slugs
|
// 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) {
|
export function middleware(request: NextRequest) {
|
||||||
const url = request.nextUrl
|
const url = request.nextUrl
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
google-site-verification: googleccd5315437d68a49.html
|
||||||
|
|
@ -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)
|
||||||
Loading…
Reference in New Issue