Files
resolutionflow/docs/archive/2026-02-12-admin-invite-user-management-enhancement.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

9.4 KiB

Admin Panel: Invite Codes + User Management Enhancement

Date: 2026-02-12
Status: Proposed

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/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

  • Invite API path standardization: use /invites (frontend and backend aligned).
  • User detail endpoint: enrich existing GET /admin/users/{id}.
  • Invite email is advisory only (no strict email-match enforcement at registration).
  • Invite plan/trial applies whenever a valid invite code is provided, even if REQUIRE_INVITE_CODE=false.
  • Trial duration bounds: 1..90 days.
  • Extend trial endpoint may convert non-trialing subscriptions to trialing.
  • User detail payload includes recent summaries (latest 10 sessions + latest 10 audit logs) plus total counts.

Scope by Phase

Phase 1: Database Migration (030)

Create backend/alembic/versions/030_enhance_invite_codes.py (down revision 029).

Add 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.

Constraints:

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

Update model backend/app/models/invite_code.py:

  • Add mapped columns above.
  • Add computed properties:
    • has_trial: bool (trial_duration_days is not None and > 0)
    • email_sent: bool (email_sent_at is not None)

Phase 2: Resend Email Integration

Create backend/app/core/email.py:

  • EmailService.send_invite_email(to_email, code, plan, trial_days, signup_url) -> bool.
  • Returns False if RESEND_API_KEY missing.
  • Catches provider failures and returns False (logs warning/error).
  • Never blocks invite creation.

Create backend/app/templates/invite_email.html:

  • Monochrome branded HTML.
  • Invite code, plan, optional trial text, signup CTA button.

Update backend/app/core/config.py:

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

Update backend/requirements.txt:

  • Add resend package.

Phase 3: Backend Schemas + Endpoints

Invite code schemas

Update backend/app/schemas/invite_code.py:

  • InviteCodeCreate adds:
    • email: Optional[EmailStr]
    • assigned_plan: Literal['free','pro','team'] = 'free'
    • trial_duration_days: Optional[int] (1..90)
  • InviteCodeResponse adds:
    • email, assigned_plan, trial_duration_days, email_sent_at
    • computed flags has_trial, email_sent.

Invite endpoints

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

  • POST /invites accepts new fields.
  • Creates invite with plan/trial/email metadata.
  • If email provided, attempts send:
    • on success: set email_sent_at.
    • on failure: invite still returns 201.
  • Add audit log for invite creation with delivery result.
  • Keep GET /invites, DELETE /invites/{code}, GET /invites/validate/{code} behavior compatible.

Registration plan assignment

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

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

Admin subscription + detail endpoints

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

  • Enrich GET /admin/users/{id} response:
    • base user fields
    • account summary
    • subscription summary
    • recent sessions (10) + total count
    • recent audit logs (10) + total count
    • invite code used summary
  • Add:
    • PUT /admin/users/{id}/subscription/plan
    • PUT /admin/users/{id}/subscription/extend-trial

Trial expiry check

Update backend/app/api/deps.py:

  • In get_current_active_user, check account subscription.
  • If status='trialing' and expired, auto-downgrade:
    • plan='free', status='active'
    • clear/normalize trial period fields
    • commit before returning user.

Phase 4: Backend Schema Additions

Use existing file backend/app/schemas/subscription.py (do not duplicate):

  • Add SubscriptionPlanUpdate.
  • Add ExtendTrialRequest.
  • Keep/extend SubscriptionResponse as needed.

Create backend/app/schemas/user_detail.py:

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

Phase 5: Frontend Types + API Client

Update frontend/src/types/admin.ts:

  • Invite response fields for email/plan/trial/email-sent metadata.
  • New detail types:
    • UserDetail
    • SubscriptionDetail
    • SessionSummary
    • AuditLogSummary
    • AccountSummary.

Update frontend/src/api/admin.ts:

  • Switch invite endpoints to /invites.
  • Enhance createInviteCode payload.
  • Add:
    • getUserDetail(userId)
    • updateUserSubscriptionPlan(userId, plan)
    • extendUserTrial(userId, days).

Phase 6: Frontend Invite Codes Page

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

  • Create form fields:
    • optional email
    • plan selector (Free/Pro/Team)
    • trial days input when plan != free
  • Table additions:
    • recipient
    • plan badge
    • trial column
    • email sent indicator
  • Preserve existing create/copy/delete actions and status badges.

Phase 7: Frontend User Detail Page

Create frontend/src/pages/admin/UserDetailPage.tsx:

  • Header: name/email/role/active.
  • Account & subscription card.
  • Admin actions:
    • change role
    • change plan
    • extend/start trial
    • activate/deactivate
  • Tabs:
    • recent sessions
    • audit logs
  • Invite code card:
    • code, assigned plan, creator.

Update frontend/src/router.tsx:

  • Add route admin/users/:userId.

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

  • Make rows navigate to detail.
  • Ensure action menu clicks do not trigger row navigation.

API / Interface Changes

Modified

  • 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.

Added

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

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.
  • Resend failure still returns 201 and does not set email_sent_at.
  1. Registration with invite applies correct subscription plan/status/period fields.
  2. Registration with optional invite (REQUIRE_INVITE_CODE=false) still applies plan/trial.
  3. Expired trial auto-downgrades on authenticated request.
  4. Admin plan update endpoint updates subscription + audit logs.
  5. Admin extend-trial endpoint converts/extends correctly + audit logs.
  6. Enriched GET /admin/users/{id} returns expected shape and list-size caps.

Frontend verification

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

Commands

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

Risks and Mitigations

  • Endpoint drift (/invite-codes vs /invites): update admin API client and validate all admin invite calls.
  • Subscription side-effects in auth/deps: centralize trial-expiry logic and cover with tests.
  • Payload growth for user detail: cap related arrays at 10 and include totals.
  • Email provider outages: best-effort send with logging, no invite creation failure.

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 this email template rendering path.