diff --git a/backend/app/api/endpoints/admin.py b/backend/app/api/endpoints/admin.py index eb2d1280..ae606fb5 100644 --- a/backend/app/api/endpoints/admin.py +++ b/backend/app/api/endpoints/admin.py @@ -431,10 +431,19 @@ async def create_account( current_user: Annotated[User, Depends(require_admin)], ): """Create a new account without requiring an initial user.""" + owner_id = None + if data.owner_email: + result = await db.execute(select(User).where(User.email == data.owner_email.strip())) + owner = result.scalar_one_or_none() + if not owner: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"No user found with email '{data.owner_email}'") + owner_id = owner.id + display_code = await _generate_unique_display_code(db) new_account = Account( name=data.name.strip(), display_code=display_code, + owner_id=owner_id, ) db.add(new_account) await db.flush() @@ -448,7 +457,7 @@ async def create_account( await log_audit( db, current_user.id, "account.create_admin", "account", new_account.id, - {"name": new_account.name, "plan": data.plan}, + {"name": new_account.name, "plan": data.plan, "owner_email": data.owner_email}, ) await db.commit() return await _get_account_detail_payload(new_account.id, db) diff --git a/backend/app/schemas/admin.py b/backend/app/schemas/admin.py index fdf5eb6a..fb995d26 100644 --- a/backend/app/schemas/admin.py +++ b/backend/app/schemas/admin.py @@ -126,6 +126,7 @@ class AdminAccountDetailResponse(AdminAccountListItem): class AdminAccountCreate(BaseModel): name: str = Field(..., min_length=1, max_length=255) plan: Literal["free", "pro", "team"] = "free" + owner_email: Optional[str] = Field(None, description="Email of an existing user to set as owner") class AdminAccountUpdate(BaseModel): diff --git a/frontend/src/pages/admin/AccountsPage.tsx b/frontend/src/pages/admin/AccountsPage.tsx index 87acb69e..39eae8f1 100644 --- a/frontend/src/pages/admin/AccountsPage.tsx +++ b/frontend/src/pages/admin/AccountsPage.tsx @@ -88,7 +88,7 @@ export function UsersPage() { }) const [inviteLoading, setInviteLoading] = useState(false) const [showCreateAccountModal, setShowCreateAccountModal] = useState(false) - const [createAccountForm, setCreateAccountForm] = useState({ name: '', plan: 'free' as 'free' | 'pro' | 'team' }) + const [createAccountForm, setCreateAccountForm] = useState({ name: '', plan: 'free' as 'free' | 'pro' | 'team', owner_email: '' }) const [createAccountLoading, setCreateAccountLoading] = useState(false) const fetchAccounts = useCallback(async () => { @@ -223,13 +223,19 @@ export function UsersPage() { const created = await adminApi.createAccount({ name: createAccountForm.name.trim(), plan: createAccountForm.plan, + owner_email: createAccountForm.owner_email.trim() || undefined, }) toast.success('Account created') setShowCreateAccountModal(false) - setCreateAccountForm({ name: '', plan: 'free' }) + setCreateAccountForm({ name: '', plan: 'free', owner_email: '' }) navigate(`/admin/accounts/${created.id}`) - } catch { - toast.error('Failed to create account') + } catch (err: unknown) { + if (err && typeof err === 'object' && 'response' in err) { + const axiosErr = err as { response?: { data?: { detail?: string } } } + toast.error(axiosErr.response?.data?.detail || 'Failed to create account') + } else { + toast.error('Failed to create account') + } } finally { setCreateAccountLoading(false) } @@ -634,6 +640,18 @@ export function UsersPage() { +
+ + setCreateAccountForm((form) => ({ ...form, owner_email: e.target.value }))} + placeholder="owner@example.com" + /> +

Must be an existing user.

+
diff --git a/frontend/src/types/admin.ts b/frontend/src/types/admin.ts index d8de4f96..33747537 100644 --- a/frontend/src/types/admin.ts +++ b/frontend/src/types/admin.ts @@ -114,6 +114,7 @@ export interface AdminAccountDetailResponse extends AdminAccountListItem { export interface AdminAccountCreate { name: string plan: 'free' | 'pro' | 'team' + owner_email?: string } export interface AdminAccountUpdate {