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>
15 KiB
Admin Panel: Invite Codes + User Management Enhancement
Date: 2026-02-12 Status: Proposed — Combined Plan
Summary
Enhance admin capabilities to:
- Create invite codes tied to plans (
free,pro,team) with optional trial durations. - Send invite emails via Resend (best-effort, non-blocking).
- Apply invite-assigned plan/trial on registration.
- Give admins a detailed user management view with subscription, session, and audit context.
- Support admin subscription actions (change plan, extend/start trial).
- 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 | 1–90 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'impliestrial_duration_days IS NULL.
Update: backend/app/models/invite_code.py
- Add mapped columns for all new fields.
- Add computed properties:
has_trial: bool—trial_duration_days is not None and > 0email_sent: bool—email_sent_at is not None
Phase 2: Resend Email Integration
New file: backend/app/core/email.py
EmailServiceclass withsend_invite_email(to_email, code, plan, trial_days, signup_url) -> bool.- Returns
FalseifRESEND_API_KEYnot 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_enabledcomputed 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 1–90)
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 178–183)
- If invite code is supplied and valid, load it and apply invite plan/trial regardless of
REQUIRE_INVITE_CODEsetting. - For non-account-invite registrations:
- Create subscription with
plan = invite_code.assigned_plan(fallback'free'). - If
trial_duration_daysis set:status = 'trialing'current_period_start = nowcurrent_period_end = now + trial_duration_days
- Else:
status = 'active'.
- Create subscription with
- 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'andcurrent_period_end < now:- Set
plan = 'free',status = 'active'. - Clear/normalize trial period fields.
- Commit before returning user.
- Set
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
AccountSummarySessionSummaryAuditLogSummaryInviteCodeUsedSummaryUserDetailResponse(superset response for enriched/admin/users/{id})
Phase 5: Frontend Types + API Client
Update: frontend/src/types/admin.ts
- Enhanced
InviteCodeResponsewith 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
createInviteCodepayload 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 ~178–183) |
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/planPUT /admin/users/{id}/subscription/extend-trial
Implementation Order
- Migration 030 (invite code fields)
- Model update (invite_code.py)
- Resend integration (email.py, config.py, template, requirements.txt)
- Backend schemas (invite_code, subscription, user_detail)
- Backend API (invite.py, auth.py, admin.py, deps.py)
- Backend tests
- Frontend types + API client
- Frontend invite codes page enhancement
- Frontend user detail page
- End-to-end testing
Test Plan
Backend Tests
- Invite create with
assigned_plan+trial_duration_dayspersists correctly. - Invite create with email — Resend success sets
email_sent_at. - Invite create with email — Resend failure still returns 201, does not set
email_sent_at. - Registration with invite applies correct subscription plan/status/period fields.
- Registration with optional invite (
REQUIRE_INVITE_CODE=false) still applies plan/trial when code provided. - Expired trial auto-downgrades on authenticated request.
- Admin plan update endpoint updates subscription + creates audit log.
- Admin extend-trial endpoint converts/extends correctly + creates audit log.
- Enriched
GET /admin/users/{id}returns expected shape and list-size caps (10 sessions, 10 audit logs). - Trial duration validation rejects values outside 1–90 range.
- Free plan invite rejects trial_duration_days (consistency guard).
Frontend Verification
- Create invite with email + plan + trial from admin UI.
- Confirm invite table renders recipient/plan/trial/email-sent columns.
- Open user detail from users table (click row).
- Change plan and extend trial from detail page.
- Confirm updated values refresh in UI.
cd frontend && npm run buildpasses.
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
- Deploy migration and backend changes.
- Validate admin invite creation and registration path in staging.
- Deploy frontend with new invite/user-detail UI.
- Monitor audit logs and invite email delivery behavior post-release.
Assumptions
- Existing admin access control (
require_admin) remains unchanged. - Plan limits for
free/pro/teamare already configured inplan_limits. - No mandatory template engine addition is required for email template rendering.
- Alembic
env.pyalready importsInviteCodemodel (per LESSONS-LEARNED.md).