Change overall background color and menu
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect, useRef } from "react";
|
||||||
import {
|
import {
|
||||||
Heading,
|
Heading,
|
||||||
VStack,
|
VStack,
|
||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
Box,
|
Box,
|
||||||
Link as ChakraLink,
|
Link as ChakraLink,
|
||||||
Flex,
|
Flex,
|
||||||
|
useBreakpointValue,
|
||||||
} from "@chakra-ui/react";
|
} from "@chakra-ui/react";
|
||||||
import Head from "next/head";
|
import Head from "next/head";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
@@ -16,7 +17,6 @@ import { BsGearFill, BsGithub, BsPersonCircle } from "react-icons/bs";
|
|||||||
import { IoStatsChartOutline } from "react-icons/io5";
|
import { IoStatsChartOutline } from "react-icons/io5";
|
||||||
import { RiHome3Line, RiDatabase2Line, RiFlaskLine } from "react-icons/ri";
|
import { RiHome3Line, RiDatabase2Line, RiFlaskLine } from "react-icons/ri";
|
||||||
import { signIn, useSession } from "next-auth/react";
|
import { signIn, useSession } from "next-auth/react";
|
||||||
import UserMenu from "./UserMenu";
|
|
||||||
import { env } from "~/env.mjs";
|
import { env } from "~/env.mjs";
|
||||||
import ProjectMenu from "./ProjectMenu";
|
import ProjectMenu from "./ProjectMenu";
|
||||||
import NavSidebarOption from "./NavSidebarOption";
|
import NavSidebarOption from "./NavSidebarOption";
|
||||||
@@ -27,10 +27,16 @@ const Divider = () => <Box h="1px" bgColor="gray.300" w="full" />;
|
|||||||
const NavSidebar = () => {
|
const NavSidebar = () => {
|
||||||
const user = useSession().data;
|
const user = useSession().data;
|
||||||
|
|
||||||
|
// Hack to get around initial flash, see https://github.com/chakra-ui/chakra-ui/issues/6452
|
||||||
|
const isMobile = useBreakpointValue({ base: true, md: false, ssr: false });
|
||||||
|
const renderCount = useRef(0);
|
||||||
|
renderCount.current++;
|
||||||
|
|
||||||
|
const displayLogo = isMobile && renderCount.current > 1;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<VStack
|
<VStack
|
||||||
align="stretch"
|
align="stretch"
|
||||||
bgColor="gray.50"
|
|
||||||
py={2}
|
py={2}
|
||||||
px={2}
|
px={2}
|
||||||
pb={0}
|
pb={0}
|
||||||
@@ -40,6 +46,8 @@ const NavSidebar = () => {
|
|||||||
borderRightWidth={1}
|
borderRightWidth={1}
|
||||||
borderColor="gray.300"
|
borderColor="gray.300"
|
||||||
>
|
>
|
||||||
|
{displayLogo && (
|
||||||
|
<>
|
||||||
<HStack
|
<HStack
|
||||||
as={Link}
|
as={Link}
|
||||||
href="/"
|
href="/"
|
||||||
@@ -54,6 +62,9 @@ const NavSidebar = () => {
|
|||||||
</Heading>
|
</Heading>
|
||||||
</HStack>
|
</HStack>
|
||||||
<Divider />
|
<Divider />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
<VStack align="flex-start" overflowY="auto" overflowX="hidden" flex={1}>
|
<VStack align="flex-start" overflowY="auto" overflowX="hidden" flex={1}>
|
||||||
{user != null && (
|
{user != null && (
|
||||||
<>
|
<>
|
||||||
@@ -75,6 +86,19 @@ const NavSidebar = () => {
|
|||||||
{env.NEXT_PUBLIC_SHOW_DATA && (
|
{env.NEXT_PUBLIC_SHOW_DATA && (
|
||||||
<IconLink icon={RiDatabase2Line} label="Data" href="/data" />
|
<IconLink icon={RiDatabase2Line} label="Data" href="/data" />
|
||||||
)}
|
)}
|
||||||
|
<VStack w="full" alignItems="flex-start" spacing={0} pt={8}>
|
||||||
|
<Text
|
||||||
|
pl={2}
|
||||||
|
pb={2}
|
||||||
|
fontSize="xs"
|
||||||
|
fontWeight="bold"
|
||||||
|
color="gray.500"
|
||||||
|
display={{ base: "none", md: "flex" }}
|
||||||
|
>
|
||||||
|
CONFIGURATION
|
||||||
|
</Text>
|
||||||
|
<IconLink icon={BsGearFill} label="Project Settings" href="/project/settings" />
|
||||||
|
</VStack>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{user === null && (
|
{user === null && (
|
||||||
@@ -96,20 +120,7 @@ const NavSidebar = () => {
|
|||||||
</NavSidebarOption>
|
</NavSidebarOption>
|
||||||
)}
|
)}
|
||||||
</VStack>
|
</VStack>
|
||||||
<VStack w="full" alignItems="flex-start" spacing={0}>
|
|
||||||
<Text
|
|
||||||
pl={2}
|
|
||||||
pb={2}
|
|
||||||
fontSize="xs"
|
|
||||||
fontWeight="bold"
|
|
||||||
color="gray.500"
|
|
||||||
display={{ base: "none", md: "flex" }}
|
|
||||||
>
|
|
||||||
CONFIGURATION
|
|
||||||
</Text>
|
|
||||||
<IconLink icon={BsGearFill} label="Project Settings" href="/project/settings" />
|
|
||||||
</VStack>
|
|
||||||
{user && <UserMenu user={user} borderColor={"gray.200"} />}
|
|
||||||
<Divider />
|
<Divider />
|
||||||
<VStack spacing={0} align="center">
|
<VStack spacing={0} align="center">
|
||||||
<ChakraLink
|
<ChakraLink
|
||||||
@@ -169,7 +180,7 @@ export default function AppShell({
|
|||||||
<title>{title ? `${title} | OpenPipe` : "OpenPipe"}</title>
|
<title>{title ? `${title} | OpenPipe` : "OpenPipe"}</title>
|
||||||
</Head>
|
</Head>
|
||||||
<NavSidebar />
|
<NavSidebar />
|
||||||
<Box h="100%" flex={1} overflowY="auto">
|
<Box h="100%" flex={1} overflowY="auto" bgColor="gray.50">
|
||||||
{children}
|
{children}
|
||||||
</Box>
|
</Box>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|||||||
@@ -6,16 +6,19 @@ import {
|
|||||||
PopoverTrigger,
|
PopoverTrigger,
|
||||||
PopoverContent,
|
PopoverContent,
|
||||||
Flex,
|
Flex,
|
||||||
IconButton,
|
|
||||||
Icon,
|
Icon,
|
||||||
Divider,
|
Divider,
|
||||||
Button,
|
Button,
|
||||||
useDisclosure,
|
useDisclosure,
|
||||||
Spinner,
|
Spinner,
|
||||||
|
Link as ChakraLink,
|
||||||
|
IconButton,
|
||||||
|
Image,
|
||||||
|
Box,
|
||||||
} from "@chakra-ui/react";
|
} from "@chakra-ui/react";
|
||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { BsChevronRight, BsGear, BsPlus } from "react-icons/bs";
|
import { BsGear, BsPlus, BsPersonCircle } from "react-icons/bs";
|
||||||
import { type Project } from "@prisma/client";
|
import { type Project } from "@prisma/client";
|
||||||
|
|
||||||
import { useAppStore } from "~/state/store";
|
import { useAppStore } from "~/state/store";
|
||||||
@@ -23,6 +26,7 @@ import { api } from "~/utils/api";
|
|||||||
import NavSidebarOption from "./NavSidebarOption";
|
import NavSidebarOption from "./NavSidebarOption";
|
||||||
import { useHandledAsyncCallback, useSelectedProject } from "~/utils/hooks";
|
import { useHandledAsyncCallback, useSelectedProject } from "~/utils/hooks";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
|
import { useSession, signOut } from "next-auth/react";
|
||||||
|
|
||||||
export default function ProjectMenu() {
|
export default function ProjectMenu() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@@ -49,28 +53,32 @@ export default function ProjectMenu() {
|
|||||||
|
|
||||||
const createMutation = api.projects.create.useMutation();
|
const createMutation = api.projects.create.useMutation();
|
||||||
const [createProject, isLoading] = useHandledAsyncCallback(async () => {
|
const [createProject, isLoading] = useHandledAsyncCallback(async () => {
|
||||||
const newProj = await createMutation.mutateAsync({ name: "New Project" });
|
const newProj = await createMutation.mutateAsync({ name: "Untitled Project" });
|
||||||
await utils.projects.list.invalidate();
|
await utils.projects.list.invalidate();
|
||||||
setselectedProjectId(newProj.id);
|
setselectedProjectId(newProj.id);
|
||||||
await router.push({ pathname: "/project/settings" });
|
await router.push({ pathname: "/project/settings" });
|
||||||
}, [createMutation, router]);
|
}, [createMutation, router]);
|
||||||
|
|
||||||
|
const user = useSession().data;
|
||||||
|
|
||||||
|
const profileImage = user?.user.image ? (
|
||||||
|
<Image src={user.user.image} alt="profile picture" boxSize={6} borderRadius="50%" />
|
||||||
|
) : (
|
||||||
|
<Icon as={BsPersonCircle} boxSize={6} />
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<VStack w="full" alignItems="flex-start" spacing={0}>
|
<VStack w="full" alignItems="flex-start" spacing={0} py={1}>
|
||||||
<Text
|
<Popover
|
||||||
pl={2}
|
placement="bottom"
|
||||||
pb={2}
|
isOpen={popover.isOpen}
|
||||||
fontSize="xs"
|
onOpen={popover.onOpen}
|
||||||
fontWeight="bold"
|
onClose={popover.onClose}
|
||||||
color="gray.500"
|
closeOnBlur
|
||||||
display={{ base: "none", md: "flex" }}
|
|
||||||
>
|
>
|
||||||
PROJECT
|
|
||||||
</Text>
|
|
||||||
<Popover placement="right-end" isOpen={popover.isOpen} onClose={popover.onClose} closeOnBlur>
|
|
||||||
<PopoverTrigger>
|
<PopoverTrigger>
|
||||||
<NavSidebarOption>
|
<NavSidebarOption>
|
||||||
<HStack w="full" onClick={popover.onToggle}>
|
<HStack w="full">
|
||||||
<Flex
|
<Flex
|
||||||
p={1}
|
p={1}
|
||||||
borderRadius={4}
|
borderRadius={4}
|
||||||
@@ -83,20 +91,35 @@ export default function ProjectMenu() {
|
|||||||
>
|
>
|
||||||
<Text>{selectedProject?.name[0]?.toUpperCase()}</Text>
|
<Text>{selectedProject?.name[0]?.toUpperCase()}</Text>
|
||||||
</Flex>
|
</Flex>
|
||||||
<Text fontSize="sm" display={{ base: "none", md: "block" }} py={1} flex={1}>
|
<Text
|
||||||
|
fontSize="sm"
|
||||||
|
display={{ base: "none", md: "block" }}
|
||||||
|
py={1}
|
||||||
|
flex={1}
|
||||||
|
fontWeight="bold"
|
||||||
|
>
|
||||||
{selectedProject?.name}
|
{selectedProject?.name}
|
||||||
</Text>
|
</Text>
|
||||||
<Icon as={BsChevronRight} boxSize={4} color="gray.500" />
|
<Box mr={2}>{profileImage}</Box>
|
||||||
</HStack>
|
</HStack>
|
||||||
</NavSidebarOption>
|
</NavSidebarOption>
|
||||||
</PopoverTrigger>
|
</PopoverTrigger>
|
||||||
<PopoverContent _focusVisible={{ outline: "unset" }} ml={-1} w="auto" minW={100} maxW={280}>
|
<PopoverContent
|
||||||
<VStack alignItems="flex-start" spacing={2} py={4} px={2}>
|
_focusVisible={{ outline: "unset" }}
|
||||||
<Text color="gray.500" fontSize="xs" fontWeight="bold" pb={1}>
|
ml={-1}
|
||||||
PROJECTS
|
w={224}
|
||||||
|
boxShadow="0 0 40px 4px rgba(0, 0, 0, 0.1);"
|
||||||
|
fontSize="sm"
|
||||||
|
>
|
||||||
|
<VStack alignItems="flex-start" spacing={1} py={1}>
|
||||||
|
<Text px={3} py={2}>
|
||||||
|
{user?.user.email}
|
||||||
</Text>
|
</Text>
|
||||||
<Divider />
|
<Divider />
|
||||||
<VStack spacing={0} w="full">
|
<Text alignSelf="flex-start" fontWeight="bold" px={3} pt={2}>
|
||||||
|
Your Projects
|
||||||
|
</Text>
|
||||||
|
<VStack spacing={0} w="full" px={1}>
|
||||||
{projects?.map((proj) => (
|
{projects?.map((proj) => (
|
||||||
<ProjectOption
|
<ProjectOption
|
||||||
key={proj.id}
|
key={proj.id}
|
||||||
@@ -105,20 +128,39 @@ export default function ProjectMenu() {
|
|||||||
onClose={popover.onClose}
|
onClose={popover.onClose}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</VStack>
|
|
||||||
<HStack
|
<HStack
|
||||||
as={Button}
|
as={Button}
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
colorScheme="blue"
|
colorScheme="blue"
|
||||||
color="blue.400"
|
color="blue.400"
|
||||||
pr={8}
|
fontSize="sm"
|
||||||
w="full"
|
justifyContent="flex-start"
|
||||||
onClick={createProject}
|
onClick={createProject}
|
||||||
|
w="full"
|
||||||
|
borderRadius={4}
|
||||||
|
spacing={0}
|
||||||
>
|
>
|
||||||
<Icon as={isLoading ? Spinner : BsPlus} boxSize={6} />
|
<Text>Add project</Text>
|
||||||
<Text>New project</Text>
|
<Icon as={isLoading ? Spinner : BsPlus} boxSize={4} strokeWidth={0.5} />
|
||||||
</HStack>
|
</HStack>
|
||||||
</VStack>
|
</VStack>
|
||||||
|
|
||||||
|
<Divider />
|
||||||
|
<VStack w="full" px={1}>
|
||||||
|
<ChakraLink
|
||||||
|
onClick={() => {
|
||||||
|
signOut().catch(console.error);
|
||||||
|
}}
|
||||||
|
_hover={{ bgColor: "gray.200", textDecoration: "none" }}
|
||||||
|
w="full"
|
||||||
|
py={2}
|
||||||
|
px={2}
|
||||||
|
borderRadius={4}
|
||||||
|
>
|
||||||
|
<Text>Sign out</Text>
|
||||||
|
</ChakraLink>
|
||||||
|
</VStack>
|
||||||
|
</VStack>
|
||||||
</PopoverContent>
|
</PopoverContent>
|
||||||
</Popover>
|
</Popover>
|
||||||
</VStack>
|
</VStack>
|
||||||
@@ -136,6 +178,7 @@ const ProjectOption = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const setselectedProjectId = useAppStore((s) => s.setselectedProjectId);
|
const setselectedProjectId = useAppStore((s) => s.setselectedProjectId);
|
||||||
const [gearHovered, setGearHovered] = useState(false);
|
const [gearHovered, setGearHovered] = useState(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<HStack
|
<HStack
|
||||||
as={Link}
|
as={Link}
|
||||||
@@ -146,9 +189,10 @@ const ProjectOption = ({
|
|||||||
}}
|
}}
|
||||||
w="full"
|
w="full"
|
||||||
justifyContent="space-between"
|
justifyContent="space-between"
|
||||||
bgColor={isActive ? "gray.100" : "transparent"}
|
|
||||||
_hover={gearHovered ? undefined : { bgColor: "gray.200", textDecoration: "none" }}
|
_hover={gearHovered ? undefined : { bgColor: "gray.200", textDecoration: "none" }}
|
||||||
p={2}
|
color={isActive ? "blue.400" : undefined}
|
||||||
|
py={2}
|
||||||
|
px={4}
|
||||||
borderRadius={4}
|
borderRadius={4}
|
||||||
spacing={4}
|
spacing={4}
|
||||||
>
|
>
|
||||||
@@ -157,7 +201,14 @@ const ProjectOption = ({
|
|||||||
as={Link}
|
as={Link}
|
||||||
href="/project/settings"
|
href="/project/settings"
|
||||||
aria-label={`Open ${proj.name} settings`}
|
aria-label={`Open ${proj.name} settings`}
|
||||||
icon={<Icon as={BsGear} boxSize={5} strokeWidth={0.5} color="gray.500" />}
|
icon={
|
||||||
|
<Icon
|
||||||
|
as={BsGear}
|
||||||
|
boxSize={5}
|
||||||
|
strokeWidth={0.5}
|
||||||
|
color={isActive ? "blue.400" : "gray.500"}
|
||||||
|
/>
|
||||||
|
}
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="xs"
|
size="xs"
|
||||||
p={0}
|
p={0}
|
||||||
|
|||||||
@@ -1,72 +0,0 @@
|
|||||||
import {
|
|
||||||
HStack,
|
|
||||||
Icon,
|
|
||||||
Image,
|
|
||||||
VStack,
|
|
||||||
Text,
|
|
||||||
Popover,
|
|
||||||
PopoverTrigger,
|
|
||||||
PopoverContent,
|
|
||||||
Link,
|
|
||||||
type StackProps,
|
|
||||||
} from "@chakra-ui/react";
|
|
||||||
import { type Session } from "next-auth";
|
|
||||||
import { signOut } from "next-auth/react";
|
|
||||||
import { BsBoxArrowRight, BsChevronRight, BsPersonCircle } from "react-icons/bs";
|
|
||||||
import NavSidebarOption from "./NavSidebarOption";
|
|
||||||
|
|
||||||
export default function UserMenu({ user, ...rest }: { user: Session } & StackProps) {
|
|
||||||
const profileImage = user.user.image ? (
|
|
||||||
<Image src={user.user.image} alt="profile picture" boxSize={8} borderRadius="50%" />
|
|
||||||
) : (
|
|
||||||
<Icon as={BsPersonCircle} boxSize={6} />
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Popover placement="right">
|
|
||||||
<PopoverTrigger>
|
|
||||||
<NavSidebarOption>
|
|
||||||
<HStack
|
|
||||||
// Weird values to make mobile look right; can clean up when we make the sidebar disappear on mobile
|
|
||||||
py={2}
|
|
||||||
px={1}
|
|
||||||
spacing={3}
|
|
||||||
{...rest}
|
|
||||||
>
|
|
||||||
{profileImage}
|
|
||||||
<VStack spacing={0} align="start" flex={1} flexShrink={1}>
|
|
||||||
<Text fontWeight="bold" fontSize="sm">
|
|
||||||
{user.user.name}
|
|
||||||
</Text>
|
|
||||||
<Text color="gray.500" fontSize="xs">
|
|
||||||
{/* {user.user.email} */}
|
|
||||||
</Text>
|
|
||||||
</VStack>
|
|
||||||
<Icon as={BsChevronRight} boxSize={4} color="gray.500" />
|
|
||||||
</HStack>
|
|
||||||
</NavSidebarOption>
|
|
||||||
</PopoverTrigger>
|
|
||||||
<PopoverContent _focusVisible={{ outline: "unset" }} ml={-1} minW={48} w="full">
|
|
||||||
<VStack align="stretch" spacing={0}>
|
|
||||||
{/* sign out */}
|
|
||||||
<HStack
|
|
||||||
as={Link}
|
|
||||||
onClick={() => {
|
|
||||||
signOut().catch(console.error);
|
|
||||||
}}
|
|
||||||
px={4}
|
|
||||||
py={2}
|
|
||||||
spacing={4}
|
|
||||||
color="gray.500"
|
|
||||||
fontSize="sm"
|
|
||||||
>
|
|
||||||
<Icon as={BsBoxArrowRight} boxSize={6} />
|
|
||||||
<Text>Sign out</Text>
|
|
||||||
</HStack>
|
|
||||||
</VStack>
|
|
||||||
</PopoverContent>
|
|
||||||
</Popover>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user