- Add Bootstrap 5.3.3 + Icons via CDN to base.html - Replace 315-line hand-written CSS with 35-line brand overrides - Update all 13 templates with Bootstrap utility classes: - Responsive navbar with mobile hamburger menu - Consistent card-based layout for forms and profile - Proper button alignment with d-flex and d-grid utilities - List groups for data display (profile info) - Professional alerts and icons - No backend changes, no build toolchain needed - Responsive design works on mobile/tablet/desktop Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
146 lines
4.6 KiB
Python
146 lines
4.6 KiB
Python
from fastapi import APIRouter, Request, Depends
|
||
from fastapi.responses import RedirectResponse
|
||
from fastapi.templating import Jinja2Templates
|
||
from sqlalchemy.orm import Session
|
||
|
||
from web.auth import get_current_user, verify_password, hash_password
|
||
from web.database import get_db
|
||
from web.models import User
|
||
from web.schemas import validate_profile, validate_reset_password
|
||
|
||
router = APIRouter()
|
||
templates = Jinja2Templates(directory="web/templates")
|
||
|
||
|
||
# VIEW PROFILE
|
||
@router.get("/profile")
|
||
def profile_view(request: Request, user: User | None = Depends(get_current_user)):
|
||
if not user:
|
||
return RedirectResponse("/login", 303)
|
||
return templates.TemplateResponse("profile_view.html", {"request": request, "user": user})
|
||
|
||
|
||
# EDIT PROFILE
|
||
@router.get("/profile/edit")
|
||
def profile_edit_form(request: Request, user: User | None = Depends(get_current_user)):
|
||
if not user:
|
||
return RedirectResponse("/login", 303)
|
||
return templates.TemplateResponse("profile_edit.html", {"request": request, "user": user})
|
||
|
||
|
||
@router.post("/profile/edit")
|
||
async def profile_edit_submit(
|
||
request: Request,
|
||
db: Session = Depends(get_db),
|
||
user: User | None = Depends(get_current_user),
|
||
):
|
||
if not user:
|
||
return RedirectResponse("/login", 303)
|
||
|
||
form = await request.form()
|
||
data = dict(form)
|
||
|
||
errors = validate_profile(data)
|
||
|
||
if not errors:
|
||
existing = db.query(User).filter(
|
||
User.phone == data["phone"].strip(), User.id != user.id
|
||
).first()
|
||
if existing:
|
||
errors.append("Пользователь с таким телефоном уже существует")
|
||
|
||
if errors:
|
||
return templates.TemplateResponse("profile_edit.html", {
|
||
"request": request, "user": user, "errors": errors, "form": data,
|
||
})
|
||
|
||
user.first_name = data["first_name"].strip()
|
||
user.last_name = data["last_name"].strip()
|
||
user.phone = data["phone"].strip()
|
||
db.commit()
|
||
|
||
return templates.TemplateResponse("profile_edit.html", {
|
||
"request": request, "user": user, "success": "Профиль обновлен",
|
||
})
|
||
|
||
|
||
# CHANGE PASSWORD
|
||
@router.get("/profile/change-password")
|
||
def change_password_form(request: Request, user: User | None = Depends(get_current_user)):
|
||
if not user:
|
||
return RedirectResponse("/login", 303)
|
||
return templates.TemplateResponse("profile_change_password.html", {"request": request, "user": user})
|
||
|
||
|
||
@router.post("/profile/change-password")
|
||
async def change_password_submit(
|
||
request: Request,
|
||
db: Session = Depends(get_db),
|
||
user: User | None = Depends(get_current_user),
|
||
):
|
||
if not user:
|
||
return RedirectResponse("/login", 303)
|
||
|
||
form = await request.form()
|
||
data = dict(form)
|
||
|
||
errors = []
|
||
current_password = data.get("current_password", "")
|
||
if not current_password:
|
||
errors.append("Введите текущий пароль")
|
||
elif not verify_password(current_password, user.password_hash):
|
||
errors.append("Неверный текущий пароль")
|
||
|
||
password_errors = validate_reset_password(data)
|
||
errors.extend(password_errors)
|
||
|
||
if errors:
|
||
return templates.TemplateResponse("profile_change_password.html", {
|
||
"request": request, "user": user, "errors": errors,
|
||
})
|
||
|
||
user.password_hash = hash_password(data["password"])
|
||
db.commit()
|
||
|
||
return templates.TemplateResponse("profile_change_password.html", {
|
||
"request": request, "user": user, "success": "Пароль изменен",
|
||
})
|
||
|
||
|
||
# DELETE ACCOUNT
|
||
@router.get("/profile/delete")
|
||
def delete_account_form(request: Request, user: User | None = Depends(get_current_user)):
|
||
if not user:
|
||
return RedirectResponse("/login", 303)
|
||
return templates.TemplateResponse("profile_delete.html", {"request": request, "user": user})
|
||
|
||
|
||
@router.post("/profile/delete")
|
||
async def delete_account_submit(
|
||
request: Request,
|
||
db: Session = Depends(get_db),
|
||
user: User | None = Depends(get_current_user),
|
||
):
|
||
if not user:
|
||
return RedirectResponse("/login", 303)
|
||
|
||
form = await request.form()
|
||
data = dict(form)
|
||
|
||
password = data.get("password", "")
|
||
if not password:
|
||
return templates.TemplateResponse("profile_delete.html", {
|
||
"request": request, "user": user, "errors": ["Введите пароль для подтверждения"],
|
||
})
|
||
|
||
if not verify_password(password, user.password_hash):
|
||
return templates.TemplateResponse("profile_delete.html", {
|
||
"request": request, "user": user, "errors": ["Неверный пароль"],
|
||
})
|
||
|
||
db.delete(user)
|
||
db.commit()
|
||
request.session.clear()
|
||
|
||
return RedirectResponse("/", 303)
|