Kgshop commited on
Commit
793bf62
·
verified ·
1 Parent(s): ead68df

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +95 -103
app.py CHANGED
@@ -11,6 +11,7 @@ import time
11
  from datetime import datetime
12
  import logging
13
  import threading
 
14
  from huggingface_hub import HfApi, hf_hub_download
15
  from huggingface_hub.utils import RepositoryNotFoundError
16
 
@@ -165,6 +166,13 @@ def verify_telegram_data(init_data_str):
165
  logging.error(f"Error verifying Telegram data: {e}")
166
  return None, False
167
 
 
 
 
 
 
 
 
168
  TEMPLATE = """
169
  <!DOCTYPE html>
170
  <html lang="ru">
@@ -175,30 +183,31 @@ TEMPLATE = """
175
  <script src="https://telegram.org/js/telegram-web-app.js"></script>
176
  <link rel="preconnect" href="https://fonts.googleapis.com">
177
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
178
- <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
179
  <style>
180
  :root {
181
- --tg-theme-bg-color: #111111;
182
- --tg-theme-text-color: #ffffff;
183
- --tg-theme-hint-color: #aaaaaa;
184
- --tg-theme-link-color: #FFC107;
185
- --tg-theme-button-color: #FFC107;
186
- --tg-theme-button-text-color: #000000;
187
- --tg-theme-secondary-bg-color: #1e1e1e;
188
-
189
  --brand-yellow: #FFC107;
190
- --brand-black: #111111;
191
- --card-bg: #222222;
192
- --text-color: #ffffff;
193
- --text-secondary-color: #aaaaaa;
194
- --border-radius-m: 14px;
195
- --border-radius-l: 18px;
196
- --padding-m: 18px;
197
- --padding-l: 28px;
 
198
  --font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
199
- --shadow-color: rgba(0, 0, 0, 0.4);
200
- --shadow-light: 0 4px 15px var(--shadow-color);
201
- --shadow-medium: 0 6px 25px var(--shadow-color);
 
 
 
 
 
 
 
 
202
  }
203
  * { box-sizing: border-box; margin: 0; padding: 0; }
204
  html, body {
@@ -217,56 +226,65 @@ TEMPLATE = """
217
  margin: 0 auto;
218
  display: flex;
219
  flex-direction: column;
220
- gap: var(--padding-l);
 
221
  }
222
  .header {
223
  text-align: center;
224
  padding: var(--padding-m) 0;
 
225
  }
226
  .logo {
227
- font-size: 2em;
228
- font-weight: 700;
229
  color: var(--brand-yellow);
 
 
230
  }
231
  .welcome-text {
232
  font-size: 1.1em;
233
  color: var(--text-secondary-color);
234
- margin-top: 8px;
235
  }
236
  .bonus-card {
237
- background: linear-gradient(145deg, #2a2a2a, #1c1c1c);
238
  border-radius: var(--border-radius-l);
239
  padding: var(--padding-l);
240
  text-align: center;
241
- box-shadow: var(--shadow-medium);
242
  border: 1px solid rgba(255, 193, 7, 0.2);
 
 
 
 
 
 
 
243
  }
244
  .bonus-label {
245
- font-size: 1.2em;
246
  font-weight: 500;
247
  color: var(--text-secondary-color);
248
- margin-bottom: 12px;
 
 
249
  }
250
  .bonus-amount {
251
- font-size: 3.5em;
252
- font-weight: 700;
253
  color: var(--brand-yellow);
254
- letter-spacing: -1px;
255
- }
256
- .bonus-amount-minor {
257
- font-size: 0.5em;
258
- opacity: 0.8;
259
  }
260
  .history-section {
261
  background-color: var(--card-bg);
262
  border-radius: var(--border-radius-l);
263
  padding: var(--padding-l);
264
- box-shadow: var(--shadow-light);
265
  border: 1px solid rgba(255, 255, 255, 0.08);
 
266
  }
267
  .history-title {
268
- font-size: 1.5em;
269
- font-weight: 600;
270
  margin-bottom: var(--padding-m);
271
  padding-bottom: var(--padding-m);
272
  border-bottom: 1px solid rgba(255, 255, 255, 0.1);
@@ -282,40 +300,21 @@ TEMPLATE = """
282
  display: flex;
283
  justify-content: space-between;
284
  align-items: center;
285
- padding: 16px 4px;
286
  border-bottom: 1px solid rgba(255, 255, 255, 0.05);
 
287
  }
288
- .history-item:last-child {
289
- border-bottom: none;
290
- }
291
- .history-details {
292
- display: flex;
293
- flex-direction: column;
294
- }
295
- .history-description {
296
- font-size: 1em;
297
- font-weight: 500;
298
- }
299
- .history-date {
300
- font-size: 0.85em;
301
- color: var(--text-secondary-color);
302
- margin-top: 4px;
303
- }
304
- .history-amount {
305
- font-size: 1.2em;
306
- font-weight: 600;
307
- }
308
- .history-amount.accrual {
309
- color: #4CAF50;
310
- }
311
- .history-amount.deduction {
312
- color: #F44336;
313
- }
314
- .no-history {
315
- text-align: center;
316
- color: var(--text-secondary-color);
317
- padding: 2rem 0;
318
  }
 
 
 
 
 
 
 
 
319
  </style>
320
  </head>
321
  <body>
@@ -357,13 +356,11 @@ TEMPLATE = """
357
 
358
  function applyTheme(themeParams) {
359
  const root = document.documentElement;
360
- if (themeParams.bg_color) root.style.setProperty('--tg-theme-bg-color', themeParams.bg_color);
361
- if (themeParams.text_color) root.style.setProperty('--tg-theme-text-color', themeParams.text_color);
362
- if (themeParams.hint_color) root.style.setProperty('--tg-theme-hint-color', themeParams.hint_color);
363
- if (themeParams.link_color) root.style.setProperty('--tg-theme-link-color', themeParams.link_color);
364
- if (themeParams.button_color) root.style.setProperty('--tg-theme-button-color', themeParams.button_color);
365
- if (themeParams.button_text_color) root.style.setProperty('--tg-theme-button-text-color', themeParams.button_text_color);
366
- if (themeParams.secondary_bg_color) root.style.setProperty('--tg-theme-secondary-bg-color', themeParams.secondary_bg_color);
367
  }
368
 
369
  function setupTelegram() {
@@ -387,24 +384,18 @@ TEMPLATE = """
387
  if (!userIdForTest) {
388
  fetch('/verify', {
389
  method: 'POST',
390
- headers: {
391
- 'Content-Type': 'application/json',
392
- 'Accept': 'application/json'
393
- },
394
  body: JSON.stringify({ initData: tg.initData }),
395
  })
396
  .then(response => response.json())
397
  .then(data => {
398
  if (data.status === 'ok' && data.verified && data.user_id) {
399
- console.log('Backend verification successful. Reloading with user data.');
400
  window.location.replace('/?user_id_for_test=' + data.user_id);
401
  } else {
402
- console.warn('Backend verification failed:', data.message);
403
  document.body.style.visibility = 'visible';
404
  }
405
  })
406
  .catch(error => {
407
- console.error('Error sending initData for verification:', error);
408
  document.body.style.visibility = 'visible';
409
  });
410
  } else {
@@ -467,7 +458,8 @@ ADMIN_TEMPLATE = """
467
  h1 { text-align: center; color: var(--admin-secondary); margin-bottom: var(--padding); font-weight: 600; }
468
  .controls-bar { display: flex; gap: 1rem; align-items: center; background: var(--admin-card-bg); padding: var(--padding); border-radius: var(--border-radius); box-shadow: 0 4px 15px var(--admin-shadow); border: 1px solid var(--admin-border); margin-bottom: var(--padding); }
469
  .controls-bar input[type="text"] { flex-grow: 1; padding: 12px 15px; font-size: 1.1em; border-radius: 8px; border: 1px solid var(--admin-border); box-sizing: border-box; }
470
- .btn { padding: 12px 20px; font-size: 1em; border: none; border-radius: 8px; font-weight: 600; cursor: pointer; transition: background-color 0.2s ease; }
 
471
  .btn-primary { background-color: var(--admin-primary); color: #000; }
472
  .btn-primary:hover { background-color: var(--admin-primary-dark); }
473
  .user-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); gap: var(--padding); margin-top: var(--padding); }
@@ -477,6 +469,7 @@ ADMIN_TEMPLATE = """
477
  .user-info img { width: 60px; height: 60px; border-radius: 50%; object-fit: cover; border: 3px solid var(--admin-border); background-color: #eee; }
478
  .user-details .name { font-weight: 600; font-size: 1.2em; }
479
  .user-details .username { color: var(--admin-secondary); font-size: 0.95em; }
 
480
  .user-bonuses { text-align: center; margin-bottom: 1rem; }
481
  .user-bonuses .label { font-size: 0.9em; color: var(--admin-secondary); }
482
  .user-bonuses .amount { font-size: 1.8em; font-weight: 700; color: var(--admin-primary-dark); }
@@ -522,12 +515,13 @@ ADMIN_TEMPLATE = """
522
  {% if users %}
523
  <div class="user-grid" id="userGrid">
524
  {% for user in users|sort(attribute='visited_at', reverse=true) %}
525
- <div class="user-card" data-user-id="{{ user.id }}" data-search-term="{{ user.first_name|lower }} {{ user.last_name|lower }} {{ user.username|lower }} {{ user.id }}">
526
  <div class="user-info">
527
  <img src="{{ user.photo_url if user.photo_url else 'data:image/svg+xml;charset=UTF-8,%3csvg xmlns=%27http://www.w3.org/2000/svg%27 viewBox=%270 0 100 100%27%3e%3crect width=%27100%27 height=%27100%27 fill=%27%23e9ecef%27/%3e%3ctext x=%2750%25%27 y=%2755%25%27 dominant-baseline=%27middle%27 text-anchor=%27middle%27 font-size=%2745%27 font-family=%27sans-serif%27 fill=%27%23adb5bd%27%3e?%3c/text%3e%3c/svg%3e' }}" alt="User Avatar">
528
  <div class="user-details">
529
  <div class="name">{{ user.first_name or '' }} {{ user.last_name or '' }}</div>
530
  <div class="username">@{{ user.username or 'N/A' }}</div>
 
531
  </div>
532
  </div>
533
  <div class="user-bonuses">
@@ -588,12 +582,8 @@ ADMIN_TEMPLATE = """
588
  <h2>Добавить нового клиента</h2>
589
  </div>
590
  <div class="form-group" style="margin-bottom: 1rem;">
591
- <label for="newClientFirstName">Имя</label>
592
- <input type="text" id="newClientFirstName" placeholder="Иван">
593
- </div>
594
- <div class="form-group" style="margin-bottom: 1rem;">
595
- <label for="newClientLastName">Фамилия</label>
596
- <input type="text" id="newClientLastName" placeholder="Иванов">
597
  </div>
598
  <div class="form-group" style="margin-bottom: 1.5rem;">
599
  <label for="newClientPhone">Номер телефона (уникальный)</label>
@@ -660,8 +650,7 @@ ADMIN_TEMPLATE = """
660
  }
661
 
662
  function openAddClientModal() {
663
- document.getElementById('newClientFirstName').value = '';
664
- document.getElementById('newClientLastName').value = '';
665
  document.getElementById('newClientPhone').value = '';
666
  document.getElementById('addClientStatus').textContent = '';
667
  addClientModal.style.display = 'block';
@@ -732,12 +721,11 @@ ADMIN_TEMPLATE = """
732
  statusEl.textContent = 'Сохранение...';
733
 
734
  const payload = {
735
- first_name: document.getElementById('newClientFirstName').value.trim(),
736
- last_name: document.getElementById('newClientLastName').value.trim(),
737
  phone_number: document.getElementById('newClientPhone').value.trim(),
738
  };
739
 
740
- if (!payload.first_name || !payload.phone_number) {
741
  statusEl.style.color = 'var(--admin-danger)';
742
  statusEl.textContent = 'Имя и номер телефона обязательны.';
743
  return;
@@ -828,9 +816,12 @@ def verify_data():
828
  'visited_at': now.timestamp(),
829
  'visited_at_str': now.strftime('%Y-%m-%d %H:%M:%S')
830
  })
 
 
831
  else:
832
  user_entry = {
833
  'id': user_id,
 
834
  'first_name': user_info_dict.get('first_name'),
835
  'last_name': user_info_dict.get('last_name'),
836
  'username': user_info_dict.get('username'),
@@ -867,22 +858,23 @@ def add_client():
867
  try:
868
  data = request.get_json()
869
  phone_number = data.get('phone_number')
870
- first_name = data.get('first_name')
871
- last_name = data.get('last_name')
872
 
873
- if not phone_number or not first_name:
874
  return jsonify({"status": "error", "message": "Имя и номер телефона обязательны."}), 400
875
 
876
  all_data = load_visitor_data()
877
 
878
- if phone_number in all_data:
879
- return jsonify({"status": "error", "message": "Клиент с таким номером телефона уже существует."}), 409
 
880
 
881
  now = datetime.now()
882
  new_client = {
883
  'id': phone_number,
884
- 'first_name': first_name,
885
- 'last_name': last_name,
 
886
  'username': phone_number,
887
  'photo_url': None,
888
  'language_code': 'ru',
 
11
  from datetime import datetime
12
  import logging
13
  import threading
14
+ import random
15
  from huggingface_hub import HfApi, hf_hub_download
16
  from huggingface_hub.utils import RepositoryNotFoundError
17
 
 
166
  logging.error(f"Error verifying Telegram data: {e}")
167
  return None, False
168
 
169
+ def generate_unique_bonus_id(all_data):
170
+ existing_ids = {user.get('bonus_id') for user in all_data.values() if 'bonus_id' in user}
171
+ while True:
172
+ new_id = str(random.randint(10000, 99999))
173
+ if new_id not in existing_ids:
174
+ return new_id
175
+
176
  TEMPLATE = """
177
  <!DOCTYPE html>
178
  <html lang="ru">
 
183
  <script src="https://telegram.org/js/telegram-web-app.js"></script>
184
  <link rel="preconnect" href="https://fonts.googleapis.com">
185
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
186
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap" rel="stylesheet">
187
  <style>
188
  :root {
 
 
 
 
 
 
 
 
189
  --brand-yellow: #FFC107;
190
+ --brand-yellow-glow: rgba(255, 193, 7, 0.5);
191
+ --brand-black: #0d0d0d;
192
+ --card-bg: #1a1a1a;
193
+ --card-bg-gradient: radial-gradient(circle, #2a2a2a 0%, #1a1a1a 100%);
194
+ --text-color: #f5f5f7;
195
+ --text-secondary-color: #888888;
196
+ --border-radius-l: 24px;
197
+ --padding-m: 20px;
198
+ --padding-l: 30px;
199
  --font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
200
+ --shadow-color: rgba(0, 0, 0, 0.6);
201
+ --shadow-glow: 0 0 40px var(--brand-yellow-glow);
202
+ }
203
+ @keyframes fadeIn {
204
+ from { opacity: 0; transform: translateY(20px); }
205
+ to { opacity: 1; transform: translateY(0); }
206
+ }
207
+ @keyframes pulseGlow {
208
+ 0% { text-shadow: 0 0 4px var(--brand-yellow-glow), 0 0 10px var(--brand-yellow-glow); }
209
+ 50% { text-shadow: 0 0 10px var(--brand-yellow-glow), 0 0 30px var(--brand-yellow-glow); }
210
+ 100% { text-shadow: 0 0 4px var(--brand-yellow-glow), 0 0 10px var(--brand-yellow-glow); }
211
  }
212
  * { box-sizing: border-box; margin: 0; padding: 0; }
213
  html, body {
 
226
  margin: 0 auto;
227
  display: flex;
228
  flex-direction: column;
229
+ gap: 2rem;
230
+ animation: fadeIn 0.8s ease-out forwards;
231
  }
232
  .header {
233
  text-align: center;
234
  padding: var(--padding-m) 0;
235
+ animation-delay: 0.1s;
236
  }
237
  .logo {
238
+ font-size: 2.5em;
239
+ font-weight: 800;
240
  color: var(--brand-yellow);
241
+ letter-spacing: 2px;
242
+ text-transform: uppercase;
243
  }
244
  .welcome-text {
245
  font-size: 1.1em;
246
  color: var(--text-secondary-color);
247
+ margin-top: 10px;
248
  }
249
  .bonus-card {
250
+ background: var(--card-bg-gradient);
251
  border-radius: var(--border-radius-l);
252
  padding: var(--padding-l);
253
  text-align: center;
 
254
  border: 1px solid rgba(255, 193, 7, 0.2);
255
+ box-shadow: 0 10px 30px var(--shadow-color);
256
+ transition: transform 0.3s ease, box-shadow 0.3s ease;
257
+ animation-delay: 0.2s;
258
+ }
259
+ .bonus-card:hover {
260
+ transform: translateY(-8px) scale(1.02);
261
+ box-shadow: 0 15px 40px var(--shadow-color), var(--shadow-glow);
262
  }
263
  .bonus-label {
264
+ font-size: 1.3em;
265
  font-weight: 500;
266
  color: var(--text-secondary-color);
267
+ margin-bottom: 15px;
268
+ text-transform: uppercase;
269
+ letter-spacing: 1px;
270
  }
271
  .bonus-amount {
272
+ font-size: 4.5em;
273
+ font-weight: 800;
274
  color: var(--brand-yellow);
275
+ line-height: 1;
276
+ animation: pulseGlow 3s infinite ease-in-out;
 
 
 
277
  }
278
  .history-section {
279
  background-color: var(--card-bg);
280
  border-radius: var(--border-radius-l);
281
  padding: var(--padding-l);
 
282
  border: 1px solid rgba(255, 255, 255, 0.08);
283
+ animation-delay: 0.3s;
284
  }
285
  .history-title {
286
+ font-size: 1.6em;
287
+ font-weight: 700;
288
  margin-bottom: var(--padding-m);
289
  padding-bottom: var(--padding-m);
290
  border-bottom: 1px solid rgba(255, 255, 255, 0.1);
 
300
  display: flex;
301
  justify-content: space-between;
302
  align-items: center;
303
+ padding: 18px 8px;
304
  border-bottom: 1px solid rgba(255, 255, 255, 0.05);
305
+ transition: background-color 0.2s ease;
306
  }
307
+ .history-item:hover {
308
+ background-color: rgba(255, 255, 255, 0.03);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
309
  }
310
+ .history-item:last-child { border-bottom: none; }
311
+ .history-details { display: flex; flex-direction: column; }
312
+ .history-description { font-size: 1.05em; font-weight: 500; }
313
+ .history-date { font-size: 0.85em; color: var(--text-secondary-color); margin-top: 5px; }
314
+ .history-amount { font-size: 1.3em; font-weight: 700; }
315
+ .history-amount.accrual { color: #28a745; }
316
+ .history-amount.deduction { color: #dc3545; }
317
+ .no-history { text-align: center; color: var(--text-secondary-color); padding: 3rem 0; font-size: 1.1em; }
318
  </style>
319
  </head>
320
  <body>
 
356
 
357
  function applyTheme(themeParams) {
358
  const root = document.documentElement;
359
+ if (themeParams.bg_color) root.style.setProperty('--brand-black', themeParams.bg_color);
360
+ if (themeParams.text_color) root.style.setProperty('--text-color', themeParams.text_color);
361
+ if (themeParams.hint_color) root.style.setProperty('--text-secondary-color', themeParams.hint_color);
362
+ if (themeParams.button_color) root.style.setProperty('--brand-yellow', themeParams.button_color);
363
+ if (themeParams.secondary_bg_color) root.style.setProperty('--card-bg', themeParams.secondary_bg_color);
 
 
364
  }
365
 
366
  function setupTelegram() {
 
384
  if (!userIdForTest) {
385
  fetch('/verify', {
386
  method: 'POST',
387
+ headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' },
 
 
 
388
  body: JSON.stringify({ initData: tg.initData }),
389
  })
390
  .then(response => response.json())
391
  .then(data => {
392
  if (data.status === 'ok' && data.verified && data.user_id) {
 
393
  window.location.replace('/?user_id_for_test=' + data.user_id);
394
  } else {
 
395
  document.body.style.visibility = 'visible';
396
  }
397
  })
398
  .catch(error => {
 
399
  document.body.style.visibility = 'visible';
400
  });
401
  } else {
 
458
  h1 { text-align: center; color: var(--admin-secondary); margin-bottom: var(--padding); font-weight: 600; }
459
  .controls-bar { display: flex; gap: 1rem; align-items: center; background: var(--admin-card-bg); padding: var(--padding); border-radius: var(--border-radius); box-shadow: 0 4px 15px var(--admin-shadow); border: 1px solid var(--admin-border); margin-bottom: var(--padding); }
460
  .controls-bar input[type="text"] { flex-grow: 1; padding: 12px 15px; font-size: 1.1em; border-radius: 8px; border: 1px solid var(--admin-border); box-sizing: border-box; }
461
+ .btn { padding: 12px 20px; font-size: 1em; border: none; border-radius: 8px; font-weight: 600; cursor: pointer; transition: background-color 0.2s ease, transform 0.2s ease; }
462
+ .btn:hover { transform: translateY(-2px); }
463
  .btn-primary { background-color: var(--admin-primary); color: #000; }
464
  .btn-primary:hover { background-color: var(--admin-primary-dark); }
465
  .user-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); gap: var(--padding); margin-top: var(--padding); }
 
469
  .user-info img { width: 60px; height: 60px; border-radius: 50%; object-fit: cover; border: 3px solid var(--admin-border); background-color: #eee; }
470
  .user-details .name { font-weight: 600; font-size: 1.2em; }
471
  .user-details .username { color: var(--admin-secondary); font-size: 0.95em; }
472
+ .user-details .bonus-id { color: var(--admin-primary-dark); font-size: 0.9em; font-weight: 500; margin-top: 4px; background: #fff8e1; padding: 2px 6px; border-radius: 4px; display: inline-block;}
473
  .user-bonuses { text-align: center; margin-bottom: 1rem; }
474
  .user-bonuses .label { font-size: 0.9em; color: var(--admin-secondary); }
475
  .user-bonuses .amount { font-size: 1.8em; font-weight: 700; color: var(--admin-primary-dark); }
 
515
  {% if users %}
516
  <div class="user-grid" id="userGrid">
517
  {% for user in users|sort(attribute='visited_at', reverse=true) %}
518
+ <div class="user-card" data-user-id="{{ user.id }}" data-search-term="{{ user.first_name|lower }} {{ user.last_name|lower }} {{ user.username|lower }} {{ user.id }} {{ user.bonus_id }}">
519
  <div class="user-info">
520
  <img src="{{ user.photo_url if user.photo_url else 'data:image/svg+xml;charset=UTF-8,%3csvg xmlns=%27http://www.w3.org/2000/svg%27 viewBox=%270 0 100 100%27%3e%3crect width=%27100%27 height=%27100%27 fill=%27%23e9ecef%27/%3e%3ctext x=%2750%25%27 y=%2755%25%27 dominant-baseline=%27middle%27 text-anchor=%27middle%27 font-size=%2745%27 font-family=%27sans-serif%27 fill=%27%23adb5bd%27%3e?%3c/text%3e%3c/svg%3e' }}" alt="User Avatar">
521
  <div class="user-details">
522
  <div class="name">{{ user.first_name or '' }} {{ user.last_name or '' }}</div>
523
  <div class="username">@{{ user.username or 'N/A' }}</div>
524
+ <div class="bonus-id">ID: {{ user.bonus_id or 'N/A' }}</div>
525
  </div>
526
  </div>
527
  <div class="user-bonuses">
 
582
  <h2>Добавить нового клиента</h2>
583
  </div>
584
  <div class="form-group" style="margin-bottom: 1rem;">
585
+ <label for="newClientName">Имя</label>
586
+ <input type="text" id="newClientName" placeholder="Иван Иванов">
 
 
 
 
587
  </div>
588
  <div class="form-group" style="margin-bottom: 1.5rem;">
589
  <label for="newClientPhone">Номер телефона (уникальный)</label>
 
650
  }
651
 
652
  function openAddClientModal() {
653
+ document.getElementById('newClientName').value = '';
 
654
  document.getElementById('newClientPhone').value = '';
655
  document.getElementById('addClientStatus').textContent = '';
656
  addClientModal.style.display = 'block';
 
721
  statusEl.textContent = 'Сохранение...';
722
 
723
  const payload = {
724
+ name: document.getElementById('newClientName').value.trim(),
 
725
  phone_number: document.getElementById('newClientPhone').value.trim(),
726
  };
727
 
728
+ if (!payload.name || !payload.phone_number) {
729
  statusEl.style.color = 'var(--admin-danger)';
730
  statusEl.textContent = 'Имя и номер телефона обязательны.';
731
  return;
 
816
  'visited_at': now.timestamp(),
817
  'visited_at_str': now.strftime('%Y-%m-%d %H:%M:%S')
818
  })
819
+ if 'bonus_id' not in user_entry or not user_entry['bonus_id']:
820
+ user_entry['bonus_id'] = generate_unique_bonus_id(all_data)
821
  else:
822
  user_entry = {
823
  'id': user_id,
824
+ 'bonus_id': generate_unique_bonus_id(all_data),
825
  'first_name': user_info_dict.get('first_name'),
826
  'last_name': user_info_dict.get('last_name'),
827
  'username': user_info_dict.get('username'),
 
858
  try:
859
  data = request.get_json()
860
  phone_number = data.get('phone_number')
861
+ name = data.get('name')
 
862
 
863
+ if not phone_number or not name:
864
  return jsonify({"status": "error", "message": "Имя и номер телефона обязательны."}), 400
865
 
866
  all_data = load_visitor_data()
867
 
868
+ for user in all_data.values():
869
+ if user.get('id') == phone_number or user.get('username') == phone_number:
870
+ return jsonify({"status": "error", "message": "Клиент с таким номером телефона уже существует."}), 409
871
 
872
  now = datetime.now()
873
  new_client = {
874
  'id': phone_number,
875
+ 'bonus_id': generate_unique_bonus_id(all_data),
876
+ 'first_name': name,
877
+ 'last_name': "",
878
  'username': phone_number,
879
  'photo_url': None,
880
  'language_code': 'ru',