diff --git a/messages/en-US.json b/messages/en-US.json index 202444882..a7b045480 100644 --- a/messages/en-US.json +++ b/messages/en-US.json @@ -2355,7 +2355,7 @@ "orgAuthChooseIdpDescription": "Choose your identity provider to continue", "orgAuthNoIdpConfigured": "This organization doesn't have any identity providers configured. You can log in with your Pangolin identity instead.", "orgAuthSignInWithPangolin": "Sign in with Pangolin", - "orgAuthSignInToOrg": "Sign in to an organization", + "orgAuthSignInToOrg": "Organization Identity Provider (SSO)", "orgAuthSelectOrgTitle": "Organization Sign In", "orgAuthSelectOrgDescription": "Enter your organization ID to continue", "orgAuthOrgIdPlaceholder": "your-organization", diff --git a/src/app/auth/login/page.tsx b/src/app/auth/login/page.tsx index c2aaefaa6..6373e334a 100644 --- a/src/app/auth/login/page.tsx +++ b/src/app/auth/login/page.tsx @@ -160,6 +160,18 @@ export default async function Page(props: { redirect={redirectUrl} forceLogin={forceLogin} defaultUser={defaultUser} + orgSignIn={ + !isInvite && + (build === "saas" || + env.app.identityProviderMode === "org") + ? { + href: `/auth/org${buildQueryString(searchParams)}`, + linkText: t("orgAuthSignInToOrg"), + descriptionText: + t("needToSignInToOrg") + } + : undefined + } /> @@ -195,7 +207,8 @@ export default async function Page(props: {

)} - {!isInvite && + {!useSmartLogin && + !isInvite && (build === "saas" || env.app.identityProviderMode === "org") ? ( )} -
- {descriptionText} - +
+ {primaryActionVariant === "button" ? ( + + ) : ( + + )}
); diff --git a/src/components/SmartLoginForm.tsx b/src/components/SmartLoginForm.tsx index 24f2acb72..164311b7b 100644 --- a/src/components/SmartLoginForm.tsx +++ b/src/components/SmartLoginForm.tsx @@ -15,15 +15,18 @@ import { FormMessage } from "@app/components/ui/form"; import { Alert, AlertDescription } from "@app/components/ui/alert"; +import Link from "next/link"; import { useRouter } from "next/navigation"; import { useUserLookup } from "@app/hooks/useUserLookup"; +import { useEnvContext } from "@app/hooks/useEnvContext"; import { LookupUserResponse } from "@server/routers/auth/lookupUser"; import { useTranslations } from "next-intl"; import LoginPasswordForm from "@app/components/LoginPasswordForm"; import LoginOrgSelector from "@app/components/LoginOrgSelector"; import UserProfileCard from "@app/components/UserProfileCard"; -import { ArrowLeft } from "lucide-react"; import SecurityKeyAuthButton from "@app/components/SecurityKeyAuthButton"; +import { Separator } from "@app/components/ui/separator"; +import OrgSignInLink from "@app/components/OrgSignInLink"; const identifierSchema = z.object({ identifier: z.string().min(1, "Username or email is required") @@ -39,10 +42,17 @@ const isValidEmail = (str: string): boolean => { } }; +type OrgSignInConfig = { + href: string; + linkText: string; + descriptionText: string; +}; + type SmartLoginFormProps = { redirect?: string; forceLogin?: boolean; defaultUser?: string; + orgSignIn?: OrgSignInConfig; }; type ViewState = @@ -58,12 +68,31 @@ type ViewState = lookupResult: LookupUserResponse; }; +function buildResetPasswordHref( + dashboardUrl: string, + identifier: string, + redirectParam?: string +) { + const trimmed = identifier.trim(); + const params = new URLSearchParams(); + if (isValidEmail(trimmed)) { + params.set("email", trimmed); + } + if (redirectParam) { + params.set("redirect", redirectParam); + } + const qs = params.toString(); + return `${dashboardUrl}/auth/reset-password${qs ? `?${qs}` : ""}`; +} + export default function SmartLoginForm({ redirect, forceLogin, - defaultUser + defaultUser, + orgSignIn }: SmartLoginFormProps) { const router = useRouter(); + const { env } = useEnvContext(); const { lookup, loading, error } = useUserLookup(); const t = useTranslations(); const [viewState, setViewState] = useState({ type: "initial" }); @@ -78,6 +107,13 @@ export default function SmartLoginForm({ } }); + const watchedIdentifier = form.watch("identifier"); + const resetPasswordHref = buildResetPasswordHref( + env.app.dashboardUrl, + watchedIdentifier, + redirect + ); + const hasAutoLookedUp = useRef(false); useEffect(() => { if (defaultUser?.trim() && !hasAutoLookedUp.current) { @@ -209,6 +245,15 @@ export default function SmartLoginForm({ )} /> +
+ + {t("passwordForgot")} + +
+ {(error || securityKeyError) && ( @@ -219,7 +264,7 @@ export default function SmartLoginForm({ -
+
);