aboutsummaryrefslogtreecommitdiff
path: root/apps/web/src/components
diff options
context:
space:
mode:
Diffstat (limited to 'apps/web/src/components')
-rw-r--r--apps/web/src/components/ChatMessage.tsx128
-rw-r--r--apps/web/src/components/Main-2.tsx709
-rw-r--r--apps/web/src/components/Main.tsx533
-rw-r--r--apps/web/src/components/MemoryDrawer.tsx51
-rw-r--r--apps/web/src/components/ProfileDrawer.tsx40
-rw-r--r--apps/web/src/components/SearchResults.tsx40
-rw-r--r--apps/web/src/components/Sidebar/AddMemoryDialog.tsx480
-rw-r--r--apps/web/src/components/Sidebar/DeleteConfirmation.tsx47
-rw-r--r--apps/web/src/components/Sidebar/EditNoteDialog.tsx155
-rw-r--r--apps/web/src/components/Sidebar/ExpandedSpace.tsx287
-rw-r--r--apps/web/src/components/Sidebar/FilterCombobox.tsx303
-rw-r--r--apps/web/src/components/Sidebar/MemoriesBar.tsx709
-rw-r--r--apps/web/src/components/Sidebar/SettingsTab.tsx99
-rw-r--r--apps/web/src/components/Sidebar/index.tsx172
-rw-r--r--apps/web/src/components/WordMark.tsx12
-rw-r--r--apps/web/src/components/dev/SessionProviderWrapper.tsx12
-rw-r--r--apps/web/src/components/dev/tailwindindicator.tsx16
-rw-r--r--apps/web/src/components/ui/avatar.tsx50
-rw-r--r--apps/web/src/components/ui/badge.tsx36
-rw-r--r--apps/web/src/components/ui/button.tsx56
-rw-r--r--apps/web/src/components/ui/card.tsx86
-rw-r--r--apps/web/src/components/ui/command.tsx161
-rw-r--r--apps/web/src/components/ui/dialog.tsx119
-rw-r--r--apps/web/src/components/ui/drawer.tsx124
-rw-r--r--apps/web/src/components/ui/dropdown-menu.tsx200
-rw-r--r--apps/web/src/components/ui/input.tsx53
-rw-r--r--apps/web/src/components/ui/label.tsx26
-rw-r--r--apps/web/src/components/ui/popover.tsx40
-rw-r--r--apps/web/src/components/ui/textarea.tsx57
29 files changed, 0 insertions, 4801 deletions
diff --git a/apps/web/src/components/ChatMessage.tsx b/apps/web/src/components/ChatMessage.tsx
deleted file mode 100644
index 58ef9870..00000000
--- a/apps/web/src/components/ChatMessage.tsx
+++ /dev/null
@@ -1,128 +0,0 @@
-import React, { useEffect } from "react";
-import { motion } from "framer-motion";
-import { ArrowUpRight, Globe, Text } from "lucide-react";
-import { convertRemToPixels } from "@/lib/utils";
-import { SpaceIcon } from "@/assets/Memories";
-import Markdown from "react-markdown";
-import { ChatHistory } from "../../types/memory";
-
-export function ChatAnswer({
- children: message,
- sources,
- loading = false,
-}: {
- children: string;
- sources?: ChatHistory["answer"]["sources"];
- loading?: boolean;
-}) {
- return (
- <div className="flex h-max w-full flex-col items-start gap-5">
- {loading ? (
- <MessageSkeleton />
- ) : (
- <div className="chat-answer h-full w-full text-lg text-white/60">
- <Markdown>{message}</Markdown>
- </div>
- )}
- {!loading && sources && sources?.length > 0 && (
- <>
- <h1 className="animate-fade-in text-rgray-12 text-md flex items-center justify-center gap-2 opacity-0 [animation-duration:1s]">
- <SpaceIcon className="h-6 w-6 -translate-y-[2px]" />
- Related Memories
- </h1>
- <div className="animate-fade-in -mt-3 flex items-center justify-start gap-1 opacity-0 [animation-duration:1s]">
- {sources?.map((source) =>
- source.isNote ? (
- <button className="bg-rgray-3 flex items-center justify-center gap-2 rounded-full py-1 pl-2 pr-3 text-sm">
- <Text className="h-4 w-4" />
- {source.source}
- </button>
- ) : (
- <a
- className="bg-rgray-3 flex items-center justify-center gap-2 rounded-full py-1 pl-2 pr-3 text-sm"
- key={source.source}
- href={source.source}
- target="_blank"
- >
- <Globe className="h-4 w-4" />
- {cleanUrl(source.source)}
- </a>
- ),
- )}
- </div>
- </>
- )}
- </div>
- );
-}
-
-export function ChatQuestion({ children }: { children: string }) {
- return (
- <div
- className={`text-rgray-12 h-max w-full text-left ${children.length > 200 ? "text-xl" : "text-2xl"}`}
- >
- {children}
- </div>
- );
-}
-
-export function ChatMessage({
- children,
- isLast = false,
- index,
-}: {
- children: React.ReactNode | React.ReactNode[];
- isLast?: boolean;
- index: number;
-}) {
- const messageRef = React.useRef<HTMLDivElement>(null);
-
- useEffect(() => {
- if (!isLast) return;
- messageRef.current?.parentElement?.scrollTo({
- top: messageRef.current?.offsetTop,
- behavior: "smooth",
- });
- }, []);
-
- return (
- <motion.div
- initial={{ opacity: 0, y: 20 }}
- animate={{ opacity: 1, y: 0 }}
- transition={{
- type: "tween",
- duration: 0.5,
- }}
- ref={messageRef}
- className={`${index === 0 ? "pt-16" : "pt-28"} flex h-max w-full resize-y flex-col items-start justify-start gap-5 transition-[height] ${isLast ? "min-h-screen pb-[40vh]" : "h-max"}`}
- >
- {children}
- </motion.div>
- );
-}
-
-function MessageSkeleton() {
- return (
- <div className="animate-fade-in flex w-full flex-col items-start gap-3 opacity-0 [animation-delay:0.5s] [animation-duration:1s]">
- <div className="bg-rgray-5 h-6 w-full animate-pulse rounded-md text-lg"></div>
- <div className="bg-rgray-5 h-6 w-full animate-pulse rounded-md text-lg"></div>
- <div className="bg-rgray-5 h-6 w-full animate-pulse rounded-md text-lg"></div>
- <div className="bg-rgray-5 h-6 w-full animate-pulse rounded-md text-lg"></div>
- <div className="bg-rgray-5 h-6 w-[70%] animate-pulse rounded-md text-lg"></div>
- </div>
- );
-}
-
-function cleanUrl(url: string) {
- if (url.startsWith("https://")) {
- url = url.slice(8);
- } else if (url.startsWith("http://")) {
- url = url.slice(7);
- }
-
- if (url.endsWith("/")) {
- url = url.slice(0, -1);
- }
-
- return url;
-}
diff --git a/apps/web/src/components/Main-2.tsx b/apps/web/src/components/Main-2.tsx
deleted file mode 100644
index 1b602712..00000000
--- a/apps/web/src/components/Main-2.tsx
+++ /dev/null
@@ -1,709 +0,0 @@
-"use client";
-import { MemoryDrawer } from "./MemoryDrawer";
-import useViewport from "@/hooks/useViewport";
-import { AnimatePresence } from "framer-motion";
-import { cn } from "@/lib/utils";
-
-import { Editor } from "novel";
-import { useAutoAnimate } from "@formkit/auto-animate/react";
-import {
- MemoryWithImage,
- MemoryWithImages3,
- MemoryWithImages2,
-} from "@/assets/MemoryWithImages";
-import { Input, InputWithIcon } from "./ui/input";
-import {
- ArrowUpRight,
- Edit3,
- Loader,
- Minus,
- MoreHorizontal,
- Plus,
- Search,
- Sparkles,
- Text,
- Trash2,
-} from "lucide-react";
-import {
- DropdownMenu,
- DropdownMenuContent,
- DropdownMenuItem,
- DropdownMenuTrigger,
-} from "./ui/dropdown-menu";
-import { useEffect, useMemo, useRef, useState } from "react";
-import { Variant, useAnimate, motion } from "framer-motion";
-import { SearchResult, useMemory } from "@/contexts/MemoryContext";
-import { SpaceIcon } from "@/assets/Memories";
-import { Dialog, DialogContent } from "./ui/dialog";
-import useTouchHold from "@/hooks/useTouchHold";
-import { DialogTrigger } from "@radix-ui/react-dialog";
-import {
- AddExistingMemoryToSpace,
- AddMemoryPage,
- NoteAddPage,
- SpaceAddPage,
-} from "./Sidebar/AddMemoryDialog";
-import { ExpandedSpace } from "./Sidebar/ExpandedSpace";
-import { StoredContent, StoredSpace } from "@/server/db/schema";
-import { useDebounce } from "@/hooks/useDebounce";
-import { NoteEdit } from "./Sidebar/EditNoteDialog";
-import DeleteConfirmation from "./Sidebar/DeleteConfirmation";
-
-import { ProfileDrawer } from "./ProfileDrawer";
-
-function supportsDVH() {
- try {
- return CSS.supports("height: 100dvh");
- } catch {
- return false;
- }
-}
-
-function pseudoRandomizeColorWithName(name: string) {
- const colorsAvailable = [
- "99e9f2",
- "a5d8ff",
- "d0bfff",
- "eebefa",
- "fcc2d7",
- "b2f2bb",
- "96f2d7",
- "ffec99",
- "ffd8a8",
- "ffc9c9",
- ];
-
- const colorIndex =
- name
- .split("")
- .map((char) => char.charCodeAt(0))
- .reduce((acc, charCode) => acc + charCode, 0) % colorsAvailable.length;
-
- return colorsAvailable[colorIndex];
-}
-
-export default function Main({ sidebarOpen }: { sidebarOpen: boolean }) {
- const { width } = useViewport();
-
- const [parent, enableAnimations] = useAutoAnimate();
- const { spaces, deleteSpace, freeMemories, search } = useMemory();
-
- const [isDropdownOpen, setIsDropdownOpen] = useState(false);
- const [addMemoryState, setAddMemoryState] = useState<
- "page" | "note" | "space" | "existing-memory" | null
- >(null);
-
- const [expandedSpace, setExpandedSpace] = useState<number | null>(null);
-
- const [searchQuery, setSearcyQuery] = useState("");
- const [searchLoading, setSearchLoading] = useState(false);
- const query = useDebounce(searchQuery, 500);
-
- const [searchResults, setSearchResults] = useState<SearchResult[]>([]);
-
- useEffect(() => {
- const q = query.trim();
- if (q.length < 1) {
- setSearchResults([]);
- return;
- }
-
- setSearchLoading(true);
-
- (async () => {
- setSearchResults(await search(q));
- setSearchLoading(false);
- })();
- }, [query]);
-
- // useEffect(() => {
- // if (!isOpen) {
- // setExpandedSpace(null);
- // }
- // }, [isOpen]);
-
- if (expandedSpace) {
- return (
- <ExpandedSpace
- spaceId={expandedSpace}
- back={() => setExpandedSpace(null)}
- // close={() => setExpandedSpace(null)}
- />
- );
- }
-
- return (
- <>
- <AnimatePresence mode="wait">
- <main
- data-sidebar-open={sidebarOpen}
- className={cn(
- "sidebar relative flex w-full flex-col items-end gap-5 overflow-auto bg-[#FFF] px-5 pt-5 transition-[padding-left,padding-top,padding-right] delay-200 duration-200 md:items-center md:gap-10 md:px-72 [&[data-sidebar-open='true']]:pr-10 [&[data-sidebar-open='true']]:delay-0 md:[&[data-sidebar-open='true']]:pl-[calc(2.5rem+30vw)]",
- )}
- >
- <div className="mt-16 w-full">
- <div className="flex justify-between gap-4">
- <h1 className="w-full text-3xl font-medium tracking-tight">
- Your Memories
- </h1>
- <div className="flex w-full">
- <AddMemoryModal type={addMemoryState}>
- <DropdownMenu
- open={isDropdownOpen}
- onOpenChange={setIsDropdownOpen}
- >
- <DropdownMenuTrigger asChild>
- <button className="focus-visible:ring-rgray-7 ml-auto flex items-center justify-center rounded-md px-3 py-2 transition hover:bg-stone-200 focus-visible:bg-white focus-visible:outline-none focus-visible:ring-2">
- <Plus className="mr-2 h-5 w-5" />
- Add
- </button>
- </DropdownMenuTrigger>
- <DropdownMenuContent
- onCloseAutoFocus={(e) => e.preventDefault()}
- >
- <DialogTrigger className="block w-full">
- <DropdownMenuItem
- onClick={() => {
- setAddMemoryState("page");
- }}
- >
- <Sparkles className="mr-2 h-4 w-4" />
- Page to Memory
- </DropdownMenuItem>
- </DialogTrigger>
- <DialogTrigger className="block w-full">
- <DropdownMenuItem
- onClick={() => {
- setAddMemoryState("note");
- }}
- >
- <Text className="mr-2 h-4 w-4" />
- Note
- </DropdownMenuItem>
- </DialogTrigger>
- <DialogTrigger className="block w-full">
- <DropdownMenuItem
- onClick={() => {
- setAddMemoryState("space");
- }}
- >
- <SpaceIcon className="mr-2 h-4 w-4" />
- Space
- </DropdownMenuItem>
- </DialogTrigger>
- </DropdownMenuContent>
- </DropdownMenu>
- </AddMemoryModal>
- </div>
- </div>
- <InputWithIcon
- placeholder="Search"
- icon={
- searchLoading ? (
- <Loader className="h-5 w-5 animate-spin opacity-50" />
- ) : (
- <Search className="h-5 w-5 opacity-50" />
- )
- }
- className="mt-4 w-full text-black"
- value={searchQuery}
- onChange={(e) => setSearcyQuery(e.target.value)}
- />
- </div>
- <div
- ref={parent}
- className="grid w-full grid-flow-row grid-cols-3 gap-4 px-2 py-5"
- >
- {typeof window !== "undefined" ? (
- query.trim().length > 0 ? (
- <>
- {searchResults.map(({ type, space, memory }, i) => (
- <>
- {type === "memory" && (
- <MemoryItem
- {...memory!}
- key={i}
- onDelete={() => {
- setSearchResults((prev) =>
- prev.filter((i) => i.memory?.id !== memory.id),
- );
- }}
- />
- )}
- {type === "space" && (
- <SpaceItem
- {...space!}
- key={i}
- onDelete={() => {
- setSearchResults((prev) =>
- prev.filter((i) => i.space?.id !== space.id),
- );
- deleteSpace(space.id);
- }}
- />
- )}
- </>
- ))}
- </>
- ) : (
- <>
- {spaces.map((space) => (
- <SpaceItem
- onDelete={() => deleteSpace(space.id)}
- key={space.id}
- onClick={() => setExpandedSpace(space.id)}
- {...space}
- />
- ))}
- {freeMemories.map((m) => (
- <MemoryItem {...m} key={m.id} />
- ))}
- </>
- )
- ) : (
- <>
- {Array.from({
- length: spaces.length + freeMemories.length,
- }).map((_, i) => (
- <div className="h-32 w-full animate-pulse rounded-2xl bg-stone-300/50"></div>
- ))}
- </>
- )}
- </div>
- <div className="absolute right-10 top-10 z-[100] block md:hidden">
- {width <= 768 && <ProfileDrawer />}
- </div>
- </main>
- {width <= 768 && <MemoryDrawer />}
- </AnimatePresence>
- </>
- );
-}
-
-export function MemoryItem(
- props: StoredContent & {
- onDelete?: () => void;
- removeFromSpace?: () => Promise<void>;
- },
-) {
- const { id, title, image, type, url, onDelete, removeFromSpace } = props;
-
- const { deleteMemory } = useMemory();
-
- const name = title
- ? title.length > 20
- ? title.slice(0, 20) + "..."
- : title
- : "Untitled Memory";
-
- const [isDialogOpen, setIsDialogOpen] = useState(false);
-
- const [moreDropdownOpen, setMoreDropdownOpen] = useState(false);
-
- const touchEventProps = useTouchHold({
- onHold() {
- setMoreDropdownOpen(true);
- },
- });
- return (
- <Dialog
- open={type === "note" ? isDialogOpen : false}
- onOpenChange={setIsDialogOpen}
- >
- <DialogTrigger asChild>
- <button
- onClick={() => (type === "page" ? window.open(url) : null)}
- data-space-text
- className="relative flex h-min select-none flex-col items-center justify-center gap-2 text-center font-normal focus-visible:outline-none"
- >
- <div
- {...touchEventProps}
- className="has-[[data-space-text]:focus-visible]:ring-rgray-7 [&:has-[[data-space-text]:focus-visible]>[data-more-button]]:opacity-100 flex h-32 w-full items-center justify-center rounded-2xl border-2 border-black/20 p-2 pb-4 shadow-sm ring-transparent transition duration-150 ease-out hover:scale-105 has-[[data-space-text]:focus-visible]:outline-none has-[[data-space-text]:focus-visible]:ring-2 md:has-[[data-state='true']]:bg-transparent [&:hover>[data-more-button]]:opacity-100"
- style={{
- backgroundColor: `#${pseudoRandomizeColorWithName(name)}`,
- }}
- >
- {type === "page" ? (
- <PageMoreButton
- isOpen={moreDropdownOpen}
- setIsOpen={setMoreDropdownOpen}
- removeFromSpace={removeFromSpace}
- onDelete={() => {
- deleteMemory(id);
- onDelete?.();
- }}
- url={url}
- />
- ) : type === "note" ? (
- <NoteMoreButton
- isOpen={moreDropdownOpen}
- setIsOpen={setMoreDropdownOpen}
- removeFromSpace={removeFromSpace}
- onEdit={() => setIsDialogOpen(true)}
- onDelete={() => {
- deleteMemory(id);
- onDelete?.();
- }}
- />
- ) : null}
-
- <div className="flex h-24 w-24 items-center justify-center">
- {type === "page" ? (
- <img
- className="h-16 w-16"
- id={id.toString()}
- src={image!}
- onError={(e) => {
- (e.target as HTMLImageElement).src =
- "/icons/white_without_bg.png";
- }}
- />
- ) : type === "note" ? (
- <Text
- onClick={() => setIsDialogOpen(true)}
- className="h-16 w-16"
- />
- ) : (
- <></>
- )}
- </div>
- </div>
- {name}
- </button>
- </DialogTrigger>
- <DialogContent className="w-max max-w-[auto]">
- <NoteEdit
- onDelete={onDelete}
- closeDialog={() => setIsDialogOpen(false)}
- memory={props}
- />
- </DialogContent>
- </Dialog>
- );
-}
-
-export function SpaceItem({
- name,
- id,
- onDelete,
- onClick,
-}: StoredSpace & { onDelete: () => void; onClick?: () => void }) {
- const { cachedMemories } = useMemory();
-
- const [itemRef, animateItem] = useAnimate();
- const { width } = useViewport();
-
- const [moreDropdownOpen, setMoreDropdownOpen] = useState(false);
-
- const touchEventProps = useTouchHold({
- onHold() {
- setMoreDropdownOpen(true);
- },
- });
-
- const spaceMemories = useMemo(() => {
- return cachedMemories.filter((m) => m.space === id);
- }, [cachedMemories]);
-
- const _name = name.length > 20 ? name.slice(0, 20) + "..." : name;
-
- return (
- <button
- onClick={onClick}
- data-space-text
- className="relative flex h-min select-none flex-col items-center justify-center gap-2 text-center font-normal focus-visible:outline-none"
- >
- <motion.div
- ref={itemRef}
- {...touchEventProps}
- className="has-[[data-space-text]:focus-visible]:ring-rgray-7 [&:has-[[data-space-text]:focus-visible]>[data-more-button]]:opacity-100 flex h-32 w-full items-center justify-center rounded-2xl border-2 border-black/20 p-2 pb-4 shadow-sm ring-transparent transition duration-150 ease-out hover:scale-105 has-[[data-space-text]:focus-visible]:outline-none has-[[data-space-text]:focus-visible]:ring-2 md:has-[[data-state='true']]:bg-transparent [&:hover>[data-more-button]]:opacity-100"
- style={{
- backgroundColor: `#${pseudoRandomizeColorWithName(name)}`,
- }}
- >
- <SpaceMoreButton
- isOpen={moreDropdownOpen}
- setIsOpen={setMoreDropdownOpen}
- onEdit={onClick}
- onDelete={onDelete}
- />
- {spaceMemories.length > 2 ? (
- <MemoryWithImages3
- onClick={onClick}
- className="h-24 w-24"
- id={id.toString()}
- images={
- spaceMemories
- .map((c) => (c.type === "note" ? "/note.svg" : c.image))
- .reverse() as string[]
- }
- />
- ) : spaceMemories.length > 1 ? (
- <MemoryWithImages2
- onClick={onClick}
- className="h-24 w-24"
- id={id.toString()}
- images={
- spaceMemories
- .map((c) => (c.type === "note" ? "/note.svg" : c.image))
- .reverse() as string[]
- }
- />
- ) : spaceMemories.length === 1 ? (
- <MemoryWithImage
- onClick={onClick}
- className="h-24 w-24"
- id={id.toString()}
- image={
- spaceMemories[0].type === "note"
- ? "/note.svg"
- : spaceMemories[0].image!
- }
- />
- ) : (
- <div
- onClick={onClick}
- className="flex items-center justify-center gap-2"
- >
- <svg
- xmlns="http://www.w3.org/2000/svg"
- fill="none"
- viewBox="0 0 24 24"
- strokeWidth="1.5"
- stroke="currentColor"
- className="h-8 w-8"
- >
- <path
- strokeLinecap="round"
- strokeLinejoin="round"
- d="M19.5 14.25v-2.625a3.375 3.375 0 0 0-3.375-3.375h-1.5A1.125 1.125 0 0 1 13.5 7.125v-1.5a3.375 3.375 0 0 0-3.375-3.375H8.25m2.25 0H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 0 0-9-9Z"
- />
- </svg>
- <span className="text-stone-800/80">Empty Space</span>
- </div>
- )}
- </motion.div>
-
- {_name}
- </button>
- );
-}
-
-export function SpaceMoreButton({
- onDelete,
- isOpen,
- setIsOpen,
- onEdit,
-}: {
- onDelete?: () => void;
- isOpen?: boolean;
- onEdit?: () => void;
- setIsOpen?: (open: boolean) => void;
-}) {
- return (
- <DeleteConfirmation onDelete={onDelete} trigger={false}>
- <DropdownMenu open={isOpen} onOpenChange={setIsOpen}>
- <DropdownMenuTrigger asChild>
- <button
- data-more-button
- className="focus-visible:ring-rgray-7 absolute right-2 top-2 scale-0 rounded-md p-1 opacity-0 ring-transparent transition hover:bg-white focus-visible:bg-white focus-visible:opacity-100 focus-visible:outline-none focus-visible:ring-2 md:block md:scale-100 md:bg-transparent"
- >
- <MoreHorizontal className="h-5 w-5 text-black" />
- </button>
- </DropdownMenuTrigger>
- <DropdownMenuContent align="start">
- <DropdownMenuItem onClick={onEdit}>
- <Edit3 className="mr-2 h-4 w-4" strokeWidth={1.5} />
- Edit
- </DropdownMenuItem>
- <DialogTrigger asChild>
- <DropdownMenuItem className="focus:bg-red-100 focus:text-red-400">
- <Trash2 className="mr-2 h-4 w-4" strokeWidth={1.5} />
- Delete
- </DropdownMenuItem>
- </DialogTrigger>
- </DropdownMenuContent>
- </DropdownMenu>
- </DeleteConfirmation>
- );
-}
-
-export function PageMoreButton({
- onDelete,
- isOpen,
- setIsOpen,
- url,
- removeFromSpace,
-}: {
- onDelete?: () => void;
- isOpen?: boolean;
- url: string;
- setIsOpen?: (open: boolean) => void;
- removeFromSpace?: () => Promise<void>;
-}) {
- return (
- <DeleteConfirmation onDelete={onDelete} trigger={false}>
- <DropdownMenu open={isOpen} onOpenChange={setIsOpen}>
- <DropdownMenuTrigger asChild>
- <button
- data-more-button
- className="focus-visible:ring-rgray-7 absolute right-2 top-2 scale-0 rounded-md p-1 opacity-0 ring-transparent transition hover:bg-white focus-visible:bg-white focus-visible:opacity-100 focus-visible:outline-none focus-visible:ring-2 md:block md:scale-100 md:bg-transparent"
- >
- <MoreHorizontal className="h-5 w-5 text-black" />
- </button>
- </DropdownMenuTrigger>
- <DropdownMenuContent align="start">
- <DropdownMenuItem onClick={() => window.open(url)}>
- <ArrowUpRight
- className="mr-2 h-4 w-4 scale-125"
- strokeWidth={1.5}
- />
- Open
- </DropdownMenuItem>
- {removeFromSpace && (
- <DropdownMenuItem onClick={removeFromSpace}>
- <Minus className="mr-2 h-4 w-4" strokeWidth={1.5} />
- Remove from space
- </DropdownMenuItem>
- )}
- <DialogTrigger asChild>
- <DropdownMenuItem className="focus:bg-red-100 focus:text-red-400">
- <Trash2 className="mr-2 h-4 w-4" strokeWidth={1.5} />
- Delete
- </DropdownMenuItem>
- </DialogTrigger>
- </DropdownMenuContent>
- </DropdownMenu>
- </DeleteConfirmation>
- );
-}
-
-export function NoteMoreButton({
- onDelete,
- isOpen,
- setIsOpen,
- onEdit,
- removeFromSpace,
-}: {
- onDelete?: () => void;
- isOpen?: boolean;
- onEdit?: () => void;
- setIsOpen?: (open: boolean) => void;
- removeFromSpace?: () => Promise<void>;
-}) {
- return (
- <DeleteConfirmation onDelete={onDelete} trigger={false}>
- <DropdownMenu open={isOpen} onOpenChange={setIsOpen}>
- <DropdownMenuTrigger asChild>
- <button
- data-more-button
- className="focus-visible:ring-rgray-7 absolute right-2 top-2 scale-0 rounded-md p-1 opacity-0 ring-transparent transition hover:bg-white focus-visible:bg-white focus-visible:opacity-100 focus-visible:outline-none focus-visible:ring-2 md:block md:scale-100 md:bg-transparent"
- >
- <MoreHorizontal className="h-5 w-5 text-black" />
- </button>
- </DropdownMenuTrigger>
- <DropdownMenuContent align="start">
- <DropdownMenuItem onClick={onEdit}>
- <Edit3 className="mr-2 h-4 w-4" strokeWidth={1.5} />
- Edit
- </DropdownMenuItem>
- {removeFromSpace && (
- <DropdownMenuItem onClick={removeFromSpace}>
- <Minus className="mr-2 h-4 w-4" strokeWidth={1.5} />
- Remove from space
- </DropdownMenuItem>
- )}
- <DialogTrigger asChild>
- <DropdownMenuItem className="focus:bg-red-100 focus:text-red-400">
- <Trash2 className="mr-2 h-4 w-4" strokeWidth={1.5} />
- Delete
- </DropdownMenuItem>
- </DialogTrigger>
- </DropdownMenuContent>
- </DropdownMenu>
- </DeleteConfirmation>
- );
-}
-
-export function AddMemoryModal({
- type,
- children,
- defaultSpaces,
- onAdd,
- data,
-}: {
- type: "page" | "note" | "space" | "existing-memory" | null;
- children?: React.ReactNode | React.ReactNode[];
- defaultSpaces?: number[];
- data?: {
- space?: {
- title: string;
- id: number;
- };
- fromSpaces?: number[];
- notInSpaces?: number[];
- };
- onAdd?: (data?: StoredSpace | StoredContent | StoredContent[]) => void;
-}) {
- const [isDialogOpen, setIsDialogOpen] = useState(false);
-
- return (
- <Dialog open={isDialogOpen} onOpenChange={setIsDialogOpen}>
- {children}
- <DialogContent
- onOpenAutoFocus={(e) => {
- e.preventDefault();
- const novel = document.querySelector('[contenteditable="true"]') as
- | HTMLDivElement
- | undefined;
- if (novel) {
- novel.autofocus = false;
- novel.onfocus = () => {
- (
- document.querySelector("[data-modal-autofocus]") as
- | HTMLInputElement
- | undefined
- )?.focus();
- novel.onfocus = null;
- };
- }
- (
- document.querySelector("[data-modal-autofocus]") as
- | HTMLInputElement
- | undefined
- )?.focus();
- }}
- className="w-max max-w-[auto]"
- >
- {type === "page" ? (
- <AddMemoryPage
- onAdd={onAdd}
- defaultSpaces={defaultSpaces}
- closeDialog={() => setIsDialogOpen(false)}
- />
- ) : type === "note" ? (
- <NoteAddPage
- onAdd={onAdd}
- defaultSpaces={defaultSpaces}
- closeDialog={() => setIsDialogOpen(false)}
- />
- ) : type === "space" ? (
- <SpaceAddPage
- onAdd={onAdd}
- closeDialog={() => setIsDialogOpen(false)}
- />
- ) : type === "existing-memory" ? (
- <AddExistingMemoryToSpace
- onAdd={onAdd}
- fromSpaces={data?.fromSpaces}
- notInSpaces={data?.notInSpaces}
- space={data!.space!}
- closeDialog={() => setIsDialogOpen(false)}
- />
- ) : (
- <></>
- )}
- </DialogContent>
- </Dialog>
- );
-}
diff --git a/apps/web/src/components/Main.tsx b/apps/web/src/components/Main.tsx
deleted file mode 100644
index 8ac52569..00000000
--- a/apps/web/src/components/Main.tsx
+++ /dev/null
@@ -1,533 +0,0 @@
-"use client";
-import { useCallback, useEffect, useRef, useState } from "react";
-import { FilterSpaces } from "./Sidebar/FilterCombobox";
-import { Textarea2 } from "./ui/textarea";
-import { ArrowRight, ArrowUp } from "lucide-react";
-import { MemoryDrawer } from "./MemoryDrawer";
-import useViewport from "@/hooks/useViewport";
-import { AnimatePresence, motion } from "framer-motion";
-import { cn, countLines, getIdsFromSource } from "@/lib/utils";
-import { ChatHistory } from "../../types/memory";
-import { ChatAnswer, ChatMessage, ChatQuestion } from "./ChatMessage";
-import { useRouter, useSearchParams } from "next/navigation";
-import { useMemory } from "@/contexts/MemoryContext";
-
-import Image from "next/image";
-import { getMemoriesFromUrl } from "@/actions/db";
-import { ProfileDrawer } from "./ProfileDrawer";
-
-function supportsDVH() {
- try {
- return CSS.supports("height: 100dvh");
- } catch {
- return false;
- }
-}
-
-export default function Main({ sidebarOpen }: { sidebarOpen: boolean }) {
- const searchParams = useSearchParams();
-
- const [hide, setHide] = useState(false);
- const [layout, setLayout] = useState<"chat" | "initial">("initial");
- const [value, setValue] = useState("");
- const { width } = useViewport();
- const [isAiLoading, setIsAiLoading] = useState(false);
-
- const { spaces } = useMemory();
-
- // Variable to keep track of the chat history in this session
- const [chatHistory, setChatHistory] = useState<ChatHistory[]>([]);
-
- const [toBeParsed, setToBeParsed] = useState("");
-
- const textArea = useRef<HTMLDivElement>(null);
- const main = useRef<HTMLDivElement>(null);
-
- const [selectedSpaces, setSelectedSpaces] = useState<number[]>([]);
-
- const [isStreaming, setIsStreaming] = useState(false);
-
- useEffect(() => {
- const search = searchParams.get("q");
- if (search && search.trim().length > 0) {
- setValue(search);
- onSend();
- //router.push("/");
- }
- }, []);
-
- useEffect(() => {
- // function onResize() {
- // if (!main.current || !window.visualViewport) return;
- // if (
- // window.visualViewport.height < window.innerHeight + 20 &&
- // window.visualViewport.height > window.innerHeight - 20
- // ) {
- // setHide(false);
- // window.scrollTo(0, 0);
- // } else {
- // setHide(true);
- // window.scrollTo(0, document.body.scrollHeight);
- // }
- // }
- // window.visualViewport?.addEventListener("resize", onResize);
- // return () => {
- // window.visualViewport?.removeEventListener("resize", onResize);
- // };
- }, []);
-
- useEffect(() => {
- // Define a function to try parsing the accumulated data
- const tryParseAccumulatedData = () => {
- // Attempt to parse the "toBeParsed" state as JSON
- try {
- // Split the accumulated data by the known delimiter "\n\n"
- const parts = toBeParsed.split("\n\n");
- let remainingData = "";
-
- // Process each part to extract JSON objects
- parts.forEach((part, index) => {
- try {
- const parsedPart = JSON.parse(part.replace("data: ", "")); // Try to parse the part as JSON
-
- // If the part is the last one and couldn't be parsed, keep it to accumulate more data
- if (index === parts.length - 1 && !parsedPart) {
- remainingData = part;
- } else if (parsedPart && parsedPart.response) {
- // Append to chat history in this way:
- // If the last message was from the model, append to that message
- // Otherwise, Start a new message from the model and append to that
- if (chatHistory.length > 0) {
- setChatHistory((prev: ChatHistory[]) => {
- const lastMessage = prev[prev.length - 1];
- const newParts = [
- ...lastMessage.answer.parts,
- { text: parsedPart.response },
- ];
- return [
- ...prev.slice(0, prev.length - 1),
- {
- ...lastMessage,
- answer: {
- parts: newParts,
- sources: lastMessage.answer.sources,
- },
- },
- ];
- });
- } else {
- }
- }
- } catch (error) {
- // If parsing fails and it's not the last part, it's a malformed JSON
- if (index !== parts.length - 1) {
- console.error("Malformed JSON part: ", part);
- } else {
- // If it's the last part, it may be incomplete, so keep it
- remainingData = part;
- }
- }
- });
-
- // Update the toBeParsed state to only contain the unparsed remainder
- if (remainingData !== toBeParsed) {
- setToBeParsed(remainingData);
- }
- } catch (error) {
- console.error("Error parsing accumulated data: ", error);
- }
- };
-
- // Call the parsing function if there's data to be parsed
- if (toBeParsed) {
- tryParseAccumulatedData();
- }
- }, [toBeParsed]);
-
- const modifyChatHistory = useCallback((old: ChatHistory[]) => {
- const final: { role: "user" | "model"; parts: { text: string }[] }[] = [];
- old.forEach((chat) => {
- final.push({
- role: "user",
- parts: [{ text: chat.question }],
- });
- final.push({
- role: "model",
- parts: chat.answer.parts.map((part) => ({ text: part.text })),
- });
- });
-
- return final;
- }, []);
-
- const getSearchResults = async () => {
- setIsAiLoading(true);
-
- const _value = value.trim();
- setValue("");
-
- setChatHistory((prev) => [
- ...prev,
- {
- question: _value,
- answer: {
- parts: [],
- sources: [],
- },
- },
- ]);
-
- const sourcesResponse = await fetch(
- `/api/chat?sourcesOnly=true&q=${_value}`,
- {
- method: "POST",
- body: JSON.stringify({
- chatHistory: modifyChatHistory(chatHistory),
- }),
- },
- );
-
- console.log("sources", sourcesResponse);
-
- const sourcesInJson =
- getIdsFromSource(
- (
- (await sourcesResponse.json()) as {
- ids: string[];
- }
- ).ids,
- ) ?? [];
-
- const notesInSources = sourcesInJson.filter((urls) =>
- urls.startsWith("https://notes.supermemory.dhr.wtf/"),
- );
- const nonNotes = sourcesInJson.filter((i) => !notesInSources.includes(i));
-
- const fetchedTitles = await getMemoriesFromUrl(notesInSources);
-
- const sources = [
- ...nonNotes.map((n) => ({ isNote: false, source: n ?? "<unnamed>" })),
- ...fetchedTitles.map((n) => ({
- isNote: true,
- source: n.title ?? "<unnamed>",
- })),
- ];
-
- setIsAiLoading(false);
- setChatHistory((prev) => {
- const lastMessage = prev[prev.length - 1];
- return [
- ...prev.slice(0, prev.length - 1),
- {
- ...lastMessage,
- answer: {
- parts: lastMessage.answer.parts,
- sources,
- },
- },
- ];
- });
-
- const actualSelectedSpaces = selectedSpaces.map(
- (space) => spaces.find((s) => s.id === space)?.name ?? "",
- );
-
- const response = await fetch(
- `/api/chat?q=${_value}&spaces=${actualSelectedSpaces.join(",")}`,
- {
- method: "POST",
- body: JSON.stringify({
- chatHistory: modifyChatHistory(chatHistory),
- }),
- },
- );
-
- if (response.status !== 200) {
- setIsAiLoading(false);
- return;
- }
-
- setIsStreaming(true);
-
- if (response.body) {
- let reader = response.body?.getReader();
- let decoder = new TextDecoder("utf-8");
- let result = "";
-
- // @ts-ignore
- reader.read().then(function processText({ done, value }) {
- if (done) {
- setIsAiLoading(false);
- setToBeParsed("");
-
- return;
- }
- setToBeParsed((prev) => prev + decoder.decode(value));
-
- return reader?.read().then(processText);
- });
- }
- };
-
- const onSend = () => {
- if (value.trim().length < 1) return;
- setLayout("chat");
- getSearchResults();
- };
-
- function onValueChange(e: React.ChangeEvent<HTMLTextAreaElement>) {
- const value = e.target.value;
- setValue(value);
- const lines = countLines(e.target);
- e.target.rows = Math.min(5, lines);
- }
-
- return (
- <>
- <AnimatePresence mode="wait">
- {layout === "chat" ? (
- <Chat
- key="chat"
- isLoading={isAiLoading}
- chatHistory={chatHistory}
- sidebarOpen={sidebarOpen}
- askQuestion={onSend}
- setValue={setValue}
- value={value}
- selectedSpaces={selectedSpaces}
- setSelectedSpaces={setSelectedSpaces}
- />
- ) : (
- <main
- key="intial"
- data-sidebar-open={sidebarOpen}
- ref={main}
- className={cn(
- "sidebar relative flex w-full flex-col items-end justify-center gap-5 px-5 pt-5 transition-[padding-left,padding-top,padding-right] delay-200 duration-200 md:items-center md:gap-10 md:px-72 [&[data-sidebar-open='true']]:pr-10 [&[data-sidebar-open='true']]:delay-0 md:[&[data-sidebar-open='true']]:pl-[calc(2.5rem+30vw)]",
- hide ? "" : "main-hidden",
- )}
- >
- <Image
- className="absolute right-10 top-10 hidden rounded-md md:block"
- src="/icons/logo_bw_without_bg.png"
- alt="Smort logo"
- width={50}
- height={50}
- />
- <div className="absolute right-10 top-10 block md:hidden">
- {width <= 768 && <ProfileDrawer hide={hide} />}
- </div>
- <h1 className="text-rgray-11 mt-auto w-full text-center text-3xl font-bold tracking-tight md:mt-0">
- Ask your second brain
- </h1>
-
- <FilterSpaces
- name={"Filter"}
- onClose={() => {
- textArea.current?.querySelector("textarea")?.focus();
- }}
- side="top"
- align="start"
- className="mr-auto bg-[#252525] md:hidden"
- selectedSpaces={selectedSpaces}
- setSelectedSpaces={setSelectedSpaces}
- />
- <Textarea2
- ref={textArea}
- className="bg-rgray-2 h-auto w-full flex-row items-start justify-center overflow-auto px-3 md:hidden md:items-center md:justify-center"
- textAreaProps={{
- placeholder: "Ask your SuperMemory...",
- className:
- "overflow-auto h-auto p-3 md:resize-none text-lg w-auto resize-y text-rgray-11 w-full",
- value,
- rows: 1,
- autoFocus: true,
- onChange: onValueChange,
- onKeyDown: (e) => {
- if (e.key === "Enter" && !e.shiftKey) {
- e.preventDefault();
- onSend();
- }
- },
- }}
- >
- <div className="text-rgray-11/70 ml-auto mt-auto flex h-full w-min items-center justify-center pb-3 pr-2 md:hidden">
- <FilterSpaces
- name={"Filter"}
- onClose={() => {
- textArea.current?.querySelector("textarea")?.focus();
- }}
- className="hidden md:flex"
- selectedSpaces={selectedSpaces}
- setSelectedSpaces={setSelectedSpaces}
- />
- <button
- onClick={onSend}
- disabled={value.trim().length < 1}
- className="text-rgray-11/70 bg-rgray-3 focus-visible:ring-rgray-8 hover:bg-rgray-4 mt-auto flex items-center justify-center rounded-full p-2 ring-2 ring-transparent transition-[filter] focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50"
- >
- <ArrowUp className="h-5 w-5" />
- </button>
- </div>
- </Textarea2>
-
- <Textarea2
- ref={textArea}
- exit={{
- opacity: 0,
- y: 50,
- }}
- transition={{
- type: "tween",
- duration: 0.2,
- }}
- textAreaProps={{
- placeholder: "Ask your second brain...",
- className:
- "h-auto overflow-auto md:h-full md:resize-none text-lg py-0 px-2 pt-2 md:py-0 md:p-5 resize-y text-rgray-11 w-full min-h-[1em]",
- value,
- autoFocus: true,
- onChange: (e) => setValue(e.target.value),
- onKeyDown: (e) => {
- if (e.key === "Enter" && !e.shiftKey) {
- e.preventDefault();
- onSend();
- }
- },
- }}
- className="hidden md:flex"
- >
- <div className="text-rgray-11/70 flex h-full w-fit items-center justify-center pl-0 md:w-full md:p-2">
- <FilterSpaces
- name={"Filter"}
- onClose={() => {
- textArea.current?.querySelector("textarea")?.focus();
- }}
- className="hidden md:flex"
- selectedSpaces={selectedSpaces}
- setSelectedSpaces={setSelectedSpaces}
- />
- <button
- onClick={onSend}
- disabled={value.trim().length < 1}
- className="text-rgray-11/70 bg-rgray-3 focus-visible:ring-rgray-8 hover:bg-rgray-4 mt-auto flex items-center justify-center rounded-full p-2 ring-2 ring-transparent focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50 md:ml-auto md:mt-0"
- >
- <ArrowRight className="h-5 w-5" />
- </button>
- </div>
- </Textarea2>
- </main>
- )}
- {width <= 768 && <MemoryDrawer hide={hide} />}
- </AnimatePresence>
- </>
- );
-}
-
-export function Chat({
- sidebarOpen,
- chatHistory,
- isLoading = false,
- askQuestion,
- setValue,
- value,
- selectedSpaces,
- setSelectedSpaces,
-}: {
- sidebarOpen: boolean;
- isLoading?: boolean;
- chatHistory: ChatHistory[];
- askQuestion: () => void;
- setValue: (value: string) => void;
- value: string;
- selectedSpaces: number[];
- setSelectedSpaces: React.Dispatch<React.SetStateAction<number[]>>;
-}) {
- const textArea = useRef<HTMLDivElement>(null);
-
- function onValueChange(e: React.ChangeEvent<HTMLTextAreaElement>) {
- const value = e.target.value;
- setValue(value);
- const lines = countLines(e.target);
- e.target.rows = Math.min(5, lines);
- }
-
- const { width } = useViewport();
-
- return (
- <main
- data-sidebar-open={sidebarOpen}
- className={cn(
- "sidebar relative flex w-full flex-col items-end gap-5 px-5 pt-5 transition-[padding-left,padding-top,padding-right] delay-200 duration-200 md:items-center md:gap-10 md:px-72 [&[data-sidebar-open='true']]:pr-10 [&[data-sidebar-open='true']]:delay-0 md:[&[data-sidebar-open='true']]:pl-[calc(2.5rem+30vw)]",
- )}
- >
- <div className="absolute right-10 top-10 z-[100] block md:hidden">
- {width <= 768 && <ProfileDrawer />}
- </div>
- <div className="scrollbar-none h-[70vh] w-full overflow-y-auto px-2 md:h-screen md:px-5">
- {chatHistory.map((msg, i) => (
- <ChatMessage index={i} key={i} isLast={i === chatHistory.length - 1}>
- <ChatQuestion>{msg.question}</ChatQuestion>
- <ChatAnswer
- loading={i === chatHistory.length - 1 ? isLoading : false}
- sources={msg.answer.sources}
- >
- {msg.answer.parts
- .map((part) => part.text)
- .join("")
- .replace("</s>", "")}
- </ChatAnswer>
- </ChatMessage>
- ))}
- </div>
- <div className="from-rgray-2 via-rgray-2 to-rgray-2/0 absolute bottom-0 left-0 w-full bg-gradient-to-t" />
- <div
- data-sidebar-open={sidebarOpen}
- className="absolute flex w-full items-center justify-center"
- >
- <div className="animate-from-top bottom-padding fixed left-1/2 mt-auto flex w-[90%] -translate-x-1/2 flex-col items-center justify-center gap-2 md:bottom-10 md:left-[auto] md:w-[50%] md:translate-x-0">
- <FilterSpaces
- name={"Filter"}
- onClose={() => {
- textArea.current?.querySelector("textarea")?.focus();
- }}
- side="top"
- align="start"
- className="mr-auto bg-[#252525]"
- selectedSpaces={selectedSpaces}
- setSelectedSpaces={setSelectedSpaces}
- />
- <Textarea2
- ref={textArea}
- className="bg-rgray-2 h-auto w-full flex-row items-start justify-center overflow-auto px-3 md:items-center md:justify-center"
- textAreaProps={{
- placeholder: "Ask your SuperMemory...",
- className:
- "overflow-auto h-auto p-3 md:resize-none text-lg w-auto resize-y text-rgray-11 w-full",
- value,
- rows: 1,
- autoFocus: true,
- onChange: onValueChange,
- onKeyDown: (e) => {
- if (e.key === "Enter" && !e.shiftKey) {
- e.preventDefault();
- askQuestion();
- }
- },
- }}
- >
- <div className="text-rgray-11/70 ml-auto mt-auto flex h-full w-min items-center justify-center pb-3 pr-2">
- <button
- onClick={askQuestion}
- disabled={value.trim().length < 1}
- className="text-rgray-11/70 bg-rgray-3 focus-visible:ring-rgray-8 hover:bg-rgray-4 mt-auto flex items-center justify-center rounded-full p-2 ring-2 ring-transparent transition-[filter] focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50"
- >
- <ArrowUp className="h-5 w-5" />
- </button>
- </div>
- </Textarea2>
- </div>
- </div>
- </main>
- );
-}
diff --git a/apps/web/src/components/MemoryDrawer.tsx b/apps/web/src/components/MemoryDrawer.tsx
deleted file mode 100644
index 14283281..00000000
--- a/apps/web/src/components/MemoryDrawer.tsx
+++ /dev/null
@@ -1,51 +0,0 @@
-import { useRef, useState } from "react";
-import { Drawer, DrawerContent, DrawerOverlay } from "./ui/drawer";
-import { MemoryIcon } from "@/assets/Memories";
-import { cn } from "@/lib/utils";
-import { MemoriesBar } from "./Sidebar/MemoriesBar";
-
-export interface Props extends React.ButtonHTMLAttributes<HTMLButtonElement> {
- hide?: boolean;
-}
-
-export function MemoryDrawer({ className, hide = false, ...props }: Props) {
- const [activeSnapPoint, setActiveSnapPoint] = useState<
- number | null | string
- >(0.1);
-
- return (
- <Drawer
- snapPoints={[0.1, 0.9]}
- activeSnapPoint={activeSnapPoint}
- shouldScaleBackground={false}
- setActiveSnapPoint={setActiveSnapPoint}
- open={true}
- dismissible={false}
- modal={false}
- >
- <DrawerContent
- overlay={false}
- data-expanded={activeSnapPoint === 0.9}
- className={cn(
- "border-rgray-6 DrawerContent data-[expanded=true]:bg-rgray-3 h-full w-screen border transition-[background] focus-visible:outline-none",
- hide ? "hidden" : "",
- )}
- handle={false}
- >
- <button
- onClick={() =>
- setActiveSnapPoint((prev) => (prev === 0.9 ? 0.1 : 0.9))
- }
- className="bg-rgray-4 border-rgray-6 text-rgray-11 absolute left-1/2 top-0 flex w-fit -translate-x-1/2 -translate-y-1/2 items-center justify-center gap-2 rounded-md border px-3 py-2"
- >
- <MemoryIcon className="h-7 w-7" />
- Memories
- </button>
- <div className="h-full w-full overflow-y-auto">
- <MemoriesBar isOpen={true} />
- </div>
- </DrawerContent>
- <DrawerOverlay className="relative bg-transparent" />
- </Drawer>
- );
-}
diff --git a/apps/web/src/components/ProfileDrawer.tsx b/apps/web/src/components/ProfileDrawer.tsx
deleted file mode 100644
index bdb32e03..00000000
--- a/apps/web/src/components/ProfileDrawer.tsx
+++ /dev/null
@@ -1,40 +0,0 @@
-import { useRef, useState } from "react";
-import {
- Drawer,
- DrawerContent,
- DrawerOverlay,
- DrawerTrigger,
-} from "./ui/drawer";
-import { cn } from "@/lib/utils";
-import { SettingsTab } from "./Sidebar/SettingsTab";
-import { useSession } from "next-auth/react";
-
-export interface Props extends React.ButtonHTMLAttributes<HTMLButtonElement> {
- hide?: boolean;
-}
-
-export function ProfileDrawer({ className, hide = false, ...props }: Props) {
- const { data: session } = useSession();
-
- return (
- <Drawer snapPoints={[0.9]} shouldScaleBackground={false}>
- <DrawerTrigger>
- <img
- src={session?.user?.image ?? "/icons/white_without_bg.png"}
- className="h-10 w-10 rounded-full"
- />
- </DrawerTrigger>
- <DrawerContent
- overlay={false}
- className={cn(
- "border-rgray-6 DrawerContent data-[expanded=true]:bg-rgray-3 z-[101] h-full w-screen border bg-white transition-[background] focus-visible:outline-none",
- hide ? "hidden" : "",
- )}
- >
- <div className="h-[85vh] w-full overflow-y-auto">
- <SettingsTab open={true} />
- </div>
- </DrawerContent>
- </Drawer>
- );
-}
diff --git a/apps/web/src/components/SearchResults.tsx b/apps/web/src/components/SearchResults.tsx
deleted file mode 100644
index d348814e..00000000
--- a/apps/web/src/components/SearchResults.tsx
+++ /dev/null
@@ -1,40 +0,0 @@
-"use client";
-
-import React from "react";
-import { Card, CardContent } from "./ui/card";
-import Markdown from "react-markdown";
-import remarkGfm from "remark-gfm";
-
-function SearchResults({
- aiResponse,
- sources,
-}: {
- aiResponse: string;
- sources: string[];
-}) {
- return (
- <div
- style={{
- backgroundImage: `linear-gradient(to right, #E5D9F2, #CDC1FF)`,
- }}
- className="mx-auto mt-4 w-full max-w-2xl space-y-6 rounded-xl border px-4 py-6"
- >
- <div className="text-start">
- <div className="text-xl text-black">
- <Markdown remarkPlugins={[remarkGfm]}>
- {aiResponse.replace("</s>", "")}
- </Markdown>
- </div>
- </div>
- <div className="grid gap-6">
- {sources.map((value, index) => (
- <Card key={index}>
- <CardContent className="space-y-2">{value}</CardContent>
- </Card>
- ))}
- </div>
- </div>
- );
-}
-
-export default SearchResults;
diff --git a/apps/web/src/components/Sidebar/AddMemoryDialog.tsx b/apps/web/src/components/Sidebar/AddMemoryDialog.tsx
deleted file mode 100644
index 64147b1e..00000000
--- a/apps/web/src/components/Sidebar/AddMemoryDialog.tsx
+++ /dev/null
@@ -1,480 +0,0 @@
-import { Editor } from "novel";
-import {
- DialogClose,
- DialogDescription,
- DialogFooter,
- DialogHeader,
- DialogTitle,
-} from "../ui/dialog";
-import { Input } from "../ui/input";
-import { Label } from "../ui/label";
-import { Markdown } from "tiptap-markdown";
-import { useEffect, useRef, useState } from "react";
-import { FilterMemories, FilterSpaces } from "./FilterCombobox";
-import { useMemory } from "@/contexts/MemoryContext";
-import { Loader, Plus, X } from "lucide-react";
-import { StoredContent, StoredSpace } from "@/server/db/schema";
-import { cleanUrl } from "@/lib/utils";
-import { motion } from "framer-motion";
-import { getMetaData } from "@/server/helpers";
-
-export function AddMemoryPage({
- closeDialog,
- defaultSpaces,
- onAdd,
-}: {
- closeDialog: () => void;
- defaultSpaces?: number[];
- onAdd?: (addedData: StoredContent) => void;
-}) {
- const { addMemory } = useMemory();
-
- const [loading, setLoading] = useState(false);
- const [url, setUrl] = useState("");
- const [selectedSpacesId, setSelectedSpacesId] = useState<number[]>(
- defaultSpaces ?? [],
- );
-
- return (
- <div className="w-[80vw] max-w-[80vw] md:w-[40vw]">
- <DialogHeader>
- <DialogTitle>Add a web page to memory</DialogTitle>
- <DialogDescription>
- This will fetch the content of the web page and add it to the memory
- </DialogDescription>
- </DialogHeader>
- <Label className="mt-5 block">URL</Label>
- <Input
- placeholder="Enter the URL of the page"
- type="url"
- data-modal-autofocus
- className="mt-2 w-full disabled:cursor-not-allowed disabled:opacity-70"
- value={url}
- onChange={(e) => setUrl(e.target.value)}
- disabled={loading}
- />
- <DialogFooter>
- <FilterSpaces
- selectedSpaces={selectedSpacesId}
- setSelectedSpaces={setSelectedSpacesId}
- className="mr-auto bg-white/5 hover:bg-slate-100 disabled:cursor-not-allowed disabled:opacity-70"
- name={"Spaces"}
- disabled={loading}
- />
- <button
- type={"submit"}
- disabled={loading}
- onClick={async () => {
- setLoading(true);
- const metadata = await getMetaData(url);
- const data = await addMemory(
- {
- title: metadata.title,
- description: metadata.description,
- content: "",
- type: "page",
- url: url,
- image: metadata.image,
- savedAt: new Date(),
- },
- selectedSpacesId,
- );
- if (data) onAdd?.(data.memory);
- closeDialog();
- }}
- className="bg-rgray-4 focus-visible:ring-rgray-7 relative rounded-md px-4 py-2 ring-transparent transition hover:bg-slate-100 focus-visible:bg-slate-100 focus-visible:outline-none focus-visible:ring-2 disabled:cursor-not-allowed disabled:opacity-70"
- >
- <motion.div
- initial={{ x: "-50%", y: "-100%" }}
- animate={loading && { y: "-50%", x: "-50%", opacity: 1 }}
- className="absolute left-1/2 top-1/2 -translate-x-1/2 translate-y-[-100%] opacity-0"
- >
- <Loader className="text-rgray-11 h-5 w-5 animate-spin" />
- </motion.div>
- <motion.div
- initial={{ y: "0%" }}
- animate={loading && { opacity: 0, y: "30%" }}
- >
- Add
- </motion.div>
- </button>
- <DialogClose
- disabled={loading}
- className="focus-visible:ring-rgray-7 rounded-md px-3 py-2 ring-transparent transition hover:bg-white focus-visible:bg-[#F4F3F2] focus-visible:outline-none focus-visible:ring-2 disabled:cursor-not-allowed disabled:opacity-70"
- >
- Cancel
- </DialogClose>
- </DialogFooter>
- </div>
- );
-}
-
-export function NoteAddPage({
- closeDialog,
- defaultSpaces,
- onAdd,
-}: {
- closeDialog: () => void;
- defaultSpaces?: number[];
- onAdd?: (addedData: StoredContent) => void;
-}) {
- const { addMemory } = useMemory();
-
- const [selectedSpacesId, setSelectedSpacesId] = useState<number[]>(
- defaultSpaces ?? [],
- );
-
- const inputRef = useRef<HTMLInputElement>(null);
- const [name, setName] = useState("");
- const [content, setContent] = useState("");
- const [loading, setLoading] = useState(false);
-
- function check(): boolean {
- const data = {
- name: name.trim(),
- content,
- };
- if (!data.name || data.name.length < 1) {
- if (!inputRef.current) {
- alert("Please enter a name for the note");
- return false;
- }
- inputRef.current.value = "";
- inputRef.current.placeholder = "Please enter a title for the note";
- inputRef.current.dataset["error"] = "true";
- setTimeout(() => {
- inputRef.current!.placeholder = "Title of the note";
- inputRef.current!.dataset["error"] = "false";
- }, 500);
- inputRef.current.focus();
- return false;
- }
- return true;
- }
-
- return (
- <div className="w-[80vw] md:w-auto">
- <Input
- ref={inputRef}
- data-error="false"
- className="w-full border-none p-0 text-xl ring-0 placeholder:transition placeholder:duration-500 focus-visible:ring-0 data-[error=true]:placeholder:text-red-400"
- placeholder="Title of the note"
- data-modal-autofocus
- value={name}
- disabled={loading}
- onChange={(e) => setName(e.target.value)}
- />
- <Editor
- disableLocalStorage
- defaultValue={""}
- onUpdate={(editor) => {
- if (!editor) return;
- setContent(editor.storage.markdown.getMarkdown());
- }}
- extensions={[Markdown]}
- className="novel-editor border-rgray-7 dark mt-5 max-h-[60vh] min-h-[40vh] w-full overflow-y-auto rounded-lg border bg-white md:w-[50vw] [&>div>div]:p-5"
- />
- <DialogFooter>
- <FilterSpaces
- selectedSpaces={selectedSpacesId}
- setSelectedSpaces={setSelectedSpacesId}
- className="hover:bg-rgray-5 mr-auto bg-white/5"
- name={"Spaces"}
- />
- <button
- onClick={() => {
- if (check()) {
- setLoading(true);
- addMemory(
- {
- content,
- title: name,
- type: "note",
- url: `https://notes.supermemory.dhr.wtf/`,
- image: "",
- savedAt: new Date(),
- },
- selectedSpacesId,
- ).then((data) => {
- if (data?.memory) onAdd?.(data.memory);
- closeDialog();
- });
- }
- }}
- disabled={loading}
- className="hover:bg-rgray-5 focus-visible:bg-rgray-5 focus-visible:ring-rgray-7 relative rounded-md bg-[#F4F3F2] px-4 py-2 ring-transparent transition focus-visible:outline-none focus-visible:ring-2 disabled:cursor-not-allowed disabled:opacity-70"
- >
- <motion.div
- initial={{ x: "-50%", y: "-100%" }}
- animate={loading && { y: "-50%", x: "-50%", opacity: 1 }}
- className="absolute left-1/2 top-1/2 -translate-x-1/2 translate-y-[-100%] opacity-0"
- >
- <Loader className="text-rgray-11 h-5 w-5 animate-spin" />
- </motion.div>
- <motion.div
- initial={{ y: "0%" }}
- animate={loading && { opacity: 0, y: "30%" }}
- >
- Add
- </motion.div>
- </button>
- <DialogClose
- type={undefined}
- disabled={loading}
- className="hover:bg-rgray-4 focus-visible:bg-rgray-4 focus-visible:ring-rgray-7 rounded-md px-3 py-2 ring-transparent transition focus-visible:outline-none focus-visible:ring-2 disabled:cursor-not-allowed disabled:opacity-70"
- >
- Cancel
- </DialogClose>
- </DialogFooter>
- </div>
- );
-}
-
-export function SpaceAddPage({
- closeDialog,
- onAdd,
-}: {
- closeDialog: () => void;
- onAdd?: (addedData: StoredSpace) => void;
-}) {
- const { addSpace } = useMemory();
-
- const inputRef = useRef<HTMLInputElement>(null);
- const [name, setName] = useState("");
-
- const [loading, setLoading] = useState(false);
-
- const [selected, setSelected] = useState<StoredContent[]>([]);
-
- function check(): boolean {
- const data = {
- name: name.trim(),
- };
- if (!data.name || data.name.length < 1) {
- if (!inputRef.current) {
- alert("Please enter a name for the note");
- return false;
- }
- inputRef.current.value = "";
- inputRef.current.placeholder = "Please enter a title for the space";
- inputRef.current.dataset["error"] = "true";
- setTimeout(() => {
- inputRef.current!.placeholder = "Enter the name of the space";
- inputRef.current!.dataset["error"] = "false";
- }, 500);
- inputRef.current.focus();
- return false;
- }
- return true;
- }
-
- return (
- <div className="w-[80vw] md:w-[40vw]">
- <DialogHeader>
- <DialogTitle>Add a space</DialogTitle>
- </DialogHeader>
- <Label className="mt-5 block">Name</Label>
- <Input
- ref={inputRef}
- placeholder="Enter the name of the space"
- type="url"
- data-modal-autofocus
- value={name}
- disabled={loading}
- onChange={(e) => setName(e.target.value)}
- className="mt-2 w-full placeholder:transition placeholder:duration-500 data-[error=true]:placeholder:text-red-400 focus-visible:data-[error=true]:ring-red-500/10"
- />
- {selected.length > 0 && (
- <>
- <Label className="mt-5 block">Add Memories</Label>
- <div className="flex min-h-5 flex-col items-center justify-center py-2">
- {selected.map((i) => (
- <MemorySelectedItem
- key={i.id}
- onRemove={() =>
- setSelected((prev) => prev.filter((p) => p.id !== i.id))
- }
- {...i}
- />
- ))}
- </div>
- </>
- )}
- <DialogFooter>
- <FilterMemories
- selected={selected}
- setSelected={setSelected}
- disabled={loading}
- className="mr-auto bg-white/5 hover:hover:bg-slate-100 focus-visible:hover:bg-slate-100 disabled:cursor-not-allowed disabled:opacity-70"
- >
- <Plus className="h-5 w-5" />
- Memory
- </FilterMemories>
- <button
- type={undefined}
- onClick={() => {
- if (check()) {
- setLoading(true);
- addSpace(
- name,
- selected.map((s) => s.id),
- ).then((data) => {
- if (data) onAdd?.(data.space);
- closeDialog();
- });
- }
- }}
- disabled={loading}
- className="bg-rgray-4 focus-visible:ring-rgray-7 relative rounded-md px-4 py-2 ring-transparent transition hover:hover:bg-slate-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:hover:bg-slate-100 disabled:cursor-not-allowed disabled:opacity-70"
- >
- <motion.div
- initial={{ x: "-50%", y: "-100%" }}
- animate={loading && { y: "-50%", x: "-50%", opacity: 1 }}
- className="absolute left-1/2 top-1/2 -translate-x-1/2 translate-y-[-100%] opacity-0"
- >
- <Loader className="text-rgray-11 h-5 w-5 animate-spin" />
- </motion.div>
- <motion.div
- initial={{ y: "0%" }}
- animate={loading && { opacity: 0, y: "30%" }}
- >
- Add
- </motion.div>
- </button>
- <DialogClose
- disabled={loading}
- className="focus-visible:ring-rgray-7 rounded-md px-3 py-2 ring-transparent transition hover:bg-white focus-visible:bg-[#F4F3F2] focus-visible:outline-none focus-visible:ring-2 disabled:cursor-not-allowed disabled:opacity-70"
- >
- Cancel
- </DialogClose>
- </DialogFooter>
- </div>
- );
-}
-
-export function MemorySelectedItem({
- id,
- title,
- url,
- type,
- image,
- onRemove,
-}: StoredContent & { onRemove: () => void }) {
- return (
- <div className="hover:bg-rgray-4 focus-within-bg-rgray-4 flex w-full items-center justify-start gap-2 rounded-md p-2 px-3 text-sm [&:hover_[data-icon]]:block [&:hover_img]:hidden">
- <button
- onClick={onRemove}
- className="ring-rgray-7 ring-offset-rgray-3 m-0 h-5 w-5 rounded-sm p-0 ring-offset-2 focus-visible:outline-none focus-visible:ring-2 [&:focus-visible>[data-icon]]:block [&:focus-visible>img]:hidden"
- >
- <img
- src={
- type === "note"
- ? "/note.svg"
- : image ?? "/icons/logo_without_bg.png"
- }
- className="h-5 w-5"
- />
- <X data-icon className="hidden h-5 w-5 scale-90" />
- </button>
- <span>{title}</span>
- <span className="ml-auto block opacity-50">
- {type === "note" ? "Note" : cleanUrl(url)}
- </span>
- </div>
- );
-}
-
-export function AddExistingMemoryToSpace({
- space,
- closeDialog,
- fromSpaces,
- notInSpaces,
- onAdd,
-}: {
- space: { title: string; id: number };
- closeDialog: () => void;
- fromSpaces?: number[];
- notInSpaces?: number[];
- onAdd?: () => void;
-}) {
- const { addMemoriesToSpace } = useMemory();
-
- const [loading, setLoading] = useState(false);
-
- const [selected, setSelected] = useState<StoredContent[]>([]);
-
- return (
- <div className="w-[80vw] md:w-[40vw]">
- <DialogHeader>
- <DialogTitle>Add an existing memory to {space.title}</DialogTitle>
- <DialogDescription>
- Pick the memories you want to add to this space
- </DialogDescription>
- </DialogHeader>
- {selected.length > 0 && (
- <>
- <Label className="mt-5 block">Add Memories</Label>
- <div className="flex min-h-5 flex-col items-center justify-center py-2">
- {selected.map((i) => (
- <MemorySelectedItem
- key={i.id}
- onRemove={() =>
- setSelected((prev) => prev.filter((p) => p.id !== i.id))
- }
- {...i}
- />
- ))}
- </div>
- </>
- )}
- <DialogFooter>
- <FilterMemories
- selected={selected}
- setSelected={setSelected}
- disabled={loading}
- fromSpaces={fromSpaces}
- notInSpaces={notInSpaces}
- className="hover:bg-rgray-4 focus-visible:bg-rgray-4 mr-auto bg-white/5 disabled:cursor-not-allowed disabled:opacity-70"
- >
- <Plus className="h-5 w-5" />
- Memory
- </FilterMemories>
- <button
- type={undefined}
- onClick={() => {
- setLoading(true);
- addMemoriesToSpace(
- space.id,
- selected.map((i) => i.id),
- ).then(() => {
- onAdd?.();
- closeDialog();
- });
- }}
- disabled={loading}
- className="bg-rgray-4 hover:bg-rgray-5 focus-visible:bg-rgray-5 focus-visible:ring-rgray-7 relative rounded-md px-4 py-2 ring-transparent transition focus-visible:outline-none focus-visible:ring-2 disabled:cursor-not-allowed disabled:opacity-70"
- >
- <motion.div
- initial={{ x: "-50%", y: "-100%" }}
- animate={loading && { y: "-50%", x: "-50%", opacity: 1 }}
- className="absolute left-1/2 top-1/2 -translate-x-1/2 translate-y-[-100%] opacity-0"
- >
- <Loader className="text-rgray-11 h-5 w-5 animate-spin" />
- </motion.div>
- <motion.div
- initial={{ y: "0%" }}
- animate={loading && { opacity: 0, y: "30%" }}
- >
- Add
- </motion.div>
- </button>
- <DialogClose
- disabled={loading}
- className="focus-visible:ring-rgray-7 rounded-md px-3 py-2 ring-transparent transition hover:bg-white focus-visible:bg-[#F4F3F2] focus-visible:outline-none focus-visible:ring-2 disabled:cursor-not-allowed disabled:opacity-70"
- >
- Cancel
- </DialogClose>
- </DialogFooter>
- </div>
- );
-}
diff --git a/apps/web/src/components/Sidebar/DeleteConfirmation.tsx b/apps/web/src/components/Sidebar/DeleteConfirmation.tsx
deleted file mode 100644
index 7955df0d..00000000
--- a/apps/web/src/components/Sidebar/DeleteConfirmation.tsx
+++ /dev/null
@@ -1,47 +0,0 @@
-import {
- Dialog,
- DialogContent,
- DialogTrigger,
- DialogTitle,
- DialogDescription,
- DialogClose,
- DialogFooter,
-} from "../ui/dialog";
-
-export default function DeleteConfirmation({
- onDelete,
- trigger = true,
- children,
-}: {
- trigger?: boolean;
- onDelete?: () => void;
- children: React.ReactNode;
-}) {
- return (
- <Dialog>
- {trigger ? (
- <DialogTrigger asChild>{children}</DialogTrigger>
- ) : (
- <>{children}</>
- )}
- <DialogContent>
- <DialogTitle className="text-xl">Are you sure?</DialogTitle>
- <DialogDescription className="text-md">
- You will not be able to recover this it.
- </DialogDescription>
- <DialogFooter>
- <DialogClose
- type={undefined}
- onClick={onDelete}
- className="ml-auto flex items-center justify-center rounded-md bg-red-100/10 px-3 py-2 text-red-400 transition hover:bg-red-100/5 focus-visible:bg-red-100/5 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-red-100/30"
- >
- Delete
- </DialogClose>
- <DialogClose className="focus-visible:bg-rgray-4 focus-visible:ring-rgray-7 hover:bg-rgray-4 ml-auto flex items-center justify-center rounded-md px-3 py-2 transition focus-visible:outline-none focus-visible:ring-2">
- Cancel
- </DialogClose>
- </DialogFooter>
- </DialogContent>
- </Dialog>
- );
-}
diff --git a/apps/web/src/components/Sidebar/EditNoteDialog.tsx b/apps/web/src/components/Sidebar/EditNoteDialog.tsx
deleted file mode 100644
index c0ad716d..00000000
--- a/apps/web/src/components/Sidebar/EditNoteDialog.tsx
+++ /dev/null
@@ -1,155 +0,0 @@
-import { Editor } from "novel";
-import { DialogClose, DialogFooter } from "../ui/dialog";
-import { Input } from "../ui/input";
-import { Markdown } from "tiptap-markdown";
-import { useEffect, useRef, useState } from "react";
-import { FilterSpaces } from "./FilterCombobox";
-import { useMemory } from "@/contexts/MemoryContext";
-import { Loader, Plus, Trash, X } from "lucide-react";
-import { motion } from "framer-motion";
-import { StoredContent } from "@/server/db/schema";
-import { fetchContent } from "@/actions/db";
-import { isArraysEqual } from "@/lib/utils";
-import DeleteConfirmation from "./DeleteConfirmation";
-
-export function NoteEdit({
- memory,
- closeDialog,
- onDelete,
-}: {
- memory: StoredContent;
- closeDialog: () => any;
- onDelete?: () => void;
-}) {
- const { updateMemory, deleteMemory } = useMemory();
-
- const [initialSpaces, setInitialSpaces] = useState<number[]>([]);
- const [selectedSpacesId, setSelectedSpacesId] = useState<number[]>([]);
-
- const inputRef = useRef<HTMLInputElement>(null);
- const [name, setName] = useState(memory.title ?? "");
- const [content, setContent] = useState(memory.content);
- const [loading, setLoading] = useState(false);
-
- function check(): boolean {
- const data = {
- name: name.trim(),
- content,
- };
- if (!data.name || data.name.length < 1) {
- if (!inputRef.current) {
- alert("Please enter a name for the note");
- return false;
- }
- inputRef.current.value = "";
- inputRef.current.placeholder = "Please enter a title for the note";
- inputRef.current.dataset["error"] = "true";
- setTimeout(() => {
- inputRef.current!.placeholder = "Title of the note";
- inputRef.current!.dataset["error"] = "false";
- }, 500);
- inputRef.current.focus();
- return false;
- }
- return true;
- }
-
- useEffect(() => {
- fetchContent(memory.id).then((data) => {
- if (data?.spaces) {
- setInitialSpaces(data.spaces);
- setSelectedSpacesId(data.spaces);
- }
- });
- }, []);
-
- return (
- <div>
- <Input
- ref={inputRef}
- data-error="false"
- className="w-full border-none p-0 text-xl ring-0 placeholder:transition placeholder:duration-500 focus-visible:ring-0 data-[error=true]:placeholder:text-red-400"
- placeholder="Title of the note"
- value={name}
- disabled={loading}
- onChange={(e) => setName(e.target.value)}
- />
- <Editor
- disableLocalStorage
- defaultValue={memory.content}
- onUpdate={(editor) => {
- if (!editor) return;
- setContent(editor.storage.markdown.getMarkdown());
- }}
- extensions={[Markdown]}
- className="novel-editor border-rgray-7 dark mt-5 max-h-[60vh] min-h-[40vh] w-[50vw] overflow-y-auto rounded-lg border bg-white [&>div>div]:p-5"
- />
- <DialogFooter>
- <FilterSpaces
- selectedSpaces={selectedSpacesId}
- setSelectedSpaces={setSelectedSpacesId}
- className="mr-auto bg-white hover:bg-slate-100"
- name={"Spaces"}
- />
- <DeleteConfirmation
- onDelete={() => {
- deleteMemory(memory.id);
- onDelete?.();
- }}
- >
- <button
- type={undefined}
- disabled={loading}
- className="rounded-md px-3 py-2 ring-transparent transition hover:bg-red-100 hover:text-red-400 focus-visible:bg-red-100 focus-visible:text-red-400 focus-visible:outline-none focus-visible:ring-2 disabled:cursor-not-allowed disabled:opacity-70"
- >
- <Trash className="h-5 w-5" />
- </button>
- </DeleteConfirmation>
- <button
- onClick={() => {
- if (check()) {
- setLoading(true);
- console.log({
- title: name === memory.title ? undefined : name,
- content: content === memory.content ? undefined : content,
- spaces: isArraysEqual(initialSpaces, selectedSpacesId)
- ? undefined
- : selectedSpacesId,
- });
- updateMemory(memory.id, {
- title: name === memory.title ? undefined : name,
- content: content === memory.content ? undefined : content,
- spaces: isArraysEqual(initialSpaces, selectedSpacesId)
- ? undefined
- : selectedSpacesId,
- }).then(closeDialog);
- }
- }}
- disabled={loading}
- className="focus-visible:ring-rgray-7 relative rounded-md bg-white px-4 py-2 ring-transparent transition hover:bg-slate-100 focus-visible:bg-slate-100 focus-visible:outline-none focus-visible:ring-2 disabled:cursor-not-allowed disabled:opacity-70"
- >
- <motion.div
- initial={{ x: "-50%", y: "-100%" }}
- animate={loading && { y: "-50%", x: "-50%", opacity: 1 }}
- className="absolute left-1/2 top-1/2 -translate-x-1/2 translate-y-[-100%] opacity-0"
- >
- <Loader className="h-5 w-5 animate-spin" />
- </motion.div>
- <motion.div
- initial={{ y: "0%" }}
- animate={loading && { opacity: 0, y: "30%" }}
- >
- Save
- </motion.div>
- </button>
- <DialogClose
- type={undefined}
- disabled={loading}
- className="focus-visible:bg-rgray-4 focus-visible:ring-rgray-7 rounded-md px-3 py-2 ring-transparent transition hover:bg-white focus-visible:outline-none focus-visible:ring-2 disabled:cursor-not-allowed disabled:opacity-70"
- >
- Cancel
- </DialogClose>
- </DialogFooter>
- </div>
- );
-}
diff --git a/apps/web/src/components/Sidebar/ExpandedSpace.tsx b/apps/web/src/components/Sidebar/ExpandedSpace.tsx
deleted file mode 100644
index 55d3f3f8..00000000
--- a/apps/web/src/components/Sidebar/ExpandedSpace.tsx
+++ /dev/null
@@ -1,287 +0,0 @@
-import { fetchContentForSpace, getSpace } from "@/actions/db";
-import { useMemory } from "@/contexts/MemoryContext";
-import { StoredContent, StoredSpace } from "@/server/db/schema";
-import {
- Edit3,
- Loader,
- Plus,
- Search,
- Sparkles,
- StickyNote,
- Text,
- Undo2,
-} from "lucide-react";
-import { useEffect, useRef, useState } from "react";
-import { Input, InputWithIcon } from "../ui/input";
-import { useDebounce } from "@/hooks/useDebounce";
-import { useAutoAnimate } from "@formkit/auto-animate/react";
-import { AddMemoryModal, MemoryItem } from "./MemoriesBar";
-import {
- DropdownMenu,
- DropdownMenuContent,
- DropdownMenuItem,
- DropdownMenuTrigger,
-} from "../ui/dropdown-menu";
-import { DialogTrigger } from "../ui/dialog";
-
-export function ExpandedSpace({
- spaceId,
- back,
-}: {
- spaceId: number;
- back: () => void;
-}) {
- const { updateMemory, updateSpace, search } = useMemory();
-
- const [parent, enableAnimations] = useAutoAnimate();
-
- const inputRef = useRef<HTMLInputElement>(null);
-
- const [contentForSpace, setContentForSpace] = useState<StoredContent[]>([]);
-
- const [lastUpdatedTitle, setLastUpdatedTitle] = useState<string | null>(null);
-
- const [title, setTitle] = useState<string>("");
- const debouncedTitle = useDebounce(title, 500);
-
- const [loading, setLoading] = useState(true);
-
- const [saveLoading, setSaveLoading] = useState(false);
-
- const [searchQuery, setSearcyQuery] = useState("");
- const [searchLoading, setSearchLoading] = useState(false);
- const query = useDebounce(searchQuery, 500);
-
- const [searchResults, setSearchResults] = useState<StoredContent[]>([]);
-
- const [addMemoryState, setAddMemoryState] = useState<
- "page" | "note" | "existing-memory" | "space" | null
- >(null);
- const [isDropdownOpen, setIsDropdownOpen] = useState(false);
-
- useEffect(() => {
- (async () => {
- const title = (await getSpace(spaceId))?.name ?? "";
- setTitle(title);
- setLastUpdatedTitle(title);
- setContentForSpace((await fetchContentForSpace(spaceId)) ?? []);
- setLoading(false);
- })();
- }, []);
-
- useEffect(() => {
- if (
- debouncedTitle.trim().length < 1 ||
- debouncedTitle.trim() === lastUpdatedTitle?.trim()
- )
- return;
- (async () => {
- setSaveLoading(true);
- await updateSpace(spaceId, debouncedTitle.trim());
- setLastUpdatedTitle(debouncedTitle);
- setSaveLoading(false);
- })();
- }, [debouncedTitle]);
-
- useEffect(() => {
- const q = query.trim();
- if (q.length < 1) {
- setSearchResults([]);
- return;
- }
-
- setSearchLoading(true);
-
- (async () => {
- setSearchResults(
- (
- await search(q, {
- filter: { spaces: false },
- memoriesRelativeToSpace: {
- fromSpaces: [spaceId],
- },
- })
- ).map((i) => i.memory!),
- );
- setSearchLoading(false);
- })();
- }, [query]);
-
- if (loading) {
- return (
- <div className="flex h-full w-full items-center justify-center">
- <Loader className="h-5 w-5 animate-spin" />
- </div>
- );
- }
-
- return (
- <div className="text-rgray-11 flex w-full flex-col items-start py-8 text-left">
- <div className="flex w-full items-center justify-start gap-2 px-8">
- <button
- onClick={back}
- className="focus-visible:ring-offset-rgray-3 focus-visible:ring-rgray-7 rounded-sm transition focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2"
- >
- <Undo2 className="h-5 w-5" />
- </button>
- <Input
- ref={inputRef}
- data-error="false"
- className="w-full border-none p-0 text-xl ring-0 placeholder:text-white/30 placeholder:transition placeholder:duration-500 focus-visible:ring-0 data-[error=true]:placeholder:text-red-400"
- placeholder="Title of the space"
- data-modal-autofocus
- value={title}
- onChange={(e) => setTitle(e.target.value)}
- />
- <button
- onClick={() => {
- inputRef.current?.focus();
- inputRef.current?.animate(
- {
- opacity: [1, 0.2, 1],
- },
- {
- duration: 100,
- },
- );
- }}
- className="focus-visible:ring-offset-rgray-3 focus-visible:ring-rgray-7 rounded-sm transition focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2"
- >
- {saveLoading ? (
- <Loader className="h-5 w-5 animate-spin opacity-70" />
- ) : (
- <Edit3 className="h-5 w-5 opacity-70" />
- )}
- </button>
- </div>
- <div className="w-full px-8">
- <InputWithIcon
- placeholder="Search"
- icon={
- searchLoading ? (
- <Loader className="text-rgray-11 h-5 w-5 animate-spin opacity-50" />
- ) : (
- <Search className="text-rgray-11 h-5 w-5 opacity-50" />
- )
- }
- className="bg-rgray-4 mt-2 w-full"
- value={searchQuery}
- onChange={(e) => setSearcyQuery(e.target.value)}
- />
- </div>
- <div className="mt-2 w-full px-8">
- <AddMemoryModal
- onAdd={(data) => {
- if (!data) {
- setLoading(true);
- (async () => {
- const title = (await getSpace(spaceId))?.name ?? "";
- setTitle(title);
- setLastUpdatedTitle(title);
- setContentForSpace((await fetchContentForSpace(spaceId)) ?? []);
- setLoading(false);
- })();
- } else if (Object.hasOwn(data, "url")) {
- const _data = data as StoredContent;
- setContentForSpace((prev) => [...prev, _data]);
- }
- }}
- data={{ space: { title, id: spaceId }, notInSpaces: [spaceId] }}
- defaultSpaces={[spaceId]}
- type={addMemoryState}
- >
- <DropdownMenu open={isDropdownOpen} onOpenChange={setIsDropdownOpen}>
- <DropdownMenuTrigger asChild>
- <button className="focus-visible:bg-rgray-4 focus-visible:ring-rgray-7 hover:bg-rgray-4 ml-auto flex items-center justify-center rounded-md px-3 py-2 transition focus-visible:outline-none focus-visible:ring-2">
- <Plus className="mr-2 h-5 w-5" />
- Add
- </button>
- </DropdownMenuTrigger>
- <DropdownMenuContent onCloseAutoFocus={(e) => e.preventDefault()}>
- <DialogTrigger className="block w-full">
- <DropdownMenuItem
- onClick={() => {
- setAddMemoryState("existing-memory");
- }}
- >
- <Sparkles className="mr-2 h-4 w-4" />
- Existing Memory
- </DropdownMenuItem>
- </DialogTrigger>
- <DialogTrigger className="block w-full">
- <DropdownMenuItem
- onClick={() => {
- setAddMemoryState("page");
- }}
- >
- <StickyNote className="mr-2 h-4 w-4" />
- Page
- </DropdownMenuItem>
- </DialogTrigger>
- <DialogTrigger className="block w-full">
- <DropdownMenuItem
- onClick={() => {
- setAddMemoryState("note");
- }}
- >
- <Text className="mr-2 h-4 w-4" />
- Note
- </DropdownMenuItem>
- </DialogTrigger>
- </DropdownMenuContent>
- </DropdownMenu>
- </AddMemoryModal>
- </div>
- <div
- ref={parent}
- className="grid w-full grid-flow-row grid-cols-3 gap-1 px-2 py-5"
- >
- {query.trim().length > 0 ? (
- <>
- {searchResults.map((memory, i) => (
- <MemoryItem
- removeFromSpace={async () => {
- await updateMemory(memory.id, {
- removedFromSpaces: [spaceId],
- });
- setContentForSpace((prev) =>
- prev.filter((s) => s.id !== memory.id),
- );
- setSearchResults((prev) =>
- prev.filter((i) => i.id !== memory.id),
- );
- }}
- {...memory!}
- key={i}
- onDelete={() => {
- setContentForSpace((prev) =>
- prev.filter((s) => s.id !== memory.id),
- );
- setSearchResults((prev) =>
- prev.filter((i) => i.id !== memory.id),
- );
- }}
- />
- ))}
- </>
- ) : (
- contentForSpace.map((m) => (
- <MemoryItem
- key={m.id}
- {...m}
- onDelete={() =>
- setContentForSpace((prev) => prev.filter((s) => s.id !== m.id))
- }
- removeFromSpace={async () => {
- await updateMemory(m.id, {
- removedFromSpaces: [spaceId],
- });
- setContentForSpace((prev) => prev.filter((s) => s.id !== m.id));
- }}
- />
- ))
- )}
- </div>
- </div>
- );
-}
diff --git a/apps/web/src/components/Sidebar/FilterCombobox.tsx b/apps/web/src/components/Sidebar/FilterCombobox.tsx
deleted file mode 100644
index 634a09e3..00000000
--- a/apps/web/src/components/Sidebar/FilterCombobox.tsx
+++ /dev/null
@@ -1,303 +0,0 @@
-"use client";
-
-import * as React from "react";
-import { Check, ChevronsUpDown } from "lucide-react";
-
-import { cn } from "@/lib/utils";
-import { Button } from "@/components/ui/button";
-import {
- Command,
- CommandEmpty,
- CommandGroup,
- CommandInput,
- CommandItem,
- CommandList,
-} from "@/components/ui/command";
-import {
- Popover,
- PopoverContent,
- PopoverTrigger,
-} from "@/components/ui/popover";
-import { SpaceIcon } from "@/assets/Memories";
-import { AnimatePresence, LayoutGroup, motion } from "framer-motion";
-import { SearchResult, useMemory } from "@/contexts/MemoryContext";
-import { useDebounce } from "@/hooks/useDebounce";
-import { StoredContent } from "@/server/db/schema";
-
-export interface FilterSpacesProps
- extends React.ButtonHTMLAttributes<HTMLButtonElement> {
- side?: "top" | "bottom";
- align?: "end" | "start" | "center";
- onClose?: () => void;
- selectedSpaces: number[];
- setSelectedSpaces: (
- spaces: number[] | ((prev: number[]) => number[]),
- ) => void;
- name: string;
-}
-
-export function FilterSpaces({
- className,
- side = "bottom",
- align = "center",
- onClose,
- selectedSpaces,
- setSelectedSpaces,
- name,
- ...props
-}: FilterSpacesProps) {
- const { spaces } = useMemory();
- const [open, setOpen] = React.useState(false);
-
- const sortedSpaces = spaces.sort(({ id: a }, { id: b }) =>
- selectedSpaces.includes(a) && !selectedSpaces.includes(b)
- ? -1
- : selectedSpaces.includes(b) && !selectedSpaces.includes(a)
- ? 1
- : 0,
- );
-
- React.useEffect(() => {
- if (!open) {
- onClose?.();
- }
- }, [open]);
-
- return (
- <Popover open={open} onOpenChange={setOpen}>
- <PopoverTrigger asChild>
- <button
- type={undefined}
- data-state-on={open}
- className={cn(
- "focus-visible:ring-rgray-8 hover:bg-rgray-3 relative flex items-center justify-center gap-1 rounded-md px-3 py-1.5 ring-2 ring-transparent focus-visible:outline-none",
- className,
- )}
- {...props}
- >
- <SpaceIcon className="mr-1 h-5 w-5" />
- {name}
- <ChevronsUpDown className="h-4 w-4" />
- <div
- data-state-on={selectedSpaces.length > 0}
- className="on:flex text-rgray-11 border-rgray-6 bg-rgray-2 absolute left-0 top-0 hidden aspect-[1] h-4 w-4 -translate-x-1/3 -translate-y-1/3 items-center justify-center rounded-full border text-center text-[9px]"
- >
- {selectedSpaces.length}
- </div>
- </button>
- </PopoverTrigger>
- <PopoverContent
- align={align}
- side={side}
- className="w-[200px] p-0"
- onCloseAutoFocus={(e) => e.preventDefault()}
- >
- <Command
- filter={(val, search) =>
- spaces
- .find((s) => s.id.toString() === val)
- ?.name.toLowerCase()
- .includes(search.toLowerCase().trim())
- ? 1
- : 0
- }
- >
- <CommandInput placeholder="Filter spaces..." />
- <CommandList asChild>
- <motion.div layoutScroll>
- <CommandEmpty>Nothing found</CommandEmpty>
- <CommandGroup>
- {sortedSpaces.map((space) => (
- <CommandItem
- key={space.id}
- value={space.id.toString()}
- onSelect={(val) => {
- setSelectedSpaces((prev: number[]) =>
- prev.includes(parseInt(val))
- ? prev.filter((v) => v !== parseInt(val))
- : [...prev, parseInt(val)],
- );
- }}
- asChild
- >
- <motion.div
- initial={{ opacity: 0 }}
- animate={{ opacity: 1, transition: { delay: 0.05 } }}
- transition={{ duration: 0.15 }}
- layout
- layoutId={`space-combobox-${space.id}`}
- className="text-rgray-11"
- >
- <SpaceIcon className="mr-2 h-4 w-4" />
- {space.name.length > 10
- ? space.name.slice(0, 10) + "..."
- : space.name}
- {selectedSpaces.includes(space.id)}
- <Check
- data-state-on={selectedSpaces.includes(space.id)}
- className={cn(
- "on:opacity-100 ml-auto h-4 w-4 opacity-0",
- )}
- />
- </motion.div>
- </CommandItem>
- ))}
- </CommandGroup>
- </motion.div>
- </CommandList>
- </Command>
- </PopoverContent>
- </Popover>
- );
-}
-
-export type FilterMemoriesProps = {
- side?: "top" | "bottom";
- align?: "end" | "start" | "center";
- onClose?: () => void;
- selected: StoredContent[];
- setSelected: React.Dispatch<React.SetStateAction<StoredContent[]>>;
- fromSpaces?: number[];
- notInSpaces?: number[];
-} & React.ButtonHTMLAttributes<HTMLButtonElement>;
-
-export function FilterMemories({
- className,
- side = "bottom",
- align = "center",
- onClose,
- selected,
- setSelected,
- fromSpaces,
- notInSpaces,
- ...props
-}: FilterMemoriesProps) {
- const { search } = useMemory();
-
- const [open, setOpen] = React.useState(false);
- const [searchQuery, setSearchQuery] = React.useState("");
- const query = useDebounce(searchQuery, 500);
-
- const [searchResults, setSearchResults] = React.useState<SearchResult[]>([]);
- const [isSearching, setIsSearching] = React.useState(false);
-
- const results = React.useMemo(() => {
- return searchResults.map((r) => r.memory);
- }, [searchResults]);
-
- console.log("memoized", results);
-
- React.useEffect(() => {
- const q = query.trim();
- if (q.length > 0) {
- setIsSearching(true);
- (async () => {
- const results = await search(q, {
- filter: {
- memories: true,
- spaces: false,
- },
- memoriesRelativeToSpace: {
- fromSpaces,
- notInSpaces,
- },
- });
- setSearchResults(results);
- setIsSearching(false);
- })();
- } else {
- setSearchResults([]);
- }
- }, [query]);
-
- React.useEffect(() => {
- if (!open) {
- onClose?.();
- }
- }, [open]);
-
- console.log(searchResults);
- return (
- <AnimatePresence mode="popLayout">
- <LayoutGroup>
- <Popover open={open} onOpenChange={setOpen}>
- <PopoverTrigger asChild>
- <button
- type={undefined}
- data-state-on={open}
- className={cn(
- "text-rgray-11/70 on:bg-rgray-3 focus-visible:ring-rgray-8 hover:bg-rgray-3 relative flex items-center justify-center gap-1 rounded-md px-3 py-1.5 ring-2 ring-transparent focus-visible:outline-none",
- className,
- )}
- {...props}
- >
- {props.children}
- </button>
- </PopoverTrigger>
- <PopoverContent
- onCloseAutoFocus={(e) => e.preventDefault()}
- align={align}
- side={side}
- className="w-[200px] p-0"
- >
- <Command shouldFilter={false}>
- <CommandInput
- isSearching={isSearching}
- value={searchQuery}
- onValueChange={setSearchQuery}
- placeholder="Filter memories..."
- />
- <CommandList>
- <CommandGroup>
- <CommandEmpty className="text-rgray-11 py-5 text-center text-sm">
- {isSearching
- ? "Searching..."
- : query.trim().length > 0
- ? "Nothing Found"
- : "Search something"}
- </CommandEmpty>
- {results.map((m) => (
- <CommandItem
- key={m.id}
- value={m.id.toString()}
- onSelect={(val) => {
- setSelected((prev) =>
- prev.find((p) => p.id === parseInt(val))
- ? prev.filter((v) => v.id !== parseInt(val))
- : [...prev, m],
- );
- }}
- asChild
- >
- <div className="text-rgray-11">
- <img
- src={
- m.type === "note"
- ? "/note.svg"
- : m.image ?? "/icons/logo_without_bg.png"
- }
- className="mr-2 h-4 w-4"
- />
- {m.title && m.title?.length > 14
- ? m.title?.slice(0, 14) + "..."
- : m.title}
- <Check
- data-state-on={
- selected.find((i) => i.id === m.id) !== undefined
- }
- className={cn(
- "on:opacity-100 ml-auto h-4 w-4 opacity-0",
- )}
- />
- </div>
- </CommandItem>
- ))}
- </CommandGroup>
- </CommandList>
- </Command>
- </PopoverContent>
- </Popover>
- </LayoutGroup>
- </AnimatePresence>
- );
-}
diff --git a/apps/web/src/components/Sidebar/MemoriesBar.tsx b/apps/web/src/components/Sidebar/MemoriesBar.tsx
deleted file mode 100644
index a81d00c0..00000000
--- a/apps/web/src/components/Sidebar/MemoriesBar.tsx
+++ /dev/null
@@ -1,709 +0,0 @@
-import { Editor } from "novel";
-import { useAutoAnimate } from "@formkit/auto-animate/react";
-import {
- MemoryWithImage,
- MemoryWithImages3,
- MemoryWithImages2,
-} from "@/assets/MemoryWithImages";
-import { Input, InputWithIcon } from "../ui/input";
-import {
- ArrowUpRight,
- Edit3,
- Loader,
- Minus,
- MoreHorizontal,
- Plus,
- Search,
- Sparkles,
- Text,
- Trash2,
-} from "lucide-react";
-import {
- DropdownMenu,
- DropdownMenuContent,
- DropdownMenuItem,
- DropdownMenuTrigger,
-} from "../ui/dropdown-menu";
-import { useEffect, useMemo, useRef, useState } from "react";
-import { Variant, useAnimate, motion } from "framer-motion";
-import { SearchResult, useMemory } from "@/contexts/MemoryContext";
-import { SpaceIcon } from "@/assets/Memories";
-import { Dialog, DialogContent } from "../ui/dialog";
-import useViewport from "@/hooks/useViewport";
-import useTouchHold from "@/hooks/useTouchHold";
-import { DialogTrigger } from "@radix-ui/react-dialog";
-import {
- AddExistingMemoryToSpace,
- AddMemoryPage,
- NoteAddPage,
- SpaceAddPage,
-} from "./AddMemoryDialog";
-import { ExpandedSpace } from "./ExpandedSpace";
-import { StoredContent, StoredSpace } from "@/server/db/schema";
-import { useDebounce } from "@/hooks/useDebounce";
-import { NoteEdit } from "./EditNoteDialog";
-import DeleteConfirmation from "./DeleteConfirmation";
-
-export function MemoriesBar({ isOpen }: { isOpen: boolean }) {
- const [parent, enableAnimations] = useAutoAnimate();
- const { spaces, deleteSpace, freeMemories, search } = useMemory();
-
- const [isDropdownOpen, setIsDropdownOpen] = useState(false);
- const [addMemoryState, setAddMemoryState] = useState<
- "page" | "note" | "space" | "existing-memory" | null
- >(null);
-
- const [expandedSpace, setExpandedSpace] = useState<number | null>(null);
-
- const [searchQuery, setSearcyQuery] = useState("");
- const [searchLoading, setSearchLoading] = useState(false);
- const query = useDebounce(searchQuery, 500);
-
- const [searchResults, setSearchResults] = useState<SearchResult[]>([]);
-
- useEffect(() => {
- const q = query.trim();
- if (q.length < 1) {
- setSearchResults([]);
- return;
- }
-
- setSearchLoading(true);
-
- (async () => {
- setSearchResults(await search(q));
- setSearchLoading(false);
- })();
- }, [query]);
-
- useEffect(() => {
- if (!isOpen) {
- setExpandedSpace(null);
- }
- }, [isOpen]);
-
- if (expandedSpace) {
- return (
- <ExpandedSpace
- spaceId={expandedSpace}
- back={() => setExpandedSpace(null)}
- // close={() => setExpandedSpace(null)}
- />
- );
- }
-
- return (
- <div className="text-rgray-11 flex w-full flex-col items-start py-8 text-left">
- <div className="w-full px-8">
- <h1 className="w-full text-2xl">Your Memories</h1>
- <InputWithIcon
- placeholder="Search"
- icon={
- searchLoading ? (
- <Loader className="text-rgray-11 h-5 w-5 animate-spin opacity-50" />
- ) : (
- <Search className="text-rgray-11 h-5 w-5 opacity-50" />
- )
- }
- className="bg-rgray-4 mt-2 w-full"
- value={searchQuery}
- onChange={(e) => setSearcyQuery(e.target.value)}
- />
- </div>
- <div className="mt-2 flex w-full px-8">
- <AddMemoryModal type={addMemoryState}>
- <DropdownMenu open={isDropdownOpen} onOpenChange={setIsDropdownOpen}>
- <DropdownMenuTrigger asChild>
- <button className="focus-visible:bg-rgray-4 focus-visible:ring-rgray-7 hover:bg-rgray-4 ml-auto flex items-center justify-center rounded-md px-3 py-2 transition focus-visible:outline-none focus-visible:ring-2">
- <Plus className="mr-2 h-5 w-5" />
- Add
- </button>
- </DropdownMenuTrigger>
- <DropdownMenuContent onCloseAutoFocus={(e) => e.preventDefault()}>
- <DialogTrigger className="block w-full">
- <DropdownMenuItem
- onClick={() => {
- setAddMemoryState("page");
- }}
- >
- <Sparkles className="mr-2 h-4 w-4" />
- Page to Memory
- </DropdownMenuItem>
- </DialogTrigger>
- <DialogTrigger className="block w-full">
- <DropdownMenuItem
- onClick={() => {
- setAddMemoryState("note");
- }}
- >
- <Text className="mr-2 h-4 w-4" />
- Note
- </DropdownMenuItem>
- </DialogTrigger>
- <DialogTrigger className="block w-full">
- <DropdownMenuItem
- onClick={() => {
- setAddMemoryState("space");
- }}
- >
- <SpaceIcon className="mr-2 h-4 w-4" />
- Space
- </DropdownMenuItem>
- </DialogTrigger>
- </DropdownMenuContent>
- </DropdownMenu>
- </AddMemoryModal>
- </div>
- <div
- ref={parent}
- className="grid w-full grid-flow-row grid-cols-3 gap-1 px-2 py-5"
- >
- {query.trim().length > 0 ? (
- <>
- {searchResults.map(({ type, space, memory }, i) => (
- <>
- {type === "memory" && (
- <MemoryItem
- {...memory!}
- key={i}
- onDelete={() => {
- setSearchResults((prev) =>
- prev.filter((i) => i.memory?.id !== memory.id),
- );
- }}
- />
- )}
- {type === "space" && (
- <SpaceItem
- {...space!}
- key={i}
- onDelete={() => {
- setSearchResults((prev) =>
- prev.filter((i) => i.space?.id !== space.id),
- );
- deleteSpace(space.id);
- }}
- />
- )}
- </>
- ))}
- </>
- ) : (
- <>
- {spaces.map((space) => (
- <SpaceItem
- onDelete={() => deleteSpace(space.id)}
- key={space.id}
- onClick={() => setExpandedSpace(space.id)}
- {...space}
- />
- ))}
- {freeMemories.map((m) => (
- <MemoryItem {...m} key={m.id} />
- ))}
- </>
- )}
- </div>
- </div>
- );
-}
-
-const SpaceExitVariant: Variant = {
- opacity: 0,
- scale: 0,
- borderRadius: "50%",
- background: "var(--gray-1)",
- transition: {
- duration: 0.2,
- },
-};
-
-export function MemoryItem(
- props: StoredContent & {
- onDelete?: () => void;
- removeFromSpace?: () => Promise<void>;
- },
-) {
- const { id, title, image, type, url, onDelete, removeFromSpace } = props;
-
- const { deleteMemory } = useMemory();
-
- const name = title
- ? title.length > 10
- ? title.slice(0, 10) + "..."
- : title
- : "<no title>";
-
- const [isDialogOpen, setIsDialogOpen] = useState(false);
-
- const [moreDropdownOpen, setMoreDropdownOpen] = useState(false);
-
- const touchEventProps = useTouchHold({
- onHold() {
- setMoreDropdownOpen(true);
- },
- });
- return (
- <Dialog
- open={type === "note" ? isDialogOpen : false}
- onOpenChange={setIsDialogOpen}
- >
- <div
- {...touchEventProps}
- className="hover:bg-rgray-2 has-[[data-state='true']]:bg-rgray-2 has-[[data-space-text]:focus-visible]:bg-rgray-2 has-[[data-space-text]:focus-visible]:ring-rgray-7 [&:has-[[data-space-text]:focus-visible]>[data-more-button]]:opacity-100 relative flex cursor-pointer select-none flex-col-reverse items-center justify-center rounded-md p-2 pb-4 text-center font-normal ring-transparent transition has-[[data-space-text]:focus-visible]:outline-none has-[[data-space-text]:focus-visible]:ring-2 md:has-[[data-state='true']]:bg-transparent [&:hover>[data-more-button]]:opacity-100"
- >
- {type === "note" ? (
- <DialogTrigger asChild>
- <button data-space-text className="focus-visible:outline-none">
- {name}
- </button>
- </DialogTrigger>
- ) : (
- <button
- onClick={() => window.open(url)}
- data-space-text
- className="focus-visible:outline-none"
- >
- {name}
- </button>
- )}
-
- {type === "page" ? (
- <PageMoreButton
- isOpen={moreDropdownOpen}
- setIsOpen={setMoreDropdownOpen}
- removeFromSpace={removeFromSpace}
- onDelete={() => {
- deleteMemory(id);
- onDelete?.();
- }}
- url={url}
- />
- ) : type === "note" ? (
- <NoteMoreButton
- isOpen={moreDropdownOpen}
- setIsOpen={setMoreDropdownOpen}
- removeFromSpace={removeFromSpace}
- onEdit={() => setIsDialogOpen(true)}
- onDelete={() => {
- deleteMemory(id);
- onDelete?.();
- }}
- />
- ) : null}
-
- <div className="flex h-24 w-24 items-center justify-center">
- {type === "page" ? (
- <img
- onClick={() => window.open(url)}
- className="h-16 w-16"
- id={id.toString()}
- src={image!}
- onError={(e) => {
- (e.target as HTMLImageElement).src =
- "/icons/white_without_bg.png";
- }}
- />
- ) : type === "note" ? (
- <Text onClick={() => setIsDialogOpen(true)} className="h-16 w-16" />
- ) : (
- <></>
- )}
- </div>
- </div>
- <DialogContent className="w-max max-w-[auto]">
- <NoteEdit
- onDelete={onDelete}
- closeDialog={() => setIsDialogOpen(false)}
- memory={props}
- />
- </DialogContent>
- </Dialog>
- );
-}
-
-export function SpaceItem({
- name,
- id,
- onDelete,
- onClick,
-}: StoredSpace & { onDelete: () => void; onClick?: () => void }) {
- const { cachedMemories } = useMemory();
-
- const [itemRef, animateItem] = useAnimate();
- const { width } = useViewport();
-
- const [moreDropdownOpen, setMoreDropdownOpen] = useState(false);
-
- const touchEventProps = useTouchHold({
- onHold() {
- setMoreDropdownOpen(true);
- },
- });
-
- const spaceMemories = useMemo(() => {
- return cachedMemories.filter((m) => m.space === id);
- }, [cachedMemories]);
-
- const _name = name.length > 10 ? name.slice(0, 10) + "..." : name;
-
- return (
- <motion.div
- ref={itemRef}
- {...touchEventProps}
- className="hover:bg-rgray-2 has-[[data-state='true']]:bg-rgray-2 has-[[data-space-text]:focus-visible]:bg-rgray-2 has-[[data-space-text]:focus-visible]:ring-rgray-7 [&:has-[[data-space-text]:focus-visible]>[data-more-button]]:opacity-100 relative flex select-none flex-col-reverse items-center justify-center rounded-md p-2 pb-4 text-center font-normal ring-transparent transition has-[[data-space-text]:focus-visible]:outline-none has-[[data-space-text]:focus-visible]:ring-2 md:has-[[data-state='true']]:bg-transparent [&:hover>[data-more-button]]:opacity-100"
- >
- <button
- onClick={onClick}
- data-space-text
- className="focus-visible:outline-none"
- >
- {_name}
- </button>
- <SpaceMoreButton
- isOpen={moreDropdownOpen}
- setIsOpen={setMoreDropdownOpen}
- onEdit={onClick}
- onDelete={() => {
- onDelete();
- return;
- if (!itemRef.current || width < 768) {
- onDelete();
- return;
- }
- // 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 initial = {
- // x: scopeRect.left + scopeRect.width / 2,
- // y: scopeRect.top + scopeRect.height / 2,
- // };
- // const delta = {
- // x:
- // trashRect.left +
- // trashRect.width / 2 -
- // scopeRect.left +
- // scopeRect.width / 2,
- // y:
- // trashRect.top +
- // trashRect.height / 4 -
- // scopeRect.top +
- // scopeRect.height / 2,
- // };
- // const end = {
- // x: trashRect.left + trashRect.width / 2,
- // y: trashRect.top + trashRect.height / 4,
- // };
- // el.style.offsetPath = `path('M ${initial.x} ${initial.y} Q ${delta.x * 0.01} ${delta.y * 0.01} ${end.x} ${end.y}`;
- // animateItem(itemRef.current, SpaceExitVariant, {
- // duration: 0.2,
- // }).then(() => {
- // itemRef.current.style.scale = "0";
- // onDelete();
- // });
- // document.body.appendChild(el);
- // el.animate(
- // {
- // 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",
- // },
- // );
- // el.animate(
- // {
- // offsetDistance: ["0%", "100%"],
- // },
- // {
- // duration: 2000,
- // 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" },
- // ).onfinish = () => {
- // el.remove();
- // };
- // };
- }}
- />
- {spaceMemories.length > 2 ? (
- <MemoryWithImages3
- onClick={onClick}
- className="h-24 w-24"
- id={id.toString()}
- images={
- spaceMemories
- .map((c) => (c.type === "note" ? "/note.svg" : c.image))
- .reverse() as string[]
- }
- />
- ) : spaceMemories.length > 1 ? (
- <MemoryWithImages2
- onClick={onClick}
- className="h-24 w-24"
- id={id.toString()}
- images={
- spaceMemories
- .map((c) => (c.type === "note" ? "/note.svg" : c.image))
- .reverse() as string[]
- }
- />
- ) : spaceMemories.length === 1 ? (
- <MemoryWithImage
- onClick={onClick}
- className="h-24 w-24"
- id={id.toString()}
- image={
- spaceMemories[0].type === "note"
- ? "/note.svg"
- : spaceMemories[0].image!
- }
- />
- ) : (
- <div
- onClick={onClick}
- className="bg-rgray-4 shadow- h-24 w-24 scale-50 rounded-full opacity-30"
- ></div>
- )}
- </motion.div>
- );
-}
-
-export function SpaceMoreButton({
- onDelete,
- isOpen,
- setIsOpen,
- onEdit,
-}: {
- onDelete?: () => void;
- isOpen?: boolean;
- onEdit?: () => void;
- setIsOpen?: (open: boolean) => void;
-}) {
- return (
- <DeleteConfirmation onDelete={onDelete} trigger={false}>
- <DropdownMenu open={isOpen} onOpenChange={setIsOpen}>
- <DropdownMenuTrigger asChild>
- <button
- data-more-button
- className="hover:bg-rgray-3 focus-visible:bg-rgray-3 focus-visible:ring-rgray-7 absolute right-2 top-2 scale-0 rounded-md p-1 opacity-0 ring-transparent transition focus-visible:opacity-100 focus-visible:outline-none focus-visible:ring-2 md:block md:scale-100 md:bg-transparent"
- >
- <MoreHorizontal className="text-rgray-11 h-5 w-5" />
- </button>
- </DropdownMenuTrigger>
- <DropdownMenuContent align="start">
- <DropdownMenuItem onClick={onEdit}>
- <Edit3 className="mr-2 h-4 w-4" strokeWidth={1.5} />
- Edit
- </DropdownMenuItem>
- <DialogTrigger asChild>
- <DropdownMenuItem className="focus:bg-red-100 focus:text-red-400">
- <Trash2 className="mr-2 h-4 w-4" strokeWidth={1.5} />
- Delete
- </DropdownMenuItem>
- </DialogTrigger>
- </DropdownMenuContent>
- </DropdownMenu>
- </DeleteConfirmation>
- );
-}
-
-export function PageMoreButton({
- onDelete,
- isOpen,
- setIsOpen,
- url,
- removeFromSpace,
-}: {
- onDelete?: () => void;
- isOpen?: boolean;
- url: string;
- setIsOpen?: (open: boolean) => void;
- removeFromSpace?: () => Promise<void>;
-}) {
- return (
- <DeleteConfirmation onDelete={onDelete} trigger={false}>
- <DropdownMenu open={isOpen} onOpenChange={setIsOpen}>
- <DropdownMenuTrigger asChild>
- <button
- data-more-button
- className="hover:bg-rgray-3 focus-visible:bg-rgray-3 focus-visible:ring-rgray-7 absolute right-2 top-2 scale-0 rounded-md p-1 opacity-0 ring-transparent transition focus-visible:opacity-100 focus-visible:outline-none focus-visible:ring-2 md:block md:scale-100 md:bg-transparent"
- >
- <MoreHorizontal className="text-rgray-11 h-5 w-5" />
- </button>
- </DropdownMenuTrigger>
- <DropdownMenuContent align="start">
- <DropdownMenuItem onClick={() => window.open(url)}>
- <ArrowUpRight
- className="mr-2 h-4 w-4 scale-125"
- strokeWidth={1.5}
- />
- Open
- </DropdownMenuItem>
- {removeFromSpace && (
- <DropdownMenuItem onClick={removeFromSpace}>
- <Minus className="mr-2 h-4 w-4" strokeWidth={1.5} />
- Remove from space
- </DropdownMenuItem>
- )}
- <DialogTrigger asChild>
- <DropdownMenuItem className="focus:bg-red-100 focus:text-red-400">
- <Trash2 className="mr-2 h-4 w-4" strokeWidth={1.5} />
- Delete
- </DropdownMenuItem>
- </DialogTrigger>
- </DropdownMenuContent>
- </DropdownMenu>
- </DeleteConfirmation>
- );
-}
-
-export function NoteMoreButton({
- onDelete,
- isOpen,
- setIsOpen,
- onEdit,
- removeFromSpace,
-}: {
- onDelete?: () => void;
- isOpen?: boolean;
- onEdit?: () => void;
- setIsOpen?: (open: boolean) => void;
- removeFromSpace?: () => Promise<void>;
-}) {
- return (
- <DeleteConfirmation onDelete={onDelete} trigger={false}>
- <DropdownMenu open={isOpen} onOpenChange={setIsOpen}>
- <DropdownMenuTrigger asChild>
- <button
- data-more-button
- className="hover:bg-rgray-3 focus-visible:bg-rgray-3 focus-visible:ring-rgray-7 absolute right-2 top-2 scale-0 rounded-md p-1 opacity-0 ring-transparent transition focus-visible:opacity-100 focus-visible:outline-none focus-visible:ring-2 md:block md:scale-100 md:bg-transparent"
- >
- <MoreHorizontal className="text-rgray-11 h-5 w-5" />
- </button>
- </DropdownMenuTrigger>
- <DropdownMenuContent align="start">
- <DropdownMenuItem onClick={onEdit}>
- <Edit3 className="mr-2 h-4 w-4" strokeWidth={1.5} />
- Edit
- </DropdownMenuItem>
- {removeFromSpace && (
- <DropdownMenuItem onClick={removeFromSpace}>
- <Minus className="mr-2 h-4 w-4" strokeWidth={1.5} />
- Remove from space
- </DropdownMenuItem>
- )}
- <DialogTrigger asChild>
- <DropdownMenuItem className="focus:bg-red-100 focus:text-red-400">
- <Trash2 className="mr-2 h-4 w-4" strokeWidth={1.5} />
- Delete
- </DropdownMenuItem>
- </DialogTrigger>
- </DropdownMenuContent>
- </DropdownMenu>
- </DeleteConfirmation>
- );
-}
-
-export function AddMemoryModal({
- type,
- children,
- defaultSpaces,
- onAdd,
- data,
-}: {
- type: "page" | "note" | "space" | "existing-memory" | null;
- children?: React.ReactNode | React.ReactNode[];
- defaultSpaces?: number[];
- data?: {
- space?: {
- title: string;
- id: number;
- };
- fromSpaces?: number[];
- notInSpaces?: number[];
- };
- onAdd?: (data?: StoredSpace | StoredContent | StoredContent[]) => void;
-}) {
- const [isDialogOpen, setIsDialogOpen] = useState(false);
-
- return (
- <Dialog open={isDialogOpen} onOpenChange={setIsDialogOpen}>
- {children}
- <DialogContent
- onOpenAutoFocus={(e) => {
- e.preventDefault();
- const novel = document.querySelector('[contenteditable="true"]') as
- | HTMLDivElement
- | undefined;
- if (novel) {
- novel.autofocus = false;
- novel.onfocus = () => {
- (
- document.querySelector("[data-modal-autofocus]") as
- | HTMLInputElement
- | undefined
- )?.focus();
- novel.onfocus = null;
- };
- }
- (
- document.querySelector("[data-modal-autofocus]") as
- | HTMLInputElement
- | undefined
- )?.focus();
- }}
- className="w-max max-w-[auto]"
- >
- {type === "page" ? (
- <AddMemoryPage
- onAdd={onAdd}
- defaultSpaces={defaultSpaces}
- closeDialog={() => setIsDialogOpen(false)}
- />
- ) : type === "note" ? (
- <NoteAddPage
- onAdd={onAdd}
- defaultSpaces={defaultSpaces}
- closeDialog={() => setIsDialogOpen(false)}
- />
- ) : type === "space" ? (
- <SpaceAddPage
- onAdd={onAdd}
- closeDialog={() => setIsDialogOpen(false)}
- />
- ) : type === "existing-memory" ? (
- <AddExistingMemoryToSpace
- onAdd={onAdd}
- fromSpaces={data?.fromSpaces}
- notInSpaces={data?.notInSpaces}
- space={data!.space!}
- closeDialog={() => setIsDialogOpen(false)}
- />
- ) : (
- <></>
- )}
- </DialogContent>
- </Dialog>
- );
-}
diff --git a/apps/web/src/components/Sidebar/SettingsTab.tsx b/apps/web/src/components/Sidebar/SettingsTab.tsx
deleted file mode 100644
index 31b8380d..00000000
--- a/apps/web/src/components/Sidebar/SettingsTab.tsx
+++ /dev/null
@@ -1,99 +0,0 @@
-import { Box, LogOut } from "lucide-react";
-import { signOut, useSession } from "next-auth/react";
-import { useEffect, useState } from "react";
-
-export function SettingsTab({ open }: { open: boolean }) {
- const { data: session } = useSession();
-
- const [tweetStat, setTweetStat] = useState<[number, number] | null>();
- const [memoryStat, setMemoryStat] = useState<[number, number] | null>();
-
- const [loading, setLoading] = useState(true);
-
- useEffect(() => {
- fetch("/api/getCount").then(async (resp) => {
- const data = (await resp.json()) as any;
- setTweetStat([data.tweetsCount, data.tweetsLimit]);
- setMemoryStat([data.pageCount, data.pageLimit]);
- setLoading(false);
- });
- }, [open]);
-
- return (
- <div className="flex h-full w-full flex-col items-start py-3 text-left font-normal text-black md:py-8">
- <div className="w-full px-6">
- <h1 className="w-full text-2xl font-medium">Settings</h1>
- <div className="mt-5 grid w-full grid-cols-3 gap-1">
- <img
- className="rounded-full"
- src={session?.user?.image ?? "/icons/white_without_bg.png"}
- onError={(e) => {
- (e.target as HTMLImageElement).src =
- "/icons/white_without_bg.png";
- }}
- />
- <div className="col-span-2 flex flex-col items-start justify-center">
- <h1 className="text-xl font-medium">{session?.user?.name}</h1>
- <span>{session?.user?.email}</span>
- <button
- onClick={() => signOut()}
- className="bg-rgray-4 hover:bg-rgray-5 focus-visible:bg-rgray-5 focus-visible:ring-rgray-7 relative mt-auto flex items-center justify-center gap-2 rounded-md px-4 py-2 text-white ring-transparent transition focus-visible:outline-none focus-visible:ring-2 disabled:cursor-not-allowed disabled:opacity-70"
- >
- <LogOut className="h-4 w-4" />
- Logout
- </button>
- </div>
- </div>
- </div>
- <div className="border-rgray-5 mt-auto w-full px-8 pt-8">
- <h1 className="flex w-full items-center gap-2 text-xl">
- <Box className="h-6 w-6" />
- Storage
- </h1>
- {loading ? (
- <div className="my-5 flex w-full flex-col items-center justify-center gap-5">
- <div className="bg-rgray-5 h-6 w-full animate-pulse rounded-md text-lg"></div>
- <div className="bg-rgray-5 h-6 w-full animate-pulse rounded-md text-lg"></div>
- </div>
- ) : (
- <>
- <div className="my-5">
- <h2 className="text-md flex w-full items-center justify-between">
- Memories
- <div className="bg-rgray-4 flex rounded-md px-2 py-2 text-xs text-white/70">
- {memoryStat?.join("/")}
- </div>
- </h2>
- <div className="mt-2 h-5 w-full overflow-hidden rounded-full bg-stone-400">
- <div
- style={{
- width: `${((memoryStat?.[0] ?? 0) / (memoryStat?.[1] ?? 100)) * 100}%`,
- minWidth: memoryStat?.[0] ?? 0 > 0 ? "5%" : "0%",
- }}
- className="bg-rgray-5 h-full rounded-full"
- />
- </div>
- </div>
- <div className="my-5">
- <h2 className="text-md flex w-full items-center justify-between">
- Tweets
- <div className="bg-rgray-4 flex rounded-md px-2 py-2 text-xs text-white/70">
- {tweetStat?.join("/")}
- </div>
- </h2>
- <div className="mt-2 h-5 w-full overflow-hidden rounded-full bg-stone-400">
- <div
- style={{
- width: `${((tweetStat?.[0] ?? 0) / (tweetStat?.[1] ?? 100)) * 100}%`,
- minWidth: tweetStat?.[0] ?? 0 > 0 ? "5%" : "0%",
- }}
- className="h-full rounded-full bg-white"
- />
- </div>
- </div>
- </>
- )}
- </div>
- </div>
- );
-}
diff --git a/apps/web/src/components/Sidebar/index.tsx b/apps/web/src/components/Sidebar/index.tsx
deleted file mode 100644
index ae757afe..00000000
--- a/apps/web/src/components/Sidebar/index.tsx
+++ /dev/null
@@ -1,172 +0,0 @@
-"use client";
-import { MemoryIcon } from "../../assets/Memories";
-import React, { useEffect, useState } from "react";
-import { AnimatePresence, motion } from "framer-motion";
-import { signOut, useSession } from "next-auth/react";
-import MessagePoster from "@/app/MessagePoster";
-import Link from "next/link";
-import { SettingsTab } from "./SettingsTab";
-import { Avatar, AvatarImage } from "@radix-ui/react-avatar";
-import { AvatarFallback } from "../ui/avatar";
-
-export type MenuItem = {
- icon: React.ReactNode | React.ReactNode[];
- label: string;
- content?: React.ReactNode;
- labelDisplay?: React.ReactNode;
-};
-
-export default function Sidebar({
- selectChange,
- jwt,
-}: {
- selectChange?: (selectedItem: string | null) => void;
- jwt: string;
-}) {
- const { data: session } = useSession();
-
- const [selectedItem, setSelectedItem] = useState<string | null>(null);
-
- const menuItemsTop: Array<MenuItem> = [];
-
- const menuItemsBottom: Array<MenuItem> = [
- {
- label: "Settings",
- content: <SettingsTab open={selectedItem !== null} />,
- icon: <></>,
- },
- ];
-
- const menuItems = [...menuItemsTop, ...menuItemsBottom];
-
- const Subbar = menuItems.find((i) => i.label === selectedItem)?.content ?? (
- <></>
- );
-
- useEffect(() => {
- void selectChange?.(selectedItem);
- }, [selectedItem]);
-
- return (
- <div className="relative hidden h-screen max-h-screen w-max flex-col items-center text-sm font-light md:flex">
- <div
- className={`relative z-[50] flex h-full w-full flex-col items-center justify-center border-r bg-stone-100 px-2 py-5 `}
- >
- <Link
- data-state-on={selectedItem === "Memories"}
- href="/"
- onClick={() => setSelectedItem(null)}
- className="focus-visible:ring-rgray-7 relative z-[100] flex w-full flex-col items-center justify-center rounded-md px-3 py-3 opacity-80 ring-2 ring-transparent transition hover:bg-stone-300 hover:opacity-100 focus-visible:opacity-100 focus-visible:outline-none"
- >
- <MemoryIcon className="h-12 w-12" />
- <span className="text-black">Memories</span>
- </Link>
-
- <div className="mt-auto" />
-
- <MenuItem
- item={{
- label: "Settings",
- icon: (
- <svg
- xmlns="http://www.w3.org/2000/svg"
- fill="white"
- viewBox="0 0 24 24"
- strokeWidth={0.5}
- stroke="black"
- className="h-10 w-10"
- >
- <path
- strokeLinecap="round"
- strokeLinejoin="round"
- d="M9.594 3.94c.09-.542.56-.94 1.11-.94h2.593c.55 0 1.02.398 1.11.94l.213 1.281c.063.374.313.686.645.87.074.04.147.083.22.127.325.196.72.257 1.075.124l1.217-.456a1.125 1.125 0 0 1 1.37.49l1.296 2.247a1.125 1.125 0 0 1-.26 1.431l-1.003.827c-.293.241-.438.613-.43.992a7.723 7.723 0 0 1 0 .255c-.008.378.137.75.43.991l1.004.827c.424.35.534.955.26 1.43l-1.298 2.247a1.125 1.125 0 0 1-1.369.491l-1.217-.456c-.355-.133-.75-.072-1.076.124a6.47 6.47 0 0 1-.22.128c-.331.183-.581.495-.644.869l-.213 1.281c-.09.543-.56.94-1.11.94h-2.594c-.55 0-1.019-.398-1.11-.94l-.213-1.281c-.062-.374-.312-.686-.644-.87a6.52 6.52 0 0 1-.22-.127c-.325-.196-.72-.257-1.076-.124l-1.217.456a1.125 1.125 0 0 1-1.369-.49l-1.297-2.247a1.125 1.125 0 0 1 .26-1.431l1.004-.827c.292-.24.437-.613.43-.991a6.932 6.932 0 0 1 0-.255c.007-.38-.138-.751-.43-.992l-1.004-.827a1.125 1.125 0 0 1-.26-1.43l1.297-2.247a1.125 1.125 0 0 1 1.37-.491l1.216.456c.356.133.751.072 1.076-.124.072-.044.146-.086.22-.128.332-.183.582-.495.644-.869l.214-1.28Z"
- />
- <path
- strokeLinecap="round"
- strokeLinejoin="round"
- d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z"
- />
- </svg>
- ),
- content: <SettingsTab open={selectedItem !== null} />,
- }}
- selectedItem={selectedItem}
- setSelectedItem={setSelectedItem}
- />
- {/* <MessagePoster jwt={jwt} /> */}
- <div className="mt-4 flex cursor-pointer flex-col items-center justify-center gap-2 rounded-b-md border-t border-stone-600 px-2 py-3 pt-4 text-black hover:bg-stone-300 hover:opacity-100">
- <Avatar>
- <AvatarImage
- className="h-10 w-10 rounded-full"
- src={session?.user?.image!}
- alt="Profile picture"
- />
- <AvatarFallback>
- {session?.user?.name?.split(" ").map((n) => n[0])}{" "}
- </AvatarFallback>
- </Avatar>
- <span>{session?.user?.name?.split(" ")[0]}</span>
- </div>
- </div>
- <AnimatePresence>
- {selectedItem && <SubSidebar>{Subbar}</SubSidebar>}
- </AnimatePresence>
- </div>
- );
-}
-
-const MenuItem = ({
- item: { icon, label, labelDisplay },
- selectedItem,
- setSelectedItem,
- ...props
-}: {
- item: MenuItem;
- selectedItem: string | null;
- setSelectedItem: React.Dispatch<React.SetStateAction<string | null>>;
-}) => {
- const handleClick = () =>
- setSelectedItem((prev) => (prev === label ? null : label));
-
- return (
- <button
- data-state-on={selectedItem === label}
- onClick={handleClick}
- className="on:opacity-100 on:bg-stone-300 focus-visible:ring-rgray-7 relative z-[100] flex w-full flex-col items-center justify-center rounded-md px-3 py-3 text-black opacity-80 ring-2 ring-transparent transition hover:bg-stone-300 hover:opacity-100 focus-visible:opacity-100 focus-visible:outline-none"
- {...props}
- >
- {icon}
- <span className="">{labelDisplay ?? label}</span>
- </button>
- );
-};
-
-export function SubSidebar({ children }: { children?: React.ReactNode }) {
- return (
- <motion.div
- initial={{ opacity: 0, x: "-100%" }}
- animate={{ opacity: 1, x: 0 }}
- exit={{
- opacity: 0,
- x: "-100%",
- transition: { delay: 0.2 },
- }}
- transition={{
- duration: 0.2,
- }}
- className="absolute left-[100%] top-0 z-[10] hidden h-screen w-[30vw] items-start justify-center overflow-x-hidden border-r bg-stone-100 font-light md:flex"
- >
- <motion.div
- initial={{ opacity: 0 }}
- animate={{ opacity: 1 }}
- exit={{ opacity: 0, transition: { delay: 0 } }}
- transition={{
- delay: 0.2,
- }}
- className="z-[10] flex h-full w-full min-w-full flex-col items-center opacity-0"
- >
- <AnimatePresence>{children}</AnimatePresence>
- </motion.div>
- </motion.div>
- );
-}
diff --git a/apps/web/src/components/WordMark.tsx b/apps/web/src/components/WordMark.tsx
deleted file mode 100644
index eb55647c..00000000
--- a/apps/web/src/components/WordMark.tsx
+++ /dev/null
@@ -1,12 +0,0 @@
-import { cn } from "@/lib/utils";
-import React from "react";
-
-function WordMark({ className }: { className?: string }) {
- return (
- <span className={cn(`text-xl font-bold tracking-tight ${className}`)}>
- smort.
- </span>
- );
-}
-
-export default WordMark;
diff --git a/apps/web/src/components/dev/SessionProviderWrapper.tsx b/apps/web/src/components/dev/SessionProviderWrapper.tsx
deleted file mode 100644
index 71f77886..00000000
--- a/apps/web/src/components/dev/SessionProviderWrapper.tsx
+++ /dev/null
@@ -1,12 +0,0 @@
-import { SessionProvider } from "next-auth/react";
-import React from "react";
-
-function SessionProviderWrapper({ children }: { children: React.ReactNode }) {
- if (typeof window === "undefined") {
- return <>{children}</>;
- } else {
- return <SessionProvider>{children}</SessionProvider>;
- }
-}
-
-export default SessionProviderWrapper;
diff --git a/apps/web/src/components/dev/tailwindindicator.tsx b/apps/web/src/components/dev/tailwindindicator.tsx
deleted file mode 100644
index fd70276d..00000000
--- a/apps/web/src/components/dev/tailwindindicator.tsx
+++ /dev/null
@@ -1,16 +0,0 @@
-export function TailwindIndicator() {
- if (process.env.NODE_ENV === "production") return null;
-
- return (
- <div>
- <div className="fixed bottom-1 left-1 z-[999] flex size-6 items-center justify-center rounded-full border-2 bg-white p-3 font-mono text-xs text-black">
- <div className="block sm:hidden">xs</div>
- <div className="hidden sm:block md:hidden">sm</div>
- <div className="hidden md:block lg:hidden">md</div>
- <div className="hidden lg:block xl:hidden">lg</div>
- <div className="hidden xl:block 2xl:hidden">xl</div>
- <div className="hidden 2xl:block">2xl</div>
- </div>
- </div>
- );
-}
diff --git a/apps/web/src/components/ui/avatar.tsx b/apps/web/src/components/ui/avatar.tsx
deleted file mode 100644
index b36abf28..00000000
--- a/apps/web/src/components/ui/avatar.tsx
+++ /dev/null
@@ -1,50 +0,0 @@
-"use client";
-
-import * as React from "react";
-import * as AvatarPrimitive from "@radix-ui/react-avatar";
-
-import { cn } from "@/lib/utils";
-
-const Avatar = React.forwardRef<
- React.ElementRef<typeof AvatarPrimitive.Root>,
- React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Root>
->(({ className, ...props }, ref) => (
- <AvatarPrimitive.Root
- ref={ref}
- className={cn(
- "relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full",
- className,
- )}
- {...props}
- />
-));
-Avatar.displayName = AvatarPrimitive.Root.displayName;
-
-const AvatarImage = React.forwardRef<
- React.ElementRef<typeof AvatarPrimitive.Image>,
- React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Image>
->(({ className, ...props }, ref) => (
- <AvatarPrimitive.Image
- ref={ref}
- className={cn("aspect-square h-full w-full", className)}
- {...props}
- />
-));
-AvatarImage.displayName = AvatarPrimitive.Image.displayName;
-
-const AvatarFallback = React.forwardRef<
- React.ElementRef<typeof AvatarPrimitive.Fallback>,
- React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Fallback>
->(({ className, ...props }, ref) => (
- <AvatarPrimitive.Fallback
- ref={ref}
- className={cn(
- "flex h-full w-full items-center justify-center rounded-full bg-gray-100",
- className,
- )}
- {...props}
- />
-));
-AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName;
-
-export { Avatar, AvatarImage, AvatarFallback };
diff --git a/apps/web/src/components/ui/badge.tsx b/apps/web/src/components/ui/badge.tsx
deleted file mode 100644
index fa390bec..00000000
--- a/apps/web/src/components/ui/badge.tsx
+++ /dev/null
@@ -1,36 +0,0 @@
-import * as React from "react";
-import { cva, type VariantProps } from "class-variance-authority";
-
-import { cn } from "@/lib/utils";
-
-const badgeVariants = cva(
- "inline-flex items-center rounded-full border border-gray-200 px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-gray-950 focus:ring-offset-2",
- {
- variants: {
- variant: {
- default:
- "border-transparent bg-primary text-primary-foreground hover:bg-primary/80",
- secondary:
- "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
- destructive:
- "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
- outline: "text-foreground",
- },
- },
- defaultVariants: {
- variant: "default",
- },
- },
-);
-
-export interface BadgeProps
- extends React.HTMLAttributes<HTMLDivElement>,
- VariantProps<typeof badgeVariants> {}
-
-function Badge({ className, variant, ...props }: BadgeProps) {
- return (
- <div className={cn(badgeVariants({ variant }), className)} {...props} />
- );
-}
-
-export { Badge, badgeVariants };
diff --git a/apps/web/src/components/ui/button.tsx b/apps/web/src/components/ui/button.tsx
deleted file mode 100644
index 24fa903e..00000000
--- a/apps/web/src/components/ui/button.tsx
+++ /dev/null
@@ -1,56 +0,0 @@
-import * as React from "react";
-import { Slot } from "@radix-ui/react-slot";
-import { cva, type VariantProps } from "class-variance-authority";
-
-import { cn } from "@/lib/utils";
-
-const buttonVariants = cva(
- "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-white transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-gray-950 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 dark:ring-offset-gray-950 dark:focus-visible:ring-gray-300",
- {
- variants: {
- variant: {
- default: "bg-primary text-primary-foreground hover:bg-primary/90",
- destructive:
- "bg-destructive text-destructive-foreground hover:bg-destructive/90",
- outline:
- "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
- secondary:
- "bg-secondary text-secondary-foreground hover:bg-secondary/80",
- ghost: "hover:bg-accent hover:text-accent-foreground",
- link: "text-primary underline-offset-4 hover:underline",
- },
- size: {
- default: "h-10 px-4 py-2",
- sm: "h-9 rounded-md px-3",
- lg: "h-11 rounded-md px-8",
- icon: "h-10 w-10",
- },
- },
- defaultVariants: {
- variant: "default",
- size: "default",
- },
- },
-);
-
-export interface ButtonProps
- extends React.ButtonHTMLAttributes<HTMLButtonElement>,
- VariantProps<typeof buttonVariants> {
- asChild?: boolean;
-}
-
-const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
- ({ className, variant, size, asChild = false, ...props }, ref) => {
- const Comp = asChild ? Slot : "button";
- return (
- <button
- className={cn(buttonVariants({ variant, size, className }))}
- ref={ref}
- {...props}
- />
- );
- },
-);
-Button.displayName = "Button";
-
-export { Button, buttonVariants };
diff --git a/apps/web/src/components/ui/card.tsx b/apps/web/src/components/ui/card.tsx
deleted file mode 100644
index e98d500c..00000000
--- a/apps/web/src/components/ui/card.tsx
+++ /dev/null
@@ -1,86 +0,0 @@
-import * as React from "react";
-
-import { cn } from "@/lib/utils";
-
-const Card = React.forwardRef<
- HTMLDivElement,
- React.HTMLAttributes<HTMLDivElement>
->(({ className, ...props }, ref) => (
- <div
- ref={ref}
- className={cn(
- "rounded-lg border border-gray-200 bg-white text-gray-950 shadow-sm dark:border-gray-800 dark:bg-gray-950 dark:text-gray-50",
- className,
- )}
- {...props}
- />
-));
-Card.displayName = "Card";
-
-const CardHeader = React.forwardRef<
- HTMLDivElement,
- React.HTMLAttributes<HTMLDivElement>
->(({ className, ...props }, ref) => (
- <div
- ref={ref}
- className={cn("flex flex-col space-y-1.5 p-6", className)}
- {...props}
- />
-));
-CardHeader.displayName = "CardHeader";
-
-const CardTitle = React.forwardRef<
- HTMLParagraphElement,
- React.HTMLAttributes<HTMLHeadingElement>
->(({ className, ...props }, ref) => (
- <h3
- ref={ref}
- className={cn(
- "text-2xl font-semibold leading-none tracking-tight",
- className,
- )}
- {...props}
- />
-));
-CardTitle.displayName = "CardTitle";
-
-const CardDescription = React.forwardRef<
- HTMLParagraphElement,
- React.HTMLAttributes<HTMLParagraphElement>
->(({ className, ...props }, ref) => (
- <p
- ref={ref}
- className={cn("text-sm text-gray-500 dark:text-gray-400", className)}
- {...props}
- />
-));
-CardDescription.displayName = "CardDescription";
-
-const CardContent = React.forwardRef<
- HTMLDivElement,
- React.HTMLAttributes<HTMLDivElement>
->(({ className, ...props }, ref) => (
- <div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
-));
-CardContent.displayName = "CardContent";
-
-const CardFooter = React.forwardRef<
- HTMLDivElement,
- React.HTMLAttributes<HTMLDivElement>
->(({ className, ...props }, ref) => (
- <div
- ref={ref}
- className={cn("flex items-center p-6 pt-0", className)}
- {...props}
- />
-));
-CardFooter.displayName = "CardFooter";
-
-export {
- Card,
- CardHeader,
- CardFooter,
- CardTitle,
- CardDescription,
- CardContent,
-};
diff --git a/apps/web/src/components/ui/command.tsx b/apps/web/src/components/ui/command.tsx
deleted file mode 100644
index afc2cf46..00000000
--- a/apps/web/src/components/ui/command.tsx
+++ /dev/null
@@ -1,161 +0,0 @@
-"use client";
-
-import * as React from "react";
-import { type DialogProps } from "@radix-ui/react-dialog";
-import { Command as CommandPrimitive } from "cmdk";
-import { Loader, Search } from "lucide-react";
-
-import { cn } from "@/lib/utils";
-import { Dialog, DialogContent } from "@/components/ui/dialog";
-
-const Command = React.forwardRef<
- React.ElementRef<typeof CommandPrimitive>,
- React.ComponentPropsWithoutRef<typeof CommandPrimitive>
->(({ className, ...props }, ref) => (
- <CommandPrimitive
- ref={ref}
- className={cn(
- "bg-rgray-3 text-rgray-11 flex h-full w-full flex-col overflow-hidden rounded-md focus-visible:outline-none [&>[cmdk-list-sizer]]:max-h-[250px] [&>[cmdk-list-sizer]]:overflow-y-scroll",
- className,
- )}
- {...props}
- />
-));
-Command.displayName = CommandPrimitive.displayName;
-
-interface CommandDialogProps extends DialogProps {}
-
-const CommandDialog = ({ children, ...props }: CommandDialogProps) => {
- return (
- <Dialog {...props}>
- <DialogContent className="overflow-hidden p-0 shadow-lg">
- <Command className="[&_[cmdk-group-heading]]:text-rgray-11 [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-group]]:px-2 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5">
- {children}
- </Command>
- </DialogContent>
- </Dialog>
- );
-};
-
-const CommandInput = React.forwardRef<
- React.ElementRef<typeof CommandPrimitive.Input>,
- React.ComponentPropsWithoutRef<typeof CommandPrimitive.Input> & {
- isSearching?: boolean;
- }
->(({ className, isSearching = false, ...props }, ref) => (
- <div
- className="border-rgray-6 flex items-center border-b px-3"
- cmdk-input-wrapper=""
- >
- {isSearching ? (
- <Loader className="shrink-9 mr-2 h-4 w-4 animate-spin opacity-50" />
- ) : (
- <Search className="mr-2 h-4 w-4 shrink-0 opacity-50" />
- )}
- <CommandPrimitive.Input
- ref={ref}
- className={cn(
- "placeholder:text-rgray-11/50 flex h-11 w-full rounded-md bg-transparent py-3 text-sm outline-none disabled:cursor-not-allowed disabled:opacity-50",
- className,
- )}
- {...props}
- />
- </div>
-));
-
-CommandInput.displayName = CommandPrimitive.Input.displayName;
-
-const CommandList = React.forwardRef<
- React.ElementRef<typeof CommandPrimitive.List>,
- React.ComponentPropsWithoutRef<typeof CommandPrimitive.List>
->(({ className, ...props }, ref) => (
- <CommandPrimitive.List
- ref={ref}
- className={cn("max-h-[300px] overflow-y-auto overflow-x-hidden", className)}
- {...props}
- />
-));
-
-CommandList.displayName = CommandPrimitive.List.displayName;
-
-const CommandEmpty = React.forwardRef<
- React.ElementRef<typeof CommandPrimitive.Empty>,
- React.ComponentPropsWithoutRef<typeof CommandPrimitive.Empty>
->((props, ref) => (
- <CommandPrimitive.Empty
- ref={ref}
- className="py-6 text-center text-sm"
- {...props}
- />
-));
-
-CommandEmpty.displayName = CommandPrimitive.Empty.displayName;
-
-const CommandGroup = React.forwardRef<
- React.ElementRef<typeof CommandPrimitive.Group>,
- React.ComponentPropsWithoutRef<typeof CommandPrimitive.Group>
->(({ className, ...props }, ref) => (
- <CommandPrimitive.Group
- ref={ref}
- className={cn(
- "text-rgray-12 [&_[cmdk-group-heading]]:text-rgray-11 overflow-hidden p-1 [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium",
- className,
- )}
- {...props}
- />
-));
-
-CommandGroup.displayName = CommandPrimitive.Group.displayName;
-
-const CommandSeparator = React.forwardRef<
- React.ElementRef<typeof CommandPrimitive.Separator>,
- React.ComponentPropsWithoutRef<typeof CommandPrimitive.Separator>
->(({ className, ...props }, ref) => (
- <CommandPrimitive.Separator
- ref={ref}
- className={cn("bg-rgray-3 -mx-1 h-px", className)}
- {...props}
- />
-));
-CommandSeparator.displayName = CommandPrimitive.Separator.displayName;
-
-const CommandItem = React.forwardRef<
- React.ElementRef<typeof CommandPrimitive.Item>,
- React.ComponentPropsWithoutRef<typeof CommandPrimitive.Item>
->(({ className, ...props }, ref) => (
- <CommandPrimitive.Item
- ref={ref}
- className={cn(
- "aria-selected:bg-rgray-5 aria-selected:text-rgray-12 relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm opacity-70 outline-none data-[disabled='true']:pointer-events-none data-[disabled='true']:opacity-50",
- className,
- )}
- {...props}
- />
-));
-
-CommandItem.displayName = CommandPrimitive.Item.displayName;
-
-const CommandShortcut = ({
- className,
- ...props
-}: React.HTMLAttributes<HTMLSpanElement>) => {
- return (
- <span
- className={cn("text-gray-11 ml-auto text-xs tracking-widest", className)}
- {...props}
- />
- );
-};
-CommandShortcut.displayName = "CommandShortcut";
-
-export {
- Command,
- CommandDialog,
- CommandInput,
- CommandList,
- CommandEmpty,
- CommandGroup,
- CommandItem,
- CommandShortcut,
- CommandSeparator,
-};
diff --git a/apps/web/src/components/ui/dialog.tsx b/apps/web/src/components/ui/dialog.tsx
deleted file mode 100644
index 0da54769..00000000
--- a/apps/web/src/components/ui/dialog.tsx
+++ /dev/null
@@ -1,119 +0,0 @@
-"use client";
-
-import * as React from "react";
-import * as DialogPrimitive from "@radix-ui/react-dialog";
-import { X } from "lucide-react";
-
-import { cn } from "@/lib/utils";
-
-const Dialog = DialogPrimitive.Root;
-
-const DialogTrigger = DialogPrimitive.Trigger;
-
-const DialogPortal = DialogPrimitive.Portal;
-
-const DialogClose = DialogPrimitive.Close;
-
-const DialogOverlay = React.forwardRef<
- React.ElementRef<typeof DialogPrimitive.Overlay>,
- React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
->(({ className, ...props }, ref) => (
- <DialogPrimitive.Overlay
- ref={ref}
- className={cn(
- "data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/80",
- className,
- )}
- {...props}
- />
-));
-DialogOverlay.displayName = DialogPrimitive.Overlay.displayName;
-
-const DialogContent = React.forwardRef<
- React.ElementRef<typeof DialogPrimitive.Content>,
- React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
->(({ className, children, ...props }, ref) => (
- <DialogPortal>
- <DialogOverlay />
- <DialogPrimitive.Content
- ref={ref}
- className={cn(
- "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-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] border-rgray-6 fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] rounded-lg border bg-[#F4F3F2] p-6 text-black shadow-lg duration-200",
- className,
- )}
- {...props}
- >
- {children}
- <DialogPrimitive.Close className="ring-offset-rgray-2 focus:ring-rgray-7 absolute right-4 top-4 rounded-sm opacity-70 transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:text-black">
- <X className="h-4 w-4" />
- <span className="sr-only">Close</span>
- </DialogPrimitive.Close>
- </DialogPrimitive.Content>
- </DialogPortal>
-));
-DialogContent.displayName = DialogPrimitive.Content.displayName;
-
-const DialogHeader = ({
- className,
- ...props
-}: React.HTMLAttributes<HTMLDivElement>) => (
- <div
- className={cn("flex flex-col space-y-1.5 text-left", className)}
- {...props}
- />
-);
-DialogHeader.displayName = "DialogHeader";
-
-const DialogFooter = ({
- className,
- ...props
-}: React.HTMLAttributes<HTMLDivElement>) => (
- <div
- className={cn(
- "mt-5 flex flex-row sm:flex-row sm:justify-end sm:space-x-2",
- className,
- )}
- {...props}
- />
-);
-DialogFooter.displayName = "DialogFooter";
-
-const DialogTitle = React.forwardRef<
- React.ElementRef<typeof DialogPrimitive.Title>,
- React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
->(({ className, ...props }, ref) => (
- <DialogPrimitive.Title
- ref={ref}
- className={cn(
- "mb-1 text-xl font-medium leading-none tracking-tight",
- className,
- )}
- {...props}
- />
-));
-DialogTitle.displayName = DialogPrimitive.Title.displayName;
-
-const DialogDescription = React.forwardRef<
- React.ElementRef<typeof DialogPrimitive.Description>,
- React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
->(({ className, ...props }, ref) => (
- <DialogPrimitive.Description
- ref={ref}
- className={cn("text-sm text-slate-800", className)}
- {...props}
- />
-));
-DialogDescription.displayName = DialogPrimitive.Description.displayName;
-
-export {
- Dialog,
- DialogPortal,
- DialogOverlay,
- DialogClose,
- DialogTrigger,
- DialogContent,
- DialogHeader,
- DialogFooter,
- DialogTitle,
- DialogDescription,
-};
diff --git a/apps/web/src/components/ui/drawer.tsx b/apps/web/src/components/ui/drawer.tsx
deleted file mode 100644
index 8ba01253..00000000
--- a/apps/web/src/components/ui/drawer.tsx
+++ /dev/null
@@ -1,124 +0,0 @@
-"use client";
-
-import * as React from "react";
-import { Drawer as DrawerPrimitive } from "vaul";
-
-import { cn } from "@/lib/utils";
-
-const Drawer = ({
- shouldScaleBackground = true,
- ...props
-}: React.ComponentProps<typeof DrawerPrimitive.Root>) => (
- <DrawerPrimitive.Root
- shouldScaleBackground={shouldScaleBackground}
- {...props}
- />
-);
-Drawer.displayName = "Drawer";
-
-const DrawerTrigger = DrawerPrimitive.Trigger;
-
-const DrawerPortal = DrawerPrimitive.Portal;
-
-const DrawerClose = DrawerPrimitive.Close;
-
-const DrawerOverlay = React.forwardRef<
- React.ElementRef<typeof DrawerPrimitive.Overlay>,
- React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Overlay>
->(({ className, ...props }, ref) => (
- <DrawerPrimitive.Overlay
- ref={ref}
- data-drawer-overlay
- className={cn("fixed inset-0 z-50 bg-black/80", className)}
- {...props}
- />
-));
-DrawerOverlay.displayName = DrawerPrimitive.Overlay.displayName;
-
-const DrawerContent = React.forwardRef<
- React.ElementRef<typeof DrawerPrimitive.Content>,
- React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Content> & {
- overlay?: boolean;
- handle?: boolean;
- }
->(({ className, children, overlay = true, handle = true, ...props }, ref) => (
- <DrawerPortal>
- {overlay && <DrawerOverlay />}
- <DrawerPrimitive.Content
- ref={ref}
- className={cn(
- "border-rgray-6 bg-rgray-2 fixed inset-x-0 bottom-0 z-50 mt-24 flex h-auto flex-col rounded-t-[10px] border",
- className,
- )}
- {...props}
- >
- {handle && (
- <div className="bg-rgray-4 mx-auto mb-1 h-2 w-[100px] rounded-full " />
- )}
- {children}
- </DrawerPrimitive.Content>
- </DrawerPortal>
-));
-DrawerContent.displayName = "DrawerContent";
-
-const DrawerHeader = ({
- className,
- ...props
-}: React.HTMLAttributes<HTMLDivElement>) => (
- <div
- className={cn("grid gap-1.5 p-4 text-center sm:text-left", className)}
- {...props}
- />
-);
-DrawerHeader.displayName = "DrawerHeader";
-
-const DrawerFooter = ({
- className,
- ...props
-}: React.HTMLAttributes<HTMLDivElement>) => (
- <div
- className={cn("mt-auto flex flex-col gap-2 p-4", className)}
- {...props}
- />
-);
-DrawerFooter.displayName = "DrawerFooter";
-
-const DrawerTitle = React.forwardRef<
- React.ElementRef<typeof DrawerPrimitive.Title>,
- React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Title>
->(({ className, ...props }, ref) => (
- <DrawerPrimitive.Title
- ref={ref}
- className={cn(
- "text-rgray-12 text-xl font-medium leading-none tracking-tight",
- className,
- )}
- {...props}
- />
-));
-DrawerTitle.displayName = DrawerPrimitive.Title.displayName;
-
-const DrawerDescription = React.forwardRef<
- React.ElementRef<typeof DrawerPrimitive.Description>,
- React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Description>
->(({ className, ...props }, ref) => (
- <DrawerPrimitive.Description
- ref={ref}
- className={cn("text-rgray-11 text-md", className)}
- {...props}
- />
-));
-DrawerDescription.displayName = DrawerPrimitive.Description.displayName;
-
-export {
- Drawer,
- DrawerPortal,
- DrawerOverlay,
- DrawerTrigger,
- DrawerClose,
- DrawerContent,
- DrawerHeader,
- DrawerFooter,
- DrawerTitle,
- DrawerDescription,
-};
diff --git a/apps/web/src/components/ui/dropdown-menu.tsx b/apps/web/src/components/ui/dropdown-menu.tsx
deleted file mode 100644
index fbe2d99c..00000000
--- a/apps/web/src/components/ui/dropdown-menu.tsx
+++ /dev/null
@@ -1,200 +0,0 @@
-"use client";
-
-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";
-
-const DropdownMenu = DropdownMenuPrimitive.Root;
-
-const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger;
-
-const DropdownMenuGroup = DropdownMenuPrimitive.Group;
-
-const DropdownMenuPortal = DropdownMenuPrimitive.Portal;
-
-const DropdownMenuSub = DropdownMenuPrimitive.Sub;
-
-const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup;
-
-const DropdownMenuSubTrigger = React.forwardRef<
- React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>,
- React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & {
- inset?: boolean;
- }
->(({ className, inset, children, ...props }, ref) => (
- <DropdownMenuPrimitive.SubTrigger
- ref={ref}
- 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,
- )}
- {...props}
- >
- {children}
- <ChevronRight className="ml-auto h-4 w-4" />
- </DropdownMenuPrimitive.SubTrigger>
-));
-DropdownMenuSubTrigger.displayName =
- DropdownMenuPrimitive.SubTrigger.displayName;
-
-const DropdownMenuSubContent = React.forwardRef<
- React.ElementRef<typeof DropdownMenuPrimitive.SubContent>,
- React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent>
->(({ className, ...props }, ref) => (
- <DropdownMenuPrimitive.SubContent
- ref={ref}
- className={cn(
- "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",
- className,
- )}
- {...props}
- />
-));
-DropdownMenuSubContent.displayName =
- DropdownMenuPrimitive.SubContent.displayName;
-
-const DropdownMenuContent = React.forwardRef<
- React.ElementRef<typeof DropdownMenuPrimitive.Content>,
- React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>
->(({ className, sideOffset = 4, ...props }, ref) => (
- <DropdownMenuPrimitive.Portal>
- <DropdownMenuPrimitive.Content
- ref={ref}
- sideOffset={sideOffset}
- className={cn(
- "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 border-rgray-6 z-50 min-w-[9rem] overflow-hidden rounded-md border bg-white p-1 text-black shadow-md",
- className,
- )}
- {...props}
- />
- </DropdownMenuPrimitive.Portal>
-));
-DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName;
-
-const DropdownMenuItem = React.forwardRef<
- React.ElementRef<typeof DropdownMenuPrimitive.Item>,
- React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {
- 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-stone-200 focus:text-slate-800 data-[disabled]:pointer-events-none data-[disabled]:opacity-50 ",
- inset && "pl-8",
- className,
- )}
- {...props}
- />
-));
-DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName;
-
-const DropdownMenuCheckboxItem = React.forwardRef<
- React.ElementRef<typeof DropdownMenuPrimitive.CheckboxItem>,
- React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.CheckboxItem>
->(({ className, children, checked, ...props }, ref) => (
- <DropdownMenuPrimitive.CheckboxItem
- 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,
- )}
- checked={checked}
- {...props}
- >
- <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
- <DropdownMenuPrimitive.ItemIndicator>
- <Check className="h-4 w-4" />
- </DropdownMenuPrimitive.ItemIndicator>
- </span>
- {children}
- </DropdownMenuPrimitive.CheckboxItem>
-));
-DropdownMenuCheckboxItem.displayName =
- DropdownMenuPrimitive.CheckboxItem.displayName;
-
-const DropdownMenuRadioItem = React.forwardRef<
- React.ElementRef<typeof DropdownMenuPrimitive.RadioItem>,
- React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.RadioItem>
->(({ className, children, ...props }, ref) => (
- <DropdownMenuPrimitive.RadioItem
- 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,
- )}
- {...props}
- >
- <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
- <DropdownMenuPrimitive.ItemIndicator>
- <Circle className="h-2 w-2 fill-current" />
- </DropdownMenuPrimitive.ItemIndicator>
- </span>
- {children}
- </DropdownMenuPrimitive.RadioItem>
-));
-DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName;
-
-const DropdownMenuLabel = React.forwardRef<
- React.ElementRef<typeof DropdownMenuPrimitive.Label>,
- React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & {
- inset?: boolean;
- }
->(({ className, inset, ...props }, ref) => (
- <DropdownMenuPrimitive.Label
- ref={ref}
- className={cn(
- "px-2 py-1.5 text-sm font-semibold",
- inset && "pl-8",
- className,
- )}
- {...props}
- />
-));
-DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName;
-
-const DropdownMenuSeparator = React.forwardRef<
- React.ElementRef<typeof DropdownMenuPrimitive.Separator>,
- React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator>
->(({ className, ...props }, ref) => (
- <DropdownMenuPrimitive.Separator
- ref={ref}
- className={cn("-mx-1 my-1 h-px bg-gray-100", className)}
- {...props}
- />
-));
-DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName;
-
-const DropdownMenuShortcut = ({
- className,
- ...props
-}: React.HTMLAttributes<HTMLSpanElement>) => {
- return (
- <span
- className={cn("ml-auto text-xs tracking-widest opacity-60", className)}
- {...props}
- />
- );
-};
-DropdownMenuShortcut.displayName = "DropdownMenuShortcut";
-
-export {
- DropdownMenu,
- DropdownMenuTrigger,
- DropdownMenuContent,
- DropdownMenuItem,
- DropdownMenuCheckboxItem,
- DropdownMenuRadioItem,
- DropdownMenuLabel,
- DropdownMenuSeparator,
- DropdownMenuShortcut,
- DropdownMenuGroup,
- DropdownMenuPortal,
- DropdownMenuSub,
- DropdownMenuSubContent,
- DropdownMenuSubTrigger,
- DropdownMenuRadioGroup,
-};
diff --git a/apps/web/src/components/ui/input.tsx b/apps/web/src/components/ui/input.tsx
deleted file mode 100644
index 9d925512..00000000
--- a/apps/web/src/components/ui/input.tsx
+++ /dev/null
@@ -1,53 +0,0 @@
-import * as React from "react";
-
-import { cn } from "@/lib/utils";
-
-export interface InputProps
- extends React.InputHTMLAttributes<HTMLInputElement> {}
-
-const Input = React.forwardRef<HTMLInputElement, InputProps>(
- ({ className, type, ...props }, ref) => {
- return (
- <input
- type={type}
- className={cn(
- "border-rgray-6 focus-visible:ring-rgray-7 flex h-10 w-full rounded-md border bg-transparent px-3 py-2 text-sm font-normal transition file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-black/50 focus-visible:outline-none focus-visible:ring-2 disabled:cursor-not-allowed disabled:opacity-50 ",
- className,
- )}
- ref={ref}
- {...props}
- />
- );
- },
-);
-
-export interface InputWithIconProps
- extends React.InputHTMLAttributes<HTMLInputElement> {
- icon: React.ReactNode;
-}
-
-const InputWithIcon = React.forwardRef<HTMLInputElement, InputWithIconProps>(
- ({ className, type, icon, ...props }, ref) => {
- return (
- <div
- className={cn(
- "border-rgray-1/70 text-rgray-11 focus-within:ring-rgray-7 flex h-10 w-full items-center justify-center gap-2 rounded-md border bg-transparent px-3 py-2 text-sm font-normal transition focus-within:outline-none focus-within:ring-2 ", // TODO: change to black
- className,
- )}
- >
- {icon}
- <input
- type={type}
- className={
- "w-full bg-transparent font-normal file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-black/50 focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50"
- }
- ref={ref}
- {...props}
- />
- </div>
- );
- },
-);
-InputWithIcon.displayName = "Input";
-
-export { Input, InputWithIcon };
diff --git a/apps/web/src/components/ui/label.tsx b/apps/web/src/components/ui/label.tsx
deleted file mode 100644
index 84f8b0c7..00000000
--- a/apps/web/src/components/ui/label.tsx
+++ /dev/null
@@ -1,26 +0,0 @@
-"use client";
-
-import * as React from "react";
-import * as LabelPrimitive from "@radix-ui/react-label";
-import { cva, type VariantProps } from "class-variance-authority";
-
-import { cn } from "@/lib/utils";
-
-const labelVariants = cva(
- "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70",
-);
-
-const Label = React.forwardRef<
- React.ElementRef<typeof LabelPrimitive.Root>,
- React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> &
- VariantProps<typeof labelVariants>
->(({ className, ...props }, ref) => (
- <LabelPrimitive.Root
- ref={ref}
- className={cn(labelVariants(), className)}
- {...props}
- />
-));
-Label.displayName = LabelPrimitive.Root.displayName;
-
-export { Label };
diff --git a/apps/web/src/components/ui/popover.tsx b/apps/web/src/components/ui/popover.tsx
deleted file mode 100644
index cabe76a9..00000000
--- a/apps/web/src/components/ui/popover.tsx
+++ /dev/null
@@ -1,40 +0,0 @@
-"use client";
-
-import * as React from "react";
-import * as PopoverPrimitive from "@radix-ui/react-popover";
-
-import { cn } from "@/lib/utils";
-
-const Popover = PopoverPrimitive.Root;
-
-const PopoverTrigger = PopoverPrimitive.Trigger;
-
-const PopoverContent = React.forwardRef<
- React.ElementRef<typeof PopoverPrimitive.Content>,
- React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content> & {
- animate?: boolean;
- }
->(
- (
- { className, align = "center", animate = true, sideOffset = 4, ...props },
- ref,
- ) => (
- <PopoverPrimitive.Portal>
- <PopoverPrimitive.Content
- ref={ref}
- align={align}
- sideOffset={sideOffset}
- className={cn(
- "border-rgray-6 bg-rgray-3 text-rgray-11 z-50 w-72 rounded-md border p-4 shadow-md outline-none",
- animate &&
- "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",
- className,
- )}
- {...props}
- />
- </PopoverPrimitive.Portal>
- ),
-);
-PopoverContent.displayName = PopoverPrimitive.Content.displayName;
-
-export { Popover, PopoverTrigger, PopoverContent };
diff --git a/apps/web/src/components/ui/textarea.tsx b/apps/web/src/components/ui/textarea.tsx
deleted file mode 100644
index 3b2c9ddd..00000000
--- a/apps/web/src/components/ui/textarea.tsx
+++ /dev/null
@@ -1,57 +0,0 @@
-import * as React from "react";
-
-import { cn } from "@/lib/utils";
-import { HTMLMotionProps, motion } from "framer-motion";
-
-export interface TextareaProps
- extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {}
-
-const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
- ({ className, ...props }, ref) => {
- return (
- <textarea
- className={cn(
- "border-rgray-6 text-rgray-11 placeholder:text-rgray-11/70 focus-within:ring-rgray-7 flex min-h-[80px] w-full rounded-md border bg-transparent px-3 py-2 text-sm font-normal transition focus-within:outline-none focus-within:ring-2 disabled:cursor-not-allowed disabled:opacity-50",
- className,
- )}
- ref={ref}
- {...props}
- />
- );
- },
-);
-Textarea.displayName = "Textarea";
-
-export interface Textarea2Props extends HTMLMotionProps<"div"> {
- textAreaProps?: TextareaProps;
- children: React.ReactNode | React.ReactNode[];
-}
-
-const Textarea2 = React.forwardRef<HTMLDivElement, Textarea2Props>(
- ({ className, children, textAreaProps: _textAreaProps, ...props }, ref) => {
- const { className: textAreaClassName, ...textAreaProps } =
- _textAreaProps || {};
- return (
- <motion.div
- ref={ref}
- className={cn(
- "border-rgray-6 text-rgray-11 has-[textarea:focus-visible]:ring-rgray-7 flex h-auto min-h-[80px] w-full flex-col items-start justify-center rounded-md border bg-transparent px-3 py-2 text-sm transition has-[textarea:focus-visible]:ring-2",
- className,
- )}
- {...props}
- >
- <textarea
- className={cn(
- "text-rgray-11 h-full w-full resize-none bg-transparent placeholder:text-white/50 focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50",
- textAreaClassName,
- )}
- {...textAreaProps}
- />
- {children}
- </motion.div>
- );
- },
-);
-Textarea2.displayName = "Textarea2";
-
-export { Textarea, Textarea2 };