From 15549a675895384c74cb4b85365e50d0707fdabe Mon Sep 17 00:00:00 2001 From: David Nguyen Date: Mon, 15 Jun 2026 16:02:18 +1000 Subject: [PATCH] fix: add early quota warning (#2986) ## Description - Add banners when plans near fair use limits - Automated email alerts to our support when nearing fair use limits --- .../organisation-quota-banner.tsx | 70 ++++++-- .../organisation-quota-banner.spec.ts | 169 ++++++++++++++++++ .../templates/organisation-limit-alert.tsx | 152 ++++++++++++++++ .../templates/organisation-limit-exceeded.tsx | 109 ----------- packages/lib/jobs/client.ts | 4 +- ...organisation-limit-alert-email.handler.ts} | 72 +++++--- .../send-organisation-limit-alert-email.ts | 34 ++++ .../send-organisation-limit-exceeded-email.ts | 34 ---- .../rate-limit/check-monthly-quota.ts | 31 ++-- .../rate-limit/compute-quota-flags.ts | 30 +++- .../rate-limit/get-quota-alert-kind.ts | 40 +++++ .../get-organisation-quota-flags.types.ts | 3 + 12 files changed, 550 insertions(+), 198 deletions(-) create mode 100644 packages/app-tests/e2e/organisations/organisation-quota-banner.spec.ts create mode 100644 packages/email/templates/organisation-limit-alert.tsx delete mode 100644 packages/email/templates/organisation-limit-exceeded.tsx rename packages/lib/jobs/definitions/emails/{send-organisation-limit-exceeded-email.handler.ts => send-organisation-limit-alert-email.handler.ts} (51%) create mode 100644 packages/lib/jobs/definitions/emails/send-organisation-limit-alert-email.ts delete mode 100644 packages/lib/jobs/definitions/emails/send-organisation-limit-exceeded-email.ts create mode 100644 packages/lib/server-only/rate-limit/get-quota-alert-kind.ts diff --git a/apps/remix/app/components/general/organisations/organisation-quota-banner.tsx b/apps/remix/app/components/general/organisations/organisation-quota-banner.tsx index c25f23bb3..b2a5e1a93 100644 --- a/apps/remix/app/components/general/organisations/organisation-quota-banner.tsx +++ b/apps/remix/app/components/general/organisations/organisation-quota-banner.tsx @@ -3,6 +3,7 @@ import { SUPPORT_EMAIL } from '@documenso/lib/constants/app'; import { DO_NOT_INVALIDATE_QUERY_ON_MUTATION, SKIP_QUERY_BATCH_META } from '@documenso/lib/constants/trpc'; import { INTERNAL_CLAIM_ID } from '@documenso/lib/types/subscription'; import { trpc } from '@documenso/trpc/react'; +import { cn } from '@documenso/ui/lib/utils'; import { Alert, AlertDescription } from '@documenso/ui/primitives/alert'; import { Button } from '@documenso/ui/primitives/button'; import { @@ -38,12 +39,18 @@ export const OrganisationQuotaBanner = () => { quotaFlags?.isDocumentQuotaExceeded || quotaFlags?.isEmailQuotaExceeded || quotaFlags?.isApiQuotaExceeded, ); - // Every member of the organisation sees the banner when a quota is exhausted. + const isAnyQuotaNearing = Boolean( + quotaFlags?.isDocumentQuotaNearing || quotaFlags?.isEmailQuotaNearing || quotaFlags?.isApiQuotaNearing, + ); + + // Every member of the organisation sees the banner when a quota is exhausted or + // nearing its limit. When both states apply, "exceeded" wins for the banner copy + // and the dialog lists both exceeded and nearing items. // Note: Skipping free plan banner for now because their quota can incorrectly show as exceeded. if ( !organisation || !quotaFlags || - !isAnyQuotaExceeded || + (!isAnyQuotaExceeded && !isAnyQuotaNearing) || organisation.organisationClaim.originalSubscriptionClaimId === INTERNAL_CLAIM_ID.FREE ) { return null; @@ -51,17 +58,29 @@ export const OrganisationQuotaBanner = () => { return ( <> -
+
- Your organisation has exceeded a fair use limit + {isAnyQuotaExceeded ? ( + Your organisation has exceeded a fair use limit + ) : ( + Your organisation is approaching a fair use limit + )}