diff options
| author | Yash <[email protected]> | 2024-03-31 10:50:41 +0000 |
|---|---|---|
| committer | Yash <[email protected]> | 2024-03-31 10:50:41 +0000 |
| commit | cf5972ec8f2560262b16203cba4fe1cb4b197c94 (patch) | |
| tree | 6ab33d384623f70e144015aec4c78e7b333602f4 /apps/web/src | |
| parent | redeploy: check for d1 database (diff) | |
| download | supermemory-cf5972ec8f2560262b16203cba4fe1cb4b197c94.tar.xz supermemory-cf5972ec8f2560262b16203cba4fe1cb4b197c94.zip | |
List Item with Sidebar
Diffstat (limited to 'apps/web/src')
| -rw-r--r-- | apps/web/src/app/globals.css | 8 | ||||
| -rw-r--r-- | apps/web/src/app/layout.tsx | 6 | ||||
| -rw-r--r-- | apps/web/src/app/ui/page.tsx | 9 | ||||
| -rw-r--r-- | apps/web/src/components/Sidebar.tsx | 145 | ||||
| -rw-r--r-- | apps/web/src/components/ui/dropdown-menu.tsx | 88 | ||||
| -rw-r--r-- | apps/web/src/server/db/schema.ts | 46 |
6 files changed, 239 insertions, 63 deletions
diff --git a/apps/web/src/app/globals.css b/apps/web/src/app/globals.css index 875c01e8..394bda43 100644 --- a/apps/web/src/app/globals.css +++ b/apps/web/src/app/globals.css @@ -1,3 +1,6 @@ +@import "@radix-ui/colors/gray"; +@import "@radix-ui/colors/gray-dark"; + @tailwind base; @tailwind components; @tailwind utilities; @@ -17,13 +20,14 @@ } body { - color: rgb(var(--foreground-rgb)); + @apply bg-rgray-1 text-rgray-11; + /* color: rgb(var(--foreground-rgb)); background: linear-gradient( to bottom, transparent, rgb(var(--background-end-rgb)) ) - rgb(var(--background-start-rgb)); + rgb(var(--background-start-rgb)); */ } @layer utilities { diff --git a/apps/web/src/app/layout.tsx b/apps/web/src/app/layout.tsx index 3314e478..5464c1d3 100644 --- a/apps/web/src/app/layout.tsx +++ b/apps/web/src/app/layout.tsx @@ -1,8 +1,8 @@ import type { Metadata } from "next"; -import { Inter } from "next/font/google"; +import { Roboto } from "next/font/google"; import "./globals.css"; -const inter = Inter({ subsets: ["latin"] }); +const roboto = Roboto({ weight: ["300", "400", "500"], subsets: ["latin"] }); export const metadata: Metadata = { title: "Create Next App", @@ -16,7 +16,7 @@ export default function RootLayout({ }>) { return ( <html lang="en"> - <body className={inter.className}>{children}</body> + <body className={roboto.className}>{children}</body> </html> ); } diff --git a/apps/web/src/app/ui/page.tsx b/apps/web/src/app/ui/page.tsx new file mode 100644 index 00000000..2f7c6a4b --- /dev/null +++ b/apps/web/src/app/ui/page.tsx @@ -0,0 +1,9 @@ +import Sidebar from "@/components/Sidebar"; + +export default async function Home() { + return ( + <> + <Sidebar /> + </> + ); +} diff --git a/apps/web/src/components/Sidebar.tsx b/apps/web/src/components/Sidebar.tsx new file mode 100644 index 00000000..1b32b1ee --- /dev/null +++ b/apps/web/src/components/Sidebar.tsx @@ -0,0 +1,145 @@ +"use client"; +import { StoredContent } from "@/server/db/schema"; +import { + Plus, + MoreHorizontal, + ArrowUpRight, + Edit3, + Trash2, +} from "lucide-react"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from "./ui/dropdown-menu"; + +import { useState, useEffect, useRef } from "react"; + +export default function Sidebar() { + const websties: StoredContent[] = [ + { + id: 1, + content: "", + title: "Visual Studio Code", + url: "https://code.visualstudio.com", + description: "", + image: "https://code.visualstudio.com/favicon.ico", + baseUrl: "https://code.visualstudio.com", + savedAt: new Date(), + }, + { + id: 1, + content: "", + title: "yxshv/vscode: An unofficial remake of vscode's landing page", + url: "https://github.com/yxshv/vscode", + description: "", + image: "https://github.com/favicon.ico", + baseUrl: "https://github.com", + savedAt: new Date(), + }, + ]; + + return ( + <aside className="bg-rgray-3 flex h-screen w-[25%] flex-col items-start justify-between py-5 pb-[50vh] font-light"> + <div className="flex items-center justify-center gap-1 px-5 text-xl font-normal"> + <img src="/brain.png" alt="logo" className="h-10 w-10" /> + SuperMemory + </div> + <div className="flex w-full flex-col items-start justify-center p-2"> + <h1 className="mb-1 flex w-full items-center justify-center px-3 font-normal"> + Websites + <button className="ml-auto "> + <Plus className="h-4 w-4 min-w-4" /> + </button> + </h1> + {websties.map((item) => ( + <ListItem key={item.id} item={item} /> + ))} + </div> + </aside> + ); +} + +export const ListItem: React.FC<{ item: StoredContent }> = ({ item }) => { + const [isEditing, setIsEditing] = useState(false); + const editInputRef = useRef<HTMLInputElement>(null); + + useEffect(() => { + if (isEditing) { + setTimeout(() => { + editInputRef.current?.focus(); + }, 500); + } + }, [isEditing]); + + return ( + <div className="hover:bg-rgray-5 focus-within:bg-rgray-5 flex w-full items-center rounded-full py-1 pl-3 pr-2 transition [&:hover>a>[data-upright-icon]]:block [&:hover>a>img]:hidden [&:hover>button]:opacity-100"> + <a + href={item.url} + target="_blank" + onClick={(e) => isEditing && e.preventDefault()} + className="flex w-[90%] items-center gap-2 focus:outline-none" + > + {isEditing ? ( + <Edit3 className="h-4 w-4" strokeWidth={1.5} /> + ) : ( + <> + <img + src={item.image ?? "/brain.png"} + alt={item.title ?? "Untitiled website"} + className="h-4 w-4" + /> + <ArrowUpRight + data-upright-icon + className="hidden h-4 w-4 min-w-4 scale-125" + strokeWidth={1.5} + /> + </> + )} + {isEditing ? ( + <input + ref={editInputRef} + autoFocus + className="text-rgray-12 w-full bg-transparent focus:outline-none" + placeholder={item.title ?? "Untitled website"} + onBlur={(e) => setIsEditing(false)} + onKeyDown={(e) => e.key === "Escape" && setIsEditing(false)} + /> + ) : ( + <span className="w-full truncate text-nowrap"> + {item.title ?? "Untitled website"} + </span> + )} + </a> + <DropdownMenu> + <DropdownMenuTrigger asChild> + <button className="ml-auto w-4 min-w-4 rounded-[0.15rem] opacity-0 focus:opacity-100 focus:outline-none"> + <MoreHorizontal className="h-4 w-4 min-w-4" /> + </button> + </DropdownMenuTrigger> + <DropdownMenuContent className="w-5"> + <DropdownMenuItem onClick={() => window.open(item.url)}> + <ArrowUpRight + className="mr-2 h-4 w-4 scale-125" + strokeWidth={1.5} + /> + Open + </DropdownMenuItem> + <DropdownMenuItem + onClick={(e) => { + setIsEditing(true); + }} + > + <Edit3 className="mr-2 h-4 w-4 " strokeWidth={1.5} /> + Edit + </DropdownMenuItem> + <DropdownMenuItem className="focus:bg-red-100 focus:text-red-400"> + <Trash2 className="mr-2 h-4 w-4 " strokeWidth={1.5} /> + Delete + </DropdownMenuItem> + </DropdownMenuContent> + </DropdownMenu> + </div> + ); +}; diff --git a/apps/web/src/components/ui/dropdown-menu.tsx b/apps/web/src/components/ui/dropdown-menu.tsx index 4243e7f2..375662bb 100644 --- a/apps/web/src/components/ui/dropdown-menu.tsx +++ b/apps/web/src/components/ui/dropdown-menu.tsx @@ -1,27 +1,27 @@ -"use client" +"use client"; -import * as React from "react" -import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu" -import { Check, ChevronRight, Circle } from "lucide-react" +import * as React from "react"; +import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"; +import { Check, ChevronRight, Circle } from "lucide-react"; -import { cn } from "@/lib/utils" +import { cn } from "@/lib/utils"; -const DropdownMenu = DropdownMenuPrimitive.Root +const DropdownMenu = DropdownMenuPrimitive.Root; -const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger +const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger; -const DropdownMenuGroup = DropdownMenuPrimitive.Group +const DropdownMenuGroup = DropdownMenuPrimitive.Group; -const DropdownMenuPortal = DropdownMenuPrimitive.Portal +const DropdownMenuPortal = DropdownMenuPrimitive.Portal; -const DropdownMenuSub = DropdownMenuPrimitive.Sub +const DropdownMenuSub = DropdownMenuPrimitive.Sub; -const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup +const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup; const DropdownMenuSubTrigger = React.forwardRef< React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>, React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & { - inset?: boolean + inset?: boolean; } >(({ className, inset, children, ...props }, ref) => ( <DropdownMenuPrimitive.SubTrigger @@ -29,16 +29,16 @@ const DropdownMenuSubTrigger = React.forwardRef< className={cn( "flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-gray-100 data-[state=open]:bg-gray-100 dark:focus:bg-gray-800 dark:data-[state=open]:bg-gray-800", inset && "pl-8", - className + className, )} {...props} > {children} <ChevronRight className="ml-auto h-4 w-4" /> </DropdownMenuPrimitive.SubTrigger> -)) +)); DropdownMenuSubTrigger.displayName = - DropdownMenuPrimitive.SubTrigger.displayName + DropdownMenuPrimitive.SubTrigger.displayName; const DropdownMenuSubContent = React.forwardRef< React.ElementRef<typeof DropdownMenuPrimitive.SubContent>, @@ -47,14 +47,14 @@ const DropdownMenuSubContent = React.forwardRef< <DropdownMenuPrimitive.SubContent ref={ref} className={cn( - "z-50 min-w-[8rem] overflow-hidden rounded-md border border-gray-200 bg-white p-1 text-gray-950 shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 dark:border-gray-800 dark:bg-gray-950 dark:text-gray-50", - className + "data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 min-w-[8rem] overflow-hidden rounded-md border border-gray-200 bg-white p-1 text-gray-950 shadow-lg dark:border-gray-800 dark:bg-gray-950 dark:text-gray-50", + className, )} {...props} /> -)) +)); DropdownMenuSubContent.displayName = - DropdownMenuPrimitive.SubContent.displayName + DropdownMenuPrimitive.SubContent.displayName; const DropdownMenuContent = React.forwardRef< React.ElementRef<typeof DropdownMenuPrimitive.Content>, @@ -65,32 +65,32 @@ const DropdownMenuContent = React.forwardRef< ref={ref} sideOffset={sideOffset} className={cn( - "z-50 min-w-[8rem] overflow-hidden rounded-md border border-gray-200 bg-white p-1 text-gray-950 shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 dark:border-gray-800 dark:bg-gray-950 dark:text-gray-50", - className + "data-[state=open]:animate-in bg-rgray-3 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 border-rgray-6 text-rgray-11 z-50 min-w-[8rem] overflow-hidden rounded-md border p-1 shadow-md", + className, )} {...props} /> </DropdownMenuPrimitive.Portal> -)) -DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName +)); +DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName; const DropdownMenuItem = React.forwardRef< React.ElementRef<typeof DropdownMenuPrimitive.Item>, React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & { - inset?: boolean + inset?: boolean; } >(({ className, inset, ...props }, ref) => ( <DropdownMenuPrimitive.Item ref={ref} className={cn( - "relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-gray-100 focus:text-gray-900 data-[disabled]:pointer-events-none data-[disabled]:opacity-50 dark:focus:bg-gray-800 dark:focus:text-gray-50", + "focus:bg-rgray-4 focus:text-rgray-12 relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors data-[disabled]:pointer-events-none data-[disabled]:opacity-50 ", inset && "pl-8", - className + className, )} {...props} /> -)) -DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName +)); +DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName; const DropdownMenuCheckboxItem = React.forwardRef< React.ElementRef<typeof DropdownMenuPrimitive.CheckboxItem>, @@ -100,7 +100,7 @@ const DropdownMenuCheckboxItem = React.forwardRef< ref={ref} className={cn( "relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-gray-100 focus:text-gray-900 data-[disabled]:pointer-events-none data-[disabled]:opacity-50 dark:focus:bg-gray-800 dark:focus:text-gray-50", - className + className, )} checked={checked} {...props} @@ -112,9 +112,9 @@ const DropdownMenuCheckboxItem = React.forwardRef< </span> {children} </DropdownMenuPrimitive.CheckboxItem> -)) +)); DropdownMenuCheckboxItem.displayName = - DropdownMenuPrimitive.CheckboxItem.displayName + DropdownMenuPrimitive.CheckboxItem.displayName; const DropdownMenuRadioItem = React.forwardRef< React.ElementRef<typeof DropdownMenuPrimitive.RadioItem>, @@ -124,7 +124,7 @@ const DropdownMenuRadioItem = React.forwardRef< ref={ref} className={cn( "relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-gray-100 focus:text-gray-900 data-[disabled]:pointer-events-none data-[disabled]:opacity-50 dark:focus:bg-gray-800 dark:focus:text-gray-50", - className + className, )} {...props} > @@ -135,13 +135,13 @@ const DropdownMenuRadioItem = React.forwardRef< </span> {children} </DropdownMenuPrimitive.RadioItem> -)) -DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName +)); +DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName; const DropdownMenuLabel = React.forwardRef< React.ElementRef<typeof DropdownMenuPrimitive.Label>, React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & { - inset?: boolean + inset?: boolean; } >(({ className, inset, ...props }, ref) => ( <DropdownMenuPrimitive.Label @@ -149,12 +149,12 @@ const DropdownMenuLabel = React.forwardRef< className={cn( "px-2 py-1.5 text-sm font-semibold", inset && "pl-8", - className + className, )} {...props} /> -)) -DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName +)); +DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName; const DropdownMenuSeparator = React.forwardRef< React.ElementRef<typeof DropdownMenuPrimitive.Separator>, @@ -165,8 +165,8 @@ const DropdownMenuSeparator = React.forwardRef< className={cn("-mx-1 my-1 h-px bg-gray-100 dark:bg-gray-800", className)} {...props} /> -)) -DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName +)); +DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName; const DropdownMenuShortcut = ({ className, @@ -177,9 +177,9 @@ const DropdownMenuShortcut = ({ className={cn("ml-auto text-xs tracking-widest opacity-60", className)} {...props} /> - ) -} -DropdownMenuShortcut.displayName = "DropdownMenuShortcut" + ); +}; +DropdownMenuShortcut.displayName = "DropdownMenuShortcut"; export { DropdownMenu, @@ -197,4 +197,4 @@ export { DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuRadioGroup, -} +}; diff --git a/apps/web/src/server/db/schema.ts b/apps/web/src/server/db/schema.ts index d473ef2d..55a2ea1e 100644 --- a/apps/web/src/server/db/schema.ts +++ b/apps/web/src/server/db/schema.ts @@ -6,7 +6,7 @@ import { sqliteTableCreator, text, integer, - unique + unique, } from "drizzle-orm/sqlite-core"; /** @@ -21,7 +21,9 @@ export const users = createTable("user", { id: text("id", { length: 255 }).notNull().primaryKey(), name: text("name", { length: 255 }), email: text("email", { length: 255 }).notNull(), - emailVerified: int("emailVerified", { mode: "timestamp" }).default(sql`CURRENT_TIMESTAMP`), + emailVerified: int("emailVerified", { mode: "timestamp" }).default( + sql`CURRENT_TIMESTAMP`, + ), image: text("image", { length: 255 }), }); @@ -34,7 +36,9 @@ export const accounts = createTable( "account", { id: integer("id").notNull().primaryKey({ autoIncrement: true }), - userId: text("userId", { length: 255 }).notNull().references(() => users.id), + userId: text("userId", { length: 255 }) + .notNull() + .references(() => users.id), type: text("type", { length: 255 }).notNull(), provider: text("provider", { length: 255 }).notNull(), providerAccountId: text("providerAccountId", { length: 255 }).notNull(), @@ -50,7 +54,7 @@ export const accounts = createTable( }, (account) => ({ userIdIdx: index("account_userId_idx").on(account.userId), - }) + }), ); export const sessions = createTable( @@ -58,12 +62,14 @@ export const sessions = createTable( { id: integer("id").notNull().primaryKey({ autoIncrement: true }), sessionToken: text("sessionToken", { length: 255 }).notNull(), - userId: text("userId", { length: 255 }).notNull().references(() => users.id), + userId: text("userId", { length: 255 }) + .notNull() + .references(() => users.id), expires: int("expires", { mode: "timestamp" }).notNull(), }, (session) => ({ userIdIdx: index("session_userId_idx").on(session.userId), - }) + }), ); export const verificationTokens = createTable( @@ -75,19 +81,29 @@ export const verificationTokens = createTable( }, (vt) => ({ compoundKey: primaryKey({ columns: [vt.identifier, vt.token] }), - }) + }), ); export const userStoredContent = createTable( "userStoredContent", { - userId: text("userId").notNull().references(() => users.id), - contentId: integer("contentId").notNull().references(() => storedContent.id), + 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), - }) + userContentIdx: index("userStoredContent_idx").on( + usc.userId, + usc.contentId, + ), + uniqueUserContent: unique("unique_user_content").on( + usc.userId, + usc.contentId, + ), + }), ); export const storedContent = createTable( @@ -106,5 +122,7 @@ export const storedContent = createTable( urlIdx: index("storedContent_url_idx").on(sc.url), savedAtIdx: index("storedContent_savedAt_idx").on(sc.savedAt), titleInx: index("storedContent_title_idx").on(sc.title), - }) -);
\ No newline at end of file + }), +); + +export type StoredContent = typeof storedContent.$inferSelect; |