diff options
| author | Yash <[email protected]> | 2024-04-05 08:27:54 +0000 |
|---|---|---|
| committer | Yash <[email protected]> | 2024-04-05 08:27:54 +0000 |
| commit | 97988798bdc8b225ec6dbe7b0a725bfde6059157 (patch) | |
| tree | 3b673f17600332f36747f381b1ad83acb7baff9a /apps/web/src | |
| parent | context added (diff) | |
| parent | make ext work with dev mode (diff) | |
| download | supermemory-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.tsx | 6 | ||||
| -rw-r--r-- | apps/web/src/app/api/store/route.ts | 43 | ||||
| -rw-r--r-- | apps/web/src/app/page.tsx | 109 | ||||
| -rw-r--r-- | apps/web/src/app/ui/content.tsx | 2 | ||||
| -rw-r--r-- | apps/web/src/app/ui/page.tsx | 18 | ||||
| -rw-r--r-- | apps/web/src/components/Sidebar/MemoriesBar.tsx | 162 | ||||
| -rw-r--r-- | apps/web/src/components/Sidebar/index.tsx | 40 | ||||
| -rw-r--r-- | apps/web/src/lib/searchParams.ts | 12 | ||||
| -rw-r--r-- | apps/web/src/server/db/schema.ts | 49 |
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 |