"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 { Badge } from "@ui/components/badge"; import { Button } from "@ui/components/button"; 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 { useEffect, useState } from "react"; export function LoginPage({ heroText = "The unified memory API for the AI era.", texts = [ "Stop building retrieval from scratch.", "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 [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 (
supermemory abstract 2d
{texts[0]}
supermemory abstract 3d
{texts[1]}
{submittedEmail ? (
Almost there! Click the magic link we've sent to{" "} {submittedEmail}.
) : (
Welcome to {" "} {heroText}
{params.get("error") && (
Error: {params.get("error")}. Please try again!
)}
{ setEmail(e.target.value); error && setError(null); }, required: true, value: email, }} inputType="email" 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 ? ( ) : null}
{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); setError(null); posthog.capture("login_attempt", { method: "social", provider: "google", }); setPendingLoginMethod("google"); signIn .social({ 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); }); }} /> {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); setError(null); posthog.capture("login_attempt", { method: "social", provider: "github", }); setPendingLoginMethod("github"); signIn .social({ 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); }); }} /> {lastUsedMethod === "github" && (
Last used
)}
) : null}
By continuing, you agree to our{" "} Terms {" "} and{" "} Privacy Policy .
)}
); }