feat(admin): allow setting owner when creating an account

Adds optional owner_email field to the Create Account modal. Superadmin
can specify an existing user's email to assign as account owner at
creation time. Backend 404s with a clear message if the email is unknown.
Error detail now surfaces to the toast instead of a generic message.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
chihlasm
2026-04-14 14:30:23 +00:00
parent c5b8229ef6
commit 0ed5977fee
4 changed files with 34 additions and 5 deletions

View File

@@ -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)

View File

@@ -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):

View File

@@ -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() {
<option value="team">Team</option>
</select>
</div>
<div>
<label className="mb-1 block text-sm font-medium text-foreground">
Owner Email <span className="text-muted-foreground font-normal">(optional)</span>
</label>
<Input
type="email"
value={createAccountForm.owner_email}
onChange={(e) => setCreateAccountForm((form) => ({ ...form, owner_email: e.target.value }))}
placeholder="owner@example.com"
/>
<p className="mt-1 text-xs text-muted-foreground">Must be an existing user.</p>
</div>
</div>
</Modal>

View File

@@ -114,6 +114,7 @@ export interface AdminAccountDetailResponse extends AdminAccountListItem {
export interface AdminAccountCreate {
name: string
plan: 'free' | 'pro' | 'team'
owner_email?: string
}
export interface AdminAccountUpdate {