Compare commits

...

1 Commits

Author SHA1 Message Date
miloschwartz
a8f6b6c1da prefill username in login 2026-02-05 16:55:00 -08:00
6 changed files with 55 additions and 17 deletions

View File

@@ -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}
/>
);
}

View File

@@ -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: {
<SmartLoginForm
redirect={redirectUrl}
forceLogin={forceLogin}
defaultUser={defaultUser}
/>
</CardContent>
</Card>
@@ -165,6 +168,7 @@ export default async function Page(props: {
(build === "saas" || env.flags.useOrgOnlyIdp)
}
searchParams={searchParams}
defaultUser={defaultUser}
/>
)}

View File

@@ -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);

View File

@@ -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();
}
}

View File

@@ -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: ""
}
});

View File

@@ -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<z.infer<typeof identifierSchema>>({
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<typeof identifierSchema>) => {
const identifier = values.identifier.trim();
const isEmail = isValidEmail(identifier);