diff options
| author | codetorso <[email protected]> | 2024-07-20 07:17:25 +0530 |
|---|---|---|
| committer | codetorso <[email protected]> | 2024-07-20 07:17:25 +0530 |
| commit | 21fe55a96c36892fc50e8198248da825a5a4bd6f (patch) | |
| tree | 83832e20adb0e4acfa529ac624e7f2b914f7a25a /apps/web/app/(dash)/home | |
| parent | fix links (diff) | |
| download | supermemory-torso.tar.xz supermemory-torso.zip | |
for god's sake this should work ;)torso
Diffstat (limited to 'apps/web/app/(dash)/home')
| -rw-r--r-- | apps/web/app/(dash)/home/filterSpaces.tsx | 108 | ||||
| -rw-r--r-- | apps/web/app/(dash)/home/heading.tsx | 38 | ||||
| -rw-r--r-- | apps/web/app/(dash)/home/history.tsx | 50 | ||||
| -rw-r--r-- | apps/web/app/(dash)/home/homeVariants.ts | 50 | ||||
| -rw-r--r-- | apps/web/app/(dash)/home/page.tsx | 97 | ||||
| -rw-r--r-- | apps/web/app/(dash)/home/queryinput.tsx | 163 |
6 files changed, 252 insertions, 254 deletions
diff --git a/apps/web/app/(dash)/home/filterSpaces.tsx b/apps/web/app/(dash)/home/filterSpaces.tsx new file mode 100644 index 00000000..8818791a --- /dev/null +++ b/apps/web/app/(dash)/home/filterSpaces.tsx @@ -0,0 +1,108 @@ +import { ChevronUpDownIcon } from "@heroicons/react/24/outline"; +import { ArrowRightIcon } from "@repo/ui/icons"; +import { + Command, + CommandGroup, + CommandInput, + CommandItem, + CommandList, +} from "@repo/ui/shadcn/command"; +import { Check } from "lucide-react"; +import Image from "next/image"; +import React, { useState } from "react"; + +type space = { + id: number; + name: string; +}; + +export function FilterSpaces({ + initialSpaces, + selectedSpaces, + setSelectedSpaces, +}: { + initialSpaces: space[]; + selectedSpaces: space[]; + setSelectedSpaces: React.Dispatch<React.SetStateAction<space[]>>; +}) { + const [input, setInput] = useState<string>(""); + + const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => { + if (e.key === "Backspace" && input === "") { + setSelectedSpaces((prevValue) => prevValue.slice(0, -1)); + } + }; + + const handleSelect = (selectedSpace: space) => { + setSelectedSpaces((current) => + current.some((space) => space.id === selectedSpace.id) + ? current.filter((space) => space.id !== selectedSpace.id) + : [...current, selectedSpace], + ); + }; + + return ( + <div className="flex p-2 px-3 w-full items-center justify-between rounded-xl overflow-hidden"> + <div className="flex bg-[#2C3338] rounded-xl overflow-hidden pl-1"> + <div className="flex rounded-lg items-center"> + {selectedSpaces.map((v) => ( + <button + key={v.id} + onClick={() => handleSelect(v)} + className="bg-[#3a4248] text-white max-w-32 truncate-wor truncate whitespace-nowrap py-1 rounded-md px-2 mx-1 aria-selected:outline" + > + {v.name} + </button> + ))} + </div> + <Command + className={`group transition-all border-0 bg-[#2c3338] text-white outline-0 ${ + selectedSpaces.length ? "w-5 hover:w-24 focus-within:w-20" : "w-44" + }`} + > + <div className="relative"> + <CommandInput + placeholder={selectedSpaces.length ? "" : "Search in Spaces"} + onKeyDown={handleKeyDown} + className="text-white peer placeholder:text-white pl-2" + onChangeCapture={(e) => setInput(e.currentTarget.value)} + value={input} + /> + <ChevronUpDownIcon + className={`h-6 w-6 text-[#858B92] pointer-events-none absolute top-1/2 -translate-y-1/2 right-2 ${ + selectedSpaces.length && "opacity-0" + }`} + /> + </div> + <CommandList className="z-10 translate-y-12 translate-x-5 opacity-0 absolute group-focus-within:opacity-100 transition-opacity p-2 rounded-lg max-w-64 bg-[#2C3338]"> + <CommandGroup className="pointer-events-none opacity-0 group-focus-within:opacity-100 scale-50 scale-y-50 group-focus-within:scale-y-100 group-focus-within:scale-100 group-focus-within:pointer-events-auto transition-all origin-top"> + {initialSpaces.map((space) => { + if (!selectedSpaces.some((v) => v.id === space.id)) { + return ( + <CommandItem + className="text-[#eaeaea] data-[disabled]:opacity-90" + value={space.name} + key={space.id} + onSelect={() => handleSelect(space)} + > + <Check + className={`mr-2 h-4 w-4 ${selectedSpaces.some((v) => v.id === space.id) ? "opacity-100" : "opacity-0"}`} + /> + {space.name} + </CommandItem> + ); + } + })} + </CommandGroup> + </CommandList> + </Command> + </div> + <button + type="submit" + className="rounded-lg bg-[#369DFD1A] p-3" + > + <Image src={ArrowRightIcon} alt="Enter" /> + </button> + </div> + ); +} diff --git a/apps/web/app/(dash)/home/heading.tsx b/apps/web/app/(dash)/home/heading.tsx new file mode 100644 index 00000000..dc5b8799 --- /dev/null +++ b/apps/web/app/(dash)/home/heading.tsx @@ -0,0 +1,38 @@ +import { useEffect, useState } from "react"; +import { AnimatePresence, motion } from "framer-motion"; +import { Inter } from "next/font/google"; + +const poppins = Inter({ subsets: ["latin"], weight: ["600"] }); + +const headings = [ + "Unlock your digital brain", + "Save everything.", + " Connect anything.", + "Turn your bookmarks into insights.", + "The smart way to use your digital treasure.", +]; + +export function Heading({ queryPresent }: { queryPresent: boolean }) { + const [showHeading, setShowHeading] = useState<number>(0); + useEffect(() => { + setShowHeading(Math.floor(Math.random() * headings.length)); + }, [queryPresent]); + return ( + <div className="h-[7rem] flex items-end justify-center overflow-hidden text-white"> + <AnimatePresence mode="popLayout"> + {!queryPresent && ( + <motion.h1 + initial={{ opacity: 0, y: "20%" }} + animate={{ opacity: 1, y: "0%" }} + exit={{ opacity: 0, y: "20%", whiteSpace: "nowrap" }} + className={`text-[2.45rem] font-semibold ${ + queryPresent ? "pointer-events-none" : "pointer-events-auto" + } transition-opacity text-center ${poppins.className}`} + > + {headings[showHeading]} + </motion.h1> + )} + </AnimatePresence> + </div> + ); +} diff --git a/apps/web/app/(dash)/home/history.tsx b/apps/web/app/(dash)/home/history.tsx new file mode 100644 index 00000000..9c6757e5 --- /dev/null +++ b/apps/web/app/(dash)/home/history.tsx @@ -0,0 +1,50 @@ +import { getRecentChats } from "@/app/actions/fetchers"; +import { ArrowLongRightIcon } from "@heroicons/react/24/outline"; +import { Skeleton } from "@repo/ui/shadcn/skeleton"; +import Link from "next/link"; +import { memo, useEffect, useState } from "react"; +import { motion } from "framer-motion"; + +const History = memo(() => { + const [chatThreads, setChatThreads] = useState(null); + + useEffect(() => { + (async () => { + const chatThreads = await getRecentChats(); + + // @ts-ignore + setChatThreads(chatThreads); + })(); + }, []); + + if (!chatThreads) { + return ( + <> + <Skeleton className="w-[80%] h-4 bg-[#3b444b] "></Skeleton> + <Skeleton className="w-[40%] h-4 bg-[#3b444b] "></Skeleton> + <Skeleton className="w-[60%] h-4 bg-[#3b444b] "></Skeleton> + </> + ); + } + + // @ts-ignore, time wastage + if (!chatThreads.success || !chatThreads.data) { + return <div>Error fetching chat threads</div>; + } + + return ( + <ul className="text-base list-none space-y-3 text-[#b9b9b9]"> + {/* @ts-ignore */} + {chatThreads.data.map((thread) => ( + <motion.li initial={{opacity: 0, filter: "blur(1px)"}} animate={{opacity: 1, filter: "blur(0px)"}} className="flex items-center gap-2 truncate"> + <ArrowLongRightIcon className="h-5" />{" "} + <Link prefetch={false} href={`/chat/${thread.id}`}> + {thread.firstMessage} + </Link> + </motion.li> + ))} + </ul> + ); +}); + +export default History;
\ No newline at end of file diff --git a/apps/web/app/(dash)/home/homeVariants.ts b/apps/web/app/(dash)/home/homeVariants.ts deleted file mode 100644 index 1b44bab9..00000000 --- a/apps/web/app/(dash)/home/homeVariants.ts +++ /dev/null @@ -1,50 +0,0 @@ -export const variants = [ - [ - { - type: "text", - content: "Unlock your", - }, - { - type: "highlighted", - content: " digital brain", - }, - ], - [ - { - type: "text", - content: "Save", - }, - { - type: "highlighted", - content: " everything.", - }, - { - type: "text", - content: " Connect", - }, - { - type: "highlighted", - content: " anything.", - }, - ], - [ - { - type: "text", - content: "Turn your bookmarks into", - }, - { - type: "highlighted", - content: " insights.", - }, - ], - [ - { - type: "text", - content: "The smart way to use your", - }, - { - type: "highlighted", - content: " digital treasure.", - }, - ], -]; diff --git a/apps/web/app/(dash)/home/page.tsx b/apps/web/app/(dash)/home/page.tsx index 378acdf8..630c4306 100644 --- a/apps/web/app/(dash)/home/page.tsx +++ b/apps/web/app/(dash)/home/page.tsx @@ -6,21 +6,18 @@ import { getSessionAuthToken, getSpaces } from "@/app/actions/fetchers"; import { useRouter } from "next/navigation"; import { createChatThread, linkTelegramToUser } from "@/app/actions/doers"; import { toast } from "sonner"; -import { motion } from "framer-motion"; -import { variants } from "./homeVariants"; +import { Heading } from "./heading"; +import History from "./history"; import { ChromeIcon, GithubIcon, TwitterIcon } from "lucide-react"; -const slap = { - initial: { - opacity: 0, - scale: 1.1, - }, - whileInView: { opacity: 1, scale: 1 }, - transition: { - duration: 0.5, - ease: "easeInOut", - }, - viewport: { once: true }, +const linkTelegram = async (telegramUser: string) => { + const response = await linkTelegramToUser(telegramUser); + + if (response.success) { + toast.success("Your telegram has been linked successfully."); + } else { + toast.error("Failed to link telegram. Please try again."); + } }; function Page({ @@ -28,41 +25,24 @@ function Page({ }: { searchParams: Record<string, string | string[] | undefined>; }) { - // TODO: use this to show a welcome page/modal - // const { firstTime } = homeSearchParamsCache.parse(searchParams); - - const [telegramUser, setTelegramUser] = useState<string | undefined>( - searchParams.telegramUser as string, - ); - const [extensionInstalled, setExtensionInstalled] = useState< - string | undefined - >(searchParams.extension as string); - const { push } = useRouter(); const [spaces, setSpaces] = useState<{ id: number; name: string }[]>([]); - const [showVariant, setShowVariant] = useState<number>(0); + const [queryPresent, setQueryPresent] = useState<boolean>(false); useEffect(() => { + // telegram bot + const telegramUser = searchParams.extension as string; if (telegramUser) { - const linkTelegram = async () => { - const response = await linkTelegramToUser(telegramUser); - - if (response.success) { - toast.success("Your telegram has been linked successfully."); - } else { - toast.error("Failed to link telegram. Please try again."); - } - }; - - linkTelegram(); + linkTelegram(telegramUser); } - if (extensionInstalled) { + if (searchParams.extension as string) { toast.success("Extension installed successfully"); } + // fetch spaces getSpaces().then((res) => { if (res.success && res.data) { setSpaces(res.data); @@ -71,44 +51,18 @@ function Page({ // TODO: HANDLE ERROR }); - setShowVariant(Math.floor(Math.random() * variants.length)); - getSessionAuthToken().then((token) => { if (typeof window === "undefined") return; window.postMessage({ token: token.data }, "*"); }); - }, [telegramUser]); + }, []); return ( - <div className="max-w-3xl h-full justify-center flex mx-auto w-full flex-col px-2 md:px-0"> - {/* all content goes here */} - {/* <div className="">hi {firstTime ? 'first time' : ''}</div> */} - - <motion.h1 - {...{ - ...slap, - transition: { ...slap.transition, delay: 0.2 }, - }} - className="text-center mx-auto bg-[linear-gradient(180deg,_#FFF_0%,_rgba(255,_255,_255,_0.00)_202.08%)] bg-clip-text text-4xl tracking-tighter text-transparent md:text-5xl" - > - {variants[showVariant]!.map((v, i) => { - return ( - <span - key={i} - className={ - v.type === "highlighted" - ? "bg-gradient-to-r to-blue-200 from-zinc-300 text-transparent bg-clip-text" - : "" - } - > - {v.content} - </span> - ); - })} - </motion.h1> - - <div className="w-full pb-20 mt-12"> + <div className="max-w-3xl mt-[18vh] mx-auto w-full px-2 md:px-0"> + <Heading queryPresent={queryPresent} /> + <div className="w-full py-12"> <QueryInput + setQueryPresent={(t: boolean) => setQueryPresent(t)} handleSubmit={async (q, spaces) => { if (q.length === 0) { toast.error("Query is required"); @@ -127,9 +81,12 @@ function Page({ ); }} initialSpaces={spaces} - setInitialSpaces={setSpaces} /> </div> + <div className="space-y-5"> + <h3 className="text-lg">Recent Searches</h3> + <History /> + </div> <div className="w-full fixed bottom-0 left-0 p-4"> <div className="flex items-center justify-center gap-8"> @@ -143,7 +100,7 @@ function Page({ Install extension </a> <a - href="https://github.com/supermemoryai/supermemory/issues/new" + href="https://github.com/Dhravya/supermemory/issues/new" target="_blank" rel="noreferrer" className="flex items-center gap-2 text-muted-foreground" @@ -152,7 +109,7 @@ function Page({ Bug report </a> <a - href="https://x.com/supermemoryai" + href="https://x.com/supermemory.ai" target="_blank" rel="noreferrer" className="flex items-center gap-2 text-muted-foreground" diff --git a/apps/web/app/(dash)/home/queryinput.tsx b/apps/web/app/(dash)/home/queryinput.tsx index c7267298..f15a712b 100644 --- a/apps/web/app/(dash)/home/queryinput.tsx +++ b/apps/web/app/(dash)/home/queryinput.tsx @@ -1,83 +1,48 @@ "use client"; -import { ArrowRightIcon } from "@repo/ui/icons"; -import Image from "next/image"; -import React, { useEffect, useMemo, useState } from "react"; -import Divider from "@repo/ui/shadcn/divider"; -import { useRouter } from "next/navigation"; -import { getSpaces } from "@/app/actions/fetchers"; -import Combobox from "@repo/ui/shadcn/combobox"; -import { MinusIcon } from "lucide-react"; -import { toast } from "sonner"; -import { createSpace } from "@/app/actions/doers"; +import React, { useState } from "react"; +import { FilterSpaces } from "./filterSpaces"; function QueryInput({ - initialQuery = "", - initialSpaces = [], - disabled = false, - className, - mini = false, + setQueryPresent, + initialSpaces, handleSubmit, - setInitialSpaces, }: { - initialQuery?: string; + setQueryPresent: (t: boolean) => void; initialSpaces?: { id: number; name: string; }[]; - disabled?: boolean; - className?: string; mini?: boolean; handleSubmit: (q: string, spaces: { id: number; name: string }[]) => void; - setInitialSpaces?: React.Dispatch< - React.SetStateAction<{ id: number; name: string }[]> - >; }) { - const [q, setQ] = useState(initialQuery); + const [q, setQ] = useState(""); - const [selectedSpaces, setSelectedSpaces] = useState<number[]>([]); - - const options = useMemo( - () => - initialSpaces.map((x) => ({ - label: x.name, - value: x.id.toString(), - })), - [initialSpaces], - ); - - const preparedSpaces = useMemo( - () => - initialSpaces - .filter((x) => selectedSpaces.includes(x.id)) - .map((x) => { - return { - id: x.id, - name: x.name, - }; - }), - [selectedSpaces, initialSpaces], - ); + const [selectedSpaces, setSelectedSpaces] = useState< + { id: number; name: string }[] + >([]); return ( - <div className={`${className}`}> + <div className={`w-full`}> <div - className={`bg-secondary border-2 border-b-0 border-border ${!mini ? "rounded-t-3xl" : "rounded-3xl"}`} + className={`bg-[#1F2428] overflow-hidden border-2 border-gray-700/50 shadow-md shadow-[#1d1d1dc7] rounded-3xl`} > {/* input and action button */} <form action={async () => { - handleSubmit(q, preparedSpaces); + if (q.trim().length === 0) { + return; + } + handleSubmit(q, selectedSpaces); setQ(""); }} - className="flex gap-4 p-3" > <textarea autoFocus name="q" cols={30} - rows={mini ? 2 : 4} - className="bg-transparent pt-2.5 text-base placeholder:text-[#9B9B9B] focus:text-gray-200 duration-200 tracking-[3%] outline-none resize-none w-full p-4" + rows={4} + className="bg-transparent pt-2.5 text-lg placeholder:text-[#9B9B9B] text-gray-200 tracking-[3%] outline-none resize-none w-full p-4" placeholder="Ask your second brain..." onKeyDown={(e) => { if (e.key === "Enter" && !e.shiftKey) { @@ -85,95 +50,25 @@ function QueryInput({ if (q.trim().length === 0) { return; } - handleSubmit(q, preparedSpaces); + handleSubmit(q, selectedSpaces); setQ(""); } }} - onChange={(e) => setQ(e.target.value)} + onChange={(e) => + setQ((prev) => { + setQueryPresent(!!e.target.value.length); + return e.target.value; + }) + } value={q} - disabled={disabled} /> - - <button - type="submit" - onClick={(e) => { - e.preventDefault(); - if (q.trim().length === 0) { - return; - } - handleSubmit(q, preparedSpaces); - }} - disabled={disabled} - className="h-12 w-12 rounded-[14px] bg-border 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> + <FilterSpaces + selectedSpaces={selectedSpaces} + setSelectedSpaces={setSelectedSpaces} + initialSpaces={initialSpaces || []} + /> </form> </div> - {/* selected sources */} - {!mini && ( - <> - <Divider /> - <div className="flex justify-between items-center gap-6 h-auto bg-secondary rounded-b-3xl border-2 border-border"> - <Combobox - options={options} - className="rounded-bl-3xl bg-[#3C464D] w-44" - onSelect={(v) => - setSelectedSpaces((prev) => { - if (v === "") { - return []; - } - return [...prev, parseInt(v)]; - }) - } - onSubmit={async (spaceName) => { - const space = options.find((x) => x.label === spaceName); - toast.info("Creating space..."); - - if (space) { - toast.error("A space with that name already exists."); - } - - const creationTask = await createSpace(spaceName); - if (creationTask.success && creationTask.data) { - toast.success("Space created " + creationTask.data); - setInitialSpaces?.((prev) => [ - ...prev, - { - name: spaceName, - id: creationTask.data!, - }, - ]); - setSelectedSpaces((prev) => [...prev, creationTask.data!]); - } else { - toast.error( - "Space creation failed: " + creationTask.error ?? - "Unknown error", - ); - } - }} - placeholder="Chat with a space..." - /> - - <div className="flex flex-row gap-0.5 h-full"> - {preparedSpaces.map((x, idx) => ( - <button - key={x.id} - onClick={() => - setSelectedSpaces((prev) => prev.filter((y) => y !== x.id)) - } - className={`relative group p-2 py-3 bg-[#3C464D] max-w-32 ${idx === preparedSpaces.length - 1 ? "rounded-br-xl" : ""}`} - > - <p className="line-clamp-1">{x.name}</p> - <div className="absolute h-full right-0 top-0 p-1 opacity-0 group-hover:opacity-100 items-center"> - <MinusIcon className="w-6 h-6 rounded-full bg-secondary" /> - </div> - </button> - ))} - </div> - </div> - </> - )} </div> ); } |