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>
This commit is contained in:
227
frontend/TENANT_ONBOARDING_PLAN.md
Normal file
227
frontend/TENANT_ONBOARDING_PLAN.md
Normal file
@@ -0,0 +1,227 @@
|
||||
# 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`)
|
||||
|
||||
```python
|
||||
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:
|
||||
|
||||
```python
|
||||
# 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
|
||||
|
||||
```tsx
|
||||
// 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)
|
||||
Reference in New Issue
Block a user