Skip to content

Commit 6f87bd4

Browse files
committed
RBAC: give upgrade-link rows a value so Ariakit handles the click
1 parent 0e430c0 commit 6f87bd4

1 file changed

Lines changed: 28 additions & 17 deletions

File tree

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.settings.team

apps/webapp/app/routes/_app.orgs.$organizationSlug.settings.team/route.tsx

Lines changed: 28 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ import { Label } from "~/components/primitives/Label";
4141
import { NavBar, PageAccessories, PageTitle } from "~/components/primitives/PageHeader";
4242
import { Paragraph } from "~/components/primitives/Paragraph";
4343
import * as Property from "~/components/primitives/PropertyTable";
44-
import { Select, SelectItem } from "~/components/primitives/Select";
44+
import { Select, SelectItem, SelectLinkItem } from "~/components/primitives/Select";
4545
import { SpinnerWhite } from "~/components/primitives/Spinner";
4646
import { SimpleTooltip } from "~/components/primitives/Tooltip";
4747
import { cn } from "~/utils/cn";
@@ -52,16 +52,14 @@ import { removeTeamMember } from "~/models/member.server";
5252
import { redirectWithSuccessMessage } from "~/models/message.server";
5353
import { TeamPresenter } from "~/presenters/TeamPresenter.server";
5454
import { rbac } from "~/services/rbac.server";
55-
import {
56-
dashboardAction,
57-
dashboardLoader,
58-
} from "~/services/routeBuilders/dashboardBuilder";
55+
import { dashboardAction, dashboardLoader } from "~/services/routeBuilders/dashboardBuilder";
5956
import {
6057
inviteTeamMemberPath,
6158
organizationRolesPath,
6259
organizationTeamPath,
6360
resendInvitePath,
6461
revokeInvitePath,
62+
selectPlanPath,
6563
v3BillingPath,
6664
} from "~/utils/pathBuilder";
6765
import { formatCurrency, formatNumber } from "~/utils/numberFormatter";
@@ -137,10 +135,7 @@ const PurchaseSchema = z.discriminatedUnion("action", [
137135
}),
138136
z.object({
139137
action: z.literal("quota-increase"),
140-
amount: z.coerce
141-
.number()
142-
.int("Must be a whole number")
143-
.min(1, "Amount must be greater than 0"),
138+
amount: z.coerce.number().int("Must be a whole number").min(1, "Amount must be greater than 0"),
144139
}),
145140
]);
146141

@@ -339,7 +334,11 @@ export default function Page() {
339334
) : requiresUpgrade ? (
340335
<SimpleTooltip
341336
button={
342-
<ButtonContent variant="primary/small" LeadingIcon={UserPlusIcon} className="cursor-not-allowed opacity-50">
337+
<ButtonContent
338+
variant="primary/small"
339+
LeadingIcon={UserPlusIcon}
340+
className="cursor-not-allowed opacity-50"
341+
>
343342
Invite a team member
344343
</ButtonContent>
345344
}
@@ -579,6 +578,7 @@ function RolePicker({
579578
assignableRoleIds: string[];
580579
canManageMembers: boolean;
581580
}) {
581+
const organization = useOrganization();
582582
const fetcher = useFetcher<{ ok: boolean; error?: string } | { ok: true }>();
583583
const assignable = new Set(assignableRoleIds);
584584
// With no RBAC plugin installed, the loader returns no roles —
@@ -587,13 +587,11 @@ function RolePicker({
587587

588588
const isSubmitting = fetcher.state === "submitting";
589589
const error =
590-
fetcher.data && "error" in fetcher.data && fetcher.data.error
591-
? fetcher.data.error
592-
: null;
590+
fetcher.data && "error" in fetcher.data && fetcher.data.error ? fetcher.data.error : null;
593591

594592
return (
595593
<div className="flex flex-col items-end gap-1">
596-
<Select<string, Role>
594+
<Select
597595
defaultValue={currentRoleId ?? ""}
598596
items={roles}
599597
variant="tertiary/small"
@@ -602,6 +600,12 @@ function RolePicker({
602600
text={(v) => roles.find((r) => r.id === v)?.name ?? "No role"}
603601
setValue={(next) => {
604602
if (typeof next !== "string" || next === (currentRoleId ?? "")) return;
603+
// Upgrade-link rows have a value too (Ariakit needs one to
604+
// make the row interactive — without it the Link inside
605+
// doesn't even register the click), but they shouldn't
606+
// submit the role-change form. The Link navigates the user
607+
// to the plan-selection page; we just bail here.
608+
if (!assignable.has(next)) return;
605609
fetcher.submit(
606610
{ _formType: "set-role", userId: memberUserId, roleId: next },
607611
{ method: "post" }
@@ -611,11 +615,18 @@ function RolePicker({
611615
{(items) =>
612616
items.map((role) => {
613617
const isAssignable = assignable.has(role.id);
614-
return (
615-
<SelectItem key={role.id} value={role.id} disabled={!isAssignable}>
618+
return isAssignable ? (
619+
<SelectItem key={role.id} value={role.id}>
616620
{role.name}
617-
{!isAssignable ? " (upgrade)" : ""}
618621
</SelectItem>
622+
) : (
623+
<SelectLinkItem
624+
key={role.id}
625+
value={role.id}
626+
to={selectPlanPath(organization)}
627+
>
628+
{role.name} (upgrade)
629+
</SelectLinkItem>
619630
);
620631
})
621632
}

0 commit comments

Comments
 (0)