You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
187 lines
5.8 KiB
TypeScript
187 lines
5.8 KiB
TypeScript
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.svg";
|
|
import { useStore } from "../state.js";
|
|
import { useEffect } from "react";
|
|
import { trpc } from "../trpc/client.js";
|
|
import { usePageContext } from "vike-react/usePageContext";
|
|
import "./hover.css";
|
|
|
|
export default function LayoutDefault({
|
|
children,
|
|
}: { children: React.ReactNode }) {
|
|
const pageContext = usePageContext();
|
|
const { urlPathname } = pageContext;
|
|
const [opened, { toggle }] = useDisclosure();
|
|
const conversations = useStore((state) => state.conversations);
|
|
const setConversations = useStore((state) => state.setConversations);
|
|
const addConversation = useStore((state) => state.addConversation);
|
|
const removeConversation = useStore((state) => state.removeConversation);
|
|
const conversationId = useStore((state) => state.selectedConversationId);
|
|
|
|
useEffect(() => {
|
|
trpc.chat.conversations.fetchAll.query().then((res) => {
|
|
setConversations(res);
|
|
});
|
|
}, [setConversations]);
|
|
|
|
// useEffect(() => {
|
|
// if (isConversationListExpanded) {
|
|
// trpc.chat.listConversations.query().then((res) => {
|
|
// setConversations(res);
|
|
// });
|
|
// }
|
|
// }, [isConversationListExpanded]);
|
|
|
|
async function handleDeleteConversation(conversationId: string) {
|
|
removeConversation(conversationId);
|
|
await trpc.chat.conversations.deleteOne.mutate({ id: conversationId });
|
|
const res = await trpc.chat.conversations.start.mutate();
|
|
if (!res?.id) return;
|
|
addConversation(res);
|
|
await navigate(`/chat/${res.id}`);
|
|
}
|
|
|
|
return (
|
|
<MantineProvider theme={theme}>
|
|
<AppShell
|
|
header={{ height: 60 }}
|
|
navbar={{
|
|
width: 300,
|
|
breakpoint: "sm",
|
|
collapsed: { mobile: !opened },
|
|
}}
|
|
padding="lg"
|
|
>
|
|
<AppShell.Header>
|
|
<Group h="100%" px="md">
|
|
<Burger
|
|
opened={opened}
|
|
onClick={toggle}
|
|
hiddenFrom="sm"
|
|
size="sm"
|
|
/>
|
|
<a href="/">
|
|
{" "}
|
|
<Image h={50} fit="contain" src={logoUrl} />{" "}
|
|
</a>
|
|
</Group>
|
|
</AppShell.Header>
|
|
<AppShell.Navbar p="md">
|
|
<NavLink href="/" label="Welcome" active={urlPathname === "/"} />
|
|
<NavLink href="/todo" label="Todo" active={urlPathname === "/todo"} />
|
|
<NavLink
|
|
href="/star-wars"
|
|
label="Data Fetching"
|
|
active={urlPathname.startsWith("/star-wars")}
|
|
/>
|
|
<NavLink
|
|
key="chat-new"
|
|
href="#required-for-focus-management"
|
|
label={
|
|
<Group justify="space-between">
|
|
<span>Chats</span>
|
|
<IconPlus
|
|
size={16}
|
|
stroke={1.5}
|
|
className="border-on-hover"
|
|
onClick={(e) => {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
trpc.chat.conversations.start.mutate().then((res) => {
|
|
if (!res?.id) return;
|
|
addConversation(res);
|
|
navigate(`/chat/${res.id}`);
|
|
});
|
|
}}
|
|
/>
|
|
</Group>
|
|
}
|
|
leftSection={<IconActivity size={16} stroke={1.5} />}
|
|
rightSection={
|
|
<IconChevronRight
|
|
size={12}
|
|
stroke={1.5}
|
|
className="mantine-rotate-rtl"
|
|
/>
|
|
}
|
|
variant="subtle"
|
|
active={urlPathname.startsWith("/chat")}
|
|
defaultOpened={true}
|
|
>
|
|
{conversations.map((conversation) => (
|
|
<NavLink
|
|
key={conversation.id}
|
|
href={`/chat/${conversation.id}`}
|
|
label={conversation.title}
|
|
className="hover-container"
|
|
leftSection={
|
|
<>
|
|
<IconCircle
|
|
size={16}
|
|
stroke={1.5}
|
|
className="show-by-default"
|
|
/>
|
|
<IconCircleFilled
|
|
size={16}
|
|
stroke={1.5}
|
|
className="show-on-hover"
|
|
/>
|
|
</>
|
|
}
|
|
rightSection={
|
|
<>
|
|
<IconTrash
|
|
size={16}
|
|
stroke={1.5}
|
|
onClick={(e) => {
|
|
e.stopPropagation();
|
|
e.preventDefault();
|
|
handleDeleteConversation(conversation.id);
|
|
}}
|
|
className="show-by-default"
|
|
/>
|
|
<IconTrashFilled
|
|
size={16}
|
|
stroke={1.5}
|
|
onClick={(e) => {
|
|
e.stopPropagation();
|
|
e.preventDefault();
|
|
handleDeleteConversation(conversation.id);
|
|
}}
|
|
className="show-on-hover border-on-hover"
|
|
/>
|
|
</>
|
|
}
|
|
variant="subtle"
|
|
active={conversation.id === conversationId}
|
|
/>
|
|
))}
|
|
</NavLink>
|
|
</AppShell.Navbar>
|
|
<AppShell.Main> {children} </AppShell.Main>
|
|
</AppShell>
|
|
</MantineProvider>
|
|
);
|
|
}
|