diff options
| author | Dhravya <[email protected]> | 2024-06-30 19:18:33 -0500 |
|---|---|---|
| committer | Dhravya <[email protected]> | 2024-06-30 19:18:33 -0500 |
| commit | 163a55ce8f462f46ecd332b905914c5e4950c02e (patch) | |
| tree | 99184911519b492e54aeaa737979b7dcea55f7fa | |
| parent | makeshift notes page (diff) | |
| download | supermemory-163a55ce8f462f46ecd332b905914c5e4950c02e.tar.xz supermemory-163a55ce8f462f46ecd332b905914c5e4950c02e.zip | |
revalidate page
| -rw-r--r-- | apps/web/app/(dash)/memories/content.tsx | 221 | ||||
| -rw-r--r-- | apps/web/app/(dash)/memories/page.tsx | 228 | ||||
| -rw-r--r-- | apps/web/app/(dash)/menu.tsx | 3 | ||||
| -rw-r--r-- | apps/web/app/actions/doers.ts | 5 | ||||
| -rw-r--r-- | apps/web/app/layout.tsx | 1 | ||||
| -rw-r--r-- | apps/web/migrations/cmd.sql | 1 | ||||
| -rw-r--r-- | package.json | 1 |
7 files changed, 234 insertions, 226 deletions
diff --git a/apps/web/app/(dash)/memories/content.tsx b/apps/web/app/(dash)/memories/content.tsx new file mode 100644 index 00000000..6b0b31b7 --- /dev/null +++ b/apps/web/app/(dash)/memories/content.tsx @@ -0,0 +1,221 @@ +"use client"; + +import { getAllUserMemoriesAndSpaces } from "@/app/actions/fetchers"; +import { Content, StoredSpace } from "@/server/db/schema"; +import { MemoriesIcon, NextIcon, SearchIcon, UrlIcon } from "@repo/ui/icons"; +import { NotebookIcon, PaperclipIcon } from "lucide-react"; +import Image from "next/image"; +import Link from "next/link"; +import React, { useEffect, useMemo, useState } from "react"; +import Masonry from "react-layout-masonry"; +import { getRawTweet } from "@repo/shared-types/utils"; +import { MyTweet } from "./render-tweet"; + +export function MemoriesPage({ + memoriesAndSpaces, +}: { + memoriesAndSpaces: { memories: Content[]; spaces: StoredSpace[] }; +}) { + const [filter, setFilter] = useState("All"); + + // Sort Both memories and spaces by their savedAt and createdAt dates respectfully. + // The output should be just one single list of items + // And it will look something like { item: "memory" | "space", date: Date, data: Content | StoredSpace } + const sortedItems = useMemo(() => { + // Merge the lists + const unifiedItems = [ + ...memoriesAndSpaces.memories.map((memory) => ({ + item: "memory", + date: new Date(memory.savedAt), // Assuming savedAt is a string date + data: memory, + })), + ...memoriesAndSpaces.spaces.map((space) => ({ + item: "space", + date: new Date(space.createdAt), // Assuming createdAt is a string date + data: space, + })), + ].map((item) => ({ + ...item, + date: Number(item.date), // Convert the date to a number + })); + + // Sort the merged list + return unifiedItems + .filter((item) => { + if (filter === "All") return true; + if (filter === "Spaces" && item.item === "space") { + console.log(item); + return true; + } + if (filter === "Pages") + return ( + item.item === "memory" && (item.data as Content).type === "page" + ); + if (filter === "Notes") + return ( + item.item === "memory" && (item.data as Content).type === "note" + ); + if (filter === "Tweet") + return ( + item.item === "memory" && (item.data as Content).type === "tweet" + ); + return false; + }) + .sort((a, b) => b.date - a.date); + }, [memoriesAndSpaces.memories, memoriesAndSpaces.spaces, filter]); + + return ( + <div + key={`${memoriesAndSpaces.memories.length + memoriesAndSpaces.spaces.length}`} + className="px-2 md:px-32 py-36 h-full flex mx-auto w-full flex-col gap-6" + > + <h2 className="text-white w-full font-medium text-2xl text-left"> + My Memories + </h2> + + <Filters setFilter={setFilter} filter={filter} /> + + <Masonry + className="mt-6 relative" + columns={{ 640: 1, 768: 2, 1024: 3 }} + gap={12} + columnProps={{ + className: "min-w-[calc(33.3333%-12px)] w-full", + }} + > + {sortedItems.map((item) => { + if (item.item === "memory") { + return ( + <LinkComponent + type={(item.data as Content).type ?? "note"} + content={(item.data as Content).content} + title={(item.data as Content).title ?? "Untitled"} + url={ + (item.data as Content).baseUrl ?? (item.data as Content).url + } + image={ + (item.data as Content).ogImage ?? + (item.data as Content).image ?? + "/placeholder-image.svg" // TODO: add this placeholder + } + description={(item.data as Content).description ?? ""} + /> + ); + } + + if (item.item === "space") { + return ( + <TabComponent + title={(item.data as StoredSpace).name} + description={`${(item.data as StoredSpace).numItems} memories`} + /> + ); + } + + return null; + })} + </Masonry> + </div> + ); +} + +function TabComponent({ + title, + description, +}: { + title: string; + description: string; +}) { + // TODO: Display the space name and desription which is the number of elemenet in the space + return ( + <div className="flex flex-col gap-4 bg-[#161f2a]/30 backdrop-blur-md border-2 border-border w-full rounded-xl p-4 -z-10"> + <div className="flex items-center gap-2 text-xs"> + <Image alt="Spaces icon" src={MemoriesIcon} className="size-3" /> Space + </div> + <div className="flex items-center"> + <div> + <div className="h-12 w-12 flex justify-center items-center rounded-md"> + {title.slice(0, 2).toUpperCase()} + </div> + </div> + <div className="grow px-4"> + <div className="text-lg text-[#fff] line-clamp-2">{title}</div> + <div>{description}</div> + </div> + <div> + <Image src={NextIcon} alt="Search icon" /> + </div> + </div> + </div> + ); +} + +function LinkComponent({ + type, + content, + title, + url, + image, + description, +}: { + type: string; + content: string; + title: string; + url: string; + image?: string; + description: string; +}) { + // TODO: DISPLAY THE ITEM BASED ON `type` being note or page + return ( + <Link + href={url.replace("https://beta.supermemory.ai", "")} + className={`bg-secondary border-2 border-border rounded-xl ${type === "tweet" ? "" : "p-4"} hover:scale-105 transition duration-200`} + > + {type === "page" ? ( + <> + <div className="flex items-center gap-2 text-xs"> + <PaperclipIcon className="w-3 h-3" /> Page + </div> + <div className="text-lg text-[#fff] mt-4 line-clamp-2">{title}</div> + <div>{url}</div> + </> + ) : type === "note" ? ( + <> + <div className="flex items-center gap-2 text-xs"> + <NotebookIcon className="w-3 h-3" /> Note + </div> + <div className="text-lg text-[#fff] mt-4 line-clamp-2">{title}</div> + <div className="line-clamp-3">{content.replace(title, "")}</div> + </> + ) : type === "tweet" ? ( + <MyTweet tweet={JSON.parse(getRawTweet(content) ?? "{}")} /> + ) : null} + </Link> + ); +} + +const FilterMethods = ["All", "Spaces", "Pages", "Notes", "Tweet"]; +function Filters({ + setFilter, + filter, +}: { + setFilter: (i: string) => void; + filter: string; +}) { + return ( + <div className="flex gap-4"> + {FilterMethods.map((i) => { + return ( + <button + onClick={() => setFilter(i)} + className={`transition px-6 py-2 rounded-xl bg-border ${i === filter ? " text-[#369DFD]" : "text-[#B3BCC5] bg-secondary hover:bg-secondary hover:text-[#76a3cc]"}`} + > + {i} + </button> + ); + })} + </div> + ); +} + +export default MemoriesPage; diff --git a/apps/web/app/(dash)/memories/page.tsx b/apps/web/app/(dash)/memories/page.tsx index fa074a0b..1856161f 100644 --- a/apps/web/app/(dash)/memories/page.tsx +++ b/apps/web/app/(dash)/memories/page.tsx @@ -1,227 +1,11 @@ -"use client"; - import { getAllUserMemoriesAndSpaces } from "@/app/actions/fetchers"; -import { Content, StoredSpace } from "@/server/db/schema"; -import { MemoriesIcon, NextIcon, SearchIcon, UrlIcon } from "@repo/ui/icons"; -import { NotebookIcon, PaperclipIcon } from "lucide-react"; -import Image from "next/image"; -import Link from "next/link"; -import React, { useEffect, useMemo, useState } from "react"; -import Masonry from "react-layout-masonry"; -import { getRawTweet } from "@repo/shared-types/utils"; -import { MyTweet } from "./render-tweet"; - -function Page() { - const [filter, setFilter] = useState("All"); - - const [memoriesAndSpaces, setMemoriesAndSpaces] = useState<{ - memories: Content[]; - spaces: StoredSpace[]; - }>({ memories: [], spaces: [] }); - - // Sort Both memories and spaces by their savedAt and createdAt dates respectfully. - // The output should be just one single list of items - // And it will look something like { item: "memory" | "space", date: Date, data: Content | StoredSpace } - const sortedItems = useMemo(() => { - // Merge the lists - const unifiedItems = [ - ...memoriesAndSpaces.memories.map((memory) => ({ - item: "memory", - date: new Date(memory.savedAt), // Assuming savedAt is a string date - data: memory, - })), - ...memoriesAndSpaces.spaces.map((space) => ({ - item: "space", - date: new Date(space.createdAt), // Assuming createdAt is a string date - data: space, - })), - ].map((item) => ({ - ...item, - date: Number(item.date), // Convert the date to a number - })); - - // Sort the merged list - return unifiedItems - .filter((item) => { - if (filter === "All") return true; - if (filter === "Spaces" && item.item === "space") { - console.log(item); - return true; - } - if (filter === "Pages") - return ( - item.item === "memory" && (item.data as Content).type === "page" - ); - if (filter === "Notes") - return ( - item.item === "memory" && (item.data as Content).type === "note" - ); - if (filter === "Tweet") - return ( - item.item === "memory" && (item.data as Content).type === "tweet" - ); - return false; - }) - .sort((a, b) => b.date - a.date); - }, [memoriesAndSpaces.memories, memoriesAndSpaces.spaces, filter]); - - useEffect(() => { - (async () => { - const { success, data } = await getAllUserMemoriesAndSpaces(); - if (!success ?? !data) return; - setMemoriesAndSpaces({ memories: data.memories, spaces: data.spaces }); - })(); - }, []); - - return ( - <div className="px-2 md:px-32 py-36 h-full flex mx-auto w-full flex-col gap-6"> - <h2 className="text-white w-full font-medium text-2xl text-left"> - My Memories - </h2> - - <Filters setFilter={setFilter} filter={filter} /> - - <Masonry - className="mt-6 relative" - columns={{ 640: 1, 768: 2, 1024: 3 }} - gap={12} - columnProps={{ - className: "min-w-[calc(33.3333%-12px)] w-full", - }} - > - {sortedItems.map((item) => { - if (item.item === "memory") { - return ( - <LinkComponent - type={(item.data as Content).type ?? "note"} - content={(item.data as Content).content} - title={(item.data as Content).title ?? "Untitled"} - url={ - (item.data as Content).baseUrl ?? (item.data as Content).url - } - image={ - (item.data as Content).ogImage ?? - (item.data as Content).image ?? - "/placeholder-image.svg" // TODO: add this placeholder - } - description={(item.data as Content).description ?? ""} - /> - ); - } - - if (item.item === "space") { - return ( - <TabComponent - title={(item.data as StoredSpace).name} - description={`${(item.data as StoredSpace).numItems} memories`} - /> - ); - } - - return null; - })} - </Masonry> - </div> - ); -} - -function TabComponent({ - title, - description, -}: { - title: string; - description: string; -}) { - // TODO: Display the space name and desription which is the number of elemenet in the space - return ( - <div className="flex flex-col gap-4 bg-[#161f2a]/30 backdrop-blur-md border-2 border-border w-full rounded-xl p-4 -z-10"> - <div className="flex items-center gap-2 text-xs"> - <Image alt="Spaces icon" src={MemoriesIcon} className="size-3" /> Space - </div> - <div className="flex items-center"> - <div> - <div className="h-12 w-12 flex justify-center items-center rounded-md"> - {title.slice(0, 2).toUpperCase()} - </div> - </div> - <div className="grow px-4"> - <div className="text-lg text-[#fff] line-clamp-2">{title}</div> - <div>{description}</div> - </div> - <div> - <Image src={NextIcon} alt="Search icon" /> - </div> - </div> - </div> - ); -} - -function LinkComponent({ - type, - content, - title, - url, - image, - description, -}: { - type: string; - content: string; - title: string; - url: string; - image?: string; - description: string; -}) { - // TODO: DISPLAY THE ITEM BASED ON `type` being note or page - return ( - <Link - href={url.replace("https://beta.supermemory.ai", "")} - className={`bg-secondary border-2 border-border rounded-xl ${type === "tweet" ? "" : "p-4"} hover:scale-105 transition duration-200`} - > - {type === "page" ? ( - <> - <div className="flex items-center gap-2 text-xs"> - <PaperclipIcon className="w-3 h-3" /> Page - </div> - <div className="text-lg text-[#fff] mt-4 line-clamp-2">{title}</div> - <div>{url}</div> - </> - ) : type === "note" ? ( - <> - <div className="flex items-center gap-2 text-xs"> - <NotebookIcon className="w-3 h-3" /> Note - </div> - <div className="text-lg text-[#fff] mt-4 line-clamp-2">{title}</div> - <div className="line-clamp-3">{content.replace(title, "")}</div> - </> - ) : type === "tweet" ? ( - <MyTweet tweet={JSON.parse(getRawTweet(content) ?? "{}")} /> - ) : null} - </Link> - ); -} +import { redirect } from "next/navigation"; +import MemoriesPage from "./content"; -const FilterMethods = ["All", "Spaces", "Pages", "Notes", "Tweet"]; -function Filters({ - setFilter, - filter, -}: { - setFilter: (i: string) => void; - filter: string; -}) { - return ( - <div className="flex gap-4"> - {FilterMethods.map((i) => { - return ( - <button - onClick={() => setFilter(i)} - className={`transition px-6 py-2 rounded-xl bg-border ${i === filter ? " text-[#369DFD]" : "text-[#B3BCC5] bg-secondary hover:bg-secondary hover:text-[#76a3cc]"}`} - > - {i} - </button> - ); - })} - </div> - ); +async function Page() { + const { success, data } = await getAllUserMemoriesAndSpaces(); + if (!success ?? !data) return redirect("/home"); + return <MemoriesPage memoriesAndSpaces={data} />; } export default Page; diff --git a/apps/web/app/(dash)/menu.tsx b/apps/web/app/(dash)/menu.tsx index cc45016e..90e114de 100644 --- a/apps/web/app/(dash)/menu.tsx +++ b/apps/web/app/(dash)/menu.tsx @@ -38,6 +38,7 @@ import { createMemory, createSpace } from "../actions/doers"; import { Input } from "@repo/ui/shadcn/input"; import ComboboxWithCreate from "@repo/ui/shadcn/combobox"; import { StoredSpace } from "@/server/db/schema"; +import { revalidatePath } from "next/cache"; function Menu() { const [spaces, setSpaces] = useState<StoredSpace[]>([]); @@ -205,7 +206,7 @@ function Menu() { value={content} onChange={(e) => setContent(e.target.value)} onKeyDown={(e) => { - if (e.key === "Enter") { + if (e.key === "Enter" && !e.shiftKey) { e.preventDefault(); handleSubmit(content); } diff --git a/apps/web/app/actions/doers.ts b/apps/web/app/actions/doers.ts index 84d43924..8acc5679 100644 --- a/apps/web/app/actions/doers.ts +++ b/apps/web/app/actions/doers.ts @@ -245,6 +245,8 @@ export const createMemory = async (input: { noteId, }) .returning({ id: storedContent.id }); + revalidatePath("/memories"); + revalidatePath("/home"); contentId = insertResponse[0]?.id; } catch (e) { @@ -318,9 +320,6 @@ export const createMemory = async (input: { }; } - revalidatePath("/home"); - revalidatePath("/memories"); - return { success: true, data: 1, diff --git a/apps/web/app/layout.tsx b/apps/web/app/layout.tsx index 9af77837..1527c8a0 100644 --- a/apps/web/app/layout.tsx +++ b/apps/web/app/layout.tsx @@ -7,6 +7,7 @@ import { GeistMono } from "geist/font/mono"; import { cn } from "@repo/ui/lib/utils"; import BackgroundPlus from "./(landing)/GridPatterns/PlusGrid"; import { Toaster } from "@repo/ui/shadcn/toaster"; + const inter = Inter({ subsets: ["latin"] }); export const runtime = "edge"; diff --git a/apps/web/migrations/cmd.sql b/apps/web/migrations/cmd.sql new file mode 100644 index 00000000..0af8ba38 --- /dev/null +++ b/apps/web/migrations/cmd.sql @@ -0,0 +1 @@ +ALTER TABLE `space` ADD COLUMN `numItems` integer NOT NULL DEFAULT 0;
\ No newline at end of file diff --git a/package.json b/package.json index 8d1e73af..e4e5cc86 100644 --- a/package.json +++ b/package.json @@ -92,6 +92,7 @@ "react-markdown": "^9.0.1", "react-responsive-masonry": "^2.2.1", "react-tweet": "^3.2.1", + "react-web-share": "^2.0.2", "rehype-highlight": "^7.0.0", "rehype-katex": "^7.0.0", "remark-gfm": "^4.0.0", |