diff --git a/apps/api/src/routers/stripe.ts b/apps/api/src/routers/stripe.ts index 1cbfcfe..4b6a735 100644 --- a/apps/api/src/routers/stripe.ts +++ b/apps/api/src/routers/stripe.ts @@ -293,6 +293,9 @@ const createCheckoutSession = ( customerId = customer.id; } + const TRIAL_PERIOD_DAYS = 14; + const planHasTrial = plan === "solo" || plan === "team"; + const session = await stripe.checkout.sessions.create({ customer: customerId, line_items: [ @@ -315,6 +318,7 @@ const createCheckoutSession = ( organization_id: String(ownerContext.organizationId), checkout_plan: plan ?? "legacy_price", }, + ...(planHasTrial && { trial_period_days: TRIAL_PERIOD_DAYS }), }, }); diff --git a/apps/api/src/routers/user.ts b/apps/api/src/routers/user.ts index 481002c..b4ce427 100644 --- a/apps/api/src/routers/user.ts +++ b/apps/api/src/routers/user.ts @@ -531,6 +531,20 @@ const inviteToOrganization = factory.createHandlers(async (c) => { return c.json({ error: "This email already belongs to another organization" }, 409); } + const TEAM_PLAN_MEMBER_LIMIT = 3; + + if ( + billingState.active_subscription_plan === "team" && + billingState.member_count >= TEAM_PLAN_MEMBER_LIMIT + ) { + return c.json( + { + error: `The Teams plan is limited to ${TEAM_PLAN_MEMBER_LIMIT} members per organization. Upgrade to the Founder plan to add unlimited members.`, + }, + 403 + ); + } + if (billingState.is_trial_expired && billingState.active_subscription_plan !== "annual") { const requiredSeatsForInvite = billingState.member_count + 1; diff --git a/apps/main/src/pages/settings.tsx b/apps/main/src/pages/settings.tsx index dd16ce4..e841b39 100644 --- a/apps/main/src/pages/settings.tsx +++ b/apps/main/src/pages/settings.tsx @@ -80,6 +80,10 @@ export default function SettingsPage() { ); const canManageMembers = organizationData?.is_billing_owner ?? false; + const TEAM_PLAN_MEMBER_LIMIT = 3; + const isAtTeamMemberLimit = + organizationData?.active_subscription_plan === "team" && + (organizationData?.organization?.member_count ?? 0) >= TEAM_PLAN_MEMBER_LIMIT; const getDisplayName = (input: { first_name?: string | null; @@ -419,13 +423,21 @@ export default function SettingsPage() { value={inviteEmail} onChange={(e) => setInviteEmail(e.target.value)} placeholder={t("settings:teamInvite.emailPlaceholder")} + disabled={isAtTeamMemberLimit} /> -

{t("settings:teamInvite.hint")}

+ {isAtTeamMemberLimit ? ( +

+ Le plan Teams est limité à {TEAM_PLAN_MEMBER_LIMIT} membres. Passez au plan + Founder pour ajouter des membres illimités. +

+ ) : ( +

{t("settings:teamInvite.hint")}

+ )}