improvement: billing and plans (#1962)

* improvement: update modal UI

* chore: total member count chip

* chore: seats banner ux copy improvement

* improvement: workspace disabled page and seats banner ux copy
This commit is contained in:
Prateek Shourya
2024-12-17 21:41:40 +05:30
committed by GitHub
parent d761663af6
commit b051c38ace
7 changed files with 52 additions and 23 deletions

View File

@@ -10,6 +10,7 @@ import { IWorkspaceBulkInviteFormData } from "@plane/types";
import { Button, CustomMenu, TOAST_TYPE, setToast } from "@plane/ui";
// components
import { NotAuthorizedView } from "@/components/auth-screens";
import { CountChip } from "@/components/common";
import { PageHead } from "@/components/core";
import { SendWorkspaceInvitationModal, WorkspaceMembersList } from "@/components/workspace";
// constants
@@ -43,7 +44,7 @@ const WorkspaceMembersSettingsPage = observer(() => {
const { workspaceUserInfo, allowPermissions } = useUserPermissions();
const { captureEvent } = useEventTracker();
const {
workspace: { inviteMembersToWorkspace },
workspace: { workspaceMemberIds, inviteMembersToWorkspace },
} = useMember();
const { currentWorkspace } = useWorkspace();
@@ -145,7 +146,12 @@ const WorkspaceMembersSettingsPage = observer(() => {
})}
>
<div className="flex justify-between gap-4 pb-3.5 items-start">
<h4 className="text-xl font-medium">Members</h4>
<h4 className="flex items-center gap-2.5 text-xl font-medium">
Members
{workspaceMemberIds && workspaceMemberIds.length > 0 && (
<CountChip count={workspaceMemberIds.length} className="h-5 m-auto" />
)}
</h4>
<div className="ml-auto flex items-center gap-1.5 rounded-md border border-custom-border-200 bg-custom-background-100 px-2.5 py-1.5">
<Search className="h-3.5 w-3.5 text-custom-text-400" />
<input

View File

@@ -39,9 +39,9 @@ export const LicenseSeatsBanner: FC = observer(() => {
: EPlanMessageKeys.NEAR_LIMIT;
const planMessages = {
free_plan: `Your workspace has been grandfathered to ${freeSeats} over the 12-member limit on the Free plan. To add more members, upgrade to Pro.`,
near_limit: `Youve already ${totalSeats} out of ${freeSeats} members allowed on the Free plan. You can only add ${freeSeats - totalSeats} more members. To remove the limit, upgrade to Pro.`,
limit_reached: `Youve reached the members limit of ${freeSeats} on this plan. To add more members, upgrade to Pro`,
free_plan: `Your workspace has been grandfathered to ${freeSeats} over the 12-user limit on the Free plan. To add more users, upgrade to a paid plan.`,
near_limit: `You have ${totalSeats} out of ${freeSeats} users allowed on the Free plan. To remove the limit, upgrade to a paid plan.`,
limit_reached: `You have ${totalSeats} out of ${freeSeats} users allowed on the Free plan. To remove the limit, upgrade to a paid plan.`,
};
const currentVariant: "primary" | "danger" =
currentPlanKey && [EPlanMessageKeys.NEAR_LIMIT, EPlanMessageKeys.LIMIT_REACHED].includes(currentPlanKey)

View File

@@ -204,12 +204,15 @@ export const PlanUpgradeCard: FC<PlanUpgradeCardProps> = (props) => {
? "Redirecting to Stripe..."
: (upgradeCTA ?? `Upgrade to ${planeName}`)}
</button>
{isTrialAllowed &&
renderTrialButton &&
renderTrialButton({
productId: product?.id,
priceId: price.id,
})}
{isTrialAllowed && (
<div className="mt-4 h-4">
{renderTrialButton &&
renderTrialButton({
productId: product?.id,
priceId: price.id,
})}
</div>
)}
</div>
)}
<div className="px-2 pt-6 pb-2">

View File

@@ -37,6 +37,7 @@ export const TalkToSalesCard: FC<TalkToSalesCardProps> = (props) => {
const { currentWorkspaceSubscribedPlanDetail: subscriptionDetail } = useWorkspaceSubscription();
// derived values
const isSelfHosted = !!subscriptionDetail?.is_self_managed;
const isTrialAllowed = !!subscriptionDetail?.is_trial_allowed;
const basePlan = getBasePlanName(planVariant, isSelfHosted);
// Plane details
const planeName = planVariant.charAt(0).toUpperCase() + planVariant.slice(1).toLowerCase();
@@ -73,6 +74,7 @@ export const TalkToSalesCard: FC<TalkToSalesCardProps> = (props) => {
>
Talk to sales
</a>
{isTrialAllowed && <div className="mt-4 h-4" />}
</div>
)}
<div className="px-2 pt-6 pb-2">

View File

@@ -50,7 +50,7 @@ export const ProTrialButton: React.FC<TTrialButtonProps> = (props: TTrialButtonP
return (
<button
disabled={trialLoader}
className="mt-4 text-center text-sm text-custom-text-300 hover:text-custom-text-100 font-medium transition-all flex justify-center items-center gap-2"
className="text-center text-sm text-custom-text-300 hover:text-custom-text-100 font-medium transition-all flex justify-center items-center gap-2"
onClick={() => handleTrial(productId, priceId)}
>
<span>Start free trial</span>

View File

@@ -9,8 +9,6 @@ import { BUSINESS_PLAN_FEATURES, PRO_PLAN_FEATURES } from "@plane/constants";
import { IPaymentProduct, TProductSubscriptionType } from "@plane/types";
// ui
import { EModalWidth, ModalCore, TOAST_TYPE, setToast } from "@plane/ui";
// ce components
import { OnePlanUpgrade } from "@/ce/components/workspace/upgrade";
// plane web components
import { cn } from "@/helpers/common.helper";
import { ProTrialButton } from "@/plane-web/components/license/modal/trial-button";

View File

@@ -4,13 +4,13 @@ import { useState } from "react";
import { AppSidebar } from "app/[workspaceSlug]/(projects)/sidebar";
import { observer } from "mobx-react";
import Image from "next/image";
import Link from "next/link";
import { useParams } from "next/navigation";
import { useTheme } from "next-themes";
import { CircleAlert } from "lucide-react";
// hooks
import { Button, getButtonStyling } from "@plane/ui";
import { AlertModalCore, Button } from "@plane/ui";
import { useUserPermissions } from "@/hooks/store";
import { useAppRouter } from "@/hooks/use-app-router";
// plane web components
import { PaidPlanUpgradeModal } from "@/plane-web/components/license";
// plane web constants
@@ -21,9 +21,11 @@ import PlaneBackgroundPattern from "@/public/auth/background-pattern.svg";
export const WorkspaceDisabledPage: React.FC = observer(() => {
// router
const router = useAppRouter();
const { workspaceSlug: routerWorkspaceSlug } = useParams();
// state
const [isPaidPlanModalOpen, togglePaidPlanModal] = useState(false);
const [isDowngradeModalOpen, toggleDowngradeModal] = useState(false);
// hooks
const { resolvedTheme } = useTheme();
const { allowPermissions } = useUserPermissions();
@@ -31,8 +33,26 @@ export const WorkspaceDisabledPage: React.FC = observer(() => {
const workspaceSlug = routerWorkspaceSlug?.toString();
const isWorkspaceAdmin = allowPermissions([EUserPermissions.ADMIN], EUserPermissionsLevel.WORKSPACE, workspaceSlug);
const handleRemoveMembers = () => {
toggleDowngradeModal(false);
router.push(`/${workspaceSlug}/settings/members`);
};
return (
<>
<AlertModalCore
variant="danger"
isOpen={isDowngradeModalOpen}
title="You've more than 12 users in this workspace."
primaryButtonText={{
loading: "Redirecting",
default: "Remove members",
}}
content={<>The Free tier only lets you have 12 users. Remove users to continue using the Free tier. </>}
handleClose={() => toggleDowngradeModal(false)}
handleSubmit={handleRemoveMembers}
isSubmitting={false}
/>
<PaidPlanUpgradeModal isOpen={isPaidPlanModalOpen} handleClose={() => togglePaidPlanModal(false)} />
<div className="relative flex h-full w-full overflow-hidden">
<AppSidebar />
@@ -51,21 +71,21 @@ export const WorkspaceDisabledPage: React.FC = observer(() => {
</div>
<div className="text-center">
<p className="text-xl font-medium py-2.5">
{isWorkspaceAdmin ? "Your payment has failed!" : "Somethings not okay"}
{isWorkspaceAdmin ? "We couldn't collect your last invoiced payment." : "A payment is overdue."}
</p>
<p className="text-custom-text-300">
{isWorkspaceAdmin
? "We tried to bill your account but the payment method is facing issues. Please fix the issue to resume service."
: "Your workspace is facing some issues. Please contact your admin."}
? "You have a payment due for your Pro workspace. Please clear it to continue using Pro features or downgrade to Free."
: "Your have a payment due for your Pro workspace. Get in touch with one of your admins to fix this."}
</p>
</div>
{isWorkspaceAdmin && (
<div className="flex items-center justify-center gap-4 pt-8">
<Link href={`/${workspaceSlug}/settings/members`} className={getButtonStyling("link-neutral", "md")}>
Remove members
</Link>
<Button size="md" onClick={() => togglePaidPlanModal(true)}>
Upgrade plan
Continue subscription
</Button>
<Button size="md" variant="link-neutral" onClick={() => toggleDowngradeModal(true)}>
Downgrade
</Button>
</div>
)}