mirror of
https://github.com/fosrl/pangolin.git
synced 2026-02-13 13:16:21 +00:00
show features in ce
This commit is contained in:
@@ -30,6 +30,7 @@ import {
|
||||
import { Separator } from "./ui/separator";
|
||||
import { InfoPopup } from "./ui/info-popup";
|
||||
import { ApprovalsEmptyState } from "./ApprovalsEmptyState";
|
||||
import { usePaidStatus } from "@app/hooks/usePaidStatus";
|
||||
|
||||
export type ApprovalFeedProps = {
|
||||
orgId: string;
|
||||
@@ -50,9 +51,12 @@ export function ApprovalFeed({
|
||||
Object.fromEntries(searchParams.entries())
|
||||
);
|
||||
|
||||
const { data, isFetching, refetch } = useQuery(
|
||||
approvalQueries.listApprovals(orgId, filters)
|
||||
);
|
||||
const { isPaidUser } = usePaidStatus();
|
||||
|
||||
const { data, isFetching, refetch } = useQuery({
|
||||
...approvalQueries.listApprovals(orgId, filters),
|
||||
enabled: isPaidUser
|
||||
});
|
||||
|
||||
const approvals = data?.approvals ?? [];
|
||||
|
||||
@@ -209,19 +213,19 @@ function ApprovalRequest({ approval, orgId, onSuccess }: ApprovalRequestProps) {
|
||||
|
||||
{approval.type === "user_device" && (
|
||||
<span className="inline-flex items-center gap-1">
|
||||
{approval.deviceName ? (
|
||||
<>
|
||||
{t("requestingNewDeviceApproval")}:{" "}
|
||||
{approval.niceId ? (
|
||||
<Link
|
||||
href={`/${orgId}/settings/clients/user/${approval.niceId}/general`}
|
||||
className="text-primary hover:underline cursor-pointer"
|
||||
>
|
||||
{approval.deviceName}
|
||||
</Link>
|
||||
) : (
|
||||
<span>{approval.deviceName}</span>
|
||||
)}
|
||||
{approval.deviceName ? (
|
||||
<>
|
||||
{t("requestingNewDeviceApproval")}:{" "}
|
||||
{approval.niceId ? (
|
||||
<Link
|
||||
href={`/${orgId}/settings/clients/user/${approval.niceId}/general`}
|
||||
className="text-primary hover:underline cursor-pointer"
|
||||
>
|
||||
{approval.deviceName}
|
||||
</Link>
|
||||
) : (
|
||||
<span>{approval.deviceName}</span>
|
||||
)}
|
||||
{approval.fingerprint && (
|
||||
<InfoPopup>
|
||||
<div className="space-y-1 text-sm">
|
||||
@@ -229,7 +233,10 @@ function ApprovalRequest({ approval, orgId, onSuccess }: ApprovalRequestProps) {
|
||||
{t("deviceInformation")}
|
||||
</div>
|
||||
<div className="text-muted-foreground whitespace-pre-line">
|
||||
{formatFingerprintInfo(approval.fingerprint, t)}
|
||||
{formatFingerprintInfo(
|
||||
approval.fingerprint,
|
||||
t
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</InfoPopup>
|
||||
|
||||
@@ -160,56 +160,51 @@ export default function CreateRoleForm({
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
{build !== "oss" && (
|
||||
<div>
|
||||
<PaidFeaturesAlert />
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="requireDeviceApproval"
|
||||
render={({ field }) => (
|
||||
<FormItem className="my-2">
|
||||
<FormControl>
|
||||
<CheckboxWithLabel
|
||||
{...field}
|
||||
disabled={
|
||||
!isPaidUser
|
||||
}
|
||||
value="on"
|
||||
checked={form.watch(
|
||||
"requireDeviceApproval"
|
||||
)}
|
||||
onCheckedChange={(
|
||||
<PaidFeaturesAlert />
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="requireDeviceApproval"
|
||||
render={({ field }) => (
|
||||
<FormItem className="my-2">
|
||||
<FormControl>
|
||||
<CheckboxWithLabel
|
||||
{...field}
|
||||
disabled={!isPaidUser}
|
||||
value="on"
|
||||
checked={form.watch(
|
||||
"requireDeviceApproval"
|
||||
)}
|
||||
onCheckedChange={(
|
||||
checked
|
||||
) => {
|
||||
if (
|
||||
checked !==
|
||||
"indeterminate"
|
||||
) {
|
||||
form.setValue(
|
||||
"requireDeviceApproval",
|
||||
checked
|
||||
) => {
|
||||
if (
|
||||
checked !==
|
||||
"indeterminate"
|
||||
) {
|
||||
form.setValue(
|
||||
"requireDeviceApproval",
|
||||
checked
|
||||
);
|
||||
}
|
||||
}}
|
||||
label={t(
|
||||
"requireDeviceApproval"
|
||||
)}
|
||||
/>
|
||||
</FormControl>
|
||||
);
|
||||
}
|
||||
}}
|
||||
label={t(
|
||||
"requireDeviceApproval"
|
||||
)}
|
||||
/>
|
||||
</FormControl>
|
||||
|
||||
<FormDescription>
|
||||
{t(
|
||||
"requireDeviceApprovalDescription"
|
||||
)}
|
||||
</FormDescription>
|
||||
<FormDescription>
|
||||
{t(
|
||||
"requireDeviceApprovalDescription"
|
||||
)}
|
||||
</FormDescription>
|
||||
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</form>
|
||||
</Form>
|
||||
</CredenzaBody>
|
||||
|
||||
@@ -168,56 +168,50 @@ export default function EditRoleForm({
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
{build !== "oss" && (
|
||||
<div>
|
||||
<PaidFeaturesAlert />
|
||||
<PaidFeaturesAlert />
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="requireDeviceApproval"
|
||||
render={({ field }) => (
|
||||
<FormItem className="my-2">
|
||||
<FormControl>
|
||||
<CheckboxWithLabel
|
||||
{...field}
|
||||
disabled={
|
||||
!isPaidUser
|
||||
}
|
||||
value="on"
|
||||
checked={form.watch(
|
||||
"requireDeviceApproval"
|
||||
)}
|
||||
onCheckedChange={(
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="requireDeviceApproval"
|
||||
render={({ field }) => (
|
||||
<FormItem className="my-2">
|
||||
<FormControl>
|
||||
<CheckboxWithLabel
|
||||
{...field}
|
||||
disabled={!isPaidUser}
|
||||
value="on"
|
||||
checked={form.watch(
|
||||
"requireDeviceApproval"
|
||||
)}
|
||||
onCheckedChange={(
|
||||
checked
|
||||
) => {
|
||||
if (
|
||||
checked !==
|
||||
"indeterminate"
|
||||
) {
|
||||
form.setValue(
|
||||
"requireDeviceApproval",
|
||||
checked
|
||||
) => {
|
||||
if (
|
||||
checked !==
|
||||
"indeterminate"
|
||||
) {
|
||||
form.setValue(
|
||||
"requireDeviceApproval",
|
||||
checked
|
||||
);
|
||||
}
|
||||
}}
|
||||
label={t(
|
||||
"requireDeviceApproval"
|
||||
)}
|
||||
/>
|
||||
</FormControl>
|
||||
);
|
||||
}
|
||||
}}
|
||||
label={t(
|
||||
"requireDeviceApproval"
|
||||
)}
|
||||
/>
|
||||
</FormControl>
|
||||
|
||||
<FormDescription>
|
||||
{t(
|
||||
"requireDeviceApprovalDescription"
|
||||
)}
|
||||
</FormDescription>
|
||||
<FormDescription>
|
||||
{t(
|
||||
"requireDeviceApprovalDescription"
|
||||
)}
|
||||
</FormDescription>
|
||||
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</form>
|
||||
</Form>
|
||||
</CredenzaBody>
|
||||
|
||||
@@ -1,8 +1,16 @@
|
||||
"use client";
|
||||
import { Alert, AlertDescription } from "@app/components/ui/alert";
|
||||
import { Card, CardContent } from "@app/components/ui/card";
|
||||
import { build } from "@server/build";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { usePaidStatus } from "@app/hooks/usePaidStatus";
|
||||
import { ExternalLink, KeyRound, Sparkles } from "lucide-react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import Link from "next/link";
|
||||
|
||||
const bannerClassName =
|
||||
"mb-6 border-primary/30 bg-linear-to-br from-primary/10 via-background to-background overflow-hidden";
|
||||
const bannerContentClassName = "py-3 px-4";
|
||||
const bannerRowClassName =
|
||||
"flex items-center gap-2.5 text-sm text-muted-foreground";
|
||||
|
||||
export function PaidFeaturesAlert() {
|
||||
const t = useTranslations();
|
||||
@@ -10,19 +18,50 @@ export function PaidFeaturesAlert() {
|
||||
return (
|
||||
<>
|
||||
{build === "saas" && !hasSaasSubscription ? (
|
||||
<Alert variant="info" className="mb-6">
|
||||
<AlertDescription>
|
||||
{t("subscriptionRequiredToUse")}
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
<Card className={bannerClassName}>
|
||||
<CardContent className={bannerContentClassName}>
|
||||
<div className={bannerRowClassName}>
|
||||
<KeyRound className="size-4 shrink-0 text-primary" />
|
||||
<span>{t("subscriptionRequiredToUse")}</span>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
) : null}
|
||||
|
||||
{build === "enterprise" && !hasEnterpriseLicense ? (
|
||||
<Alert variant="info" className="mb-6">
|
||||
<AlertDescription>
|
||||
{t("licenseRequiredToUse")}
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
<Card className={bannerClassName}>
|
||||
<CardContent className={bannerContentClassName}>
|
||||
<div className={bannerRowClassName}>
|
||||
<KeyRound className="size-4 shrink-0 text-primary" />
|
||||
<span>{t("licenseRequiredToUse")}</span>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
) : null}
|
||||
|
||||
{build === "oss" && !hasEnterpriseLicense ? (
|
||||
<Card className={bannerClassName}>
|
||||
<CardContent className={bannerContentClassName}>
|
||||
<div className={bannerRowClassName}>
|
||||
<KeyRound className="size-4 shrink-0 text-primary" />
|
||||
<span>
|
||||
{t.rich("ossEnterpriseEditionRequired", {
|
||||
enterpriseEditionLink: (chunks) => (
|
||||
<Link
|
||||
href="https://docs.pangolin.net/self-host/enterprise-edition"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="inline-flex items-center gap-1 font-medium text-foreground underline"
|
||||
>
|
||||
{chunks}
|
||||
<ExternalLink className="size-3.5 shrink-0" />
|
||||
</Link>
|
||||
)
|
||||
})}
|
||||
</span>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
) : null}
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -190,7 +190,7 @@ export default function UserDevicesTable({ userClients }: ClientTableProps) {
|
||||
const approvalsRes = await api.get<{
|
||||
data: { approvals: Array<{ approvalId: number; clientId: number }> };
|
||||
}>(`/org/${clientRow.orgId}/approvals?approvalState=pending&clientId=${clientRow.id}`);
|
||||
|
||||
|
||||
const approval = approvalsRes.data.data.approvals[0];
|
||||
|
||||
if (!approval) {
|
||||
@@ -232,7 +232,7 @@ export default function UserDevicesTable({ userClients }: ClientTableProps) {
|
||||
const approvalsRes = await api.get<{
|
||||
data: { approvals: Array<{ approvalId: number; clientId: number }> };
|
||||
}>(`/org/${clientRow.orgId}/approvals?approvalState=pending&clientId=${clientRow.id}`);
|
||||
|
||||
|
||||
const approval = approvalsRes.data.data.approvals[0];
|
||||
|
||||
if (!approval) {
|
||||
@@ -548,7 +548,7 @@ export default function UserDevicesTable({ userClients }: ClientTableProps) {
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
{clientRow.approvalState === "pending" && build !== "oss" && (
|
||||
{clientRow.approvalState === "pending" && (
|
||||
<>
|
||||
<DropdownMenuItem
|
||||
onClick={() => approveDevice(clientRow)}
|
||||
@@ -652,17 +652,10 @@ export default function UserDevicesTable({ userClients }: ClientTableProps) {
|
||||
}
|
||||
];
|
||||
|
||||
if (build === "oss") {
|
||||
return allOptions.filter((option) => option.value !== "pending" && option.value !== "denied");
|
||||
}
|
||||
|
||||
return allOptions;
|
||||
}, [t]);
|
||||
|
||||
const statusFilterDefaultValues = useMemo(() => {
|
||||
if (build === "oss") {
|
||||
return ["active"];
|
||||
}
|
||||
return ["active", "pending"];
|
||||
}, []);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user