aboutsummaryrefslogtreecommitdiff
path: root/apps/web/app/(dash)
diff options
context:
space:
mode:
authorDhravya <[email protected]>2024-06-02 12:12:38 -0500
committerDhravya <[email protected]>2024-06-02 12:12:38 -0500
commit5fe0caabf8e650aa221ef0faccf4520b0e1cbee2 (patch)
treecfba72d2cbacd070eb6c7891c2c1fd36f32aac26 /apps/web/app/(dash)
parentfixed some import bugs (diff)
downloadsupermemory-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.ts1
-rw-r--r--apps/web/app/(dash)/chat/chatWindow.tsx60
-rw-r--r--apps/web/app/(dash)/chat/page.tsx14
-rw-r--r--apps/web/app/(dash)/header.tsx34
-rw-r--r--apps/web/app/(dash)/home/page.tsx27
-rw-r--r--apps/web/app/(dash)/home/queryinput.tsx98
-rw-r--r--apps/web/app/(dash)/layout.tsx17
-rw-r--r--apps/web/app/(dash)/menu.tsx49
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;