mirror of
https://github.com/Luchexx/wacrm.git
synced 2026-11-02 09:19:51 +00:00
Merge pull request #53 from ArnasDon/revert-48-feat/mobile-app-shell
Revert "feat(mobile): app shell drawer + responsive header"
This commit is contained in:
commit
ad6c5eba75
@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { useEffect } from "react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { AuthProvider, useAuth } from "@/hooks/use-auth";
|
||||
import { Sidebar } from "@/components/layout/sidebar";
|
||||
@ -14,11 +14,6 @@ function DashboardShellInner({ children }: { children: React.ReactNode }) {
|
||||
const { user, loading } = useAuth();
|
||||
const router = useRouter();
|
||||
|
||||
// Sidebar drawer state — only used on mobile. On lg+ the sidebar is
|
||||
// always visible and this stays at `false` (ignored by the component).
|
||||
const [sidebarOpen, setSidebarOpen] = useState(false);
|
||||
const closeSidebar = useCallback(() => setSidebarOpen(false), []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!loading && !user) {
|
||||
router.push("/login");
|
||||
@ -40,11 +35,10 @@ function DashboardShellInner({ children }: { children: React.ReactNode }) {
|
||||
|
||||
return (
|
||||
<div className="flex h-screen overflow-hidden bg-slate-950">
|
||||
<Sidebar open={sidebarOpen} onClose={closeSidebar} />
|
||||
<Sidebar />
|
||||
<div className="flex flex-1 flex-col overflow-hidden">
|
||||
<Header onOpenSidebar={() => setSidebarOpen(true)} />
|
||||
{/* Thinner horizontal padding on mobile so cards have room to breathe. */}
|
||||
<main className="flex-1 overflow-y-auto p-4 sm:p-6">{children}</main>
|
||||
<Header />
|
||||
<main className="flex-1 overflow-y-auto p-6">{children}</main>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
|
||||
import { usePathname } from "next/navigation";
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
import { LogOut, Menu } from "lucide-react";
|
||||
import { LogOut } from "lucide-react";
|
||||
|
||||
const pageTitles: Record<string, string> = {
|
||||
"/dashboard": "Dashboard",
|
||||
@ -16,42 +16,24 @@ const pageTitles: Record<string, string> = {
|
||||
|
||||
function getPageTitle(pathname: string): string {
|
||||
if (pageTitles[pathname]) return pageTitles[pathname];
|
||||
|
||||
const match = Object.entries(pageTitles).find(([path]) =>
|
||||
pathname.startsWith(path),
|
||||
pathname.startsWith(path)
|
||||
);
|
||||
return match ? match[1] : "Dashboard";
|
||||
}
|
||||
|
||||
interface HeaderProps {
|
||||
/** Wired to the shell's drawer state. Used only on mobile — the
|
||||
* hamburger button is hidden on lg+. */
|
||||
onOpenSidebar?: () => void;
|
||||
}
|
||||
|
||||
export function Header({ onOpenSidebar }: HeaderProps) {
|
||||
export function Header() {
|
||||
const pathname = usePathname();
|
||||
const { profile, signOut } = useAuth();
|
||||
const title = getPageTitle(pathname);
|
||||
|
||||
return (
|
||||
<header className="flex h-14 shrink-0 items-center justify-between gap-3 border-b border-slate-800 bg-slate-950 px-4 lg:px-6">
|
||||
<div className="flex min-w-0 items-center gap-2">
|
||||
{/* Hamburger — mobile only. 44×44 hit target per Apple HIG. */}
|
||||
<button
|
||||
type="button"
|
||||
onClick={onOpenSidebar}
|
||||
aria-label="Open menu"
|
||||
className="flex h-10 w-10 items-center justify-center rounded-md text-slate-300 transition-colors hover:bg-slate-800 hover:text-white lg:hidden"
|
||||
>
|
||||
<Menu className="h-5 w-5" />
|
||||
</button>
|
||||
<h1 className="truncate text-base font-semibold text-white sm:text-lg">
|
||||
{title}
|
||||
</h1>
|
||||
</div>
|
||||
<header className="flex h-14 shrink-0 items-center justify-between border-b border-slate-800 bg-slate-950 px-6">
|
||||
<h1 className="text-lg font-semibold text-white">{title}</h1>
|
||||
|
||||
<div className="flex items-center gap-2 sm:gap-3">
|
||||
<div className="flex items-center gap-2 sm:gap-3">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="flex h-8 w-8 items-center justify-center rounded-full bg-emerald-500/10 text-sm font-medium text-emerald-500">
|
||||
{profile?.full_name?.charAt(0)?.toUpperCase() ?? "U"}
|
||||
</div>
|
||||
@ -63,9 +45,8 @@ export function Header({ onOpenSidebar }: HeaderProps) {
|
||||
</div>
|
||||
<button
|
||||
onClick={signOut}
|
||||
className="flex h-10 w-10 items-center justify-center rounded-md text-slate-400 transition-colors hover:bg-slate-800 hover:text-white"
|
||||
className="rounded-md p-1.5 text-slate-400 transition-colors hover:bg-slate-800 hover:text-white"
|
||||
title="Sign out"
|
||||
aria-label="Sign out"
|
||||
>
|
||||
<LogOut className="h-4 w-4" />
|
||||
</button>
|
||||
|
||||
@ -2,7 +2,6 @@
|
||||
|
||||
import Link from "next/link";
|
||||
import { usePathname } from "next/navigation";
|
||||
import { useEffect } from "react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
import { useTotalUnread } from "@/hooks/use-total-unread";
|
||||
@ -15,7 +14,6 @@ import {
|
||||
Zap,
|
||||
Settings,
|
||||
LogOut,
|
||||
X,
|
||||
} from "lucide-react";
|
||||
|
||||
const navItems = [
|
||||
@ -31,86 +29,19 @@ const bottomNavItems = [
|
||||
{ href: "/settings", label: "Settings", icon: Settings },
|
||||
];
|
||||
|
||||
interface SidebarProps {
|
||||
/** Controlled on mobile by the Header's hamburger button. Ignored on lg+. */
|
||||
open?: boolean;
|
||||
onClose?: () => void;
|
||||
}
|
||||
|
||||
export function Sidebar({ open = false, onClose }: SidebarProps) {
|
||||
export function Sidebar() {
|
||||
const pathname = usePathname();
|
||||
const { profile, signOut } = useAuth();
|
||||
const totalUnread = useTotalUnread();
|
||||
|
||||
// Close the drawer when route changes — users opened it to navigate,
|
||||
// so once they pick a destination the drawer should get out of the way.
|
||||
useEffect(() => {
|
||||
onClose?.();
|
||||
// Only pathname drives this — onClose identity doesn't need to re-run it.
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [pathname]);
|
||||
|
||||
// Lock body scroll and allow Escape to close while the drawer is open on
|
||||
// mobile. No-ops on desktop because the sidebar isn't positioned there.
|
||||
useEffect(() => {
|
||||
if (!open) return;
|
||||
const prev = document.body.style.overflow;
|
||||
document.body.style.overflow = "hidden";
|
||||
const onKey = (e: KeyboardEvent) => {
|
||||
if (e.key === "Escape") onClose?.();
|
||||
};
|
||||
window.addEventListener("keydown", onKey);
|
||||
return () => {
|
||||
document.body.style.overflow = prev;
|
||||
window.removeEventListener("keydown", onKey);
|
||||
};
|
||||
}, [open, onClose]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Backdrop — only exists on mobile and only when open. Clicking
|
||||
it closes the drawer. Hidden from lg+ since the sidebar is
|
||||
part of the main flex row there. */}
|
||||
<button
|
||||
type="button"
|
||||
aria-label="Close menu"
|
||||
onClick={onClose}
|
||||
className={cn(
|
||||
"fixed inset-0 z-30 bg-slate-950/70 backdrop-blur-sm transition-opacity lg:hidden",
|
||||
open
|
||||
? "pointer-events-auto opacity-100"
|
||||
: "pointer-events-none opacity-0",
|
||||
)}
|
||||
/>
|
||||
|
||||
<aside
|
||||
className={cn(
|
||||
// Mobile: fixed drawer that slides in from the left.
|
||||
"fixed inset-y-0 left-0 z-40 flex h-full w-64 flex-col border-r border-slate-800 bg-slate-900",
|
||||
"transition-transform duration-200 ease-out will-change-transform",
|
||||
open ? "translate-x-0" : "-translate-x-full",
|
||||
// Desktop: static, always visible — reset all the mobile framing.
|
||||
"lg:static lg:z-0 lg:w-60 lg:translate-x-0 lg:transition-none",
|
||||
)}
|
||||
aria-label="Primary"
|
||||
>
|
||||
{/* Logo row. On mobile we put a close button here; on desktop the
|
||||
close button is hidden since the sidebar is always-visible. */}
|
||||
<div className="flex h-14 shrink-0 items-center justify-between gap-2 border-b border-slate-800 px-4">
|
||||
<Link href="/dashboard" className="flex items-center gap-2">
|
||||
<aside className="flex h-screen w-60 flex-col border-r border-slate-800 bg-slate-900">
|
||||
{/* Logo */}
|
||||
<div className="flex h-14 items-center gap-2 border-b border-slate-800 px-4">
|
||||
<div className="flex h-8 w-8 items-center justify-center rounded-lg bg-emerald-500">
|
||||
<MessageSquare className="h-4 w-4 text-white" />
|
||||
</div>
|
||||
<span className="text-lg font-semibold text-white">WaCRM</span>
|
||||
</Link>
|
||||
<button
|
||||
type="button"
|
||||
onClick={onClose}
|
||||
aria-label="Close menu"
|
||||
className="flex h-9 w-9 items-center justify-center rounded-md text-slate-400 hover:bg-slate-800 hover:text-white lg:hidden"
|
||||
>
|
||||
<X className="h-5 w-5" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Main navigation */}
|
||||
@ -121,6 +52,10 @@ export function Sidebar({ open = false, onClose }: SidebarProps) {
|
||||
pathname === item.href ||
|
||||
(item.href !== "/dashboard" && pathname.startsWith(item.href));
|
||||
|
||||
// Green dot on the Inbox entry when there are unread
|
||||
// conversations AND the user isn't currently in /inbox (they'd
|
||||
// be seeing the unread badges on the conversation list there,
|
||||
// so the sidebar dot would be redundant).
|
||||
const showUnreadDot =
|
||||
item.href === "/inbox" && totalUnread > 0 && !isActive;
|
||||
|
||||
@ -129,11 +64,10 @@ export function Sidebar({ open = false, onClose }: SidebarProps) {
|
||||
<Link
|
||||
href={item.href}
|
||||
className={cn(
|
||||
// Taller on mobile so fingers can hit the row reliably (≥44px).
|
||||
"flex items-center gap-3 rounded-lg px-3 py-2.5 text-sm font-medium transition-colors lg:py-2",
|
||||
"flex items-center gap-3 rounded-lg px-3 py-2 text-sm font-medium transition-colors",
|
||||
isActive
|
||||
? "bg-emerald-500/10 text-emerald-500"
|
||||
: "text-slate-400 hover:bg-slate-800 hover:text-white",
|
||||
: "text-slate-400 hover:bg-slate-800 hover:text-white"
|
||||
)}
|
||||
>
|
||||
<item.icon className="h-4 w-4" />
|
||||
@ -153,20 +87,23 @@ export function Sidebar({ open = false, onClose }: SidebarProps) {
|
||||
})}
|
||||
</ul>
|
||||
|
||||
{/* Divider */}
|
||||
<div className="my-4 border-t border-slate-800" />
|
||||
|
||||
{/* Bottom nav items */}
|
||||
<ul className="flex flex-col gap-1">
|
||||
{bottomNavItems.map((item) => {
|
||||
const isActive = pathname.startsWith(item.href);
|
||||
|
||||
return (
|
||||
<li key={item.href}>
|
||||
<Link
|
||||
href={item.href}
|
||||
className={cn(
|
||||
"flex items-center gap-3 rounded-lg px-3 py-2.5 text-sm font-medium transition-colors lg:py-2",
|
||||
"flex items-center gap-3 rounded-lg px-3 py-2 text-sm font-medium transition-colors",
|
||||
isActive
|
||||
? "bg-emerald-500/10 text-emerald-500"
|
||||
: "text-slate-400 hover:bg-slate-800 hover:text-white",
|
||||
: "text-slate-400 hover:bg-slate-800 hover:text-white"
|
||||
)}
|
||||
>
|
||||
<item.icon className="h-4 w-4" />
|
||||
@ -179,7 +116,7 @@ export function Sidebar({ open = false, onClose }: SidebarProps) {
|
||||
</nav>
|
||||
|
||||
{/* User section */}
|
||||
<div className="shrink-0 border-t border-slate-800 p-3">
|
||||
<div className="border-t border-slate-800 p-3">
|
||||
<div className="flex items-center gap-3 rounded-lg px-3 py-2">
|
||||
<div className="flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-emerald-500/10 text-sm font-medium text-emerald-500">
|
||||
{profile?.full_name?.charAt(0)?.toUpperCase() ?? "U"}
|
||||
@ -202,6 +139,5 @@ export function Sidebar({ open = false, onClose }: SidebarProps) {
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user