diff --git a/apps/web/app/(app)/environments/[environmentId]/settings/(organization)/enterprise/components/EnterpriseLicenseStatus.tsx b/apps/web/app/(app)/environments/[environmentId]/settings/(organization)/enterprise/components/EnterpriseLicenseStatus.tsx
new file mode 100644
index 0000000000..106c3a3509
--- /dev/null
+++ b/apps/web/app/(app)/environments/[environmentId]/settings/(organization)/enterprise/components/EnterpriseLicenseStatus.tsx
@@ -0,0 +1,138 @@
+"use client";
+
+import { TFunction } from "i18next";
+import { RotateCcwIcon } from "lucide-react";
+import { useRouter } from "next/navigation";
+import { useState } from "react";
+import toast from "react-hot-toast";
+import { useTranslation } from "react-i18next";
+import { recheckLicenseAction } from "@/modules/ee/license-check/actions";
+import { Alert, AlertDescription } from "@/modules/ui/components/alert";
+import { Badge } from "@/modules/ui/components/badge";
+import { Button } from "@/modules/ui/components/button";
+import { SettingsCard } from "../../../components/SettingsCard";
+
+type LicenseStatus = "active" | "expired" | "unreachable" | "invalid_license";
+
+interface EnterpriseLicenseStatusProps {
+ status: LicenseStatus;
+ gracePeriodEnd?: Date;
+ environmentId: string;
+}
+
+const getBadgeConfig = (
+ status: LicenseStatus,
+ t: TFunction
+): { type: "success" | "error" | "warning" | "gray"; label: string } => {
+ switch (status) {
+ case "active":
+ return { type: "success", label: t("environments.settings.enterprise.license_status_active") };
+ case "expired":
+ return { type: "error", label: t("environments.settings.enterprise.license_status_expired") };
+ case "unreachable":
+ return { type: "warning", label: t("environments.settings.enterprise.license_status_unreachable") };
+ case "invalid_license":
+ return { type: "error", label: t("environments.settings.enterprise.license_status_invalid") };
+ default:
+ return { type: "gray", label: t("environments.settings.enterprise.license_status") };
+ }
+};
+
+export const EnterpriseLicenseStatus = ({ status, gracePeriodEnd, environmentId }: EnterpriseLicenseStatusProps) => {
+ const { t } = useTranslation();
+ const router = useRouter();
+ const [isRechecking, setIsRechecking] = useState(false);
+
+ const handleRecheck = async () => {
+ setIsRechecking(true);
+ try {
+ const result = await recheckLicenseAction({ environmentId });
+ if (result?.serverError) {
+ toast.error(result.serverError || t("environments.settings.enterprise.recheck_license_failed"));
+ return;
+ }
+
+ if (result?.data) {
+ if (result.data.status === "unreachable") {
+ toast.error(t("environments.settings.enterprise.recheck_license_unreachable"));
+ } else if (result.data.status === "invalid_license") {
+ toast.error(t("environments.settings.enterprise.recheck_license_invalid"));
+ } else {
+ toast.success(t("environments.settings.enterprise.recheck_license_success"));
+ }
+ router.refresh();
+ } else {
+ toast.error(t("environments.settings.enterprise.recheck_license_failed"));
+ }
+ } catch (error) {
+ toast.error(
+ error instanceof Error ? error.message : t("environments.settings.enterprise.recheck_license_failed")
+ );
+ } finally {
+ setIsRechecking(false);
+ }
+ };
+
+ const badgeConfig = getBadgeConfig(status, t);
+
+ return (
+
+
+
+
+
+
+
+
+ {status === "unreachable" && gracePeriodEnd && (
+
+
+ {t("environments.settings.enterprise.license_unreachable_grace_period", {
+ gracePeriodEnd: new Date(gracePeriodEnd).toLocaleDateString(undefined, {
+ year: "numeric",
+ month: "short",
+ day: "numeric",
+ }),
+ })}
+
+
+ )}
+ {status === "invalid_license" && (
+
+
+ {t("environments.settings.enterprise.license_invalid_description")}
+
+
+ )}
+
+ {t("environments.settings.enterprise.questions_please_reach_out_to")}{" "}
+
+ hola@formbricks.com
+
+
+
+
+ );
+};
diff --git a/apps/web/app/(app)/environments/[environmentId]/settings/(organization)/enterprise/page.tsx b/apps/web/app/(app)/environments/[environmentId]/settings/(organization)/enterprise/page.tsx
index a515bcd75f..9252c58e3b 100644
--- a/apps/web/app/(app)/environments/[environmentId]/settings/(organization)/enterprise/page.tsx
+++ b/apps/web/app/(app)/environments/[environmentId]/settings/(organization)/enterprise/page.tsx
@@ -2,9 +2,10 @@ import { CheckIcon } from "lucide-react";
import Link from "next/link";
import { notFound } from "next/navigation";
import { OrganizationSettingsNavbar } from "@/app/(app)/environments/[environmentId]/settings/(organization)/components/OrganizationSettingsNavbar";
+import { EnterpriseLicenseStatus } from "@/app/(app)/environments/[environmentId]/settings/(organization)/enterprise/components/EnterpriseLicenseStatus";
import { IS_FORMBRICKS_CLOUD } from "@/lib/constants";
import { getTranslate } from "@/lingodotdev/server";
-import { getEnterpriseLicense } from "@/modules/ee/license-check/lib/license";
+import { GRACE_PERIOD_MS, getEnterpriseLicense } from "@/modules/ee/license-check/lib/license";
import { getEnvironmentAuth } from "@/modules/environments/lib/utils";
import { Button } from "@/modules/ui/components/button";
import { PageContentWrapper } from "@/modules/ui/components/page-content-wrapper";
@@ -25,7 +26,8 @@ const Page = async (props) => {
return notFound();
}
- const { active: isEnterpriseEdition } = await getEnterpriseLicense();
+ const licenseState = await getEnterpriseLicense();
+ const hasLicense = licenseState.status !== "no-license";
const paidFeatures = [
{
@@ -90,35 +92,22 @@ const Page = async (props) => {
activeId="enterprise"
/>
- {isEnterpriseEdition ? (
-
-
-
-
-
-
-
-
- {t(
- "environments.settings.enterprise.your_enterprise_license_is_active_all_features_unlocked"
- )}
-
-
-
- {t("environments.settings.enterprise.questions_please_reach_out_to")}{" "}
-
- hola@formbricks.com
-
-
-
-
-
+ {hasLicense ? (
+
) : (