feat(mobile-nav): enhance responsive sidebar with user info and auth actions
- Remove userRole prop, read user directly from auth store - Add user info section (avatar, name, email) when logged in - Add Masuk/Daftar buttons at bottom when unauthenticated - Add Keluar button with loading state when authenticated - Add tagline under logo in sidebar header - Add backdrop blur effect for better visual depth - Use flex-col layout for consistent spacing at any height
This commit is contained in:
@@ -8,7 +8,7 @@ export function MainLayout({ children }: Props) {
|
||||
return (
|
||||
<div className="min-h-screen flex flex-col bg-[#ECF4E8]">
|
||||
<Navbar />
|
||||
<main className="mx-auto w-full max-w-6xl flex-1 px-6 py-8">
|
||||
<main className="mx-auto w-full max-w-6xl flex-1 px-4 sm:px-6 py-6 sm:py-8">
|
||||
{children}
|
||||
</main>
|
||||
<Footer />
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
import { Link, useLocation } from "react-router-dom";
|
||||
import { X, Leaf } from "lucide-react";
|
||||
|
||||
type Props = {
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
userRole?: string | null;
|
||||
};
|
||||
|
||||
const NAV_ITEMS = [
|
||||
{ path: "/dashboard", label: "Dashboard" },
|
||||
{ path: "/scan", label: "Scan Tanaman" },
|
||||
{ path: "/diagnoses", label: "Diagnosa" },
|
||||
{ path: "/catalog", label: "Pustaka" },
|
||||
];
|
||||
|
||||
export function MobileNav({ open, onClose, userRole }: Props) {
|
||||
const location = useLocation();
|
||||
|
||||
if (!open) return null;
|
||||
|
||||
const isActive = (path: string) => location.pathname === path;
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 z-50 lg:hidden">
|
||||
{/* Backdrop */}
|
||||
<div className="fixed inset-0 bg-black/40" onClick={onClose} />
|
||||
{/* Panel */}
|
||||
<div className="fixed inset-y-0 right-0 z-50 w-full max-w-sm bg-[#306D29] shadow-xl">
|
||||
<div className="flex items-center justify-between px-6 h-20 border-b border-white/10">
|
||||
<div className="flex items-center gap-3 font-semibold text-white">
|
||||
<div className="flex h-10 w-10 items-center justify-center rounded-2xl bg-[#48A111]">
|
||||
<Leaf className="h-5 w-5" />
|
||||
</div>
|
||||
<span className="text-lg font-bold">ZeaVis Edu</span>
|
||||
</div>
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="rounded-full p-2 text-white/80 hover:bg-white/10 hover:text-white transition-colors"
|
||||
aria-label="Tutup menu"
|
||||
>
|
||||
<X className="h-6 w-6" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<nav className="flex flex-col gap-2 px-4 pt-6">
|
||||
{NAV_ITEMS.map((item) => {
|
||||
const active = isActive(item.path);
|
||||
return (
|
||||
<Link
|
||||
key={item.path}
|
||||
to={item.path}
|
||||
onClick={onClose}
|
||||
className={`rounded-full px-4 py-3 text-[16px] font-medium transition-colors ${
|
||||
active
|
||||
? "bg-[#48A111] text-white shadow-sm"
|
||||
: "text-white/85 hover:bg-white/10 hover:text-white"
|
||||
}`}
|
||||
>
|
||||
{item.label}
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
|
||||
{userRole === "expert" && (
|
||||
<Link
|
||||
to="/expert/reviews"
|
||||
onClick={onClose}
|
||||
className={`rounded-full px-4 py-3 text-[16px] font-medium transition-colors ${
|
||||
isActive("/expert/reviews")
|
||||
? "bg-[#48A111] text-white shadow-sm"
|
||||
: "text-white/85 hover:bg-white/10 hover:text-white"
|
||||
}`}
|
||||
>
|
||||
Review
|
||||
</Link>
|
||||
)}
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,11 +1,14 @@
|
||||
import { useState } from "react";
|
||||
import { Link, useLocation, useNavigate } from "react-router-dom";
|
||||
import { Leaf } from "lucide-react";
|
||||
import { Leaf, Menu } from "lucide-react";
|
||||
import { MobileNav } from "./mobile-nav";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import { useAuthStore } from "@/store/auth-store";
|
||||
import { apiClient } from "@/lib/api-client";
|
||||
|
||||
export function Navbar() {
|
||||
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
|
||||
const location = useLocation();
|
||||
const navigate = useNavigate();
|
||||
const queryClient = useQueryClient();
|
||||
@@ -50,7 +53,7 @@ export function Navbar() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<nav className="flex items-center gap-2">
|
||||
<nav className="hidden lg:flex items-center gap-2">
|
||||
<Link to="/dashboard" className={navLinkClassName(isActive("/dashboard"))}>
|
||||
Dashboard
|
||||
</Link>
|
||||
@@ -72,6 +75,19 @@ export function Navbar() {
|
||||
</Link>
|
||||
)}
|
||||
</nav>
|
||||
|
||||
<button
|
||||
onClick={() => setMobileMenuOpen(true)}
|
||||
className="lg:hidden rounded-full p-2 text-white/80 hover:bg-white/10 hover:text-white transition-colors"
|
||||
aria-label="Buka menu navigasi"
|
||||
>
|
||||
<Menu className="h-6 w-6" />
|
||||
</button>
|
||||
|
||||
<MobileNav
|
||||
open={mobileMenuOpen}
|
||||
onClose={() => setMobileMenuOpen(false)}
|
||||
/>
|
||||
</div>
|
||||
</header>
|
||||
);
|
||||
|
||||
@@ -63,7 +63,7 @@ export function Modal({
|
||||
aria-modal="true"
|
||||
tabIndex={-1}
|
||||
ref={containerRef}
|
||||
className={`relative z-10 w-full ${sizeClass[size]} rounded-lg bg-white p-6 shadow-lg ${className ?? ""}`}
|
||||
className={`relative z-10 w-full ${sizeClass[size]} mx-4 max-h-[90vh] overflow-y-auto rounded-lg bg-white p-6 shadow-lg ${className ?? ""}`}
|
||||
>
|
||||
<ModalHeader right={headerRight}>
|
||||
{title ? <h2 className="text-lg font-semibold">{title}</h2> : null}
|
||||
|
||||
Reference in New Issue
Block a user