mirror of
https://github.com/fosrl/pangolin.git
synced 2026-05-06 20:37:45 +00:00
log in page improvements
This commit is contained in:
@@ -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",
|
||||
|
||||
@@ -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
|
||||
}
|
||||
/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
@@ -195,7 +207,8 @@ export default async function Page(props: {
|
||||
</p>
|
||||
)}
|
||||
|
||||
{!isInvite &&
|
||||
{!useSmartLogin &&
|
||||
!isInvite &&
|
||||
(build === "saas" || env.app.identityProviderMode === "org") ? (
|
||||
<OrgSignInLink
|
||||
href={`/auth/org${buildQueryString(searchParams)}`}
|
||||
|
||||
@@ -5,11 +5,15 @@ import { useRouter } from "next/navigation";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { Alert, AlertDescription, AlertTitle } from "@app/components/ui/alert";
|
||||
import { Button } from "@app/components/ui/button";
|
||||
import { cn } from "@app/lib/cn";
|
||||
import { Building2 } from "lucide-react";
|
||||
|
||||
type OrgSignInLinkProps = {
|
||||
href: string;
|
||||
linkText: string;
|
||||
descriptionText: string;
|
||||
primaryActionVariant?: "link" | "button";
|
||||
className?: string;
|
||||
};
|
||||
|
||||
const STORAGE_KEY_CLICKED = "orgSignInLinkClicked";
|
||||
@@ -18,7 +22,9 @@ const STORAGE_KEY_ACKNOWLEDGED = "orgSignInTipAcknowledged";
|
||||
export default function OrgSignInLink({
|
||||
href,
|
||||
linkText,
|
||||
descriptionText
|
||||
descriptionText,
|
||||
primaryActionVariant = "link",
|
||||
className
|
||||
}: OrgSignInLinkProps) {
|
||||
const router = useRouter();
|
||||
const t = useTranslations();
|
||||
@@ -93,14 +99,32 @@ export default function OrgSignInLink({
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
)}
|
||||
<div className="text-sm text-center text-muted-foreground mt-8 flex flex-col items-center">
|
||||
<span>{descriptionText}</span>
|
||||
<button
|
||||
onClick={handleClick}
|
||||
className="underline text-inherit bg-transparent border-none p-0 cursor-pointer"
|
||||
>
|
||||
{linkText}
|
||||
</button>
|
||||
<div
|
||||
className={cn(
|
||||
"",
|
||||
primaryActionVariant === "button" && "gap-3",
|
||||
className
|
||||
)}
|
||||
>
|
||||
{primaryActionVariant === "button" ? (
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
className="w-full inline-flex items-center gap-2"
|
||||
onClick={handleClick}
|
||||
>
|
||||
<Building2 className="size-4 shrink-0" aria-hidden />
|
||||
<span>{linkText}</span>
|
||||
</Button>
|
||||
) : (
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleClick}
|
||||
className="underline text-inherit bg-transparent border-none p-0 cursor-pointer"
|
||||
>
|
||||
{linkText}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -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<ViewState>({ 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({
|
||||
)}
|
||||
/>
|
||||
|
||||
<div className="text-center">
|
||||
<Link
|
||||
href={resetPasswordHref}
|
||||
className="text-sm text-muted-foreground"
|
||||
>
|
||||
{t("passwordForgot")}
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
{(error || securityKeyError) && (
|
||||
<Alert variant="destructive">
|
||||
<AlertDescription>
|
||||
@@ -219,7 +264,7 @@ export default function SmartLoginForm({
|
||||
</form>
|
||||
</Form>
|
||||
|
||||
<div className="space-y-2">
|
||||
<div className="space-y-4">
|
||||
<Button
|
||||
type="submit"
|
||||
form="form"
|
||||
@@ -236,6 +281,28 @@ export default function SmartLoginForm({
|
||||
onError={setSecurityKeyError}
|
||||
disabled={loading}
|
||||
/>
|
||||
|
||||
{orgSignIn && (
|
||||
<>
|
||||
<div className="relative my-4">
|
||||
<div className="absolute inset-0 flex items-center">
|
||||
<Separator />
|
||||
</div>
|
||||
<div className="relative flex justify-center text-xs uppercase">
|
||||
<span className="px-2 bg-card text-muted-foreground">
|
||||
{t("idpContinue")}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<OrgSignInLink
|
||||
href={orgSignIn.href}
|
||||
linkText={orgSignIn.linkText}
|
||||
descriptionText={orgSignIn.descriptionText}
|
||||
primaryActionVariant="button"
|
||||
className="mt-0"
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user