aboutsummaryrefslogtreecommitdiff
path: root/apps/web/src
diff options
context:
space:
mode:
authorYash <[email protected]>2024-03-31 10:50:41 +0000
committerYash <[email protected]>2024-03-31 10:50:41 +0000
commitcf5972ec8f2560262b16203cba4fe1cb4b197c94 (patch)
tree6ab33d384623f70e144015aec4c78e7b333602f4 /apps/web/src
parentredeploy: check for d1 database (diff)
downloadsupermemory-cf5972ec8f2560262b16203cba4fe1cb4b197c94.tar.xz
supermemory-cf5972ec8f2560262b16203cba4fe1cb4b197c94.zip
List Item with Sidebar
Diffstat (limited to 'apps/web/src')
-rw-r--r--apps/web/src/app/globals.css8
-rw-r--r--apps/web/src/app/layout.tsx6
-rw-r--r--apps/web/src/app/ui/page.tsx9
-rw-r--r--apps/web/src/components/Sidebar.tsx145
-rw-r--r--apps/web/src/components/ui/dropdown-menu.tsx88
-rw-r--r--apps/web/src/server/db/schema.ts46
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;