Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3a084a9d79 | |||
| 85512ab48b |
Generated
+1
-1
@@ -3255,7 +3255,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "web-petting"
|
name = "web-petting"
|
||||||
version = "0.1.7"
|
version = "0.1.8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"chrono",
|
"chrono",
|
||||||
"chrono-tz",
|
"chrono-tz",
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "web-petting"
|
name = "web-petting"
|
||||||
version = "0.1.7"
|
version = "0.1.8"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
|||||||
+34
-2
@@ -401,6 +401,8 @@ async fn setup_submit(request: Request, session: Session, db: Database) -> cot::
|
|||||||
login: form.login,
|
login: form.login,
|
||||||
password_hash: password_auth::generate_hash(&form.password),
|
password_hash: password_auth::generate_hash(&form.password),
|
||||||
display_name: display,
|
display_name: display,
|
||||||
|
telegram_chat_id: None,
|
||||||
|
telegram_notifications: Some(false),
|
||||||
status: "active".to_string(),
|
status: "active".to_string(),
|
||||||
created_at: now_utc(),
|
created_at: now_utc(),
|
||||||
updated_at: now_utc(),
|
updated_at: now_utc(),
|
||||||
@@ -760,6 +762,8 @@ async fn add_user(request: Request, session: Session, db: Database) -> cot::Resu
|
|||||||
login: form.login,
|
login: form.login,
|
||||||
password_hash: password_auth::generate_hash(&form.password),
|
password_hash: password_auth::generate_hash(&form.password),
|
||||||
display_name: display,
|
display_name: display,
|
||||||
|
telegram_chat_id: None,
|
||||||
|
telegram_notifications: Some(false),
|
||||||
status: "active".to_string(),
|
status: "active".to_string(),
|
||||||
created_at: now_utc(),
|
created_at: now_utc(),
|
||||||
updated_at: now_utc(),
|
updated_at: now_utc(),
|
||||||
@@ -788,7 +792,6 @@ async fn add_user(request: Request, session: Session, db: Database) -> cot::Resu
|
|||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
struct SettingsForm {
|
struct SettingsForm {
|
||||||
telegram_bot_token: String,
|
telegram_bot_token: String,
|
||||||
telegram_chat_id: String,
|
|
||||||
contact_info: String,
|
contact_info: String,
|
||||||
pricing_info: String,
|
pricing_info: String,
|
||||||
timezone: String,
|
timezone: String,
|
||||||
@@ -804,7 +807,6 @@ async fn save_settings(request: Request, session: Session, db: Database) -> cot:
|
|||||||
|
|
||||||
for (key, value) in [
|
for (key, value) in [
|
||||||
("telegram_bot_token", form.telegram_bot_token),
|
("telegram_bot_token", form.telegram_bot_token),
|
||||||
("telegram_chat_id", form.telegram_chat_id),
|
|
||||||
("contact_info", form.contact_info),
|
("contact_info", form.contact_info),
|
||||||
("pricing_info", form.pricing_info),
|
("pricing_info", form.pricing_info),
|
||||||
("timezone", form.timezone),
|
("timezone", form.timezone),
|
||||||
@@ -975,6 +977,31 @@ async fn user_activate(
|
|||||||
Redirect::new(format!("/admin/users?lang={}", lang.code())).into_response()
|
Redirect::new(format!("/admin/users?lang={}", lang.code())).into_response()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct UserTelegramForm {
|
||||||
|
telegram_chat_id: Option<String>,
|
||||||
|
telegram_notifications: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn user_update_telegram(
|
||||||
|
request: Request,
|
||||||
|
session: Session,
|
||||||
|
db: Database,
|
||||||
|
Path(user_id): Path<i64>,
|
||||||
|
) -> cot::Result<Response> {
|
||||||
|
let (lang, form): (_, UserTelegramForm) = parse_form_from_request(request).await?;
|
||||||
|
if let Err(resp) = require_auth(&session, lang).await {
|
||||||
|
return Ok(resp);
|
||||||
|
}
|
||||||
|
if let Some(mut user) = query!(User, $id == user_id).get(&db).await? {
|
||||||
|
user.telegram_chat_id = form.telegram_chat_id.filter(|s| !s.trim().is_empty());
|
||||||
|
user.telegram_notifications = Some(form.telegram_notifications.as_deref() == Some("true"));
|
||||||
|
user.updated_at = now_utc();
|
||||||
|
user.save(&db).await?;
|
||||||
|
}
|
||||||
|
Redirect::new(format!("/admin/users?lang={}", lang.code())).into_response()
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
struct AddLeadForm {
|
struct AddLeadForm {
|
||||||
name: String,
|
name: String,
|
||||||
@@ -2077,6 +2104,11 @@ pub fn admin_router() -> Router {
|
|||||||
user_activate,
|
user_activate,
|
||||||
"admin-user-activate",
|
"admin-user-activate",
|
||||||
),
|
),
|
||||||
|
Route::with_handler_and_name(
|
||||||
|
"/users/{user_id}/telegram",
|
||||||
|
user_update_telegram,
|
||||||
|
"admin-user-telegram",
|
||||||
|
),
|
||||||
Route::with_handler_and_name("/media", media_page, "admin-media"),
|
Route::with_handler_and_name("/media", media_page, "admin-media"),
|
||||||
Route::with_handler_and_name(
|
Route::with_handler_and_name(
|
||||||
"/media/{visit_id}/upload",
|
"/media/{visit_id}/upload",
|
||||||
|
|||||||
@@ -118,6 +118,8 @@ pub struct Translations {
|
|||||||
pub users_add_button: &'static str,
|
pub users_add_button: &'static str,
|
||||||
pub users_error_passwords_mismatch: &'static str,
|
pub users_error_passwords_mismatch: &'static str,
|
||||||
pub users_error_login_taken: &'static str,
|
pub users_error_login_taken: &'static str,
|
||||||
|
pub users_telegram_chat_id: &'static str,
|
||||||
|
pub users_telegram_enabled: &'static str,
|
||||||
|
|
||||||
// Settings
|
// Settings
|
||||||
pub settings_title: &'static str,
|
pub settings_title: &'static str,
|
||||||
@@ -329,6 +331,8 @@ static RU: Translations = Translations {
|
|||||||
users_add_button: "Добавить",
|
users_add_button: "Добавить",
|
||||||
users_error_passwords_mismatch: "Пароли не совпадают.",
|
users_error_passwords_mismatch: "Пароли не совпадают.",
|
||||||
users_error_login_taken: "Этот логин уже занят.",
|
users_error_login_taken: "Этот логин уже занят.",
|
||||||
|
users_telegram_chat_id: "Telegram Chat ID",
|
||||||
|
users_telegram_enabled: "Уведомления",
|
||||||
|
|
||||||
settings_title: "Настройки",
|
settings_title: "Настройки",
|
||||||
settings_key: "Параметр",
|
settings_key: "Параметр",
|
||||||
@@ -529,6 +533,8 @@ static EN: Translations = Translations {
|
|||||||
users_add_button: "Add",
|
users_add_button: "Add",
|
||||||
users_error_passwords_mismatch: "Passwords do not match.",
|
users_error_passwords_mismatch: "Passwords do not match.",
|
||||||
users_error_login_taken: "This login is already taken.",
|
users_error_login_taken: "This login is already taken.",
|
||||||
|
users_telegram_chat_id: "Telegram Chat ID",
|
||||||
|
users_telegram_enabled: "Notifications",
|
||||||
|
|
||||||
settings_title: "Settings",
|
settings_title: "Settings",
|
||||||
settings_key: "Parameter",
|
settings_key: "Parameter",
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ pub mod m_0002_visit_schedule;
|
|||||||
pub mod m_0003_visit_feedback;
|
pub mod m_0003_visit_feedback;
|
||||||
pub mod m_0004_visit_public_notes;
|
pub mod m_0004_visit_public_notes;
|
||||||
pub mod m_0005_testimonials;
|
pub mod m_0005_testimonials;
|
||||||
|
pub mod m_0006_user_telegram;
|
||||||
/// The list of migrations for current app.
|
/// The list of migrations for current app.
|
||||||
pub const MIGRATIONS: &[&::cot::db::migrations::SyncDynMigration] = &[
|
pub const MIGRATIONS: &[&::cot::db::migrations::SyncDynMigration] = &[
|
||||||
&m_0001_initial::Migration,
|
&m_0001_initial::Migration,
|
||||||
@@ -14,4 +15,5 @@ pub const MIGRATIONS: &[&::cot::db::migrations::SyncDynMigration] = &[
|
|||||||
&m_0003_visit_feedback::Migration,
|
&m_0003_visit_feedback::Migration,
|
||||||
&m_0004_visit_public_notes::Migration,
|
&m_0004_visit_public_notes::Migration,
|
||||||
&m_0005_testimonials::Migration,
|
&m_0005_testimonials::Migration,
|
||||||
|
&m_0006_user_telegram::Migration,
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
//! Migration: add telegram_chat_id and telegram_notifications to User
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone)]
|
||||||
|
pub(super) struct Migration;
|
||||||
|
impl ::cot::db::migrations::Migration for Migration {
|
||||||
|
const APP_NAME: &'static str = "web-petting";
|
||||||
|
const MIGRATION_NAME: &'static str = "m_0006_user_telegram";
|
||||||
|
const DEPENDENCIES: &'static [::cot::db::migrations::MigrationDependency] =
|
||||||
|
&[::cot::db::migrations::MigrationDependency::migration(
|
||||||
|
"web-petting",
|
||||||
|
"m_0005_testimonials",
|
||||||
|
)];
|
||||||
|
const OPERATIONS: &'static [::cot::db::migrations::Operation] = &[
|
||||||
|
::cot::db::migrations::Operation::add_field()
|
||||||
|
.table_name(::cot::db::Identifier::new("web_petting__user"))
|
||||||
|
.field(
|
||||||
|
::cot::db::migrations::Field::new(
|
||||||
|
::cot::db::Identifier::new("telegram_chat_id"),
|
||||||
|
<Option<String> as ::cot::db::DatabaseField>::TYPE,
|
||||||
|
)
|
||||||
|
.set_null(<Option<String> as ::cot::db::DatabaseField>::NULLABLE),
|
||||||
|
)
|
||||||
|
.build(),
|
||||||
|
::cot::db::migrations::Operation::add_field()
|
||||||
|
.table_name(::cot::db::Identifier::new("web_petting__user"))
|
||||||
|
.field(
|
||||||
|
::cot::db::migrations::Field::new(
|
||||||
|
::cot::db::Identifier::new("telegram_notifications"),
|
||||||
|
<Option<bool> as ::cot::db::DatabaseField>::TYPE,
|
||||||
|
)
|
||||||
|
.set_null(<Option<bool> as ::cot::db::DatabaseField>::NULLABLE),
|
||||||
|
)
|
||||||
|
.build(),
|
||||||
|
];
|
||||||
|
}
|
||||||
@@ -179,6 +179,8 @@ pub struct User {
|
|||||||
pub login: String,
|
pub login: String,
|
||||||
pub password_hash: String,
|
pub password_hash: String,
|
||||||
pub display_name: Option<String>,
|
pub display_name: Option<String>,
|
||||||
|
pub telegram_chat_id: Option<String>,
|
||||||
|
pub telegram_notifications: Option<bool>,
|
||||||
/// active | archived
|
/// active | archived
|
||||||
pub status: String,
|
pub status: String,
|
||||||
pub created_at: chrono::NaiveDateTime,
|
pub created_at: chrono::NaiveDateTime,
|
||||||
|
|||||||
+21
-7
@@ -1,8 +1,8 @@
|
|||||||
use cot::db::{Database, query};
|
use cot::db::{Database, query};
|
||||||
|
|
||||||
use crate::models::Setting;
|
use crate::models::{Setting, User};
|
||||||
|
|
||||||
/// Send a Telegram message using bot settings from DB.
|
/// Send a Telegram notification to all admins with notifications enabled.
|
||||||
/// Silently ignores errors (missing config, network issues) — notifications are best-effort.
|
/// Silently ignores errors (missing config, network issues) — notifications are best-effort.
|
||||||
pub async fn notify_new_lead(
|
pub async fn notify_new_lead(
|
||||||
db: &Database,
|
db: &Database,
|
||||||
@@ -14,10 +14,6 @@ pub async fn notify_new_lead(
|
|||||||
Some(t) if !t.is_empty() => t,
|
Some(t) if !t.is_empty() => t,
|
||||||
_ => return,
|
_ => return,
|
||||||
};
|
};
|
||||||
let chat_id = match get_setting(db, "telegram_chat_id").await {
|
|
||||||
Some(c) if !c.is_empty() => c,
|
|
||||||
_ => return,
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut text = format!("📋 Новая заявка!\n\nИмя: {name}");
|
let mut text = format!("📋 Новая заявка!\n\nИмя: {name}");
|
||||||
if let Some(phone) = phone.filter(|s| !s.is_empty()) {
|
if let Some(phone) = phone.filter(|s| !s.is_empty()) {
|
||||||
@@ -27,8 +23,25 @@ pub async fn notify_new_lead(
|
|||||||
text.push_str(&format!("\nКомментарий: {comment}"));
|
text.push_str(&format!("\nКомментарий: {comment}"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let active = "active".to_string();
|
||||||
|
let users = match query!(User, $status == active).all(db).await {
|
||||||
|
Ok(u) => u,
|
||||||
|
Err(_) => return,
|
||||||
|
};
|
||||||
|
|
||||||
|
let client = reqwest::Client::new();
|
||||||
let url = format!("https://api.telegram.org/bot{token}/sendMessage");
|
let url = format!("https://api.telegram.org/bot{token}/sendMessage");
|
||||||
let _ = reqwest::Client::new()
|
|
||||||
|
for user in &users {
|
||||||
|
if user.telegram_notifications != Some(true) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let chat_id = match &user.telegram_chat_id {
|
||||||
|
Some(id) if !id.is_empty() => id,
|
||||||
|
_ => continue,
|
||||||
|
};
|
||||||
|
|
||||||
|
let _ = client
|
||||||
.post(&url)
|
.post(&url)
|
||||||
.json(&serde_json::json!({
|
.json(&serde_json::json!({
|
||||||
"chat_id": chat_id,
|
"chat_id": chat_id,
|
||||||
@@ -36,6 +49,7 @@ pub async fn notify_new_lead(
|
|||||||
}))
|
}))
|
||||||
.send()
|
.send()
|
||||||
.await;
|
.await;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_setting(db: &Database, key_name: &str) -> Option<String> {
|
async fn get_setting(db: &Database, key_name: &str) -> Option<String> {
|
||||||
|
|||||||
+20
-11
@@ -26,15 +26,24 @@
|
|||||||
position: fixed; bottom: 0; left: 0; right: 0; z-index: 30;
|
position: fixed; bottom: 0; left: 0; right: 0; z-index: 30;
|
||||||
background: #fff; border-top: 1px solid #e8e8e8;
|
background: #fff; border-top: 1px solid #e8e8e8;
|
||||||
display: flex; height: 3.5rem;
|
display: flex; height: 3.5rem;
|
||||||
|
overflow-x: auto; -webkit-overflow-scrolling: touch;
|
||||||
|
scrollbar-width: none;
|
||||||
}
|
}
|
||||||
|
.bottom-tabs::-webkit-scrollbar { display: none; }
|
||||||
.bottom-tabs a {
|
.bottom-tabs a {
|
||||||
flex: 1; display: flex; flex-direction: column; align-items: center;
|
flex: 0 0 auto; min-width: 3.2rem; padding: 0 0.45rem;
|
||||||
|
display: flex; flex-direction: column; align-items: center;
|
||||||
justify-content: center; text-decoration: none; color: #999;
|
justify-content: center; text-decoration: none; color: #999;
|
||||||
font-size: 0.65rem; font-weight: 600; gap: 0.15rem;
|
font-size: 0.6rem; font-weight: 600; gap: 0.1rem;
|
||||||
transition: color 0.15s;
|
transition: color 0.15s; white-space: nowrap;
|
||||||
}
|
}
|
||||||
.bottom-tabs a .tab-icon { font-size: 1.25rem; line-height: 1; }
|
.bottom-tabs a .tab-icon { font-size: 1.25rem; line-height: 1; }
|
||||||
|
.bottom-tabs a .tab-label { display: block; }
|
||||||
.bottom-tabs a.is-active { color: var(--accent); }
|
.bottom-tabs a.is-active { color: var(--accent); }
|
||||||
|
@media (max-width: 400px) {
|
||||||
|
.bottom-tabs a .tab-label { display: none; }
|
||||||
|
.bottom-tabs a { min-width: 2.8rem; padding: 0 0.3rem; }
|
||||||
|
}
|
||||||
|
|
||||||
/* ── Desktop: hide bottom tabs, show top nav ── */
|
/* ── Desktop: hide bottom tabs, show top nav ── */
|
||||||
.desktop-nav { display: none; }
|
.desktop-nav { display: none; }
|
||||||
@@ -135,28 +144,28 @@
|
|||||||
<!-- Bottom tabs (mobile) -->
|
<!-- Bottom tabs (mobile) -->
|
||||||
<nav class="bottom-tabs">
|
<nav class="bottom-tabs">
|
||||||
<a href="/admin/?lang={{ lang.code() }}" {% if active_page == "dashboard" %}class="is-active"{% endif %}>
|
<a href="/admin/?lang={{ lang.code() }}" {% if active_page == "dashboard" %}class="is-active"{% endif %}>
|
||||||
<span class="tab-icon">🏠</span>{{ t.dashboard_title }}
|
<span class="tab-icon">🏠</span><span class="tab-label">{{ t.dashboard_title }}</span>
|
||||||
</a>
|
</a>
|
||||||
<a href="/admin/leads?lang={{ lang.code() }}" {% if active_page == "leads" %}class="is-active"{% endif %}>
|
<a href="/admin/leads?lang={{ lang.code() }}" {% if active_page == "leads" %}class="is-active"{% endif %}>
|
||||||
<span class="tab-icon">📋</span>{{ t.nav_leads }}
|
<span class="tab-icon">📋</span><span class="tab-label">{{ t.nav_leads }}</span>
|
||||||
</a>
|
</a>
|
||||||
<a href="/admin/clients?lang={{ lang.code() }}" {% if active_page == "clients" %}class="is-active"{% endif %}>
|
<a href="/admin/clients?lang={{ lang.code() }}" {% if active_page == "clients" %}class="is-active"{% endif %}>
|
||||||
<span class="tab-icon">👥</span>{{ t.nav_clients }}
|
<span class="tab-icon">👥</span><span class="tab-label">{{ t.nav_clients }}</span>
|
||||||
</a>
|
</a>
|
||||||
<a href="/admin/schedule?lang={{ lang.code() }}" {% if active_page == "schedule" %}class="is-active"{% endif %}>
|
<a href="/admin/schedule?lang={{ lang.code() }}" {% if active_page == "schedule" %}class="is-active"{% endif %}>
|
||||||
<span class="tab-icon">📅</span>{{ t.nav_schedule }}
|
<span class="tab-icon">📅</span><span class="tab-label">{{ t.nav_schedule }}</span>
|
||||||
</a>
|
</a>
|
||||||
<a href="/admin/media?lang={{ lang.code() }}" {% if active_page == "media" %}class="is-active"{% endif %}>
|
<a href="/admin/media?lang={{ lang.code() }}" {% if active_page == "media" %}class="is-active"{% endif %}>
|
||||||
<span class="tab-icon">📷</span>{{ t.nav_media }}
|
<span class="tab-icon">📷</span><span class="tab-label">{{ t.nav_media }}</span>
|
||||||
</a>
|
</a>
|
||||||
<a href="/admin/testimonials?lang={{ lang.code() }}" {% if active_page == "testimonials" %}class="is-active"{% endif %}>
|
<a href="/admin/testimonials?lang={{ lang.code() }}" {% if active_page == "testimonials" %}class="is-active"{% endif %}>
|
||||||
<span class="tab-icon">💬</span>{{ t.nav_testimonials }}
|
<span class="tab-icon">💬</span><span class="tab-label">{{ t.nav_testimonials }}</span>
|
||||||
</a>
|
</a>
|
||||||
<a href="/admin/users?lang={{ lang.code() }}" {% if active_page == "users" %}class="is-active"{% endif %}>
|
<a href="/admin/users?lang={{ lang.code() }}" {% if active_page == "users" %}class="is-active"{% endif %}>
|
||||||
<span class="tab-icon">🔑</span>{{ t.nav_users }}
|
<span class="tab-icon">🔑</span><span class="tab-label">{{ t.nav_users }}</span>
|
||||||
</a>
|
</a>
|
||||||
<a href="/admin/settings?lang={{ lang.code() }}" {% if active_page == "settings" %}class="is-active"{% endif %}>
|
<a href="/admin/settings?lang={{ lang.code() }}" {% if active_page == "settings" %}class="is-active"{% endif %}>
|
||||||
<span class="tab-icon">⚙️</span>{{ t.nav_settings }}
|
<span class="tab-icon">⚙️</span><span class="tab-label">{{ t.nav_settings }}</span>
|
||||||
</a>
|
</a>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
|
|||||||
@@ -20,12 +20,6 @@
|
|||||||
<input class="input" type="text" name="telegram_bot_token" value="{% for s in &settings %}{% if s.key == "telegram_bot_token" %}{{ s.value }}{% endif %}{% endfor %}">
|
<input class="input" type="text" name="telegram_bot_token" value="{% for s in &settings %}{% if s.key == "telegram_bot_token" %}{{ s.value }}{% endif %}{% endfor %}">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
|
||||||
<label class="label">{{ t.settings_telegram_chat_id }}</label>
|
|
||||||
<div class="control">
|
|
||||||
<input class="input" type="text" name="telegram_chat_id" value="{% for s in &settings %}{% if s.key == "telegram_chat_id" %}{{ s.value }}{% endif %}{% endfor %}">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label class="label">{{ t.settings_contact_info }}</label>
|
<label class="label">{{ t.settings_contact_info }}</label>
|
||||||
<div class="control">
|
<div class="control">
|
||||||
|
|||||||
@@ -17,6 +17,29 @@
|
|||||||
<div class="item-card-meta">
|
<div class="item-card-meta">
|
||||||
<span>🕐 {{ user.created_at.format("%d.%m.%Y %H:%M") }}</span>
|
<span>🕐 {{ user.created_at.format("%d.%m.%Y %H:%M") }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
{% if user.status == "active" %}
|
||||||
|
<form method="post" action="/admin/users/{{ user.id }}/telegram" style="margin-top:0.5rem; padding-top:0.5rem; border-top:1px solid #eee;">
|
||||||
|
<div class="columns is-mobile is-vcentered" style="margin-bottom:0;">
|
||||||
|
<div class="column">
|
||||||
|
<div class="field" style="margin-bottom:0;">
|
||||||
|
<label class="label is-small">{{ t.users_telegram_chat_id }}</label>
|
||||||
|
<div class="control">
|
||||||
|
<input class="input is-small" type="text" name="telegram_chat_id" placeholder="123456789" value="{{ user.telegram_chat_id.as_deref().unwrap_or_default() }}">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="column is-narrow">
|
||||||
|
<label class="checkbox is-small">
|
||||||
|
<input type="checkbox" name="telegram_notifications" value="true" {% if user.telegram_notifications == Some(true) %}checked{% endif %}>
|
||||||
|
{{ t.users_telegram_enabled }}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="column is-narrow">
|
||||||
|
<button type="submit" class="button is-small is-info is-outlined">💾</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
{% endif %}
|
||||||
<div class="item-card-actions">
|
<div class="item-card-actions">
|
||||||
{% if user.status == "active" %}
|
{% if user.status == "active" %}
|
||||||
<form method="post" action="/admin/users/{{ user.id }}/archive">
|
<form method="post" action="/admin/users/{{ user.id }}/archive">
|
||||||
|
|||||||
@@ -304,7 +304,7 @@
|
|||||||
|
|
||||||
/* ── Mobile ── */
|
/* ── Mobile ── */
|
||||||
@media (max-width: 600px) {
|
@media (max-width: 600px) {
|
||||||
.hero { padding: 6.5rem 1rem 3rem; }
|
.hero { padding: 6.5rem 1rem 10rem; }
|
||||||
.section { padding: 3rem 1rem; }
|
.section { padding: 3rem 1rem; }
|
||||||
.form-section { padding: 3rem 1rem; }
|
.form-section { padding: 3rem 1rem; }
|
||||||
.form-wrapper { padding: 1.75rem 1.25rem; }
|
.form-wrapper { padding: 1.75rem 1.25rem; }
|
||||||
|
|||||||
Reference in New Issue
Block a user