diff --git a/apps/web/app/(auth)/login/page.tsx b/apps/web/app/(auth)/login/page.tsx index 5f9bf8cc..8500a236 100644 --- a/apps/web/app/(auth)/login/page.tsx +++ b/apps/web/app/(auth)/login/page.tsx @@ -20,6 +20,10 @@ import { cn } from "@lib/utils" import { Logo } from "@ui/assets/Logo" import { getBackendUrl, resolveAuthRedirectUrl } from "@/lib/url-helpers" import { Loader2 } from "lucide-react" +import { + normalizeReviewerPasswordEmail, + shouldUseReviewerPasswordLogin, +} from "@/lib/reviewer-password-login" function isMcpOAuthAuthorizeContext(sp: Pick): boolean { return sp.get("response_type") === "code" && Boolean(sp.get("client_id")) @@ -113,9 +117,14 @@ function LoginCardBody({ export default function LoginPage() { const [email, setEmail] = useState("") + const [password, setPassword] = useState("") + const [passwordLoginEmail, setPasswordLoginEmail] = useState( + null, + ) const [submittedEmail, setSubmittedEmail] = useState(null) const [isLoading, setIsLoading] = useState(false) const [isLoadingEmail, setIsLoadingEmail] = useState(false) + const [isLoadingPassword, setIsLoadingPassword] = useState(false) const [error, setError] = useState(null) const [lastUsedMethod, setLastUsedMethod] = useState(null) const router = useRouter() @@ -135,7 +144,9 @@ export default function LoginPage() { : isLoading ? isLoadingEmail ? "Sending login link…" - : "Redirecting…" + : isLoadingPassword + ? "Signing in…" + : "Redirecting…" : null useEffect(() => { @@ -226,9 +237,20 @@ export default function LoginPage() { const handleSubmit = async (e: React.FormEvent) => { e.preventDefault() + setError(null) + + if ( + shouldUseReviewerPasswordLogin({ + submittedEmail: email, + }) + ) { + setPasswordLoginEmail(normalizeReviewerPasswordEmail(email)) + setPassword("") + return + } + setIsLoading(true) setIsLoadingEmail(true) - setError(null) // Track login attempt posthog.capture("login_attempt", { @@ -268,6 +290,38 @@ export default function LoginPage() { setIsLoadingEmail(false) } + const handlePasswordSubmit = async ( + event: React.FormEvent, + ) => { + event.preventDefault() + if (!passwordLoginEmail) return + + setIsLoading(true) + setIsLoadingPassword(true) + setError(null) + posthog.capture("login_attempt", { + method: "password", + email_domain: passwordLoginEmail.split("@")[1] || "unknown", + }) + + const { error } = await signIn.email({ + callbackURL: getCallbackURL(), + email: passwordLoginEmail, + password, + }) + + if (error) { + posthog.capture("login_failed", { + method: "password", + email_domain: passwordLoginEmail.split("@")[1] || "unknown", + is_network_error: isNetworkError(error), + }) + setError(getErrorMessage(error)) + setIsLoading(false) + setIsLoadingPassword(false) + } + } + const handleSubmitToken = async (event: React.FormEvent) => { event.preventDefault() setIsLoading(true) @@ -299,7 +353,75 @@ export default function LoginPage() { - {submittedEmail ? ( + {passwordLoginEmail ? ( +
+
+ + Enter password + + + Sign in to your Supermemory account. + +
+ +
+ + , + ) => { + setPassword(e.target.value) + error && setError(null) + }, + required: true, + value: password, + }} + inputType="password" + label="Password" + /> + + + +
+ ) : submittedEmail ? (
diff --git a/apps/web/lib/reviewer-password-login.ts b/apps/web/lib/reviewer-password-login.ts new file mode 100644 index 00000000..ecafda76 --- /dev/null +++ b/apps/web/lib/reviewer-password-login.ts @@ -0,0 +1,15 @@ +export const MCP_REVIEWER_EMAIL = "test@supermemory.com" + +export function normalizeReviewerPasswordEmail(value: unknown): string | null { + if (typeof value !== "string") return null + const normalized = value.trim().toLowerCase() + return normalized || null +} + +export function shouldUseReviewerPasswordLogin({ + submittedEmail, +}: { + submittedEmail: string +}): boolean { + return normalizeReviewerPasswordEmail(submittedEmail) === MCP_REVIEWER_EMAIL +}