From 8033377cdf505aa7c80dcbe075de60e56169c1e2 Mon Sep 17 00:00:00 2001 From: Jaymiesh Date: Wed, 11 Feb 2026 16:35:20 +0700 Subject: [PATCH] Switch onboarding city selection to manual text input --- api/_lib/db.ts | 18 ++++++++++++++++++ bot/conversations/onboarding.ts | 30 ++++++++++++++++-------------- bot/i18n/en.ts | 3 ++- bot/i18n/ru.ts | 3 ++- 4 files changed, 38 insertions(+), 16 deletions(-) diff --git a/api/_lib/db.ts b/api/_lib/db.ts index 8473f78..251e3a7 100644 --- a/api/_lib/db.ts +++ b/api/_lib/db.ts @@ -57,6 +57,24 @@ export async function getCityById(id: string) { return rows[0] || null } +export async function getOrCreateCityByName(name: string) { + const normalizedName = name.trim().replace(/\s+/g, ' ') + + const { rows: existing } = await pool.query( + 'SELECT * FROM cities WHERE lower(name) = lower($1) LIMIT 1', + [normalizedName] + ) + if (existing[0]) return existing[0] + + const { rows } = await pool.query( + `INSERT INTO cities (name, country, description, status) + VALUES ($1, $2, $3, 'active') + RETURNING *`, + [normalizedName, 'Unknown', 'User-added city'] + ) + return rows[0] +} + // ============================================ // Quests // ============================================ diff --git a/bot/conversations/onboarding.ts b/bot/conversations/onboarding.ts index ae4bcdc..4ba8f4f 100644 --- a/bot/conversations/onboarding.ts +++ b/bot/conversations/onboarding.ts @@ -2,7 +2,7 @@ import type { Conversation } from '@grammyjs/conversations' import type { BotContext } from '../types.js' type BotConversation = Conversation -import { getActiveCities, findUserByTelegramId } from '../../api/_lib/db.js' +import { getOrCreateCityByName, findUserByTelegramId } from '../../api/_lib/db.js' import { t, getLang } from '../i18n/index.js' import { createQuestFromOnboarding } from '../services/quest.service.js' @@ -22,21 +22,23 @@ const PACE_MAP: Record = { export async function onboardingConversation(conversation: BotConversation, ctx: BotContext) { const lang = getLang(ctx.from?.language_code) - // Step 1: City selection - const cities = await conversation.external(() => getActiveCities()) + // Step 1: City input + await ctx.reply(t(lang, 'choose_city')) - const cityButtons = cities.map((city: { id: string; name: string }) => ([ - { text: city.name, callback_data: `city_${city.id}` } - ])) + let cityName = '' + while (true) { + const cityResponse = await conversation.waitFor('message:text') + const input = cityResponse.message.text.trim().replace(/\s+/g, ' ') + if (input.length >= 2 && !input.startsWith('/')) { + cityName = input + break + } + await ctx.reply(t(lang, 'invalid_city')) + } - await ctx.reply(t(lang, 'choose_city'), { - reply_markup: { inline_keyboard: cityButtons }, - }) - - const cityResponse = await conversation.waitForCallbackQuery(/^city_/) - const cityId = cityResponse.callbackQuery!.data!.replace('city_', '') - const cityName = cities.find((c: { id: string }) => c.id === cityId)?.name || 'Unknown' - await cityResponse.answerCallbackQuery() + const city = await conversation.external(() => getOrCreateCityByName(cityName)) + const cityId = city.id + cityName = city.name // Step 2: Number of days await ctx.reply(t(lang, 'choose_days')) diff --git a/bot/i18n/en.ts b/bot/i18n/en.ts index 0738380..5df1ef9 100644 --- a/bot/i18n/en.ts +++ b/bot/i18n/en.ts @@ -9,7 +9,8 @@ Let's begin! Where are you headed?`, welcome_back: `Welcome back! You have an active quest in {city}. Open the app to continue.`, // Onboarding - choose_city: 'Choose a city for your adventure:', + choose_city: 'Which city are you planning your quest in? Type the city name.', + invalid_city: 'Please enter a valid city name (at least 2 characters).', choose_days: 'How many days are you planning? (1-30)', invalid_days: 'Please enter a number from 1 to 30.', choose_companions: 'Who are you traveling with?', diff --git a/bot/i18n/ru.ts b/bot/i18n/ru.ts index 14cebff..0015f30 100644 --- a/bot/i18n/ru.ts +++ b/bot/i18n/ru.ts @@ -9,7 +9,8 @@ export const ru: Record = { welcome_back: `С возвращением! У тебя есть активный квест по городу {city}. Открой приложение, чтобы продолжить.`, // Onboarding - choose_city: 'Выбери город для приключения:', + choose_city: 'В каком городе планируешь квест? Напиши название города.', + invalid_city: 'Введи корректное название города (минимум 2 символа).', choose_days: 'На сколько дней планируешь поездку? (1-30)', invalid_days: 'Введи число от 1 до 30.', choose_companions: 'С кем путешествуешь?',