From 075f45986fd4d198292226e64afb71b3515576b4 Mon Sep 17 00:00:00 2001 From: Dhravya Date: Sat, 25 May 2024 18:41:26 -0500 Subject: refactored UI, with shared components and UI, better rules and million lint --- apps/web/src/components/ChatMessage.tsx | 128 ---- apps/web/src/components/Main-2.tsx | 709 --------------------- apps/web/src/components/Main.tsx | 533 ---------------- apps/web/src/components/MemoryDrawer.tsx | 51 -- apps/web/src/components/ProfileDrawer.tsx | 40 -- apps/web/src/components/SearchResults.tsx | 40 -- .../web/src/components/Sidebar/AddMemoryDialog.tsx | 480 -------------- .../src/components/Sidebar/DeleteConfirmation.tsx | 47 -- apps/web/src/components/Sidebar/EditNoteDialog.tsx | 155 ----- apps/web/src/components/Sidebar/ExpandedSpace.tsx | 287 --------- apps/web/src/components/Sidebar/FilterCombobox.tsx | 303 --------- apps/web/src/components/Sidebar/MemoriesBar.tsx | 709 --------------------- apps/web/src/components/Sidebar/SettingsTab.tsx | 99 --- apps/web/src/components/Sidebar/index.tsx | 172 ----- apps/web/src/components/WordMark.tsx | 12 - .../src/components/dev/SessionProviderWrapper.tsx | 12 - apps/web/src/components/dev/tailwindindicator.tsx | 16 - apps/web/src/components/ui/avatar.tsx | 50 -- apps/web/src/components/ui/badge.tsx | 36 -- apps/web/src/components/ui/button.tsx | 56 -- apps/web/src/components/ui/card.tsx | 86 --- apps/web/src/components/ui/command.tsx | 161 ----- apps/web/src/components/ui/dialog.tsx | 119 ---- apps/web/src/components/ui/drawer.tsx | 124 ---- apps/web/src/components/ui/dropdown-menu.tsx | 200 ------ apps/web/src/components/ui/input.tsx | 53 -- apps/web/src/components/ui/label.tsx | 26 - apps/web/src/components/ui/popover.tsx | 40 -- apps/web/src/components/ui/textarea.tsx | 57 -- 29 files changed, 4801 deletions(-) delete mode 100644 apps/web/src/components/ChatMessage.tsx delete mode 100644 apps/web/src/components/Main-2.tsx delete mode 100644 apps/web/src/components/Main.tsx delete mode 100644 apps/web/src/components/MemoryDrawer.tsx delete mode 100644 apps/web/src/components/ProfileDrawer.tsx delete mode 100644 apps/web/src/components/SearchResults.tsx delete mode 100644 apps/web/src/components/Sidebar/AddMemoryDialog.tsx delete mode 100644 apps/web/src/components/Sidebar/DeleteConfirmation.tsx delete mode 100644 apps/web/src/components/Sidebar/EditNoteDialog.tsx delete mode 100644 apps/web/src/components/Sidebar/ExpandedSpace.tsx delete mode 100644 apps/web/src/components/Sidebar/FilterCombobox.tsx delete mode 100644 apps/web/src/components/Sidebar/MemoriesBar.tsx delete mode 100644 apps/web/src/components/Sidebar/SettingsTab.tsx delete mode 100644 apps/web/src/components/Sidebar/index.tsx delete mode 100644 apps/web/src/components/WordMark.tsx delete mode 100644 apps/web/src/components/dev/SessionProviderWrapper.tsx delete mode 100644 apps/web/src/components/dev/tailwindindicator.tsx delete mode 100644 apps/web/src/components/ui/avatar.tsx delete mode 100644 apps/web/src/components/ui/badge.tsx delete mode 100644 apps/web/src/components/ui/button.tsx delete mode 100644 apps/web/src/components/ui/card.tsx delete mode 100644 apps/web/src/components/ui/command.tsx delete mode 100644 apps/web/src/components/ui/dialog.tsx delete mode 100644 apps/web/src/components/ui/drawer.tsx delete mode 100644 apps/web/src/components/ui/dropdown-menu.tsx delete mode 100644 apps/web/src/components/ui/input.tsx delete mode 100644 apps/web/src/components/ui/label.tsx delete mode 100644 apps/web/src/components/ui/popover.tsx delete mode 100644 apps/web/src/components/ui/textarea.tsx (limited to 'apps/web/src/components') 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 ( -
- {loading ? ( - - ) : ( -
- {message} -
- )} - {!loading && sources && sources?.length > 0 && ( - <> -

- - Related Memories -

-
- {sources?.map((source) => - source.isNote ? ( - - ) : ( - - - {cleanUrl(source.source)} - - ), - )} -
- - )} -
- ); -} - -export function ChatQuestion({ children }: { children: string }) { - return ( -
200 ? "text-xl" : "text-2xl"}`} - > - {children} -
- ); -} - -export function ChatMessage({ - children, - isLast = false, - index, -}: { - children: React.ReactNode | React.ReactNode[]; - isLast?: boolean; - index: number; -}) { - const messageRef = React.useRef(null); - - useEffect(() => { - if (!isLast) return; - messageRef.current?.parentElement?.scrollTo({ - top: messageRef.current?.offsetTop, - behavior: "smooth", - }); - }, []); - - return ( - - {children} - - ); -} - -function MessageSkeleton() { - return ( -
-
-
-
-
-
-
- ); -} - -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(null); - - const [searchQuery, setSearcyQuery] = useState(""); - const [searchLoading, setSearchLoading] = useState(false); - const query = useDebounce(searchQuery, 500); - - const [searchResults, setSearchResults] = useState([]); - - 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 ( - setExpandedSpace(null)} - // close={() => setExpandedSpace(null)} - /> - ); - } - - return ( - <> - -
-
-
-

- Your Memories -

-
- - - - - - e.preventDefault()} - > - - { - setAddMemoryState("page"); - }} - > - - Page to Memory - - - - { - setAddMemoryState("note"); - }} - > - - Note - - - - { - setAddMemoryState("space"); - }} - > - - Space - - - - - -
-
- - ) : ( - - ) - } - className="mt-4 w-full text-black" - value={searchQuery} - onChange={(e) => setSearcyQuery(e.target.value)} - /> -
-
- {typeof window !== "undefined" ? ( - query.trim().length > 0 ? ( - <> - {searchResults.map(({ type, space, memory }, i) => ( - <> - {type === "memory" && ( - { - setSearchResults((prev) => - prev.filter((i) => i.memory?.id !== memory.id), - ); - }} - /> - )} - {type === "space" && ( - { - setSearchResults((prev) => - prev.filter((i) => i.space?.id !== space.id), - ); - deleteSpace(space.id); - }} - /> - )} - - ))} - - ) : ( - <> - {spaces.map((space) => ( - deleteSpace(space.id)} - key={space.id} - onClick={() => setExpandedSpace(space.id)} - {...space} - /> - ))} - {freeMemories.map((m) => ( - - ))} - - ) - ) : ( - <> - {Array.from({ - length: spaces.length + freeMemories.length, - }).map((_, i) => ( -
- ))} - - )} -
-
- {width <= 768 && } -
-
- {width <= 768 && } -
- - ); -} - -export function MemoryItem( - props: StoredContent & { - onDelete?: () => void; - removeFromSpace?: () => Promise; - }, -) { - 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 ( - - - - - - setIsDialogOpen(false)} - memory={props} - /> - - - ); -} - -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 ( - - ); -} - -export function SpaceMoreButton({ - onDelete, - isOpen, - setIsOpen, - onEdit, -}: { - onDelete?: () => void; - isOpen?: boolean; - onEdit?: () => void; - setIsOpen?: (open: boolean) => void; -}) { - return ( - - - - - - - - - Edit - - - - - Delete - - - - - - ); -} - -export function PageMoreButton({ - onDelete, - isOpen, - setIsOpen, - url, - removeFromSpace, -}: { - onDelete?: () => void; - isOpen?: boolean; - url: string; - setIsOpen?: (open: boolean) => void; - removeFromSpace?: () => Promise; -}) { - return ( - - - - - - - window.open(url)}> - - Open - - {removeFromSpace && ( - - - Remove from space - - )} - - - - Delete - - - - - - ); -} - -export function NoteMoreButton({ - onDelete, - isOpen, - setIsOpen, - onEdit, - removeFromSpace, -}: { - onDelete?: () => void; - isOpen?: boolean; - onEdit?: () => void; - setIsOpen?: (open: boolean) => void; - removeFromSpace?: () => Promise; -}) { - return ( - - - - - - - - - Edit - - {removeFromSpace && ( - - - Remove from space - - )} - - - - Delete - - - - - - ); -} - -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 ( - - {children} - { - 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" ? ( - setIsDialogOpen(false)} - /> - ) : type === "note" ? ( - setIsDialogOpen(false)} - /> - ) : type === "space" ? ( - setIsDialogOpen(false)} - /> - ) : type === "existing-memory" ? ( - setIsDialogOpen(false)} - /> - ) : ( - <> - )} - - - ); -} 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([]); - - const [toBeParsed, setToBeParsed] = useState(""); - - const textArea = useRef(null); - const main = useRef(null); - - const [selectedSpaces, setSelectedSpaces] = useState([]); - - 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 ?? "" })), - ...fetchedTitles.map((n) => ({ - isNote: true, - source: n.title ?? "", - })), - ]; - - 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) { - const value = e.target.value; - setValue(value); - const lines = countLines(e.target); - e.target.rows = Math.min(5, lines); - } - - return ( - <> - - {layout === "chat" ? ( - - ) : ( -
- Smort logo -
- {width <= 768 && } -
-

- Ask your second brain -

- - { - textArea.current?.querySelector("textarea")?.focus(); - }} - side="top" - align="start" - className="mr-auto bg-[#252525] md:hidden" - selectedSpaces={selectedSpaces} - setSelectedSpaces={setSelectedSpaces} - /> - { - if (e.key === "Enter" && !e.shiftKey) { - e.preventDefault(); - onSend(); - } - }, - }} - > -
- { - textArea.current?.querySelector("textarea")?.focus(); - }} - className="hidden md:flex" - selectedSpaces={selectedSpaces} - setSelectedSpaces={setSelectedSpaces} - /> - -
-
- - setValue(e.target.value), - onKeyDown: (e) => { - if (e.key === "Enter" && !e.shiftKey) { - e.preventDefault(); - onSend(); - } - }, - }} - className="hidden md:flex" - > -
- { - textArea.current?.querySelector("textarea")?.focus(); - }} - className="hidden md:flex" - selectedSpaces={selectedSpaces} - setSelectedSpaces={setSelectedSpaces} - /> - -
-
-
- )} - {width <= 768 && } -
- - ); -} - -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>; -}) { - const textArea = useRef(null); - - function onValueChange(e: React.ChangeEvent) { - const value = e.target.value; - setValue(value); - const lines = countLines(e.target); - e.target.rows = Math.min(5, lines); - } - - const { width } = useViewport(); - - return ( -
-
- {width <= 768 && } -
-
- {chatHistory.map((msg, i) => ( - - {msg.question} - - {msg.answer.parts - .map((part) => part.text) - .join("") - .replace("", "")} - - - ))} -
-
-
-
- { - textArea.current?.querySelector("textarea")?.focus(); - }} - side="top" - align="start" - className="mr-auto bg-[#252525]" - selectedSpaces={selectedSpaces} - setSelectedSpaces={setSelectedSpaces} - /> - { - if (e.key === "Enter" && !e.shiftKey) { - e.preventDefault(); - askQuestion(); - } - }, - }} - > -
- -
-
-
-
-
- ); -} 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 { - hide?: boolean; -} - -export function MemoryDrawer({ className, hide = false, ...props }: Props) { - const [activeSnapPoint, setActiveSnapPoint] = useState< - number | null | string - >(0.1); - - return ( - - - -
- -
-
- -
- ); -} 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 { - hide?: boolean; -} - -export function ProfileDrawer({ className, hide = false, ...props }: Props) { - const { data: session } = useSession(); - - return ( - - - - - -
- -
-
-
- ); -} 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 ( -
-
-
- - {aiResponse.replace("", "")} - -
-
-
- {sources.map((value, index) => ( - - {value} - - ))} -
-
- ); -} - -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( - defaultSpaces ?? [], - ); - - return ( -
- - Add a web page to memory - - This will fetch the content of the web page and add it to the memory - - - - setUrl(e.target.value)} - disabled={loading} - /> - - - - - Cancel - - -
- ); -} - -export function NoteAddPage({ - closeDialog, - defaultSpaces, - onAdd, -}: { - closeDialog: () => void; - defaultSpaces?: number[]; - onAdd?: (addedData: StoredContent) => void; -}) { - const { addMemory } = useMemory(); - - const [selectedSpacesId, setSelectedSpacesId] = useState( - defaultSpaces ?? [], - ); - - const inputRef = useRef(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 ( -
- setName(e.target.value)} - /> - { - 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" - /> - - - - - Cancel - - -
- ); -} - -export function SpaceAddPage({ - closeDialog, - onAdd, -}: { - closeDialog: () => void; - onAdd?: (addedData: StoredSpace) => void; -}) { - const { addSpace } = useMemory(); - - const inputRef = useRef(null); - const [name, setName] = useState(""); - - const [loading, setLoading] = useState(false); - - const [selected, setSelected] = useState([]); - - 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 ( -
- - Add a space - - - 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 && ( - <> - -
- {selected.map((i) => ( - - setSelected((prev) => prev.filter((p) => p.id !== i.id)) - } - {...i} - /> - ))} -
- - )} - - - - Memory - - - - Cancel - - -
- ); -} - -export function MemorySelectedItem({ - id, - title, - url, - type, - image, - onRemove, -}: StoredContent & { onRemove: () => void }) { - return ( -
- - {title} - - {type === "note" ? "Note" : cleanUrl(url)} - -
- ); -} - -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([]); - - return ( -
- - Add an existing memory to {space.title} - - Pick the memories you want to add to this space - - - {selected.length > 0 && ( - <> - -
- {selected.map((i) => ( - - setSelected((prev) => prev.filter((p) => p.id !== i.id)) - } - {...i} - /> - ))} -
- - )} - - - - Memory - - - - Cancel - - -
- ); -} 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 ( - - {trigger ? ( - {children} - ) : ( - <>{children} - )} - - Are you sure? - - You will not be able to recover this it. - - - - Delete - - - Cancel - - - - - ); -} 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([]); - const [selectedSpacesId, setSelectedSpacesId] = useState([]); - - const inputRef = useRef(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 ( -
- setName(e.target.value)} - /> - { - 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" - /> - - - { - deleteMemory(memory.id); - onDelete?.(); - }} - > - - - - - Cancel - - -
- ); -} 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(null); - - const [contentForSpace, setContentForSpace] = useState([]); - - const [lastUpdatedTitle, setLastUpdatedTitle] = useState(null); - - const [title, setTitle] = useState(""); - 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([]); - - 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 ( -
- -
- ); - } - - return ( -
-
- - setTitle(e.target.value)} - /> - -
-
- - ) : ( - - ) - } - className="bg-rgray-4 mt-2 w-full" - value={searchQuery} - onChange={(e) => setSearcyQuery(e.target.value)} - /> -
-
- { - 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} - > - - - - - e.preventDefault()}> - - { - setAddMemoryState("existing-memory"); - }} - > - - Existing Memory - - - - { - setAddMemoryState("page"); - }} - > - - Page - - - - { - setAddMemoryState("note"); - }} - > - - Note - - - - - -
-
- {query.trim().length > 0 ? ( - <> - {searchResults.map((memory, i) => ( - { - 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) => ( - - 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)); - }} - /> - )) - )} -
-
- ); -} 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 { - 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 ( - - - - - e.preventDefault()} - > - - spaces - .find((s) => s.id.toString() === val) - ?.name.toLowerCase() - .includes(search.toLowerCase().trim()) - ? 1 - : 0 - } - > - - - - Nothing found - - {sortedSpaces.map((space) => ( - { - setSelectedSpaces((prev: number[]) => - prev.includes(parseInt(val)) - ? prev.filter((v) => v !== parseInt(val)) - : [...prev, parseInt(val)], - ); - }} - asChild - > - - - {space.name.length > 10 - ? space.name.slice(0, 10) + "..." - : space.name} - {selectedSpaces.includes(space.id)} - - - - ))} - - - - - - - ); -} - -export type FilterMemoriesProps = { - side?: "top" | "bottom"; - align?: "end" | "start" | "center"; - onClose?: () => void; - selected: StoredContent[]; - setSelected: React.Dispatch>; - fromSpaces?: number[]; - notInSpaces?: number[]; -} & React.ButtonHTMLAttributes; - -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([]); - 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 ( - - - - - - - e.preventDefault()} - align={align} - side={side} - className="w-[200px] p-0" - > - - - - - - {isSearching - ? "Searching..." - : query.trim().length > 0 - ? "Nothing Found" - : "Search something"} - - {results.map((m) => ( - { - setSelected((prev) => - prev.find((p) => p.id === parseInt(val)) - ? prev.filter((v) => v.id !== parseInt(val)) - : [...prev, m], - ); - }} - asChild - > -
- - {m.title && m.title?.length > 14 - ? m.title?.slice(0, 14) + "..." - : m.title} - i.id === m.id) !== undefined - } - className={cn( - "on:opacity-100 ml-auto h-4 w-4 opacity-0", - )} - /> -
-
- ))} -
-
-
-
-
-
-
- ); -} 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(null); - - const [searchQuery, setSearcyQuery] = useState(""); - const [searchLoading, setSearchLoading] = useState(false); - const query = useDebounce(searchQuery, 500); - - const [searchResults, setSearchResults] = useState([]); - - 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 ( - setExpandedSpace(null)} - // close={() => setExpandedSpace(null)} - /> - ); - } - - return ( -
-
-

Your Memories

- - ) : ( - - ) - } - className="bg-rgray-4 mt-2 w-full" - value={searchQuery} - onChange={(e) => setSearcyQuery(e.target.value)} - /> -
-
- - - - - - e.preventDefault()}> - - { - setAddMemoryState("page"); - }} - > - - Page to Memory - - - - { - setAddMemoryState("note"); - }} - > - - Note - - - - { - setAddMemoryState("space"); - }} - > - - Space - - - - - -
-
- {query.trim().length > 0 ? ( - <> - {searchResults.map(({ type, space, memory }, i) => ( - <> - {type === "memory" && ( - { - setSearchResults((prev) => - prev.filter((i) => i.memory?.id !== memory.id), - ); - }} - /> - )} - {type === "space" && ( - { - setSearchResults((prev) => - prev.filter((i) => i.space?.id !== space.id), - ); - deleteSpace(space.id); - }} - /> - )} - - ))} - - ) : ( - <> - {spaces.map((space) => ( - deleteSpace(space.id)} - key={space.id} - onClick={() => setExpandedSpace(space.id)} - {...space} - /> - ))} - {freeMemories.map((m) => ( - - ))} - - )} -
-
- ); -} - -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; - }, -) { - const { id, title, image, type, url, onDelete, removeFromSpace } = props; - - const { deleteMemory } = useMemory(); - - const name = title - ? title.length > 10 - ? title.slice(0, 10) + "..." - : title - : ""; - - const [isDialogOpen, setIsDialogOpen] = useState(false); - - const [moreDropdownOpen, setMoreDropdownOpen] = useState(false); - - const touchEventProps = useTouchHold({ - onHold() { - setMoreDropdownOpen(true); - }, - }); - return ( - -
- {type === "note" ? ( - - - - ) : ( - - )} - - {type === "page" ? ( - { - deleteMemory(id); - onDelete?.(); - }} - url={url} - /> - ) : type === "note" ? ( - setIsDialogOpen(true)} - onDelete={() => { - deleteMemory(id); - onDelete?.(); - }} - /> - ) : null} - -
- {type === "page" ? ( - 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" ? ( - setIsDialogOpen(true)} className="h-16 w-16" /> - ) : ( - <> - )} -
-
- - setIsDialogOpen(false)} - memory={props} - /> - -
- ); -} - -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 ( - - - { - 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 ? ( - (c.type === "note" ? "/note.svg" : c.image)) - .reverse() as string[] - } - /> - ) : spaceMemories.length > 1 ? ( - (c.type === "note" ? "/note.svg" : c.image)) - .reverse() as string[] - } - /> - ) : spaceMemories.length === 1 ? ( - - ) : ( -
- )} -
- ); -} - -export function SpaceMoreButton({ - onDelete, - isOpen, - setIsOpen, - onEdit, -}: { - onDelete?: () => void; - isOpen?: boolean; - onEdit?: () => void; - setIsOpen?: (open: boolean) => void; -}) { - return ( - - - - - - - - - Edit - - - - - Delete - - - - - - ); -} - -export function PageMoreButton({ - onDelete, - isOpen, - setIsOpen, - url, - removeFromSpace, -}: { - onDelete?: () => void; - isOpen?: boolean; - url: string; - setIsOpen?: (open: boolean) => void; - removeFromSpace?: () => Promise; -}) { - return ( - - - - - - - window.open(url)}> - - Open - - {removeFromSpace && ( - - - Remove from space - - )} - - - - Delete - - - - - - ); -} - -export function NoteMoreButton({ - onDelete, - isOpen, - setIsOpen, - onEdit, - removeFromSpace, -}: { - onDelete?: () => void; - isOpen?: boolean; - onEdit?: () => void; - setIsOpen?: (open: boolean) => void; - removeFromSpace?: () => Promise; -}) { - return ( - - - - - - - - - Edit - - {removeFromSpace && ( - - - Remove from space - - )} - - - - Delete - - - - - - ); -} - -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 ( - - {children} - { - 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" ? ( - setIsDialogOpen(false)} - /> - ) : type === "note" ? ( - setIsDialogOpen(false)} - /> - ) : type === "space" ? ( - setIsDialogOpen(false)} - /> - ) : type === "existing-memory" ? ( - setIsDialogOpen(false)} - /> - ) : ( - <> - )} - - - ); -} 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 ( -
-
-

Settings

-
- { - (e.target as HTMLImageElement).src = - "/icons/white_without_bg.png"; - }} - /> -
-

{session?.user?.name}

- {session?.user?.email} - -
-
-
-
-

- - Storage -

- {loading ? ( -
-
-
-
- ) : ( - <> -
-

- Memories -
- {memoryStat?.join("/")} -
-

-
-
0 ? "5%" : "0%", - }} - className="bg-rgray-5 h-full rounded-full" - /> -
-
-
-

- Tweets -
- {tweetStat?.join("/")} -
-

-
-
0 ? "5%" : "0%", - }} - className="h-full rounded-full bg-white" - /> -
-
- - )} -
-
- ); -} 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(null); - - const menuItemsTop: Array = []; - - const menuItemsBottom: Array = [ - { - label: "Settings", - content: , - icon: <>, - }, - ]; - - const menuItems = [...menuItemsTop, ...menuItemsBottom]; - - const Subbar = menuItems.find((i) => i.label === selectedItem)?.content ?? ( - <> - ); - - useEffect(() => { - void selectChange?.(selectedItem); - }, [selectedItem]); - - return ( -
-
- 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" - > - - Memories - - -
- - - - - - ), - content: , - }} - selectedItem={selectedItem} - setSelectedItem={setSelectedItem} - /> - {/* */} -
- - - - {session?.user?.name?.split(" ").map((n) => n[0])}{" "} - - - {session?.user?.name?.split(" ")[0]} -
-
- - {selectedItem && {Subbar}} - -
- ); -} - -const MenuItem = ({ - item: { icon, label, labelDisplay }, - selectedItem, - setSelectedItem, - ...props -}: { - item: MenuItem; - selectedItem: string | null; - setSelectedItem: React.Dispatch>; -}) => { - const handleClick = () => - setSelectedItem((prev) => (prev === label ? null : label)); - - return ( - - ); -}; - -export function SubSidebar({ children }: { children?: React.ReactNode }) { - return ( - - - {children} - - - ); -} 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 ( - - smort. - - ); -} - -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 {children}; - } -} - -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 ( -
-
-
xs
-
sm
-
md
-
lg
-
xl
-
2xl
-
-
- ); -} 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, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)); -Avatar.displayName = AvatarPrimitive.Root.displayName; - -const AvatarImage = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)); -AvatarImage.displayName = AvatarPrimitive.Image.displayName; - -const AvatarFallback = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)); -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, - VariantProps {} - -function Badge({ className, variant, ...props }: BadgeProps) { - return ( -
- ); -} - -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, - VariantProps { - asChild?: boolean; -} - -const Button = React.forwardRef( - ({ className, variant, size, asChild = false, ...props }, ref) => { - const Comp = asChild ? Slot : "button"; - return ( -