From e42b352216b4ef09a20c4e8a25b35db3c97b4e5c Mon Sep 17 00:00:00 2001 From: Thatsaphorn Atchariyaphap Date: Mon, 7 Jul 2025 19:49:58 +0200 Subject: [PATCH] Refactor navigation structure and API routes - Centralize user menu, sidebar items, and breadcrumb logic. - Map consistent API endpoints in `customerRoutes`. - Replace inline route definitions with reusable constants. - Refactor auth configuration file location. - Improve `` usage to replace static `` elements. - Adjust sidebar and dropdown components to use dynamic navigation configurations. --- .../app/api/auth/[...nextauth]/route.ts | 2 +- .../app/api/customers/[id]/route.ts | 3 +- .../app/api/customers/customerRoutes.ts | 1 + internal_frontend/app/api/customers/route.ts | 5 +- internal_frontend/app/customers/page.tsx | 17 ++-- internal_frontend/app/layout.tsx | 2 +- internal_frontend/components/app-sidebar.tsx | 86 ++++++------------- .../components/dynamic-breadcrumb.tsx | 4 +- .../lib/{ => api}/auth/authOptions.ts | 12 +-- internal_frontend/lib/api/serverCall.ts | 2 +- .../lib/{ => navigation}/breadcrumb-map.ts | 9 +- .../lib/navigation/sidebar-items.ts | 38 ++++++++ .../lib/navigation/user-menu-items.ts | 20 +++++ .../navigation/breadcrumb-utils.ts} | 2 +- internal_frontend/types/navigation/sidebar.ts | 12 +++ 15 files changed, 127 insertions(+), 88 deletions(-) rename internal_frontend/lib/{ => api}/auth/authOptions.ts (84%) rename internal_frontend/lib/{ => navigation}/breadcrumb-map.ts (69%) create mode 100644 internal_frontend/lib/navigation/sidebar-items.ts create mode 100644 internal_frontend/lib/navigation/user-menu-items.ts rename internal_frontend/{utils/BreadcrumbUtils.ts => services/navigation/breadcrumb-utils.ts} (91%) create mode 100644 internal_frontend/types/navigation/sidebar.ts diff --git a/internal_frontend/app/api/auth/[...nextauth]/route.ts b/internal_frontend/app/api/auth/[...nextauth]/route.ts index fe6ebe0..2db62c8 100644 --- a/internal_frontend/app/api/auth/[...nextauth]/route.ts +++ b/internal_frontend/app/api/auth/[...nextauth]/route.ts @@ -1,5 +1,5 @@ import NextAuth from "next-auth"; -import {authOptions} from "@/lib/auth/authOptions"; +import {authOptions} from "@/lib/api/auth/authOptions"; const handler = NextAuth(authOptions); export {handler as GET, handler as POST}; diff --git a/internal_frontend/app/api/customers/[id]/route.ts b/internal_frontend/app/api/customers/[id]/route.ts index b022417..edaa62d 100644 --- a/internal_frontend/app/api/customers/[id]/route.ts +++ b/internal_frontend/app/api/customers/[id]/route.ts @@ -1,9 +1,10 @@ import {NextRequest, NextResponse} from "next/server"; import {serverCall} from "@/lib/api/serverCall"; +import {customerRoutes} from "@/app/api/customers/customerRoutes"; export async function GET(request: NextRequest) { const id = request.url.split('/').pop(); - const response = await serverCall(`/customers/${id}`, "GET"); + const response = await serverCall(customerRoutes.getById(id!), "GET"); if (!response.ok) { return NextResponse.json({error: "Customer not found"}, {status: 404}); diff --git a/internal_frontend/app/api/customers/customerRoutes.ts b/internal_frontend/app/api/customers/customerRoutes.ts index 7f410b1..974626c 100644 --- a/internal_frontend/app/api/customers/customerRoutes.ts +++ b/internal_frontend/app/api/customers/customerRoutes.ts @@ -1,4 +1,5 @@ export const customerRoutes = { create: "/customers", validate: "/customers/validate", + getById: (id: string) => `/customers/${id}`, }; \ No newline at end of file diff --git a/internal_frontend/app/api/customers/route.ts b/internal_frontend/app/api/customers/route.ts index 082b5bb..efd7177 100644 --- a/internal_frontend/app/api/customers/route.ts +++ b/internal_frontend/app/api/customers/route.ts @@ -1,14 +1,15 @@ import {NextRequest, NextResponse} from "next/server"; import {serverCall} from "@/lib/api/serverCall"; +import {customerRoutes} from "@/app/api/customers/customerRoutes"; export async function GET() { - const data = await serverCall("/customers", "GET"); + const data = await serverCall(customerRoutes.create, "GET"); const customers = await data.json(); return NextResponse.json(customers); } export async function POST(req: NextRequest) { const body = await req.json() - const result = await serverCall("/customers", "POST", body); + const result = await serverCall(customerRoutes.create, "POST", body); return NextResponse.json(result.json()); } diff --git a/internal_frontend/app/customers/page.tsx b/internal_frontend/app/customers/page.tsx index 614d439..41bdbfc 100644 --- a/internal_frontend/app/customers/page.tsx +++ b/internal_frontend/app/customers/page.tsx @@ -1,7 +1,6 @@ "use client"; import {useState, useEffect, useMemo} from "react"; -import {useRouter} from "next/navigation"; import {Button} from "@/components/ui/button"; import {Input} from "@/components/ui/input"; import {Card, CardContent} from "@/components/ui/card"; @@ -25,9 +24,9 @@ import {ArrowRight} from "lucide-react"; import {NewCustomerModal} from "@/components/customers/modal/NewCustomerModal"; import axios from "axios"; import {Customer} from "@/services/customers/entities/customer"; +import Link from "next/link"; export default function CustomersPage() { - const router = useRouter(); const [customers, setCustomers] = useState([]); const [search, setSearch] = useState(""); const [loading, setLoading] = useState(true); @@ -50,7 +49,7 @@ export default function CustomersPage() { }, []); const filtered = useMemo(() => { - if(customers.length === 0) return []; + if (customers.length === 0) return []; return customers.filter( (c) => @@ -143,13 +142,11 @@ export default function CustomersPage() { {customer.city} {new Date(customer.createdAt).toLocaleString()} - + + + ))} diff --git a/internal_frontend/app/layout.tsx b/internal_frontend/app/layout.tsx index 909bd72..89f6214 100644 --- a/internal_frontend/app/layout.tsx +++ b/internal_frontend/app/layout.tsx @@ -8,7 +8,7 @@ import {Separator} from "@/components/ui/separator"; import {DynamicBreadcrumb} from "@/components/dynamic-breadcrumb"; import {getServerSession} from "next-auth"; import LoginScreen from "@/components/login-screen"; -import {authOptions} from "@/lib/auth/authOptions"; +import {authOptions} from "@/lib/api/auth/authOptions"; export const metadata: Metadata = { title: "Internal | Rhein Software", diff --git a/internal_frontend/components/app-sidebar.tsx b/internal_frontend/components/app-sidebar.tsx index 3c56ec3..98b3156 100644 --- a/internal_frontend/components/app-sidebar.tsx +++ b/internal_frontend/components/app-sidebar.tsx @@ -1,13 +1,11 @@ +import Link from 'next/link'; import { - AppWindowIcon, ChevronUp, ChevronRight, - Home, Scale, User2, - Settings, LayoutDashboard + Settings, } from "lucide-react"; - import { Sidebar, SidebarContent, @@ -23,40 +21,20 @@ import { SidebarMenuSubItem, SidebarMenuSubButton, } from "@/components/ui/sidebar"; - import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; - import { Collapsible, CollapsibleTrigger, CollapsibleContent, } from "@/components/ui/collapsible"; +import {rheinItems, customerItems, kanzleiItems} from "@/lib/navigation/sidebar-items"; +import {userMenuItems} from "@/lib/navigation/user-menu-items"; -const rheinItems = [ - { - title: "Dashboard", - url: "/", - icon: Home, - }, - { - title: "Apps", - url: "/apps", - icon: AppWindowIcon, - }, -]; - -const customerItems = [ - { - title: "Kundenübersicht", - url: "/customers", - icon: LayoutDashboard, - }, -]; export function AppSidebar() { return ( @@ -74,10 +52,10 @@ export function AppSidebar() { asChild className="hover:bg-accent hover:text-accent-foreground" > - + {item.title} - + ))} @@ -95,10 +73,10 @@ export function AppSidebar() { asChild className="hover:bg-accent hover:text-accent-foreground" > - + {item.title} - + ))} @@ -117,11 +95,10 @@ export function AppSidebar() { asChild className="hover:bg-accent hover:text-accent-foreground" > - - + Demo Settings - + @@ -144,21 +121,15 @@ export function AppSidebar() { - - - Steuer - - - - - Rechtsanwalt - - - - - Bilanzbuchhalter - - + {kanzleiItems.map((item) => ( + + + + {item.title} + + + + ))} @@ -183,15 +154,14 @@ export function AppSidebar() { side="top" className="w-[--radix-popper-anchor-width]" > - - Account - - - Billing - - - Sign out - + {userMenuItems.map((item) => ( + + + {item.icon && } + {item.title} + + + ))} @@ -199,4 +169,4 @@ export function AppSidebar() { ); -} +} \ No newline at end of file diff --git a/internal_frontend/components/dynamic-breadcrumb.tsx b/internal_frontend/components/dynamic-breadcrumb.tsx index 884a282..c439f6f 100644 --- a/internal_frontend/components/dynamic-breadcrumb.tsx +++ b/internal_frontend/components/dynamic-breadcrumb.tsx @@ -11,8 +11,8 @@ import { } from '@/components/ui/breadcrumb'; import {Skeleton} from "@/components/ui/skeleton"; import React, {useEffect, useState} from 'react'; -import {getBreadcrumbs} from '@/utils/BreadcrumbUtils'; -import {breadcrumbResolvers} from "@/lib/breadcrumb-map"; +import {getBreadcrumbs} from '@/services/navigation/breadcrumb-utils'; +import {breadcrumbResolvers} from "@/lib/navigation/breadcrumb-map"; interface ResolvedBreadcrumb { href: string; diff --git a/internal_frontend/lib/auth/authOptions.ts b/internal_frontend/lib/api/auth/authOptions.ts similarity index 84% rename from internal_frontend/lib/auth/authOptions.ts rename to internal_frontend/lib/api/auth/authOptions.ts index 4248f07..a8bb1eb 100644 --- a/internal_frontend/lib/auth/authOptions.ts +++ b/internal_frontend/lib/api/auth/authOptions.ts @@ -15,10 +15,10 @@ const { NEXTAUTH_SECRET, } = process.env; -// if (!KEYCLOAK_CLIENT_ID) throw new Error("Missing KEYCLOAK_CLIENT_ID"); -// if (!KEYCLOAK_CLIENT_SECRET) throw new Error("Missing KEYCLOAK_CLIENT_SECRET"); -// if (!KEYCLOAK_ISSUER) throw new Error("Missing KEYCLOAK_ISSUER"); -// if (!NEXTAUTH_SECRET) throw new Error("Missing NEXTAUTH_SECRET"); +if (!KEYCLOAK_CLIENT_ID) throw new Error("Missing KEYCLOAK_CLIENT_ID"); +if (!KEYCLOAK_CLIENT_SECRET) throw new Error("Missing KEYCLOAK_CLIENT_SECRET"); +if (!KEYCLOAK_ISSUER) throw new Error("Missing KEYCLOAK_ISSUER"); +if (!NEXTAUTH_SECRET) throw new Error("Missing NEXTAUTH_SECRET"); console.log("[auth] Using Keycloak provider:"); console.log(" - Client ID:", KEYCLOAK_CLIENT_ID); @@ -42,8 +42,8 @@ async function isTokenValid(token: string): Promise { export const authOptions: NextAuthOptions = { providers: [ KeycloakProvider({ - clientId: KEYCLOAK_CLIENT_ID as string, - clientSecret: KEYCLOAK_CLIENT_SECRET as string, + clientId: KEYCLOAK_CLIENT_ID, + clientSecret: KEYCLOAK_CLIENT_SECRET, issuer: KEYCLOAK_ISSUER, }), ], diff --git a/internal_frontend/lib/api/serverCall.ts b/internal_frontend/lib/api/serverCall.ts index c6b188d..a470764 100644 --- a/internal_frontend/lib/api/serverCall.ts +++ b/internal_frontend/lib/api/serverCall.ts @@ -1,6 +1,6 @@ // lib/callBackendApi.ts import {getServerSession} from "next-auth"; -import {authOptions} from "@/lib/auth/authOptions"; +import {authOptions} from "@/lib/api/auth/authOptions"; export async function serverCall( path: string, diff --git a/internal_frontend/lib/breadcrumb-map.ts b/internal_frontend/lib/navigation/breadcrumb-map.ts similarity index 69% rename from internal_frontend/lib/breadcrumb-map.ts rename to internal_frontend/lib/navigation/breadcrumb-map.ts index 11dfaa1..1c6a3d0 100644 --- a/internal_frontend/lib/breadcrumb-map.ts +++ b/internal_frontend/lib/navigation/breadcrumb-map.ts @@ -1,20 +1,19 @@ -// lib/breadcrumb-map.ts +import {customerRoutes} from "@/app/api/customers/customerRoutes"; + export const breadcrumbMap: Record = { 'dashboard': 'Dashboard', 'settings': 'Settings', 'demo': 'Demo', 'users': 'User Management', 'customers': 'Kundenübersicht', - // Add more mappings as needed }; export const breadcrumbResolvers: Record Promise> = { "customers": async (id: string) => { - const res = await fetch(`/api/customers/${id}`, {cache: "no-store"}); - const customer = await res .json(); + const res = await fetch(`/api${customerRoutes.getById(id)}`, {cache: "no-store"}); + const customer = await res.json(); if (customer.companyName) return `Firma: ${customer.companyName}`; if (customer.name) return `Name: ${customer.name}`; return `ID: ${id}`; }, - // Add more mappings as needed }; diff --git a/internal_frontend/lib/navigation/sidebar-items.ts b/internal_frontend/lib/navigation/sidebar-items.ts new file mode 100644 index 0000000..9a96dbf --- /dev/null +++ b/internal_frontend/lib/navigation/sidebar-items.ts @@ -0,0 +1,38 @@ +import {AppWindowIcon, Home, LayoutDashboard} from "lucide-react"; +import {MenuItem, SubMenuItem} from "@/types/navigation/sidebar"; + +export const rheinItems: MenuItem[] = [ + { + title: "Dashboard", + url: "/", + icon: Home, + }, + { + title: "Apps", + url: "/apps", + icon: AppWindowIcon, + }, +]; + +export const customerItems: MenuItem[] = [ + { + title: "Kundenübersicht", + url: "/customers", + icon: LayoutDashboard, + }, +]; + +export const kanzleiItems: SubMenuItem[] = [ + { + title: "Steuer", + url: "/demo/kanzlei/steuer", + }, + { + title: "Rechtsanwalt", + url: "/demo/kanzlei/rechtsanwalt", + }, + { + title: "Bilanzbuchhalter", + url: "/demo/kanzlei/bilanzbuchhalter", + }, +]; \ No newline at end of file diff --git a/internal_frontend/lib/navigation/user-menu-items.ts b/internal_frontend/lib/navigation/user-menu-items.ts new file mode 100644 index 0000000..c47e860 --- /dev/null +++ b/internal_frontend/lib/navigation/user-menu-items.ts @@ -0,0 +1,20 @@ +import {LogOut, Settings} from "lucide-react"; + +export interface UserMenuItem { + title: string; + url: string; + icon?: typeof Settings; +} + +export const userMenuItems: UserMenuItem[] = [ + { + title: "Settings", + url: "/settings", + icon: Settings, + }, + { + title: "Ausloggen", + url: "/api/auth/signout", + icon: LogOut, + }, +]; \ No newline at end of file diff --git a/internal_frontend/utils/BreadcrumbUtils.ts b/internal_frontend/services/navigation/breadcrumb-utils.ts similarity index 91% rename from internal_frontend/utils/BreadcrumbUtils.ts rename to internal_frontend/services/navigation/breadcrumb-utils.ts index 33f3004..973977d 100644 --- a/internal_frontend/utils/BreadcrumbUtils.ts +++ b/internal_frontend/services/navigation/breadcrumb-utils.ts @@ -1,4 +1,4 @@ -import {breadcrumbMap} from "@/lib/breadcrumb-map"; +import {breadcrumbMap} from "@/lib/navigation/breadcrumb-map"; export interface Breadcrumb { href: string; diff --git a/internal_frontend/types/navigation/sidebar.ts b/internal_frontend/types/navigation/sidebar.ts new file mode 100644 index 0000000..0458e24 --- /dev/null +++ b/internal_frontend/types/navigation/sidebar.ts @@ -0,0 +1,12 @@ +import {LucideIcon} from "lucide-react"; + +export interface MenuItem { + title: string; + url: string; + icon: LucideIcon; +} + +export interface SubMenuItem { + title: string; + url: string; +} \ No newline at end of file