From 8d9f677c4e9ccfcc6dc9297864dc49446fb5ee59 Mon Sep 17 00:00:00 2001
From: Ellie Huxtable <ellie@atuin.sh>
Date: Wed, 10 Jul 2024 15:56:33 +0100
Subject: feat(gui): use fancy new side nav (#2243)

* feat(gui): use fancy new side nav

* compact only sidebar, no expand-collapse

* custom drag region, remove titlebar

* add user popup

* wire up login/logout/register, move user button to bottom and add menu

* link help and feedback to forum
---
 ui/src/App.tsx | 257 +++++++++++++++++++++++++++++++++++++++++----------------
 1 file changed, 184 insertions(+), 73 deletions(-)

(limited to 'ui/src/App.tsx')

diff --git a/ui/src/App.tsx b/ui/src/App.tsx
index 6dba3d71..7a9ac395 100644
--- a/ui/src/App.tsx
+++ b/ui/src/App.tsx
@@ -1,10 +1,17 @@
 import "./App.css";
+import { open } from "@tauri-apps/plugin-shell";
 
-import { useState, ReactElement } from "react";
+import { useState, ReactElement, useEffect } from "react";
 import { useStore } from "@/state/store";
 
-import Button, { ButtonStyle } from "@/components/Button";
 import { Toaster } from "@/components/ui/toaster";
+import {
+  SettingsIcon,
+  CircleHelpIcon,
+  KeyRoundIcon,
+  LogOutIcon,
+} from "lucide-react";
+import { Icon } from "@iconify/react";
 
 import { Dialog, DialogContent, DialogTrigger } from "@/components/ui/dialog";
 
@@ -28,6 +35,35 @@ import Dotfiles from "./pages/Dotfiles.tsx";
 import LoginOrRegister from "./components/LoginOrRegister.tsx";
 import Runbooks from "./pages/Runbooks.tsx";
 
+import {
+  Avatar,
+  User,
+  Button,
+  ScrollShadow,
+  Spacer,
+  Tooltip,
+  Dropdown,
+  DropdownItem,
+  DropdownMenu,
+  DropdownSection,
+  DropdownTrigger,
+  Modal,
+  ModalContent,
+  ModalHeader,
+  ModalBody,
+  ModalFooter,
+  useDisclosure,
+  Checkbox,
+  Input,
+  Link,
+} from "@nextui-org/react";
+import { cn } from "@/lib/utils";
+import { sectionItems } from "@/components/Sidebar/sidebar-items";
+import Sidebar, { SidebarItem } from "@/components/Sidebar";
+import icon from "@/assets/icon.svg";
+import iconText from "@/assets/logo-light.svg";
+import { logout } from "./state/client.ts";
+
 enum Section {
   Home,
   History,
@@ -54,90 +90,165 @@ function App() {
   // pages
   const [section, setSection] = useState(Section.Home);
   const user = useStore((state) => state.user);
-  console.log(user);
+  const refreshUser = useStore((state) => state.refreshUser);
+  const { isOpen, onOpen, onOpenChange } = useDisclosure();
 
-  const navigation = [
-    {
-      name: "Home",
-      icon: HomeIcon,
-      section: Section.Home,
-    },
-    {
-      name: "History",
-      icon: ClockIcon,
-      section: Section.History,
-    },
-    {
-      name: "Dotfiles",
-      icon: WrenchScrewdriverIcon,
-      section: Section.Dotfiles,
-    },
+  const navigation: SidebarItem[] = [
     {
-      name: "Runbooks",
-      icon: ChevronRightSquare,
-      section: Section.Runbooks,
+      key: "personal",
+      title: "Personal",
+      items: [
+        {
+          key: "home",
+          icon: "solar:home-2-linear",
+          title: "Home",
+          onPress: () => setSection(Section.Home),
+        },
+        {
+          key: "runbooks",
+          icon: "solar:notebook-linear",
+          title: "Runbooks",
+          onPress: () => {
+            console.log("runbooks");
+            setSection(Section.Runbooks);
+          },
+        },
+        {
+          key: "history",
+          icon: "solar:history-outline",
+          title: "History",
+          onPress: () => setSection(Section.History),
+        },
+        {
+          key: "dotfiles",
+          icon: "solar:file-smile-linear",
+          title: "Dotfiles",
+          onPress: () => setSection(Section.Dotfiles),
+        },
+      ],
     },
   ];
 
   return (
-    <div>
-      <div className="fixed inset-y-0 z-50 flex w-60 flex-col">
-        <div className="flex grow flex-col gap-y-5 overflow-y-auto border-r border-gray-200 bg-white px-6 pb-4">
-          <div className="flex h-16 shrink-0 items-center">
-            <img className="h-8 w-auto" src={Logo} alt="Atuin" />
+    <div className="flex h-dvh w-full select-none">
+      <div className="relative flex h-full flex-col !border-r-small border-divider p-6 transition-width px-2 pb-6 pt-9 w-16 items-center">
+        <div className="flex items-center gap-0 px-3 justify-center">
+          <div className="flex h-8 w-8">
+            <img src={icon} alt="icon" className="h-8 w-8" />
           </div>
-          <nav className="flex flex-1 flex-col">
-            <ul role="list" className="flex flex-1 flex-col gap-y-7">
-              <li>
-                <ul role="list" className="-mx-2 space-y-1 w-full">
-                  {navigation.map((item) => (
-                    <li key={item.name}>
-                      <button
-                        onClick={() => setSection(item.section)}
-                        className={classNames(
-                          section == item.section
-                            ? "bg-gray-50 text-green-600"
-                            : "text-gray-700 hover:text-green-600 hover:bg-gray-50",
-                          "group flex gap-x-3 rounded-md p-2 text-sm leading-6 font-semibold w-full",
-                        )}
-                      >
-                        <item.icon
-                          className={classNames(
-                            section == item.section
-                              ? "text-green-600"
-                              : "text-gray-400 group-hover:text-green-600",
-                            "h-6 w-6 shrink-0",
-                          )}
-                          aria-hidden="true"
-                        />
-                        {item.name}
-                      </button>
-                    </li>
-                  ))}
-                </ul>
-              </li>
-              <li className="mt-auto">
-                {user && user.username === "" && !user.username && (
-                  <Dialog>
-                    <DialogTrigger className="w-full">
-                      <Button
-                        text={"Login or Register"}
-                        style={ButtonStyle.PrimarySmFill}
-                      />
-                    </DialogTrigger>
-                    <DialogContent>
-                      <LoginOrRegister />
-                    </DialogContent>
-                  </Dialog>
+        </div>
+
+        <ScrollShadow className="-mr-6 h-full max-h-full py-6 pr-6">
+          <Sidebar
+            defaultSelectedKey="home"
+            isCompact={true}
+            items={navigation}
+          />
+        </ScrollShadow>
+
+        <Spacer y={2} />
+
+        <div className="flex items-center gap-3 px-3">
+          <Dropdown showArrow placement="right-start">
+            <DropdownTrigger>
+              <Button disableRipple isIconOnly radius="full" variant="light">
+                <Avatar
+                  isBordered
+                  className="flex-none"
+                  size="sm"
+                  name={user.username || ""}
+                />
+              </Button>
+            </DropdownTrigger>
+            <DropdownMenu aria-label="Custom item styles">
+              <DropdownItem
+                key="profile"
+                isReadOnly
+                className="h-14 opacity-100"
+                textValue="Signed in as"
+              >
+                <User
+                  avatarProps={{
+                    size: "sm",
+                    name: user.username || "Anonymous User",
+                    showFallback: true,
+                    imgProps: {
+                      className: "transition-none",
+                    },
+                  }}
+                  classNames={{
+                    name: "text-default-600",
+                    description: "text-default-500",
+                  }}
+                  description={
+                    user.bio || (user.username && "No bio") || "Sign up now"
+                  }
+                  name={user.username || "Anonymous User"}
+                />
+              </DropdownItem>
+
+              <DropdownItem
+                key="settings"
+                description="Configure Atuin"
+                startContent={<Icon icon="solar:settings-linear" width={24} />}
+              >
+                Settings
+              </DropdownItem>
+
+              <DropdownSection aria-label="Help & Feedback">
+                <DropdownItem
+                  key="help_and_feedback"
+                  description="Get in touch"
+                  onPress={() => open("https://forum.atuin.sh")}
+                  startContent={
+                    <Icon width={24} icon="solar:question-circle-linear" />
+                  }
+                >
+                  Help & Feedback
+                </DropdownItem>
+
+                {(user.username && (
+                  <DropdownItem
+                    key="logout"
+                    startContent={
+                      <Icon width={24} icon="solar:logout-broken" />
+                    }
+                    onClick={() => {
+                      logout();
+                      refreshUser();
+                    }}
+                  >
+                    Log Out
+                  </DropdownItem>
+                )) || (
+                  <DropdownItem
+                    key="signup"
+                    description="Sync, backup and share your data"
+                    className="bg-emerald-100"
+                    startContent={<KeyRoundIcon size="18px" />}
+                    onPress={onOpen}
+                  >
+                    Log in or Register
+                  </DropdownItem>
                 )}
-              </li>
-            </ul>
-          </nav>
+              </DropdownSection>
+            </DropdownMenu>
+          </Dropdown>
         </div>
       </div>
 
       {renderMain(section)}
+
       <Toaster />
+      <Modal isOpen={isOpen} onOpenChange={onOpenChange} placement="top-center">
+        <ModalContent className="p-8">
+          {(onClose) => (
+            <>
+              <LoginOrRegister onClose={onClose} />
+            </>
+          )}
+        </ModalContent>
+      </Modal>
     </div>
   );
 }
-- 
cgit v1.3.1

