aboutsummaryrefslogtreecommitdiff
path: root/apps/web/components
diff options
context:
space:
mode:
authorMahesh Sanikommu <[email protected]>2026-01-13 17:53:28 -0800
committerGitHub <[email protected]>2026-01-13 17:53:28 -0800
commit641db19e35009e22b101f9e673a3af4528de2a30 (patch)
tree698f9c3c22efed5f4061ef7e89b4963da150440a /apps/web/components
parentreproduce (diff)
downloadsupermemory-641db19e35009e22b101f9e673a3af4528de2a30.tar.xz
supermemory-641db19e35009e22b101f9e673a3af4528de2a30.zip
chore: quick bugs squash across the elements and added few more changes (#671)
Diffstat (limited to 'apps/web/components')
-rw-r--r--apps/web/components/chrome-extension-button.tsx2
-rw-r--r--apps/web/components/connect-ai-modal.tsx2
-rw-r--r--apps/web/components/new/add-document/connections.tsx43
-rw-r--r--apps/web/components/new/add-document/index.tsx39
-rw-r--r--apps/web/components/new/add-document/link.tsx150
-rw-r--r--apps/web/components/new/add-document/note.tsx9
-rw-r--r--apps/web/components/new/add-document/useDocumentMutations.ts313
-rw-r--r--apps/web/components/new/header.tsx78
-rw-r--r--apps/web/components/new/settings/account.tsx25
-rw-r--r--apps/web/components/views/add-memory/action-buttons.tsx2
10 files changed, 271 insertions, 392 deletions
diff --git a/apps/web/components/chrome-extension-button.tsx b/apps/web/components/chrome-extension-button.tsx
index 3f7510e3..30a8b982 100644
--- a/apps/web/components/chrome-extension-button.tsx
+++ b/apps/web/components/chrome-extension-button.tsx
@@ -11,7 +11,7 @@ import {
TwitterIcon,
} from "lucide-react"
import { useEffect, useState } from "react"
-import { motion } from "framer-motion"
+import { motion } from "motion/react"
import Image from "next/image"
import { analytics } from "@/lib/analytics"
import { useIsMobile } from "@hooks/use-mobile"
diff --git a/apps/web/components/connect-ai-modal.tsx b/apps/web/components/connect-ai-modal.tsx
index 942af105..d9353e6d 100644
--- a/apps/web/components/connect-ai-modal.tsx
+++ b/apps/web/components/connect-ai-modal.tsx
@@ -32,7 +32,7 @@ import { z } from "zod/v4"
import { analytics } from "@/lib/analytics"
import { cn } from "@lib/utils"
import type { Project } from "@repo/lib/types"
-import { motion, AnimatePresence } from "framer-motion"
+import { motion, AnimatePresence } from "motion/react"
const clients = {
cursor: "Cursor",
diff --git a/apps/web/components/new/add-document/connections.tsx b/apps/web/components/new/add-document/connections.tsx
index 2ba6513f..5a89cf70 100644
--- a/apps/web/components/new/add-document/connections.tsx
+++ b/apps/web/components/new/add-document/connections.tsx
@@ -53,6 +53,7 @@ export function ConnectContent({ selectedProject }: ConnectContentProps) {
const [isProUser, setIsProUser] = useState(false)
const [connectingProvider, setConnectingProvider] =
useState<ConnectorProvider | null>(null)
+ const [isUpgrading, setIsUpgrading] = useState(false)
// Check Pro status
useEffect(() => {
@@ -65,6 +66,20 @@ export function ConnectContent({ selectedProject }: ConnectContentProps) {
}
}, [autumn.isLoading, autumn.customer])
+ const handleUpgrade = async () => {
+ setIsUpgrading(true)
+ try {
+ await autumn.attach({
+ productId: "consumer_pro",
+ successUrl: window.location.href,
+ })
+ } catch (error) {
+ console.error("Upgrade error:", error)
+ toast.error("Failed to start upgrade process")
+ setIsUpgrading(false)
+ }
+ }
+
// Check connections feature limits
const { data: connectionsCheck } = fetchConnectionsFeature(
autumn,
@@ -359,15 +374,25 @@ export function ConnectContent({ selectedProject }: ConnectContentProps) {
{!isProUser ? (
<>
<p className="text-[14px] text-[#737373] mb-4 text-center">
- <a
- href="/pricing"
- className="underline text-[#737373] hover:text-white"
- >
- Upgrade to Pro
- </a>{" "}
- to get
- <br />
- Supermemory Connections
+ {isUpgrading || autumn.isLoading ? (
+ <span className="inline-flex items-center gap-2">
+ <Loader className="h-4 w-4 animate-spin" />
+ Upgrading...
+ </span>
+ ) : (
+ <>
+ <button
+ type="button"
+ onClick={handleUpgrade}
+ className="underline text-[#737373] hover:text-white transition-colors cursor-pointer"
+ >
+ Upgrade to Pro
+ </button>{" "}
+ to get
+ <br />
+ Supermemory Connections
+ </>
+ )}
</p>
<div className="space-y-2 text-[14px]">
<div className="flex items-center gap-2">
diff --git a/apps/web/components/new/add-document/index.tsx b/apps/web/components/new/add-document/index.tsx
index fe4e8966..9e117912 100644
--- a/apps/web/components/new/add-document/index.tsx
+++ b/apps/web/components/new/add-document/index.tsx
@@ -30,7 +30,9 @@ import {
DropdownMenuTrigger,
} from "@repo/ui/components/dropdown-menu"
import { toast } from "sonner"
-import { useDocumentMutations } from "./useDocumentMutations"
+import { useDocumentMutations } from "../../../hooks/use-document-mutations"
+import { useCustomer } from "autumn-js/react"
+import { useMemoriesUsage } from "@/hooks/use-memories-usage"
type TabType = "note" | "link" | "file" | "connect"
@@ -132,6 +134,15 @@ export function AddDocument({
onClose,
})
+ const autumn = useCustomer()
+ const {
+ memoriesUsed,
+ memoriesLimit,
+ hasProProduct,
+ isLoading: isLoadingMemories,
+ usagePercent,
+ } = useMemoriesUsage(autumn)
+
useEffect(() => {
setLocalSelectedProject(globalSelectedProject)
}, [globalSelectedProject])
@@ -242,7 +253,7 @@ export function AddDocument({
noteMutation.isPending || linkMutation.isPending || fileMutation.isPending
return (
- <div className="h-full flex flex-row text-white space-x-6">
+ <div className="h-full flex flex-row text-white space-x-5">
<div className="w-1/3 flex flex-col justify-between">
<div className="flex flex-col gap-1">
{tabs.map((tab) => (
@@ -266,7 +277,7 @@ export function AddDocument({
"0 2.842px 14.211px 0 rgba(0, 0, 0, 0.25), 0.711px 0.711px 0.711px 0 rgba(255, 255, 255, 0.10) inset",
}}
>
- <div className="flex justify-between items-center mb-2">
+ <div className="flex justify-between items-center">
<span
className={cn(
"text-white text-[16px] font-medium",
@@ -276,19 +287,25 @@ export function AddDocument({
Memories
</span>
<span className={cn("text-[#737373] text-sm", dmSansClassName())}>
- 120/200
+ {isLoadingMemories
+ ? "…"
+ : hasProProduct
+ ? "Unlimited"
+ : `${memoriesUsed}/${memoriesLimit}`}
</span>
</div>
- <div className="h-1.5 bg-[#0D121A] rounded-full overflow-hidden">
- <div
- className="h-full bg-[#2261CA] rounded-full"
- style={{ width: "60%" }}
- />
- </div>
+ {!hasProProduct && (
+ <div className="h-1.5 bg-[#0D121A] rounded-full overflow-hidden mt-2">
+ <div
+ className="h-full bg-[#2261CA] rounded-full"
+ style={{ width: `${usagePercent}%` }}
+ />
+ </div>
+ )}
</div>
</div>
- <div className="w-2/3 overflow-auto flex flex-col justify-between">
+ <div className="w-2/3 overflow-auto flex flex-col justify-between px-1 scrollbar-thin">
{activeTab === "note" && (
<NoteContent
onSubmit={handleNoteSubmit}
diff --git a/apps/web/components/new/add-document/link.tsx b/apps/web/components/new/add-document/link.tsx
index 0c50d194..544af86d 100644
--- a/apps/web/components/new/add-document/link.tsx
+++ b/apps/web/components/new/add-document/link.tsx
@@ -5,11 +5,14 @@ import { cn } from "@lib/utils"
import { Button } from "@ui/components/button"
import { dmSansClassName } from "@/utils/fonts"
import { useHotkeys } from "react-hotkeys-hook"
+import { Image as ImageIcon, Loader2 } from "lucide-react"
+import { toast } from "sonner"
export interface LinkData {
url: string
title: string
description: string
+ image?: string
}
interface LinkContentProps {
@@ -19,26 +22,50 @@ interface LinkContentProps {
isOpen?: boolean
}
-export function LinkContent({ onSubmit, onDataChange, isSubmitting, isOpen }: LinkContentProps) {
+export function LinkContent({
+ onSubmit,
+ onDataChange,
+ isSubmitting,
+ isOpen,
+}: LinkContentProps) {
const [url, setUrl] = useState("")
const [title, setTitle] = useState("")
const [description, setDescription] = useState("")
+ const [image, setImage] = useState<string | undefined>(undefined)
+ const [isPreviewLoading, setIsPreviewLoading] = useState(false)
const canSubmit = url.trim().length > 0 && !isSubmitting
const handleSubmit = () => {
if (canSubmit && onSubmit) {
- onSubmit({ url, title, description })
+ let normalizedUrl = url.trim()
+ if (
+ !normalizedUrl.startsWith("http://") &&
+ !normalizedUrl.startsWith("https://")
+ ) {
+ normalizedUrl = `https://${normalizedUrl}`
+ }
+ onSubmit({ url: normalizedUrl, title, description })
}
}
- const updateData = (newUrl: string, newTitle: string, newDescription: string) => {
- onDataChange?.({ url: newUrl, title: newTitle, description: newDescription })
+ const updateData = (
+ newUrl: string,
+ newTitle: string,
+ newDescription: string,
+ newImage?: string,
+ ) => {
+ onDataChange?.({
+ url: newUrl,
+ title: newTitle,
+ description: newDescription,
+ ...(newImage && { image: newImage }),
+ })
}
const handleUrlChange = (newUrl: string) => {
setUrl(newUrl)
- updateData(newUrl, title, description)
+ updateData(newUrl, title, description, image)
}
const handleTitleChange = (newTitle: string) => {
@@ -48,7 +75,60 @@ export function LinkContent({ onSubmit, onDataChange, isSubmitting, isOpen }: Li
const handleDescriptionChange = (newDescription: string) => {
setDescription(newDescription)
- updateData(url, title, newDescription)
+ updateData(url, title, newDescription, image)
+ }
+
+ const handlePreviewLink = async () => {
+ if (!url.trim()) {
+ toast.error("Please enter a URL first")
+ return
+ }
+
+ let normalizedUrl = url.trim()
+ if (
+ !normalizedUrl.startsWith("http://") &&
+ !normalizedUrl.startsWith("https://")
+ ) {
+ normalizedUrl = `https://${normalizedUrl}`
+ setUrl(normalizedUrl)
+ updateData(normalizedUrl, title, description, image)
+ }
+
+ setIsPreviewLoading(true)
+ try {
+ const response = await fetch(
+ `/api/og?url=${encodeURIComponent(normalizedUrl)}`,
+ )
+
+ if (!response.ok) {
+ const errorData = await response.json().catch(() => ({}))
+ throw new Error(errorData.error || "Failed to fetch preview")
+ }
+
+ const data = await response.json()
+
+ const newTitle = data.title || ""
+ const newDescription = data.description || ""
+ const newImage = data.image || undefined
+
+ setTitle(newTitle)
+ setDescription(newDescription)
+ setImage(newImage)
+ updateData(url, newTitle, newDescription, newImage)
+
+ if (!newTitle && !newDescription && !newImage) {
+ toast.info("No Open Graph data found for this URL")
+ } else {
+ toast.success("Preview loaded successfully")
+ }
+ } catch (error) {
+ console.error("Preview error:", error)
+ toast.error(
+ error instanceof Error ? error.message : "Failed to load preview",
+ )
+ } finally {
+ setIsPreviewLoading(false)
+ }
}
useHotkeys("mod+enter", handleSubmit, {
@@ -62,12 +142,13 @@ export function LinkContent({ onSubmit, onDataChange, isSubmitting, isOpen }: Li
setUrl("")
setTitle("")
setDescription("")
+ setImage(undefined)
onDataChange?.({ url: "", title: "", description: "" })
}
}, [isOpen, onDataChange])
return (
- <div className={cn("flex flex-col space-y-4 pt-4", dmSansClassName())}>
+ <div className={cn("flex flex-col space-y-4 pt-4 mb-4", dmSansClassName())}>
<div>
<p
className={cn("text-[16px] font-medium pl-2 pb-2", dmSansClassName())}
@@ -79,14 +160,24 @@ export function LinkContent({ onSubmit, onDataChange, isSubmitting, isOpen }: Li
type="text"
value={url}
onChange={(e) => handleUrlChange(e.target.value)}
- placeholder="https://maheshthedev.me"
+ placeholder="https://example.com"
disabled={isSubmitting}
- className={cn(
- "w-full p-4 rounded-xl bg-[#14161A] shadow-inside-out disabled:opacity-50",
- )}
+ className="w-full p-4 rounded-xl bg-[#14161A] shadow-inside-out disabled:opacity-50 outline-1 outline-transparent focus:outline-[#525D6EB2]"
/>
- <Button variant="linkPreview" className="absolute right-2 top-2" disabled={isSubmitting}>
- Preview Link
+ <Button
+ variant="linkPreview"
+ className="absolute right-2 top-2"
+ disabled={isSubmitting || isPreviewLoading || !url.trim()}
+ onClick={handlePreviewLink}
+ >
+ {isPreviewLoading ? (
+ <>
+ <Loader2 className="size-4 animate-spin mr-2" />
+ Loading...
+ </>
+ ) : (
+ "Preview Link"
+ )}
</Button>
</div>
</div>
@@ -100,8 +191,8 @@ export function LinkContent({ onSubmit, onDataChange, isSubmitting, isOpen }: Li
value={title}
onChange={(e) => handleTitleChange(e.target.value)}
placeholder="Mahesh Sanikommu - Portfolio"
- disabled={isSubmitting}
- className="w-full px-4 py-3 bg-[#0F1217] rounded-xl disabled:opacity-50"
+ disabled
+ className="w-full px-4 py-3 bg-[#0F1217] rounded-xl disabled:opacity-50 outline-1 outline-transparent focus:outline-[#525D6EB2]"
/>
</div>
<div>
@@ -112,17 +203,34 @@ export function LinkContent({ onSubmit, onDataChange, isSubmitting, isOpen }: Li
value={description}
onChange={(e) => handleDescriptionChange(e.target.value)}
placeholder="Portfolio website of Mahesh Sanikommu"
- disabled={isSubmitting}
- className="w-full px-4 py-3 bg-[#0F1217] rounded-xl disabled:opacity-50"
+ disabled
+ className="w-full px-4 py-3 bg-[#0F1217] rounded-xl resize-none disabled:opacity-50 outline-1 outline-transparent focus:outline-[#525D6EB2]"
/>
</div>
<div>
<p className="pl-2 pb-2 font-semibold text-[16px] text-[#737373]">
- Link Preview
+ Link Preview Image
</p>
- <div className="w-full px-4 py-3 bg-[#0F1217] rounded-xl">
- <p>{description || "Portfolio website of Mahesh Sanikommu"}</p>
- </div>
+ {image ? (
+ <div className="w-full max-w-md aspect-4/2 bg-[#0F1217] rounded-xl overflow-hidden">
+ <img
+ src={image}
+ alt={title || "Link preview"}
+ className="w-full h-full object-cover"
+ onError={(e) => {
+ e.currentTarget.style.display = "none"
+ e.currentTarget.parentElement?.classList.add("opacity-50")
+ e.currentTarget.parentElement?.classList.add("flex")
+ e.currentTarget.parentElement?.classList.add("items-center")
+ e.currentTarget.parentElement?.classList.add("justify-center")
+ }}
+ />
+ </div>
+ ) : (
+ <div className="w-full max-w-md aspect-4/2 bg-[#0F1217] opacity-50 rounded-xl flex items-center justify-center">
+ <ImageIcon className="w-8 h-8 text-[#737373]" />
+ </div>
+ )}
</div>
</div>
</div>
diff --git a/apps/web/components/new/add-document/note.tsx b/apps/web/components/new/add-document/note.tsx
index c7c2a2ac..465e295a 100644
--- a/apps/web/components/new/add-document/note.tsx
+++ b/apps/web/components/new/add-document/note.tsx
@@ -10,7 +10,12 @@ interface NoteContentProps {
isOpen?: boolean
}
-export function NoteContent({ onSubmit, onContentChange, isSubmitting, isOpen }: NoteContentProps) {
+export function NoteContent({
+ onSubmit,
+ onContentChange,
+ isSubmitting,
+ isOpen,
+}: NoteContentProps) {
const [content, setContent] = useState("")
const canSubmit = content.trim().length > 0 && !isSubmitting
@@ -45,7 +50,7 @@ export function NoteContent({ onSubmit, onContentChange, isSubmitting, isOpen }:
onChange={(e) => handleContentChange(e.target.value)}
placeholder="Write your note here..."
disabled={isSubmitting}
- className="w-full h-full p-4 mb-4! rounded-[14px] bg-[#14161A] shadow-inside-out resize-none disabled:opacity-50"
+ className="w-full h-full p-4 mb-4! rounded-[14px] bg-[#14161A] shadow-inside-out resize-none disabled:opacity-50 outline-none"
/>
)
}
diff --git a/apps/web/components/new/add-document/useDocumentMutations.ts b/apps/web/components/new/add-document/useDocumentMutations.ts
deleted file mode 100644
index fc57bbea..00000000
--- a/apps/web/components/new/add-document/useDocumentMutations.ts
+++ /dev/null
@@ -1,313 +0,0 @@
-"use client"
-
-import { useMutation, useQueryClient } from "@tanstack/react-query"
-import { toast } from "sonner"
-import { $fetch } from "@lib/api"
-
-interface DocumentsQueryData {
- documents: unknown[]
- totalCount: number
-}
-
-interface UseDocumentMutationsOptions {
- onClose: () => void
-}
-
-export function useDocumentMutations({ onClose }: UseDocumentMutationsOptions) {
- const queryClient = useQueryClient()
-
- const noteMutation = useMutation({
- mutationFn: async ({
- content,
- project,
- }: {
- content: string
- project: string
- }) => {
- const response = await $fetch("@post/documents", {
- body: {
- content: content,
- containerTags: [project],
- metadata: {
- sm_source: "consumer",
- },
- },
- })
-
- if (response.error) {
- throw new Error(response.error?.message || "Failed to add note")
- }
-
- return response.data
- },
- onMutate: async ({ content, project }) => {
- await queryClient.cancelQueries({
- queryKey: ["documents-with-memories", project],
- })
-
- const previousMemories = queryClient.getQueryData([
- "documents-with-memories",
- project,
- ])
-
- const optimisticMemory = {
- id: `temp-${Date.now()}`,
- content: content,
- url: null,
- title: content.substring(0, 100),
- description: "Processing content...",
- containerTags: [project],
- createdAt: new Date().toISOString(),
- updatedAt: new Date().toISOString(),
- status: "queued",
- type: "note",
- metadata: {
- processingStage: "queued",
- processingMessage: "Added to processing queue",
- },
- memoryEntries: [],
- isOptimistic: true,
- }
-
- queryClient.setQueryData(
- ["documents-with-memories", project],
- (old: DocumentsQueryData | undefined) => {
- if (!old) return { documents: [optimisticMemory], totalCount: 1 }
- return {
- ...old,
- documents: [optimisticMemory, ...old.documents],
- totalCount: old.totalCount + 1,
- }
- },
- )
-
- return { previousMemories }
- },
- onError: (_error, variables, context) => {
- if (context?.previousMemories) {
- queryClient.setQueryData(
- ["documents-with-memories", variables.project],
- context.previousMemories,
- )
- }
- toast.error("Failed to add note", {
- description: _error instanceof Error ? _error.message : "Unknown error",
- })
- },
- onSuccess: (_data, variables) => {
- toast.success("Note added successfully!", {
- description: "Your note is being processed",
- })
- queryClient.invalidateQueries({
- queryKey: ["documents-with-memories", variables.project],
- })
- onClose()
- },
- })
-
- const linkMutation = useMutation({
- mutationFn: async ({ url, project }: { url: string; project: string }) => {
- const response = await $fetch("@post/documents", {
- body: {
- content: url,
- containerTags: [project],
- metadata: {
- sm_source: "consumer",
- },
- },
- })
-
- if (response.error) {
- throw new Error(response.error?.message || "Failed to add link")
- }
-
- return response.data
- },
- onMutate: async ({ url, project }) => {
- await queryClient.cancelQueries({
- queryKey: ["documents-with-memories", project],
- })
-
- const previousMemories = queryClient.getQueryData([
- "documents-with-memories",
- project,
- ])
-
- const optimisticMemory = {
- id: `temp-${Date.now()}`,
- content: "",
- url: url,
- title: "Processing...",
- description: "Extracting content...",
- containerTags: [project],
- createdAt: new Date().toISOString(),
- updatedAt: new Date().toISOString(),
- status: "queued",
- type: "link",
- metadata: {
- processingStage: "queued",
- processingMessage: "Added to processing queue",
- },
- memoryEntries: [],
- isOptimistic: true,
- }
-
- queryClient.setQueryData(
- ["documents-with-memories", project],
- (old: DocumentsQueryData | undefined) => {
- if (!old) return { documents: [optimisticMemory], totalCount: 1 }
- return {
- ...old,
- documents: [optimisticMemory, ...old.documents],
- totalCount: old.totalCount + 1,
- }
- },
- )
-
- return { previousMemories }
- },
- onError: (_error, variables, context) => {
- if (context?.previousMemories) {
- queryClient.setQueryData(
- ["documents-with-memories", variables.project],
- context.previousMemories,
- )
- }
- toast.error("Failed to add link", {
- description: _error instanceof Error ? _error.message : "Unknown error",
- })
- },
- onSuccess: (_data, variables) => {
- toast.success("Link added successfully!", {
- description: "Your link is being processed",
- })
- queryClient.invalidateQueries({
- queryKey: ["documents-with-memories", variables.project],
- })
- onClose()
- },
- })
-
- const fileMutation = useMutation({
- mutationFn: async ({
- file,
- title,
- description,
- project,
- }: {
- file: File
- title?: string
- description?: string
- project: string
- }) => {
- const formData = new FormData()
- formData.append("file", file)
- formData.append("containerTags", JSON.stringify([project]))
- formData.append(
- "metadata",
- JSON.stringify({
- sm_source: "consumer",
- }),
- )
-
- const response = await fetch(
- `${process.env.NEXT_PUBLIC_BACKEND_URL}/v3/documents/file`,
- {
- method: "POST",
- body: formData,
- credentials: "include",
- },
- )
-
- if (!response.ok) {
- const error = await response.json()
- throw new Error(error.error || "Failed to upload file")
- }
-
- const data = await response.json()
-
- if (title || description) {
- await $fetch(`@patch/documents/${data.id}`, {
- body: {
- metadata: {
- ...(title && { title }),
- ...(description && { description }),
- sm_source: "consumer",
- },
- },
- })
- }
-
- return data
- },
- onMutate: async ({ file, title, description, project }) => {
- await queryClient.cancelQueries({
- queryKey: ["documents-with-memories", project],
- })
-
- const previousMemories = queryClient.getQueryData([
- "documents-with-memories",
- project,
- ])
-
- const optimisticMemory = {
- id: `temp-file-${Date.now()}`,
- content: "",
- url: null,
- title: title || file.name,
- description: description || `Uploading ${file.name}...`,
- containerTags: [project],
- createdAt: new Date().toISOString(),
- updatedAt: new Date().toISOString(),
- status: "processing",
- type: "file",
- metadata: {
- fileName: file.name,
- fileSize: file.size,
- mimeType: file.type,
- },
- memoryEntries: [],
- }
-
- queryClient.setQueryData(
- ["documents-with-memories", project],
- (old: DocumentsQueryData | undefined) => {
- if (!old) return { documents: [optimisticMemory], totalCount: 1 }
- return {
- ...old,
- documents: [optimisticMemory, ...old.documents],
- totalCount: old.totalCount + 1,
- }
- },
- )
-
- return { previousMemories }
- },
- onError: (_error, variables, context) => {
- if (context?.previousMemories) {
- queryClient.setQueryData(
- ["documents-with-memories", variables.project],
- context.previousMemories,
- )
- }
- toast.error("Failed to upload file", {
- description: _error instanceof Error ? _error.message : "Unknown error",
- })
- },
- onSuccess: (_data, variables) => {
- toast.success("File uploaded successfully!", {
- description: "Your file is being processed",
- })
- queryClient.invalidateQueries({
- queryKey: ["documents-with-memories", variables.project],
- })
- onClose()
- },
- })
-
- return {
- noteMutation,
- linkMutation,
- fileMutation,
- }
-}
diff --git a/apps/web/components/new/header.tsx b/apps/web/components/new/header.tsx
index 4ef344a3..a5aa47d3 100644
--- a/apps/web/components/new/header.tsx
+++ b/apps/web/components/new/header.tsx
@@ -12,6 +12,9 @@ import {
FolderIcon,
LogOut,
Settings,
+ Home,
+ Code2,
+ ExternalLink,
} from "lucide-react"
import { Button } from "@ui/components/button"
import { cn } from "@lib/utils"
@@ -31,6 +34,7 @@ import { DEFAULT_PROJECT_ID } from "@repo/lib/constants"
import { useProjectMutations } from "@/hooks/use-project-mutations"
import { useProject } from "@/stores"
import { useRouter } from "next/navigation"
+import Link from "next/link"
import type { Project } from "@repo/lib/types"
interface HeaderProps {
@@ -69,19 +73,62 @@ export function Header({ onAddMemory, onOpenMCP }: HeaderProps) {
return (
<div className="flex p-4 justify-between items-center">
<div className="flex items-center justify-center gap-4 z-10!">
- <div className="flex items-center">
- <Logo className="h-7" />
- {name && (
- <div className="flex flex-col items-start justify-center ml-2">
- <p className="text-[#8B8B8B] text-[11px] leading-tight">
- {userName}
- </p>
- <p className="text-white font-bold text-xl leading-none -mt-1">
- supermemory
- </p>
- </div>
- )}
- </div>
+ <DropdownMenu>
+ <DropdownMenuTrigger asChild>
+ <button
+ type="button"
+ className="flex items-center rounded-lg px-2 py-1.5 -ml-2 cursor-pointer hover:bg-white/5 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/50 transition-colors"
+ >
+ <Logo className="h-7" />
+ {name && (
+ <div className="flex flex-col items-start justify-center ml-2">
+ <p className="text-[#8B8B8B] text-[11px] leading-tight">
+ {userName}
+ </p>
+ <p className="text-white font-bold text-xl leading-none -mt-1">
+ supermemory
+ </p>
+ </div>
+ )}
+ </button>
+ </DropdownMenuTrigger>
+ <DropdownMenuContent
+ align="start"
+ className="w-56 bg-[#0D121A] rounded-xl border-none p-1.5 ml-4 shadow-[0_0_20px_rgba(255,255,255,0.15)]"
+ >
+ <DropdownMenuItem
+ asChild
+ className="px-3 py-2 rounded-md hover:bg-[#293952]/40 cursor-pointer"
+ >
+ <Link href="/new">
+ <Home className="h-4 w-4" />
+ Home
+ </Link>
+ </DropdownMenuItem>
+ <DropdownMenuItem
+ asChild
+ className="px-3 py-2 rounded-md hover:bg-[#293952]/40 cursor-pointer"
+ >
+ <a
+ href="https://console.supermemory.ai"
+ target="_blank"
+ rel="noreferrer"
+ >
+ <Code2 className="h-4 w-4" />
+ Developer console
+ </a>
+ </DropdownMenuItem>
+ <DropdownMenuItem
+ asChild
+ className="px-3 py-2 rounded-md hover:bg-[#293952]/40 cursor-pointer"
+ >
+ <a href="https://supermemory.ai" target="_blank" rel="noreferrer">
+ <ExternalLink className="h-4 w-4" />
+ supermemory.ai
+ </a>
+ </DropdownMenuItem>
+ </DropdownMenuContent>
+ </DropdownMenu>
<div className="self-stretch w-px bg-[#FFFFFF33]" />
<div className="flex items-center gap-2">
<p>📁 {projectName}</p>
@@ -212,7 +259,10 @@ export function Header({ onAddMemory, onOpenMCP }: HeaderProps) {
<Settings className="h-4 w-4" />
Settings
</DropdownMenuItem>
- <DropdownMenuItem onClick={() => authClient.signOut()}>
+ <DropdownMenuItem onClick={() => {
+ authClient.signOut()
+ router.push("/login/new")
+ }}>
<LogOut className="h-4 w-4" />
Logout
</DropdownMenuItem>
diff --git a/apps/web/components/new/settings/account.tsx b/apps/web/components/new/settings/account.tsx
index 7ac0598d..10967947 100644
--- a/apps/web/components/new/settings/account.tsx
+++ b/apps/web/components/new/settings/account.tsx
@@ -3,8 +3,8 @@
import { dmSans125ClassName } from "@/utils/fonts"
import { cn } from "@lib/utils"
import { useAuth } from "@lib/auth-context"
-import { fetchMemoriesFeature, fetchSubscriptionStatus } from "@lib/queries"
import { Avatar, AvatarFallback, AvatarImage } from "@ui/components/avatar"
+import { useMemoriesUsage } from "@/hooks/use-memories-usage"
import {
Dialog,
DialogContent,
@@ -82,26 +82,13 @@ export default function Account() {
const [deleteConfirmText, setDeleteConfirmText] = useState("")
const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false)
- // Billing data
const {
- data: status = {
- consumer_pro: { allowed: false, status: null },
- },
+ memoriesUsed,
+ memoriesLimit,
+ hasProProduct,
isLoading: isCheckingStatus,
- } = fetchSubscriptionStatus(autumn, !autumn.isLoading)
-
- const proStatus = status.consumer_pro
- const hasProProduct = proStatus?.status !== null
-
- const { data: memoriesCheck } = fetchMemoriesFeature(
- autumn,
- !autumn.isLoading && !isCheckingStatus,
- )
- const memoriesUsed = memoriesCheck?.usage ?? 0
- const memoriesLimit = memoriesCheck?.included_usage ?? 200
-
- // Calculate progress percentage
- const usagePercent = Math.min((memoriesUsed / memoriesLimit) * 100, 100)
+ usagePercent,
+ } = useMemoriesUsage(autumn)
// Handlers
const handleUpgrade = async () => {
diff --git a/apps/web/components/views/add-memory/action-buttons.tsx b/apps/web/components/views/add-memory/action-buttons.tsx
index a85042e5..ae60e9fa 100644
--- a/apps/web/components/views/add-memory/action-buttons.tsx
+++ b/apps/web/components/views/add-memory/action-buttons.tsx
@@ -40,7 +40,7 @@ export function ActionButtons({
className="flex-1 sm:flex-initial"
>
<Button
- className="w-full cursor-pointer"
+ className="w-full cursor-pointer text-black dark:text-white"
disabled={isSubmitting || isSubmitDisabled}
onClick={submitType === "button" ? onSubmit : undefined}
type={submitType}