Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion components/billing/add-seat-modal.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import Link from "next/link";
import { useRouter } from "next/router";

import { useEffect, useState } from "react";
Expand Down Expand Up @@ -180,10 +181,17 @@ export function AddSeatModal({
)}
</div>

<DialogFooter>
<DialogFooter className="flex flex-col gap-2 sm:flex-col sm:justify-center">
<Button onClick={handleSubmit} className="w-full" disabled={loading}>
{loading ? "Redirecting..." : "Proceed to checkout"}
</Button>
<Link
href="/settings/upgrade"
className="block w-full text-center text-xs text-muted-foreground underline underline-offset-4 hover:text-foreground"
onClick={() => setOpen(false)}
>
or upgrade to higher plan
</Link>
</DialogFooter>
</DialogContent>
</Dialog>
Expand Down
9 changes: 6 additions & 3 deletions components/billing/upgrade-plan-container.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ export default function UpgradePlanContainer() {
plan,
isFree,
isDataroomsPlus,
isDataroomsPremium,
isPaused,
isCancelled,
isCustomer,
Expand Down Expand Up @@ -291,9 +292,11 @@ export default function UpgradePlanContainer() {
<Card className="bg-transparent">
<CardHeader>
<CardTitle>
{isDataroomsPlus
? "Datarooms+"
: plan.charAt(0).toUpperCase() + plan.slice(1)}{" "}
{isDataroomsPremium
? "Premium"
: isDataroomsPlus
? "Datarooms+"
: plan.charAt(0).toUpperCase() + plan.slice(1)}{" "}
Plan
</CardTitle>
{!isCancelled && startsAt && endsAt && isBillingCycleCurrent() && (
Expand Down
87 changes: 57 additions & 30 deletions components/billing/upgrade-plan-modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -96,38 +96,49 @@ const FeatureItem = ({ feature }: { feature: Feature }) => {
);
};

// Segmented control component for Base/Plus selection
// Segmented control component for Base/Plus/Premium selection
const PlanSelector = ({
value,
onChange,
}: {
value: boolean;
onChange: (value: boolean) => void;
value: "base" | "plus" | "premium";
onChange: (value: "base" | "plus" | "premium") => void;
}) => {
return (
<div className="mt-1 flex w-1/2 rounded-lg border border-gray-200 p-1">
<div className="mt-1 flex w-full rounded-lg border border-gray-200 p-1">
<button
className={cn(
"flex-1 rounded-md px-3 py-1 text-sm transition-colors",
!value
value === "base"
? "bg-gray-300 text-foreground dark:bg-gray-600 dark:text-white"
: "text-gray-600 hover:text-gray-900 dark:text-muted-foreground dark:hover:text-white",
)}
onClick={() => onChange(false)}
onClick={() => onChange("base")}
>
Base
</button>
<button
className={cn(
"flex-1 rounded-md px-3 py-1 text-sm transition-colors",
value
? "bg-gray-900 text-white dark:bg-gray-100 dark:text-gray-900"
value === "plus"
? "bg-gray-800 text-white dark:bg-gray-200 dark:text-gray-900"
: "text-gray-600 hover:text-gray-900 dark:text-muted-foreground dark:hover:text-white",
)}
onClick={() => onChange(true)}
onClick={() => onChange("plus")}
>
Plus
</button>
<button
className={cn(
"flex-1 rounded-md px-3 py-1 text-sm transition-colors",
value === "premium"
? "bg-gray-900 text-white dark:bg-gray-100 dark:text-gray-900"
: "text-gray-600 hover:text-gray-900 dark:text-muted-foreground dark:hover:text-white",
)}
onClick={() => onChange("premium")}
>
Premium
</button>
</div>
);
};
Expand All @@ -154,7 +165,7 @@ export function UpgradePlanModal({
const teamId = teamInfo?.currentTeam?.id;
const { plan: teamPlan, isCustomer, isOldAccount, isTrial } = usePlan();
const analytics = useAnalytics();
const [showDataRoomsPlus, setShowDataRoomsPlus] = useState(false);
const [dataRoomsPlanSelection, setDataRoomsPlanSelection] = useState<"base" | "plus" | "premium">("base");

const plansToShow = useMemo(() => {
switch (clickedPlan) {
Expand All @@ -165,7 +176,9 @@ export function UpgradePlanModal({
case PlanEnum.DataRooms:
return [PlanEnum.DataRooms, PlanEnum.DataRoomsPlus];
case PlanEnum.DataRoomsPlus:
return [PlanEnum.DataRooms, PlanEnum.DataRoomsPlus];
return [PlanEnum.DataRoomsPlus, PlanEnum.DataRoomsPremium];
case PlanEnum.DataRoomsPremium:
return [PlanEnum.DataRoomsPlus, PlanEnum.DataRoomsPremium];
default:
return [PlanEnum.Pro, PlanEnum.Business];
}
Expand All @@ -179,7 +192,7 @@ export function UpgradePlanModal({
teamId,
});
} else {
setShowDataRoomsPlus(false);
setDataRoomsPlanSelection("base");
}
}, [open, trigger]);

Expand Down Expand Up @@ -222,20 +235,26 @@ export function UpgradePlanModal({

<div className="isolate grid grid-cols-1 gap-4 overflow-hidden rounded-xl p-4 md:grid-cols-2">
{plansToShow.map((planOption) => {
const planFeatures = getPlanFeatures(planOption, {
const isDataRoomsUpgrade = plansToShow.includes(PlanEnum.DataRooms);

// Determine which plan to show based on selection for Data Rooms
let effectivePlan = planOption;
let displayPlanName = planOption;

if (planOption === PlanEnum.DataRooms && isDataRoomsUpgrade) {
if (dataRoomsPlanSelection === "plus") {
effectivePlan = PlanEnum.DataRoomsPlus;
displayPlanName = PlanEnum.DataRoomsPlus;
} else if (dataRoomsPlanSelection === "premium") {
effectivePlan = PlanEnum.DataRoomsPremium;
displayPlanName = PlanEnum.DataRoomsPremium;
}
}

const planFeatures = getPlanFeatures(effectivePlan, {
period,
showDataRoomsPlus:
planOption === PlanEnum.DataRooms && showDataRoomsPlus,
});

// Get the effective plan name for display
const displayPlanName =
planOption === PlanEnum.DataRooms && showDataRoomsPlus
? PlanEnum.DataRoomsPlus
: planOption;

const isDataRoomsUpgrade = plansToShow.includes(PlanEnum.DataRooms);

return (
<div
key={displayPlanName}
Expand All @@ -251,15 +270,15 @@ export function UpgradePlanModal({
<div className="mb-4 border-b border-gray-200 pb-2">
<div className="flex items-center justify-between">
<h3 className="text-balance text-xl font-medium text-gray-900 dark:text-white">
Papermark {displayPlanName}
{displayPlanName}
</h3>
</div>
<span
className={cn(
"absolute right-2 top-2 rounded px-2 py-1 text-xs text-white",
planOption === PlanEnum.Business && "bg-[#fb7a00]",
displayPlanName === PlanEnum.DataRoomsPlus &&
"bg-gray-900 dark:bg-gray-100 dark:text-gray-900",
"bg-gray-800 dark:bg-gray-200 dark:text-gray-900",
)}
>
{planOption === PlanEnum.Business && "Most popular"}
Expand All @@ -285,8 +304,8 @@ export function UpgradePlanModal({
isDataRoomsUpgrade &&
!plansToShow.includes(PlanEnum.DataRoomsPlus) && (
<PlanSelector
value={showDataRoomsPlus}
onChange={setShowDataRoomsPlus}
value={dataRoomsPlanSelection}
onChange={setDataRoomsPlanSelection}
/>
)}

Expand Down Expand Up @@ -315,7 +334,9 @@ export function UpgradePlanModal({
className={`w-full py-2 text-sm ${
planOption === PlanEnum.Business
? "bg-[#fb7a00]/90 text-white hover:bg-[#fb7a00]"
: "bg-gray-800 text-white hover:bg-gray-900 hover:text-white dark:hover:bg-gray-700/80"
: displayPlanName === PlanEnum.DataRoomsPremium
? "bg-gray-900 text-white hover:bg-gray-800 dark:bg-gray-100 dark:text-gray-900 dark:hover:bg-gray-200"
: "bg-gray-800 text-white hover:bg-gray-900 hover:text-white dark:hover:bg-gray-700/80"
}`}
loading={selectedPlan === planOption}
disabled={selectedPlan !== null}
Expand Down Expand Up @@ -381,11 +402,17 @@ export function UpgradePlanModal({
})}
</div>
<div className="flex flex-col items-center text-center text-sm text-muted-foreground">
All plans include unlimited viewers and page by page document
All plans include unlimited visitors and page by page document
analytics.
<div className="flex items-center gap-2">
<Link
href="/settings/upgrade"
href={`/settings/upgrade${
clickedPlan === PlanEnum.Pro
? "?view=documents"
: clickedPlan === PlanEnum.Business
? "?view=business-datarooms"
: ""
}`}
className="underline underline-offset-4 hover:text-foreground"
>
See all plans
Expand Down
8 changes: 7 additions & 1 deletion components/sidebar/app-sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
isBusiness,
isDatarooms,
isDataroomsPlus,
isDataroomsPremium,
isFree,
isTrial,
} = usePlan();
Expand Down Expand Up @@ -215,7 +216,7 @@ export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
</p>
<p className="ml-2 flex items-center text-2xl font-bold tracking-tighter text-black group-data-[collapsible=icon]:hidden dark:text-white">
<Link href="/dashboard">Papermark</Link>
{userPlan && !isFree && !isDataroomsPlus ? (
{userPlan && !isFree && !isDataroomsPlus && !isDataroomsPremium ? (
<span className="ml-4 rounded-full bg-background px-2.5 py-1 text-xs tracking-normal text-foreground ring-1 ring-gray-800">
{userPlan.charAt(0).toUpperCase() + userPlan.slice(1)}
</span>
Expand All @@ -225,6 +226,11 @@ export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
Datarooms+
</span>
) : null}
{isDataroomsPremium ? (
<span className="ml-4 rounded-full bg-background px-2.5 py-1 text-xs tracking-normal text-foreground ring-1 ring-gray-800">
Premium
</span>
) : null}
{isTrial ? (
<span className="ml-2 rounded-sm bg-foreground px-2 py-0.5 text-xs tracking-normal text-background ring-1 ring-gray-800">
Trial
Expand Down
40 changes: 32 additions & 8 deletions components/sidebar/team-switcher.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import {
import { AddSeatModal } from "../billing/add-seat-modal";
import { UpgradePlanModal } from "../billing/upgrade-plan-modal";
import { AddTeamMembers } from "../teams/add-team-member-modal";
import { AddTeamModal } from "../teams/add-team-modal";
import { Avatar, AvatarFallback } from "../ui/avatar";

export function TeamSwitcher({
Expand All @@ -44,7 +45,7 @@ export function TeamSwitcher({
React.useState<boolean>(false);
const { isMobile } = useSidebar();
const { canAddUsers, showUpgradePlanModal } = useLimits();
const { isTrial } = usePlan();
const { isTrial, isDataroomsPremium } = usePlan();

const switchTeam = (team: Team) => {
localStorage.setItem("currentTeamId", team.id);
Expand Down Expand Up @@ -104,13 +105,36 @@ export function TeamSwitcher({
{/* <DropdownMenuShortcut>⌘{index + 1}</DropdownMenuShortcut> */}
</DropdownMenuItem>
))}
{/* <DropdownMenuSeparator />
<DropdownMenuItem className="gap-2 p-2">
<div className="flex size-6 items-center justify-center rounded-md border bg-background">
<Plus className="size-4" />
</div>
<div className="font-medium text-muted-foreground">Add team</div>
</DropdownMenuItem> */}
<DropdownMenuSeparator />
{isDataroomsPremium ? (
<AddTeamModal setCurrentTeam={setCurrentTeam}>
<DropdownMenuItem
className="gap-2 p-2 cursor-pointer"
onSelect={(e) => e.preventDefault()}
>
<div className="flex size-6 items-center justify-center rounded-md border bg-background">
<UserRoundPlusIcon className="size-4" />
</div>
<div className="font-medium text-muted-foreground">Add new team</div>
</DropdownMenuItem>
</AddTeamModal>
) : (
<UpgradePlanModal
clickedPlan={PlanEnum.DataRoomsPremium}
trigger="add_new_team"
highlightItem={["teams"]}
>
<DropdownMenuItem
className="gap-2 p-2 cursor-pointer"
onSelect={(e) => e.preventDefault()}
>
<div className="flex size-6 items-center justify-center rounded-md border bg-background">
<UserRoundPlusIcon className="size-4" />
</div>
<div className="font-medium text-muted-foreground">Add new team</div>
</DropdownMenuItem>
</UpgradePlanModal>
)}
</DropdownMenuContent>
</DropdownMenu>
</SidebarMenuItem>
Expand Down
5 changes: 5 additions & 0 deletions components/teams/add-team-member-modal.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { useState } from "react";
import { useRouter } from "next/router";

import { useTeam } from "@/context/team-context";
import { toast } from "sonner";
Expand Down Expand Up @@ -34,6 +35,7 @@ export function AddTeamMembers({
const teamInfo = useTeam();
const teamId = teamInfo?.currentTeam?.id;
const analytics = useAnalytics();
const router = useRouter();
const emailSchema = z
.string()
.trim()
Expand Down Expand Up @@ -81,6 +83,9 @@ export function AddTeamMembers({
toast.success("An invitation email has been sent!");
setOpen(false);
setLoading(false);

// Redirect to team members page
router.push("/settings/people");
Comment on lines +86 to +88
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

Verify redirect behavior across all modal usage contexts.

The unconditional redirect to /settings/people may disrupt the user flow if this modal is invoked from pages other than the settings area. For example, if a user adds a team member from the team switcher or another context, they might expect to remain on their current page.

Run the following script to identify all locations where AddTeamMembers is used:


🏁 Script executed:

#!/bin/bash
# Find all usages of AddTeamMembers component
rg -n "AddTeamMembers" --type=tsx --type=ts -C3

Length of output: 27



---

🏁 Script executed:

```shell
#!/bin/bash
# Find all usages of AddTeamMembers component without type specification
rg -n "AddTeamMembers" -C3

Length of output: 3445


🏁 Script executed:

#!/bin/bash
# Also search for imports of the modal to understand usage patterns
rg -n "add-team-member-modal" -C3

Length of output: 1131


🏁 Script executed:

#!/bin/bash
# Find file extensions in the components/teams directory to understand structure
fd . components/teams --type f

Length of output: 150



---

🏁 Script executed:

```shell
#!/bin/bash
# Find all usages of team-switcher component to understand where it appears
rg -n "team-switcher" -C2

Length of output: 390


🏁 Script executed:

#!/bin/bash
# Check the content of add-team-member-modal.tsx to see the full redirect context
cat components/teams/add-team-member-modal.tsx | head -100

Length of output: 2667


Fix unconditional redirect that disrupts user flow when adding team members from sidebar.

The modal redirects to /settings/people unconditionally after a successful invitation, which disrupts users who invoke it from the sidebar (used throughout the app, not just settings). Users adding members while on other pages (dashboard, projects, etc.) will be unexpectedly redirected away.

Options:

  • Remove the redirect entirely (modal closes, user stays on current page)
  • Make redirect conditional (e.g., only redirect if already on settings/people)
  • Accept a callback prop to let parent components control post-invitation navigation
🤖 Prompt for AI Agents
In components/teams/add-team-member-modal.tsx around lines 86-88, remove the
unconditional router.push("/settings/people") after a successful invitation and
instead either 1) simply close the modal so the user stays on the current page,
or 2) make the redirect conditional by checking the current route and only
calling router.push if the user is already on /settings/people, or 3) expose and
call an optional onSuccess callback prop (invoked with the invitation result) so
parent components can decide navigation; implement one of these approaches and
ensure the modal close logic still runs after a successful invite.

};

return (
Expand Down
16 changes: 16 additions & 0 deletions ee/limits/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,22 @@ export const DATAROOMS_PLUS_PLAN_LIMITS = {
},
};

export const DATAROOMS_PREMIUM_PLAN_LIMITS = {
users: 10,
links: null,
documents: null,
domains: 1000,
datarooms: 1000,
customDomainOnPro: true,
customDomainInDataroom: true,
conversationsInDataroom: true,
advancedLinkControlsOnPro: false,
fileSizeLimits: {
maxFiles: 5000,
maxPages: 1000,
},
};

export const PAUSED_PLAN_LIMITS = {
// During pause: keep all data accessible but restrict new creations and views
canCreateLinks: false,
Expand Down
2 changes: 2 additions & 0 deletions ee/limits/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
BUSINESS_PLAN_LIMITS,
DATAROOMS_PLAN_LIMITS,
DATAROOMS_PLUS_PLAN_LIMITS,
DATAROOMS_PREMIUM_PLAN_LIMITS,
FREE_PLAN_LIMITS,
PRO_PLAN_LIMITS,
TPlanLimits,
Expand All @@ -24,6 +25,7 @@ const planLimitsMap: Record<string, TPlanLimits> = {
business: BUSINESS_PLAN_LIMITS,
datarooms: DATAROOMS_PLAN_LIMITS,
"datarooms-plus": DATAROOMS_PLUS_PLAN_LIMITS,
"datarooms-premium": DATAROOMS_PREMIUM_PLAN_LIMITS,
};

export const configSchema = z.object({
Expand Down
Loading
Loading