aboutsummaryrefslogtreecommitdiffstats
path: root/ui/src/App.tsx
diff options
context:
space:
mode:
authorEllie Huxtable <ellie@atuin.sh>2024-07-10 15:56:33 +0100
committerGitHub <noreply@github.com>2024-07-10 15:56:33 +0100
commit8d9f677c4e9ccfcc6dc9297864dc49446fb5ee59 (patch)
tree1fe507403c4d690937834a815a663336bf104039 /ui/src/App.tsx
parentchore(deps): update to tonic 0.12, prost 0.13 (#2250) (diff)
downloadatuin-8d9f677c4e9ccfcc6dc9297864dc49446fb5ee59.zip
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
Diffstat (limited to 'ui/src/App.tsx')
-rw-r--r--ui/src/App.tsx257
1 files changed, 184 insertions, 73 deletions
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>
);
}