Files
smoothschedule/frontend/TENANT_ONBOARDING_PLAN.md
poduck d158c1ddb0 feat: Implement tenant invitation system with onboarding wizard
Backend Implementation:
- Add TenantInvitation model with lifecycle management (PENDING/ACCEPTED/EXPIRED/CANCELLED)
- Create platform admin API endpoints for invitation CRUD operations
- Add public token-based endpoints for invitation retrieval and acceptance
- Implement schema_context wrappers to ensure tenant operations run in public schema
- Add tenant permissions: can_manage_oauth_credentials, can_accept_payments, can_use_custom_domain, can_white_label, can_api_access
- Fix tenant update/create serializers to handle multi-schema environment
- Add migrations for tenant permissions and invitation system

Frontend Implementation:
- Create TenantInviteModal with comprehensive invitation form (350 lines)
  - Email, business name, subscription tier configuration
  - Custom user/resource limits
  - Platform permissions toggles
  - Future feature flags (video conferencing, event types, calendars, 2FA, logs, data deletion, POS, mobile app)
- Build TenantOnboardPage with 4-step wizard for invitation acceptance
  - Step 1: Account setup (email, password, name)
  - Step 2: Business details (name, subdomain, contact)
  - Step 3: Payment setup (conditional based on permissions)
  - Step 4: Success confirmation with redirect
- Extract BusinessCreateModal and BusinessEditModal into separate components
- Refactor PlatformBusinesses from 1080 lines to 220 lines (80% reduction)
- Add inactive businesses dropdown section (similar to staff page pattern)
- Update masquerade button styling to match Users page
- Remove deprecated "Add New Tenant" functionality in favor of invitation flow
- Add /tenant-onboard route for public access

API Integration:
- Add platform.ts API functions for tenant invitations
- Create React Query hooks in usePlatform.ts for invitation management
- Implement proper error handling and success states
- Add TypeScript interfaces for invitation types

Testing:
- Verified end-to-end invitation flow from creation to acceptance
- Confirmed tenant, domain, and owner user creation
- Validated schema context fixes for multi-tenant environment
- Tested active/inactive business filtering

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-28 03:55:07 -05:00

7.4 KiB

Tenant Invitation & Custom Onboarding Flow

Overview

Replace the current "Create Tenant" modal with an invitation-based flow:

  1. Platform admin sends invitation with custom permissions/limits
  2. Invited owner receives email with onboarding link
  3. Owner completes multi-step onboarding wizard
  4. Tenant is fully provisioned with custom settings

Part 1: Backend - TenantInvitation Model

New Model: TenantInvitation (in platform_admin/models.py)

class TenantInvitation(models.Model):
    """
    Invitation for new business owners to create their tenant.
    Allows platform admins to pre-configure custom limits and permissions.
    """

    class Status(models.TextChoices):
        PENDING = 'PENDING', 'Pending'
        ACCEPTED = 'ACCEPTED', 'Accepted'
        EXPIRED = 'EXPIRED', 'Expired'
        CANCELLED = 'CANCELLED', 'Cancelled'

    # Invitation target
    email = models.EmailField()
    token = models.CharField(max_length=64, unique=True)
    status = models.CharField(max_length=20, choices=Status.choices, default=Status.PENDING)

    # Pre-configured business name (optional, owner can change during onboarding)
    suggested_business_name = models.CharField(max_length=100, blank=True)

    # Custom subscription settings (overrides tier defaults)
    subscription_tier = models.CharField(max_length=50, default='PROFESSIONAL')
    custom_max_users = models.IntegerField(null=True, blank=True)  # null = use tier default
    custom_max_resources = models.IntegerField(null=True, blank=True)

    # Platform permissions (what features this tenant can access)
    permissions = models.JSONField(default=dict, blank=True)
    # Example permissions:
    # {
    #   "can_manage_oauth_credentials": true,
    #   "can_accept_payments": true,
    #   "can_use_custom_domain": true,
    #   "can_white_label": false,
    #   "can_api_access": true,
    # }

    # Metadata
    invited_by = models.ForeignKey('users.User', on_delete=models.SET_NULL, null=True)
    created_at = models.DateTimeField(auto_now_add=True)
    expires_at = models.DateTimeField()

    # After acceptance
    accepted_at = models.DateTimeField(null=True, blank=True)
    created_tenant = models.ForeignKey('core.Tenant', null=True, blank=True, on_delete=models.SET_NULL)
    created_user = models.ForeignKey('users.User', null=True, blank=True, on_delete=models.SET_NULL, related_name='tenant_invitation_accepted')

API Endpoints

  1. Create Invitation (Platform Admin)

    • POST /api/platform/tenant-invitations/
    • Body: { email, suggested_business_name?, subscription_tier, custom_max_users?, custom_max_resources?, permissions }
    • Sends invitation email
  2. List Invitations (Platform Admin)

    • GET /api/platform/tenant-invitations/
    • Returns all invitations with status
  3. Get Invitation Details (Public - by token)

    • GET /api/platform/tenant-invitations/token/{token}/
    • Returns invitation details for onboarding page
  4. Accept Invitation (Public - by token)

    • POST /api/platform/tenant-invitations/token/{token}/accept/
    • Body: { password, first_name, last_name, business_name, subdomain, contact_email?, phone? }
    • Creates tenant, domain, and owner user
    • Returns auth tokens for immediate login
  5. Resend/Cancel (Platform Admin)

    • POST /api/platform/tenant-invitations/{id}/resend/
    • DELETE /api/platform/tenant-invitations/{id}/

Part 2: Update Tenant Model

Add new permission fields to core/models.py Tenant:

# Platform-controlled permissions (copied from invitation)
can_accept_payments = models.BooleanField(default=False)
can_use_custom_domain = models.BooleanField(default=False)
can_white_label = models.BooleanField(default=False)
can_api_access = models.BooleanField(default=False)
# ... existing can_manage_oauth_credentials

Part 3: Frontend - Simplified Create Modal

Replace current create modal with invitation form:

PlatformBusinesses.tsx - New "Invite Tenant" Modal

Fields:

  • Email address (required)
  • Suggested business name (optional)
  • Subscription tier dropdown
  • Custom limits section (toggle to override):
    • Max users (number input)
    • Max resources (number input)
  • Platform permissions (toggles):
    • Can manage OAuth credentials
    • Can accept payments
    • Can use custom domain
    • Can white-label
    • Can use API

Actions:

  • Send Invitation button
  • Shows success message with "Invitation sent to {email}"

Part 4: Frontend - Onboarding Wizard

New Page: /tenant-onboard (TenantOnboardPage.tsx)

Multi-step wizard triggered when owner clicks email link:

Step 1: Welcome & Account Setup

  • Display: "You've been invited to create a business on SmoothSchedule"
  • Show: Invited email, tier, what permissions they'll have
  • Form: Password, Confirm Password
  • First Name, Last Name

Step 2: Business Details

  • Business Name (pre-filled from invitation if provided)
  • Subdomain (auto-generated from name, editable)
  • Contact Email (pre-filled from invitation email)
  • Phone (optional)

Step 3: Payment Setup (conditional)

  • Only shown if can_accept_payments permission is true
  • Stripe Connect embedded onboarding
  • Skip option available

Step 4: Complete

  • Summary of what was created
  • "Go to Dashboard" button

Route Setup

// App.tsx
<Route path="/tenant-onboard" element={<TenantOnboardPage />} />

Part 5: Email Template

Subject: You're invited to create your business on SmoothSchedule

Hi,

{inviter_name} from SmoothSchedule has invited you to create your own business account.

Your plan: {tier}
Features included:
- Up to {max_users} team members
- Up to {max_resources} resources
{if can_accept_payments}- Accept online payments{/if}
{if can_use_custom_domain}- Custom domain support{/if}

Click the link below to get started:
{onboarding_url}

This invitation expires in 7 days.

Thanks,
The SmoothSchedule Team

Implementation Order

  1. Backend Model & Migration

    • Create TenantInvitation model
    • Add new permission fields to Tenant model
    • Run migrations
  2. Backend API Endpoints

    • Create/list/cancel invitation endpoints
    • Public token lookup and accept endpoints
    • Email sending
  3. Frontend - Update Create Modal

    • Replace current form with invitation form
    • Add invitation list/status to businesses view
  4. Frontend - Onboarding Wizard

    • Create TenantOnboardPage component
    • Multi-step form with validation
    • Conditional Stripe Connect step
    • Route configuration
  5. Testing

    • E2E test for full flow
    • Unit tests for API endpoints

Files to Create/Modify

Backend

  • smoothschedule/platform_admin/models.py (new - TenantInvitation)
  • smoothschedule/core/models.py (modify - add permissions)
  • smoothschedule/core/migrations/0007_*.py (new - permissions)
  • smoothschedule/platform_admin/migrations/0001_*.py (new - TenantInvitation)
  • smoothschedule/platform_admin/serializers.py (modify - add invitation serializers)
  • smoothschedule/platform_admin/views.py (modify - add invitation viewset)
  • smoothschedule/platform_admin/urls.py (modify - add invitation routes)

Frontend

  • src/api/platform.ts (modify - add invitation API)
  • src/hooks/usePlatform.ts (modify - add invitation hooks)
  • src/pages/platform/PlatformBusinesses.tsx (modify - replace create modal)
  • src/pages/TenantOnboardPage.tsx (new - onboarding wizard)
  • src/App.tsx (modify - add route)
  • src/i18n/locales/en.json (modify - add translations)