mirror of
https://github.com/formbricks/formbricks.git
synced 2025-12-30 18:30:32 -06:00
feat: role management is only allowed on paid inAppSurveys plan (#1824)
This commit is contained in:
committed by
GitHub
parent
1ce02edc1b
commit
ab22c0297e
@@ -93,6 +93,10 @@ export default function PricingTableComponent({
|
||||
};
|
||||
|
||||
const coreAndWebAppSurveyFeatures = [
|
||||
{
|
||||
title: "Remove Formbricks Branding",
|
||||
comingSoon: false,
|
||||
},
|
||||
{
|
||||
title: "Team Roles",
|
||||
comingSoon: false,
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import { getServerSession } from "next-auth";
|
||||
|
||||
import { getIsEnterpriseEdition } from "@formbricks/ee/lib/service";
|
||||
import {
|
||||
getRemoveInAppBrandingPermission,
|
||||
getRemoveLinkBrandingPermission,
|
||||
} from "@formbricks/ee/lib/service";
|
||||
import { authOptions } from "@formbricks/lib/authOptions";
|
||||
import { DEFAULT_BRAND_COLOR, IS_FORMBRICKS_CLOUD } from "@formbricks/lib/constants";
|
||||
import { getMembershipByUserIdTeamId } from "@formbricks/lib/membership/service";
|
||||
@@ -33,12 +36,8 @@ export default async function ProfileSettingsPage({ params }: { params: { enviro
|
||||
throw new Error("Team not found");
|
||||
}
|
||||
|
||||
const isEnterpriseEdition = await getIsEnterpriseEdition();
|
||||
|
||||
const canRemoveLinkBranding =
|
||||
team.billing.features.linkSurvey.status !== "inactive" || !IS_FORMBRICKS_CLOUD;
|
||||
const canRemoveInAppBranding =
|
||||
team.billing.features.inAppSurvey.status !== "inactive" || isEnterpriseEdition;
|
||||
const canRemoveInAppBranding = getRemoveInAppBrandingPermission(team);
|
||||
const canRemoveLinkBranding = getRemoveLinkBrandingPermission(team);
|
||||
|
||||
const currentUserMembership = await getMembershipByUserIdTeamId(session?.user.id, team.id);
|
||||
const { isDeveloper, isViewer } = getAccessFlags(currentUserMembership?.role);
|
||||
|
||||
@@ -19,10 +19,19 @@ interface MemberModalProps {
|
||||
open: boolean;
|
||||
setOpen: (v: boolean) => void;
|
||||
onSubmit: (data: { name: string; email: string; role: MembershipRole }) => void;
|
||||
isEnterpriseEdition: boolean;
|
||||
canDoRoleManagement: boolean;
|
||||
isFormbricksCloud: boolean;
|
||||
environmentId: string;
|
||||
}
|
||||
|
||||
export default function AddMemberModal({ open, setOpen, onSubmit, isEnterpriseEdition }: MemberModalProps) {
|
||||
export default function AddMemberModal({
|
||||
open,
|
||||
setOpen,
|
||||
onSubmit,
|
||||
canDoRoleManagement,
|
||||
isFormbricksCloud,
|
||||
environmentId,
|
||||
}: MemberModalProps) {
|
||||
const { register, getValues, handleSubmit, reset, control } = useForm<{
|
||||
name: string;
|
||||
email: string;
|
||||
@@ -47,11 +56,25 @@ export default function AddMemberModal({ open, setOpen, onSubmit, isEnterpriseEd
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{!isEnterpriseEdition && (
|
||||
<div className="mx-6 mt-2">
|
||||
<UpgradePlanNotice message="Upgrade to an Enterprise License to manage access roles for your team" />
|
||||
</div>
|
||||
)}
|
||||
{!canDoRoleManagement &&
|
||||
(isFormbricksCloud ? (
|
||||
<div className="mx-6 mt-2">
|
||||
<UpgradePlanNotice
|
||||
message="To manage access roles for your team"
|
||||
url={`/environments/${environmentId}/settings/billing`}
|
||||
textForUrl="Upgrade to the App Surveys plan."
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div className="mx-6 mt-2">
|
||||
<UpgradePlanNotice
|
||||
message="To manage access roles for your team,"
|
||||
url="mailto:hola@formbricks.com"
|
||||
textForUrl="get a self-hosted license (free to get started)."
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
|
||||
<form onSubmit={handleSubmit(submitEventClass)}>
|
||||
<div className="flex justify-between rounded-lg p-6">
|
||||
<div className="w-full space-y-4">
|
||||
@@ -66,7 +89,7 @@ export default function AddMemberModal({ open, setOpen, onSubmit, isEnterpriseEd
|
||||
<Label>Email Adress</Label>
|
||||
<Input type="email" placeholder="hans@wurst.com" {...register("email", { required: true })} />
|
||||
</div>
|
||||
{isEnterpriseEdition && <AddMemberRole control={control} />}
|
||||
{canDoRoleManagement && <AddMemberRole control={control} />}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex justify-end border-t border-slate-200 p-6">
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import MembersInfo from "@/app/(app)/environments/[environmentId]/settings/members/components/EditMemberships/MembersInfo";
|
||||
import React from "react";
|
||||
|
||||
import { getIsEnterpriseEdition } from "@formbricks/ee/lib/service";
|
||||
import { getRoleManagementPermission } from "@formbricks/ee/lib/service";
|
||||
import { getInvitesByTeamId } from "@formbricks/lib/invite/service";
|
||||
import { getMembersByTeamId } from "@formbricks/lib/membership/service";
|
||||
import { TMembership } from "@formbricks/types/memberships";
|
||||
@@ -24,7 +24,8 @@ export async function EditMemberships({
|
||||
|
||||
const currentUserRole = membership?.role;
|
||||
const isUserAdminOrOwner = membership?.role === "admin" || membership?.role === "owner";
|
||||
const isEnterpriseEdition = await getIsEnterpriseEdition();
|
||||
const canDoRoleManagement = getRoleManagementPermission(team);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="rounded-lg border border-slate-200">
|
||||
@@ -32,7 +33,7 @@ export async function EditMemberships({
|
||||
<div className="col-span-2"></div>
|
||||
<div className="col-span-5">Fullname</div>
|
||||
<div className="col-span-5">Email</div>
|
||||
{isEnterpriseEdition && <div className="col-span-3">Role</div>}
|
||||
{canDoRoleManagement && <div className="col-span-3">Role</div>}
|
||||
<div className="col-span-5"></div>
|
||||
</div>
|
||||
|
||||
@@ -44,7 +45,7 @@ export async function EditMemberships({
|
||||
members={members ?? []}
|
||||
isUserAdminOrOwner={isUserAdminOrOwner}
|
||||
currentUserRole={currentUserRole}
|
||||
isEnterpriseEdition={isEnterpriseEdition}
|
||||
canDoRoleManagement={canDoRoleManagement}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -16,7 +16,7 @@ type MembersInfoProps = {
|
||||
isUserAdminOrOwner: boolean;
|
||||
currentUserId: string;
|
||||
currentUserRole: TMembershipRole;
|
||||
isEnterpriseEdition: boolean;
|
||||
canDoRoleManagement: boolean;
|
||||
};
|
||||
|
||||
// Type guard to check if member is an invitee
|
||||
@@ -31,7 +31,7 @@ const MembersInfo = async ({
|
||||
members,
|
||||
currentUserId,
|
||||
currentUserRole,
|
||||
isEnterpriseEdition,
|
||||
canDoRoleManagement,
|
||||
}: MembersInfoProps) => {
|
||||
const allMembers = [...members, ...invites];
|
||||
|
||||
@@ -56,7 +56,7 @@ const MembersInfo = async ({
|
||||
</div>
|
||||
|
||||
<div className="ph-no-capture col-span-3 flex flex-col items-start justify-center break-all">
|
||||
{isEnterpriseEdition && allMembers?.length > 0 && (
|
||||
{canDoRoleManagement && allMembers?.length > 0 && (
|
||||
<EditMembershipRole
|
||||
isAdminOrOwner={isUserAdminOrOwner}
|
||||
memberRole={member.role}
|
||||
|
||||
@@ -21,7 +21,9 @@ type TeamActionsProps = {
|
||||
isLeaveTeamDisabled: boolean;
|
||||
team: TTeam;
|
||||
isInviteDisabled: boolean;
|
||||
isEnterpriseEdition: boolean;
|
||||
canDoRoleManagement: boolean;
|
||||
isFormbricksCloud: boolean;
|
||||
environmentId: string;
|
||||
};
|
||||
|
||||
export default function TeamActions({
|
||||
@@ -30,7 +32,9 @@ export default function TeamActions({
|
||||
team,
|
||||
isLeaveTeamDisabled,
|
||||
isInviteDisabled,
|
||||
isEnterpriseEdition,
|
||||
canDoRoleManagement,
|
||||
isFormbricksCloud,
|
||||
environmentId,
|
||||
}: TeamActionsProps) {
|
||||
const router = useRouter();
|
||||
const [isLeaveTeamModalOpen, setLeaveTeamModalOpen] = useState(false);
|
||||
@@ -94,7 +98,9 @@ export default function TeamActions({
|
||||
open={isAddMemberModalOpen}
|
||||
setOpen={setAddMemberModalOpen}
|
||||
onSubmit={handleAddMember}
|
||||
isEnterpriseEdition={isEnterpriseEdition}
|
||||
canDoRoleManagement={canDoRoleManagement}
|
||||
isFormbricksCloud={isFormbricksCloud}
|
||||
environmentId={environmentId}
|
||||
/>
|
||||
|
||||
<CustomDialog
|
||||
|
||||
@@ -2,9 +2,9 @@ import TeamActions from "@/app/(app)/environments/[environmentId]/settings/membe
|
||||
import { getServerSession } from "next-auth";
|
||||
import { Suspense } from "react";
|
||||
|
||||
import { getIsEnterpriseEdition } from "@formbricks/ee/lib/service";
|
||||
import { getRoleManagementPermission } from "@formbricks/ee/lib/service";
|
||||
import { authOptions } from "@formbricks/lib/authOptions";
|
||||
import { INVITE_DISABLED } from "@formbricks/lib/constants";
|
||||
import { INVITE_DISABLED, IS_FORMBRICKS_CLOUD } from "@formbricks/lib/constants";
|
||||
import { getMembershipByUserIdTeamId, getMembershipsByUserId } from "@formbricks/lib/membership/service";
|
||||
import { getAccessFlags } from "@formbricks/lib/membership/utils";
|
||||
import { getTeamByEnvironmentId } from "@formbricks/lib/team/service";
|
||||
@@ -44,9 +44,6 @@ const MembersLoading = () => (
|
||||
|
||||
export default async function MembersSettingsPage({ params }: { params: { environmentId: string } }) {
|
||||
const session = await getServerSession(authOptions);
|
||||
|
||||
const isEnterpriseEdition = await getIsEnterpriseEdition();
|
||||
|
||||
if (!session) {
|
||||
throw new Error("Unauthenticated");
|
||||
}
|
||||
@@ -55,6 +52,7 @@ export default async function MembersSettingsPage({ params }: { params: { enviro
|
||||
if (!team) {
|
||||
throw new Error("Team not found");
|
||||
}
|
||||
const canDoRoleManagement = getRoleManagementPermission(team);
|
||||
|
||||
const currentUserMembership = await getMembershipByUserIdTeamId(session?.user.id, team.id);
|
||||
const { isOwner, isAdmin } = getAccessFlags(currentUserMembership?.role);
|
||||
@@ -77,7 +75,9 @@ export default async function MembersSettingsPage({ params }: { params: { enviro
|
||||
role={currentUserRole}
|
||||
isLeaveTeamDisabled={isLeaveTeamDisabled}
|
||||
isInviteDisabled={INVITE_DISABLED}
|
||||
isEnterpriseEdition={isEnterpriseEdition}
|
||||
canDoRoleManagement={canDoRoleManagement}
|
||||
isFormbricksCloud={IS_FORMBRICKS_CLOUD}
|
||||
environmentId={params.environmentId}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
||||
@@ -1,17 +1,29 @@
|
||||
import "server-only";
|
||||
|
||||
import { unstable_cache } from "next/cache";
|
||||
import { ENTERPRISE_LICENSE_KEY, IS_FORMBRICKS_CLOUD } from "@formbricks/lib/constants";
|
||||
import { TTeam } from "@formbricks/types/teams";
|
||||
|
||||
import { ENTERPRISE_LICENSE_KEY } from "@formbricks/lib/constants";
|
||||
export const getIsEnterpriseEdition = (): boolean => {
|
||||
if (ENTERPRISE_LICENSE_KEY) {
|
||||
return ENTERPRISE_LICENSE_KEY.length > 0;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
export const getIsEnterpriseEdition = () =>
|
||||
unstable_cache(
|
||||
async () => {
|
||||
if (ENTERPRISE_LICENSE_KEY) {
|
||||
return ENTERPRISE_LICENSE_KEY?.length > 0;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
["getIsEnterpriseEdition"],
|
||||
{ revalidate: 60 * 60 * 24 }
|
||||
)();
|
||||
export const getRemoveInAppBrandingPermission = (team: TTeam): boolean => {
|
||||
if (IS_FORMBRICKS_CLOUD) return team.billing.features.inAppSurvey.status !== "inactive";
|
||||
else if (!IS_FORMBRICKS_CLOUD) return getIsEnterpriseEdition();
|
||||
else return false;
|
||||
};
|
||||
|
||||
export const getRemoveLinkBrandingPermission = (team: TTeam): boolean => {
|
||||
if (IS_FORMBRICKS_CLOUD) return team.billing.features.linkSurvey.status !== "inactive";
|
||||
else if (!IS_FORMBRICKS_CLOUD) return true;
|
||||
else return false;
|
||||
};
|
||||
|
||||
export const getRoleManagementPermission = (team: TTeam): boolean => {
|
||||
if (IS_FORMBRICKS_CLOUD) return team.billing.features.inAppSurvey.status !== "inactive";
|
||||
else if (!IS_FORMBRICKS_CLOUD) return getIsEnterpriseEdition();
|
||||
else return false;
|
||||
};
|
||||
|
||||
@@ -1,12 +1,26 @@
|
||||
import { LightBulbIcon } from "@heroicons/react/24/outline";
|
||||
import Link from "next/link";
|
||||
|
||||
import { Alert } from "../Alert";
|
||||
import { Alert, AlertDescription } from "../Alert";
|
||||
|
||||
export const UpgradePlanNotice = ({ message }: { message: string }) => {
|
||||
export const UpgradePlanNotice = ({
|
||||
message,
|
||||
url,
|
||||
textForUrl,
|
||||
}: {
|
||||
message: string;
|
||||
url: string;
|
||||
textForUrl: string;
|
||||
}) => {
|
||||
return (
|
||||
<Alert className="flex items-center">
|
||||
<LightBulbIcon className="h-5 w-5 text-gray-500" />
|
||||
<span className="text-sm text-gray-500">{message}</span>
|
||||
<AlertDescription>
|
||||
<span className="mr-2 text-sm text-gray-500">{message}</span>
|
||||
<span className="underline">
|
||||
<Link href={url}>{textForUrl}</Link>
|
||||
</span>
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user