Compare commits
1 Commits
47dced6f89
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
99f9b51db8 |
63
app/[lang]/dashboard/DashboardLayoutClient.tsx
Normal file
63
app/[lang]/dashboard/DashboardLayoutClient.tsx
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useState } from "react";
|
||||||
|
import Providers from "@/components/Providers";
|
||||||
|
import Sidebar from "@/components/Sidebar";
|
||||||
|
import { DictionaryProvider } from "@/components/DictionaryContext";
|
||||||
|
|
||||||
|
export default function DashboardLayout({
|
||||||
|
children,
|
||||||
|
dict,
|
||||||
|
lang,
|
||||||
|
}: {
|
||||||
|
children: React.ReactNode;
|
||||||
|
dict: any;
|
||||||
|
lang: string;
|
||||||
|
}) {
|
||||||
|
const [isSidebarOpen, setIsSidebarOpen] = useState(false);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Providers>
|
||||||
|
<DictionaryProvider dictionary={dict}>
|
||||||
|
<div className={`app-layout ${isSidebarOpen ? "sidebar-open" : ""}`}>
|
||||||
|
{/* Mobile Overlay */}
|
||||||
|
<div
|
||||||
|
className="sidebar-overlay"
|
||||||
|
onClick={() => setIsSidebarOpen(false)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Sidebar
|
||||||
|
dict={dict}
|
||||||
|
lang={lang}
|
||||||
|
onClose={() => setIsSidebarOpen(false)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className="main-content">
|
||||||
|
{/* Mobile Header */}
|
||||||
|
<header className="mobile-header">
|
||||||
|
<button
|
||||||
|
className="mobile-menu-btn"
|
||||||
|
onClick={() => setIsSidebarOpen(true)}
|
||||||
|
>
|
||||||
|
<MenuIcon />
|
||||||
|
</button>
|
||||||
|
<div className="mobile-logo">AyrisMail</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</DictionaryProvider>
|
||||||
|
</Providers>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function MenuIcon() {
|
||||||
|
return (
|
||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||||
|
<line x1="4" x2="20" y1="12" y2="12" />
|
||||||
|
<line x1="4" x2="20" y1="6" y2="6" />
|
||||||
|
<line x1="4" x2="20" y1="18" y2="18" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,9 +1,7 @@
|
|||||||
import { auth } from "@/auth";
|
import { auth } from "@/auth";
|
||||||
import { redirect } from "next/navigation";
|
import { redirect } from "next/navigation";
|
||||||
import Providers from "@/components/Providers";
|
|
||||||
import Sidebar from "@/components/Sidebar";
|
|
||||||
import { getDictionary, Locale } from "@/app/dictionaries";
|
import { getDictionary, Locale } from "@/app/dictionaries";
|
||||||
import { DictionaryProvider } from "@/components/DictionaryContext";
|
import DashboardLayoutClient from "./DashboardLayoutClient";
|
||||||
|
|
||||||
export default async function DashboardLayout(
|
export default async function DashboardLayout(
|
||||||
props: {
|
props: {
|
||||||
@@ -12,10 +10,7 @@ export default async function DashboardLayout(
|
|||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
const params = await props.params;
|
const params = await props.params;
|
||||||
|
const { children } = props;
|
||||||
const {
|
|
||||||
children
|
|
||||||
} = props;
|
|
||||||
|
|
||||||
const session = await auth();
|
const session = await auth();
|
||||||
if (!session) redirect(`/${params.lang}/login`);
|
if (!session) redirect(`/${params.lang}/login`);
|
||||||
@@ -23,13 +18,8 @@ export default async function DashboardLayout(
|
|||||||
const dict = await getDictionary(params.lang as Locale);
|
const dict = await getDictionary(params.lang as Locale);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Providers>
|
<DashboardLayoutClient dict={dict} lang={params.lang}>
|
||||||
<DictionaryProvider dictionary={dict}>
|
{children}
|
||||||
<div className="app-layout">
|
</DashboardLayoutClient>
|
||||||
<Sidebar dict={dict} lang={params.lang} />
|
|
||||||
<div className="main-content">{children}</div>
|
|
||||||
</div>
|
|
||||||
</DictionaryProvider>
|
|
||||||
</Providers>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1085,6 +1085,46 @@ tr:hover td {
|
|||||||
background: var(--danger-dim);
|
background: var(--danger-dim);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ── Mobile Header ── */
|
||||||
|
.mobile-header {
|
||||||
|
display: none;
|
||||||
|
align-items: center;
|
||||||
|
gap: 16px;
|
||||||
|
padding: 12px 16px;
|
||||||
|
background: var(--bg-card);
|
||||||
|
border-bottom: 1px solid var(--border);
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
z-index: 50;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-menu-btn {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
color: var(--text-primary);
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 4px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-logo {
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 16px;
|
||||||
|
color: var(--accent-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-overlay {
|
||||||
|
display: none;
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
background: rgba(0, 0, 0, 0.5);
|
||||||
|
backdrop-filter: blur(4px);
|
||||||
|
z-index: 90;
|
||||||
|
animation: fadeIn 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
/* ── Responsive ── */
|
/* ── Responsive ── */
|
||||||
@media (max-width: 1024px) {
|
@media (max-width: 1024px) {
|
||||||
.mail-layout { grid-template-columns: 60px 280px 1fr; }
|
.mail-layout { grid-template-columns: 60px 280px 1fr; }
|
||||||
@@ -1097,12 +1137,42 @@ tr:hover td {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
.sidebar { display: none; }
|
.mobile-header { display: flex; }
|
||||||
.mail-layout { grid-template-columns: 1fr; }
|
|
||||||
|
.sidebar {
|
||||||
|
position: fixed;
|
||||||
|
left: -240px;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
z-index: 100;
|
||||||
|
transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
box-shadow: 10px 0 30px rgba(0,0,0,0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-open .sidebar {
|
||||||
|
transform: translateX(240px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-open .sidebar-overlay {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mail-layout { grid-template-columns: 1fr; height: auto; overflow: visible; }
|
||||||
.mail-sidebar { display: none; }
|
.mail-sidebar { display: none; }
|
||||||
.mail-detail { display: none; }
|
.mail-detail { display: none; }
|
||||||
|
|
||||||
|
/* Show active mail view if selected */
|
||||||
|
.mail-view-active .mail-list { display: none; }
|
||||||
|
.mail-view-active .mail-detail { display: block; }
|
||||||
|
|
||||||
.page-body { padding: 16px; }
|
.page-body { padding: 16px; }
|
||||||
.page-header { padding: 16px; }
|
.page-header {
|
||||||
|
padding: 16px;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
.page-header .btn { width: 100%; justify-content: center; }
|
||||||
|
|
||||||
.stats-grid { grid-template-columns: 1fr 1fr; }
|
.stats-grid { grid-template-columns: 1fr 1fr; }
|
||||||
}
|
}
|
||||||
/* ── Language Switcher ── */
|
/* ── Language Switcher ── */
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import Link from "next/link";
|
|||||||
import { usePathname } from "next/navigation";
|
import { usePathname } from "next/navigation";
|
||||||
import LanguageSwitcher from "./LanguageSwitcher";
|
import LanguageSwitcher from "./LanguageSwitcher";
|
||||||
|
|
||||||
export default function Sidebar({ dict, lang }: { dict: any; lang: string }) {
|
export default function Sidebar({ dict, lang, onClose }: { dict: any; lang: string; onClose?: () => void }) {
|
||||||
const { data: session } = useSession();
|
const { data: session } = useSession();
|
||||||
const pathname = usePathname();
|
const pathname = usePathname();
|
||||||
const role = session?.user?.role ?? "";
|
const role = session?.user?.role ?? "";
|
||||||
@@ -61,6 +61,7 @@ export default function Sidebar({ dict, lang }: { dict: any; lang: string }) {
|
|||||||
key={item.href}
|
key={item.href}
|
||||||
href={item.href}
|
href={item.href}
|
||||||
className={`nav-item ${pathname === item.href ? "active" : ""}`}
|
className={`nav-item ${pathname === item.href ? "active" : ""}`}
|
||||||
|
onClick={onClose}
|
||||||
>
|
>
|
||||||
<item.icon />
|
<item.icon />
|
||||||
{item.label}
|
{item.label}
|
||||||
|
|||||||
Reference in New Issue
Block a user