import "@mantine/core/styles.css"; import { navigate } from "vike/client/router"; import { AppShell, Burger, Group, Image, MantineProvider, NavLink, } from "@mantine/core"; import { IconHome2, IconChevronRight, IconActivity, IconTrash, IconCircle, IconCircleFilled, IconTrashFilled, IconPlus, } from "@tabler/icons-react"; import { useDisclosure } from "@mantine/hooks"; import theme from "./theme.js"; import logoUrl from "../assets/logo.png"; import { useStore } from "../state.js"; import { useEffect, useState } from "react"; import { TRPCProvider, useTRPC } from "../trpc/client.js"; import { usePageContext } from "vike-react/usePageContext"; import "./hover.css"; import { QueryClient, QueryClientProvider, useMutation, useQuery, } from "@tanstack/react-query"; import { createTRPCClient, httpBatchLink, httpSubscriptionLink, splitLink, } from "@trpc/client"; import type { AppRouter } from "../trpc/router.js"; function makeQueryClient() { return new QueryClient({ defaultOptions: { queries: { // With SSR, we usually want to set some default staleTime // above 0 to avoid refetching immediately on the client staleTime: 60 * 1000, }, }, }); } let browserQueryClient: QueryClient | undefined = undefined; function getQueryClient() { if (typeof window === "undefined") { // Server: always make a new query client return makeQueryClient(); } // Browser: make a new query client if we don't already have one // This is very important, so we don't re-make a new client if React // suspends during the initial render. This may not be needed if we // have a suspense boundary BELOW the creation of the query client if (!browserQueryClient) browserQueryClient = makeQueryClient(); return browserQueryClient; } export default function LayoutDefault({ children, }: { children: React.ReactNode; }) { const pageContext = usePageContext(); const { urlPathname } = pageContext; const [opened, { toggle }] = useDisclosure(); const queryClient = getQueryClient(); const [trpc] = useState(() => createTRPCClient({ links: [ splitLink({ // uses the httpSubscriptionLink for subscriptions condition: (op) => op.type === "subscription", true: httpSubscriptionLink({ url: "/api/trpc", }), false: httpBatchLink({ url: "/api/trpc", methodOverride: "POST", }), }), ], }) ); return ( {" "} {" "} {children} ); } function NavLinkChat() { const pageContext = usePageContext(); const { urlPathname } = pageContext; const trpc = useTRPC(); // const const startConversation = useMutation( trpc.chat.conversations.start.mutationOptions() ); const deleteConversation = useMutation( trpc.chat.conversations.deleteOne.mutationOptions() ); const { data: conversations } = useQuery( trpc.chat.conversations.fetchAll.queryOptions() ); // TODO: should we be using zustand for this, or trpc/react-query's useMutation? const addConversation = useStore((state) => state.addConversation); const removeConversation = useStore((state) => state.removeConversation); const conversationId = useStore((state) => state.selectedConversationId); async function handleDeleteConversation(conversationId: string) { removeConversation(conversationId); await deleteConversation.mutateAsync({ id: conversationId }); const res = await startConversation.mutateAsync(); if (!res?.id) return; addConversation(res); await navigate(`/chat/${res.id}`); } return ( Chats { e.preventDefault(); e.stopPropagation(); startConversation.mutateAsync().then((res) => { if (!res?.id) return; addConversation(res); navigate(`/chat/${res.id}`); }); }} /> } leftSection={} rightSection={ } variant="subtle" active={urlPathname.startsWith("/chat")} defaultOpened={true} > {conversations?.map((conversation) => ( } rightSection={ <> { e.stopPropagation(); e.preventDefault(); handleDeleteConversation(conversation.id); }} className="show-by-default" /> { e.stopPropagation(); e.preventDefault(); handleDeleteConversation(conversation.id); }} className="show-on-hover border-on-hover" /> } variant="subtle" active={conversation.id === conversationId} /> ))} ); }