diff options
| author | Dhravya <[email protected]> | 2024-06-02 12:12:38 -0500 |
|---|---|---|
| committer | Dhravya <[email protected]> | 2024-06-02 12:12:38 -0500 |
| commit | 5fe0caabf8e650aa221ef0faccf4520b0e1cbee2 (patch) | |
| tree | cfba72d2cbacd070eb6c7891c2c1fd36f32aac26 /apps/web/app/(dash) | |
| parent | fixed some import bugs (diff) | |
| download | supermemory-5fe0caabf8e650aa221ef0faccf4520b0e1cbee2.tar.xz supermemory-5fe0caabf8e650aa221ef0faccf4520b0e1cbee2.zip | |
improvements in dash + chatUI
Diffstat (limited to 'apps/web/app/(dash)')
| -rw-r--r-- | apps/web/app/(dash)/chat/actions.ts | 1 | ||||
| -rw-r--r-- | apps/web/app/(dash)/chat/chatWindow.tsx | 60 | ||||
| -rw-r--r-- | apps/web/app/(dash)/chat/page.tsx | 14 | ||||
| -rw-r--r-- | apps/web/app/(dash)/header.tsx | 34 | ||||
| -rw-r--r-- | apps/web/app/(dash)/home/page.tsx | 27 | ||||
| -rw-r--r-- | apps/web/app/(dash)/home/queryinput.tsx | 98 | ||||
| -rw-r--r-- | apps/web/app/(dash)/layout.tsx | 17 | ||||
| -rw-r--r-- | apps/web/app/(dash)/menu.tsx | 49 |
8 files changed, 300 insertions, 0 deletions
diff --git a/apps/web/app/(dash)/chat/actions.ts b/apps/web/app/(dash)/chat/actions.ts new file mode 100644 index 00000000..908fe79e --- /dev/null +++ b/apps/web/app/(dash)/chat/actions.ts @@ -0,0 +1 @@ +"use server"; diff --git a/apps/web/app/(dash)/chat/chatWindow.tsx b/apps/web/app/(dash)/chat/chatWindow.tsx new file mode 100644 index 00000000..8361bbf8 --- /dev/null +++ b/apps/web/app/(dash)/chat/chatWindow.tsx @@ -0,0 +1,60 @@ +"use client"; + +import { AnimatePresence } from "framer-motion"; +import React, { useEffect, useState } from "react"; +import QueryInput from "../home/queryinput"; +import { cn } from "@repo/ui/lib/utils"; +import { motion } from "framer-motion"; +import { useRouter } from "next/navigation"; + +function ChatWindow({ q, spaces }: { q: string; spaces: number[] }) { + const [layout, setLayout] = useState<"chat" | "initial">("initial"); + + const router = useRouter(); + + useEffect(() => { + if (q !== "") { + setTimeout(() => { + setLayout("chat"); + }, 300); + } else { + router.push("/home"); + } + }, [q]); + return ( + <div> + <AnimatePresence mode="popLayout"> + {layout === "initial" ? ( + <motion.div + exit={{ opacity: 0 }} + key="initial" + className="max-w-3xl flex mx-auto w-full flex-col" + > + <div className="w-full h-96"> + <QueryInput + initialQuery={q} + initialSpaces={spaces ?? []} + disabled + /> + </div> + </motion.div> + ) : ( + <div + className="max-w-3xl flex mx-auto w-full flex-col mt-8" + key="chat" + > + <h2 + className={cn( + "transition-all transform translate-y-0 opacity-100 duration-500 ease-in-out font-semibold text-2xl", + )} + > + {q} + </h2> + </div> + )} + </AnimatePresence> + </div> + ); +} + +export default ChatWindow; diff --git a/apps/web/app/(dash)/chat/page.tsx b/apps/web/app/(dash)/chat/page.tsx new file mode 100644 index 00000000..4ad4d468 --- /dev/null +++ b/apps/web/app/(dash)/chat/page.tsx @@ -0,0 +1,14 @@ +import { chatSearchParamsCache } from "../../helpers/lib/searchParams"; +import ChatWindow from "./chatWindow"; + +function Page({ + searchParams, +}: { + searchParams: Record<string, string | string[] | undefined>; +}) { + const { firstTime, q, spaces } = chatSearchParamsCache.parse(searchParams); + + return <ChatWindow q={q} spaces={spaces ?? []} />; +} + +export default Page; diff --git a/apps/web/app/(dash)/header.tsx b/apps/web/app/(dash)/header.tsx new file mode 100644 index 00000000..bcbe9691 --- /dev/null +++ b/apps/web/app/(dash)/header.tsx @@ -0,0 +1,34 @@ +import React from "react"; +import Image from "next/image"; +import Link from "next/link"; +import Logo from "../../public/logo.svg"; +import { AddIcon, ChatIcon } from "@repo/ui/icons"; + +function Header() { + return ( + <div> + <div className="flex items-center justify-between relative z-10"> + <Link href="/"> + <Image + src={Logo} + alt="SuperMemory logo" + className="hover:brightness-125 duration-200" + /> + </Link> + + <div className="absolute flex justify-center w-full -z-10"> + <button className="bg-secondary all-center h-11 rounded-full p-2 min-w-14"> + <Image src={AddIcon} alt="Add icon" /> + </button> + </div> + + <button className="flex shrink-0 duration-200 items-center gap-2 px-2 py-1.5 rounded-xl hover:bg-secondary"> + <Image src={ChatIcon} alt="Chat icon" /> + Start new chat + </button> + </div> + </div> + ); +} + +export default Header; diff --git a/apps/web/app/(dash)/home/page.tsx b/apps/web/app/(dash)/home/page.tsx new file mode 100644 index 00000000..a6624c1b --- /dev/null +++ b/apps/web/app/(dash)/home/page.tsx @@ -0,0 +1,27 @@ +import React from "react"; +import Menu from "../menu"; +import Header from "../header"; +import QueryInput from "./queryinput"; +import { homeSearchParamsCache } from "@/app/helpers/lib/searchParams"; + +function Page({ + searchParams, +}: { + searchParams: Record<string, string | string[] | undefined>; +}) { + // TODO: use this to show a welcome page/modal + const { firstTime } = homeSearchParamsCache.parse(searchParams); + + return ( + <div className="max-w-3xl flex mx-auto w-full flex-col"> + {/* all content goes here */} + {/* <div className="">hi {firstTime ? 'first time' : ''}</div> */} + + <div className="w-full h-96"> + <QueryInput /> + </div> + </div> + ); +} + +export default Page; diff --git a/apps/web/app/(dash)/home/queryinput.tsx b/apps/web/app/(dash)/home/queryinput.tsx new file mode 100644 index 00000000..904fcca6 --- /dev/null +++ b/apps/web/app/(dash)/home/queryinput.tsx @@ -0,0 +1,98 @@ +"use client"; + +import { ArrowRightIcon } from "@repo/ui/icons"; +import Image from "next/image"; +import React, { useState } from "react"; +import Divider from "@repo/ui/shadcn/divider"; +import { MultipleSelector, Option } from "@repo/ui/shadcn/combobox"; +import { useRouter } from "next/navigation"; + +const OPTIONS: Option[] = [ + { label: "nextjs", value: "0" }, + { label: "React", value: "1" }, + { label: "Remix", value: "2" }, + { label: "Vite", value: "3" }, + { label: "Nuxt", value: "4" }, + { label: "Vue", value: "5" }, + { label: "Svelte", value: "6" }, + { label: "Angular", value: "7" }, + { label: "Ember", value: "8" }, + { label: "Gatsby", value: "9" }, +]; + +function QueryInput({ + initialQuery = "", + initialSpaces = [], + disabled = false, +}: { + initialQuery?: string; + initialSpaces?: number[]; + disabled?: boolean; +}) { + const [q, setQ] = useState(initialQuery); + + const [selectedSpaces, setSelectedSpaces] = useState<number[]>(initialSpaces); + + const { push } = useRouter(); + + const parseQ = () => { + const newQ = + "/chat?q=" + + encodeURI(q) + + (selectedSpaces ? "&spaces=" + selectedSpaces.join(",") : ""); + + return newQ; + }; + + return ( + <div> + <div className="bg-secondary rounded-t-[24px] w-full mt-40"> + {/* input and action button */} + <form action={async () => push(parseQ())} className="flex gap-4 p-3"> + <textarea + name="q" + cols={30} + rows={4} + className="bg-transparent pt-2.5 text-base text-[#989EA4] focus:text-foreground duration-200 tracking-[3%] outline-none resize-none w-full p-4" + placeholder="Ask your second brain..." + onKeyDown={(e) => { + if (e.key === "Enter") { + e.preventDefault(); + if (!e.shiftKey) push(parseQ()); + } + }} + onChange={(e) => setQ(e.target.value)} + value={q} + disabled={disabled} + /> + + <button + type="submit" + disabled={disabled} + className="h-12 w-12 rounded-[14px] bg-[#21303D] all-center shrink-0 hover:brightness-125 duration-200 outline-none focus:outline focus:outline-primary active:scale-90" + > + <Image src={ArrowRightIcon} alt="Right arrow icon" /> + </button> + </form> + + <Divider /> + </div> + {/* selected sources */} + <div className="flex items-center gap-6 p-2 h-auto bg-secondary rounded-b-[24px]"> + <MultipleSelector + disabled={disabled} + defaultOptions={OPTIONS} + onChange={(e) => setSelectedSpaces(e.map((x) => parseInt(x.value)))} + placeholder="Focus on specific spaces..." + emptyIndicator={ + <p className="text-center text-lg leading-10 text-gray-600 dark:text-gray-400"> + no results found. + </p> + } + /> + </div> + </div> + ); +} + +export default QueryInput; diff --git a/apps/web/app/(dash)/layout.tsx b/apps/web/app/(dash)/layout.tsx new file mode 100644 index 00000000..07c16485 --- /dev/null +++ b/apps/web/app/(dash)/layout.tsx @@ -0,0 +1,17 @@ +import React from "react"; +import Header from "./header"; +import Menu from "./menu"; + +function Layout({ children }: { children: React.ReactNode }) { + return ( + <main className="h-screen flex flex-col p-4 relative"> + <Header /> + + <Menu /> + + {children} + </main> + ); +} + +export default Layout; diff --git a/apps/web/app/(dash)/menu.tsx b/apps/web/app/(dash)/menu.tsx new file mode 100644 index 00000000..1177bca6 --- /dev/null +++ b/apps/web/app/(dash)/menu.tsx @@ -0,0 +1,49 @@ +import React from "react"; +import Image from "next/image"; +import { MemoriesIcon, ExploreIcon, HistoryIcon } from "@repo/ui/icons"; + +function Menu() { + const menuItems = [ + { + icon: MemoriesIcon, + text: "Memories", + url: "/", + }, + { + icon: ExploreIcon, + text: "Explore", + url: "/explore", + }, + { + icon: HistoryIcon, + text: "History", + url: "/history", + }, + ]; + + return ( + <div className="absolute h-full p-4 flex items-center top-0 left-0"> + <div className=""> + <div className="hover:rounded-2x group inline-flex w-14 text-foreground-menu text-[15px] font-medium flex-col items-start gap-6 overflow-hidden rounded-[28px] bg-secondary px-3 py-4 duration-200 hover:w-40"> + {menuItems.map((item) => ( + <div + key={item.url} + className="flex w-full cursor-pointer items-center gap-3 px-1 duration-200 hover:scale-105 hover:brightness-150 active:scale-90" + > + <Image + src={item.icon} + alt={`${item.text} icon`} + className="hover:brightness-125 duration-200" + /> + <p className="opacity-0 duration-200 group-hover:opacity-100"> + {item.text} + </p> + </div> + ))} + </div> + </div> + </div> + ); +} + +export default Menu; |