feat: admin invite codes with plan assignment + user detail page
- Migration 030: add email, assigned_plan, trial_duration_days, email_sent_at
to invite_codes with CHECK constraints
- Resend email integration (graceful degradation when API key not set)
- Invite codes now support plan assignment (free/pro/team) and trial duration (1-90 days)
- Registration applies invite code plan/trial to new subscription
- Auto-downgrade expired trials on authenticated access
- Enriched GET /admin/users/{id} with account, subscription, sessions, audit logs
- New endpoints: PUT /admin/users/{id}/subscription/plan and extend-trial
- Frontend: enhanced invite codes page with email, plan, trial fields
- Frontend: new user detail page at /admin/users/:userId
- Fixed API path drift: /invite-codes -> /invites
- 11 new backend tests, 416 total passing
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -13,6 +13,9 @@ import type {
|
||||
AccountFeatureOverrideCreate,
|
||||
AdminCategory,
|
||||
GlobalCategoryCreate,
|
||||
InviteCodeResponse,
|
||||
InviteCodeCreateRequest,
|
||||
UserDetailResponse,
|
||||
} from '@/types/admin'
|
||||
|
||||
export const adminApi = {
|
||||
@@ -38,13 +41,21 @@ export const adminApi = {
|
||||
moveUserAccount: (id: string, display_code: string) =>
|
||||
api.put(`/admin/users/${id}/move-account`, { display_code }).then(r => r.data),
|
||||
|
||||
// Invite Codes (existing endpoints)
|
||||
// Users - detail + subscription
|
||||
getUserDetail: (id: string) =>
|
||||
api.get<UserDetailResponse>(`/admin/users/${id}`).then(r => r.data),
|
||||
updateUserSubscriptionPlan: (id: string, plan: string) =>
|
||||
api.put(`/admin/users/${id}/subscription/plan`, { plan }).then(r => r.data),
|
||||
extendUserTrial: (id: string, days: number) =>
|
||||
api.put(`/admin/users/${id}/subscription/extend-trial`, { days }).then(r => r.data),
|
||||
|
||||
// Invite Codes
|
||||
listInviteCodes: (params?: Record<string, unknown>) =>
|
||||
api.get('/invite-codes', { params }).then(r => r.data),
|
||||
createInviteCode: (data?: { expires_at?: string }) =>
|
||||
api.post('/invite-codes', data || {}).then(r => r.data),
|
||||
deleteInviteCode: (id: string) =>
|
||||
api.delete(`/invite-codes/${id}`),
|
||||
api.get<InviteCodeResponse[]>('/invites', { params }).then(r => r.data),
|
||||
createInviteCode: (data: InviteCodeCreateRequest = {}) =>
|
||||
api.post<InviteCodeResponse>('/invites', data).then(r => r.data),
|
||||
deleteInviteCode: (code: string) =>
|
||||
api.delete(`/invites/${code}`),
|
||||
|
||||
// Audit Logs
|
||||
listAuditLogs: (params?: Record<string, unknown>) =>
|
||||
|
||||
Reference in New Issue
Block a user