diff --git a/apps/web/instrumentation.ts b/apps/web/instrumentation.ts
deleted file mode 100644
index 964f937c..00000000
--- a/apps/web/instrumentation.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-import * as Sentry from '@sentry/nextjs';
-
-export async function register() {
- if (process.env.NEXT_RUNTIME === 'nodejs') {
- await import('./sentry.server.config');
- }
-
- if (process.env.NEXT_RUNTIME === 'edge') {
- await import('./sentry.edge.config');
- }
-}
-
-export const onRequestError = Sentry.captureRequestError;
diff --git a/apps/web/lib/document-icon.tsx b/apps/web/lib/document-icon.tsx
new file mode 100644
index 00000000..3a80b2e0
--- /dev/null
+++ b/apps/web/lib/document-icon.tsx
@@ -0,0 +1,54 @@
+import { colors } from '@repo/ui/memory-graph/constants';
+import {
+ GoogleDocs,
+ MicrosoftWord,
+ NotionDoc,
+ GoogleDrive,
+ GoogleSheets,
+ GoogleSlides,
+ PDF,
+ OneDrive,
+ MicrosoftOneNote,
+ MicrosoftPowerpoint,
+ MicrosoftExcel,
+} from '@ui/assets/icons';
+import { FileText } from 'lucide-react';
+
+export const getDocumentIcon = (type: string, className: string) => {
+ const iconProps = {
+ className,
+ style: { color: colors.text.muted },
+ };
+
+ switch (type) {
+ case 'google_doc':
+ return
;
+ case 'google_sheet':
+ return
;
+ case 'google_slide':
+ return
;
+ case 'google_drive':
+ return
;
+ case 'notion':
+ case 'notion_doc':
+ return
;
+ case 'word':
+ case 'microsoft_word':
+ return
;
+ case 'excel':
+ case 'microsoft_excel':
+ return
;
+ case 'powerpoint':
+ case 'microsoft_powerpoint':
+ return
;
+ case 'onenote':
+ case 'microsoft_onenote':
+ return
;
+ case 'onedrive':
+ return
;
+ case 'pdf':
+ return
;
+ default:
+ return
;
+ }
+};
diff --git a/apps/web/sentry.edge.config.ts b/apps/web/sentry.edge.config.ts
deleted file mode 100644
index cff5a86d..00000000
--- a/apps/web/sentry.edge.config.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-// This file configures the initialization of Sentry for edge features (middleware, edge routes, and so on).
-// The config you add here will be used whenever one of the edge features is loaded.
-// Note that this config is unrelated to the Vercel Edge Runtime and is also required when running locally.
-// https://docs.sentry.io/platforms/javascript/guides/nextjs/
-
-import * as Sentry from "@sentry/nextjs";
-
-Sentry.init({
- dsn: "https://2451ebfd1a7490f05fa7776482df81b6@o4508385422802944.ingest.us.sentry.io/4509872269819904",
-
- // Define how likely traces are sampled. Adjust this value in production, or use tracesSampler for greater control.
- tracesSampleRate: 1,
-
- // Enable logs to be sent to Sentry
- enableLogs: true,
-
- // Setting this option to true will print useful information to the console while you're setting up Sentry.
- debug: false,
-});
diff --git a/apps/web/sentry.server.config.ts b/apps/web/sentry.server.config.ts
deleted file mode 100644
index 2cd5afbe..00000000
--- a/apps/web/sentry.server.config.ts
+++ /dev/null
@@ -1,18 +0,0 @@
-// This file configures the initialization of Sentry on the server.
-// The config you add here will be used whenever the server handles a request.
-// https://docs.sentry.io/platforms/javascript/guides/nextjs/
-
-import * as Sentry from "@sentry/nextjs";
-
-Sentry.init({
- dsn: "https://2451ebfd1a7490f05fa7776482df81b6@o4508385422802944.ingest.us.sentry.io/4509872269819904",
-
- // Define how likely traces are sampled. Adjust this value in production, or use tracesSampler for greater control.
- tracesSampleRate: 1,
-
- // Enable logs to be sent to Sentry
- enableLogs: true,
-
- // Setting this option to true will print useful information to the console while you're setting up Sentry.
- debug: false,
-});
diff --git a/packages/lib/auth-context.tsx b/packages/lib/auth-context.tsx
index 5b2d58bc..66ff84bc 100644
--- a/packages/lib/auth-context.tsx
+++ b/packages/lib/auth-context.tsx
@@ -33,6 +33,40 @@ export function AuthProvider({ children }: { children: ReactNode }) {
}
}, [session?.session.activeOrganizationId])
+ // When a session exists and there is a pending login method recorded,
+ // promote it to the last-used method (successful login) and clear pending.
+ useEffect(() => {
+ if (typeof window === "undefined") return
+ if (!session?.session) return
+
+ try {
+ const pendingMethod = localStorage.getItem(
+ "supermemory-pending-login-method",
+ )
+ const pendingTsRaw = localStorage.getItem(
+ "supermemory-pending-login-timestamp",
+ )
+
+ if (pendingMethod) {
+ const now = Date.now()
+ const ts = pendingTsRaw ? Number.parseInt(pendingTsRaw, 10) : NaN
+ const isFresh = Number.isFinite(ts) && now - ts < 10 * 60 * 1000 // 10 minutes TTL
+
+ if (isFresh) {
+ localStorage.setItem(
+ "supermemory-last-login-method",
+ pendingMethod,
+ )
+ }
+ }
+ } catch { }
+ // Always clear pending markers once a session is present
+ try {
+ localStorage.removeItem("supermemory-pending-login-method")
+ localStorage.removeItem("supermemory-pending-login-timestamp")
+ } catch { }
+ }, [session?.session])
+
const setActiveOrg = async (slug: string) => {
if (!slug) return
diff --git a/packages/ui/components/sheet.tsx b/packages/ui/components/sheet.tsx
index 242a4688..fc49af38 100644
--- a/packages/ui/components/sheet.tsx
+++ b/packages/ui/components/sheet.tsx
@@ -83,7 +83,7 @@ function SheetContent({
function SheetHeader({ className, ...props }: React.ComponentProps<"div">) {
return (
diff --git a/packages/ui/pages/login.tsx b/packages/ui/pages/login.tsx
index c14ba4ea..fcd48eae 100644
--- a/packages/ui/pages/login.tsx
+++ b/packages/ui/pages/login.tsx
@@ -1,25 +1,26 @@
-"use client";
+"use client"
-import { signIn } from "@lib/auth";
-import { usePostHog } from "@lib/posthog";
-import { LogoFull } from "@repo/ui/assets/Logo";
-import { TextSeparator } from "@repo/ui/components/text-separator";
-import { ExternalAuthButton } from "@ui/button/external-auth";
-import { Button } from "@ui/components/button";
+import { signIn } from "@lib/auth"
+import { usePostHog } from "@lib/posthog"
+import { LogoFull } from "@repo/ui/assets/Logo"
+import { TextSeparator } from "@repo/ui/components/text-separator"
+import { ExternalAuthButton } from "@ui/button/external-auth"
+import { Button } from "@ui/components/button"
+import { Badge } from "@ui/components/badge"
import {
Carousel,
CarouselContent,
CarouselItem,
-} from "@ui/components/carousel";
-import { LabeledInput } from "@ui/input/labeled-input";
-import { HeadingH1Medium } from "@ui/text/heading/heading-h1-medium";
-import { HeadingH3Medium } from "@ui/text/heading/heading-h3-medium";
-import { Label1Regular } from "@ui/text/label/label-1-regular";
-import { Title1Bold } from "@ui/text/title/title-1-bold";
-import Autoplay from "embla-carousel-autoplay";
-import Image from "next/image";
-import { useRouter, useSearchParams } from "next/navigation";
-import { useState } from "react";
+} from "@ui/components/carousel"
+import { LabeledInput } from "@ui/input/labeled-input"
+import { HeadingH1Medium } from "@ui/text/heading/heading-h1-medium"
+import { HeadingH3Medium } from "@ui/text/heading/heading-h3-medium"
+import { Label1Regular } from "@ui/text/label/label-1-regular"
+import { Title1Bold } from "@ui/text/title/title-1-bold"
+import Autoplay from "embla-carousel-autoplay"
+import Image from "next/image"
+import { useRouter, useSearchParams } from "next/navigation"
+import { useState, useEffect } from "react"
export function LoginPage({
heroText = "The unified memory API for the AI era.",
@@ -28,74 +29,101 @@ export function LoginPage({
"Trusted by Open Source, enterprise and developers.",
],
}) {
- const [email, setEmail] = useState("");
- const [submittedEmail, setSubmittedEmail] = useState
(null);
- const [isLoading, setIsLoading] = useState(false);
- const [isLoadingEmail, setIsLoadingEmail] = useState(false);
- const [error, setError] = useState(null);
- const router = useRouter();
+ const [email, setEmail] = useState("")
+ const [submittedEmail, setSubmittedEmail] = useState(null)
+ const [isLoading, setIsLoading] = useState(false)
+ const [isLoadingEmail, setIsLoadingEmail] = useState(false)
+ const [error, setError] = useState(null)
+ const [lastUsedMethod, setLastUsedMethod] = useState(null)
+ const router = useRouter()
+
+ const posthog = usePostHog()
+
+ const params = useSearchParams()
+
+ // Load last used method from localStorage on mount
+ useEffect(() => {
+ const savedMethod = localStorage.getItem('supermemory-last-login-method')
+ setLastUsedMethod(savedMethod)
+ }, [])
+
+ // Record the pending login method (will be committed after successful auth)
+ function setPendingLoginMethod(method: string) {
+ try {
+ localStorage.setItem('supermemory-pending-login-method', method)
+ localStorage.setItem('supermemory-pending-login-timestamp', String(Date.now()))
+ } catch { }
+ }
+
+ // If we land back on this page with an error, clear any pending marker
+ useEffect(() => {
+ if (params.get("error")) {
+ try {
+ localStorage.removeItem('supermemory-pending-login-method')
+ localStorage.removeItem('supermemory-pending-login-timestamp')
+ } catch { }
+ }
+ }, [params])
- const posthog = usePostHog();
- const params = useSearchParams();
const handleSubmit = async (e: React.FormEvent) => {
- e.preventDefault();
- setIsLoading(true);
- setIsLoadingEmail(true);
- setError(null);
+ e.preventDefault()
+ setIsLoading(true)
+ setIsLoadingEmail(true)
+ setError(null)
// Track login attempt
posthog.capture("login_attempt", {
method: "magic_link",
email_domain: email.split("@")[1] || "unknown",
- });
+ })
try {
await signIn.magicLink({
callbackURL: window.location.origin,
email,
- });
- setSubmittedEmail(email);
-
+ })
+ setSubmittedEmail(email)
+ setPendingLoginMethod('magic_link')
// Track successful magic link send
posthog.capture("login_magic_link_sent", {
email_domain: email.split("@")[1] || "unknown",
- });
+ })
} catch (error) {
- console.error(error);
+ console.error(error)
// Track login failure
posthog.capture("login_failed", {
method: "magic_link",
error: error instanceof Error ? error.message : "Unknown error",
email_domain: email.split("@")[1] || "unknown",
- });
+ })
setError(
error instanceof Error
? error.message
: "Failed to send login link. Please try again.",
- );
- setIsLoading(false);
- setIsLoadingEmail(false);
- return;
+ )
+ setIsLoading(false)
+ setIsLoadingEmail(false)
+ return
}
- setIsLoading(false);
- setIsLoadingEmail(false);
- };
+ setIsLoading(false)
+ setIsLoadingEmail(false)
+ }
const handleSubmitToken = async (event: React.FormEvent) => {
- event.preventDefault();
- setIsLoading(true);
+ event.preventDefault()
+ setIsLoading(true)
- const formData = new FormData(event.currentTarget);
- const token = formData.get("token") as string;
+ const formData = new FormData(event.currentTarget)
+ const token = formData.get("token") as string
router.push(
`${process.env.NEXT_PUBLIC_BACKEND_URL}/api/auth/magic-link/verify?token=${token}&callbackURL=${encodeURIComponent(window.location.host)}`,
- );
- };
+ )
+ }
return (
@@ -197,8 +225,8 @@ export function LoginPage({
disabled: isLoading,
id: "email",
onChange: (e) => {
- setEmail(e.target.value);
- error && setError(null);
+ setEmail(e.target.value)
+ error && setError(null)
},
required: true,
value: email,
@@ -207,124 +235,149 @@ export function LoginPage({
label="Email"
/>
-
+
+
+ {lastUsedMethod === 'magic_link' && (
+
+ Last used
+
+ )}
+
{process.env.NEXT_PUBLIC_HOST_ID === "supermemory" ||
- !process.env.NEXT_PUBLIC_GOOGLE_AUTH_ENABLED ||
- !process.env.NEXT_PUBLIC_GITHUB_AUTH_ENABLED ? (
+ !process.env.NEXT_PUBLIC_GOOGLE_AUTH_ENABLED ||
+ !process.env.NEXT_PUBLIC_GITHUB_AUTH_ENABLED ? (