feat(telegram,game): single bot + per-user variant preferences
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Successful in 10s
CI / integration (pull_request) Successful in 13s
CI / ui (pull_request) Successful in 54s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 57s
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Successful in 10s
CI / integration (pull_request) Successful in 13s
CI / ui (pull_request) Successful in 54s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 57s
Collapse the two per-language Telegram bots into one unified bot and
replace language-based variant gating with explicit per-user variant
preferences.
- Telegram: one bot; drop service_language and the supported_languages
set everywhere (DB, account, auth, FlatBuffers Session wire, gateway,
connector proto). The single bot renders chat and out-of-app push in
the recipient's preferred_language; remove the game-language push
routing override (notify Intent.Language / push Event.language).
- Preferences: new accounts.variant_preferences (text[], DB default
{erudit_ru}, CHECK non-empty + subset of the three variants). Gates
the New Game picker, vs-AI and the friend invitation the player
creates, enforced server-side (HTTP 400 otherwise); an invited friend
may still accept any variant. Edited on the Settings screen; variants
are Erudit-first everywhere.
- Admin: drop the per-bot language selectors (broadcast / send-to-user)
and the feedback channel_lang column/field.
- Env/CI: collapse TELEGRAM_BOT_TOKEN_{EN,RU}, TELEGRAM_GAME_CHANNEL_ID_{EN,RU},
VITE_TELEGRAM_LINK{,_EN,_RU} and VITE_TELEGRAM_GAME_CHANNEL_NAME_{EN,RU}
to single unsuffixed names; drop GATEWAY_DEFAULT_SUPPORTED_LANGUAGES.
- Docs updated (ARCHITECTURE, FUNCTIONAL + _ru, platform/telegram, gateway,
backend, ui, UI_DESIGN, PRERELEASE).
The migration squash is deferred to a follow-up PR.
This commit is contained in:
@@ -16,6 +16,8 @@
|
||||
validDisplayName,
|
||||
validEmail,
|
||||
} from '../lib/profileValidation';
|
||||
import { ALL_VARIANTS, VARIANT_RULES } from '../lib/variants';
|
||||
import type { Variant } from '../lib/model';
|
||||
|
||||
let dn = $state('');
|
||||
let tz = $state('+00:00');
|
||||
@@ -28,6 +30,7 @@
|
||||
let blockChat = $state(false);
|
||||
let blockFriendRequests = $state(false);
|
||||
let notificationsInAppOnly = $state(true);
|
||||
let variantPrefs = $state<Variant[]>([]);
|
||||
let emailInput = $state('');
|
||||
let codeInput = $state('');
|
||||
let emailSent = $state(false);
|
||||
@@ -60,6 +63,7 @@
|
||||
blockChat = p.blockChat;
|
||||
blockFriendRequests = p.blockFriendRequests;
|
||||
notificationsInAppOnly = p.notificationsInAppOnly;
|
||||
variantPrefs = [...p.variantPreferences];
|
||||
}
|
||||
onMount(populate);
|
||||
|
||||
@@ -67,9 +71,19 @@
|
||||
const awayEnd = $derived(`${endH}:${endM}`);
|
||||
const nameOk = $derived(validDisplayName(dn));
|
||||
const awayOk = $derived(awayDurationOk(awayStart, awayEnd));
|
||||
const formValid = $derived(nameOk && awayOk);
|
||||
const formValid = $derived(nameOk && awayOk && variantPrefs.length >= 1);
|
||||
const emailOk = $derived(validEmail(emailInput));
|
||||
|
||||
// toggleVariant flips a variant in the preference set, refusing to remove the last one —
|
||||
// the player must allow themselves at least one variant.
|
||||
function toggleVariant(id: Variant) {
|
||||
if (variantPrefs.includes(id)) {
|
||||
if (variantPrefs.length > 1) variantPrefs = variantPrefs.filter((v) => v !== id);
|
||||
} else {
|
||||
variantPrefs = [...variantPrefs, id];
|
||||
}
|
||||
}
|
||||
|
||||
async function save() {
|
||||
if (!formValid) return;
|
||||
try {
|
||||
@@ -82,6 +96,7 @@
|
||||
blockChat,
|
||||
blockFriendRequests,
|
||||
notificationsInAppOnly,
|
||||
variantPreferences: variantPrefs,
|
||||
});
|
||||
showToast(t('profile.saved'));
|
||||
} catch (e) {
|
||||
@@ -207,6 +222,22 @@
|
||||
<input type="checkbox" bind:checked={notificationsInAppOnly} />
|
||||
<span>{t('profile.notificationsInAppOnly')}</span>
|
||||
</label>
|
||||
<fieldset class="prefs">
|
||||
<legend>{t('profile.preferences')}</legend>
|
||||
<p class="muted">{t('profile.preferencesHint')}</p>
|
||||
{#each ALL_VARIANTS as v (v.id)}
|
||||
<label class="check pref">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={variantPrefs.includes(v.id)}
|
||||
disabled={variantPrefs.length === 1 && variantPrefs.includes(v.id)}
|
||||
onchange={() => toggleVariant(v.id)}
|
||||
/>
|
||||
<span class="pname">{t(v.label)}</span>
|
||||
<span class="prule">{t(VARIANT_RULES[v.id])}</span>
|
||||
</label>
|
||||
{/each}
|
||||
</fieldset>
|
||||
<div class="formacts">
|
||||
<button type="submit" class="btn" disabled={!formValid || !connection.online}>{t('common.save')}</button>
|
||||
</div>
|
||||
@@ -342,6 +373,30 @@
|
||||
gap: 10px !important;
|
||||
color: var(--text) !important;
|
||||
}
|
||||
.prefs {
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-sm);
|
||||
padding: 10px 12px;
|
||||
margin: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
.prefs legend {
|
||||
color: var(--text-muted);
|
||||
font-size: 0.9rem;
|
||||
padding: 0 4px;
|
||||
}
|
||||
.pref {
|
||||
align-items: baseline;
|
||||
}
|
||||
.pname {
|
||||
font-weight: 600;
|
||||
}
|
||||
.prule {
|
||||
color: var(--text-muted);
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
.formacts {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
|
||||
Reference in New Issue
Block a user