use pricing matrix in existing usePaidStatus funcitons

This commit is contained in:
miloschwartz
2026-02-09 18:17:18 -08:00
parent 1b5cfaa49b
commit a095dddd01
12 changed files with 33 additions and 17 deletions

View File

@@ -1,3 +1,5 @@
import { Tier } from "@server/types/Tiers";
export enum TierFeature {
OrgOidc = "orgOidc",
CustomAuthenticationDomain = "customAuthenticationDomain",
@@ -14,7 +16,7 @@ export enum TierFeature {
PasswordExpirationPolicies = "passwordExpirationPolicies"
}
export const tierMatrix: Record<TierFeature, string[]> = {
export const tierMatrix: Record<TierFeature, Tier[]> = {
[TierFeature.OrgOidc]: ["tier1", "tier2", "tier3", "enterprise"],
[TierFeature.CustomAuthenticationDomain]: [
"tier1",

View File

@@ -1,6 +1,8 @@
import { Tier } from "@server/types/Tiers";
export async function isLicensedOrSubscribed(
orgId: string,
tiers: string[]
tiers: Tier[]
): Promise<boolean> {
return false;
}

View File

@@ -1,6 +1,8 @@
import { Tier } from "@server/types/Tiers";
export async function isSubscribed(
orgId: string,
tiers: string[]
tiers: Tier[]
): Promise<boolean> {
return false;
}

View File

@@ -14,10 +14,11 @@
import { build } from "@server/build";
import license from "#private/license/license";
import { isSubscribed } from "#private/lib/isSubscribed";
import { Tier } from "@server/types/Tiers";
export async function isLicensedOrSubscribed(
orgId: string,
tiers: string[]
tiers: Tier[]
): Promise<boolean> {
if (build === "enterprise") {
return await license.isUnlocked();

View File

@@ -13,10 +13,11 @@
import { build } from "@server/build";
import { getOrgTierData } from "#private/lib/billing";
import { Tier } from "@server/types/Tiers";
export async function isSubscribed(
orgId: string,
tiers: string[]
tiers: Tier[]
): Promise<boolean> {
if (build === "saas") {
const { tier, active } = await getOrgTierData(orgId);

View File

@@ -16,8 +16,9 @@ import createHttpError from "http-errors";
import HttpCode from "@server/types/HttpCode";
import { build } from "@server/build";
import { getOrgTierData } from "#private/lib/billing";
import { Tier } from "@server/types/Tiers";
export function verifyValidSubscription(tiers: string[]) {
export function verifyValidSubscription(tiers: Tier[]) {
return async function (
req: Request,
res: Response,

View File

@@ -40,6 +40,7 @@ import {
import { useParams } from "next/navigation";
import { FaApple, FaWindows, FaLinux } from "react-icons/fa";
import { SiAndroid } from "react-icons/si";
import { tierMatrix } from "@server/lib/billing/tierMatrix";
function formatTimestamp(timestamp: number | null | undefined): string {
if (!timestamp) return "-";
@@ -156,8 +157,11 @@ export default function GeneralPage() {
const showApprovalFeatures = build !== "oss" && isPaidUser;
const formatPostureValue = (value: boolean | null | undefined) => {
if (value === null || value === undefined) return "-";
const formatPostureValue = (
value: boolean | null | undefined | "-"
) => {
if (value === null || value === undefined || value === "-")
return "-";
return (
<div className="flex items-center gap-2">
{value ? (
@@ -594,7 +598,7 @@ export default function GeneralPage() {
{t("biometricsEnabled")}
</InfoSectionTitle>
<InfoSectionContent>
{isPaidUser
{isPaidUser(tierMatrix.devicePosture)
? formatPostureValue(
client.posture
.biometricsEnabled

View File

@@ -44,6 +44,7 @@ import { getUserDisplayName } from "@app/lib/getUserDisplayName";
import { orgQueries, resourceQueries } from "@app/lib/queries";
import { zodResolver } from "@hookform/resolvers/zod";
import { build } from "@server/build";
import { tierMatrix } from "@server/lib/billing/tierMatrix";
import { UserType } from "@server/types/UserTypes";
import { useQuery, useQueryClient } from "@tanstack/react-query";
import SetResourcePasswordForm from "components/SetResourcePasswordForm";
@@ -164,7 +165,7 @@ export default function ResourceAuthenticationPage() {
const allIdps = useMemo(() => {
if (build === "saas") {
if (isPaidUser) {
if (isPaidUser(tierMatrix.orgOidc)) {
return orgIdps.map((idp) => ({
id: idp.idpId,
text: idp.name

View File

@@ -31,6 +31,7 @@ import { Separator } from "./ui/separator";
import { InfoPopup } from "./ui/info-popup";
import { ApprovalsEmptyState } from "./ApprovalsEmptyState";
import { usePaidStatus } from "@app/hooks/usePaidStatus";
import { tierMatrix } from "@server/lib/billing/tierMatrix";
export type ApprovalFeedProps = {
orgId: string;
@@ -55,7 +56,7 @@ export function ApprovalFeed({
const { data, isFetching, refetch } = useQuery({
...approvalQueries.listApprovals(orgId, filters),
enabled: isPaidUser
enabled: isPaidUser(tierMatrix.deviceApprovals)
});
const approvals = data?.approvals ?? [];

View File

@@ -47,10 +47,10 @@ export function PaidFeaturesAlert() {
) : null}
{build === "oss" && !hasEnterpriseLicense ? (
<Card className={bannerClassName}>
<Card className="mb-6 border-purple-500/30 bg-linear-to-br from-purple-500/10 via-background to-background overflow-hidden">
<CardContent className={bannerContentClassName}>
<div className={bannerRowClassName}>
<KeyRound className="size-4 shrink-0 text-primary" />
<KeyRound className="size-4 shrink-0 text-purple-500" />
<span>
{t.rich("ossEnterpriseEditionRequired", {
enterpriseEditionLink: (chunks) => (
@@ -58,7 +58,7 @@ export function PaidFeaturesAlert() {
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"
className="inline-flex items-center gap-1 font-medium text-purple-600 underline"
>
{chunks}
<ExternalLink className="size-3.5 shrink-0" />

View File

@@ -1,10 +1,11 @@
import { GetOrgSubscriptionResponse } from "@server/routers/billing/types";
import { Tier } from "@server/types/Tiers";
import { createContext } from "react";
type SubscriptionStatusContextType = {
subscriptionStatus: GetOrgSubscriptionResponse | null;
updateSubscriptionStatus: (updatedSite: GetOrgSubscriptionResponse) => void;
getTier: () => { tier: string | null; active: boolean };
getTier: () => { tier: Tier | null; active: boolean };
isSubscribed: () => boolean;
subscribed: boolean;
};

View File

@@ -1,7 +1,7 @@
import { build } from "@server/build";
import { useLicenseStatusContext } from "./useLicenseStatusContext";
import { useSubscriptionStatusContext } from "./useSubscriptionStatusContext";
import { Tier } from "@server/lib/tiers";
import { Tier } from "@server/types/Tiers";
export function usePaidStatus() {
const { isUnlocked } = useLicenseStatusContext();
@@ -12,7 +12,7 @@ export function usePaidStatus() {
const tierData = subscription?.getTier();
const hasSaasSubscription = build === "saas" && tierData?.active;
function isPaidUser(tiers: Tier): boolean {
function isPaidUser(tiers: Tier[]): boolean {
if (hasEnterpriseLicense) {
return true;
}