Added telegram bot WEB App
Build and Publish / Build and Publish Docker Image (push) Successful in 6m0s
Build and Publish / Build and Publish Docker Image (push) Successful in 6m0s
This commit is contained in:
+123
-14
@@ -27,6 +27,13 @@
|
||||
.brand-version { color: #7a858d; font-size: .75rem; }
|
||||
.top-actions { display: flex; align-items: center; justify-content: flex-end; gap: .35rem; flex-wrap: wrap; }
|
||||
.user-pill { color: #53616c; font-size: .86rem; max-width: 240px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.telegram-status-button { min-width: 38px; width: 38px; height: 38px; min-height: 38px; padding: 0; display: inline-grid; place-items: center; position: relative; border-color: #c7d4ce; background: #fff; color: #18342f; }
|
||||
.telegram-status-button .telegram-mark { font-size: .72rem; font-weight: 900; letter-spacing: 0; }
|
||||
.telegram-dot { position: absolute; right: 5px; top: 5px; width: .56rem; height: .56rem; border-radius: 999px; border: 2px solid #fff; background: #8a969e; }
|
||||
.telegram-status-button.tone-connected .telegram-dot { background: #2f8f4e; }
|
||||
.telegram-status-button.tone-pending .telegram-dot { background: #d39a00; }
|
||||
.telegram-status-button.tone-empty .telegram-dot { background: #69777f; }
|
||||
.telegram-status-button.tone-disabled .telegram-dot { background: #a8312d; }
|
||||
.lang-switch { display: inline-flex; align-items: center; border: 1px solid #d6dfda; border-radius: 8px; background: #f7faf8; overflow: hidden; }
|
||||
.lang-switch a { min-width: 38px; padding: .38rem .5rem; color: #53616c; text-align: center; text-decoration: none; font-size: .82rem; font-weight: 800; }
|
||||
.lang-switch a.active { background: #18342f; color: #fff; }
|
||||
@@ -111,6 +118,8 @@
|
||||
.guide-list { margin: 0; padding-left: 1.2rem; color: #34424b; display: grid; gap: .45rem; line-height: 1.4; }
|
||||
.guide-list a { color: #18342f; font-weight: 850; }
|
||||
.guide-list a.disabled { color: #69777f; pointer-events: none; text-decoration: none; }
|
||||
.secret-box { display: grid; gap: .35rem; border: 1px solid #d6dfda; border-radius: 8px; background: #f7faf8; padding: .7rem; }
|
||||
.secret-box code { display: block; padding: .55rem .65rem; font-size: .92rem; white-space: normal; overflow-wrap: anywhere; }
|
||||
@media (max-width: 720px) {
|
||||
.topbar-inner { align-items: flex-start; flex-direction: column; }
|
||||
.top-actions { width: 100%; justify-content: space-between; }
|
||||
@@ -150,6 +159,10 @@
|
||||
</a>
|
||||
<div class="top-actions">
|
||||
<span class="user-pill">{{ user_name }} ({{ user_role }})</span>
|
||||
<button type="button" x-show="telegram.enabled" class="telegram-status-button" :class="`tone-${telegramStatusTone()}`" @click="openTelegramManage()" :title="telegramStatusTitle()" :aria-label="telegramStatusTitle()">
|
||||
<span class="telegram-mark">TG</span>
|
||||
<span class="telegram-dot"></span>
|
||||
</button>
|
||||
<div class="lang-switch">
|
||||
<a href="#"{% if t.lang.code() == "en" %} class="active"{% endif %} onclick="location.href='/set-lang?lang=en&next='+encodeURIComponent(location.pathname);return false">EN</a>
|
||||
<a href="#"{% if t.lang.code() == "ru" %} class="active"{% endif %} onclick="location.href='/set-lang?lang=ru&next='+encodeURIComponent(location.pathname);return false">RU</a>
|
||||
@@ -341,6 +354,37 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template x-if="telegramModal === 'manage'">
|
||||
<div>
|
||||
<p class="telegram-copy" x-text="telegramManageMessage()"></p>
|
||||
<template x-if="telegram.linked">
|
||||
<div class="secret-box">
|
||||
<span>{{ t.telegram_status_connected }}</span>
|
||||
<code x-text="telegram.telegram_id || ''"></code>
|
||||
</div>
|
||||
</template>
|
||||
<template x-if="telegram.pending">
|
||||
<div class="secret-box">
|
||||
<span>{{ t.telegram_secret_label }}</span>
|
||||
<code x-text="telegram.pending_secret || ''"></code>
|
||||
</div>
|
||||
</template>
|
||||
<div class="modal-actions">
|
||||
<button type="button" class="secondary" @click="telegramModal = null">{{ t.admin_close }}</button>
|
||||
<template x-if="telegram.pending">
|
||||
<button type="button" class="secondary" @click="copyTelegramSecret()" :disabled="telegramBusy || !telegram.pending_secret">{{ t.telegram_copy_secret }}</button>
|
||||
</template>
|
||||
<template x-if="telegram.pending">
|
||||
<button type="button" class="secondary" @click="refreshTelegramStatus()" :disabled="telegramBusy">{{ t.telegram_refresh_status }}</button>
|
||||
</template>
|
||||
<button type="button" class="secondary" @click="startTelegramBotLink()" :disabled="telegramBusy" x-text="telegram.linked || telegram.pending ? '{{ t.telegram_change }}' : '{{ t.telegram_connect }}'"></button>
|
||||
<template x-if="telegram.linked || telegram.pending || telegram.declined">
|
||||
<button type="button" class="danger" @click="deleteTelegramLink()" :disabled="telegramBusy">{{ t.telegram_delete }}</button>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template x-if="telegramModal === 'webapp'">
|
||||
<div>
|
||||
<p class="telegram-copy">{{ t.telegram_webapp_message }}</p>
|
||||
@@ -356,7 +400,7 @@
|
||||
<p class="telegram-copy">{{ t.telegram_manual_message }}</p>
|
||||
<div class="modal-actions">
|
||||
<button type="button" class="secondary" @click="declineTelegramLink()" :disabled="telegramBusy">{{ t.telegram_skip }}</button>
|
||||
<button type="button" @click="telegramModal = 'manualGuide'">{{ t.telegram_connect }}</button>
|
||||
<button type="button" @click="startTelegramBotLink()" :disabled="telegramBusy">{{ t.telegram_connect }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -369,11 +413,14 @@
|
||||
<li>{{ t.telegram_guide_get_id }}</li>
|
||||
<li>{{ t.telegram_guide_paste }}</li>
|
||||
</ol>
|
||||
<label for="telegram-id">{{ t.telegram_id_label }}</label>
|
||||
<input id="telegram-id" x-model="telegramManualId" inputmode="numeric" pattern="[0-9]*" placeholder="{{ t.telegram_id_placeholder }}" @keydown.enter.prevent="saveTelegramManual()">
|
||||
<div class="secret-box">
|
||||
<span>{{ t.telegram_secret_label }}</span>
|
||||
<code x-text="telegram.pending_secret || ''"></code>
|
||||
</div>
|
||||
<div class="modal-actions">
|
||||
<button type="button" class="secondary" @click="declineTelegramLink()" :disabled="telegramBusy">{{ t.telegram_skip }}</button>
|
||||
<button type="button" @click="saveTelegramManual()" :disabled="telegramBusy || !telegramManualId.trim()">{{ t.telegram_save }}</button>
|
||||
<button type="button" class="secondary" @click="copyTelegramSecret()" :disabled="telegramBusy || !telegram.pending_secret">{{ t.telegram_copy_secret }}</button>
|
||||
<button type="button" @click="refreshTelegramStatus()" :disabled="telegramBusy">{{ t.telegram_refresh_status }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -404,12 +451,14 @@ function clientPortal() {
|
||||
bot_url: '',
|
||||
linked: false,
|
||||
declined: false,
|
||||
pending: false,
|
||||
pending_secret: null,
|
||||
pending_expires_at: null,
|
||||
telegram_id: null,
|
||||
isWebApp: false,
|
||||
initData: '',
|
||||
},
|
||||
telegramModal: null,
|
||||
telegramManualId: '',
|
||||
telegramBusy: false,
|
||||
telegramPromptChecked: false,
|
||||
init() {
|
||||
@@ -485,13 +534,24 @@ function clientPortal() {
|
||||
bot_url: status.bot_url || '',
|
||||
linked: Boolean(status.linked),
|
||||
declined: Boolean(status.declined),
|
||||
pending: Boolean(status.pending),
|
||||
pending_secret: status.pending_secret || null,
|
||||
pending_expires_at: status.pending_expires_at || null,
|
||||
telegram_id: status.telegram_id || null,
|
||||
};
|
||||
},
|
||||
maybePromptTelegram() {
|
||||
if (this.telegramPromptChecked) return;
|
||||
this.telegramPromptChecked = true;
|
||||
if (!this.telegram.enabled || this.telegram.linked || this.telegram.declined) return;
|
||||
if (!this.telegram.enabled || this.telegram.linked || this.telegram.pending || this.telegram.declined) return;
|
||||
this.telegramModal = (this.telegram.isWebApp && this.telegram.initData) ? 'webapp' : 'manualPrompt';
|
||||
},
|
||||
openTelegramManage() {
|
||||
if (!this.telegram.enabled) return;
|
||||
if (this.telegram.linked || this.telegram.pending || this.telegram.declined) {
|
||||
this.telegramModal = 'manage';
|
||||
return;
|
||||
}
|
||||
this.telegramModal = (this.telegram.isWebApp && this.telegram.initData) ? 'webapp' : 'manualPrompt';
|
||||
},
|
||||
async linkTelegramWebApp() {
|
||||
@@ -511,24 +571,41 @@ function clientPortal() {
|
||||
this.telegramBusy = false;
|
||||
}
|
||||
},
|
||||
async saveTelegramManual() {
|
||||
async startTelegramBotLink() {
|
||||
this.telegramBusy = true;
|
||||
this.clearNotice();
|
||||
try {
|
||||
const data = await this.request('/api/telegram-link/manual', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ telegram_id: this.telegramManualId }),
|
||||
});
|
||||
const data = await this.request('/api/telegram-link/start', { method: 'POST' });
|
||||
this.applyTelegramStatus(data.status || {});
|
||||
this.telegramManualId = '';
|
||||
this.telegramModal = null;
|
||||
this.setNotice('success', '{{ t.notice_success_title }}', '{{ t.telegram_saved }}');
|
||||
this.telegramModal = 'manualGuide';
|
||||
} catch (e) {
|
||||
this.showError(e);
|
||||
} finally {
|
||||
this.telegramBusy = false;
|
||||
}
|
||||
},
|
||||
async refreshTelegramStatus() {
|
||||
this.telegramBusy = true;
|
||||
try {
|
||||
await this.loadTelegramStatus();
|
||||
if (this.telegram.linked) {
|
||||
this.telegramModal = 'manage';
|
||||
this.setNotice('success', '{{ t.notice_success_title }}', '{{ t.telegram_saved }}');
|
||||
}
|
||||
} finally {
|
||||
this.telegramBusy = false;
|
||||
}
|
||||
},
|
||||
async copyTelegramSecret() {
|
||||
if (!this.telegram.pending_secret) return;
|
||||
this.clearNotice();
|
||||
try {
|
||||
await this.copyText(this.telegram.pending_secret);
|
||||
this.setNotice('success', '{{ t.notice_success_title }}', '{{ t.telegram_secret_copied }}');
|
||||
} catch (e) {
|
||||
this.showError(e);
|
||||
}
|
||||
},
|
||||
async declineTelegramLink() {
|
||||
this.telegramBusy = true;
|
||||
this.clearNotice();
|
||||
@@ -543,12 +620,44 @@ function clientPortal() {
|
||||
this.telegramBusy = false;
|
||||
}
|
||||
},
|
||||
async deleteTelegramLink() {
|
||||
if (!confirm('{{ t.telegram_delete_confirm }}')) return;
|
||||
this.telegramBusy = true;
|
||||
this.clearNotice();
|
||||
try {
|
||||
const data = await this.request('/api/telegram-link/unlink', { method: 'POST' });
|
||||
this.applyTelegramStatus(data.status || {});
|
||||
this.telegramModal = null;
|
||||
this.setNotice('success', '{{ t.notice_success_title }}', '{{ t.telegram_unlinked }}');
|
||||
} catch (e) {
|
||||
this.showError(e);
|
||||
} finally {
|
||||
this.telegramBusy = false;
|
||||
}
|
||||
},
|
||||
telegramBotUrl() {
|
||||
return this.telegram.bot_url || '#';
|
||||
},
|
||||
telegramBotLabel() {
|
||||
return this.telegram.bot_username ? `@${this.telegram.bot_username}` : '{{ t.telegram_bot_unconfigured }}';
|
||||
},
|
||||
telegramStatusTone() {
|
||||
if (!this.telegram.enabled) return 'disabled';
|
||||
if (this.telegram.pending) return 'pending';
|
||||
if (this.telegram.linked) return 'connected';
|
||||
return 'empty';
|
||||
},
|
||||
telegramStatusTitle() {
|
||||
if (!this.telegram.enabled) return '{{ t.telegram_status_disabled }}';
|
||||
if (this.telegram.pending) return '{{ t.telegram_status_pending }}';
|
||||
if (this.telegram.linked) return '{{ t.telegram_status_connected }}';
|
||||
return '{{ t.telegram_status_empty }}';
|
||||
},
|
||||
telegramManageMessage() {
|
||||
if (this.telegram.pending) return '{{ t.telegram_pending_message }}';
|
||||
if (this.telegram.linked) return '{{ t.telegram_connected_message }}';
|
||||
return '{{ t.telegram_not_connected_message }}';
|
||||
},
|
||||
async createClient() {
|
||||
this.clearNotice();
|
||||
this.busy = true;
|
||||
|
||||
Reference in New Issue
Block a user