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")}
+ )}