diff --git a/backend/users/models.py b/backend/users/models.py
index aa73365..dffab92 100644
--- a/backend/users/models.py
+++ b/backend/users/models.py
@@ -4,7 +4,7 @@ from typing import List, Optional
# --- Request / Response schemas ---
-class UserCreate(BaseModel):
+class UserProfile(BaseModel):
email: str = ""
display_name: str = ""
photo_url: str = ""
@@ -17,6 +17,10 @@ class UserCreate(BaseModel):
quickSettingsPIN: str = ""
+class UserCreate(UserProfile):
+ password: str = "" # request-only — used to create the Firebase Auth account, never persisted to Firestore or echoed back
+
+
class UserUpdate(BaseModel):
email: Optional[str] = None
display_name: Optional[str] = None
@@ -29,7 +33,7 @@ class UserUpdate(BaseModel):
quickSettingsPIN: Optional[str] = None
-class UserInDB(UserCreate):
+class UserInDB(UserProfile):
id: str
created_time: str = ""
lastActive: str = ""
diff --git a/backend/users/service.py b/backend/users/service.py
index 8175b93..4695ba3 100644
--- a/backend/users/service.py
+++ b/backend/users/service.py
@@ -84,12 +84,28 @@ def get_user(user_doc_id: str) -> UserInDB:
def create_user(data: UserCreate) -> UserInDB:
- """Create a new user document in Firestore."""
+ """Create a new user: a Firebase Auth account (so they can log into the app
+ immediately) plus the matching Firestore profile document."""
+ if not data.password or len(data.password) < 6:
+ raise ValidationError("Password must be at least 6 characters.")
+ if not data.email:
+ raise ValidationError("Email is required.")
+
db = get_db()
- doc_data = data.model_dump()
+ doc_data = data.model_dump(exclude={"password"})
doc_data["friendsList"] = []
doc_data["friendsInvited"] = []
+ try:
+ firebase_user = firebase_auth.create_user(
+ email=data.email,
+ password=data.password,
+ display_name=data.display_name or None,
+ )
+ except firebase_auth.EmailAlreadyExistsError:
+ raise ValidationError(f"A user with email {data.email} already exists.")
+ doc_data["uid"] = firebase_user.uid
+
_, doc_ref = db.collection(COLLECTION).add(doc_data)
return UserInDB(id=doc_ref.id, **doc_data)
@@ -111,13 +127,20 @@ def update_user(user_doc_id: str, data: UserUpdate) -> UserInDB:
def delete_user(user_doc_id: str) -> None:
- """Delete a user document from Firestore."""
+ """Delete a user document from Firestore, plus their Firebase Auth account if one exists."""
db = get_db()
doc_ref = db.collection(COLLECTION).document(user_doc_id)
doc = doc_ref.get()
if not doc.exists:
raise NotFoundError("User")
+ uid = doc.to_dict().get("uid", "")
+ if uid:
+ try:
+ firebase_auth.delete_user(uid)
+ except firebase_auth.UserNotFoundError:
+ pass
+
doc_ref.delete()
diff --git a/frontend/src/modals/bellcloud/users/CreateUserModal.jsx b/frontend/src/modals/bellcloud/users/CreateUserModal.jsx
new file mode 100644
index 0000000..1d5ba4d
--- /dev/null
+++ b/frontend/src/modals/bellcloud/users/CreateUserModal.jsx
@@ -0,0 +1,97 @@
+// frontend/src/modals/bellcloud/users/CreateUserModal.jsx
+// Create a new app user (Firebase Auth account + Firestore profile) from anywhere in the console.
+
+import { useState, useEffect } from 'react'
+import api from '@/lib/api'
+import Modal from '@/components/ui/Modal'
+import Button from '@/components/ui/Button'
+import FormField from '@/components/ui/FormField'
+
+export default function CreateUserModal({ open, onClose, onCreated }) {
+ const [email, setEmail] = useState('')
+ const [displayName, setDisplayName] = useState('')
+ const [password, setPassword] = useState('')
+ const [saving, setSaving] = useState(false)
+ const [error, setError] = useState('')
+
+ useEffect(() => {
+ if (!open) return
+ setEmail(''); setDisplayName(''); setPassword(''); setError('')
+ }, [open])
+
+ const handleCreate = async () => {
+ setError('')
+ if (!email || !displayName || !password) {
+ setError('Email, display name, and password are required.')
+ return
+ }
+ if (password.length < 6) {
+ setError('Password must be at least 6 characters.')
+ return
+ }
+ setSaving(true)
+ try {
+ const created = await api.post('/users', {
+ email,
+ display_name: displayName,
+ password,
+ status: 'active',
+ })
+ onCreated(created)
+ } catch (err) {
+ setError(err.message || 'Failed to create user.')
+ } finally {
+ setSaving(false)
+ }
+ }
+
+ return (
+
+
+
+
+ }
+ >
+
+
setEmail(e.target.value)}
+ required
+ placeholder="user@example.com"
+ autoFocus
+ />
+ setDisplayName(e.target.value)}
+ required
+ placeholder="e.g. Jane Smith"
+ />
+ setPassword(e.target.value)}
+ required
+ placeholder="Min. 6 characters"
+ hint="Used to sign into the mobile app immediately."
+ />
+ {error && (
+ {error}
+ )}
+
+
+ )
+}
diff --git a/frontend/src/pages/bellcloud/users/UserForm.jsx b/frontend/src/pages/bellcloud/users/UserForm.jsx
index e9660d8..d8ea9b4 100644
--- a/frontend/src/pages/bellcloud/users/UserForm.jsx
+++ b/frontend/src/pages/bellcloud/users/UserForm.jsx
@@ -71,7 +71,7 @@ function StatusToggle({ value, onChange }) {
function PhotoField({ value, onChange, userId }) {
const fileInputRef = useRef(null)
- const toast = useToast()
+ const { toast } = useToast()
const [uploading, setUploading] = useState(false)
const [hovered, setHovered] = useState(false)
@@ -192,7 +192,7 @@ function EyeToggle({ show, onToggle }) {
}
function PasswordCard({ userId, hasUid }) {
- const toast = useToast()
+ const { toast } = useToast()
const [password, setPassword] = useState('')
const [confirm, setConfirm] = useState('')
const [showPass, setShowPass] = useState(false)
@@ -302,9 +302,9 @@ function PasswordCard({ userId, hasUid }) {
// ─── Main ────────────────────────────────────────────────────────────────────
export default function UserForm() {
- const { id } = useParams()
- const navigate = useNavigate()
- const toast = useToast()
+ const { id } = useParams()
+ const navigate = useNavigate()
+ const { toast } = useToast()
const isEdit = Boolean(id)
const [form, setForm] = useState({
@@ -317,6 +317,7 @@ export default function UserForm() {
userTitle: '',
settingsPIN: '',
quickSettingsPIN: '',
+ password: '',
})
const [uid, setUid] = useState('')
const [loading, setLoading] = useState(false)
@@ -435,6 +436,18 @@ export default function UserForm() {
placeholder="+1 234 567 8900"
/>
+ {!isEdit && (
+
+ )}