aboutsummaryrefslogtreecommitdiff
path: root/apps/web/src
diff options
context:
space:
mode:
authorYash <[email protected]>2024-04-05 08:27:54 +0000
committerYash <[email protected]>2024-04-05 08:27:54 +0000
commit97988798bdc8b225ec6dbe7b0a725bfde6059157 (patch)
tree3b673f17600332f36747f381b1ad83acb7baff9a /apps/web/src
parentcontext added (diff)
parentmake ext work with dev mode (diff)
downloadsupermemory-97988798bdc8b225ec6dbe7b0a725bfde6059157.tar.xz
supermemory-97988798bdc8b225ec6dbe7b0a725bfde6059157.zip
Merge branch 'new-ui' of https://github.com/Dhravya/supermemory into new-ui
Diffstat (limited to 'apps/web/src')
-rw-r--r--apps/web/src/app/MessagePoster.tsx6
-rw-r--r--apps/web/src/app/api/store/route.ts43
-rw-r--r--apps/web/src/app/page.tsx109
-rw-r--r--apps/web/src/app/ui/content.tsx2
-rw-r--r--apps/web/src/app/ui/page.tsx18
-rw-r--r--apps/web/src/components/Sidebar/MemoriesBar.tsx162
-rw-r--r--apps/web/src/components/Sidebar/index.tsx40
-rw-r--r--apps/web/src/lib/searchParams.ts12
-rw-r--r--apps/web/src/server/db/schema.ts49
9 files changed, 134 insertions, 307 deletions
diff --git a/apps/web/src/app/MessagePoster.tsx b/apps/web/src/app/MessagePoster.tsx
index 76bbc4dd..3d0bbe7e 100644
--- a/apps/web/src/app/MessagePoster.tsx
+++ b/apps/web/src/app/MessagePoster.tsx
@@ -8,11 +8,7 @@ function MessagePoster({ jwt }: { jwt: string }) {
window.postMessage({ jwt }, '*');
}, [jwt]);
- return (
- <button onClick={() => window.postMessage({ jwt }, '*')}>
- Send auth token to extension
- </button>
- );
+ return null;
}
export default MessagePoster;
diff --git a/apps/web/src/app/api/store/route.ts b/apps/web/src/app/api/store/route.ts
index 46e4cdfb..3a4f7e27 100644
--- a/apps/web/src/app/api/store/route.ts
+++ b/apps/web/src/app/api/store/route.ts
@@ -1,6 +1,6 @@
import { db } from "@/server/db";
import { eq } from "drizzle-orm";
-import { sessions, storedContent, userStoredContent, users } from "@/server/db/schema";
+import { sessions, storedContent, users } from "@/server/db/schema";
import { type NextRequest, NextResponse } from "next/server";
import { env } from "@/env";
import { getMetaData } from "@/server/helpers";
@@ -38,32 +38,19 @@ export async function POST(req: NextRequest) {
let id: number | undefined = undefined;
- const storedCont = await db.select().from(storedContent).where(eq(storedContent.url, data.url)).limit(1)
-
- if (storedCont.length > 0) {
- id = storedCont[0].id;
- } else {
- const storedContentId = await db.insert(storedContent).values({
- content: data.pageContent,
- title: metadata.title,
- description: metadata.description,
- url: data.url,
- baseUrl: metadata.baseUrl,
- image: metadata.image,
- savedAt: new Date()
- })
-
- id = storedContentId.meta.last_row_id;
- }
+ const storedContentId = await db.insert(storedContent).values({
+ content: data.pageContent,
+ title: metadata.title,
+ description: metadata.description,
+ url: data.url,
+ baseUrl: metadata.baseUrl,
+ image: metadata.image,
+ savedAt: new Date(),
+ space: "all",
+ user: session.user.id
+ })
- try {
- await db.insert(userStoredContent).values({
- userId: session.user.id,
- contentId: id
- });
- } catch (e) {
- console.log(e);
- }
+ id = storedContentId.meta.last_row_id;
const res = await Promise.race([
fetch("https://cf-ai-backend.dhravya.workers.dev/add", {
@@ -78,10 +65,6 @@ export async function POST(req: NextRequest) {
)
]) as Response
- const _ = await res.text();
-
- console.log(_)
-
if (res.status !== 200) {
return NextResponse.json({ message: "Error", error: "Error in CF function" }, { status: 500 });
}
diff --git a/apps/web/src/app/page.tsx b/apps/web/src/app/page.tsx
index 2522a25f..d1d47ae5 100644
--- a/apps/web/src/app/page.tsx
+++ b/apps/web/src/app/page.tsx
@@ -1,17 +1,12 @@
-import { CardContent, Card } from '@/components/ui/card';
import { db } from '@/server/db';
-import {
- sessions,
- storedContent,
- userStoredContent,
- users,
-} from '@/server/db/schema';
+import { sessions, storedContent, users } from '@/server/db/schema';
import { eq, inArray } from 'drizzle-orm';
import { cookies, headers } from 'next/headers';
import { redirect } from 'next/navigation';
-import Image from 'next/image';
-import QueryAI from '@/components/QueryAI';
+import Sidebar from '@/components/Sidebar/index';
+import Main from '@/components/Main';
import MessagePoster from './MessagePoster';
+import { transformContent } from '../../types/memory';
export const runtime = 'edge';
@@ -26,6 +21,13 @@ export default async function Home() {
return redirect('/api/auth/signin');
}
+ const selectedItem = cookies().get('selectedItem')?.value;
+
+ const setSelectedItem = async (selectedItem: string | null) => {
+ 'use server';
+ cookies().set('selectedItem', selectedItem!);
+ };
+
const session = await db
.select()
.from(sessions)
@@ -35,101 +37,28 @@ export default async function Home() {
return redirect('/api/auth/signin');
}
- const userContent = await db
- .select()
- .from(userStoredContent)
- .where(eq(userStoredContent.userId, session[0].userId));
-
- const userData = await db
+ const [userData] = await db
.select()
.from(users)
.where(eq(users.id, session[0].userId))
.limit(1);
- if (!userData || userData.length === 0) {
+ if (!userData) {
return redirect('/api/auth/signin');
}
- const listOfContent =
- userContent.map((content) => content.contentId).length > 0
- ? userContent.map((content) => content.contentId)
- : [1];
-
const posts = await db
.select()
.from(storedContent)
- .where(inArray(storedContent.id, listOfContent));
+ .where(eq(storedContent.user, userData.id));
- return (
- <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 flex flex-col items-center">
- <div className="flex flex-col mt-16">
- <div className="flex flex-col md:flex-row gap-4">
- <Image
- className="rounded-2xl"
- src="/logo.png"
- width={120}
- height={120}
- alt="logo"
- />
- <div className="mt-4 text-gray-400 max-w-md">
- <h1 className="text-xl font-bold text-white">SuperMemory</h1>
- Remember that one thing you read a while ago? We got you covered.
- Add the extension, click a button and I'll remember it for you.{' '}
- <a
- href="https://github.com/dhravyashah/anycontext"
- target="_blank"
- rel="noreferrer"
- className="text-sky-500"
- >
- Get the Extension
- </a>
- </div>
- </div>
- </div>
-
- <QueryAI />
+ const collectedSpaces = transformContent(posts);
+ return (
+ <div className="flex w-screen">
+ <Sidebar selectChange={setSelectedItem} spaces={collectedSpaces} />
+ <Main sidebarOpen={selectedItem !== null} />
<MessagePoster jwt={token} />
-
- {/* TODO: LABEL THE WEBSITES USING A CLASSIFICATION MODEL */}
- {/* <nav className="flex space-x-2 my-4">
- <Badge variant="secondary">Technology (2)</Badge>
- <Badge variant="secondary">Business & Finance (1)</Badge>
- <Badge variant="secondary">Education & Career (1)</Badge>
- </nav> */}
- <main className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 mt-16">
- {posts.reverse().map((post) => (
- <a
- href={post.url}
- target="_blank"
- rel="noreferrer"
- className="hover:scale-105 ease-in-out transition-transform duration-300"
- >
- <Card className="w-full">
- <img
- alt="Not found"
- className="w-full h-48 object-cover rounded-md"
- height="200"
- src={
- post.image && post.image !== 'Image not found'
- ? post.image
- : '/placeholder.svg'
- }
- style={{
- aspectRatio: '300/200',
- objectFit: 'cover',
- }}
- width="300"
- />
- <CardContent>
- <h3 className="text-lg font-semibold mt-4">{post.title}</h3>
- <p className="text-sm text-gray-600">{post.baseUrl}</p>
- <p className="text-sm">{post.description}</p>
- </CardContent>
- </Card>
- </a>
- ))}
- </main>
</div>
);
}
diff --git a/apps/web/src/app/ui/content.tsx b/apps/web/src/app/ui/content.tsx
index 6e28eaf8..16f66394 100644
--- a/apps/web/src/app/ui/content.tsx
+++ b/apps/web/src/app/ui/content.tsx
@@ -8,7 +8,7 @@ export default function Content() {
return (
<div className="flex w-screen">
- <Sidebar onSelectChange={setSelectedItem} />
+ {/* <Sidebar selectChange={setSelectedItem} /> */}
<Main sidebarOpen={selectedItem !== null} />
</div>
);
diff --git a/apps/web/src/app/ui/page.tsx b/apps/web/src/app/ui/page.tsx
index 35175334..ec1998b6 100644
--- a/apps/web/src/app/ui/page.tsx
+++ b/apps/web/src/app/ui/page.tsx
@@ -1,10 +1,18 @@
-import { MemoryProvider } from "@/contexts/MemoryContext";
-import Content from "./content";
+import Main from "@/components/Main";
+import Sidebar from "@/components/Sidebar/index";
+import { cookies } from "next/headers";
export default function Home() {
+ const selectedItem = cookies().get("selectedItem")?.value;
+ const setSelectedItem = async (selectedItem: string | null) => {
+ "use server";
+ cookies().set("selectedItem", selectedItem!);
+ };
+
return (
- <MemoryProvider spaces={[]}>
- <Content />
- </MemoryProvider>
+ <div className="flex w-screen">
+ {/* <Sidebar selectChange={setSelectedItem} spaces={spaces} /> */}
+ <Main sidebarOpen={selectedItem !== null} />
+ </div>
);
}
diff --git a/apps/web/src/components/Sidebar/MemoriesBar.tsx b/apps/web/src/components/Sidebar/MemoriesBar.tsx
index 367f0173..9fcd3ff8 100644
--- a/apps/web/src/components/Sidebar/MemoriesBar.tsx
+++ b/apps/web/src/components/Sidebar/MemoriesBar.tsx
@@ -1,24 +1,24 @@
-import { useAutoAnimate } from "@formkit/auto-animate/react";
+import { useAutoAnimate } from '@formkit/auto-animate/react';
import {
MemoryWithImage,
MemoryWithImages3,
MemoryWithImages2,
-} from "@/assets/MemoryWithImages";
-import { type Space } from "../../../types/memory";
-import { InputWithIcon } from "../ui/input";
+} from '@/assets/MemoryWithImages';
+import { type CollectedSpaces } from '../../../types/memory';
+import { InputWithIcon } from '../ui/input';
import {
ArrowUpRight,
Edit3,
MoreHorizontal,
Search,
Trash2,
-} from "lucide-react";
+} from 'lucide-react';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
-} from "../ui/dropdown-menu";
+} from '../ui/dropdown-menu';
import {
animate,
AnimatePresence,
@@ -26,105 +26,15 @@ import {
motion,
useAnimate,
Variant,
-} from "framer-motion";
-import { useRef, useState } from "react";
+} from 'framer-motion';
+import { useRef, useState } from 'react';
-const spaces: Space[] = [
- {
- id: 1,
- title: "Cool Tech",
- description: "Really cool mind blowing tech",
- content: [
- {
- id: 1,
- title: "Perplexity",
- description: "A good ui",
- content: "",
- image: "https://perplexity.ai/favicon.ico",
- url: "https://perplexity.ai",
- savedAt: new Date(),
- baseUrl: "https://perplexity.ai",
- space: "Cool tech",
- },
- {
- id: 2,
- title: "Pi.ai",
- description: "A good ui",
- content: "",
- image: "https://pi.ai/pi-logo-192.png?v=2",
- url: "https://pi.ai",
- savedAt: new Date(),
- baseUrl: "https://pi.ai",
- space: "Cool tech",
- },
- {
- id: 3,
- title: "Visual Studio Code",
- description: "A good ui",
- content: "",
- image: "https://code.visualstudio.com/favicon.ico",
- url: "https://code.visualstudio.com",
- savedAt: new Date(),
- baseUrl: "https://code.visualstudio.com",
- space: "Cool tech",
- },
- ],
- },
- {
- id: 2,
- title: "Cool Courses",
- description: "Amazng",
- content: [
- {
- id: 1,
- title: "Animation on the web",
- description: "A good ui",
- content: "",
- image: "https://animations.dev/favicon.ico",
- url: "https://animations.dev",
- savedAt: new Date(),
- baseUrl: "https://animations.dev",
- space: "Cool courses",
- },
- {
- id: 2,
- title: "Tailwind Course",
- description: "A good ui",
- content: "",
- image:
- "https://tailwindcss.com/_next/static/media/tailwindcss-mark.3c5441fc7a190fb1800d4a5c7f07ba4b1345a9c8.svg",
- url: "https://tailwindcss.com",
- savedAt: new Date(),
- baseUrl: "https://tailwindcss.com",
- space: "Cool courses",
- },
- ],
- },
- {
- id: 3,
- title: "Cool Libraries",
- description: "Really cool mind blowing tech",
- content: [
- {
- id: 1,
- title: "Perplexity",
- description: "A good ui",
- content: "",
- image: "https://yashverma.me/logo.jpg",
- url: "https://perplexity.ai",
- savedAt: new Date(),
- baseUrl: "https://perplexity.ai",
- space: "Cool libraries",
- },
- ],
- },
-];
-export function MemoriesBar() {
+export function MemoriesBar({ spaces }: { spaces: CollectedSpaces[] }) {
const [parent, enableAnimations] = useAutoAnimate();
const [currentSpaces, setCurrentSpaces] = useState(spaces);
- console.log("currentSpaces: ", currentSpaces);
+ console.log('currentSpaces: ', currentSpaces);
return (
<div className="text-rgray-11 flex w-full flex-col items-start py-8 text-left">
@@ -157,8 +67,8 @@ export function MemoriesBar() {
const SpaceExitVariant: Variant = {
opacity: 0,
scale: 0,
- borderRadius: "50%",
- background: "var(--gray-1)",
+ borderRadius: '50%',
+ background: 'var(--gray-1)',
transition: {
duration: 0.2,
},
@@ -170,7 +80,7 @@ export function SpaceItem({
content,
id,
onDelete,
-}: Space & { onDelete: () => void }) {
+}: CollectedSpaces & { onDelete: () => void }) {
const [itemRef, animateItem] = useAnimate();
return (
@@ -184,22 +94,22 @@ export function SpaceItem({
<SpaceMoreButton
onDelete={() => {
if (!itemRef.current) return;
- const trash = document.querySelector("#trash")! as HTMLDivElement;
- const trashBin = document.querySelector("#trash-button")!;
+ const trash = document.querySelector('#trash')! as HTMLDivElement;
+ const trashBin = document.querySelector('#trash-button')!;
const trashRect = trashBin.getBoundingClientRect();
const scopeRect = itemRef.current.getBoundingClientRect();
- const el = document.createElement("div");
- el.style.position = "fixed";
- el.style.top = "0";
- el.style.left = "0";
- el.style.width = "15px";
- el.style.height = "15px";
- el.style.backgroundColor = "var(--gray-7)";
- el.style.zIndex = "60";
- el.style.borderRadius = "50%";
- el.style.transform = "scale(5)";
- el.style.opacity = "0";
- trash.dataset["open"] = "true";
+ const el = document.createElement('div');
+ el.style.position = 'fixed';
+ el.style.top = '0';
+ el.style.left = '0';
+ el.style.width = '15px';
+ el.style.height = '15px';
+ el.style.backgroundColor = 'var(--gray-7)';
+ el.style.zIndex = '60';
+ el.style.borderRadius = '50%';
+ el.style.transform = 'scale(5)';
+ el.style.opacity = '0';
+ trash.dataset['open'] = 'true';
const initial = {
x: scopeRect.left + scopeRect.width / 2,
y: scopeRect.top + scopeRect.height / 2,
@@ -224,35 +134,35 @@ export function SpaceItem({
animateItem(itemRef.current, SpaceExitVariant, {
duration: 0.2,
}).then(() => {
- itemRef.current.style.scale = "0";
+ itemRef.current.style.scale = '0';
onDelete();
});
document.body.appendChild(el);
el.animate(
{
- transform: ["scale(5)", "scale(1)"],
+ transform: ['scale(5)', 'scale(1)'],
opacity: [0, 0.3, 1],
},
{
duration: 200,
- easing: "cubic-bezier(0.64, 0.57, 0.67, 1.53)",
- fill: "forwards",
+ easing: 'cubic-bezier(0.64, 0.57, 0.67, 1.53)',
+ fill: 'forwards',
},
);
el.animate(
{
- offsetDistance: ["0%", "100%"],
+ offsetDistance: ['0%', '100%'],
},
{
duration: 2000,
- easing: "cubic-bezier(0.64, 0.57, 0.67, 1.53)",
- fill: "forwards",
+ easing: 'cubic-bezier(0.64, 0.57, 0.67, 1.53)',
+ fill: 'forwards',
delay: 200,
},
).onfinish = () => {
el.animate(
- { transform: "scale(0)", opacity: 0 },
- { duration: 200, fill: "forwards" },
+ { transform: 'scale(0)', opacity: 0 },
+ { duration: 200, fill: 'forwards' },
).onfinish = () => {
el.remove();
};
diff --git a/apps/web/src/components/Sidebar/index.tsx b/apps/web/src/components/Sidebar/index.tsx
index 9c2cfd63..c4328b5d 100644
--- a/apps/web/src/components/Sidebar/index.tsx
+++ b/apps/web/src/components/Sidebar/index.tsx
@@ -2,25 +2,18 @@
import { StoredContent } from "@/server/db/schema";
import { MemoryIcon } from "../../assets/Memories";
import { Trash2, User2 } from "lucide-react";
-import React, { useEffect, useState } from "react";
+import React, { ElementType, useEffect, useState } from "react";
import { MemoriesBar } from "./MemoriesBar";
import { AnimatePresence, motion } from "framer-motion";
import { Bin } from "@/assets/Bin";
+import { CollectedSpaces } from "../../../types/memory";
export type MenuItem = {
icon: React.ReactNode | React.ReactNode[];
label: string;
- content?: React.FC;
+ content?: React.ReactElement;
};
-const menuItemsTop: Array<MenuItem> = [
- {
- icon: <MemoryIcon className="h-10 w-10" />,
- label: "Memories",
- content: MemoriesBar,
- },
-];
-
const menuItemsBottom: Array<MenuItem> = [
{
icon: <Trash2 strokeWidth={1.3} className="h-6 w-6" />,
@@ -33,10 +26,19 @@ const menuItemsBottom: Array<MenuItem> = [
];
export default function Sidebar({
- onSelectChange,
+ selectChange,
+ spaces,
}: {
- onSelectChange?: (selectedItem: string | null) => void;
+ selectChange?: (selectedItem: string | null) => Promise<void>;
+ spaces: CollectedSpaces[];
}) {
+ const menuItemsTop: Array<MenuItem> = [
+ {
+ icon: <MemoryIcon className="h-10 w-10" />,
+ label: "Memories",
+ content: <MemoriesBar spaces={spaces} />,
+ },
+ ];
const menuItems = [...menuItemsTop, ...menuItemsBottom];
const [selectedItem, setSelectedItem] = useState<string | null>(null);
@@ -44,7 +46,7 @@ export default function Sidebar({
menuItems.find((i) => i.label === selectedItem)?.content ?? (() => <></>);
useEffect(() => {
- void onSelectChange?.(selectedItem);
+ void selectChange?.(selectedItem);
}, [selectedItem]);
return (
@@ -55,7 +57,7 @@ export default function Sidebar({
item={{
label: "Memories",
icon: <MemoryIcon className="h-10 w-10" />,
- content: MemoriesBar,
+ content: <MemoriesBar spaces={spaces} />,
}}
selectedItem={selectedItem}
setSelectedItem={setSelectedItem}
@@ -82,11 +84,11 @@ export default function Sidebar({
/>
</div>
<AnimatePresence>
- {selectedItem && (
- <SubSidebar>
- <Subbar />
- </SubSidebar>
- )}
+ {/* @yxshv idk why this is giving typeerror
+ when used as <Subbar/> it says it's not valid element type
+ */}
+ {/* @ts-ignore */}
+ {selectedItem && <SubSidebar>{Subbar}</SubSidebar>}
</AnimatePresence>
</div>
</>
diff --git a/apps/web/src/lib/searchParams.ts b/apps/web/src/lib/searchParams.ts
new file mode 100644
index 00000000..b435295d
--- /dev/null
+++ b/apps/web/src/lib/searchParams.ts
@@ -0,0 +1,12 @@
+import {
+ createSearchParamsCache,
+ parseAsInteger,
+ parseAsString
+ } from 'nuqs/server'
+ // Note: import from 'nuqs/server' to avoid the "use client" directive
+
+ export const searchParamsCache = createSearchParamsCache({
+ // List your search param keys and associated parsers here:
+ q: parseAsString.withDefault(''),
+ maxResults: parseAsInteger.withDefault(10)
+ }) \ No newline at end of file
diff --git a/apps/web/src/server/db/schema.ts b/apps/web/src/server/db/schema.ts
index 46f00f71..d66965c4 100644
--- a/apps/web/src/server/db/schema.ts
+++ b/apps/web/src/server/db/schema.ts
@@ -6,15 +6,9 @@ import {
sqliteTableCreator,
text,
integer,
- unique,
+ unique
} from "drizzle-orm/sqlite-core";
-/**
- * This is an example of how to use the multi-project schema feature of Drizzle ORM. Use the same
- * database instance for multiple projects.
- *
- * @see https://orm.drizzle.team/docs/goodies#multi-project-schema
- */
export const createTable = sqliteTableCreator((name) => `${name}`);
export const users = createTable("user", {
@@ -84,27 +78,6 @@ export const verificationTokens = createTable(
}),
);
-export const userStoredContent = createTable(
- "userStoredContent",
- {
- userId: text("userId")
- .notNull()
- .references(() => users.id),
- contentId: integer("contentId")
- .notNull()
- .references(() => storedContent.id),
- },
- (usc) => ({
- userContentIdx: index("userStoredContent_idx").on(
- usc.userId,
- usc.contentId,
- ),
- uniqueUserContent: unique("unique_user_content").on(
- usc.userId,
- usc.contentId,
- ),
- }),
-);
export const storedContent = createTable(
"storedContent",
@@ -113,18 +86,32 @@ export const storedContent = createTable(
content: text("content").notNull(),
title: text("title", { length: 255 }),
description: text("description", { length: 255 }),
- url: text("url").notNull().unique(),
- space: text("space", { length: 255 }),
+ url: text("url").notNull(),
+ space: text("space", { length: 255 }).references(() => spaces.name).default('all'),
savedAt: int("savedAt", { mode: "timestamp" }).notNull(),
baseUrl: text("baseUrl", { length: 255 }),
image: text("image", { length: 255 }),
+ user: text("user", { length: 255 }).references(() => users.id),
},
(sc) => ({
urlIdx: index("storedContent_url_idx").on(sc.url),
savedAtIdx: index("storedContent_savedAt_idx").on(sc.savedAt),
titleInx: index("storedContent_title_idx").on(sc.title),
spaceIdx: index("storedContent_space_idx").on(sc.space),
+ userIdx: index("storedContent_user_idx").on(sc.user),
+ }),
+);
+
+export const spaces = createTable(
+ "spaces",
+ {
+ id: integer("id").notNull().primaryKey({ autoIncrement: true }),
+ name: text('name').notNull().default('all'),
+ description: text("description", { length: 255 }),
+ },
+ (space) => ({
+ nameIdx: index("spaces_name_idx").on(space.name),
}),
);
-export type StoredContent = typeof storedContent.$inferSelect;
+export type StoredContent = Omit<typeof storedContent.$inferSelect, 'user'> \ No newline at end of file