Add platform email templates, staff invitations, and quota tracking

- Add PlatformEmailTemplate model and API for superuser-managed email templates
- Add PlatformStaffInvitation model with email sending via Celery tasks
- Add platform staff invite page and acceptance flow with auto-login
- Add quota tracking models (DailyAppointmentUsage, DailyAPIUsage, StorageUsage)
- Add quota status API endpoints and frontend banners
- Add storage usage service for tenant media tracking
- Fix platform user deletion with raw SQL to handle multi-tenant FK constraints
- Update EditPlatformUserModal with archive/delete buttons
- Update PlatformSidebar with email templates link for superusers
- Configure console email backend and Celery eager mode for local development

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
poduck
2026-01-01 10:35:35 -05:00
parent f13a40e4bc
commit fc63cf4fce
66 changed files with 8145 additions and 666 deletions

View File

@@ -66,10 +66,12 @@ const PlatformStaff = React.lazy(() => import('./pages/platform/PlatformStaff'))
const PlatformSettings = React.lazy(() => import('./pages/platform/PlatformSettings'));
const BillingManagement = React.lazy(() => import('./pages/platform/BillingManagement'));
const PlatformStaffEmail = React.lazy(() => import('./pages/platform/PlatformStaffEmail'));
const PlatformEmailTemplates = React.lazy(() => import('./pages/platform/PlatformEmailTemplates'));
const ProfileSettings = React.lazy(() => import('./pages/ProfileSettings'));
const VerifyEmail = React.lazy(() => import('./pages/VerifyEmail'));
const EmailVerificationRequired = React.lazy(() => import('./pages/EmailVerificationRequired'));
const AcceptInvitePage = React.lazy(() => import('./pages/AcceptInvitePage'));
const PlatformStaffInvitePage = React.lazy(() => import('./pages/platform/PlatformStaffInvitePage'));
const TenantOnboardPage = React.lazy(() => import('./pages/TenantOnboardPage'));
const TenantLandingPage = React.lazy(() => import('./pages/TenantLandingPage'));
const Tickets = React.lazy(() => import('./pages/Tickets')); // Import Tickets page
@@ -375,6 +377,7 @@ const AppContent: React.FC = () => {
<Route path="/verify-email" element={<VerifyEmail />} />
<Route path="/accept-invite" element={<AcceptInvitePage />} />
<Route path="/accept-invite/:token" element={<AcceptInvitePage />} />
<Route path="/platform-staff-invite" element={<PlatformStaffInvitePage />} />
<Route path="/tenant-onboard" element={<TenantOnboardPage />} />
<Route path="/sign/:token" element={<ContractSigning />} />
<Route path="*" element={<Navigate to="/" replace />} />
@@ -411,6 +414,7 @@ const AppContent: React.FC = () => {
<Route path="/verify-email" element={<VerifyEmail />} />
<Route path="/accept-invite" element={<AcceptInvitePage />} />
<Route path="/accept-invite/:token" element={<AcceptInvitePage />} />
<Route path="/platform-staff-invite" element={<PlatformStaffInvitePage />} />
<Route path="/tenant-onboard" element={<TenantOnboardPage />} />
<Route path="/sign/:token" element={<ContractSigning />} />
<Route path="*" element={<Navigate to="/" replace />} />
@@ -419,10 +423,10 @@ const AppContent: React.FC = () => {
);
}
// For platform subdomain, only /platform/login exists - everything else renders nothing
// For platform subdomain, only specific paths exist - everything else renders nothing
if (isPlatformSubdomain) {
const path = window.location.pathname;
const allowedPaths = ['/platform/login', '/mfa-verify', '/verify-email'];
const allowedPaths = ['/platform/login', '/mfa-verify', '/verify-email', '/platform-staff-invite'];
// If not an allowed path, render nothing
if (!allowedPaths.includes(path)) {
@@ -435,6 +439,7 @@ const AppContent: React.FC = () => {
<Route path="/platform/login" element={<PlatformLoginPage />} />
<Route path="/mfa-verify" element={<MFAVerifyPage />} />
<Route path="/verify-email" element={<VerifyEmail />} />
<Route path="/platform-staff-invite" element={<PlatformStaffInvitePage />} />
</Routes>
</Suspense>
);
@@ -460,6 +465,7 @@ const AppContent: React.FC = () => {
<Route path="/verify-email" element={<VerifyEmail />} />
<Route path="/accept-invite" element={<AcceptInvitePage />} />
<Route path="/accept-invite/:token" element={<AcceptInvitePage />} />
<Route path="/platform-staff-invite" element={<PlatformStaffInvitePage />} />
<Route path="/tenant-onboard" element={<TenantOnboardPage />} />
<Route path="/sign/:token" element={<ContractSigning />} />
<Route path="*" element={<Navigate to="/" replace />} />
@@ -599,6 +605,7 @@ const AppContent: React.FC = () => {
<>
<Route path="/platform/settings" element={<PlatformSettings />} />
<Route path="/platform/billing" element={<BillingManagement />} />
<Route path="/platform/email-templates" element={<PlatformEmailTemplates />} />
</>
)}
<Route path="/platform/profile" element={<ProfileSettings />} />