mirror of
https://github.com/fosrl/pangolin.git
synced 2026-02-17 02:16:38 +00:00
Merge branch 'dev' into feat/login-page-customization
This commit is contained in:
@@ -154,7 +154,7 @@ export default async function OrgAuthPage(props: {
|
||||
</Link>
|
||||
</span>
|
||||
</div>
|
||||
<Card className="shadow-md w-full max-w-md">
|
||||
<Card className="w-full max-w-md">
|
||||
<CardHeader>
|
||||
{branding?.logoUrl && (
|
||||
<div className="flex flex-row items-center justify-center mb-3">
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
import ThemeSwitcher from "@app/components/ThemeSwitcher";
|
||||
import { Separator } from "@app/components/ui/separator";
|
||||
import { priv } from "@app/lib/api";
|
||||
import { pullEnv } from "@app/lib/pullEnv";
|
||||
import { build } from "@server/build";
|
||||
import { GetLicenseStatusResponse } from "@server/routers/license/types";
|
||||
import { AxiosResponse } from "axios";
|
||||
import { Metadata } from "next";
|
||||
import { getTranslations } from "next-intl/server";
|
||||
import { cache } from "react";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: `Auth - ${process.env.BRANDING_APP_NAME || "Pangolin"}`,
|
||||
@@ -11,15 +19,117 @@ type AuthLayoutProps = {
|
||||
};
|
||||
|
||||
export default async function AuthLayout({ children }: AuthLayoutProps) {
|
||||
const env = pullEnv();
|
||||
const t = await getTranslations();
|
||||
let hideFooter = false;
|
||||
|
||||
if (build == "enterprise") {
|
||||
const licenseStatusRes = await cache(
|
||||
async () =>
|
||||
await priv.get<AxiosResponse<GetLicenseStatusResponse>>(
|
||||
"/license/status"
|
||||
)
|
||||
)();
|
||||
if (
|
||||
env.branding.hideAuthLayoutFooter &&
|
||||
licenseStatusRes.data.data.isHostLicensed &&
|
||||
licenseStatusRes.data.data.isLicenseValid
|
||||
) {
|
||||
hideFooter = true;
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="h-full flex flex-col">
|
||||
<div className="flex justify-end items-center p-3 space-x-2">
|
||||
<ThemeSwitcher />
|
||||
</div>
|
||||
|
||||
<div className="flex-1 flex items-center justify-center">
|
||||
<div className="flex-1 flex md:items-center justify-center">
|
||||
<div className="w-full max-w-md p-3">{children}</div>
|
||||
</div>
|
||||
|
||||
{!hideFooter && (
|
||||
<footer className="hidden md:block w-full mt-12 py-3 mb-6 px-4">
|
||||
<div className="container mx-auto flex flex-wrap justify-center items-center h-3 space-x-4 text-xs text-neutral-400 dark:text-neutral-600">
|
||||
<a
|
||||
href="https://pangolin.net"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
aria-label="Built by Fossorial"
|
||||
className="flex items-center space-x-2 whitespace-nowrap"
|
||||
>
|
||||
<span>
|
||||
© {new Date().getFullYear()} Fossorial, Inc.
|
||||
</span>
|
||||
</a>
|
||||
<Separator orientation="vertical" />
|
||||
<a
|
||||
href="https://pangolin.net"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
aria-label="Built by Fossorial"
|
||||
className="flex items-center space-x-2 whitespace-nowrap"
|
||||
>
|
||||
<span>
|
||||
{process.env.BRANDING_APP_NAME || "Pangolin"}
|
||||
</span>
|
||||
</a>
|
||||
<Separator orientation="vertical" />
|
||||
<span>
|
||||
{build === "oss"
|
||||
? t("communityEdition")
|
||||
: build === "enterprise"
|
||||
? t("enterpriseEdition")
|
||||
: t("pangolinCloud")}
|
||||
</span>
|
||||
{build === "saas" && (
|
||||
<>
|
||||
<Separator orientation="vertical" />
|
||||
<a
|
||||
href="https://pangolin.net/terms-of-service.html"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
aria-label="GitHub"
|
||||
className="flex items-center space-x-2 whitespace-nowrap"
|
||||
>
|
||||
<span>{t("terms")}</span>
|
||||
</a>
|
||||
<Separator orientation="vertical" />
|
||||
<a
|
||||
href="https://pangolin.net/privacy-policy.html"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
aria-label="GitHub"
|
||||
className="flex items-center space-x-2 whitespace-nowrap"
|
||||
>
|
||||
<span>{t("privacy")}</span>
|
||||
</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>
|
||||
</footer>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
34
src/app/auth/login/device/page.tsx
Normal file
34
src/app/auth/login/device/page.tsx
Normal file
@@ -0,0 +1,34 @@
|
||||
import { verifySession } from "@app/lib/auth/verifySession";
|
||||
import { redirect } from "next/navigation";
|
||||
import DeviceLoginForm from "@/components/DeviceLoginForm";
|
||||
import { cache } from "react";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
type Props = {
|
||||
searchParams: Promise<{ code?: string }>;
|
||||
};
|
||||
|
||||
export default async function DeviceLoginPage({ searchParams }: Props) {
|
||||
const user = await verifySession({ forceLogin: true });
|
||||
|
||||
const params = await searchParams;
|
||||
const code = params.code || "";
|
||||
|
||||
if (!user) {
|
||||
const redirectDestination = code
|
||||
? `/auth/login/device?code=${encodeURIComponent(code)}`
|
||||
: "/auth/login/device";
|
||||
redirect(`/auth/login?forceLogin=true&redirect=${encodeURIComponent(redirectDestination)}`);
|
||||
}
|
||||
|
||||
const userName = user?.name || user?.username || "";
|
||||
|
||||
return (
|
||||
<DeviceLoginForm
|
||||
userEmail={user?.email || ""}
|
||||
userName={userName}
|
||||
initialCode={code}
|
||||
/>
|
||||
);
|
||||
}
|
||||
47
src/app/auth/login/device/success/page.tsx
Normal file
47
src/app/auth/login/device/success/page.tsx
Normal file
@@ -0,0 +1,47 @@
|
||||
"use client";
|
||||
|
||||
import { Card, CardContent, CardHeader } from "@/components/ui/card";
|
||||
import BrandingLogo from "@app/components/BrandingLogo";
|
||||
import { useEnvContext } from "@app/hooks/useEnvContext";
|
||||
import { useLicenseStatusContext } from "@app/hooks/useLicenseStatusContext";
|
||||
import { CheckCircle2 } from "lucide-react";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
export default function DeviceAuthSuccessPage() {
|
||||
const { env } = useEnvContext();
|
||||
const { isUnlocked } = useLicenseStatusContext();
|
||||
const t = useTranslations();
|
||||
|
||||
const logoWidth = isUnlocked()
|
||||
? env.branding.logo?.authPage?.width || 175
|
||||
: 175;
|
||||
const logoHeight = isUnlocked()
|
||||
? env.branding.logo?.authPage?.height || 58
|
||||
: 58;
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader className="border-b">
|
||||
<div className="flex flex-row items-center justify-center">
|
||||
<BrandingLogo height={logoHeight} width={logoWidth} />
|
||||
</div>
|
||||
<div className="text-center space-y-1 pt-3">
|
||||
<p className="text-muted-foreground">{t("deviceActivation")}</p>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className="p-6">
|
||||
<div className="flex flex-col items-center space-y-4">
|
||||
<CheckCircle2 className="h-12 w-12 text-green-500" />
|
||||
<div className="space-y-2">
|
||||
<h3 className="text-xl font-bold text-center">
|
||||
{t("deviceConnected")}
|
||||
</h3>
|
||||
<p className="text-center text-sm text-muted-foreground">
|
||||
{t("deviceAuthorizedMessage")}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
@@ -25,12 +25,14 @@ export default async function Page(props: {
|
||||
const user = await getUser({ skipCheckVerifyEmail: true });
|
||||
|
||||
const isInvite = searchParams?.redirect?.includes("/invite");
|
||||
const forceLoginParam = searchParams?.forceLogin;
|
||||
const forceLogin = forceLoginParam === "true";
|
||||
|
||||
const env = pullEnv();
|
||||
|
||||
const signUpDisabled = env.flags.disableSignupWithoutInvite;
|
||||
|
||||
if (user) {
|
||||
if (user && !forceLogin) {
|
||||
redirect("/");
|
||||
}
|
||||
|
||||
@@ -53,7 +55,7 @@ export default async function Page(props: {
|
||||
|
||||
if (loginPageDomain) {
|
||||
const redirectUrl = searchParams.redirect as string | undefined;
|
||||
|
||||
|
||||
let url = `https://${loginPageDomain}/auth/org`;
|
||||
if (redirectUrl) {
|
||||
url += `?redirect=${redirectUrl}`;
|
||||
@@ -96,7 +98,7 @@ export default async function Page(props: {
|
||||
</div>
|
||||
)}
|
||||
|
||||
<DashboardLoginForm redirect={redirectUrl} idps={loginIdps} />
|
||||
<DashboardLoginForm redirect={redirectUrl} idps={loginIdps} forceLogin={forceLogin} />
|
||||
|
||||
{(!signUpDisabled || isInvite) && (
|
||||
<p className="text-center text-muted-foreground mt-4">
|
||||
|
||||
Reference in New Issue
Block a user