Files
resolutionflow/docs/archive/2026-02-12-admin-invite-user-mgmt.md
chihlasm 350c977eda feat: add procedural flows with intake forms, navigation, and seed templates
Adds a new "procedural" tree type for linear step-by-step project workflows
(domain controller setup, M365 onboarding, VPN config, etc). Includes intake
form builder, two-panel step navigation, variable resolution, procedural
exports, 3 seed templates, and UI rename from "Trees" to "Flows".

Also archives 19 implemented plan docs and creates deferred features backlog.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-14 04:13:52 -05:00

15 KiB
Raw Permalink Blame History

Admin Panel: Invite Codes + User Management Enhancement

Date: 2026-02-12 Status: Proposed — Combined Plan


Summary

Enhance admin capabilities to:

  1. Create invite codes tied to plans (free, pro, team) with optional trial durations.
  2. Send invite emails via Resend (best-effort, non-blocking).
  3. Apply invite-assigned plan/trial on registration.
  4. Give admins a detailed user management view with subscription, session, and audit context.
  5. Support admin subscription actions (change plan, extend/start trial).
  6. Auto-downgrade expired trials during authenticated access checks.

Goals

  • Remove manual invite-code sharing workflow.
  • Support controlled beta onboarding with plan + trial at invite level.
  • Enable operational admin workflows for account/subscription lifecycle.
  • Keep backward compatibility where practical and avoid unsafe breaking changes.

Non-Goals

  • Stripe billing workflow redesign.
  • Full historical pagination for user-detail sessions/audits in this iteration.
  • Rework of account invite (/accounts/me/invites) flow.

Key Decisions Locked

Decision Choice Rationale
Invite API path Standardize on /invites Already in use (router = APIRouter(prefix="/invites")). Update CLAUDE.md which incorrectly references /invite-codes.
User detail endpoint Enrich existing GET /admin/users/{id} One endpoint, richer response. No reason for admin to get a "lite" version.
Invite email matching Advisory only (no strict enforcement) The invite code itself is the security gate. Email is for admin tracking. Strict matching creates friction during beta.
Invite plan/trial application Applies whenever a valid invite code is provided, even if REQUIRE_INVITE_CODE=false Ensures plan/trial always carries through regardless of registration policy.
Trial duration bounds 190 days 90 days covers any realistic beta period. Protects against typos. Admin can always extend after expiry.
Extend trial behavior May convert non-trialing subscriptions to trialing Admin should have maximum control. Covers scenarios like forgotten trial assignment or second chances.
User detail payload Recent summaries (latest 10 sessions + 10 audit logs) + total counts Balances useful at-a-glance admin view with response performance. Full history via future paginated endpoints.

Phase 1: Database Migration (030)

New file: backend/alembic/versions/030_enhance_invite_codes.py (down revision 029)

Add columns to invite_codes:

  • email: String(255), nullable, indexed.
  • assigned_plan: String(50), non-null, server default 'free'.
  • trial_duration_days: Integer, nullable.
  • email_sent_at: DateTime(timezone=True), nullable.

Database constraints:

  • assigned_plan IN ('free', 'pro', 'team').
  • trial_duration_days IS NULL OR trial_duration_days BETWEEN 1 AND 90.
  • Consistency guard: assigned_plan = 'free' implies trial_duration_days IS NULL.

Update: backend/app/models/invite_code.py

  • Add mapped columns for all new fields.
  • Add computed properties:
    • has_trial: booltrial_duration_days is not None and > 0
    • email_sent: boolemail_sent_at is not None

Phase 2: Resend Email Integration

New file: backend/app/core/email.py

  • EmailService class with send_invite_email(to_email, code, plan, trial_days, signup_url) -> bool.
  • Returns False if RESEND_API_KEY not set (graceful degradation).
  • Catches provider failures, returns False, logs warning/error.
  • Never blocks invite creation (best-effort delivery).

New file: backend/app/templates/invite_email.html

  • Branded HTML email: monochrome design, ResolutionFlow logo, CTA button.
  • Shows invite code, plan name, trial duration if applicable, signup link.

Update: backend/app/core/config.py

  • Add RESEND_API_KEY: Optional[str] = None
  • Add FROM_EMAIL: str = "ResolutionFlow <invites@resolutionflow.com>"
  • Add email_enabled computed property.

Update: backend/requirements.txt — add resend package.

Env vars required: RESEND_API_KEY, FROM_EMAIL (has default).

Prerequisite: DNS records (SPF, DKIM) must be configured in Resend for resolutionflow.com domain before production email delivery will work.


Phase 3: Backend Schemas + Endpoints

3a. Invite Code Schemas

Update: backend/app/schemas/invite_code.py

InviteCodeCreate — add fields:

  • email: Optional[EmailStr]
  • assigned_plan: Literal['free', 'pro', 'team'] = 'free'
  • trial_duration_days: Optional[int] (validated 190)

InviteCodeResponse — add fields:

  • email, assigned_plan, trial_duration_days, email_sent_at
  • Computed flags: has_trial, email_sent

3b. Invite Endpoints

Update: backend/app/api/endpoints/invite.py

  • POST /invites — accept new fields (email, assigned_plan, trial_duration_days).
    • Create invite with plan/trial/email metadata.
    • If email provided, attempt send via EmailService.
    • On send success: set email_sent_at.
    • On send failure: invite still returns 201.
    • Add audit log entry for invite creation with delivery result.
  • Keep GET /invites, DELETE /invites/{code}, GET /invites/validate/{code} behavior compatible.

3c. Registration Plan Assignment

Update: backend/app/api/endpoints/auth.py (registration endpoint, around lines 178183)

  • If invite code is supplied and valid, load it and apply invite plan/trial regardless of REQUIRE_INVITE_CODE setting.
  • For non-account-invite registrations:
    • Create subscription with plan = invite_code.assigned_plan (fallback 'free').
    • If trial_duration_days is set:
      • status = 'trialing'
      • current_period_start = now
      • current_period_end = now + trial_duration_days
    • Else: status = 'active'.
  • Preserve existing account-invite join flow behavior.
  • Mark invite as used after user creation.

3d. Admin Subscription + User Detail Endpoints

Update: backend/app/api/endpoints/admin.py

Enrich GET /admin/users/{id} response to include:

  • Base user fields (name, email, role, active status).
  • Account summary (account name, display code).
  • Subscription summary (plan, status, trial end date).
  • Recent sessions: latest 10 + total count.
  • Recent audit logs: latest 10 + total count.
  • Invite code used summary (code, assigned plan, who created it).

Add new endpoints:

  • PUT /admin/users/{id}/subscription/plan — change user's plan.
  • PUT /admin/users/{id}/subscription/extend-trial — add days to trial, or convert to trialing if not already.

Both endpoints should create audit log entries.

3e. Trial Expiry Check

Update: backend/app/api/deps.py — in get_current_active_user

  • Check account subscription status.
  • If status = 'trialing' and current_period_end < now:
    • Set plan = 'free', status = 'active'.
    • Clear/normalize trial period fields.
    • Commit before returning user.

Note: This is a lightweight login-time check. Users with active JWT sessions will retain access until token refresh. Acceptable for beta; revisit if stricter enforcement needed later.


Phase 4: Backend Schema Additions

Check first: Verify whether backend/app/schemas/subscription.py already exists. If it does, extend it. If not, create it.

Schemas needed in backend/app/schemas/subscription.py:

  • SubscriptionPlanUpdate — for plan change requests.
  • ExtendTrialRequest — for trial extension requests.
  • SubscriptionResponse — for subscription state in responses.

New file: backend/app/schemas/user_detail.py

  • AccountSummary
  • SessionSummary
  • AuditLogSummary
  • InviteCodeUsedSummary
  • UserDetailResponse (superset response for enriched /admin/users/{id})

Phase 5: Frontend Types + API Client

Update: frontend/src/types/admin.ts

  • Enhanced InviteCodeResponse with email, plan, trial, email-sent fields.
  • New types: UserDetail, SubscriptionDetail, SessionSummary, AuditLogSummary, AccountSummary.

Update: frontend/src/api/admin.ts

  • Ensure invite endpoints target /invites (not /invite-codes).
  • Enhance createInviteCode payload with new fields.
  • Add: getUserDetail(userId), updateUserSubscriptionPlan(userId, plan), extendUserTrial(userId, days).

Phase 6: Frontend — Enhanced Invite Codes Page

Update: frontend/src/pages/admin/InviteCodesPage.tsx

Create form additions:

  • Email input (optional, validated).
  • Plan selector dropdown (Free / Pro / Team).
  • Trial duration input (number of days, shown only when plan ≠ free).

Table additions:

  • "Recipient" column (email or "—").
  • "Plan" column with badge.
  • "Trial" column (days or "—").
  • "Email Sent" indicator.

Preserve existing create/copy/delete actions and status badges.


Phase 7: Frontend — User Detail Page

New file: frontend/src/pages/admin/UserDetailPage.tsx

Sections:

  • Header — name, email, role badges, active status.
  • Account & Subscription card — plan, status, trial end date, account display code.
  • Admin Actions card — Change Role, Change Plan, Extend/Start Trial, Activate/Deactivate (modal-based).
  • Recent Sessions tab — tree name, started, completed, outcome.
  • Audit Logs tab — action, resource, timestamp, expandable details.
  • Invite Code card — code used, plan assigned, who created it.

Update: frontend/src/router.tsx — add route admin/users/:userId.

Update: frontend/src/pages/admin/UsersPage.tsx — make user rows clickable to navigate to detail page. Ensure action menu clicks (dropdowns, buttons) don't trigger row navigation.


File Inventory

Files to Create

File Phase
backend/alembic/versions/030_enhance_invite_codes.py 1
backend/app/core/email.py 2
backend/app/templates/invite_email.html 2
backend/app/schemas/subscription.py (verify doesn't exist first) 4
backend/app/schemas/user_detail.py 4
frontend/src/pages/admin/UserDetailPage.tsx 7

Files to Modify

File Phase What Changes
backend/app/models/invite_code.py 1 Add new columns + computed properties
backend/app/core/config.py 2 Add RESEND_API_KEY, FROM_EMAIL, email_enabled
backend/requirements.txt 2 Add resend package
backend/app/schemas/invite_code.py 3a Add email, plan, trial fields to create/response
backend/app/api/endpoints/invite.py 3b Accept new fields, send email, audit log
backend/app/api/endpoints/auth.py 3c Apply invite plan/trial on registration (lines ~178183)
backend/app/api/endpoints/admin.py 3d Enrich user detail, add subscription endpoints
backend/app/api/deps.py 3e Trial expiry check in get_current_active_user
frontend/src/types/admin.ts 5 Enhanced invite + new detail types
frontend/src/api/admin.ts 5 New API functions, fix invite path
frontend/src/pages/admin/InviteCodesPage.tsx 6 Form + table enhancements
frontend/src/pages/admin/UsersPage.tsx 7 Clickable rows → detail page
frontend/src/router.tsx 7 Add user detail route

Also Update (Housekeeping)

File What Changes
CLAUDE.md Fix invite codes endpoint reference from /invite-codes to /invites

API / Interface Changes

Modified Endpoints

  • POST /invites — new request fields: email, assigned_plan, trial_duration_days.
  • GET /invites — new response fields: email, assigned_plan, trial_duration_days, email_sent_at, has_trial, email_sent.
  • GET /admin/users/{id} — enriched response with account/subscription/recent activity details.

New Endpoints

  • PUT /admin/users/{id}/subscription/plan
  • PUT /admin/users/{id}/subscription/extend-trial

Implementation Order

  1. Migration 030 (invite code fields)
  2. Model update (invite_code.py)
  3. Resend integration (email.py, config.py, template, requirements.txt)
  4. Backend schemas (invite_code, subscription, user_detail)
  5. Backend API (invite.py, auth.py, admin.py, deps.py)
  6. Backend tests
  7. Frontend types + API client
  8. Frontend invite codes page enhancement
  9. Frontend user detail page
  10. End-to-end testing

Test Plan

Backend Tests

  1. Invite create with assigned_plan + trial_duration_days persists correctly.
  2. Invite create with email — Resend success sets email_sent_at.
  3. Invite create with email — Resend failure still returns 201, does not set email_sent_at.
  4. Registration with invite applies correct subscription plan/status/period fields.
  5. Registration with optional invite (REQUIRE_INVITE_CODE=false) still applies plan/trial when code provided.
  6. Expired trial auto-downgrades on authenticated request.
  7. Admin plan update endpoint updates subscription + creates audit log.
  8. Admin extend-trial endpoint converts/extends correctly + creates audit log.
  9. Enriched GET /admin/users/{id} returns expected shape and list-size caps (10 sessions, 10 audit logs).
  10. Trial duration validation rejects values outside 190 range.
  11. Free plan invite rejects trial_duration_days (consistency guard).

Frontend Verification

  1. Create invite with email + plan + trial from admin UI.
  2. Confirm invite table renders recipient/plan/trial/email-sent columns.
  3. Open user detail from users table (click row).
  4. Change plan and extend trial from detail page.
  5. Confirm updated values refresh in UI.
  6. cd frontend && npm run build passes.

Commands

cd backend && pytest --override-ini="addopts="
cd frontend && npm run build

Risks and Mitigations

Risk Mitigation
Endpoint drift (/invite-codes vs /invites) Update CLAUDE.md and admin API client. Verify all admin invite calls use /invites.
Subscription side-effects in auth/deps Centralize trial-expiry logic. Cover with targeted tests.
Payload growth for user detail Cap related arrays at 10 items, include total counts.
Email provider outages Best-effort send with logging. Invite creation never fails due to email.
DNS not configured for Resend Document as prerequisite. Email gracefully degrades when API key missing.
subscription.py may already exist Verify before creating. Extend if present, create if not.
JWT session outlives trial expiry Acceptable for beta. Document as known limitation.

Rollout

  1. Deploy migration and backend changes.
  2. Validate admin invite creation and registration path in staging.
  3. Deploy frontend with new invite/user-detail UI.
  4. Monitor audit logs and invite email delivery behavior post-release.

Assumptions

  • Existing admin access control (require_admin) remains unchanged.
  • Plan limits for free/pro/team are already configured in plan_limits.
  • No mandatory template engine addition is required for email template rendering.
  • Alembic env.py already imports InviteCode model (per LESSONS-LEARNED.md).