diff --git a/server/emails/templates/NotifyTrialExpiring.tsx b/server/emails/templates/NotifyTrialExpiring.tsx index 7cd6d30ac..7c712e278 100644 --- a/server/emails/templates/NotifyTrialExpiring.tsx +++ b/server/emails/templates/NotifyTrialExpiring.tsx @@ -64,7 +64,7 @@ export const NotifyTrialExpiring = ({ Some features and resources may now be - restricted or disconnected. To restore full + restricted. To restore full access and continue using all the features you had during your trial, please upgrade to a paid plan. @@ -85,7 +85,7 @@ export const NotifyTrialExpiring = ({ {orgName} will end on{" "} {trialEndsAt} {isLastDay - ? " — that's tomorrow!" + ? " - that's tomorrow!" : `, in ${daysRemaining} days`} . @@ -93,8 +93,7 @@ export const NotifyTrialExpiring = ({ After your trial ends, your account will be moved to the free plan and some - functionality may be restricted or your - sites may disconnect. + functionality may be restricted. diff --git a/server/lib/billing/limitSet.ts b/server/lib/billing/limitSet.ts index ae9a18ffe..e45ae637d 100644 --- a/server/lib/billing/limitSet.ts +++ b/server/lib/billing/limitSet.ts @@ -25,7 +25,7 @@ export const tier1LimitSet: LimitSet = { export const tier2LimitSet: LimitSet = { [FeatureId.USERS]: { - value: 100, + value: 50, description: "Team limit" }, [FeatureId.SITES]: { @@ -48,7 +48,7 @@ export const tier2LimitSet: LimitSet = { export const tier3LimitSet: LimitSet = { [FeatureId.USERS]: { - value: 500, + value: 250, description: "Business limit" }, [FeatureId.SITES]: { diff --git a/server/private/lib/billing/getOrgTierData.ts b/server/private/lib/billing/getOrgTierData.ts index 1dc9f83a4..9df9b3b74 100644 --- a/server/private/lib/billing/getOrgTierData.ts +++ b/server/private/lib/billing/getOrgTierData.ts @@ -19,12 +19,13 @@ import { eq, and, ne } from "drizzle-orm"; export async function getOrgTierData( orgId: string -): Promise<{ tier: Tier | null; active: boolean }> { +): Promise<{ tier: Tier | null; active: boolean; isTrial: boolean }> { let tier: Tier | null = null; let active = false; + let isTrial = false; if (build !== "saas") { - return { tier, active }; + return { tier, active, isTrial }; } try { @@ -35,7 +36,7 @@ export async function getOrgTierData( .limit(1); if (!org) { - return { tier, active }; + return { tier, active, isTrial }; } let orgIdToUse = org.orgId; @@ -44,7 +45,7 @@ export async function getOrgTierData( logger.warn( `Org ${orgId} is not a billing org and does not have a billingOrgId` ); - return { tier, active }; + return { tier, active, isTrial }; } orgIdToUse = org.billingOrgId; } @@ -57,7 +58,7 @@ export async function getOrgTierData( .limit(1); if (!customer) { - return { tier, active }; + return { tier, active, isTrial }; } // Query for active subscriptions that are not license type @@ -84,11 +85,13 @@ export async function getOrgTierData( tier = subscription.type; active = true; } + + isTrial = subscription.trial ?? false; } } catch (error) { // If org not found or error occurs, return null tier and inactive // This is acceptable behavior as per the function signature } - return { tier, active }; + return { tier, active, isTrial }; } diff --git a/server/routers/auth/deleteMyAccount.ts b/server/routers/auth/deleteMyAccount.ts index b824e582b..07bdf883d 100644 --- a/server/routers/auth/deleteMyAccount.ts +++ b/server/routers/auth/deleteMyAccount.ts @@ -104,8 +104,9 @@ export async function deleteMyAccount( (r) => r.isBillingOrg && r.isOwner )?.orgId; if (primaryOrgId) { - const { tier, active } = await getOrgTierData(primaryOrgId); - if (active && tier) { + const { tier, active, isTrial } = + await getOrgTierData(primaryOrgId); + if (active && tier && !isTrial) { return next( createHttpError( HttpCode.BAD_REQUEST, diff --git a/server/routers/siteResource/deleteSiteResource.ts b/server/routers/siteResource/deleteSiteResource.ts index df43d5c25..7dbb111ad 100644 --- a/server/routers/siteResource/deleteSiteResource.ts +++ b/server/routers/siteResource/deleteSiteResource.ts @@ -63,17 +63,26 @@ export async function deleteSiteResource( ); } - await db.transaction(async (trx) => { - // Delete the site resource - const [removedSiteResource] = await trx - .delete(siteResources) - .where(eq(siteResources.siteResourceId, siteResourceId)) - .returning(); + // Delete the site resource + const [removedSiteResource] = await db + .delete(siteResources) + .where(eq(siteResources.siteResourceId, siteResourceId)) + .returning(); + // Run in the background after the response is sent. Wrapped in its + // own transaction so it always executes on the primary — avoiding any + // replica-lag issues while still allowing the HTTP response to return + // early. + db.transaction(async (trx) => { await rebuildClientAssociationsFromSiteResource( removedSiteResource, trx ); + }).catch((err) => { + logger.error( + `Error rebuilding client associations for site resource ${removedSiteResource!.siteResourceId}:`, + err + ); }); logger.info(`Deleted site resource ${siteResourceId}`);