mirror of
https://github.com/fosrl/pangolin.git
synced 2026-06-19 05:42:47 +00:00
standardize and fix branding on new resources auth pages
This commit is contained in:
@@ -22,7 +22,8 @@ import {
|
|||||||
CardTitle,
|
CardTitle,
|
||||||
CardDescription
|
CardDescription
|
||||||
} from "@app/components/ui/card";
|
} from "@app/components/ui/card";
|
||||||
import Link from "next/link";
|
import BrandedAuthSurface from "@app/components/BrandedAuthSurface";
|
||||||
|
import PoweredByPangolin from "@app/components/PoweredByPangolin";
|
||||||
|
|
||||||
declare module "react" {
|
declare module "react" {
|
||||||
namespace JSX {
|
namespace JSX {
|
||||||
@@ -60,10 +61,12 @@ const isIronError = (error: unknown): error is IronError => {
|
|||||||
|
|
||||||
export default function RdpClient({
|
export default function RdpClient({
|
||||||
target,
|
target,
|
||||||
error
|
error,
|
||||||
|
primaryColor
|
||||||
}: {
|
}: {
|
||||||
target: GetBrowserTargetResponse | null;
|
target: GetBrowserTargetResponse | null;
|
||||||
error: string | null;
|
error: string | null;
|
||||||
|
primaryColor?: string | null;
|
||||||
}) {
|
}) {
|
||||||
const STORAGE_KEY = "pangolin_rdp_credentials";
|
const STORAGE_KEY = "pangolin_rdp_credentials";
|
||||||
|
|
||||||
@@ -315,20 +318,8 @@ export default function RdpClient({
|
|||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
return (
|
return (
|
||||||
<div>
|
<BrandedAuthSurface primaryColor={primaryColor}>
|
||||||
<div className="text-center mb-2">
|
<PoweredByPangolin />
|
||||||
<span className="text-sm text-muted-foreground">
|
|
||||||
Powered by{" "}
|
|
||||||
<Link
|
|
||||||
href="https://pangolin.net/"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
className="underline"
|
|
||||||
>
|
|
||||||
Pangolin
|
|
||||||
</Link>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<Card className="w-full">
|
<Card className="w-full">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle>RDP</CardTitle>
|
<CardTitle>RDP</CardTitle>
|
||||||
@@ -337,27 +328,15 @@ export default function RdpClient({
|
|||||||
<p className="text-destructive text-sm">{error}</p>
|
<p className="text-destructive text-sm">{error}</p>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</BrandedAuthSurface>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{showLogin && (
|
{showLogin && (
|
||||||
<div>
|
<BrandedAuthSurface primaryColor={primaryColor}>
|
||||||
<div className="text-center mb-2">
|
<PoweredByPangolin />
|
||||||
<span className="text-sm text-muted-foreground">
|
|
||||||
Powered by{" "}
|
|
||||||
<Link
|
|
||||||
href="https://pangolin.net/"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
className="underline"
|
|
||||||
>
|
|
||||||
Pangolin
|
|
||||||
</Link>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<Card className="w-full">
|
<Card className="w-full">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle>Sign in to Remote Desktop</CardTitle>
|
<CardTitle>Sign in to Remote Desktop</CardTitle>
|
||||||
@@ -441,7 +420,7 @@ export default function RdpClient({
|
|||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</BrandedAuthSurface>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { generateBrowserGatewayMetadata } from "@app/lib/browserGatewayMetadata";
|
import { generateBrowserGatewayMetadata } from "@app/lib/browserGatewayMetadata";
|
||||||
import { getBrowserTargetForRequest } from "@app/lib/getBrowserTargetForRequest";
|
import { getBrowserTargetForRequest } from "@app/lib/getBrowserTargetForRequest";
|
||||||
|
import { loadOrgLoginPageBranding } from "@app/lib/loadOrgLoginPageBranding";
|
||||||
import RdpClient from "./RdpClient";
|
import RdpClient from "./RdpClient";
|
||||||
import AuthFooter from "@app/components/AuthFooter";
|
import AuthFooter from "@app/components/AuthFooter";
|
||||||
|
|
||||||
@@ -12,12 +13,19 @@ export async function generateMetadata() {
|
|||||||
export default async function RdpPage() {
|
export default async function RdpPage() {
|
||||||
const { target } = await getBrowserTargetForRequest();
|
const { target } = await getBrowserTargetForRequest();
|
||||||
const error = target ? null : "No resource found for this domain";
|
const error = target ? null : "No resource found for this domain";
|
||||||
|
const { primaryColor } = target
|
||||||
|
? await loadOrgLoginPageBranding(target.orgId)
|
||||||
|
: { primaryColor: null };
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="h-full flex flex-col">
|
<div className="h-full flex flex-col">
|
||||||
<div className="flex-1 flex md:items-center justify-center">
|
<div className="flex-1 flex md:items-center justify-center">
|
||||||
<div className="w-full max-w-md p-3">
|
<div className="w-full max-w-md p-3">
|
||||||
<RdpClient target={target} error={error} />
|
<RdpClient
|
||||||
|
target={target}
|
||||||
|
error={error}
|
||||||
|
primaryColor={primaryColor}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<AuthFooter />
|
<AuthFooter />
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ import { Alert, AlertDescription } from "@/components/ui/alert";
|
|||||||
import { HorizontalTabs } from "@app/components/HorizontalTabs";
|
import { HorizontalTabs } from "@app/components/HorizontalTabs";
|
||||||
import type { SignSshKeyResponse } from "@server/routers/ssh/types";
|
import type { SignSshKeyResponse } from "@server/routers/ssh/types";
|
||||||
import { useTranslations } from "next-intl";
|
import { useTranslations } from "next-intl";
|
||||||
|
import BrandedAuthSurface from "@app/components/BrandedAuthSurface";
|
||||||
|
import PoweredByPangolin from "@app/components/PoweredByPangolin";
|
||||||
|
|
||||||
type AuthTab = "password" | "privateKey";
|
type AuthTab = "password" | "privateKey";
|
||||||
|
|
||||||
@@ -40,12 +42,14 @@ export default function SshClient({
|
|||||||
target,
|
target,
|
||||||
error,
|
error,
|
||||||
signedKeyData,
|
signedKeyData,
|
||||||
privateKey: signedPrivateKey
|
privateKey: signedPrivateKey,
|
||||||
|
primaryColor
|
||||||
}: {
|
}: {
|
||||||
target: GetBrowserTargetResponse | null;
|
target: GetBrowserTargetResponse | null;
|
||||||
error: string | null;
|
error: string | null;
|
||||||
signedKeyData?: SignSshKeyResponse | null;
|
signedKeyData?: SignSshKeyResponse | null;
|
||||||
privateKey?: string | null;
|
privateKey?: string | null;
|
||||||
|
primaryColor?: string | null;
|
||||||
}) {
|
}) {
|
||||||
const STORAGE_KEY = "pangolin_ssh_credentials";
|
const STORAGE_KEY = "pangolin_ssh_credentials";
|
||||||
|
|
||||||
@@ -377,20 +381,8 @@ export default function SshClient({
|
|||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
return (
|
return (
|
||||||
<div>
|
<BrandedAuthSurface primaryColor={primaryColor}>
|
||||||
<div className="text-center mb-2">
|
<PoweredByPangolin />
|
||||||
<span className="text-sm text-muted-foreground">
|
|
||||||
{t("sshPoweredBy")}{" "}
|
|
||||||
<Link
|
|
||||||
href="https://pangolin.net/"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
className="underline"
|
|
||||||
>
|
|
||||||
Pangolin
|
|
||||||
</Link>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<Card className="w-full">
|
<Card className="w-full">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle>{t("sshTitle")}</CardTitle>
|
<CardTitle>{t("sshTitle")}</CardTitle>
|
||||||
@@ -399,27 +391,15 @@ export default function SshClient({
|
|||||||
<p className="text-destructive text-sm">{error}</p>
|
<p className="text-destructive text-sm">{error}</p>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</BrandedAuthSurface>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{!connected && (
|
{!connected && (
|
||||||
<div>
|
<BrandedAuthSurface primaryColor={primaryColor}>
|
||||||
<div className="text-center mb-2">
|
<PoweredByPangolin />
|
||||||
<span className="text-sm text-muted-foreground">
|
|
||||||
{t("sshPoweredBy")}{" "}
|
|
||||||
<Link
|
|
||||||
href="https://pangolin.net/"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
className="underline"
|
|
||||||
>
|
|
||||||
Pangolin
|
|
||||||
</Link>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<Card className="w-full">
|
<Card className="w-full">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle>{t("sshSignInTitle")}</CardTitle>
|
<CardTitle>{t("sshSignInTitle")}</CardTitle>
|
||||||
@@ -496,10 +476,10 @@ export default function SshClient({
|
|||||||
href="https://docs.pangolin.net/"
|
href="https://docs.pangolin.net/"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
className="underline inline-flex items-center gap-1"
|
className="text-primary hover:underline inline-flex items-center gap-1"
|
||||||
>
|
>
|
||||||
{t("sshLearnMore")}
|
{t("sshLearnMore")}
|
||||||
<ExternalLink className="h-3 w-3" />
|
<ExternalLink className="size-3.5 shrink-0" />
|
||||||
</Link>
|
</Link>
|
||||||
</p>
|
</p>
|
||||||
<Field
|
<Field
|
||||||
@@ -573,7 +553,7 @@ export default function SshClient({
|
|||||||
</HorizontalTabs>
|
</HorizontalTabs>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</BrandedAuthSurface>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{connected && (
|
{connected && (
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { headers } from "next/headers";
|
|||||||
import { priv } from "@app/lib/api";
|
import { priv } from "@app/lib/api";
|
||||||
import { generateBrowserGatewayMetadata } from "@app/lib/browserGatewayMetadata";
|
import { generateBrowserGatewayMetadata } from "@app/lib/browserGatewayMetadata";
|
||||||
import { getBrowserTargetForRequest } from "@app/lib/getBrowserTargetForRequest";
|
import { getBrowserTargetForRequest } from "@app/lib/getBrowserTargetForRequest";
|
||||||
|
import { loadOrgLoginPageBranding } from "@app/lib/loadOrgLoginPageBranding";
|
||||||
import { AxiosResponse } from "axios";
|
import { AxiosResponse } from "axios";
|
||||||
import { GetBrowserTargetResponse } from "@server/routers/browserGatewayTarget";
|
import { GetBrowserTargetResponse } from "@server/routers/browserGatewayTarget";
|
||||||
import SshClient from "./SshClient";
|
import SshClient from "./SshClient";
|
||||||
@@ -154,6 +155,10 @@ export default async function SshPage() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { primaryColor } = target
|
||||||
|
? await loadOrgLoginPageBranding(target.orgId)
|
||||||
|
: { primaryColor: null };
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="h-full flex flex-col">
|
<div className="h-full flex flex-col">
|
||||||
<div className="flex-1 flex md:items-center justify-center">
|
<div className="flex-1 flex md:items-center justify-center">
|
||||||
@@ -163,6 +168,7 @@ export default async function SshPage() {
|
|||||||
error={error}
|
error={error}
|
||||||
signedKeyData={signedKeyData}
|
signedKeyData={signedKeyData}
|
||||||
privateKey={privateKey}
|
privateKey={privateKey}
|
||||||
|
primaryColor={primaryColor}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -13,7 +13,8 @@ import {
|
|||||||
CardTitle,
|
CardTitle,
|
||||||
CardDescription
|
CardDescription
|
||||||
} from "@app/components/ui/card";
|
} from "@app/components/ui/card";
|
||||||
import Link from "next/link";
|
import BrandedAuthSurface from "@app/components/BrandedAuthSurface";
|
||||||
|
import PoweredByPangolin from "@app/components/PoweredByPangolin";
|
||||||
|
|
||||||
type FormState = {
|
type FormState = {
|
||||||
password: string;
|
password: string;
|
||||||
@@ -21,10 +22,12 @@ type FormState = {
|
|||||||
|
|
||||||
export default function VncClient({
|
export default function VncClient({
|
||||||
target,
|
target,
|
||||||
error
|
error,
|
||||||
|
primaryColor
|
||||||
}: {
|
}: {
|
||||||
target: GetBrowserTargetResponse | null;
|
target: GetBrowserTargetResponse | null;
|
||||||
error: string | null;
|
error: string | null;
|
||||||
|
primaryColor?: string | null;
|
||||||
}) {
|
}) {
|
||||||
const STORAGE_KEY = "pangolin_vnc_credentials";
|
const STORAGE_KEY = "pangolin_vnc_credentials";
|
||||||
|
|
||||||
@@ -152,20 +155,8 @@ export default function VncClient({
|
|||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
return (
|
return (
|
||||||
<div>
|
<BrandedAuthSurface primaryColor={primaryColor}>
|
||||||
<div className="text-center mb-2">
|
<PoweredByPangolin />
|
||||||
<span className="text-sm text-muted-foreground">
|
|
||||||
Powered by{" "}
|
|
||||||
<Link
|
|
||||||
href="https://pangolin.net/"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
className="underline"
|
|
||||||
>
|
|
||||||
Pangolin
|
|
||||||
</Link>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<Card className="w-full">
|
<Card className="w-full">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle>VNC</CardTitle>
|
<CardTitle>VNC</CardTitle>
|
||||||
@@ -174,27 +165,15 @@ export default function VncClient({
|
|||||||
<p className="text-destructive text-sm">{error}</p>
|
<p className="text-destructive text-sm">{error}</p>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</BrandedAuthSurface>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{!connected && (
|
{!connected && (
|
||||||
<div>
|
<BrandedAuthSurface primaryColor={primaryColor}>
|
||||||
<div className="text-center mb-2">
|
<PoweredByPangolin />
|
||||||
<span className="text-sm text-muted-foreground">
|
|
||||||
Powered by{" "}
|
|
||||||
<Link
|
|
||||||
href="https://pangolin.net/"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
className="underline"
|
|
||||||
>
|
|
||||||
Pangolin
|
|
||||||
</Link>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<Card className="w-full">
|
<Card className="w-full">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle>VNC</CardTitle>
|
<CardTitle>VNC</CardTitle>
|
||||||
@@ -224,7 +203,7 @@ export default function VncClient({
|
|||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</BrandedAuthSurface>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { generateBrowserGatewayMetadata } from "@app/lib/browserGatewayMetadata";
|
import { generateBrowserGatewayMetadata } from "@app/lib/browserGatewayMetadata";
|
||||||
import { getBrowserTargetForRequest } from "@app/lib/getBrowserTargetForRequest";
|
import { getBrowserTargetForRequest } from "@app/lib/getBrowserTargetForRequest";
|
||||||
|
import { loadOrgLoginPageBranding } from "@app/lib/loadOrgLoginPageBranding";
|
||||||
import VncClient from "./VncClient";
|
import VncClient from "./VncClient";
|
||||||
import AuthFooter from "@app/components/AuthFooter";
|
import AuthFooter from "@app/components/AuthFooter";
|
||||||
|
|
||||||
@@ -12,12 +13,19 @@ export async function generateMetadata() {
|
|||||||
export default async function VncPage() {
|
export default async function VncPage() {
|
||||||
const { target } = await getBrowserTargetForRequest();
|
const { target } = await getBrowserTargetForRequest();
|
||||||
const error = target ? null : "No resource found for this domain";
|
const error = target ? null : "No resource found for this domain";
|
||||||
|
const { primaryColor } = target
|
||||||
|
? await loadOrgLoginPageBranding(target.orgId)
|
||||||
|
: { primaryColor: null };
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="h-full flex flex-col">
|
<div className="h-full flex flex-col">
|
||||||
<div className="flex-1 flex md:items-center justify-center">
|
<div className="flex-1 flex md:items-center justify-center">
|
||||||
<div className="w-full max-w-md p-3">
|
<div className="w-full max-w-md p-3">
|
||||||
<VncClient target={target} error={error} />
|
<VncClient
|
||||||
|
target={target}
|
||||||
|
error={error}
|
||||||
|
primaryColor={primaryColor}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<AuthFooter />
|
<AuthFooter />
|
||||||
|
|||||||
26
src/components/BrandedAuthSurface.tsx
Normal file
26
src/components/BrandedAuthSurface.tsx
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useLicenseStatusContext } from "@app/hooks/useLicenseStatusContext";
|
||||||
|
|
||||||
|
type BrandedAuthSurfaceProps = {
|
||||||
|
primaryColor?: string | null;
|
||||||
|
children: React.ReactNode;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function BrandedAuthSurface({
|
||||||
|
primaryColor,
|
||||||
|
children
|
||||||
|
}: BrandedAuthSurfaceProps) {
|
||||||
|
const { isUnlocked } = useLicenseStatusContext();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
// @ts-expect-error CSS variable
|
||||||
|
"--primary": isUnlocked() ? primaryColor : null
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -14,9 +14,10 @@ import {
|
|||||||
import { Button } from "@app/components/ui/button";
|
import { Button } from "@app/components/ui/button";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { replacePlaceholder } from "@app/lib/replacePlaceholder";
|
import { replacePlaceholder } from "@app/lib/replacePlaceholder";
|
||||||
|
import PoweredByPangolin from "@app/components/PoweredByPangolin";
|
||||||
|
import BrandedAuthSurface from "@app/components/BrandedAuthSurface";
|
||||||
import { getTranslations } from "next-intl/server";
|
import { getTranslations } from "next-intl/server";
|
||||||
import { pullEnv } from "@app/lib/pullEnv";
|
import { pullEnv } from "@app/lib/pullEnv";
|
||||||
import { build } from "@server/build";
|
|
||||||
|
|
||||||
type OrgLoginPageProps = {
|
type OrgLoginPageProps = {
|
||||||
loginPage: LoadLoginPageResponse | undefined;
|
loginPage: LoadLoginPageResponse | undefined;
|
||||||
@@ -52,22 +53,8 @@ export default async function OrgLoginPage({
|
|||||||
const env = pullEnv();
|
const env = pullEnv();
|
||||||
const t = await getTranslations();
|
const t = await getTranslations();
|
||||||
return (
|
return (
|
||||||
<div>
|
<BrandedAuthSurface primaryColor={branding?.primaryColor ?? null}>
|
||||||
{build !== "enterprise" || !env.branding.hidePoweredBy ? (
|
<PoweredByPangolin />
|
||||||
<div className="text-center mb-2">
|
|
||||||
<span className="text-sm text-muted-foreground">
|
|
||||||
{t("poweredBy")}{" "}
|
|
||||||
<Link
|
|
||||||
href="https://pangolin.net/"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
className="underline"
|
|
||||||
>
|
|
||||||
{env.branding.appName || "Pangolin"}
|
|
||||||
</Link>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
<Card className="w-full max-w-md">
|
<Card className="w-full max-w-md">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
{branding?.logoUrl && (
|
{branding?.logoUrl && (
|
||||||
@@ -127,6 +114,6 @@ export default async function OrgLoginPage({
|
|||||||
{t("loginBack")}
|
{t("loginBack")}
|
||||||
</Link>
|
</Link>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</BrandedAuthSurface>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
53
src/components/PoweredByPangolin.tsx
Normal file
53
src/components/PoweredByPangolin.tsx
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import Link from "next/link";
|
||||||
|
import { useEnvContext } from "@app/hooks/useEnvContext";
|
||||||
|
import { useLicenseStatusContext } from "@app/hooks/useLicenseStatusContext";
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
|
import { build } from "@server/build";
|
||||||
|
|
||||||
|
function PoweredByLabel({ brandName }: { brandName: string }) {
|
||||||
|
const t = useTranslations();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="text-center mb-2">
|
||||||
|
<span className="text-sm text-muted-foreground">
|
||||||
|
{t("poweredBy")}{" "}
|
||||||
|
{brandName === "Pangolin" ? (
|
||||||
|
<Link
|
||||||
|
href="https://pangolin.net/"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="underline"
|
||||||
|
>
|
||||||
|
Pangolin
|
||||||
|
</Link>
|
||||||
|
) : (
|
||||||
|
brandName
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function PoweredByPangolin() {
|
||||||
|
const { env } = useEnvContext();
|
||||||
|
const { isUnlocked } = useLicenseStatusContext();
|
||||||
|
|
||||||
|
if (isUnlocked() && build === "enterprise") {
|
||||||
|
if (
|
||||||
|
env.branding.resourceAuthPage?.hidePoweredBy ||
|
||||||
|
env.branding.hidePoweredBy
|
||||||
|
) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PoweredByLabel
|
||||||
|
brandName={env.branding.appName || "Pangolin"}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return <PoweredByLabel brandName="Pangolin" />;
|
||||||
|
}
|
||||||
@@ -41,8 +41,9 @@ import {
|
|||||||
} from "@app/actions/server";
|
} from "@app/actions/server";
|
||||||
import { useEnvContext } from "@app/hooks/useEnvContext";
|
import { useEnvContext } from "@app/hooks/useEnvContext";
|
||||||
import { toast } from "@app/hooks/useToast";
|
import { toast } from "@app/hooks/useToast";
|
||||||
import Link from "next/link";
|
|
||||||
import BrandingLogo from "@app/components/BrandingLogo";
|
import BrandingLogo from "@app/components/BrandingLogo";
|
||||||
|
import BrandedAuthSurface from "@app/components/BrandedAuthSurface";
|
||||||
|
import PoweredByPangolin from "@app/components/PoweredByPangolin";
|
||||||
import { useSupporterStatusContext } from "@app/hooks/useSupporterStatusContext";
|
import { useSupporterStatusContext } from "@app/hooks/useSupporterStatusContext";
|
||||||
import { useTranslations } from "next-intl";
|
import { useTranslations } from "next-intl";
|
||||||
import { build } from "@server/build";
|
import { build } from "@server/build";
|
||||||
@@ -366,57 +367,20 @@ export default function ResourceAuthPortal(props: ResourceAuthPortalProps) {
|
|||||||
: 100;
|
: 100;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<BrandedAuthSurface primaryColor={props.branding?.primaryColor}>
|
||||||
style={{
|
|
||||||
// @ts-expect-error CSS variable
|
|
||||||
"--primary": isUnlocked() ? props.branding?.primaryColor : null
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{!accessDenied ? (
|
{!accessDenied ? (
|
||||||
<div>
|
<div>
|
||||||
{isUnlocked() && build === "enterprise" ? (
|
<PoweredByPangolin />
|
||||||
!env.branding.resourceAuthPage?.hidePoweredBy &&
|
|
||||||
!env.branding.hidePoweredBy && (
|
|
||||||
<div className="text-center mb-2">
|
|
||||||
<span className="text-sm text-muted-foreground">
|
|
||||||
{t("poweredBy")}{" "}
|
|
||||||
<Link
|
|
||||||
href="https://pangolin.net/"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
className="underline"
|
|
||||||
>
|
|
||||||
{env.branding.appName || "Pangolin"}
|
|
||||||
</Link>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
) : (
|
|
||||||
<div className="text-center mb-2">
|
|
||||||
<span className="text-sm text-muted-foreground">
|
|
||||||
{t("poweredBy")}{" "}
|
|
||||||
<Link
|
|
||||||
href="https://pangolin.net/"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
className="underline"
|
|
||||||
>
|
|
||||||
Pangolin
|
|
||||||
</Link>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
{isUnlocked() &&
|
{isUnlocked() &&
|
||||||
build !== "oss" &&
|
build !== "oss" &&
|
||||||
(env.branding?.resourceAuthPage?.showLogo ||
|
props.branding?.logoUrl && (
|
||||||
props.branding) && (
|
|
||||||
<div className="flex flex-row items-center justify-center mb-3">
|
<div className="flex flex-row items-center justify-center mb-3">
|
||||||
<BrandingLogo
|
<BrandingLogo
|
||||||
height={logoHeight}
|
height={logoHeight}
|
||||||
width={logoWidth}
|
width={logoWidth}
|
||||||
logoPath={props.branding?.logoUrl}
|
logoPath={props.branding.logoUrl}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -790,6 +754,6 @@ export default function ResourceAuthPortal(props: ResourceAuthPortalProps) {
|
|||||||
) : (
|
) : (
|
||||||
<ResourceAccessDenied />
|
<ResourceAccessDenied />
|
||||||
)}
|
)}
|
||||||
</div>
|
</BrandedAuthSurface>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
31
src/lib/loadOrgLoginPageBranding.ts
Normal file
31
src/lib/loadOrgLoginPageBranding.ts
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import { priv } from "@app/lib/api";
|
||||||
|
import { isOrgSubscribed } from "@app/lib/api/isOrgSubscribed";
|
||||||
|
import { build } from "@server/build";
|
||||||
|
import { LoadLoginPageBrandingResponse } from "@server/routers/loginPage/types";
|
||||||
|
import { AxiosResponse } from "axios";
|
||||||
|
|
||||||
|
export async function loadOrgLoginPageBranding(orgId: string): Promise<{
|
||||||
|
primaryColor: string | null;
|
||||||
|
}> {
|
||||||
|
if (build === "oss") {
|
||||||
|
return { primaryColor: null };
|
||||||
|
}
|
||||||
|
|
||||||
|
const subscribed = await isOrgSubscribed(orgId);
|
||||||
|
if (!subscribed) {
|
||||||
|
return { primaryColor: null };
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await priv.get<
|
||||||
|
AxiosResponse<LoadLoginPageBrandingResponse>
|
||||||
|
>(`/login-page-branding?orgId=${orgId}`);
|
||||||
|
if (res.status === 200) {
|
||||||
|
return { primaryColor: res.data.data.primaryColor ?? null };
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
|
||||||
|
return { primaryColor: null };
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user