From e42fd4697f4db4c34e2a8500371778ac94ee1bff Mon Sep 17 00:00:00 2001 From: ogrechko Date: Mon, 7 Sep 2026 07:58:52 +0000 Subject: [PATCH] Add mobile bottom nav + PWA installability Same shell pattern as top-buh/top-finance/thbd-portal: desktop top nav stays >=lg, a fixed bottom tab bar takes over below it. Only 2 nav links here so both fit directly, no overflow menu needed. Also adds manifest.webmanifest + 192/512 icons (rendered from the existing icon.svg) and appleWebApp/viewport metadata so the app can be installed to a phone's home screen. Nudged ticket-toasts.tsx's fixed bottom offset so it clears the new bottom nav bar on mobile instead of overlapping it. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01Gh2UXUQUVBWroWEnn1FLFG --- public/icon-192.png | Bin 0 -> 3276 bytes public/icon-512.png | Bin 0 -> 18165 bytes public/manifest.webmanifest | 14 ++++++++++++ src/app/(admin)/layout.tsx | 21 ++++++++++-------- src/app/(admin)/mobile-nav.tsx | 34 ++++++++++++++++++++++++++++++ src/app/(admin)/nav-data.ts | 13 ++++++++++++ src/app/(admin)/nav-links.tsx | 7 +----- src/app/(admin)/nav-shell.tsx | 16 ++++++++++++++ src/app/(admin)/ticket-toasts.tsx | 2 +- src/app/layout.tsx | 19 ++++++++++++++++- 10 files changed, 109 insertions(+), 17 deletions(-) create mode 100644 public/icon-192.png create mode 100644 public/icon-512.png create mode 100644 public/manifest.webmanifest create mode 100644 src/app/(admin)/mobile-nav.tsx create mode 100644 src/app/(admin)/nav-data.ts create mode 100644 src/app/(admin)/nav-shell.tsx diff --git a/public/icon-192.png b/public/icon-192.png new file mode 100644 index 0000000000000000000000000000000000000000..c60a041dd9e867529ef574ff901a519f154f0dd6 GIT binary patch literal 3276 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 Ig*hea|237zg8%>k literal 0 HcmV?d00001 diff --git a/public/icon-512.png b/public/icon-512.png new file mode 100644 index 0000000000000000000000000000000000000000..0de14525397afa4c8a58ed94e0091c5107537ae1 GIT binary patch literal 18165 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 Nx|-KCiqx#0{y*HscUb@c literal 0 HcmV?d00001 diff --git a/public/manifest.webmanifest b/public/manifest.webmanifest new file mode 100644 index 0000000..6e0f6dd --- /dev/null +++ b/public/manifest.webmanifest @@ -0,0 +1,14 @@ +{ + "name": "top-tickets", + "short_name": "top-tickets", + "description": "Helpdesk for support@top-sysops.ru, Telegram, and the web.", + "start_url": "/dashboard", + "scope": "/", + "display": "standalone", + "background_color": "#f8f7fb", + "theme_color": "#6d28d9", + "icons": [ + { "src": "/icon-192.png", "sizes": "192x192", "type": "image/png", "purpose": "any" }, + { "src": "/icon-512.png", "sizes": "512x512", "type": "image/png", "purpose": "any" } + ] +} diff --git a/src/app/(admin)/layout.tsx b/src/app/(admin)/layout.tsx index 50f1fcc..5320b16 100644 --- a/src/app/(admin)/layout.tsx +++ b/src/app/(admin)/layout.tsx @@ -3,7 +3,7 @@ import Link from "next/link"; import { Ticket } from "lucide-react"; import { getCurrentSession } from "@/lib/auth/session"; import { LogoutButton } from "./logout-button"; -import { NavLinks } from "./nav-links"; +import { NavShell } from "./nav-shell"; import { ThemeToggle } from "./theme-toggle"; import { SettingsMenu } from "./settings-menu"; import { NotificationToggle } from "./notification-toggle"; @@ -21,18 +21,18 @@ export default async function AdminLayout({ children }: { children: React.ReactN return (
-
-
- +
+
+
- top-tickets + top-tickets -
- +
+
-
+
{isAdmin && } @@ -41,7 +41,10 @@ export default async function AdminLayout({ children }: { children: React.ReactN
-
{children}
+ {/* pb: clears the fixed mobile bottom-tab bar (~52px + safe-area) so it never covers the last bit of scrolled content */} +
+ {children} +
diff --git a/src/app/(admin)/mobile-nav.tsx b/src/app/(admin)/mobile-nav.tsx new file mode 100644 index 0000000..0a0d049 --- /dev/null +++ b/src/app/(admin)/mobile-nav.tsx @@ -0,0 +1,34 @@ +"use client"; + +import Link from "next/link"; +import { usePathname } from "next/navigation"; +import { links } from "./nav-data"; + +/** Bottom tab bar for mobile — mirrors top-buh/top-finance's pattern. Only 2 links here, both fit as primary tabs, no overflow "Ещё" needed. */ +export function MobileNav({ isAdmin }: { isAdmin: boolean }) { + const pathname = usePathname(); + const visibleLinks = links.filter((link) => !link.adminOnly || isAdmin); + + return ( + + ); +} diff --git a/src/app/(admin)/nav-data.ts b/src/app/(admin)/nav-data.ts new file mode 100644 index 0000000..db568aa --- /dev/null +++ b/src/app/(admin)/nav-data.ts @@ -0,0 +1,13 @@ +import { LayoutDashboard, BarChart3 } from "lucide-react"; + +export interface NavLink { + href: string; + label: string; + icon: typeof LayoutDashboard; + adminOnly: boolean; +} + +export const links: NavLink[] = [ + { href: "/dashboard", label: "Заявки", icon: LayoutDashboard, adminOnly: false }, + { href: "/stats", label: "Статистика", icon: BarChart3, adminOnly: true }, +]; diff --git a/src/app/(admin)/nav-links.tsx b/src/app/(admin)/nav-links.tsx index 9448482..42e8ba6 100644 --- a/src/app/(admin)/nav-links.tsx +++ b/src/app/(admin)/nav-links.tsx @@ -2,12 +2,7 @@ import Link from "next/link"; import { usePathname } from "next/navigation"; -import { LayoutDashboard, BarChart3 } from "lucide-react"; - -const links = [ - { href: "/dashboard", label: "Заявки", icon: LayoutDashboard, adminOnly: false }, - { href: "/stats", label: "Статистика", icon: BarChart3, adminOnly: true }, -]; +import { links } from "./nav-data"; export function NavLinks({ isAdmin }: { isAdmin: boolean }) { const pathname = usePathname(); diff --git a/src/app/(admin)/nav-shell.tsx b/src/app/(admin)/nav-shell.tsx new file mode 100644 index 0000000..f055262 --- /dev/null +++ b/src/app/(admin)/nav-shell.tsx @@ -0,0 +1,16 @@ +"use client"; + +import { NavLinks } from "./nav-links"; +import { MobileNav } from "./mobile-nav"; + +/** Desktop top nav (>=lg) + mobile bottom tab bar ( +
+ +
+ + + ); +} diff --git a/src/app/(admin)/ticket-toasts.tsx b/src/app/(admin)/ticket-toasts.tsx index 47a8a54..6550c13 100644 --- a/src/app/(admin)/ticket-toasts.tsx +++ b/src/app/(admin)/ticket-toasts.tsx @@ -103,7 +103,7 @@ export function TicketToasts({ currentUser }: { currentUser: { id: string; role: } return ( -
+
{toasts.map((toast) => (