diff --git a/app/[locale]/(site)/root/page.tsx b/app/[locale]/(site)/root/page.tsx index a402aa5..705a5a4 100644 --- a/app/[locale]/(site)/root/page.tsx +++ b/app/[locale]/(site)/root/page.tsx @@ -1,324 +1,5 @@ -import { - BarChart3, - Boxes, - FolderKanban, - LockKeyhole, - LogOut, - Power, - ShieldAlert, - Users2, -} from "lucide-react"; -import { revalidatePath } from "next/cache"; import { redirect } from "next/navigation"; -import { MotionFade } from "@/components/motion-fade"; -import { - clearAdminSessionCookie, - getAdminLockState, - isAdminAuthConfigured, - isAdminAuthenticated, - isPasswordValid, - registerFailedAdminAttempt, - resetAdminFailedAttempts, - setAdminSessionCookie, -} from "@/lib/admin-auth"; -import { getMaintenanceMode, setMaintenanceMode } from "@/lib/app-config"; -import { resolveLocale } from "@/lib/site-data"; - -type RootPageProps = { - params: { - locale: string; - }; - searchParams?: { - error?: string; - }; -}; - -export const dynamic = "force-dynamic"; - -export default async function RootPage({ - params: { locale }, - searchParams, -}: RootPageProps) { - const localeKey = resolveLocale(locale); - const authConfigured = isAdminAuthConfigured(); - const basicConfigured = Boolean( - process.env.ROOT_BASIC_AUTH_USER && process.env.ROOT_BASIC_AUTH_PASS, - ); - const authenticated = isAdminAuthenticated(); - const lockState = getAdminLockState(); - const maintenanceEnabled = authenticated ? await getMaintenanceMode() : false; - - async function loginAction(formData: FormData) { - "use server"; - - const password = String(formData.get("password") ?? ""); - const currentLockState = getAdminLockState(); - - if (currentLockState.locked) { - redirect(`/${localeKey}/root?error=locked`); - } - - if (!isAdminAuthConfigured() || !isPasswordValid(password)) { - const failState = registerFailedAdminAttempt(); - if (failState.locked) { - redirect(`/${localeKey}/root?error=locked`); - } - - redirect(`/${localeKey}/root?error=invalid`); - } - - resetAdminFailedAttempts(); - setAdminSessionCookie(); - redirect(`/${localeKey}/root`); - } - - async function logoutAction() { - "use server"; - - clearAdminSessionCookie(); - redirect(`/${localeKey}/root`); - } - - async function updateMaintenanceMode(formData: FormData) { - "use server"; - - if (!isAdminAuthenticated()) { - redirect(`/${localeKey}/root`); - } - - const nextValue = formData.get("enabled") === "true"; - - await setMaintenanceMode(nextValue); - revalidatePath(`/${localeKey}/root`); - revalidatePath(`/${localeKey}/coming-soon`); - redirect(`/${localeKey}/root`); - } - - const copy = - localeKey === "de" - ? { - title: "Root", - subtitle: "Interner Bereich fuer Kennzahlen und Wartungssteuerung.", - activeUsers: "Aktive Nutzer", - projects: "Laufende Projekte", - products: "Aktive Produkte", - conversion: "Conversion", - updates: "Letzte Updates", - updateOne: "Kontaktformular wurde ueberarbeitet.", - updateTwo: "Neue Produktseite fuer Growth Kit vorbereitet.", - updateThree: "Portfolio-Daten fuer Q2 aktualisiert.", - maintenanceTitle: "Wartungsmodus", - maintenanceText: - "Wenn aktiv, werden alle Seiten auf die Coming Soon Seite weitergeleitet.", - maintenanceOn: "Aktiv", - maintenanceOff: "Inaktiv", - enableMaintenance: "Wartungsmodus aktivieren", - disableMaintenance: "Wartungsmodus deaktivieren", - loginTitle: "Root Login", - loginText: "Nur autorisierte Nutzer duerfen diesen Bereich verwenden.", - passwordLabel: "Passwort", - loginButton: "Einloggen", - invalidLogin: "Falsches Passwort.", - lockedLogin: "Zu viele Fehlversuche. Bitte spaeter erneut versuchen.", - configMissing: "ADMIN_PASSWORD und ADMIN_AUTH_SECRET fehlen in env.", - basicAuthMissing: - "ROOT_BASIC_AUTH_USER und ROOT_BASIC_AUTH_PASS fehlen in env.", - logout: "Ausloggen", - } - : { - title: "Root", - subtitle: "Internal area for metrics and maintenance controls.", - activeUsers: "Active users", - projects: "Running projects", - products: "Active products", - conversion: "Conversion", - updates: "Latest updates", - updateOne: "Contact form layout updated.", - updateTwo: "New Growth Kit product page prepared.", - updateThree: "Portfolio data updated for Q2.", - maintenanceTitle: "Maintenance mode", - maintenanceText: - "When enabled, all site routes are redirected to the coming soon page.", - maintenanceOn: "Enabled", - maintenanceOff: "Disabled", - enableMaintenance: "Enable maintenance mode", - disableMaintenance: "Disable maintenance mode", - loginTitle: "Root login", - loginText: "Only authorized users can access this area.", - passwordLabel: "Password", - loginButton: "Sign in", - invalidLogin: "Invalid password.", - lockedLogin: "Too many failed attempts. Please try again later.", - configMissing: "ADMIN_PASSWORD and ADMIN_AUTH_SECRET are missing in env.", - basicAuthMissing: - "ROOT_BASIC_AUTH_USER and ROOT_BASIC_AUTH_PASS are missing in env.", - logout: "Sign out", - }; - - if (!authenticated) { - return ( -
- -
-

- - {copy.loginTitle} -

-

{copy.loginText}

- - {!authConfigured ? ( -

- {copy.configMissing} -

- ) : null} - - {!basicConfigured ? ( -

- {copy.basicAuthMissing} -

- ) : null} - - {searchParams?.error === "invalid" ? ( -

- {copy.invalidLogin} -

- ) : null} - - {searchParams?.error === "locked" || lockState.locked ? ( -

- {copy.lockedLogin} -

- ) : null} - -
- - - -
-
-
-
- ); - } - - const stats = [ - { label: copy.activeUsers, value: "1,280", icon: Users2 }, - { label: copy.projects, value: "24", icon: FolderKanban }, - { label: copy.products, value: "8", icon: Boxes }, - { label: copy.conversion, value: "4.8%", icon: BarChart3 }, - ]; - - return ( -
- -
-
-

- {copy.title} -

-
- -
-
-

- {copy.subtitle} -

-
-
- - -
-
-
-

- - {copy.maintenanceTitle} -

-

- {copy.maintenanceText} -

-
- - {maintenanceEnabled ? copy.maintenanceOn : copy.maintenanceOff} - -
- -
- - -
-
-
- -
- {stats.map((item, index) => { - const Icon = item.icon; - return ( - -
- -

{item.label}

-

- {item.value} -

-
-
- ); - })} -
- - -
-

{copy.updates}

-
    - {[copy.updateOne, copy.updateTwo, copy.updateThree].map((item) => ( -
  • - {item} -
  • - ))} -
-
-
-
- ); +export default function LocalizedRootRedirectPage() { + redirect("/root"); } diff --git a/app/[locale]/coming-soon/page.tsx b/app/[locale]/coming-soon/page.tsx index f34d062..23ef466 100644 --- a/app/[locale]/coming-soon/page.tsx +++ b/app/[locale]/coming-soon/page.tsx @@ -64,7 +64,7 @@ export default function ComingSoonPage({ params: { locale } }: ComingSoonPagePro
diff --git a/app/root/page.tsx b/app/root/page.tsx new file mode 100644 index 0000000..146bca3 --- /dev/null +++ b/app/root/page.tsx @@ -0,0 +1,320 @@ +import { + BarChart3, + Boxes, + FolderKanban, + LockKeyhole, + LogOut, + Power, + ShieldAlert, + Users2, +} from "lucide-react"; +import { revalidatePath } from "next/cache"; +import { cookies } from "next/headers"; +import { redirect } from "next/navigation"; + +import { MotionFade } from "@/components/motion-fade"; +import { + clearAdminSessionCookie, + getAdminLockState, + isAdminAuthConfigured, + isAdminAuthenticated, + isPasswordValid, + registerFailedAdminAttempt, + resetAdminFailedAttempts, + setAdminSessionCookie, +} from "@/lib/admin-auth"; +import { getMaintenanceMode, setMaintenanceMode } from "@/lib/app-config"; +import { resolveLocale } from "@/lib/site-data"; + +type RootPageProps = { + searchParams?: { + error?: string; + }; +}; + +export const dynamic = "force-dynamic"; + +export default async function RootPage({ searchParams }: RootPageProps) { + const requestLocale = cookies().get("NEXT_LOCALE")?.value; + const localeKey = resolveLocale(requestLocale ?? "de"); + const authConfigured = isAdminAuthConfigured(); + const basicConfigured = Boolean( + process.env.ROOT_BASIC_AUTH_USER && process.env.ROOT_BASIC_AUTH_PASS, + ); + const authenticated = isAdminAuthenticated(); + const lockState = getAdminLockState(); + const maintenanceEnabled = authenticated ? await getMaintenanceMode() : false; + + async function loginAction(formData: FormData) { + "use server"; + + const password = String(formData.get("password") ?? ""); + const currentLockState = getAdminLockState(); + + if (currentLockState.locked) { + redirect("/root?error=locked"); + } + + if (!isAdminAuthConfigured() || !isPasswordValid(password)) { + const failState = registerFailedAdminAttempt(); + if (failState.locked) { + redirect("/root?error=locked"); + } + + redirect("/root?error=invalid"); + } + + resetAdminFailedAttempts(); + setAdminSessionCookie(); + redirect("/root"); + } + + async function logoutAction() { + "use server"; + + clearAdminSessionCookie(); + redirect("/root"); + } + + async function updateMaintenanceMode(formData: FormData) { + "use server"; + + if (!isAdminAuthenticated()) { + redirect("/root"); + } + + const nextValue = formData.get("enabled") === "true"; + + await setMaintenanceMode(nextValue); + revalidatePath("/root"); + revalidatePath(`/${localeKey}/coming-soon`); + redirect("/root"); + } + + const copy = + resolveLocale(requestLocale ?? localeKey) === "de" + ? { + title: "Root", + subtitle: "Interner Bereich fuer Kennzahlen und Wartungssteuerung.", + activeUsers: "Aktive Nutzer", + projects: "Laufende Projekte", + products: "Aktive Produkte", + conversion: "Conversion", + updates: "Letzte Updates", + updateOne: "Kontaktformular wurde ueberarbeitet.", + updateTwo: "Neue Produktseite fuer Growth Kit vorbereitet.", + updateThree: "Portfolio-Daten fuer Q2 aktualisiert.", + maintenanceTitle: "Wartungsmodus", + maintenanceText: + "Wenn aktiv, werden alle Seiten auf die Coming Soon Seite weitergeleitet.", + maintenanceOn: "Aktiv", + maintenanceOff: "Inaktiv", + enableMaintenance: "Wartungsmodus aktivieren", + disableMaintenance: "Wartungsmodus deaktivieren", + loginTitle: "Root Login", + loginText: "Nur autorisierte Nutzer duerfen diesen Bereich verwenden.", + passwordLabel: "Passwort", + loginButton: "Einloggen", + invalidLogin: "Falsches Passwort.", + lockedLogin: "Zu viele Fehlversuche. Bitte spaeter erneut versuchen.", + configMissing: "ADMIN_PASSWORD und ADMIN_AUTH_SECRET fehlen in env.", + basicAuthMissing: + "ROOT_BASIC_AUTH_USER und ROOT_BASIC_AUTH_PASS fehlen in env.", + logout: "Ausloggen", + } + : { + title: "Root", + subtitle: "Internal area for metrics and maintenance controls.", + activeUsers: "Active users", + projects: "Running projects", + products: "Active products", + conversion: "Conversion", + updates: "Latest updates", + updateOne: "Contact form layout updated.", + updateTwo: "New Growth Kit product page prepared.", + updateThree: "Portfolio data updated for Q2.", + maintenanceTitle: "Maintenance mode", + maintenanceText: + "When enabled, all site routes are redirected to the coming soon page.", + maintenanceOn: "Enabled", + maintenanceOff: "Disabled", + enableMaintenance: "Enable maintenance mode", + disableMaintenance: "Disable maintenance mode", + loginTitle: "Root login", + loginText: "Only authorized users can access this area.", + passwordLabel: "Password", + loginButton: "Sign in", + invalidLogin: "Invalid password.", + lockedLogin: "Too many failed attempts. Please try again later.", + configMissing: "ADMIN_PASSWORD and ADMIN_AUTH_SECRET are missing in env.", + basicAuthMissing: + "ROOT_BASIC_AUTH_USER and ROOT_BASIC_AUTH_PASS are missing in env.", + logout: "Sign out", + }; + + if (!authenticated) { + return ( +
+ +
+

+ + {copy.loginTitle} +

+

{copy.loginText}

+ + {!authConfigured ? ( +

+ {copy.configMissing} +

+ ) : null} + + {!basicConfigured ? ( +

+ {copy.basicAuthMissing} +

+ ) : null} + + {searchParams?.error === "invalid" ? ( +

+ {copy.invalidLogin} +

+ ) : null} + + {searchParams?.error === "locked" || lockState.locked ? ( +

+ {copy.lockedLogin} +

+ ) : null} + +
+ + + +
+
+
+
+ ); + } + + const stats = [ + { label: copy.activeUsers, value: "1,280", icon: Users2 }, + { label: copy.projects, value: "24", icon: FolderKanban }, + { label: copy.products, value: "8", icon: Boxes }, + { label: copy.conversion, value: "4.8%", icon: BarChart3 }, + ]; + + return ( +
+ +
+
+

+ {copy.title} +

+
+ +
+
+

+ {copy.subtitle} +

+
+
+ + +
+
+
+

+ + {copy.maintenanceTitle} +

+

+ {copy.maintenanceText} +

+
+ + {maintenanceEnabled ? copy.maintenanceOn : copy.maintenanceOff} + +
+ +
+ + +
+
+
+ +
+ {stats.map((item, index) => { + const Icon = item.icon; + return ( + +
+ +

{item.label}

+

+ {item.value} +

+
+
+ ); + })} +
+ + +
+

{copy.updates}

+
    + {[copy.updateOne, copy.updateTwo, copy.updateThree].map((item) => ( +
  • + {item} +
  • + ))} +
+
+
+
+ ); +} diff --git a/components/footer.tsx b/components/footer.tsx index 7f9419e..c476379 100644 --- a/components/footer.tsx +++ b/components/footer.tsx @@ -23,7 +23,7 @@ export function Footer() { item.key === "root" ? (
{tNav(item.key)} diff --git a/components/navbar.tsx b/components/navbar.tsx index 7b240a4..d9f74a5 100644 --- a/components/navbar.tsx +++ b/components/navbar.tsx @@ -52,7 +52,7 @@ export function Navbar() { item.key === "root" ? ( {t(item.key)} @@ -97,7 +97,7 @@ export function Navbar() { item.key === "root" ? ( setIsOpen(false)} > diff --git a/middleware.ts b/middleware.ts index 962ca17..15fbc9d 100644 --- a/middleware.ts +++ b/middleware.ts @@ -115,10 +115,10 @@ export default async function middleware(request: NextRequest) { const { pathname } = request.nextUrl; const locale = getLocaleFromPath(pathname); const isSuperAdmin = await isSuperAdminSessionValid(request); + const isRootBaseRoute = pathname === "/root" || pathname.startsWith("/root/"); const isRootRoute = - pathname === "/root" || - pathname.startsWith("/root/") || + isRootBaseRoute || pathname === `/${locale}/root` || pathname.startsWith(`/${locale}/root/`); const isComingSoonRoute = @@ -136,6 +136,10 @@ export default async function middleware(request: NextRequest) { }); } + if (isRootBaseRoute) { + return NextResponse.next(); + } + if (!isRootRoute && !isComingSoonRoute && !isSuperAdmin) { const maintenanceModeEnabled = await isMaintenanceModeEnabled(request);