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:
@@ -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)
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -114,6 +114,7 @@ export interface AdminAccountDetailResponse extends AdminAccountListItem {
|
||||
export interface AdminAccountCreate {
|
||||
name: string
|
||||
plan: 'free' | 'pro' | 'team'
|
||||
owner_email?: string
|
||||
}
|
||||
|
||||
export interface AdminAccountUpdate {
|
||||
|
||||
Reference in New Issue
Block a user