UI: 3-tab layout (Admin overview / My dashboard / Training) + Settings page
Frontend: - App shell with 3 tabs: Overview (admin), My Dashboard (all incl admin), Training + Settings icon - New Settings.vue: edit profile (name/email) + change password (verify current) - New MyBoard.vue: personal win/lose + weak areas + summary (all roles) - New Training.vue: product/group list -> personas; admin manage + add product - Dashboard.vue -> admin overview with date-range filter + per-trainee stats - Added i18n keys (EN/TH) and tab/settings styling (ui-ux-pro-max design system) Backend: - /api/auth/change-password (verify current pw) + PUT /api/auth/profile - Allow admin to train: board/weak-areas/chat/personas/sessions open to user+admin+super_admin - /api/analytics now accepts ?from=&to= date filter + returns per_user summary Verified: frontend builds; change-password (wrong/ok), profile, board all pass via test client; security tests pass
This commit is contained in:
@@ -65,3 +65,49 @@ def setup():
|
||||
"user": _store().public_user(updated),
|
||||
"must_setup": False,
|
||||
})
|
||||
|
||||
|
||||
@auth_bp.post("/change-password")
|
||||
@require_auth
|
||||
def change_password():
|
||||
"""Change own password — requires the current password to be correct."""
|
||||
user = current_user()
|
||||
data = request.get_json(silent=True) or {}
|
||||
current_pw = data.get("current_password") or ""
|
||||
new_pw = data.get("new_password") or ""
|
||||
if not current_pw or not new_pw:
|
||||
raise ApiError("current and new password are required")
|
||||
# verify the current password first
|
||||
try:
|
||||
_store().verify(user["username"], current_pw)
|
||||
except AuthError:
|
||||
raise ApiError("current password is incorrect", 400)
|
||||
if len(new_pw) < 4:
|
||||
raise ApiError("new password must be at least 4 characters", 400)
|
||||
updated = _store().set_password(user["username"], new_pw)
|
||||
return jsonify({"ok": True, "user": _store().public_user(updated)})
|
||||
|
||||
|
||||
@auth_bp.put("/profile")
|
||||
@require_auth
|
||||
def update_profile():
|
||||
"""Update own profile: display name and/or email."""
|
||||
user = current_user()
|
||||
data = request.get_json(silent=True) or {}
|
||||
updates = {}
|
||||
if "name" in data:
|
||||
name = (data.get("name") or "").strip()
|
||||
if name:
|
||||
updates["name"] = name
|
||||
_store().users.update(user["username"], name=name)
|
||||
if "email" in data:
|
||||
email = (data.get("email") or "").strip()
|
||||
try:
|
||||
_store().set_email(user["username"], email)
|
||||
except AuthError as exc:
|
||||
raise ApiError(str(exc), 400)
|
||||
updates["email"] = (email or "").lower()
|
||||
if not updates:
|
||||
raise ApiError("nothing to update", 400)
|
||||
updated = _store().get_user(user["username"])
|
||||
return jsonify({"ok": True, "user": _store().public_user(updated)})
|
||||
|
||||
Reference in New Issue
Block a user