diff options
| author | yxshv <[email protected]> | 2024-04-15 01:57:39 +0530 |
|---|---|---|
| committer | yxshv <[email protected]> | 2024-04-15 01:57:39 +0530 |
| commit | 0ce7c916ffcbf6515c3da521775c72861bddd53c (patch) | |
| tree | a23c4b9b8bce9b7124c8574ee6f60709b522c6ae /apps/web/src/components | |
| parent | space expand layout (diff) | |
| download | supermemory-0ce7c916ffcbf6515c3da521775c72861bddd53c.tar.xz supermemory-0ce7c916ffcbf6515c3da521775c72861bddd53c.zip | |
add profile and fix drawer scroll
Diffstat (limited to 'apps/web/src/components')
| -rw-r--r-- | apps/web/src/components/Main.tsx | 84 | ||||
| -rw-r--r-- | apps/web/src/components/MemoryDrawer.tsx | 22 | ||||
| -rw-r--r-- | apps/web/src/components/Sidebar/AddMemoryDialog.tsx | 10 | ||||
| -rw-r--r-- | apps/web/src/components/Sidebar/index.tsx | 102 | ||||
| -rw-r--r-- | apps/web/src/components/ui/dialog.tsx | 6 |
5 files changed, 194 insertions, 30 deletions
diff --git a/apps/web/src/components/Main.tsx b/apps/web/src/components/Main.tsx index 68ee8d9f..79225eb5 100644 --- a/apps/web/src/components/Main.tsx +++ b/apps/web/src/components/Main.tsx @@ -45,6 +45,8 @@ export default function Main({ sidebarOpen }: { sidebarOpen: boolean }) { const [selectedSpaces, setSelectedSpaces] = useState<number[]>([]); + const [isStreaming, setIsStreaming] = useState(false) + useEffect(() => { const search = searchParams.get("q"); if (search && search.trim().length > 0) { @@ -241,6 +243,8 @@ export default function Main({ sidebarOpen }: { sidebarOpen: boolean }) { return; } + setIsStreaming(true) + if (response.body) { let reader = response.body?.getReader(); let decoder = new TextDecoder("utf-8"); @@ -259,14 +263,24 @@ export default function Main({ sidebarOpen }: { sidebarOpen: boolean }) { return reader?.read().then(processText); }); + } }; - const onSend = async () => { + const onSend = () => { + if (value.trim().length < 1) return setLayout("chat"); - await getSearchResults(); + getSearchResults(); }; + function onValueChange(e: React.ChangeEvent<HTMLTextAreaElement>) { + const value = e.target.value; + setValue(value); + const lines = countLines(e.target); + e.target.rows = Math.min(5, lines); + } + + return ( <> <AnimatePresence mode="wait"> @@ -284,6 +298,7 @@ export default function Main({ sidebarOpen }: { sidebarOpen: boolean }) { /> ) : ( <main + key='intial' data-sidebar-open={sidebarOpen} ref={main} className={cn( @@ -302,7 +317,57 @@ export default function Main({ sidebarOpen }: { sidebarOpen: boolean }) { Ask your second brain </h1> - <Textarea2 + <FilterSpaces + name={"Filter"} + onClose={() => { + textArea.current?.querySelector("textarea")?.focus(); + }} + side="top" + align="start" + className="bg-[#252525] mr-auto md:hidden" + selectedSpaces={selectedSpaces} + setSelectedSpaces={setSelectedSpaces} + /> + <Textarea2 + ref={textArea} + className="bg-rgray-2 md:hidden h-auto w-full flex-row items-start justify-center overflow-auto px-3 md:items-center md:justify-center" + textAreaProps={{ + placeholder: "Ask your SuperMemory...", + className: + "overflow-auto h-auto p-3 md:resize-none text-lg w-auto resize-y text-rgray-11 w-full", + value, + rows: 1, + autoFocus: true, + onChange: onValueChange, + onKeyDown: (e) => { + if (e.key === "Enter" && (e.ctrlKey || e.metaKey)) { + onSend(); + } + }, + }} + > + <div className="md:hidden text-rgray-11/70 ml-auto mt-auto flex h-full w-min items-center justify-center pb-3 pr-2"> + + <FilterSpaces + name={"Filter"} + onClose={() => { + textArea.current?.querySelector("textarea")?.focus(); + }} + className="hidden md:flex" + selectedSpaces={selectedSpaces} + setSelectedSpaces={setSelectedSpaces} + /> + <button + onClick={onSend} + disabled={value.trim().length < 1} + className="text-rgray-11/70 bg-rgray-3 focus-visible:ring-rgray-8 hover:bg-rgray-4 mt-auto flex items-center justify-center rounded-full p-2 ring-2 ring-transparent transition-[filter] focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50" + > + <ArrowUp className="h-5 w-5" /> + </button> + </div> + </Textarea2> + + <Textarea2 ref={textArea} exit={{ opacity: 0, @@ -325,6 +390,7 @@ export default function Main({ sidebarOpen }: { sidebarOpen: boolean }) { } }, }} + className="hidden md:flex" > <div className="text-rgray-11/70 flex h-full w-fit items-center justify-center pl-0 md:w-full md:p-2"> <FilterSpaces @@ -344,10 +410,10 @@ export default function Main({ sidebarOpen }: { sidebarOpen: boolean }) { <ArrowRight className="h-5 w-5" /> </button> </div> - </Textarea2> - {width <= 768 && <MemoryDrawer hide={hide} />} - </main> + </Textarea2> + </main> )} + {width <= 768 && <MemoryDrawer hide={hide} />} </AnimatePresence> </> ); @@ -388,7 +454,7 @@ export function Chat({ "sidebar relative flex w-full flex-col items-end gap-5 px-5 pt-5 transition-[padding-left,padding-top,padding-right] delay-200 duration-200 md:items-center md:gap-10 md:px-72 [&[data-sidebar-open='true']]:pr-10 [&[data-sidebar-open='true']]:delay-0 md:[&[data-sidebar-open='true']]:pl-[calc(2.5rem+30vw)]", )} > - <div className="scrollbar-none h-screen w-full overflow-y-auto px-2 md:px-5"> + <div className="scrollbar-none h-[70vh] md:h-screen w-full overflow-y-auto px-2 md:px-5"> {chatHistory.map((msg, i) => ( <ChatMessage index={i} key={i} isLast={i === chatHistory.length - 1}> <ChatQuestion>{msg.question}</ChatQuestion> @@ -404,12 +470,12 @@ export function Chat({ </ChatMessage> ))} </div> - <div className="from-rgray-2 via-rgray-2 to-rgray-2/0 absolute bottom-0 left-0 h-[30%] w-full bg-gradient-to-t" /> + <div className="from-rgray-2 via-rgray-2 to-rgray-2/0 absolute bottom-0 left-0 w-full bg-gradient-to-t" /> <div data-sidebar-open={sidebarOpen} className="absolute flex w-full items-center justify-center" > - <div className="animate-from-top fixed bottom-10 left-1/2 md:left-[auto] md:translate-x-0 -translate-x-1/2 mt-auto flex w-[90%] md:w-[50%] flex-col items-center justify-center gap-2"> + <div className="animate-from-top fixed bottom-padding md:bottom-10 left-1/2 md:left-[auto] md:translate-x-0 -translate-x-1/2 mt-auto flex w-[90%] md:w-[50%] flex-col items-center justify-center gap-2"> <FilterSpaces name={"Filter"} onClose={() => { diff --git a/apps/web/src/components/MemoryDrawer.tsx b/apps/web/src/components/MemoryDrawer.tsx index f1ca5d47..a71d3d19 100644 --- a/apps/web/src/components/MemoryDrawer.tsx +++ b/apps/web/src/components/MemoryDrawer.tsx @@ -32,16 +32,18 @@ export function MemoryDrawer({ className, hide = false, ...props }: Props) { )} handle={false} > - <button - onClick={() => - setActiveSnapPoint((prev) => (prev === 0.9 ? 0.1 : 0.9)) - } - className="bg-rgray-4 border-rgray-6 text-rgray-11 absolute left-1/2 top-0 flex w-fit -translate-x-1/2 -translate-y-1/2 items-center justify-center gap-2 rounded-md border px-3 py-2" - > - <MemoryIcon className="h-7 w-7" /> - Memories - </button> - <MemoriesBar /> + <button + onClick={() => + setActiveSnapPoint((prev) => (prev === 0.9 ? 0.1 : 0.9)) + } + className="bg-rgray-4 border-rgray-6 text-rgray-11 absolute left-1/2 top-0 flex w-fit -translate-x-1/2 -translate-y-1/2 items-center justify-center gap-2 rounded-md border px-3 py-2" + > + <MemoryIcon className="h-7 w-7" /> + Memories + </button> + <div className="w-full h-full overflow-y-auto"> + <MemoriesBar isOpen={true} /> + </div> </DrawerContent> <DrawerOverlay className="relative bg-transparent" /> </Drawer> diff --git a/apps/web/src/components/Sidebar/AddMemoryDialog.tsx b/apps/web/src/components/Sidebar/AddMemoryDialog.tsx index 1406925c..63f0d122 100644 --- a/apps/web/src/components/Sidebar/AddMemoryDialog.tsx +++ b/apps/web/src/components/Sidebar/AddMemoryDialog.tsx @@ -26,7 +26,7 @@ export function AddMemoryPage({ closeDialog, defaultSpaces, onAdd }: { closeDial const [selectedSpacesId, setSelectedSpacesId] = useState<number[]>(defaultSpaces ?? []); return ( - <div className="md:w-[40vw]"> + <div className="max-w-[80vw] w-[80vw] md:w-[40vw]"> <DialogHeader> <DialogTitle>Add a web page to memory</DialogTitle> <DialogDescription> @@ -133,7 +133,7 @@ export function NoteAddPage({ closeDialog, defaultSpaces, onAdd }: { closeDialog } return ( - <div> + <div className="w-[80vw] md:w-auto"> <Input ref={inputRef} data-error="false" @@ -152,7 +152,7 @@ export function NoteAddPage({ closeDialog, defaultSpaces, onAdd }: { closeDialog setContent(editor.storage.markdown.getMarkdown()); }} extensions={[Markdown]} - className="novel-editor bg-rgray-4 border-rgray-7 dark mt-5 max-h-[60vh] min-h-[40vh] w-[50vw] overflow-y-auto rounded-lg border [&>div>div]:p-5" + className="novel-editor w-full bg-rgray-4 border-rgray-7 dark mt-5 max-h-[60vh] min-h-[40vh] md:w-[50vw] overflow-y-auto rounded-lg border [&>div>div]:p-5" /> <DialogFooter> <FilterSpaces @@ -243,7 +243,7 @@ export function SpaceAddPage({ closeDialog, onAdd }: { closeDialog: () => void, } return ( - <div className="md:w-[40vw]"> + <div className="w-[80vw] md:w-[40vw]"> <DialogHeader> <DialogTitle>Add a space</DialogTitle> </DialogHeader> @@ -376,7 +376,7 @@ export function AddExistingMemoryToSpace({ const [selected, setSelected] = useState<StoredContent[]>([]); return ( - <div className="md:w-[40vw]"> + <div className="w-[80vw] md:w-[40vw]"> <DialogHeader> <DialogTitle>Add an existing memory to {space.title}</DialogTitle> <DialogDescription> diff --git a/apps/web/src/components/Sidebar/index.tsx b/apps/web/src/components/Sidebar/index.tsx index f87d516b..f1a25a85 100644 --- a/apps/web/src/components/Sidebar/index.tsx +++ b/apps/web/src/components/Sidebar/index.tsx @@ -1,12 +1,12 @@ "use client"; import { MemoryIcon } from "../../assets/Memories"; -import { Trash2, User2 } from "lucide-react"; +import { Box, LogOut, Trash2, User2 } from "lucide-react"; import React, { useEffect, useState } from "react"; import { MemoriesBar } from "./MemoriesBar"; import { AnimatePresence, motion } from "framer-motion"; import { Bin } from "@/assets/Bin"; import { Avatar, AvatarFallback, AvatarImage } from "@radix-ui/react-avatar"; -import { useSession } from "next-auth/react"; +import { signOut, useSession } from "next-auth/react"; import MessagePoster from "@/app/MessagePoster"; export type MenuItem = { @@ -61,6 +61,7 @@ export default function Sidebar({ </div> ), label: "Profile", + content: <ProfileTab open={selectedItem !== null} /> }, ]; @@ -88,6 +89,7 @@ export default function Sidebar({ setSelectedItem={setSelectedItem} /> <div className="mt-auto" /> + {/* <MenuItem item={{ label: "Trash", @@ -97,6 +99,7 @@ export default function Sidebar({ id="trash-button" setSelectedItem={setSelectedItem} /> + */} <MenuItem item={{ label: "Profile", @@ -118,11 +121,12 @@ export default function Sidebar({ </Avatar> </div> ), + content: <ProfileTab open={selectedItem !== null} /> }} selectedItem={selectedItem} setSelectedItem={setSelectedItem} /> - <MessagePoster jwt={jwt} /> + {/* <MessagePoster jwt={jwt} /> */} </div> <AnimatePresence> {selectedItem && <SubSidebar>{Subbar}</SubSidebar>} @@ -184,3 +188,95 @@ export function SubSidebar({ children }: { children?: React.ReactNode }) { </motion.div> ); } + +export function ProfileTab({ open }: { open: boolean }) { + + const { data: session } = useSession() + + const [tweetStat, setTweetStat] = useState<[number, number] | null>(); + const [memoryStat, setMemoryStat] = useState<[number, number] | null>(); + + const [loading, setLoading] = useState(true) + + useEffect(() => { + fetch("/api/getCount").then(async resp => { + const data = await resp.json() as any; + setTweetStat([data.tweetsCount, data.tweetsLimit]) + setMemoryStat([data.pageCount, data.pageLimit]) + setLoading(false) + }) + }, [open]) + + return ( + <div className="text-rgray-11 h-full flex w-full font-normal flex-col items-start py-8 text-left"> + <div className="w-full px-8"> + <h1 className="w-full text-2xl font-medium">Profile</h1> + <div className="w-full mt-5 grid grid-cols-3"> + <img + className="rounded-full" + src={session?.user?.image ?? "/icons/white_without_bg.png"} + onError={(e) => { + (e.target as HTMLImageElement).src = "/icons/white_without_bg.png" + }} + /> + <div className="col-span-2 flex flex-col justify-center items-start"> + <h1 className="text-xl font-medium">{session?.user?.name}</h1> + <span> + {session?.user?.email} + </span> + <button + onClick={() => signOut()} + className="flex justify-center mt-auto gap-2 items-center bg-rgray-4 hover:bg-rgray-5 focus-visible:bg-rgray-5 focus-visible:ring-rgray-7 relative rounded-md px-4 py-2 ring-transparent transition focus-visible:outline-none focus-visible:ring-2 disabled:cursor-not-allowed disabled:opacity-70" + > + <LogOut className="w-4 h-4" /> + Logout + </button> + </div> + </div> + </div> + <div className="w-full mt-auto pt-8 px-8 border-t border-rgray-5"> + <h1 className="w-full text-xl flex items-center gap-2"> + <Box className="w-6 h-6" /> + Storage + </h1> + {loading ? ( + <div className="flex w-full my-5 gap-5 flex-col justify-center items-center"> + <div className="bg-rgray-5 h-6 w-full animate-pulse rounded-md text-lg"></div> + <div className="bg-rgray-5 h-6 w-full animate-pulse rounded-md text-lg"></div> + </div> + ) : ( + <> + <div className="my-5"> + <h2 className="w-full text-md flex justify-between items-center"> + Memories + <div className="flex rounded-md bg-rgray-4 px-2 py-2 text-white/50 text-xs"> + {memoryStat?.join("/")} + </div> + </h2> + <div className="rounded-full overflow-hidden w-full h-5 bg-rgray-2 mt-2"> + <div style={{ + width: `${((memoryStat?.[0] ?? 0) / (memoryStat?.[1] ?? 100))*100}%`, + minWidth: memoryStat?.[0] ?? 0 > 0 ? '5%' : '0%' + }} className="rounded-full h-full bg-rgray-5" /> + </div> + </div> + <div className="my-5"> + <h2 className="w-full text-md flex justify-between items-center"> + Tweets + <div className="flex rounded-md bg-rgray-4 px-2 py-2 text-white/50 text-xs"> + {tweetStat?.join("/")} + </div> + </h2> + <div className="rounded-full overflow-hidden w-full h-5 bg-rgray-2 mt-2"> + <div style={{ + width: `${((tweetStat?.[0] ?? 0) / (tweetStat?.[1] ?? 100))*100}%` , + minWidth: tweetStat?.[0] ?? 0 > 0 ? '5%' : '0%' + }} className="rounded-full h-full bg-rgray-5" /> + </div> + </div> + </> + )} + </div> + </div> + ) +} diff --git a/apps/web/src/components/ui/dialog.tsx b/apps/web/src/components/ui/dialog.tsx index bc36e749..12ccd5ea 100644 --- a/apps/web/src/components/ui/dialog.tsx +++ b/apps/web/src/components/ui/dialog.tsx @@ -38,7 +38,7 @@ const DialogContent = React.forwardRef< <DialogPrimitive.Content ref={ref} className={cn( - "data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] border-rgray-6 bg-rgray-3 fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] border p-6 shadow-lg duration-200 sm:rounded-lg", + "data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] border-rgray-6 bg-rgray-3 fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] border p-6 shadow-lg duration-200 rounded-lg", className, )} {...props} @@ -59,7 +59,7 @@ const DialogHeader = ({ }: React.HTMLAttributes<HTMLDivElement>) => ( <div className={cn( - "flex flex-col space-y-1.5 text-center sm:text-left", + "flex flex-col space-y-1.5 text-left", className, )} {...props} @@ -73,7 +73,7 @@ const DialogFooter = ({ }: React.HTMLAttributes<HTMLDivElement>) => ( <div className={cn( - "mt-5 flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2", + "mt-5 flex flex-row sm:flex-row sm:justify-end sm:space-x-2", className, )} {...props} |