Bootstrap dashboard totals in Web App links

This commit is contained in:
Codex 2026-09-07 21:16:48 +00:00
parent e1b91a2bed
commit 912c41c3bb
4 changed files with 60 additions and 5 deletions

View file

@ -7,6 +7,7 @@ from datetime import UTC, datetime
from pathlib import Path from pathlib import Path
from threading import Thread from threading import Thread
from typing import Any from typing import Any
from urllib.parse import urlencode
import telebot import telebot
from telebot import types from telebot import types
@ -19,8 +20,20 @@ logger = logging.getLogger(__name__)
CATEGORY_PATTERN = re.compile(r"^[^\x00-\x1f]{1,40}$") CATEGORY_PATTERN = re.compile(r"^[^\x00-\x1f]{1,40}$")
def webapp_url_for_user(settings: Settings, user_id: int) -> str: def webapp_url_for_user(
return f"{settings.webapp_url}?user_id={user_id}&mode=keyboard" settings: Settings,
user_id: int,
summary: dict[str, Any] | None = None,
) -> str:
params: dict[str, str | int] = {"user_id": user_id, "mode": "keyboard"}
if summary:
params.update(
nickname=str(summary["nickname"]),
balance=int(summary["balance_cents"]),
income=int(summary["income_cents"]),
expense=int(summary["expense_cents"]),
)
return f"{settings.webapp_url}?{urlencode(params)}"
def decode_payload(raw: str) -> dict[str, Any]: def decode_payload(raw: str) -> dict[str, Any]:
@ -107,7 +120,11 @@ class TelegramBotService:
types.KeyboardButton( types.KeyboardButton(
text="Открыть Пожитки", text="Открыть Пожитки",
web_app=types.WebAppInfo( web_app=types.WebAppInfo(
url=webapp_url_for_user(self.settings, user_id) url=webapp_url_for_user(
self.settings,
user_id,
self.database.summary(user_id),
)
), ),
) )
) )

View file

@ -29,6 +29,22 @@
const $ = (selector) => document.querySelector(selector); const $ = (selector) => document.querySelector(selector);
const money = new Intl.NumberFormat("ru-RU", { style: "currency", currency: "RUB", maximumFractionDigits: 2 }); const money = new Intl.NumberFormat("ru-RU", { style: "currency", currency: "RUB", maximumFractionDigits: 2 });
function bootstrapSummaryFromUrl() {
const nickname = query.get("nickname")?.trim();
const values = ["balance", "income", "expense"].map((key) => {
const raw = query.get(key);
return raw !== null && /^-?\d+$/.test(raw) ? Number(raw) : NaN;
});
if (!nickname || values.some((value) => !Number.isSafeInteger(value))) return null;
return {
nickname,
balance_cents: values[0],
income_cents: values[1],
expense_cents: values[2],
categories: [],
};
}
function safeTelegram(action) { function safeTelegram(action) {
try { action(); } catch (error) { console.warn("Telegram WebApp method failed", error); } try { action(); } catch (error) { console.warn("Telegram WebApp method failed", error); }
} }
@ -460,7 +476,14 @@
} }
initTelegram(); initTelegram();
setIdentity(); const bootstrapSummary = bootstrapSummaryFromUrl();
if (bootstrapSummary) {
state.summary = bootstrapSummary;
setIdentity(bootstrapSummary);
renderSummary(bootstrapSummary);
} else {
setIdentity();
}
bindNavigation(); bindNavigation();
setupNativeControls(); setupNativeControls();
bindForm(); bindForm();

View file

@ -8,7 +8,7 @@
<title>Пожитки</title> <title>Пожитки</title>
<link rel="stylesheet" href="./styles.css?v=16" /> <link rel="stylesheet" href="./styles.css?v=16" />
<script src="./telegram-web-app.js"></script> <script src="./telegram-web-app.js"></script>
<script src="./app.js?v=15" defer></script> <script src="./app.js?v=17" defer></script>
</head> </head>
<body> <body>
<main class="shell"> <main class="shell">

View file

@ -108,6 +108,21 @@ def test_personalized_keyboard_webapp_url(tmp_path: Path) -> None:
assert webapp_url_for_user(settings, 123456) == ( assert webapp_url_for_user(settings, 123456) == (
"https://example.com/app/?user_id=123456&mode=keyboard" "https://example.com/app/?user_id=123456&mode=keyboard"
) )
url = webapp_url_for_user(
settings,
123456,
{
"nickname": "Иван Петров",
"balance_cents": 2500,
"income_cents": 10000,
"expense_cents": 7500,
},
)
assert url == (
"https://example.com/app/?user_id=123456&mode=keyboard"
"&nickname=%D0%98%D0%B2%D0%B0%D0%BD+%D0%9F%D0%B5%D1%82%D1%80%D0%BE%D0%B2"
"&balance=2500&income=10000&expense=7500"
)
def test_custom_category_and_cancellation(tmp_path: Path) -> None: def test_custom_category_and_cancellation(tmp_path: Path) -> None: