aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--apps/web/app/(dash)/memories/content.tsx221
-rw-r--r--apps/web/app/(dash)/memories/page.tsx228
-rw-r--r--apps/web/app/(dash)/menu.tsx3
-rw-r--r--apps/web/app/actions/doers.ts5
-rw-r--r--apps/web/app/layout.tsx1
-rw-r--r--apps/web/migrations/cmd.sql1
-rw-r--r--package.json1
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",