Add a maintenance pause/resume switch and a proper user navbar

RoundConfig gets a paused flag toggled via new POST /admin/pause and
/admin/resume endpoints (audit-logged, surfaced as a "Manutenzione" card in
the admin Parametri view). Pausing only stops the *next* round from opening
once the current one closes — rounds/service.py:open_new_round_if_needed
still lets an in-progress round finish, draw, and pay out its winner
normally. GET /rounds/current exposes lottery_paused so the user page shows
a maintenance banner (even while logged out) instead of silently going idle.

Also replaces the user dashboard's stacked account-bar card + bento-grid
menu with a single sticky navbar (identity row + Deposito/Bet/Prelievo
tabs), and moves the page content into a dedicated .app-shell container so
the navbar itself can span full width.
This commit is contained in:
2026-07-22 10:36:36 +02:00
parent 78109aa4c4
commit 162a63d04a
12 changed files with 299 additions and 72 deletions
+24
View File
@@ -29,6 +29,7 @@ _CONFIG_FIELDS = (
"fee_rate_sat_vb",
"rbf_timeout_seconds",
"draw_animation_seconds",
"paused",
)
@@ -41,6 +42,7 @@ class RoundConfigResponse(BaseModel):
fee_rate_sat_vb: int
rbf_timeout_seconds: int
draw_animation_seconds: int
paused: bool
class RoundConfigUpdate(BaseModel):
@@ -52,6 +54,7 @@ class RoundConfigUpdate(BaseModel):
fee_rate_sat_vb: int | None = None
rbf_timeout_seconds: int | None = None
draw_animation_seconds: int | None = None
paused: bool | None = None
def _config_response(config) -> RoundConfigResponse:
@@ -78,6 +81,27 @@ async def update_config(
return _config_response(config)
@router.post("/pause", response_model=RoundConfigResponse, dependencies=[Depends(require_admin)])
async def pause_lottery(session: AsyncSession = Depends(get_session)) -> RoundConfigResponse:
"""Maintenance switch: the round in progress (if any) still closes, draws,
and pays out its winner normally — only opening the *next* round is
suppressed until /admin/resume is called (rounds/service.py)."""
config = await get_round_config(session)
config.paused = True
await write_audit_log(session, "lottery_paused", {})
await session.commit()
return _config_response(config)
@router.post("/resume", response_model=RoundConfigResponse, dependencies=[Depends(require_admin)])
async def resume_lottery(session: AsyncSession = Depends(get_session)) -> RoundConfigResponse:
config = await get_round_config(session)
config.paused = False
await write_audit_log(session, "lottery_resumed", {})
await session.commit()
return _config_response(config)
class AdminUserResponse(BaseModel):
id: int
username: str
+3
View File
@@ -25,6 +25,7 @@ class CurrentRoundResponse(BaseModel):
winner_user_id: int | None = None
winner_amount_sats: int | None = None
chain_tip_height: int | None = None
lottery_paused: bool = False
@router.get("/current", response_model=CurrentRoundResponse)
@@ -39,6 +40,7 @@ async def current_round(request: Request, session: AsyncSession = Depends(get_se
bet_amount_sats=config.bet_amount_sats,
draw_animation_seconds=config.draw_animation_seconds,
chain_tip_height=chain_tip_height,
lottery_paused=config.paused,
)
participant_count = await session.scalar(
@@ -60,4 +62,5 @@ async def current_round(request: Request, session: AsyncSession = Depends(get_se
winner_user_id=round_.winner_user_id,
winner_amount_sats=round_.winner_amount_sats,
chain_tip_height=chain_tip_height,
lottery_paused=config.paused,
)