# 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 "` - `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`. 3. Registration with invite applies correct subscription plan/status/period fields. 4. Registration with optional invite (`REQUIRE_INVITE_CODE=false`) still applies plan/trial. 5. Expired trial auto-downgrades on authenticated request. 6. Admin plan update endpoint updates subscription + audit logs. 7. Admin extend-trial endpoint converts/extends correctly + audit logs. 8. 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.