"use client" import { signIn } from "@lib/auth" import { usePostHog } from "@lib/posthog" 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 { LabeledInput } from "@ui/input/labeled-input" 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 { InitialHeader } from "@/components/initial-header" import { useRouter, useSearchParams } from "next/navigation" import { useState, useEffect } from "react" import { motion } from "motion/react" import { dmSansClassName } from "@/lib/fonts" import { cn } from "@lib/utils" import { Logo } from "@ui/assets/Logo" function AnimatedGradientBackground() { return (
) } function LoginCard({ children }: { children: React.ReactNode }) { return ( {children} ) } export default function LoginPage() { 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() // Get redirect URL from query params const redirectUrl = params.get("redirect") // Create callback URL that includes redirect parameter if provided const getCallbackURL = () => { const origin = window.location.origin let finalUrl: URL if (redirectUrl) { try { finalUrl = new URL(redirectUrl, origin) } catch { finalUrl = new URL(origin) } } else { finalUrl = new URL(origin) } finalUrl.searchParams.set("extension-auth-success", "true") return finalUrl.toString() } // 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 {} } 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")) { try { localStorage.removeItem("supermemory-pending-login-method") localStorage.removeItem("supermemory-pending-login-timestamp") } catch {} } }, [params]) const handleSubmit = async (e: React.FormEvent) => { 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: getCallbackURL(), 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) // Track login failure posthog.capture("login_failed", { method: "magic_link", error: error instanceof Error ? error.message : "Unknown error", email_domain: email.split("@")[1] || "unknown", is_network_error: isNetworkError(error), }) setError(getErrorMessage(error)) setIsLoading(false) setIsLoadingEmail(false) return } setIsLoading(false) setIsLoadingEmail(false) } const handleSubmitToken = async (event: React.FormEvent) => { event.preventDefault() setIsLoading(true) const formData = new FormData(event.currentTarget) const token = formData.get("token") as string const callbackURL = getCallbackURL() router.push( `${process.env.NEXT_PUBLIC_BACKEND_URL}/api/auth/magic-link/verify?token=${token}&callbackURL=${encodeURIComponent(callbackURL)}`, ) } return (
Never forget anything, anywhere
with supermemory
{submittedEmail ? (
Almost there! Click the magic link we've sent to{" "} {submittedEmail}.
) : (
{params.get("error") && (
Error: {params.get("error")}. Please try again!
)}
{process.env.NEXT_PUBLIC_HOST_ID === "supermemory" || !process.env.NEXT_PUBLIC_GOOGLE_AUTH_ENABLED ? (
Google } authProvider="Google" className="w-full" disabled={isLoading} onClick={() => { if (isLoading) return setIsLoading(true) posthog.capture("login_attempt", { method: "social", provider: "google", }) setPendingLoginMethod("google") signIn .social({ callbackURL: getCallbackURL(), provider: "google", }) .finally(() => { setIsLoading(false) }) }} /> {lastUsedMethod === "google" && (
Last used
)}
) : null} {process.env.NEXT_PUBLIC_HOST_ID === "supermemory" || !process.env.NEXT_PUBLIC_GITHUB_AUTH_ENABLED ? (
Github } authProvider="Github" className="w-full" disabled={isLoading} onClick={() => { if (isLoading) return setIsLoading(true) posthog.capture("login_attempt", { method: "social", provider: "github", }) setPendingLoginMethod("github") signIn .social({ callbackURL: getCallbackURL(), provider: "github", }) .finally(() => { setIsLoading(false) }) }} /> {lastUsedMethod === "github" && (
Last used
)}
) : null}
{ setEmail(e.target.value) error && setError(null) }, required: true, value: email, }} inputType="email" />
{lastUsedMethod === "magic_link" && (
Last used
)}
By continuing, you agree to our{" "} Terms {" "} and{" "} Privacy Policy .
)}
) }