Skip to content

Commit

Permalink
feat: role management is only allowed on paid inAppSurveys plan (#1824)
Browse files Browse the repository at this point in the history
  • Loading branch information
ShubhamPalriwala committed Dec 22, 2023
1 parent 1ce02ed commit ab22c02
Show file tree
Hide file tree
Showing 9 changed files with 106 additions and 47 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,10 @@ export default function PricingTableComponent({
};

const coreAndWebAppSurveyFeatures = [
{
title: "Remove Formbricks Branding",
comingSoon: false,
},
{
title: "Team Roles",
comingSoon: false,
Expand Down
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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">
Expand All @@ -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">
Expand Down
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -24,15 +24,16 @@ 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">
<div className="grid-cols-20 grid h-12 content-center rounded-t-lg bg-slate-100 text-left text-sm font-semibold text-slate-900">
<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>

Expand All @@ -44,7 +45,7 @@ export async function EditMemberships({
members={members ?? []}
isUserAdminOrOwner={isUserAdminOrOwner}
currentUserRole={currentUserRole}
isEnterpriseEdition={isEnterpriseEdition}
canDoRoleManagement={canDoRoleManagement}
/>
)}
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -31,7 +31,7 @@ const MembersInfo = async ({
members,
currentUserId,
currentUserRole,
isEnterpriseEdition,
canDoRoleManagement,
}: MembersInfoProps) => {
const allMembers = [...members, ...invites];

Expand All @@ -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}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@ type TeamActionsProps = {
isLeaveTeamDisabled: boolean;
team: TTeam;
isInviteDisabled: boolean;
isEnterpriseEdition: boolean;
canDoRoleManagement: boolean;
isFormbricksCloud: boolean;
environmentId: string;
};

export default function TeamActions({
Expand All @@ -30,7 +32,9 @@ export default function TeamActions({
team,
isLeaveTeamDisabled,
isInviteDisabled,
isEnterpriseEdition,
canDoRoleManagement,
isFormbricksCloud,
environmentId,
}: TeamActionsProps) {
const router = useRouter();
const [isLeaveTeamModalOpen, setLeaveTeamModalOpen] = useState(false);
Expand Down Expand Up @@ -94,7 +98,9 @@ export default function TeamActions({
open={isAddMemberModalOpen}
setOpen={setAddMemberModalOpen}
onSubmit={handleAddMember}
isEnterpriseEdition={isEnterpriseEdition}
canDoRoleManagement={canDoRoleManagement}
isFormbricksCloud={isFormbricksCloud}
environmentId={environmentId}
/>

<CustomDialog
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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");
}
Expand All @@ -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);
Expand All @@ -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}
/>
)}

Expand Down
38 changes: 25 additions & 13 deletions packages/ee/lib/service.ts
Original file line number Diff line number Diff line change
@@ -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;
};
20 changes: 17 additions & 3 deletions packages/ui/UpgradePlanNotice/index.tsx
Original file line number Diff line number Diff line change
@@ -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>
);
};

0 comments on commit ab22c02

Please sign in to comment.