From a8f6b6c1daee1e2af0d1931b2bedeef32f062b99 Mon Sep 17 00:00:00 2001 From: miloschwartz Date: Thu, 5 Feb 2026 16:55:00 -0800 Subject: [PATCH] prefill username in login --- src/app/auth/login/device/page.tsx | 28 ++++++++++++++++++++------- src/app/auth/login/page.tsx | 4 ++++ src/components/DashboardLoginForm.tsx | 5 ++++- src/components/DeviceLoginForm.tsx | 13 +++++++++---- src/components/LoginForm.tsx | 6 ++++-- src/components/SmartLoginForm.tsx | 16 ++++++++++++--- 6 files changed, 55 insertions(+), 17 deletions(-) diff --git a/src/app/auth/login/device/page.tsx b/src/app/auth/login/device/page.tsx index 9b6b2bd2..7d2ed4e3 100644 --- a/src/app/auth/login/device/page.tsx +++ b/src/app/auth/login/device/page.tsx @@ -7,22 +7,35 @@ import { cache } from "react"; export const dynamic = "force-dynamic"; type Props = { - searchParams: Promise<{ code?: string }>; + searchParams: Promise<{ code?: string; user?: string }>; }; +function deviceRedirectSearchParams(params: { + code?: string; + user?: string; +}): string { + const search = new URLSearchParams(); + if (params.code) search.set("code", params.code); + if (params.user) search.set("user", params.user); + const q = search.toString(); + return q ? `?${q}` : ""; +} + export default async function DeviceLoginPage({ searchParams }: Props) { const user = await verifySession({ forceLogin: true }); const params = await searchParams; const code = params.code || ""; + const defaultUser = params.user; if (!user) { - const redirectDestination = code - ? `/auth/login/device?code=${encodeURIComponent(code)}` - : "/auth/login/device"; - redirect( - `/auth/login?forceLogin=true&redirect=${encodeURIComponent(redirectDestination)}` - ); + const redirectDestination = `/auth/login/device${deviceRedirectSearchParams({ code, user: params.user })}`; + const loginUrl = new URL("/auth/login", "http://x"); + loginUrl.searchParams.set("forceLogin", "true"); + loginUrl.searchParams.set("redirect", redirectDestination); + if (defaultUser) loginUrl.searchParams.set("user", defaultUser); + console.log("loginUrl", loginUrl.pathname + loginUrl.search); + redirect(loginUrl.pathname + loginUrl.search); } const userName = user @@ -37,6 +50,7 @@ export default async function DeviceLoginPage({ searchParams }: Props) { userEmail={user?.email || ""} userName={userName} initialCode={code} + userQueryParam={defaultUser} /> ); } diff --git a/src/app/auth/login/page.tsx b/src/app/auth/login/page.tsx index 071020cd..2ba4d7f8 100644 --- a/src/app/auth/login/page.tsx +++ b/src/app/auth/login/page.tsx @@ -72,6 +72,8 @@ export default async function Page(props: { searchParams.redirect = redirectUrl; } + const defaultUser = searchParams.user as string | undefined; + // Only use SmartLoginForm if NOT (OSS build OR org-only IdP enabled) const useSmartLogin = build === "saas" || (build === "enterprise" && env.flags.useOrgOnlyIdp); @@ -151,6 +153,7 @@ export default async function Page(props: { @@ -165,6 +168,7 @@ export default async function Page(props: { (build === "saas" || env.flags.useOrgOnlyIdp) } searchParams={searchParams} + defaultUser={defaultUser} /> )} diff --git a/src/components/DashboardLoginForm.tsx b/src/components/DashboardLoginForm.tsx index 8a4c611e..4484ba69 100644 --- a/src/components/DashboardLoginForm.tsx +++ b/src/components/DashboardLoginForm.tsx @@ -29,6 +29,7 @@ type DashboardLoginFormProps = { searchParams?: { [key: string]: string | string[] | undefined; }; + defaultUser?: string; }; export default function DashboardLoginForm({ @@ -36,7 +37,8 @@ export default function DashboardLoginForm({ idps, forceLogin, showOrgLogin, - searchParams + searchParams, + defaultUser }: DashboardLoginFormProps) { const router = useRouter(); const { env } = useEnvContext(); @@ -75,6 +77,7 @@ export default function DashboardLoginForm({ redirect={redirect} idps={idps} forceLogin={forceLogin} + defaultEmail={defaultUser} onLogin={(redirectUrl) => { if (redirectUrl) { const safe = cleanRedirect(redirectUrl); diff --git a/src/components/DeviceLoginForm.tsx b/src/components/DeviceLoginForm.tsx index cadeb230..16e7f2e1 100644 --- a/src/components/DeviceLoginForm.tsx +++ b/src/components/DeviceLoginForm.tsx @@ -55,12 +55,14 @@ type DeviceLoginFormProps = { userEmail: string; userName?: string; initialCode?: string; + userQueryParam?: string; }; export default function DeviceLoginForm({ userEmail, userName, - initialCode = "" + initialCode = "", + userQueryParam }: DeviceLoginFormProps) { const router = useRouter(); const { env } = useEnvContext(); @@ -219,9 +221,12 @@ export default function DeviceLoginForm({ const currentSearch = typeof window !== "undefined" ? window.location.search : ""; const redirectTarget = `/auth/login/device${currentSearch || ""}`; - router.push( - `/auth/login?forceLogin=true&redirect=${encodeURIComponent(redirectTarget)}` - ); + const loginUrl = new URL("/auth/login", "http://x"); + loginUrl.searchParams.set("forceLogin", "true"); + loginUrl.searchParams.set("redirect", redirectTarget); + if (userQueryParam) + loginUrl.searchParams.set("user", userQueryParam); + router.push(loginUrl.pathname + loginUrl.search); router.refresh(); } } diff --git a/src/components/LoginForm.tsx b/src/components/LoginForm.tsx index 5497826c..c3b1fc38 100644 --- a/src/components/LoginForm.tsx +++ b/src/components/LoginForm.tsx @@ -54,6 +54,7 @@ type LoginFormProps = { idps?: LoginFormIDP[]; orgId?: string; forceLogin?: boolean; + defaultEmail?: string; }; export default function LoginForm({ @@ -61,7 +62,8 @@ export default function LoginForm({ onLogin, idps, orgId, - forceLogin + forceLogin, + defaultEmail }: LoginFormProps) { const router = useRouter(); @@ -116,7 +118,7 @@ export default function LoginForm({ const form = useForm({ resolver: zodResolver(formSchema), defaultValues: { - email: "", + email: defaultEmail ?? "", password: "" } }); diff --git a/src/components/SmartLoginForm.tsx b/src/components/SmartLoginForm.tsx index 5e1498ff..24f2acb7 100644 --- a/src/components/SmartLoginForm.tsx +++ b/src/components/SmartLoginForm.tsx @@ -1,6 +1,6 @@ "use client"; -import { useState } from "react"; +import { useEffect, useRef, useState } from "react"; import { useForm } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; import * as z from "zod"; @@ -42,6 +42,7 @@ const isValidEmail = (str: string): boolean => { type SmartLoginFormProps = { redirect?: string; forceLogin?: boolean; + defaultUser?: string; }; type ViewState = @@ -59,7 +60,8 @@ type ViewState = export default function SmartLoginForm({ redirect, - forceLogin + forceLogin, + defaultUser }: SmartLoginFormProps) { const router = useRouter(); const { lookup, loading, error } = useUserLookup(); @@ -72,10 +74,18 @@ export default function SmartLoginForm({ const form = useForm>({ resolver: zodResolver(identifierSchema), defaultValues: { - identifier: "" + identifier: defaultUser ?? "" } }); + const hasAutoLookedUp = useRef(false); + useEffect(() => { + if (defaultUser?.trim() && !hasAutoLookedUp.current) { + hasAutoLookedUp.current = true; + void handleLookup({ identifier: defaultUser.trim() }); + } + }, [defaultUser]); + const handleLookup = async (values: z.infer) => { const identifier = values.identifier.trim(); const isEmail = isValidEmail(identifier);