From 5a4835091f5c57d1760315177c61119b4d8f0cde Mon Sep 17 00:00:00 2001 From: Henry <30578846+hjball@users.noreply.github.com> Date: Sun, 7 Sep 2025 22:50:20 +0100 Subject: [PATCH] feat(cloud): unlimited seats (#166) * feat: add unlimitedSeats to subscription * feat: set unlimited seats to true for pro plans * feat: add unlimited invites messaging * feat: upgrade to pro via auth client * feat: add subscription repo funcs * chore: move subscription utils to shared * feat: skip updating subscription if unlimited seats --- .../members/components/InviteMemberForm.tsx | 48 +- apps/web/src/views/members/index.tsx | 27 +- .../components/CustomURLConfirmation.tsx | 45 +- packages/api/src/routers/member.ts | 56 +- packages/auth/src/auth.ts | 19 + ...185512_AddUnlimitedSeatsToSubscription.sql | 1 + .../meta/20250907185512_snapshot.json | 2543 +++++++++++++++++ packages/db/migrations/meta/_journal.json | 7 + .../db/src/repository/subscription.repo.ts | 77 + packages/db/src/repository/workspace.repo.ts | 1 + packages/db/src/schema/subscriptions.ts | 1 + packages/shared/src/utils/index.ts | 1 + packages/shared/src/utils/subscriptions.ts | 57 + 13 files changed, 2807 insertions(+), 76 deletions(-) create mode 100644 packages/db/migrations/20250907185512_AddUnlimitedSeatsToSubscription.sql create mode 100644 packages/db/migrations/meta/20250907185512_snapshot.json create mode 100644 packages/db/src/repository/subscription.repo.ts create mode 100644 packages/shared/src/utils/subscriptions.ts diff --git a/apps/web/src/views/members/components/InviteMemberForm.tsx b/apps/web/src/views/members/components/InviteMemberForm.tsx index f22049cf..3646ef62 100644 --- a/apps/web/src/views/members/components/InviteMemberForm.tsx +++ b/apps/web/src/views/members/components/InviteMemberForm.tsx @@ -7,7 +7,9 @@ import { HiXMark } from "react-icons/hi2"; import { z } from "zod"; import type { InviteMemberInput } from "@kan/api/types"; +import type { Subscription } from "@kan/shared/utils"; import { authClient } from "@kan/auth/client"; +import { getSubscriptionByPlan } from "@kan/shared/utils"; import Button from "~/components/Button"; import Input from "~/components/Input"; @@ -19,20 +21,13 @@ import { api } from "~/utils/api"; export function InviteMemberForm({ numberOfMembers, - activeTeamSubscription, + subscriptions, + unlimitedSeats, userId, }: { numberOfMembers: number; - activeTeamSubscription: - | { - id: number | null; - plan: string; - status: string; - seats: number | null; - periodStart: Date | null; - periodEnd: Date | null; - } - | undefined; + subscriptions: Subscription[] | undefined; + unlimitedSeats: boolean; userId: string | undefined; }) { const utils = api.useUtils(); @@ -87,16 +82,19 @@ export function InviteMemberForm({ }, }); + const teamSubscription = getSubscriptionByPlan(subscriptions, "team"); + const proSubscription = getSubscriptionByPlan(subscriptions, "pro"); + + const hasTeamSubscription = !!teamSubscription; + const hasProSubscription = !!proSubscription; + let isYearly = false; let price = t`$10/month`; let billingType = t`monthly billing`; - if ( - activeTeamSubscription?.periodStart && - activeTeamSubscription?.periodEnd - ) { - const periodStartDate = new Date(activeTeamSubscription.periodStart); - const periodEndDate = new Date(activeTeamSubscription.periodEnd); + if (teamSubscription?.periodStart && teamSubscription?.periodEnd) { + const periodStartDate = new Date(teamSubscription.periodStart); + const periodEndDate = new Date(teamSubscription.periodEnd); const diffInDays = Math.round( (periodEndDate.getTime() - periodStartDate.getTime()) / (1000 * 60 * 60 * 24), @@ -163,7 +161,8 @@ export function InviteMemberForm({ placeholder={t`Email`} disabled={ env("NEXT_PUBLIC_KAN_ENV") === "cloud" && - !activeTeamSubscription?.id + !hasTeamSubscription && + !hasProSubscription } {...register("email", { required: true })} onKeyDown={async (e) => { @@ -177,13 +176,15 @@ export function InviteMemberForm({ {env("NEXT_PUBLIC_KAN_ENV") === "cloud" && (
- {activeTeamSubscription?.id ? ( + {hasTeamSubscription || hasProSubscription ? (
- {t`Team Plan`} + {hasTeamSubscription ? t`Team Plan` : t`Pro Plan ∞`}

- {t`Adding a new member will cost an additional ${price} (${billingType}) per seat.`} + {unlimitedSeats + ? t`You have unlimited seats with your Pro Plan. There is no additional charge for new members!` + : t`Adding a new member will cost an additional ${price} (${billingType}) per seat.`}

) : ( @@ -201,7 +202,7 @@ export function InviteMemberForm({
- {activeTeamSubscription?.id && + {(hasTeamSubscription || hasProSubscription) && env("NEXT_PUBLIC_KAN_ENV") === "cloud" && ( {env("NEXT_PUBLIC_KAN_ENV") === "cloud" && - !activeTeamSubscription?.id ? ( + !hasTeamSubscription && + !hasProSubscription ? (