project menu updates

This commit is contained in:
Kyle Corbitt
2023-08-09 14:21:05 -07:00
parent 4feb3e5829
commit f6f04e537e
11 changed files with 92 additions and 118 deletions

View File

@@ -23,6 +23,7 @@ ARG NEXT_PUBLIC_SOCKET_URL
ARG NEXT_PUBLIC_HOST ARG NEXT_PUBLIC_HOST
ARG NEXT_PUBLIC_SENTRY_DSN ARG NEXT_PUBLIC_SENTRY_DSN
ARG SENTRY_AUTH_TOKEN ARG SENTRY_AUTH_TOKEN
ARG NEXT_PUBLIC_FF_SHOW_LOGGED_CALLS
WORKDIR /app WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules COPY --from=deps /app/node_modules ./node_modules

View File

@@ -52,7 +52,9 @@ const NavSidebar = () => {
<> <>
<ProjectMenu /> <ProjectMenu />
<Divider /> <Divider />
<IconLink icon={IoStatsChartOutline} label="Logged Calls" href="/logged-calls" beta /> {env.NEXT_PUBLIC_FF_SHOW_LOGGED_CALLS && (
<IconLink icon={IoStatsChartOutline} label="Logged Calls" href="/logged-calls" beta />
)}
<IconLink icon={RiFlaskLine} label="Experiments" href="/experiments" /> <IconLink icon={RiFlaskLine} label="Experiments" href="/experiments" />
{env.NEXT_PUBLIC_SHOW_DATA && ( {env.NEXT_PUBLIC_SHOW_DATA && (
<IconLink icon={RiDatabase2Line} label="Data" href="/data" /> <IconLink icon={RiDatabase2Line} label="Data" href="/data" />

View File

@@ -12,9 +12,8 @@ import {
Button, Button,
useDisclosure, useDisclosure,
Spinner, Spinner,
useBreakpointValue,
} from "@chakra-ui/react"; } from "@chakra-ui/react";
import React, { useCallback, useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import Link from "next/link"; import Link from "next/link";
import { AiFillCaretDown } from "react-icons/ai"; import { AiFillCaretDown } from "react-icons/ai";
import { BsGear, BsPlus } from "react-icons/bs"; import { BsGear, BsPlus } from "react-icons/bs";
@@ -44,8 +43,6 @@ export default function ProjectMenu() {
const { data: selectedOrg } = useSelectedOrg(); const { data: selectedOrg } = useSelectedOrg();
const [expandButtonHovered, setExpandButtonHovered] = useState(false);
const popover = useDisclosure(); const popover = useDisclosure();
const createMutation = api.organizations.create.useMutation(); const createMutation = api.organizations.create.useMutation();
@@ -56,113 +53,84 @@ export default function ProjectMenu() {
await router.push({ pathname: "/project/settings" }); await router.push({ pathname: "/project/settings" });
}, [createMutation, router]); }, [createMutation, router]);
const openMenu = useCallback(
(event: React.MouseEvent<Element, MouseEvent>) => {
event.preventDefault();
popover.onToggle();
},
[popover],
);
const sidebarExpanded = useBreakpointValue({ base: false, md: true });
return ( return (
<> <VStack w="full" alignItems="flex-start" spacing={0}>
<Popover <Text
placement="bottom-start" pl={2}
isOpen={popover.isOpen} pb={2}
onClose={popover.onClose} fontSize="xs"
closeOnBlur fontWeight="bold"
color="gray.500"
display={{ base: "none", md: "flex" }}
> >
<VStack w="full" alignItems="flex-start" spacing={0}> PROJECT
<Text </Text>
pl={2} <NavSidebarOption>
pb={2} <Popover
fontSize="xs" placement="bottom-start"
fontWeight="bold" isOpen={popover.isOpen}
color="gray.500" onClose={popover.onClose}
display={{ base: "none", md: "flex" }} closeOnBlur
>
PROJECT
</Text>
<NavSidebarOption activeHrefPattern="/home" disableHoverEffect={expandButtonHovered}>
<Link href="/logged-calls">
<HStack w="full" justifyContent="space-between">
<HStack>
<Flex
p={1}
borderRadius={4}
backgroundColor="orange.100"
minW={{ base: 10, md: 8 }}
minH={{ base: 10, md: 8 }}
m={{ base: 0, md: 1 }}
alignItems="center"
justifyContent="center"
onClick={sidebarExpanded ? undefined : openMenu}
>
<Text>{selectedOrg?.name[0]?.toUpperCase()}</Text>
</Flex>
<Text fontSize="sm" display={{ base: "none", md: "block" }} py={1}>
{selectedOrg?.name}
</Text>
</HStack>
<PopoverTrigger>
<IconButton
aria-label="Open Project Menu"
icon={<Icon as={AiFillCaretDown} boxSize={3} />}
size="xs"
colorScheme="gray"
color="gray.500"
variant="ghost"
mr={2}
borderRadius={4}
onMouseEnter={() => setExpandButtonHovered(true)}
onMouseLeave={() => setExpandButtonHovered(false)}
_hover={{ bgColor: isActive ? "gray.300" : "gray.200", transitionDelay: 0 }}
onClick={openMenu}
/>
</PopoverTrigger>
</HStack>
</Link>
</NavSidebarOption>
</VStack>
<PopoverContent
_focusVisible={{ boxShadow: "unset" }}
minW={0}
borderColor="blue.400"
w="auto"
> >
<VStack alignItems="flex-start" spacing={2} py={4} px={2}> <PopoverTrigger>
<Text color="gray.500" fontSize="xs" fontWeight="bold" pb={1}> <HStack w="full" justifyContent="space-between" onClick={popover.onToggle}>
PROJECTS <Flex
</Text> p={1}
<Divider /> borderRadius={4}
<VStack spacing={0} w="full"> backgroundColor="orange.100"
{orgs?.map((org) => ( minW={{ base: 10, md: 8 }}
<ProjectOption minH={{ base: 10, md: 8 }}
key={org.id} m={{ base: 0, md: 1 }}
org={org} alignItems="center"
isActive={org.id === selectedOrgId} justifyContent="center"
onClose={popover.onClose} // onClick={sidebarExpanded ? undefined : openMenu}
/> >
))} <Text>{selectedOrg?.name[0]?.toUpperCase()}</Text>
</VStack> </Flex>
<HStack <Text fontSize="sm" display={{ base: "none", md: "block" }} py={1}>
as={Button} {selectedOrg?.name}
variant="ghost" </Text>
colorScheme="blue" <Icon as={AiFillCaretDown} boxSize={3} size="xs" color="gray.500" mr={2} />
color="blue.400"
pr={8}
w="full"
onClick={createProject}
>
<Icon as={isLoading ? Spinner : BsPlus} boxSize={6} />
<Text>New project</Text>
</HStack> </HStack>
</VStack> </PopoverTrigger>
</PopoverContent> <PopoverContent
</Popover> _focusVisible={{ boxShadow: "unset" }}
</> minW={0}
borderColor="blue.400"
w="full"
>
<VStack alignItems="flex-start" spacing={2} py={4} px={2}>
<Text color="gray.500" fontSize="xs" fontWeight="bold" pb={1}>
PROJECTS
</Text>
<Divider />
<VStack spacing={0} w="full">
{orgs?.map((org) => (
<ProjectOption
key={org.id}
org={org}
isActive={org.id === selectedOrgId}
onClose={popover.onClose}
/>
))}
</VStack>
<HStack
as={Button}
variant="ghost"
colorScheme="blue"
color="blue.400"
pr={8}
w="full"
onClick={createProject}
>
<Icon as={isLoading ? Spinner : BsPlus} boxSize={6} />
<Text>New project</Text>
</HStack>
</VStack>
</PopoverContent>
</Popover>
</NavSidebarOption>
</VStack>
); );
} }
@@ -180,7 +148,7 @@ const ProjectOption = ({
return ( return (
<HStack <HStack
as={Link} as={Link}
href="/logged-calls" href="/experiments"
onClick={() => { onClick={() => {
setSelectedOrgId(org.id); setSelectedOrgId(org.id);
onClose(); onClose();
@@ -190,7 +158,6 @@ const ProjectOption = ({
bgColor={isActive ? "gray.100" : "transparent"} bgColor={isActive ? "gray.100" : "transparent"}
_hover={gearHovered ? undefined : { bgColor: "gray.200", textDecoration: "none" }} _hover={gearHovered ? undefined : { bgColor: "gray.200", textDecoration: "none" }}
p={2} p={2}
borderRadius={4}
> >
<Text>{org.name}</Text> <Text>{org.name}</Text>
<IconButton <IconButton

View File

@@ -36,7 +36,7 @@ export const DeleteProjectDialog = ({
if (!selectedOrg.data?.id) return; if (!selectedOrg.data?.id) return;
await deleteMutation.mutateAsync({ id: selectedOrg.data.id }); await deleteMutation.mutateAsync({ id: selectedOrg.data.id });
await utils.organizations.list.invalidate(); await utils.organizations.list.invalidate();
await router.push({ pathname: "/logged-calls" }); await router.push({ pathname: "/experiments" });
onClose(); onClose();
}, [deleteMutation, selectedOrg, router]); }, [deleteMutation, selectedOrg, router]);

View File

@@ -34,6 +34,7 @@ export const env = createEnv({
NEXT_PUBLIC_HOST: z.string().url().default("http://localhost:3000"), NEXT_PUBLIC_HOST: z.string().url().default("http://localhost:3000"),
NEXT_PUBLIC_SENTRY_DSN: z.string().optional(), NEXT_PUBLIC_SENTRY_DSN: z.string().optional(),
NEXT_PUBLIC_SHOW_DATA: z.string().optional(), NEXT_PUBLIC_SHOW_DATA: z.string().optional(),
NEXT_PUBLIC_FF_SHOW_LOGGED_CALLS: z.string().optional(),
}, },
/** /**
@@ -56,6 +57,7 @@ export const env = createEnv({
NEXT_PUBLIC_SENTRY_DSN: process.env.NEXT_PUBLIC_SENTRY_DSN, NEXT_PUBLIC_SENTRY_DSN: process.env.NEXT_PUBLIC_SENTRY_DSN,
SENTRY_AUTH_TOKEN: process.env.SENTRY_AUTH_TOKEN, SENTRY_AUTH_TOKEN: process.env.SENTRY_AUTH_TOKEN,
OPENPIPE_API_KEY: process.env.OPENPIPE_API_KEY, OPENPIPE_API_KEY: process.env.OPENPIPE_API_KEY,
NEXT_PUBLIC_FF_SHOW_LOGGED_CALLS: process.env.NEXT_PUBLIC_FF_SHOW_LOGGED_CALLS,
}, },
/** /**
* Run `build` or `dev` with `SKIP_ENV_VALIDATION` to skip env validation. * Run `build` or `dev` with `SKIP_ENV_VALIDATION` to skip env validation.

View File

@@ -4,7 +4,7 @@ import { type GetServerSideProps } from "next";
export const getServerSideProps: GetServerSideProps = async () => { export const getServerSideProps: GetServerSideProps = async () => {
return { return {
redirect: { redirect: {
destination: "/logged-calls", destination: "/experiments",
permanent: false, permanent: false,
}, },
}; };

View File

@@ -111,7 +111,7 @@ export default function Settings() {
your code. your code.
</Text> </Text>
</VStack> </VStack>
<CopiableCode code={`OPENPIPE_API_KEY=${apiKey}`} /> <CopiableCode code={apiKey} />
<Divider /> <Divider />
{selectedOrg?.personalOrgUserId ? ( {selectedOrg?.personalOrgUserId ? (
<VStack alignItems="flex-start"> <VStack alignItems="flex-start">

View File

@@ -34,9 +34,12 @@ export const dashboardRouter = createTRPCRouter({
let originalDataIndex = periods.length - 1; let originalDataIndex = periods.length - 1;
// *SLAMS DOWN GLASS OF WHISKEY* timezones, amirite? // *SLAMS DOWN GLASS OF WHISKEY* timezones, amirite?
let dayToMatch = dayjs(input.startDate || new Date()) let dayToMatch = dayjs(input.startDate || new Date());
// Ensure that the initial date we're matching against is never before the first period // Ensure that the initial date we're matching against is never before the first period
if (periods[originalDataIndex] && dayToMatch.isBefore(periods[originalDataIndex]?.period, "day")) { if (
periods[originalDataIndex] &&
dayToMatch.isBefore(periods[originalDataIndex]?.period, "day")
) {
dayToMatch = dayjs(periods[originalDataIndex]?.period); dayToMatch = dayjs(periods[originalDataIndex]?.period);
} }
const backfilledPeriods: typeof periods = []; const backfilledPeriods: typeof periods = [];

View File

@@ -16,7 +16,7 @@ fs.writeFileSync(schemaPath, JSON.stringify(openApiDocument, null, 2), "utf-8");
console.log("Generating Typescript client"); console.log("Generating Typescript client");
const tsClientPath = path.join(clientLibsPath, "js/codegen"); const tsClientPath = path.join(clientLibsPath, "typescript/codegen");
fs.rmSync(tsClientPath, { recursive: true, force: true }); fs.rmSync(tsClientPath, { recursive: true, force: true });

View File

@@ -1,5 +1,5 @@
import { type Prisma } from "@prisma/client"; import { type Prisma } from "@prisma/client";
import { JsonValue, type JsonObject } from "type-fest"; import { type JsonValue, type JsonObject } from "type-fest";
import modelProviders from "~/modelProviders/modelProviders"; import modelProviders from "~/modelProviders/modelProviders";
import { prisma } from "~/server/db"; import { prisma } from "~/server/db";
import { wsConnection } from "~/utils/wsConnection"; import { wsConnection } from "~/utils/wsConnection";

View File

@@ -11,7 +11,6 @@ export default function useSocket<T>(channel?: string | null) {
useEffect(() => { useEffect(() => {
if (!channel) return; if (!channel) return;
console.log("connecting to channel", channel);
// Create websocket connection // Create websocket connection
socketRef.current = io(url); socketRef.current = io(url);