feat: role management is only allowed on paid inAppSurveys plan (#1824)

This commit is contained in:
Shubham Palriwala
2023-12-22 18:00:03 +05:30
committed by GitHub
parent 1ce02edc1b
commit ab22c0297e
9 changed files with 106 additions and 47 deletions

View File

@@ -93,6 +93,10 @@ export default function PricingTableComponent({
};
const coreAndWebAppSurveyFeatures = [
{
title: "Remove Formbricks Branding",
comingSoon: false,
},
{
title: "Team Roles",
comingSoon: false,

View File

@@ -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);

View File

@@ -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">

View File

@@ -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>

View File

@@ -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}

View File

@@ -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

View File

@@ -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}
/>
)}

View File

@@ -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;
};

View File

@@ -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>
);
};