feat(subscriptions): add SaaS subscription tiers, pricing page, and enforced upload quotas

- Add Free / Starter / Professional / Business tiers with lifetime, daily, and monthly
  file limits (app/utils/subscription.py)
- Add subscription_tier column to UserProfile model + migration 014
- Enforce quotas at upload time (HTTP 402 on violation) in /api/ui-upload
- New REST API: GET /api/subscriptions/tiers, /my, /platform (admin)
- New pages: /pricing (marketing, public) and /subscription (per-user status)
- Enhanced dashboard: SaaS stats (files today/month, OCR count, active users)
  in multi-user mode; original single-user layout preserved
- Admin users page: show Plan badge, allow tier editing via dropdown
- Navigation: add Pricing link + subscription icon in user header
- Tests: 23 unit tests for subscription tier logic
- Docs: docs/SubscriptionTiers.md

Co-authored-by: christianlouis <361235+christianlouis@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2026-03-06 15:53:12 +00:00
parent 70dd35dec4
commit 179f6125e8
18 changed files with 2014 additions and 18 deletions
+13 -3
View File
@@ -141,25 +141,35 @@ window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', fun
if (authSection) {
authSection.textContent = ''; // Clear existing content
const container = document.createElement('div');
container.className = 'flex items-center';
container.className = 'flex items-center gap-2';
const img = document.createElement('img');
img.src = data.picture;
img.alt = 'Avatar';
img.className = 'w-8 h-8 rounded-full mr-2';
img.className = 'w-8 h-8 rounded-full';
const span = document.createElement('span');
span.textContent = displayName;
// Subscription link
const planLink = document.createElement('a');
planLink.href = '/subscription';
planLink.className = 'text-xs text-indigo-600 hover:text-indigo-800 font-medium hidden md:inline';
planLink.title = 'My subscription';
const planIcon = document.createElement('i');
planIcon.className = 'fas fa-layer-group';
planLink.appendChild(planIcon);
const logoutLink = document.createElement('a');
logoutLink.href = '/logout';
logoutLink.className = 'ml-3 text-red-600 hover:text-red-800';
logoutLink.className = 'text-red-600 hover:text-red-800';
const icon = document.createElement('i');
icon.className = 'fas fa-sign-out-alt';
logoutLink.appendChild(icon);
container.appendChild(img);
container.appendChild(span);
container.appendChild(planLink);
container.appendChild(logoutLink);
authSection.appendChild(container);
}