mirror of
https://github.com/wasrusgen/zov-tech.git
synced 2026-06-03 15:44:47 +00:00
feat: one-tap role buttons (WebApp directly, no intermediate step) + role param in URL/backend
This commit is contained in:
parent
017d179746
commit
af7dc07720
@ -147,7 +147,8 @@ function handleMe(body) {
|
|||||||
|
|
||||||
// Регистрируем пользователя если первый раз, обновляем last_seen_at
|
// Регистрируем пользователя если первый раз, обновляем last_seen_at
|
||||||
const startParam = body.startParam || auth.start_param;
|
const startParam = body.startParam || auth.start_param;
|
||||||
const user = getOrCreateUser(auth.user, startParam);
|
const explicitRole = (body.role === "manager" || body.role === "client") ? body.role : null;
|
||||||
|
const user = getOrCreateUser(auth.user, startParam, explicitRole);
|
||||||
|
|
||||||
if (user.role === "manager") {
|
if (user.role === "manager") {
|
||||||
const m = getManagerProfile(tgId) || synthesizeManagerFromUser(user);
|
const m = getManagerProfile(tgId) || synthesizeManagerFromUser(user);
|
||||||
@ -301,7 +302,7 @@ function findUser(tgId) {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getOrCreateUser(tgUser, startParam) {
|
function getOrCreateUser(tgUser, startParam, explicitRole) {
|
||||||
const tgId = tgUser.id;
|
const tgId = tgUser.id;
|
||||||
const props = PropertiesService.getScriptProperties();
|
const props = PropertiesService.getScriptProperties();
|
||||||
const adminId = parseInt(props.getProperty("ADMIN_TG_ID") || "0", 10);
|
const adminId = parseInt(props.getProperty("ADMIN_TG_ID") || "0", 10);
|
||||||
@ -309,22 +310,26 @@ function getOrCreateUser(tgUser, startParam) {
|
|||||||
const existing = findUser(tgId);
|
const existing = findUser(tgId);
|
||||||
if (existing) {
|
if (existing) {
|
||||||
updateColumnByKey("Users", "tg_id", tgId, "last_seen_at", new Date());
|
updateColumnByKey("Users", "tg_id", tgId, "last_seen_at", new Date());
|
||||||
// Если это админ и роль ещё не manager — повышаем + автозавод в Managers
|
// Админ всегда manager
|
||||||
if (tgId === adminId && existing.role !== "manager") {
|
if (tgId === adminId && existing.role !== "manager") {
|
||||||
updateColumnByKey("Users", "tg_id", tgId, "role", "manager");
|
updateColumnByKey("Users", "tg_id", tgId, "role", "manager");
|
||||||
ensureAdminManager(tgUser);
|
ensureAdminManager(tgUser);
|
||||||
existing.role = "manager";
|
existing.role = "manager";
|
||||||
}
|
}
|
||||||
|
// Не-админ может явно сменить роль через URL ?role=
|
||||||
|
else if (explicitRole && tgId !== adminId && existing.role !== explicitRole) {
|
||||||
|
updateColumnByKey("Users", "tg_id", tgId, "role", explicitRole);
|
||||||
|
existing.role = explicitRole;
|
||||||
|
}
|
||||||
return existing;
|
return existing;
|
||||||
}
|
}
|
||||||
// Определяем роль:
|
// Новый пользователь — определяем роль
|
||||||
// - админ → manager (автозавод в Managers как ZOV-employee)
|
|
||||||
// - invite-код менеджера → клиент с привязкой
|
|
||||||
// - иначе → client
|
|
||||||
let role = "client";
|
let role = "client";
|
||||||
let inviteCode = "";
|
let inviteCode = "";
|
||||||
if (tgId === adminId) {
|
if (tgId === adminId) {
|
||||||
role = "manager";
|
role = "manager";
|
||||||
|
} else if (explicitRole) {
|
||||||
|
role = explicitRole;
|
||||||
} else if (startParam && startParam.indexOf("client_inv_") === 0) {
|
} else if (startParam && startParam.indexOf("client_inv_") === 0) {
|
||||||
role = "client";
|
role = "client";
|
||||||
inviteCode = startParam;
|
inviteCode = startParam;
|
||||||
@ -337,7 +342,7 @@ function getOrCreateUser(tgUser, startParam) {
|
|||||||
if (tgId === adminId) {
|
if (tgId === adminId) {
|
||||||
ensureAdminManager(tgUser);
|
ensureAdminManager(tgUser);
|
||||||
}
|
}
|
||||||
log("user_registered", tgId, { role, startParam });
|
log("user_registered", tgId, { role, startParam, explicitRole });
|
||||||
return findUser(tgId);
|
return findUser(tgId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,11 +1,12 @@
|
|||||||
from aiogram import Router, F
|
import time
|
||||||
|
|
||||||
|
from aiogram import Router
|
||||||
from aiogram.filters import CommandStart
|
from aiogram.filters import CommandStart
|
||||||
from aiogram.types import (
|
from aiogram.types import (
|
||||||
Message,
|
Message,
|
||||||
InlineKeyboardMarkup,
|
InlineKeyboardMarkup,
|
||||||
InlineKeyboardButton,
|
InlineKeyboardButton,
|
||||||
WebAppInfo,
|
WebAppInfo,
|
||||||
CallbackQuery,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
from config import Config
|
from config import Config
|
||||||
@ -13,25 +14,25 @@ from config import Config
|
|||||||
router = Router(name="start")
|
router = Router(name="start")
|
||||||
|
|
||||||
|
|
||||||
def role_choice_kb() -> InlineKeyboardMarkup:
|
def _bust_cache(url: str) -> str:
|
||||||
return InlineKeyboardMarkup(
|
"""Append unique timestamp to MiniApp URL so Telegram WebView can't cache between sessions."""
|
||||||
inline_keyboard=[
|
sep = "&" if "?" in url else "?"
|
||||||
[
|
return f"{url}{sep}t={int(time.time())}"
|
||||||
InlineKeyboardButton(text="👤 Менеджер", callback_data="role:manager"),
|
|
||||||
InlineKeyboardButton(text="🏠 Клиент", callback_data="role:client"),
|
|
||||||
]
|
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def open_app_kb(miniapp_url: str) -> InlineKeyboardMarkup:
|
def role_choice_kb(miniapp_url: str) -> InlineKeyboardMarkup:
|
||||||
|
"""Two WebApp buttons — one tap opens the cabinet directly, no intermediate step."""
|
||||||
return InlineKeyboardMarkup(
|
return InlineKeyboardMarkup(
|
||||||
inline_keyboard=[
|
inline_keyboard=[
|
||||||
[
|
[
|
||||||
InlineKeyboardButton(
|
InlineKeyboardButton(
|
||||||
text="🚀 Открыть кабинет",
|
text="👤 Менеджер",
|
||||||
web_app=WebAppInfo(url=miniapp_url),
|
web_app=WebAppInfo(url=_bust_cache(f"{miniapp_url}?role=manager")),
|
||||||
)
|
),
|
||||||
|
InlineKeyboardButton(
|
||||||
|
text="🏠 Клиент",
|
||||||
|
web_app=WebAppInfo(url=_bust_cache(f"{miniapp_url}?role=client")),
|
||||||
|
),
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
@ -39,25 +40,8 @@ def open_app_kb(miniapp_url: str) -> InlineKeyboardMarkup:
|
|||||||
|
|
||||||
@router.message(CommandStart())
|
@router.message(CommandStart())
|
||||||
async def cmd_start(message: Message, config: Config) -> None:
|
async def cmd_start(message: Message, config: Config) -> None:
|
||||||
# TODO: проверить, есть ли пользователь в БД (Google Sheet → users).
|
|
||||||
# Если есть → сразу показывать "Открыть кабинет".
|
|
||||||
# Если нет → спрашивать роль.
|
|
||||||
await message.answer(
|
await message.answer(
|
||||||
"👋 Здравствуйте, я бот-помощник от Руслана ВАСИЛЬЕВА.\n\n"
|
"👋 Здравствуйте, я бот-помощник от Руслана ВАСИЛЬЕВА.\n\n"
|
||||||
"Кто вы?",
|
"Кто вы?",
|
||||||
reply_markup=role_choice_kb(),
|
reply_markup=role_choice_kb(config.miniapp_url),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@router.callback_query(F.data.startswith("role:"))
|
|
||||||
async def on_role_chosen(callback: CallbackQuery, config: Config) -> None:
|
|
||||||
role = callback.data.split(":", 1)[1]
|
|
||||||
# TODO: сохранить роль в БД (Google Sheet → users)
|
|
||||||
|
|
||||||
text = {
|
|
||||||
"manager": "Отлично, открываю кабинет менеджера 👇",
|
|
||||||
"client": "Спасибо! Открываю ваш кабинет 👇",
|
|
||||||
}.get(role, "Открываю кабинет 👇")
|
|
||||||
|
|
||||||
await callback.message.edit_text(text, reply_markup=open_app_kb(config.miniapp_url))
|
|
||||||
await callback.answer()
|
|
||||||
|
|||||||
@ -53,11 +53,16 @@ async function fetchMe() {
|
|||||||
// Заголовок Content-Type НЕ ставим — иначе браузер шлёт CORS preflight,
|
// Заголовок Content-Type НЕ ставим — иначе браузер шлёт CORS preflight,
|
||||||
// который Apps Script не обрабатывает. Без заголовка fetch использует
|
// который Apps Script не обрабатывает. Без заголовка fetch использует
|
||||||
// text/plain — Apps Script всё равно парсит body как JSON.
|
// text/plain — Apps Script всё равно парсит body как JSON.
|
||||||
|
// Роль приходит в URL (?role=manager|client) — её бот подставляет в WebApp-кнопку
|
||||||
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
|
const explicitRole = urlParams.get("role");
|
||||||
|
|
||||||
const res = await fetch(`${BACKEND_URL}?path=me`, {
|
const res = await fetch(`${BACKEND_URL}?path=me`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
initData: tg?.initData || "",
|
initData: tg?.initData || "",
|
||||||
startParam: tg?.initDataUnsafe?.start_param || null,
|
startParam: tg?.initDataUnsafe?.start_param || null,
|
||||||
|
role: explicitRole,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
if (!res.ok) throw new Error("backend HTTP " + res.status);
|
if (!res.ok) throw new Error("backend HTTP " + res.status);
|
||||||
|
|||||||
@ -12,7 +12,7 @@
|
|||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Geist:wght@400;500;600&family=Newsreader:ital,wght@0,400..600;1,400..600&family=Instrument+Serif:ital@0;1&family=JetBrains+Mono:wght@400;500&display=swap">
|
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Geist:wght@400;500;600&family=Newsreader:ital,wght@0,400..600;1,400..600&family=Instrument+Serif:ital@0;1&family=JetBrains+Mono:wght@400;500&display=swap">
|
||||||
<script src="https://telegram.org/js/telegram-web-app.js"></script>
|
<script src="https://telegram.org/js/telegram-web-app.js"></script>
|
||||||
<link rel="stylesheet" href="assets/styles.css?v=20260509h">
|
<link rel="stylesheet" href="assets/styles.css?v=20260509i">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<main id="app">
|
<main id="app">
|
||||||
@ -20,7 +20,7 @@
|
|||||||
<div class="spinner"></div>
|
<div class="spinner"></div>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
<script src="assets/icons.js?v=20260509h"></script>
|
<script src="assets/icons.js?v=20260509i"></script>
|
||||||
<script src="assets/app.js?v=20260509h"></script>
|
<script src="assets/app.js?v=20260509i"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user