fix(web): sentry issues across the web app (#570)

Fixes all following sentry issues
- CONSUMER-APP-FF
- CONSUMER-APP-1T
- CONSUMER-APP-86
- CONSUMER-APP-7H
- CONSUMER-APP-4F
- CONSUMER-APP-7X
This commit is contained in:
MaheshtheDev 2025-11-09 07:32:51 +00:00
parent 8f1eda6b60
commit 10ada4a1e2
8 changed files with 193 additions and 86 deletions

View file

@ -62,6 +62,9 @@ export function GraphDialog() {
return response.data
},
getNextPageParam: (lastPage, allPages) => {
if (!lastPage || !lastPage.pagination) return undefined
if (!Array.isArray(allPages)) return undefined
const loaded = allPages.reduce(
(acc, p) => acc + (p.documents?.length ?? 0),
0,

View file

@ -60,6 +60,9 @@ export function Memories() {
return response.data
},
getNextPageParam: (lastPage, allPages) => {
if (!lastPage || !lastPage.pagination) return undefined
if (!Array.isArray(allPages)) return undefined
const loaded = allPages.reduce(
(acc, p) => acc + (p.documents?.length ?? 0),
0,
@ -177,42 +180,22 @@ export function Memories() {
}
return (
<>
<div className="relative h-full mx-4 md:mx-24">
<MasonryMemoryList
documents={allDocuments}
error={error}
hasMore={hasMore}
isLoading={isPending}
isLoadingMore={isLoadingMore}
loadMoreDocuments={loadMoreDocuments}
totalLoaded={totalLoaded}
>
<div className="absolute inset-0 flex items-center justify-center">
{!isMobile ? (
<ConnectAIModal
onOpenChange={setShowConnectAIModal}
open={showConnectAIModal}
>
<div className="rounded-xl overflow-hidden cursor-pointer hover:bg-white/5 transition-colors p-6">
<div className="relative z-10 text-slate-200 text-center">
<div className="flex flex-col gap-3">
<button
className="text-sm text-blue-400 hover:text-blue-300 transition-colors underline"
onClick={(e) => {
e.stopPropagation()
setShowAddMemoryView(true)
setShowConnectAIModal(false)
}}
type="button"
>
Add your first memory
</button>
</div>
</div>
</div>
</ConnectAIModal>
) : (
<div className="relative h-full mx-4 md:mx-24">
<MasonryMemoryList
documents={allDocuments}
error={error}
hasMore={hasMore}
isLoading={isPending}
isLoadingMore={isLoadingMore}
loadMoreDocuments={loadMoreDocuments}
totalLoaded={totalLoaded}
>
<div className="absolute inset-0 flex items-center justify-center">
{!isMobile ? (
<ConnectAIModal
onOpenChange={setShowConnectAIModal}
open={showConnectAIModal}
>
<div className="rounded-xl overflow-hidden cursor-pointer hover:bg-white/5 transition-colors p-6">
<div className="relative z-10 text-slate-200 text-center">
<div className="flex flex-col gap-3">
@ -221,6 +204,7 @@ export function Memories() {
onClick={(e) => {
e.stopPropagation()
setShowAddMemoryView(true)
setShowConnectAIModal(false)
}}
type="button"
>
@ -229,17 +213,34 @@ export function Memories() {
</div>
</div>
</div>
)}
</div>
</MasonryMemoryList>
</ConnectAIModal>
) : (
<div className="rounded-xl overflow-hidden cursor-pointer hover:bg-white/5 transition-colors p-6">
<div className="relative z-10 text-slate-200 text-center">
<div className="flex flex-col gap-3">
<button
className="text-sm text-blue-400 hover:text-blue-300 transition-colors underline"
onClick={(e) => {
e.stopPropagation()
setShowAddMemoryView(true)
}}
type="button"
>
Add your first memory
</button>
</div>
</div>
</div>
)}
</div>
</MasonryMemoryList>
{showAddMemoryView && (
<AddMemoryView
initialTab="note"
onClose={() => setShowAddMemoryView(false)}
/>
)}
</div>
</>
{showAddMemoryView && (
<AddMemoryView
initialTab="note"
onClose={() => setShowAddMemoryView(false)}
/>
)}
</div>
)
}
}

View file

@ -1,6 +1,6 @@
"use client"
import { useChat, useCompletion } from "@ai-sdk/react"
import { useChat, useCompletion, type UIMessage } from "@ai-sdk/react"
import { cn } from "@lib/utils"
import { Button } from "@ui/components/button"
import { DefaultChatTransport } from "ai"
@ -21,6 +21,7 @@ import { usePersistentChat, useProject } from "@/stores"
import { useGraphHighlights } from "@/stores/highlights"
import { modelNames, ModelIcon } from "@/lib/models"
import { Spinner } from "../../spinner"
import { areUIMessageArraysEqual } from "@/stores/chat"
interface MemoryResult {
documentId?: string
@ -247,6 +248,10 @@ export function ChatMessages() {
const activeChatIdRef = useRef<string | null>(null)
const shouldGenerateTitleRef = useRef<boolean>(false)
const hasRunInitialMessageRef = useRef<boolean>(false)
const lastSavedMessagesRef = useRef<UIMessage[] | null>(null)
const lastSavedActiveIdRef = useRef<string | null>(null)
const lastLoadedChatIdRef = useRef<string | null>(null)
const lastLoadedMessagesRef = useRef<UIMessage[] | null>(null)
const { setDocumentIds } = useGraphHighlights()
@ -282,6 +287,10 @@ export function ChatMessages() {
},
})
useEffect(() => {
lastLoadedMessagesRef.current = messages
}, [messages])
useEffect(() => {
activeChatIdRef.current = currentChatId ?? id ?? null
}, [currentChatId, id])
@ -326,20 +335,56 @@ export function ChatMessages() {
}, [id, currentChatId, setCurrentChatId])
useEffect(() => {
const msgs = getCurrentConversation()
if (msgs && msgs.length > 0) {
setMessages(msgs)
} else if (!currentChatId) {
setMessages([])
if (currentChatId !== lastLoadedChatIdRef.current) {
lastLoadedMessagesRef.current = null
lastSavedMessagesRef.current = null
}
if (currentChatId === lastLoadedChatIdRef.current) {
setInput("")
return
}
const msgs = getCurrentConversation()
if (msgs && msgs.length > 0) {
const currentMessages = lastLoadedMessagesRef.current
if (!currentMessages || !areUIMessageArraysEqual(currentMessages, msgs)) {
lastLoadedMessagesRef.current = msgs
setMessages(msgs)
}
} else if (!currentChatId) {
if (
lastLoadedMessagesRef.current &&
lastLoadedMessagesRef.current.length > 0
) {
lastLoadedMessagesRef.current = []
setMessages([])
}
}
lastLoadedChatIdRef.current = currentChatId
setInput("")
}, [currentChatId, getCurrentConversation, setMessages])
useEffect(() => {
const activeId = currentChatId ?? id
if (activeId && messages.length > 0) {
setConversation(activeId, messages)
if (!activeId || messages.length === 0) {
return
}
if (activeId !== lastSavedActiveIdRef.current) {
lastSavedMessagesRef.current = null
lastSavedActiveIdRef.current = activeId
}
const lastSaved = lastSavedMessagesRef.current
if (lastSaved && areUIMessageArraysEqual(lastSaved, messages)) {
return
}
lastSavedMessagesRef.current = messages
setConversation(activeId, messages)
}, [messages, currentChatId, id, setConversation])
const { complete } = useCompletion({

View file

@ -7,7 +7,7 @@ import { indexedDBStorage } from "./indexeddb-storage"
/**
* Deep equality check for UIMessage arrays to prevent unnecessary state updates
*/
function areUIMessageArraysEqual(a: UIMessage[], b: UIMessage[]): boolean {
export function areUIMessageArraysEqual(a: UIMessage[], b: UIMessage[]): boolean {
if (a === b) return true
if (a.length !== b.length) return false

View file

@ -38,17 +38,23 @@ export function AuthProvider({ children }: { children: ReactNode }) {
// biome-ignore lint/correctness/useExhaustiveDependencies: ignoring the setActiveOrg dependency
useEffect(() => {
if (session?.session.activeOrganizationId) {
authClient.organization.getFullOrganization().then((org) => {
// TODO: Uncomment this when we have a way to handle consumer organizations better way
//if (org.metadata?.isConsumer === true) {
setOrg(org)
//} else {
// const consumerOrg = orgs?.find((o) => o.metadata?.isConsumer === true)
// if (consumerOrg) {
// setActiveOrg(consumerOrg.slug)
// }
//}
})
authClient.organization
.getFullOrganization()
.then((org) => {
// TODO: Uncomment this when we have a way to handle consumer organizations better way
//if (org.metadata?.isConsumer === true) {
setOrg(org)
//} else {
// const consumerOrg = orgs?.find((o) => o.metadata?.isConsumer === true)
// if (consumerOrg) {
// setActiveOrg(consumerOrg.slug)
// }
//}
})
.catch((error) => {
// Silently handle organization fetch failures to prevent unhandled rejections
console.error("Failed to fetch organization:", error)
})
}
}, [session?.session.activeOrganizationId, orgs])
@ -68,7 +74,7 @@ export function AuthProvider({ children }: { children: ReactNode }) {
if (pendingMethod) {
const now = Date.now()
const ts = pendingTsRaw ? Number.parseInt(pendingTsRaw, 10) : NaN
const ts = pendingTsRaw ? Number.parseInt(pendingTsRaw, 10) : Number.NaN
const isFresh = Number.isFinite(ts) && now - ts < 10 * 60 * 1000 // 10 minutes TTL
if (isFresh) {

View file

@ -22,7 +22,7 @@ function PostHogPageTracking() {
$current_url: url,
path: pathname,
search_params: searchParams.toString(),
page_type: getPageType(pathname),
page_type: getPageType(),
org_slug: getOrgSlug(pathname),
}
@ -39,16 +39,21 @@ export function PostHogProvider({ children }: { children: React.ReactNode }) {
useEffect(() => {
if (typeof window !== "undefined") {
const posthogKey = process.env.NEXT_PUBLIC_POSTHOG_KEY
if (posthogKey){
posthog.init(posthogKey, {
api_host: process.env.NEXT_PUBLIC_BACKEND_URL + "/orange",
ui_host: "https://us.i.posthog.com",
person_profiles: "identified_only",
capture_pageview: false,
capture_pageleave: true,
})}
else{
console.warn("PostHog API key is not set. PostHog will not be initialized.")
const backendUrl =
process.env.NEXT_PUBLIC_BACKEND_URL ?? "https://api.supermemory.ai"
if (posthogKey) {
posthog.init(posthogKey, {
api_host: `${backendUrl}/orange`,
ui_host: "https://us.i.posthog.com",
person_profiles: "identified_only",
capture_pageview: false,
capture_pageleave: true,
})
} else {
console.warn(
"PostHog API key is not set. PostHog will not be initialized.",
)
}
}
}, [])
@ -75,7 +80,7 @@ export function PostHogProvider({ children }: { children: React.ReactNode }) {
)
}
function getPageType(pathname: string): string {
function getPageType(): string {
return "other"
}

View file

@ -761,6 +761,7 @@ export const GraphWebGLCanvas = memo<GraphCanvasProps>(
}}
>
<Application
preference="webgl"
antialias
autoDensity
backgroundColor={0x0f1419}

View file

@ -80,6 +80,27 @@ export function LoginPage({
} catch {}
}
function isNetworkError(error: unknown): boolean {
if (!(error instanceof Error)) return false;
const message = error.message.toLowerCase();
return (
message.includes("load failed") ||
message.includes("networkerror") ||
message.includes("failed to fetch") ||
message.includes("network request failed")
);
}
function getErrorMessage(error: unknown): string {
if (isNetworkError(error)) {
return "Network error. Please check your connection and try again.";
}
if (error instanceof Error) {
return error.message;
}
return "An unexpected error occurred. Please try again.";
}
// If we land back on this page with an error, clear any pending marker
useEffect(() => {
if (params.get("error")) {
@ -121,13 +142,10 @@ export function LoginPage({
method: "magic_link",
error: error instanceof Error ? error.message : "Unknown error",
email_domain: email.split("@")[1] || "unknown",
is_network_error: isNetworkError(error),
});
setError(
error instanceof Error
? error.message
: "Failed to send login link. Please try again.",
);
setError(getErrorMessage(error));
setIsLoading(false);
setIsLoadingEmail(false);
return;
@ -321,6 +339,7 @@ export function LoginPage({
onClick={() => {
if (isLoading) return;
setIsLoading(true);
setError(null);
posthog.capture("login_attempt", {
method: "social",
provider: "google",
@ -331,6 +350,19 @@ export function LoginPage({
callbackURL: getCallbackURL(),
provider: "google",
})
.catch((error) => {
console.error("Google login error:", error);
posthog.capture("login_failed", {
method: "social",
provider: "google",
error:
error instanceof Error
? error.message
: "Unknown error",
is_network_error: isNetworkError(error),
});
setError(getErrorMessage(error));
})
.finally(() => {
setIsLoading(false);
});
@ -385,6 +417,7 @@ export function LoginPage({
onClick={() => {
if (isLoading) return;
setIsLoading(true);
setError(null);
posthog.capture("login_attempt", {
method: "social",
provider: "github",
@ -395,6 +428,19 @@ export function LoginPage({
callbackURL: getCallbackURL(),
provider: "github",
})
.catch((error) => {
console.error("GitHub login error:", error);
posthog.capture("login_failed", {
method: "social",
provider: "github",
error:
error instanceof Error
? error.message
: "Unknown error",
is_network_error: isNetworkError(error),
});
setError(getErrorMessage(error));
})
.finally(() => {
setIsLoading(false);
});