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,