clean up login page

This commit is contained in:
miloschwartz
2026-01-09 14:41:22 -08:00
parent bb98bf03aa
commit 4c8d2266ec
6 changed files with 54 additions and 63 deletions

View File

@@ -1135,7 +1135,7 @@
"create": "Create", "create": "Create",
"orgs": "Organizations", "orgs": "Organizations",
"loginError": "An error occurred while logging in", "loginError": "An error occurred while logging in",
"loginRequiredForDevice": "Login is required to authenticate your device.", "loginRequiredForDevice": "Login is required for your device.",
"passwordForgot": "Forgot your password?", "passwordForgot": "Forgot your password?",
"otpAuth": "Two-Factor Authentication", "otpAuth": "Two-Factor Authentication",
"otpAuthDescription": "Enter the code from your authenticator app or one of your single-use backup codes.", "otpAuthDescription": "Enter the code from your authenticator app or one of your single-use backup codes.",
@@ -1876,7 +1876,7 @@
"orgAuthChooseIdpDescription": "Choose your identity provider to continue", "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.", "orgAuthNoIdpConfigured": "This organization doesn't have any identity providers configured. You can log in with your Pangolin identity instead.",
"orgAuthSignInWithPangolin": "Sign in with Pangolin", "orgAuthSignInWithPangolin": "Sign in with Pangolin",
"orgAuthSignInToOrg": "Sign in to an organization", "orgAuthSignInToOrg": "Use organization's identity provider",
"orgAuthSelectOrgTitle": "Organization Sign In", "orgAuthSelectOrgTitle": "Organization Sign In",
"orgAuthSelectOrgDescription": "Enter your organization ID to continue", "orgAuthSelectOrgDescription": "Enter your organization ID to continue",
"orgAuthOrgIdPlaceholder": "your-organization", "orgAuthOrgIdPlaceholder": "your-organization",

View File

@@ -16,4 +16,4 @@ export * from "./checkResourceSession";
export * from "./securityKey"; export * from "./securityKey";
export * from "./startDeviceWebAuth"; export * from "./startDeviceWebAuth";
export * from "./verifyDeviceWebAuth"; export * from "./verifyDeviceWebAuth";
export * from "./pollDeviceWebAuth"; export * from "./pollDeviceWebAuth";

View File

@@ -44,7 +44,7 @@ export default async function AuthLayout({ children }: AuthLayoutProps) {
return ( return (
<div className="h-full flex flex-col"> <div className="h-full flex flex-col">
<div className="flex justify-end items-center p-3 space-x-2"> <div className="hidden md:flex justify-end items-center p-3 space-x-2">
<ThemeSwitcher /> <ThemeSwitcher />
</div> </div>
@@ -127,26 +127,6 @@ export default async function AuthLayout({ children }: AuthLayoutProps) {
</a> </a>
</> </>
)} )}
<Separator orientation="vertical" />
<a
href="https://docs.pangolin.net"
target="_blank"
rel="noopener noreferrer"
aria-label="Built by Fossorial"
className="flex items-center space-x-2 whitespace-nowrap"
>
<span>{t("docs")}</span>
</a>
<Separator orientation="vertical" />
<a
href="https://github.com/fosrl/pangolin"
target="_blank"
rel="noopener noreferrer"
aria-label="GitHub"
className="flex items-center space-x-2 whitespace-nowrap"
>
<span>{t("github")}</span>
</a>
</div> </div>
</footer> </footer>
)} )}

View File

@@ -103,6 +103,10 @@ export default async function Page(props: {
redirect={redirectUrl} redirect={redirectUrl}
idps={loginIdps} idps={loginIdps}
forceLogin={forceLogin} forceLogin={forceLogin}
showOrgLogin={
!isInvite && (build === "saas" || env.flags.useOrgOnlyIdp)
}
searchParams={searchParams}
/> />
{(!signUpDisabled || isInvite) && ( {(!signUpDisabled || isInvite) && (
@@ -120,35 +124,6 @@ export default async function Page(props: {
</Link> </Link>
</p> </p>
)} )}
{!isInvite && (build === "saas" || env.flags.useOrgOnlyIdp) ? (
<div className="text-center text-muted-foreground mt-12 flex flex-col items-center">
<span>{t("needToSignInToOrg")}</span>
<Link
href={`/auth/org${buildQueryString(searchParams)}`}
className="underline"
>
{t("orgAuthSignInToOrg")}
</Link>
</div>
) : null}
</> </>
); );
} }
function buildQueryString(searchParams: {
[key: string]: string | string[] | undefined;
}): string {
const params = new URLSearchParams();
const redirect = searchParams.redirect;
const forceLogin = searchParams.forceLogin;
if (redirect && typeof redirect === "string") {
params.set("redirect", redirect);
}
if (forceLogin && typeof forceLogin === "string") {
params.set("forceLogin", forceLogin);
}
const queryString = params.toString();
return queryString ? `?${queryString}` : "";
}

View File

@@ -17,17 +17,26 @@ import { cleanRedirect } from "@app/lib/cleanRedirect";
import BrandingLogo from "@app/components/BrandingLogo"; import BrandingLogo from "@app/components/BrandingLogo";
import { useTranslations } from "next-intl"; import { useTranslations } from "next-intl";
import { useLicenseStatusContext } from "@app/hooks/useLicenseStatusContext"; import { useLicenseStatusContext } from "@app/hooks/useLicenseStatusContext";
import Link from "next/link";
import { Button } from "./ui/button";
import { ArrowRight } from "lucide-react";
type DashboardLoginFormProps = { type DashboardLoginFormProps = {
redirect?: string; redirect?: string;
idps?: LoginFormIDP[]; idps?: LoginFormIDP[];
forceLogin?: boolean; forceLogin?: boolean;
showOrgLogin?: boolean;
searchParams?: {
[key: string]: string | string[] | undefined;
};
}; };
export default function DashboardLoginForm({ export default function DashboardLoginForm({
redirect, redirect,
idps, idps,
forceLogin forceLogin,
showOrgLogin,
searchParams
}: DashboardLoginFormProps) { }: DashboardLoginFormProps) {
const router = useRouter(); const router = useRouter();
const { env } = useEnvContext(); const { env } = useEnvContext();
@@ -35,6 +44,9 @@ export default function DashboardLoginForm({
const { isUnlocked } = useLicenseStatusContext(); const { isUnlocked } = useLicenseStatusContext();
function getSubtitle() { function getSubtitle() {
if (forceLogin) {
return t("loginRequiredForDevice");
}
if (isUnlocked() && env.branding?.loginPage?.subtitleText) { if (isUnlocked() && env.branding?.loginPage?.subtitleText) {
return env.branding.loginPage.subtitleText; return env.branding.loginPage.subtitleText;
} }
@@ -57,6 +69,22 @@ export default function DashboardLoginForm({
<div className="text-center space-y-1 pt-3"> <div className="text-center space-y-1 pt-3">
<p className="text-muted-foreground">{getSubtitle()}</p> <p className="text-muted-foreground">{getSubtitle()}</p>
</div> </div>
{showOrgLogin && (
<div className="space-y-2 mt-4">
<Link
href={`/auth/org${buildQueryString(searchParams || {})}`}
className="underline"
>
<Button
variant="secondary"
className="w-full gap-2"
>
{t("orgAuthSignInToOrg")}
<ArrowRight className="h-4 w-4" />
</Button>
</Link>
</div>
)}
</CardHeader> </CardHeader>
<CardContent className="pt-6"> <CardContent className="pt-6">
<LoginForm <LoginForm
@@ -76,3 +104,20 @@ export default function DashboardLoginForm({
</Card> </Card>
); );
} }
function buildQueryString(searchParams: {
[key: string]: string | string[] | undefined;
}): string {
const params = new URLSearchParams();
const redirect = searchParams.redirect;
const forceLogin = searchParams.forceLogin;
if (redirect && typeof redirect === "string") {
params.set("redirect", redirect);
}
if (forceLogin && typeof forceLogin === "string") {
params.set("forceLogin", forceLogin);
}
const queryString = params.toString();
return queryString ? `?${queryString}` : "";
}

View File

@@ -409,15 +409,6 @@ export default function LoginForm({
return ( return (
<div className="space-y-4"> <div className="space-y-4">
{forceLogin && (
<Alert variant="neutral">
<AlertDescription className="flex items-center gap-2">
<LockIcon className="w-4 h-4" />
{t("loginRequiredForDevice")}
</AlertDescription>
</Alert>
)}
{showSecurityKeyPrompt && ( {showSecurityKeyPrompt && (
<Alert> <Alert>
<FingerprintIcon className="w-5 h-5 mr-2" /> <FingerprintIcon className="w-5 h-5 mr-2" />