refactor front end hooks

This commit is contained in:
miloschwartz
2026-02-09 20:50:36 -08:00
parent 10be9bcd56
commit 69c2212ea0
27 changed files with 82 additions and 183 deletions

View File

@@ -46,7 +46,7 @@ import IdpTypeBadge from "@app/components/IdpTypeBadge";
import { useTranslations } from "next-intl"; import { useTranslations } from "next-intl";
import { AxiosResponse } from "axios"; import { AxiosResponse } from "axios";
import { ListRolesResponse } from "@server/routers/role"; import { ListRolesResponse } from "@server/routers/role";
import AutoProvisionConfigWidget from "@app/components/private/AutoProvisionConfigWidget"; import AutoProvisionConfigWidget from "@app/components/AutoProvisionConfigWidget";
export default function GeneralPage() { export default function GeneralPage() {
const { env } = useEnvContext(); const { env } = useEnvContext();

View File

@@ -1,6 +1,6 @@
"use client"; "use client";
import AutoProvisionConfigWidget from "@app/components/private/AutoProvisionConfigWidget"; import AutoProvisionConfigWidget from "@app/components/AutoProvisionConfigWidget";
import { import {
SettingsContainer, SettingsContainer,
SettingsSection, SettingsSection,

View File

@@ -2,7 +2,7 @@ import { internal } from "@app/lib/api";
import { authCookieHeader } from "@app/lib/api/cookies"; import { authCookieHeader } from "@app/lib/api/cookies";
import { AxiosResponse } from "axios"; import { AxiosResponse } from "axios";
import SettingsSectionTitle from "@app/components/SettingsSectionTitle"; import SettingsSectionTitle from "@app/components/SettingsSectionTitle";
import IdpTable, { IdpRow } from "@app/components/private/OrgIdpTable"; import IdpTable, { IdpRow } from "@app/components/OrgIdpTable";
import { getTranslations } from "next-intl/server"; import { getTranslations } from "next-intl/server";
import { PaidFeaturesAlert } from "@app/components/PaidFeaturesAlert"; import { PaidFeaturesAlert } from "@app/components/PaidFeaturesAlert";

View File

@@ -23,9 +23,6 @@ import {
} from "@server/routers/remoteExitNode/types"; } from "@server/routers/remoteExitNode/types";
import { useRemoteExitNodeContext } from "@app/hooks/useRemoteExitNodeContext"; import { useRemoteExitNodeContext } from "@app/hooks/useRemoteExitNodeContext";
import ConfirmDeleteDialog from "@app/components/ConfirmDeleteDialog"; import ConfirmDeleteDialog from "@app/components/ConfirmDeleteDialog";
import { useSubscriptionStatusContext } from "@app/hooks/useSubscriptionStatusContext";
import { useLicenseStatusContext } from "@app/hooks/useLicenseStatusContext";
import { build } from "@server/build";
import { import {
InfoSection, InfoSection,
InfoSectionContent, InfoSectionContent,
@@ -36,6 +33,8 @@ import CopyToClipboard from "@app/components/CopyToClipboard";
import { Alert, AlertDescription, AlertTitle } from "@app/components/ui/alert"; import { Alert, AlertDescription, AlertTitle } from "@app/components/ui/alert";
import { InfoIcon } from "lucide-react"; import { InfoIcon } from "lucide-react";
import { PaidFeaturesAlert } from "@app/components/PaidFeaturesAlert"; import { PaidFeaturesAlert } from "@app/components/PaidFeaturesAlert";
import { usePaidStatus } from "@app/hooks/usePaidStatus";
import { tierMatrix } from "@server/lib/billing/tierMatrix";
export default function CredentialsPage() { export default function CredentialsPage() {
const { env } = useEnvContext(); const { env } = useEnvContext();
@@ -45,6 +44,8 @@ export default function CredentialsPage() {
const t = useTranslations(); const t = useTranslations();
const { remoteExitNode } = useRemoteExitNodeContext(); const { remoteExitNode } = useRemoteExitNodeContext();
const { isPaidUser } = usePaidStatus();
const [modalOpen, setModalOpen] = useState(false); const [modalOpen, setModalOpen] = useState(false);
const [credentials, setCredentials] = const [credentials, setCredentials] =
useState<PickRemoteExitNodeDefaultsResponse | null>(null); useState<PickRemoteExitNodeDefaultsResponse | null>(null);
@@ -57,16 +58,6 @@ export default function CredentialsPage() {
const [showCredentialsAlert, setShowCredentialsAlert] = useState(false); const [showCredentialsAlert, setShowCredentialsAlert] = useState(false);
const [shouldDisconnect, setShouldDisconnect] = useState(true); const [shouldDisconnect, setShouldDisconnect] = useState(true);
const { licenseStatus, isUnlocked } = useLicenseStatusContext();
const subscription = useSubscriptionStatusContext();
const isSecurityFeatureDisabled = () => {
const isEnterpriseNotLicensed = build === "enterprise" && !isUnlocked();
const isSaasNotSubscribed =
build === "saas" && !subscription?.isSubscribed();
return isEnterpriseNotLicensed || isSaasNotSubscribed;
};
const handleConfirmRegenerate = async () => { const handleConfirmRegenerate = async () => {
try { try {
const response = await api.get< const response = await api.get<
@@ -203,7 +194,7 @@ export default function CredentialsPage() {
setShouldDisconnect(false); setShouldDisconnect(false);
setModalOpen(true); setModalOpen(true);
}} }}
disabled={isSecurityFeatureDisabled()} disabled={isPaidUser(tierMatrix.rotateCredentials)}
> >
{t("regenerateCredentialsButton")} {t("regenerateCredentialsButton")}
</Button> </Button>
@@ -212,7 +203,7 @@ export default function CredentialsPage() {
setShouldDisconnect(true); setShouldDisconnect(true);
setModalOpen(true); setModalOpen(true);
}} }}
disabled={isSecurityFeatureDisabled()} disabled={isPaidUser(tierMatrix.rotateCredentials)}
> >
{t("remoteExitNodeRegenerateAndDisconnect")} {t("remoteExitNodeRegenerateAndDisconnect")}
</Button> </Button>

View File

@@ -47,7 +47,8 @@ import { ListIdpsResponse } from "@server/routers/idp";
import { useTranslations } from "next-intl"; import { useTranslations } from "next-intl";
import { build } from "@server/build"; import { build } from "@server/build";
import Image from "next/image"; import Image from "next/image";
import { useSubscriptionStatusContext } from "@app/hooks/useSubscriptionStatusContext"; import { usePaidStatus } from "@app/hooks/usePaidStatus";
import { tierMatrix } from "@server/lib/billing/tierMatrix";
type UserType = "internal" | "oidc"; type UserType = "internal" | "oidc";
@@ -75,7 +76,7 @@ export default function Page() {
const api = createApiClient({ env }); const api = createApiClient({ env });
const t = useTranslations(); const t = useTranslations();
const subscription = useSubscriptionStatusContext(); const { hasSaasSubscription } = usePaidStatus();
const [selectedOption, setSelectedOption] = useState<string | null>( const [selectedOption, setSelectedOption] = useState<string | null>(
"internal" "internal"
@@ -237,7 +238,7 @@ export default function Page() {
} }
async function fetchIdps() { async function fetchIdps() {
if (build === "saas" && !subscription?.subscribed) { if (build === "saas" && !hasSaasSubscription(tierMatrix.orgOidc)) {
return; return;
} }

View File

@@ -19,9 +19,6 @@ import { useTranslations } from "next-intl";
import { PickClientDefaultsResponse } from "@server/routers/client"; import { PickClientDefaultsResponse } from "@server/routers/client";
import { useClientContext } from "@app/hooks/useClientContext"; import { useClientContext } from "@app/hooks/useClientContext";
import ConfirmDeleteDialog from "@app/components/ConfirmDeleteDialog"; import ConfirmDeleteDialog from "@app/components/ConfirmDeleteDialog";
import { useLicenseStatusContext } from "@app/hooks/useLicenseStatusContext";
import { useSubscriptionStatusContext } from "@app/hooks/useSubscriptionStatusContext";
import { build } from "@server/build";
import { import {
InfoSection, InfoSection,
InfoSectionContent, InfoSectionContent,
@@ -33,6 +30,8 @@ import { Alert, AlertDescription, AlertTitle } from "@app/components/ui/alert";
import { InfoIcon } from "lucide-react"; import { InfoIcon } from "lucide-react";
import { PaidFeaturesAlert } from "@app/components/PaidFeaturesAlert"; import { PaidFeaturesAlert } from "@app/components/PaidFeaturesAlert";
import { OlmInstallCommands } from "@app/components/olm-install-commands"; import { OlmInstallCommands } from "@app/components/olm-install-commands";
import { usePaidStatus } from "@app/hooks/usePaidStatus";
import { tierMatrix } from "@server/lib/billing/tierMatrix";
export default function CredentialsPage() { export default function CredentialsPage() {
const { env } = useEnvContext(); const { env } = useEnvContext();
@@ -54,17 +53,7 @@ export default function CredentialsPage() {
const [showCredentialsAlert, setShowCredentialsAlert] = useState(false); const [showCredentialsAlert, setShowCredentialsAlert] = useState(false);
const [shouldDisconnect, setShouldDisconnect] = useState(true); const [shouldDisconnect, setShouldDisconnect] = useState(true);
const { licenseStatus, isUnlocked } = useLicenseStatusContext(); const { isPaidUser } = usePaidStatus();
const subscription = useSubscriptionStatusContext();
const isSecurityFeatureDisabled = () => {
const isEnterpriseNotLicensed = build === "enterprise" && !isUnlocked();
const isSaasNotSubscribed =
build === "saas" && !subscription?.isSubscribed();
return (
isEnterpriseNotLicensed || isSaasNotSubscribed || build === "oss"
);
};
const handleConfirmRegenerate = async () => { const handleConfirmRegenerate = async () => {
try { try {
@@ -191,7 +180,7 @@ export default function CredentialsPage() {
setShouldDisconnect(false); setShouldDisconnect(false);
setModalOpen(true); setModalOpen(true);
}} }}
disabled={isSecurityFeatureDisabled()} disabled={isPaidUser(tierMatrix.rotateCredentials)}
> >
{t("regenerateCredentialsButton")} {t("regenerateCredentialsButton")}
</Button> </Button>
@@ -200,7 +189,7 @@ export default function CredentialsPage() {
setShouldDisconnect(true); setShouldDisconnect(true);
setModalOpen(true); setModalOpen(true);
}} }}
disabled={isSecurityFeatureDisabled()} disabled={isPaidUser(tierMatrix.rotateCredentials)}
> >
{t("clientRegenerateAndDisconnect")} {t("clientRegenerateAndDisconnect")}
</Button> </Button>

View File

@@ -1,5 +1,5 @@
import AuthPageBrandingForm from "@app/components/AuthPageBrandingForm"; import AuthPageBrandingForm from "@app/components/AuthPageBrandingForm";
import AuthPageSettings from "@app/components/private/AuthPageSettings"; import AuthPageSettings from "@app/components/AuthPageSettings";
import { SettingsContainer } from "@app/components/Settings"; import { SettingsContainer } from "@app/components/Settings";
import { internal } from "@app/lib/api"; import { internal } from "@app/lib/api";
import { authCookieHeader } from "@app/lib/api/cookies"; import { authCookieHeader } from "@app/lib/api/cookies";

View File

@@ -13,14 +13,13 @@ import { ArrowUpRight, Key, User } from "lucide-react";
import Link from "next/link"; import Link from "next/link";
import { ColumnFilter } from "@app/components/ColumnFilter"; import { ColumnFilter } from "@app/components/ColumnFilter";
import SettingsSectionTitle from "@app/components/SettingsSectionTitle"; import SettingsSectionTitle from "@app/components/SettingsSectionTitle";
import { useSubscriptionStatusContext } from "@app/hooks/useSubscriptionStatusContext";
import { useLicenseStatusContext } from "@app/hooks/useLicenseStatusContext";
import { build } from "@server/build"; import { build } from "@server/build";
import { Alert, AlertDescription } from "@app/components/ui/alert";
import { getSevenDaysAgo } from "@app/lib/getSevenDaysAgo"; import { getSevenDaysAgo } from "@app/lib/getSevenDaysAgo";
import axios from "axios"; import axios from "axios";
import { useStoredPageSize } from "@app/hooks/useStoredPageSize"; import { useStoredPageSize } from "@app/hooks/useStoredPageSize";
import { PaidFeaturesAlert } from "@app/components/PaidFeaturesAlert"; import { PaidFeaturesAlert } from "@app/components/PaidFeaturesAlert";
import { usePaidStatus } from "@app/hooks/usePaidStatus";
import { tierMatrix } from "@server/lib/billing/tierMatrix";
export default function GeneralPage() { export default function GeneralPage() {
const router = useRouter(); const router = useRouter();
@@ -28,8 +27,8 @@ export default function GeneralPage() {
const api = createApiClient(useEnvContext()); const api = createApiClient(useEnvContext());
const t = useTranslations(); const t = useTranslations();
const { orgId } = useParams(); const { orgId } = useParams();
const subscription = useSubscriptionStatusContext();
const { isUnlocked } = useLicenseStatusContext(); const { isPaidUser } = usePaidStatus();
const [rows, setRows] = useState<any[]>([]); const [rows, setRows] = useState<any[]>([]);
const [isRefreshing, setIsRefreshing] = useState(false); const [isRefreshing, setIsRefreshing] = useState(false);
@@ -208,11 +207,7 @@ export default function GeneralPage() {
} }
) => { ) => {
console.log("Date range changed:", { startDate, endDate, page, size }); console.log("Date range changed:", { startDate, endDate, page, size });
if ( if (!isPaidUser(tierMatrix.accessLogs) || build === "oss") {
(build == "saas" && !subscription?.subscribed) ||
(build == "enterprise" && !isUnlocked()) ||
build === "oss"
) {
console.log( console.log(
"Access denied: subscription inactive or license locked" "Access denied: subscription inactive or license locked"
); );
@@ -642,11 +637,7 @@ export default function GeneralPage() {
// Row expansion props // Row expansion props
expandable={true} expandable={true}
renderExpandedRow={renderExpandedRow} renderExpandedRow={renderExpandedRow}
disabled={ disabled={!isPaidUser(tierMatrix.accessLogs) || build === "oss"}
(build == "saas" && !subscription?.subscribed) ||
(build == "enterprise" && !isUnlocked()) ||
build === "oss"
}
/> />
</> </>
); );

View File

@@ -4,15 +4,14 @@ import { DateTimeValue } from "@app/components/DateTimePicker";
import { LogDataTable } from "@app/components/LogDataTable"; import { LogDataTable } from "@app/components/LogDataTable";
import { PaidFeaturesAlert } from "@app/components/PaidFeaturesAlert"; import { PaidFeaturesAlert } from "@app/components/PaidFeaturesAlert";
import SettingsSectionTitle from "@app/components/SettingsSectionTitle"; import SettingsSectionTitle from "@app/components/SettingsSectionTitle";
import { Alert, AlertDescription } from "@app/components/ui/alert";
import { useEnvContext } from "@app/hooks/useEnvContext"; import { useEnvContext } from "@app/hooks/useEnvContext";
import { useLicenseStatusContext } from "@app/hooks/useLicenseStatusContext"; import { usePaidStatus } from "@app/hooks/usePaidStatus";
import { useStoredPageSize } from "@app/hooks/useStoredPageSize"; import { useStoredPageSize } from "@app/hooks/useStoredPageSize";
import { useSubscriptionStatusContext } from "@app/hooks/useSubscriptionStatusContext";
import { toast } from "@app/hooks/useToast"; import { toast } from "@app/hooks/useToast";
import { createApiClient } from "@app/lib/api"; import { createApiClient } from "@app/lib/api";
import { getSevenDaysAgo } from "@app/lib/getSevenDaysAgo"; import { getSevenDaysAgo } from "@app/lib/getSevenDaysAgo";
import { build } from "@server/build"; import { build } from "@server/build";
import { tierMatrix } from "@server/lib/billing/tierMatrix";
import { ColumnDef } from "@tanstack/react-table"; import { ColumnDef } from "@tanstack/react-table";
import axios from "axios"; import axios from "axios";
import { Key, User } from "lucide-react"; import { Key, User } from "lucide-react";
@@ -26,8 +25,8 @@ export default function GeneralPage() {
const t = useTranslations(); const t = useTranslations();
const { orgId } = useParams(); const { orgId } = useParams();
const searchParams = useSearchParams(); const searchParams = useSearchParams();
const subscription = useSubscriptionStatusContext();
const { isUnlocked } = useLicenseStatusContext(); const { isPaidUser } = usePaidStatus();
const [rows, setRows] = useState<any[]>([]); const [rows, setRows] = useState<any[]>([]);
const [isRefreshing, setIsRefreshing] = useState(false); const [isRefreshing, setIsRefreshing] = useState(false);
@@ -195,10 +194,7 @@ export default function GeneralPage() {
} }
) => { ) => {
console.log("Date range changed:", { startDate, endDate, page, size }); console.log("Date range changed:", { startDate, endDate, page, size });
if ( if (!isPaidUser(tierMatrix.actionLogs)) {
(build == "saas" && !subscription?.subscribed) ||
(build == "enterprise" && !isUnlocked())
) {
console.log( console.log(
"Access denied: subscription inactive or license locked" "Access denied: subscription inactive or license locked"
); );
@@ -496,11 +492,7 @@ export default function GeneralPage() {
// Row expansion props // Row expansion props
expandable={true} expandable={true}
renderExpandedRow={renderExpandedRow} renderExpandedRow={renderExpandedRow}
disabled={ disabled={!isPaidUser(tierMatrix.actionLogs) || build === "oss"}
(build == "saas" && !subscription?.subscribed) ||
(build == "enterprise" && !isUnlocked()) ||
build === "oss"
}
/> />
</> </>
); );

View File

@@ -1,54 +1,3 @@
"use client";
import ConfirmDeleteDialog from "@app/components/ConfirmDeleteDialog";
import AuthPageSettings, {
AuthPageSettingsRef
} from "@app/components/private/AuthPageSettings";
import { Button } from "@app/components/ui/button";
import { useOrgContext } from "@app/hooks/useOrgContext";
import { userOrgUserContext } from "@app/hooks/useOrgUserContext";
import { toast } from "@app/hooks/useToast";
import { useState, useRef } from "react";
import {
Form,
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
FormMessage
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { z } from "zod";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { createApiClient } from "@app/lib/api";
import { useEnvContext } from "@app/hooks/useEnvContext";
import { formatAxiosError } from "@app/lib/api";
import { AxiosResponse } from "axios";
import { DeleteOrgResponse, ListUserOrgsResponse } from "@server/routers/org";
import { useRouter } from "next/navigation";
import {
SettingsContainer,
SettingsSection,
SettingsSectionHeader,
SettingsSectionTitle,
SettingsSectionDescription,
SettingsSectionBody,
SettingsSectionForm,
SettingsSectionFooter
} from "@app/components/Settings";
import { useUserContext } from "@app/hooks/useUserContext";
import { useTranslations } from "next-intl";
import { build } from "@server/build";
export default function GeneralPage() { export default function GeneralPage() {
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false); return null;
const router = useRouter();
const api = createApiClient(useEnvContext());
const t = useTranslations();
const { env } = useEnvContext();
return <p>dfas</p>;
} }

View File

@@ -50,9 +50,6 @@ import { useActionState, useMemo, useState } from "react";
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod"; import { zodResolver } from "@hookform/resolvers/zod";
import z from "zod"; import z from "zod";
import { build } from "@server/build";
import { useLicenseStatusContext } from "@app/hooks/useLicenseStatusContext";
import { useSubscriptionStatusContext } from "@app/hooks/useSubscriptionStatusContext";
import { Alert, AlertDescription } from "@app/components/ui/alert"; import { Alert, AlertDescription } from "@app/components/ui/alert";
import { RadioGroup, RadioGroupItem } from "@app/components/ui/radio-group"; import { RadioGroup, RadioGroupItem } from "@app/components/ui/radio-group";
import { import {
@@ -64,6 +61,7 @@ import { PaidFeaturesAlert } from "@app/components/PaidFeaturesAlert";
import { GetResourceResponse } from "@server/routers/resource/getResource"; import { GetResourceResponse } from "@server/routers/resource/getResource";
import type { ResourceContextType } from "@app/contexts/resourceContext"; import type { ResourceContextType } from "@app/contexts/resourceContext";
import { usePaidStatus } from "@app/hooks/usePaidStatus"; import { usePaidStatus } from "@app/hooks/usePaidStatus";
import { tierMatrix } from "@server/lib/billing/tierMatrix";
type MaintenanceSectionFormProps = { type MaintenanceSectionFormProps = {
resource: GetResourceResponse; resource: GetResourceResponse;
@@ -77,8 +75,6 @@ function MaintenanceSectionForm({
const { env } = useEnvContext(); const { env } = useEnvContext();
const t = useTranslations(); const t = useTranslations();
const api = createApiClient({ env }); const api = createApiClient({ env });
const { isUnlocked } = useLicenseStatusContext();
const subscription = useSubscriptionStatusContext();
const { isPaidUser } = usePaidStatus(); const { isPaidUser } = usePaidStatus();
const MaintenanceFormSchema = z.object({ const MaintenanceFormSchema = z.object({
@@ -159,15 +155,6 @@ function MaintenanceSectionForm({
} }
} }
const isSecurityFeatureDisabled = () => {
const isEnterpriseNotLicensed = build === "enterprise" && !isUnlocked();
const isSaasNotSubscribed =
build === "saas" && !subscription?.isSubscribed();
return (
isEnterpriseNotLicensed || isSaasNotSubscribed || build === "oss"
);
};
if (!resource.http) { if (!resource.http) {
return null; return null;
} }
@@ -197,7 +184,7 @@ function MaintenanceSectionForm({
name="maintenanceModeEnabled" name="maintenanceModeEnabled"
render={({ field }) => { render={({ field }) => {
const isDisabled = const isDisabled =
isSecurityFeatureDisabled() || isPaidUser(tierMatrix.maintencePage) ||
resource.http === false; resource.http === false;
return ( return (
@@ -264,7 +251,7 @@ function MaintenanceSectionForm({
defaultValue={ defaultValue={
field.value field.value
} }
disabled={isSecurityFeatureDisabled()} disabled={isPaidUser(tierMatrix.maintencePage)}
className="flex flex-col space-y-1" className="flex flex-col space-y-1"
> >
<FormItem className="flex items-start space-x-3 space-y-0"> <FormItem className="flex items-start space-x-3 space-y-0">
@@ -337,7 +324,7 @@ function MaintenanceSectionForm({
<FormControl> <FormControl>
<Input <Input
{...field} {...field}
disabled={isSecurityFeatureDisabled()} disabled={isPaidUser(tierMatrix.maintencePage)}
placeholder="We'll be back soon!" placeholder="We'll be back soon!"
/> />
</FormControl> </FormControl>
@@ -363,7 +350,7 @@ function MaintenanceSectionForm({
<Textarea <Textarea
{...field} {...field}
rows={4} rows={4}
disabled={isSecurityFeatureDisabled()} disabled={isPaidUser(tierMatrix.maintencePage)}
placeholder={t( placeholder={t(
"maintenancePageMessagePlaceholder" "maintenancePageMessagePlaceholder"
)} )}
@@ -392,7 +379,7 @@ function MaintenanceSectionForm({
<FormControl> <FormControl>
<Input <Input
{...field} {...field}
disabled={isSecurityFeatureDisabled()} disabled={isPaidUser(tierMatrix.maintencePage)}
placeholder={t( placeholder={t(
"maintenanceTime" "maintenanceTime"
)} )}

View File

@@ -20,9 +20,6 @@ import { PickSiteDefaultsResponse } from "@server/routers/site";
import { useSiteContext } from "@app/hooks/useSiteContext"; import { useSiteContext } from "@app/hooks/useSiteContext";
import { generateKeypair } from "../wireguardConfig"; import { generateKeypair } from "../wireguardConfig";
import ConfirmDeleteDialog from "@app/components/ConfirmDeleteDialog"; import ConfirmDeleteDialog from "@app/components/ConfirmDeleteDialog";
import { useLicenseStatusContext } from "@app/hooks/useLicenseStatusContext";
import { useSubscriptionStatusContext } from "@app/hooks/useSubscriptionStatusContext";
import { build } from "@server/build";
import { import {
InfoSection, InfoSection,
InfoSectionContent, InfoSectionContent,
@@ -40,6 +37,8 @@ import {
import { QRCodeCanvas } from "qrcode.react"; import { QRCodeCanvas } from "qrcode.react";
import { PaidFeaturesAlert } from "@app/components/PaidFeaturesAlert"; import { PaidFeaturesAlert } from "@app/components/PaidFeaturesAlert";
import { NewtSiteInstallCommands } from "@app/components/newt-install-commands"; import { NewtSiteInstallCommands } from "@app/components/newt-install-commands";
import { usePaidStatus } from "@app/hooks/usePaidStatus";
import { tierMatrix } from "@server/lib/billing/tierMatrix";
export default function CredentialsPage() { export default function CredentialsPage() {
const { env } = useEnvContext(); const { env } = useEnvContext();
@@ -65,17 +64,7 @@ export default function CredentialsPage() {
const [loadingDefaults, setLoadingDefaults] = useState(false); const [loadingDefaults, setLoadingDefaults] = useState(false);
const [shouldDisconnect, setShouldDisconnect] = useState(true); const [shouldDisconnect, setShouldDisconnect] = useState(true);
const { licenseStatus, isUnlocked } = useLicenseStatusContext(); const { isPaidUser } = usePaidStatus();
const subscription = useSubscriptionStatusContext();
const isSecurityFeatureDisabled = () => {
const isEnterpriseNotLicensed = build === "enterprise" && !isUnlocked();
const isSaasNotSubscribed =
build === "saas" && !subscription?.isSubscribed();
return (
isEnterpriseNotLicensed || isSaasNotSubscribed || build === "oss"
);
};
// Fetch site defaults for wireguard sites to show in obfuscated config // Fetch site defaults for wireguard sites to show in obfuscated config
useEffect(() => { useEffect(() => {
@@ -279,7 +268,7 @@ export default function CredentialsPage() {
setShouldDisconnect(false); setShouldDisconnect(false);
setModalOpen(true); setModalOpen(true);
}} }}
disabled={isSecurityFeatureDisabled()} disabled={isPaidUser(tierMatrix.rotateCredentials)}
> >
{t("regenerateCredentialsButton")} {t("regenerateCredentialsButton")}
</Button> </Button>
@@ -288,7 +277,7 @@ export default function CredentialsPage() {
setShouldDisconnect(true); setShouldDisconnect(true);
setModalOpen(true); setModalOpen(true);
}} }}
disabled={isSecurityFeatureDisabled()} disabled={isPaidUser(tierMatrix.rotateCredentials)}
> >
{t("siteRegenerateAndDisconnect")} {t("siteRegenerateAndDisconnect")}
</Button> </Button>
@@ -389,7 +378,7 @@ export default function CredentialsPage() {
<SettingsSectionFooter> <SettingsSectionFooter>
<Button <Button
onClick={() => setModalOpen(true)} onClick={() => setModalOpen(true)}
disabled={isSecurityFeatureDisabled()} disabled={isPaidUser(tierMatrix.rotateCredentials)}
> >
{t("siteRegenerateAndDisconnect")} {t("siteRegenerateAndDisconnect")}
</Button> </Button>

View File

@@ -14,7 +14,7 @@ import {
LoadLoginPageResponse LoadLoginPageResponse
} from "@server/routers/loginPage/types"; } from "@server/routers/loginPage/types";
import { GetSessionTransferTokenRenponse } from "@server/routers/auth/types"; import { GetSessionTransferTokenRenponse } from "@server/routers/auth/types";
import ValidateSessionTransferToken from "@app/components/private/ValidateSessionTransferToken"; import ValidateSessionTransferToken from "@app/components/ValidateSessionTransferToken";
import { isOrgSubscribed } from "@app/lib/api/isOrgSubscribed"; import { isOrgSubscribed } from "@app/lib/api/isOrgSubscribed";
import { OrgSelectionForm } from "@app/components/OrgSelectionForm"; import { OrgSelectionForm } from "@app/components/OrgSelectionForm";
import OrgLoginPage from "@app/components/OrgLoginPage"; import OrgLoginPage from "@app/components/OrgLoginPage";

View File

@@ -5,7 +5,7 @@ import { ThemeProvider } from "@app/providers/ThemeProvider";
import EnvProvider from "@app/providers/EnvProvider"; import EnvProvider from "@app/providers/EnvProvider";
import { pullEnv } from "@app/lib/pullEnv"; import { pullEnv } from "@app/lib/pullEnv";
import ThemeDataProvider from "@app/providers/ThemeDataProvider"; import ThemeDataProvider from "@app/providers/ThemeDataProvider";
import SplashImage from "@app/components/private/SplashImage"; import SplashImage from "@app/components/SplashImage";
import SupportStatusProvider from "@app/providers/SupporterStatusProvider"; import SupportStatusProvider from "@app/providers/SupporterStatusProvider";
import { priv } from "@app/lib/api"; import { priv } from "@app/lib/api";
import { AxiosResponse } from "axios"; import { AxiosResponse } from "axios";

View File

@@ -28,7 +28,7 @@ import { ListDomainsResponse } from "@server/routers/domain";
import { DomainRow } from "@app/components/DomainsTable"; import { DomainRow } from "@app/components/DomainsTable";
import { toUnicode } from "punycode"; import { toUnicode } from "punycode";
import { Globe, Trash2 } from "lucide-react"; import { Globe, Trash2 } from "lucide-react";
import CertificateStatus from "@app/components/private/CertificateStatus"; import CertificateStatus from "@app/components/CertificateStatus";
import { import {
Credenza, Credenza,
CredenzaBody, CredenzaBody,
@@ -42,10 +42,10 @@ import {
import DomainPicker from "@app/components/DomainPicker"; import DomainPicker from "@app/components/DomainPicker";
import { finalizeSubdomainSanitize } from "@app/lib/subdomain-utils"; import { finalizeSubdomainSanitize } from "@app/lib/subdomain-utils";
import { InfoPopup } from "@app/components/ui/info-popup"; import { InfoPopup } from "@app/components/ui/info-popup";
import { Alert, AlertDescription } from "@app/components/ui/alert";
import { build } from "@server/build"; import { build } from "@server/build";
import { usePaidStatus } from "@app/hooks/usePaidStatus"; import { usePaidStatus } from "@app/hooks/usePaidStatus";
import { PaidFeaturesAlert } from "../PaidFeaturesAlert"; import { PaidFeaturesAlert } from "@app/components/PaidFeaturesAlert";
import { tierMatrix } from "@server/lib/billing/tierMatrix";
// Auth page form schema // Auth page form schema
const AuthPageFormSchema = z.object({ const AuthPageFormSchema = z.object({
@@ -75,7 +75,7 @@ function AuthPageSettings({
const t = useTranslations(); const t = useTranslations();
const { env } = useEnvContext(); const { env } = useEnvContext();
const { hasSaasSubscription } = usePaidStatus(); const { isPaidUser } = usePaidStatus();
// Auth page domain state // Auth page domain state
const [loginPage, setLoginPage] = useState(defaultLoginPage); const [loginPage, setLoginPage] = useState(defaultLoginPage);
@@ -177,7 +177,7 @@ function AuthPageSettings({
try { try {
// Handle auth page domain // Handle auth page domain
if (data.authPageDomainId) { if (data.authPageDomainId) {
if (build === "enterprise" || hasSaasSubscription) { if (isPaidUser(tierMatrix.loginPageDomain)) {
const sanitizedSubdomain = data.authPageSubdomain const sanitizedSubdomain = data.authPageSubdomain
? finalizeSubdomainSanitize(data.authPageSubdomain) ? finalizeSubdomainSanitize(data.authPageSubdomain)
: ""; : "";
@@ -361,7 +361,11 @@ function AuthPageSettings({
onClick={() => onClick={() =>
setEditDomainOpen(true) setEditDomainOpen(true)
} }
disabled={!hasSaasSubscription} disabled={
!isPaidUser(
tierMatrix.loginPageDomain
)
}
> >
{form.watch("authPageDomainId") {form.watch("authPageDomainId")
? t("changeDomain") ? t("changeDomain")
@@ -376,7 +380,9 @@ function AuthPageSettings({
clearAuthPageDomain clearAuthPageDomain
} }
disabled={ disabled={
!hasSaasSubscription !isPaidUser(
tierMatrix.loginPageDomain
)
} }
> >
<Trash2 size="14" /> <Trash2 size="14" />
@@ -395,7 +401,9 @@ function AuthPageSettings({
{env.flags.usePangolinDns && {env.flags.usePangolinDns &&
(build === "enterprise" || (build === "enterprise" ||
!hasSaasSubscription) && !isPaidUser(
tierMatrix.loginPageDomain
)) &&
loginPage?.domainId && loginPage?.domainId &&
loginPage?.fullDomain && loginPage?.fullDomain &&
!hasUnsavedChanges && ( !hasUnsavedChanges && (
@@ -424,7 +432,7 @@ function AuthPageSettings({
disabled={ disabled={
isSubmitting || isSubmitting ||
!hasUnsavedChanges || !hasUnsavedChanges ||
!hasSaasSubscription !isPaidUser(tierMatrix.loginPageDomain)
} }
> >
{t("saveAuthPageDomain")} {t("saveAuthPageDomain")}
@@ -477,7 +485,10 @@ function AuthPageSettings({
handleDomainSelection(selectedDomain); handleDomainSelection(selectedDomain);
} }
}} }}
disabled={!selectedDomain || !hasSaasSubscription} disabled={
!selectedDomain ||
!isPaidUser(tierMatrix.loginPageDomain)
}
> >
{t("selectDomain")} {t("selectDomain")}
</Button> </Button>

View File

@@ -2,11 +2,9 @@
import { useState } from "react"; import { useState } from "react";
import { Button } from "@app/components/ui/button"; import { Button } from "@app/components/ui/button";
import { Alert, AlertDescription } from "@app/components/ui/alert";
import { useTranslations } from "next-intl"; import { useTranslations } from "next-intl";
import { Separator } from "./ui/separator";
import LoginPasswordForm from "./LoginPasswordForm"; import LoginPasswordForm from "./LoginPasswordForm";
import IdpLoginButtons from "./private/IdpLoginButtons"; import IdpLoginButtons from "./IdpLoginButtons";
import { LookupUserResponse } from "@server/routers/auth/lookupUser"; import { LookupUserResponse } from "@server/routers/auth/lookupUser";
import UserProfileCard from "./UserProfileCard"; import UserProfileCard from "./UserProfileCard";

View File

@@ -2,7 +2,7 @@
import { ColumnDef } from "@tanstack/react-table"; import { ColumnDef } from "@tanstack/react-table";
import { ExtendedColumnDef } from "@app/components/ui/data-table"; import { ExtendedColumnDef } from "@app/components/ui/data-table";
import { IdpDataTable } from "@app/components/private/OrgIdpDataTable"; import { IdpDataTable } from "@app/components/OrgIdpDataTable";
import { Button } from "@app/components/ui/button"; import { Button } from "@app/components/ui/button";
import { ArrowRight, ArrowUpDown, MoreHorizontal } from "lucide-react"; import { ArrowRight, ArrowUpDown, MoreHorizontal } from "lucide-react";
import { useState } from "react"; import { useState } from "react";

View File

@@ -3,7 +3,7 @@ import {
LoadLoginPageBrandingResponse, LoadLoginPageBrandingResponse,
LoadLoginPageResponse LoadLoginPageResponse
} from "@server/routers/loginPage/types"; } from "@server/routers/loginPage/types";
import IdpLoginButtons from "@app/components/private/IdpLoginButtons"; import IdpLoginButtons from "@app/components/IdpLoginButtons";
import { import {
Card, Card,
CardContent, CardContent,

View File

@@ -11,7 +11,7 @@ import {
InfoSectionTitle InfoSectionTitle
} from "@app/components/InfoSection"; } from "@app/components/InfoSection";
import { useTranslations } from "next-intl"; import { useTranslations } from "next-intl";
import CertificateStatus from "@app/components/private/CertificateStatus"; import CertificateStatus from "@app/components/CertificateStatus";
import { toUnicode } from "punycode"; import { toUnicode } from "punycode";
import { useEnvContext } from "@app/hooks/useEnvContext"; import { useEnvContext } from "@app/hooks/useEnvContext";

View File

@@ -10,22 +10,23 @@ export function usePaidStatus() {
// Check if features are disabled due to licensing/subscription // Check if features are disabled due to licensing/subscription
const hasEnterpriseLicense = build === "enterprise" && isUnlocked(); const hasEnterpriseLicense = build === "enterprise" && isUnlocked();
const tierData = subscription?.getTier(); const tierData = subscription?.getTier();
const hasSaasSubscription = build === "saas" && tierData?.active;
function hasSaasSubscription(tiers: Tier[]): boolean {
return (
(build === "saas" &&
tierData?.active &&
tierData?.tier &&
tiers.includes(tierData.tier)) ||
false
);
}
function isPaidUser(tiers: Tier[]): boolean { function isPaidUser(tiers: Tier[]): boolean {
if (hasEnterpriseLicense) { if (hasEnterpriseLicense) {
return true; return true;
} }
if ( return hasSaasSubscription(tiers);
hasSaasSubscription &&
tierData?.tier &&
tiers.includes(tierData.tier)
) {
return true;
}
return false;
} }
return { return {