diff options
| -rw-r--r-- | apps/web/app/admin/layout.tsx | 63 | ||||
| -rw-r--r-- | apps/web/app/dashboard/layout.tsx | 89 | ||||
| -rw-r--r-- | apps/web/app/settings/layout.tsx | 86 | ||||
| -rw-r--r-- | apps/web/components/admin/sidebar/items.tsx | 31 | ||||
| -rw-r--r-- | apps/web/components/dashboard/sidebar/ModileSidebar.tsx | 23 | ||||
| -rw-r--r-- | apps/web/components/dashboard/sidebar/Sidebar.tsx | 81 | ||||
| -rw-r--r-- | apps/web/components/settings/sidebar/ModileSidebar.tsx | 21 | ||||
| -rw-r--r-- | apps/web/components/settings/sidebar/Sidebar.tsx | 36 | ||||
| -rw-r--r-- | apps/web/components/settings/sidebar/items.tsx | 55 | ||||
| -rw-r--r-- | apps/web/components/shared/sidebar/MobileSidebar.tsx (renamed from apps/web/components/admin/sidebar/MobileSidebar.tsx) | 13 | ||||
| -rw-r--r-- | apps/web/components/shared/sidebar/Sidebar.tsx (renamed from apps/web/components/admin/sidebar/Sidebar.tsx) | 22 | ||||
| -rw-r--r-- | apps/web/components/shared/sidebar/SidebarLayout.tsx | 37 | ||||
| -rw-r--r-- | apps/web/components/shared/sidebar/TSidebarItem.ts | 5 |
13 files changed, 233 insertions, 329 deletions
diff --git a/apps/web/app/admin/layout.tsx b/apps/web/app/admin/layout.tsx index 0d876736..7b20b7ad 100644 --- a/apps/web/app/admin/layout.tsx +++ b/apps/web/app/admin/layout.tsx @@ -1,11 +1,41 @@ import { redirect } from "next/navigation"; import { AdminCard } from "@/components/admin/AdminCard"; import { AdminNotices } from "@/components/admin/AdminNotices"; -import MobileAdminSidebar from "@/components/admin/sidebar/MobileSidebar"; -import AdminSidebar from "@/components/admin/sidebar/Sidebar"; -import Header from "@/components/dashboard/header/Header"; -import { Separator } from "@/components/ui/separator"; +import MobileSidebar from "@/components/shared/sidebar/MobileSidebar"; +import Sidebar from "@/components/shared/sidebar/Sidebar"; +import SidebarLayout from "@/components/shared/sidebar/SidebarLayout"; import { getServerAuthSession } from "@/server/auth"; +import { TFunction } from "i18next"; +import { Activity, ArrowLeft, Settings, Users } from "lucide-react"; + +const adminSidebarItems = ( + t: TFunction, +): { + name: string; + icon: JSX.Element; + path: string; +}[] => [ + { + name: t("settings.back_to_app"), + icon: <ArrowLeft size={18} />, + path: "/dashboard/bookmarks", + }, + { + name: t("admin.server_stats.server_stats"), + icon: <Activity size={18} />, + path: "/admin/overview", + }, + { + name: t("admin.users_list.users_list"), + icon: <Users size={18} />, + path: "/admin/users", + }, + { + name: t("common.actions"), + icon: <Settings size={18} />, + path: "/admin/actions", + }, +]; export default async function AdminLayout({ children, @@ -18,23 +48,14 @@ export default async function AdminLayout({ } return ( - <div> - <Header /> - <div className="flex min-h-[calc(100vh-64px)] w-screen flex-col sm:h-[calc(100vh-64px)] sm:flex-row"> - <div className="hidden flex-none sm:flex"> - <AdminSidebar /> - </div> - <main className="flex-1 bg-muted sm:overflow-y-auto"> - <div className="block w-full sm:hidden"> - <MobileAdminSidebar /> - <Separator /> - </div> - <div className="min-h-30 container flex flex-col gap-1 p-4"> - <AdminNotices /> - <AdminCard>{children}</AdminCard> - </div> - </main> + <SidebarLayout + sidebar={<Sidebar items={adminSidebarItems} />} + mobileSidebar={<MobileSidebar items={adminSidebarItems} />} + > + <div className="flex flex-col gap-1"> + <AdminNotices /> + <AdminCard>{children}</AdminCard> </div> - </div> + </SidebarLayout> ); } diff --git a/apps/web/app/dashboard/layout.tsx b/apps/web/app/dashboard/layout.tsx index cbd51245..17a7c144 100644 --- a/apps/web/app/dashboard/layout.tsx +++ b/apps/web/app/dashboard/layout.tsx @@ -1,9 +1,13 @@ -import Header from "@/components/dashboard/header/Header"; -import MobileSidebar from "@/components/dashboard/sidebar/ModileSidebar"; -import Sidebar from "@/components/dashboard/sidebar/Sidebar"; -import DemoModeBanner from "@/components/DemoModeBanner"; +import { redirect } from "next/navigation"; +import AllLists from "@/components/dashboard/sidebar/AllLists"; +import MobileSidebar from "@/components/shared/sidebar/MobileSidebar"; +import Sidebar from "@/components/shared/sidebar/Sidebar"; +import SidebarLayout from "@/components/shared/sidebar/SidebarLayout"; import { Separator } from "@/components/ui/separator"; -import ValidAccountCheck from "@/components/utils/ValidAccountCheck"; +import { api } from "@/server/api/client"; +import { getServerAuthSession } from "@/server/auth"; +import { TFunction } from "i18next"; +import { Archive, Highlighter, Home, Search, Tag } from "lucide-react"; import serverConfig from "@hoarder/shared/config"; @@ -14,24 +18,63 @@ export default async function Dashboard({ children: React.ReactNode; modal: React.ReactNode; }>) { + const session = await getServerAuthSession(); + if (!session) { + redirect("/"); + } + + const lists = await api.lists.list(); + + const items = (t: TFunction) => + [ + { + name: t("common.home"), + icon: <Home size={18} />, + path: "/dashboard/bookmarks", + }, + serverConfig.meilisearch + ? [ + { + name: t("common.search"), + icon: <Search size={18} />, + path: "/dashboard/search", + }, + ] + : [], + { + name: t("common.tags"), + icon: <Tag size={18} />, + path: "/dashboard/tags", + }, + { + name: t("common.highlights"), + icon: <Highlighter size={18} />, + path: "/dashboard/highlights", + }, + { + name: t("common.archive"), + icon: <Archive size={18} />, + path: "/dashboard/archive", + }, + ].flat(); + return ( - <div> - <Header /> - <div className="flex min-h-[calc(100vh-64px)] w-screen flex-col sm:h-[calc(100vh-64px)] sm:flex-row"> - <ValidAccountCheck /> - <div className="hidden flex-none sm:flex"> - <Sidebar /> - </div> - <main className="flex-1 bg-muted sm:overflow-y-auto"> - {serverConfig.demoMode && <DemoModeBanner />} - <div className="block w-full sm:hidden"> - <MobileSidebar /> - <Separator /> - </div> - {modal} - <div className="min-h-30 container p-4">{children}</div> - </main> - </div> - </div> + <SidebarLayout + sidebar={ + <Sidebar + items={items} + extraSections={ + <> + <Separator /> + <AllLists initialData={lists} /> + </> + } + /> + } + mobileSidebar={<MobileSidebar items={items} />} + modal={modal} + > + {children} + </SidebarLayout> ); } diff --git a/apps/web/app/settings/layout.tsx b/apps/web/app/settings/layout.tsx index 0ab6c624..bbff68a9 100644 --- a/apps/web/app/settings/layout.tsx +++ b/apps/web/app/settings/layout.tsx @@ -1,11 +1,60 @@ -import Header from "@/components/dashboard/header/Header"; -import DemoModeBanner from "@/components/DemoModeBanner"; -import MobileSidebar from "@/components/settings/sidebar/ModileSidebar"; -import Sidebar from "@/components/settings/sidebar/Sidebar"; -import { Separator } from "@/components/ui/separator"; -import ValidAccountCheck from "@/components/utils/ValidAccountCheck"; +import MobileSidebar from "@/components/shared/sidebar/MobileSidebar"; +import Sidebar from "@/components/shared/sidebar/Sidebar"; +import SidebarLayout from "@/components/shared/sidebar/SidebarLayout"; +import { TFunction } from "i18next"; +import { + ArrowLeft, + Download, + KeyRound, + Link, + Rss, + Sparkles, + User, +} from "lucide-react"; -import serverConfig from "@hoarder/shared/config"; +const settingsSidebarItems = ( + t: TFunction, +): { + name: string; + icon: JSX.Element; + path: string; +}[] => [ + { + name: t("settings.back_to_app"), + icon: <ArrowLeft size={18} />, + path: "/dashboard/bookmarks", + }, + { + name: t("settings.info.user_info"), + icon: <User size={18} />, + path: "/settings/info", + }, + { + name: t("settings.ai.ai_settings"), + icon: <Sparkles size={18} />, + path: "/settings/ai", + }, + { + name: t("settings.feeds.rss_subscriptions"), + icon: <Rss size={18} />, + path: "/settings/feeds", + }, + { + name: t("settings.import.import_export"), + icon: <Download size={18} />, + path: "/settings/import", + }, + { + name: t("settings.api_keys.api_keys"), + icon: <KeyRound size={18} />, + path: "/settings/api-keys", + }, + { + name: t("settings.broken_links.broken_links"), + icon: <Link size={18} />, + path: "/settings/broken-links", + }, +]; export default async function SettingsLayout({ children, @@ -13,22 +62,11 @@ export default async function SettingsLayout({ children: React.ReactNode; }>) { return ( - <div> - <Header /> - <div className="flex min-h-[calc(100vh-64px)] w-screen flex-col sm:h-[calc(100vh-64px)] sm:flex-row"> - <ValidAccountCheck /> - <div className="hidden flex-none sm:flex"> - <Sidebar /> - </div> - <main className="flex-1 bg-muted sm:overflow-y-auto"> - {serverConfig.demoMode && <DemoModeBanner />} - <div className="block w-full sm:hidden"> - <MobileSidebar /> - <Separator /> - </div> - <div className="min-h-30 container p-4">{children}</div> - </main> - </div> - </div> + <SidebarLayout + sidebar={<Sidebar items={settingsSidebarItems} />} + mobileSidebar={<MobileSidebar items={settingsSidebarItems} />} + > + {children} + </SidebarLayout> ); } diff --git a/apps/web/components/admin/sidebar/items.tsx b/apps/web/components/admin/sidebar/items.tsx deleted file mode 100644 index 78dfee34..00000000 --- a/apps/web/components/admin/sidebar/items.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import { TFunction } from "i18next"; -import { Activity, ArrowLeft, Settings, Users } from "lucide-react"; - -export const adminSidebarItems = ( - t: TFunction, -): { - name: string; - icon: JSX.Element; - path: string; -}[] => [ - { - name: t("settings.back_to_app"), - icon: <ArrowLeft size={18} />, - path: "/dashboard/bookmarks", - }, - { - name: t("admin.server_stats.server_stats"), - icon: <Activity size={18} />, - path: "/admin/overview", - }, - { - name: t("admin.users_list.users_list"), - icon: <Users size={18} />, - path: "/admin/users", - }, - { - name: t("common.actions"), - icon: <Settings size={18} />, - path: "/admin/actions", - }, -]; diff --git a/apps/web/components/dashboard/sidebar/ModileSidebar.tsx b/apps/web/components/dashboard/sidebar/ModileSidebar.tsx deleted file mode 100644 index 777877bf..00000000 --- a/apps/web/components/dashboard/sidebar/ModileSidebar.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import MobileSidebarItem from "@/components/shared/sidebar/ModileSidebarItem"; -import HoarderLogoIcon from "@/public/icons/logo-icon.svg"; -import { ClipboardList, Highlighter, Search, Tag } from "lucide-react"; - -export default async function MobileSidebar() { - return ( - <aside className="w-full"> - <ul className="flex justify-between space-x-2 border-b-black px-5 py-2 pt-5"> - <MobileSidebarItem - logo={<HoarderLogoIcon className="w-5 fill-foreground" />} - path="/dashboard/bookmarks" - /> - <MobileSidebarItem logo={<Search />} path="/dashboard/search" /> - <MobileSidebarItem logo={<ClipboardList />} path="/dashboard/lists" /> - <MobileSidebarItem logo={<Tag />} path="/dashboard/tags" /> - <MobileSidebarItem - logo={<Highlighter />} - path="/dashboard/highlights" - /> - </ul> - </aside> - ); -} diff --git a/apps/web/components/dashboard/sidebar/Sidebar.tsx b/apps/web/components/dashboard/sidebar/Sidebar.tsx deleted file mode 100644 index 0f805a09..00000000 --- a/apps/web/components/dashboard/sidebar/Sidebar.tsx +++ /dev/null @@ -1,81 +0,0 @@ -import { redirect } from "next/navigation"; -import SidebarItem from "@/components/shared/sidebar/SidebarItem"; -import { Separator } from "@/components/ui/separator"; -import { useTranslation } from "@/lib/i18n/server"; -import { api } from "@/server/api/client"; -import { getServerAuthSession } from "@/server/auth"; -import { Archive, Highlighter, Home, Search, Tag } from "lucide-react"; - -import serverConfig from "@hoarder/shared/config"; - -import AllLists from "./AllLists"; - -export default async function Sidebar() { - const { t } = await useTranslation(); - const session = await getServerAuthSession(); - if (!session) { - redirect("/"); - } - - const lists = await api.lists.list(); - - const searchItem = serverConfig.meilisearch - ? [ - { - name: t("common.search"), - icon: <Search size={18} />, - path: "/dashboard/search", - }, - ] - : []; - - const menu: { - name: string; - icon: JSX.Element; - path: string; - }[] = [ - { - name: t("common.home"), - icon: <Home size={18} />, - path: "/dashboard/bookmarks", - }, - ...searchItem, - { - name: t("common.tags"), - icon: <Tag size={18} />, - path: "/dashboard/tags", - }, - { - name: t("common.highlights"), - icon: <Highlighter size={18} />, - path: "/dashboard/highlights", - }, - { - name: t("common.archive"), - icon: <Archive size={18} />, - path: "/dashboard/archive", - }, - ]; - - return ( - <aside className="flex h-[calc(100vh-64px)] w-60 flex-col gap-5 border-r p-4 "> - <div> - <ul className="space-y-2 text-sm font-medium"> - {menu.map((item) => ( - <SidebarItem - key={item.name} - logo={item.icon} - name={item.name} - path={item.path} - /> - ))} - </ul> - </div> - <Separator /> - <AllLists initialData={lists} /> - <div className="mt-auto flex items-center border-t pt-2 text-sm text-gray-400"> - Hoarder v{serverConfig.serverVersion} - </div> - </aside> - ); -} diff --git a/apps/web/components/settings/sidebar/ModileSidebar.tsx b/apps/web/components/settings/sidebar/ModileSidebar.tsx deleted file mode 100644 index cbed9ef9..00000000 --- a/apps/web/components/settings/sidebar/ModileSidebar.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import MobileSidebarItem from "@/components/shared/sidebar/ModileSidebarItem"; -import { useTranslation } from "@/lib/i18n/server"; - -import { settingsSidebarItems } from "./items"; - -export default async function MobileSidebar() { - const { t } = await useTranslation(); - return ( - <aside className="w-full"> - <ul className="flex justify-between space-x-2 border-b-black px-5 py-2 pt-5"> - {settingsSidebarItems(t).map((item) => ( - <MobileSidebarItem - key={item.name} - logo={item.icon} - path={item.path} - /> - ))} - </ul> - </aside> - ); -} diff --git a/apps/web/components/settings/sidebar/Sidebar.tsx b/apps/web/components/settings/sidebar/Sidebar.tsx deleted file mode 100644 index a1b61e98..00000000 --- a/apps/web/components/settings/sidebar/Sidebar.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import { redirect } from "next/navigation"; -import SidebarItem from "@/components/shared/sidebar/SidebarItem"; -import { useTranslation } from "@/lib/i18n/server"; -import { getServerAuthSession } from "@/server/auth"; - -import serverConfig from "@hoarder/shared/config"; - -import { settingsSidebarItems } from "./items"; - -export default async function Sidebar() { - const { t } = await useTranslation(); - const session = await getServerAuthSession(); - if (!session) { - redirect("/"); - } - - return ( - <aside className="flex h-[calc(100vh-64px)] w-60 flex-col gap-5 border-r p-4 "> - <div> - <ul className="space-y-2 text-sm font-medium"> - {settingsSidebarItems(t).map((item) => ( - <SidebarItem - key={item.name} - logo={item.icon} - name={item.name} - path={item.path} - /> - ))} - </ul> - </div> - <div className="mt-auto flex items-center border-t pt-2 text-sm text-gray-400"> - Hoarder v{serverConfig.serverVersion} - </div> - </aside> - ); -} diff --git a/apps/web/components/settings/sidebar/items.tsx b/apps/web/components/settings/sidebar/items.tsx deleted file mode 100644 index f76d494a..00000000 --- a/apps/web/components/settings/sidebar/items.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import React from "react"; -import { TFunction } from "i18next"; -import { - ArrowLeft, - Download, - KeyRound, - Link, - Rss, - Sparkles, - User, -} from "lucide-react"; - -export const settingsSidebarItems = ( - t: TFunction, -): { - name: string; - icon: JSX.Element; - path: string; -}[] => [ - { - name: t("settings.back_to_app"), - icon: <ArrowLeft size={18} />, - path: "/dashboard/bookmarks", - }, - { - name: t("settings.info.user_info"), - icon: <User size={18} />, - path: "/settings/info", - }, - { - name: t("settings.ai.ai_settings"), - icon: <Sparkles size={18} />, - path: "/settings/ai", - }, - { - name: t("settings.feeds.rss_subscriptions"), - icon: <Rss size={18} />, - path: "/settings/feeds", - }, - { - name: t("settings.import.import_export"), - icon: <Download size={18} />, - path: "/settings/import", - }, - { - name: t("settings.api_keys.api_keys"), - icon: <KeyRound size={18} />, - path: "/settings/api-keys", - }, - { - name: t("settings.broken_links.broken_links"), - icon: <Link size={18} />, - path: "/settings/broken-links", - }, -]; diff --git a/apps/web/components/admin/sidebar/MobileSidebar.tsx b/apps/web/components/shared/sidebar/MobileSidebar.tsx index 416b944c..d3edc7df 100644 --- a/apps/web/components/admin/sidebar/MobileSidebar.tsx +++ b/apps/web/components/shared/sidebar/MobileSidebar.tsx @@ -1,14 +1,19 @@ -import MobileSidebarItem from "@/components/shared/sidebar/ModileSidebarItem"; import { useTranslation } from "@/lib/i18n/server"; +import { TFunction } from "i18next"; -import { adminSidebarItems } from "./items"; +import MobileSidebarItem from "./ModileSidebarItem"; +import { TSidebarItem } from "./TSidebarItem"; -export default async function MobileSidebar() { +export default async function MobileSidebar({ + items, +}: { + items: (t: TFunction) => TSidebarItem[]; +}) { const { t } = await useTranslation(); return ( <aside className="w-full"> <ul className="flex justify-between space-x-2 border-b-black px-5 py-2 pt-5"> - {adminSidebarItems(t).map((item) => ( + {items(t).map((item) => ( <MobileSidebarItem key={item.name} logo={item.icon} diff --git a/apps/web/components/admin/sidebar/Sidebar.tsx b/apps/web/components/shared/sidebar/Sidebar.tsx index 8a5d615a..bd5d23e6 100644 --- a/apps/web/components/admin/sidebar/Sidebar.tsx +++ b/apps/web/components/shared/sidebar/Sidebar.tsx @@ -1,24 +1,25 @@ -import { redirect } from "next/navigation"; -import SidebarItem from "@/components/shared/sidebar/SidebarItem"; import { useTranslation } from "@/lib/i18n/server"; -import { getServerAuthSession } from "@/server/auth"; +import { TFunction } from "i18next"; import serverConfig from "@hoarder/shared/config"; -import { adminSidebarItems } from "./items"; +import SidebarItem from "./SidebarItem"; +import { TSidebarItem } from "./TSidebarItem"; -export default async function Sidebar() { +export default async function Sidebar({ + items, + extraSections, +}: { + items: (t: TFunction) => TSidebarItem[]; + extraSections?: React.ReactNode; +}) { const { t } = await useTranslation(); - const session = await getServerAuthSession(); - if (!session) { - redirect("/"); - } return ( <aside className="flex h-[calc(100vh-64px)] w-60 flex-col gap-5 border-r p-4 "> <div> <ul className="space-y-2 text-sm font-medium"> - {adminSidebarItems(t).map((item) => ( + {items(t).map((item) => ( <SidebarItem key={item.name} logo={item.icon} @@ -28,6 +29,7 @@ export default async function Sidebar() { ))} </ul> </div> + {extraSections} <div className="mt-auto flex items-center border-t pt-2 text-sm text-gray-400"> Hoarder v{serverConfig.serverVersion} </div> diff --git a/apps/web/components/shared/sidebar/SidebarLayout.tsx b/apps/web/components/shared/sidebar/SidebarLayout.tsx new file mode 100644 index 00000000..2a2a872e --- /dev/null +++ b/apps/web/components/shared/sidebar/SidebarLayout.tsx @@ -0,0 +1,37 @@ +import Header from "@/components/dashboard/header/Header"; +import DemoModeBanner from "@/components/DemoModeBanner"; +import { Separator } from "@/components/ui/separator"; +import ValidAccountCheck from "@/components/utils/ValidAccountCheck"; + +import serverConfig from "@hoarder/shared/config"; + +export default function SidebarLayout({ + children, + mobileSidebar, + sidebar, + modal, +}: { + children: React.ReactNode; + mobileSidebar: React.ReactNode; + sidebar: React.ReactNode; + modal?: React.ReactNode; +}) { + return ( + <div> + <Header /> + <div className="flex min-h-[calc(100vh-64px)] w-screen flex-col sm:h-[calc(100vh-64px)] sm:flex-row"> + <ValidAccountCheck /> + <div className="hidden flex-none sm:flex">{sidebar}</div> + <main className="flex-1 bg-muted sm:overflow-y-auto"> + {serverConfig.demoMode && <DemoModeBanner />} + <div className="block w-full sm:hidden"> + {mobileSidebar} + <Separator /> + </div> + {modal} + <div className="min-h-30 container p-4">{children}</div> + </main> + </div> + </div> + ); +} diff --git a/apps/web/components/shared/sidebar/TSidebarItem.ts b/apps/web/components/shared/sidebar/TSidebarItem.ts new file mode 100644 index 00000000..84cd58f5 --- /dev/null +++ b/apps/web/components/shared/sidebar/TSidebarItem.ts @@ -0,0 +1,5 @@ +export interface TSidebarItem { + name: string; + icon: JSX.Element; + path: string; +} |
