From be3388cf58ce1c09a25d0af190f5cf533568c981 Mon Sep 17 00:00:00 2001 From: barsa Date: Fri, 6 Mar 2026 14:16:43 +0900 Subject: [PATCH] refactor: enhance AppShell layout and remove Header component - Replaced the Header component with a mobile-only hamburger menu in the AppShell for improved navigation on smaller screens. - Integrated user profile information directly into the Sidebar for better accessibility. - Removed the Settings link from the navigation to streamline the user experience. - Updated Sidebar and NotificationBell components to accommodate new user profile display logic. --- .../organisms/AppShell/AppShell.tsx | 44 ++- .../components/organisms/AppShell/Header.tsx | 90 ----- .../components/organisms/AppShell/Sidebar.tsx | 72 +++- .../organisms/AppShell/navigation.ts | 6 - .../templates/PageLayout/PageLayout.tsx | 2 +- .../components/NotificationBell.tsx | 5 +- .../components/NotificationDropdown.tsx | 5 +- ...026-03-06-portal-layout-redesign-design.md | 78 ++++ .../2026-03-06-portal-layout-redesign-plan.md | 357 ++++++++++++++++++ 9 files changed, 551 insertions(+), 108 deletions(-) delete mode 100644 apps/portal/src/components/organisms/AppShell/Header.tsx create mode 100644 docs/plans/2026-03-06-portal-layout-redesign-design.md create mode 100644 docs/plans/2026-03-06-portal-layout-redesign-plan.md diff --git a/apps/portal/src/components/organisms/AppShell/AppShell.tsx b/apps/portal/src/components/organisms/AppShell/AppShell.tsx index 0bd036ab..68f4d6fc 100644 --- a/apps/portal/src/components/organisms/AppShell/AppShell.tsx +++ b/apps/portal/src/components/organisms/AppShell/AppShell.tsx @@ -4,9 +4,10 @@ import { useState, useEffect, useRef } from "react"; import { usePathname, useRouter } from "next/navigation"; import { useAuthStore, useAuthSession } from "@/features/auth/stores/auth.store"; import { accountService } from "@/features/account/api/account.api"; +import { Bars3Icon } from "@heroicons/react/24/outline"; import { Sidebar } from "./Sidebar"; -import { Header } from "./Header"; import { baseNavigation } from "./navigation"; +import { Logo } from "@/components/atoms/logo"; interface AppShellProps { children: React.ReactNode; @@ -109,7 +110,6 @@ function useSidebarExpansion(pathname: string) { if (pathname.startsWith("/account/subscriptions")) next.add("Subscriptions"); if (pathname.startsWith("/account/billing")) next.add("Billing"); if (pathname.startsWith("/account/support")) next.add("Support"); - if (pathname.startsWith("/account/settings")) next.add("Settings"); const result = [...next]; if (result.length === prev.length && result.every(v => prev.includes(v))) return prev; return result; @@ -180,6 +180,16 @@ export function AppShell({ children }: AppShellProps) { expandedItems={expandedItems} toggleExpanded={toggleExpanded} isMobile + user={ + user + ? { + firstName: user.firstname ?? null, + lastName: user.lastname ?? null, + email: user.email, + } + : null + } + profileReady={!!(user?.firstname || user?.lastname)} /> @@ -193,18 +203,36 @@ export function AppShell({ children }: AppShellProps) { pathname={pathname} expandedItems={expandedItems} toggleExpanded={toggleExpanded} + user={ + user + ? { + firstName: user.firstname ?? null, + lastName: user.lastname ?? null, + email: user.email, + } + : null + } + profileReady={!!(user?.firstname || user?.lastname)} /> {/* Main content */}
- {/* Header */} -
setSidebarOpen(true)} - user={user} - profileReady={!!(user?.firstname || user?.lastname)} - /> + {/* Mobile-only hamburger bar */} +
+ +
+ +
+
{/* Main content area */}
diff --git a/apps/portal/src/components/organisms/AppShell/Header.tsx b/apps/portal/src/components/organisms/AppShell/Header.tsx deleted file mode 100644 index c253afb9..00000000 --- a/apps/portal/src/components/organisms/AppShell/Header.tsx +++ /dev/null @@ -1,90 +0,0 @@ -"use client"; - -import Link from "next/link"; -import { memo } from "react"; -import { Bars3Icon, QuestionMarkCircleIcon } from "@heroicons/react/24/outline"; -import { NotificationBell } from "@/features/notifications"; - -interface UserInfo { - firstName?: string | null; - lastName?: string | null; - email?: string | null; -} - -function getDisplayName(user: UserInfo | null, profileReady: boolean): string { - const fullName = [user?.firstName, user?.lastName].filter(Boolean).join(" "); - const emailPrefix = user?.email?.split("@")[0]; - - if (profileReady) { - return fullName || emailPrefix || "Account"; - } - return emailPrefix || "Account"; -} - -function getInitials(user: UserInfo | null, profileReady: boolean, displayName: string): string { - if (profileReady && user?.firstName && user?.lastName) { - return `${user.firstName[0]}${user.lastName[0]}`.toUpperCase(); - } - return displayName.slice(0, 2).toUpperCase(); -} - -interface HeaderProps { - onMenuClick: () => void; - user: UserInfo | null; - profileReady: boolean; -} - -export const Header = memo(function Header({ onMenuClick, user, profileReady }: HeaderProps) { - const displayName = getDisplayName(user, profileReady); - const initials = getInitials(user, profileReady, displayName); - - return ( -
-
- {/* Mobile menu button */} - - -
- - {/* Right side actions */} -
- {/* Notification bell */} - - - {/* Help link */} - - - - - {/* Divider */} -
- - {/* Profile link */} - -
- {initials} -
- {displayName} - -
-
-
- ); -}); diff --git a/apps/portal/src/components/organisms/AppShell/Sidebar.tsx b/apps/portal/src/components/organisms/AppShell/Sidebar.tsx index 1836eb4b..42b3614e 100644 --- a/apps/portal/src/components/organisms/AppShell/Sidebar.tsx +++ b/apps/portal/src/components/organisms/AppShell/Sidebar.tsx @@ -5,6 +5,7 @@ import { memo } from "react"; import { useRouter } from "next/navigation"; import { useAuthStore } from "@/features/auth/stores/auth.store"; import { Logo } from "@/components/atoms/logo"; +import { NotificationBell } from "@/features/notifications"; import type { NavigationChild, NavigationItem } from "./navigation"; import type { ComponentType, SVGProps } from "react"; @@ -48,12 +49,75 @@ function NavIcon({ ); } +interface SidebarUserInfo { + firstName?: string | null | undefined; + lastName?: string | null | undefined; + email?: string | null | undefined; +} + interface SidebarProps { navigation: NavigationItem[]; pathname: string; expandedItems: string[]; toggleExpanded: (name: string) => void; isMobile?: boolean; + user?: SidebarUserInfo | null; + profileReady?: boolean; +} + +function getSidebarDisplayName( + user: SidebarUserInfo | null | undefined, + profileReady: boolean +): string { + const fullName = [user?.firstName, user?.lastName].filter(Boolean).join(" "); + const emailPrefix = user?.email?.split("@")[0]; + if (profileReady) return fullName || emailPrefix || "Account"; + return emailPrefix || "Account"; +} + +function getSidebarInitials( + user: SidebarUserInfo | null | undefined, + profileReady: boolean, + displayName: string +): string { + if (profileReady && user?.firstName && user?.lastName) { + return `${user.firstName[0]}${user.lastName[0]}`.toUpperCase(); + } + return displayName.slice(0, 2).toUpperCase(); +} + +function SidebarProfile({ + user, + profileReady, +}: { + user?: SidebarUserInfo | null | undefined; + profileReady: boolean; +}) { + const displayName = getSidebarDisplayName(user, profileReady); + const initials = getSidebarInitials(user, profileReady, displayName); + + return ( +
+
+ +
+ {initials} +
+ + {displayName} + + + +
+
+ ); } export const Sidebar = memo(function Sidebar({ @@ -61,6 +125,8 @@ export const Sidebar = memo(function Sidebar({ pathname, expandedItems, toggleExpanded, + user, + profileReady = false, }: SidebarProps) { return (
@@ -76,7 +142,11 @@ export const Sidebar = memo(function Sidebar({
-
+
+ +
+ +