From 6059fa35a2aec8aacaa0ce7b2ee70d0057afcc3d Mon Sep 17 00:00:00 2001
From: Henry <30578846+hjball@users.noreply.github.com>
Date: Sat, 27 Sep 2025 20:48:16 +0100
Subject: [PATCH] fix: show invite link toggle on self hosted environments
(#198)
---
apps/web/src/components/Toggle.tsx | 3 +
.../members/components/InviteMemberForm.tsx | 58 +++++++++++--------
packages/api/src/routers/member.ts | 24 ++++++++
3 files changed, 62 insertions(+), 23 deletions(-)
diff --git a/apps/web/src/components/Toggle.tsx b/apps/web/src/components/Toggle.tsx
index e7c61a74..0c0e657c 100644
--- a/apps/web/src/components/Toggle.tsx
+++ b/apps/web/src/components/Toggle.tsx
@@ -5,10 +5,12 @@ const Toggle = ({
isChecked,
onChange,
label,
+ disabled,
}: {
isChecked: boolean;
onChange: () => void;
label: string;
+ disabled?: boolean;
}) => (
@@ -17,6 +19,7 @@ const Toggle = ({
("");
- const [isLoadingInviteLink, setIsLoadingInviteLink] = useState(false);
+ const [_isLoadingInviteLink, setIsLoadingInviteLink] = useState(false);
const [copied, setCopied] = useState(false);
const { closeModal } = useModal();
const { workspace } = useWorkspace();
@@ -68,7 +68,7 @@ export function InviteMemberForm({
const refetchBoards = () => utils.board.all.refetch();
// Fetch active invite link on component mount
- const { data: activeInviteLink, refetch: refetchInviteLink } =
+ const { data: activeInviteLink, refetch: _refetchInviteLink } =
api.member.getActiveInviteLink.useQuery(
{ workspacePublicId: workspace.publicId || "" },
{ enabled: !!workspace.publicId },
@@ -78,7 +78,7 @@ export function InviteMemberForm({
useEffect(() => {
if (activeInviteLink) {
setIsShareInviteLinkEnabled(activeInviteLink.isActive);
- setInviteLink(activeInviteLink.inviteLink || "");
+ setInviteLink(activeInviteLink.inviteLink ?? "");
}
}, [activeInviteLink]);
@@ -113,13 +113,23 @@ export function InviteMemberForm({
setInviteLink(data.inviteLink);
setIsLoadingInviteLink(false);
},
- onError: () => {
+ onError: (error) => {
setIsLoadingInviteLink(false);
- showPopup({
- header: t`Error creating invite link`,
- message: t`Please try again later.`,
- icon: "error",
- });
+ setIsShareInviteLinkEnabled(false);
+
+ if (error.data?.code === "FORBIDDEN") {
+ showPopup({
+ header: t`Subscription Required`,
+ message: t`Invite links require a Team or Pro subscription. Please upgrade your workspace.`,
+ icon: "error",
+ });
+ } else {
+ showPopup({
+ header: t`Error creating invite link`,
+ message: t`Please try again later.`,
+ icon: "error",
+ });
+ }
},
});
@@ -148,7 +158,7 @@ export function InviteMemberForm({
let price = t`$10/month`;
let billingType = t`monthly billing`;
- if (teamSubscription?.periodStart && teamSubscription?.periodEnd) {
+ if (teamSubscription?.periodStart && teamSubscription.periodEnd) {
const periodStartDate = new Date(teamSubscription.periodStart);
const periodEndDate = new Date(teamSubscription.periodEnd);
const diffInDays = Math.round(
@@ -193,7 +203,7 @@ export function InviteMemberForm({
message: t`Invite link copied to clipboard`,
icon: "success",
});
- } catch (error) {
+ } catch {
showPopup({
header: t`Error`,
message: t`Failed to copy invite link`,
@@ -325,18 +335,20 @@ export function InviteMemberForm({
- {(hasTeamSubscription || hasProSubscription) &&
- env("NEXT_PUBLIC_KAN_ENV") === "cloud" && (
-
- )}
+
{env("NEXT_PUBLIC_KAN_ENV") === "cloud" &&
!hasTeamSubscription &&
diff --git a/packages/api/src/routers/member.ts b/packages/api/src/routers/member.ts
index f36c277b..0759f566 100644
--- a/packages/api/src/routers/member.ts
+++ b/packages/api/src/routers/member.ts
@@ -362,6 +362,30 @@ export const memberRouter = createTRPCRouter({
// Check if user is in workspace
await assertUserInWorkspace(ctx.db, userId, workspace.id, "admin");
+ // Check subscription for cloud environment
+ if (process.env.NEXT_PUBLIC_KAN_ENV === "cloud") {
+ const subscriptions = await subscriptionRepo.getByReferenceId(
+ ctx.db,
+ workspace.publicId,
+ );
+
+ const activeTeamSubscription = getSubscriptionByPlan(
+ subscriptions,
+ "team",
+ );
+ const activeProSubscription = getSubscriptionByPlan(
+ subscriptions,
+ "pro",
+ );
+
+ if (!activeTeamSubscription && !activeProSubscription) {
+ throw new TRPCError({
+ message: `Invite links require a Team or Pro subscription`,
+ code: "FORBIDDEN",
+ });
+ }
+ }
+
// Deactivate any existing active invite links
await inviteLinkRepo.deactivateAllActiveForWorkspace(ctx.db, {
workspaceId: workspace.id,