Aleksmorshen commited on
Commit
3ec3bd6
·
verified ·
1 Parent(s): 7a9f6f2

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +295 -581
app.py CHANGED
@@ -55,7 +55,6 @@ def index():
55
  --success-color: #28a745;
56
  --error-color: #dc3545;
57
  --font-family: 'Inter', sans-serif;
58
- --nav-height: 60px;
59
  }
60
 
61
  * {
@@ -65,9 +64,7 @@ def index():
65
  -webkit-tap-highlight-color: transparent;
66
  }
67
 
68
- html {
69
- font-size: 16px;
70
- }
71
 
72
  body {
73
  font-family: var(--font-family);
@@ -79,7 +76,6 @@ def index():
79
  display: flex;
80
  align-items: center;
81
  justify-content: center;
82
- position: relative; /* Needed for fixed elements positioning context */
83
  }
84
 
85
  .main-container {
@@ -90,7 +86,6 @@ def index():
90
  transition: opacity 0.3s ease;
91
  }
92
 
93
- /* --- Login View --- */
94
  #login-view {
95
  display: flex;
96
  flex-direction: column;
@@ -121,30 +116,37 @@ def index():
121
  margin-bottom: 40px;
122
  }
123
 
124
- /* --- App View Container --- */
125
  #app-view {
126
  display: none;
127
  width: 100%;
128
- height: 100%; /* Full height initially */
129
  flex-direction: column;
130
- padding-bottom: var(--nav-height); /* Space for nav bar */
131
  }
132
 
133
- /* --- Content Area (Chats + Browser) --- */
134
- #app-content {
135
- flex-grow: 1;
 
 
 
 
 
 
 
 
136
  display: flex;
137
- overflow: hidden; /* Prevent content overflow */
 
 
 
138
  }
139
 
140
- /* --- Chat List View --- */
141
  #chatroom-list-view {
142
  display: flex;
143
  flex-direction: column;
144
  height: 100%;
145
- width: 100%; /* Full width on mobile */
146
  background-color: var(--bg-secondary);
147
- flex-shrink: 0;
148
  }
149
 
150
  .list-header {
@@ -158,10 +160,7 @@ def index():
158
  align-items: center;
159
  margin-bottom: 16px;
160
  }
161
- .list-header-top h2 {
162
- font-size: 1.5rem;
163
- font-weight: 600;
164
- }
165
 
166
  .user-profile {
167
  padding: 12px;
@@ -201,7 +200,6 @@ def index():
201
  align-items: center;
202
  justify-content: center;
203
  gap: 8px;
204
- text-decoration: none; /* For potential links */
205
  }
206
  .action-btn:hover {
207
  transform: translateY(-2px);
@@ -212,10 +210,7 @@ def index():
212
  font-size: 0.9rem;
213
  }
214
 
215
- #chatroom-list {
216
- flex-grow: 1;
217
- overflow-y: auto;
218
- }
219
  .chatroom-item {
220
  display: flex;
221
  align-items: center;
@@ -237,12 +232,8 @@ def index():
237
  font-weight: 600;
238
  color: white;
239
  flex-shrink: 0;
240
- background-color: gray; /* Default */
241
- }
242
- .chatroom-info {
243
- flex-grow: 1;
244
- overflow: hidden;
245
  }
 
246
  .chatroom-name {
247
  font-weight: 500;
248
  white-space: nowrap;
@@ -256,12 +247,11 @@ def index():
256
  flex-shrink: 0;
257
  }
258
 
259
- /* --- Chat Window View --- */
260
  #chat-window-view {
261
- display: none; /* Hidden by default on mobile */
262
  flex-direction: column;
263
  height: 100%;
264
- width: 100%; /* Full width on mobile */
265
  background-color: var(--bg-primary);
266
  }
267
 
@@ -274,23 +264,9 @@ def index():
274
  border-bottom: 1px solid var(--border-color);
275
  flex-shrink: 0;
276
  }
277
- .back-btn {
278
- background: none;
279
- border: none;
280
- cursor: pointer;
281
- display: block; /* Visible on mobile */
282
- padding: 0;
283
- margin-right: 8px;
284
- }
285
- .back-btn svg {
286
- width: 24px;
287
- height: 24px;
288
- fill: var(--text-primary);
289
- }
290
- #chat-header-title {
291
- font-size: 1.2rem;
292
- font-weight: 600;
293
- }
294
 
295
  #messages-container {
296
  flex-grow: 1;
@@ -301,21 +277,9 @@ def index():
301
  gap: 12px;
302
  }
303
 
304
- .message {
305
- display: flex;
306
- gap: 10px;
307
- max-width: 80%;
308
- }
309
- .message .avatar {
310
- width: 36px;
311
- height: 36px;
312
- align-self: flex-end;
313
- }
314
- .message-content {
315
- display: flex;
316
- flex-direction: column;
317
- gap: 4px;
318
- }
319
  .message-sender {
320
  font-size: 0.8rem;
321
  font-weight: 500;
@@ -377,57 +341,93 @@ def index():
377
  }
378
  #message-input:focus { border-color: var(--accent-blue); }
379
 
380
- .send-btn {
381
- width: 44px;
382
- height: 44px;
383
- border-radius: 50%;
384
- flex-shrink: 0;
385
- padding: 0;
386
- }
387
  .send-btn svg { width: 20px; height: 20px; fill: white; }
388
 
389
- /* --- Browser View --- */
390
- #browser-view {
391
- display: none; /* Hidden by default */
392
- flex-direction: column;
393
- height: 100%;
394
- width: 100%; /* Full width on mobile */
395
- background-color: var(--bg-primary);
396
- }
397
- .browser-bar {
398
  display: flex;
399
- padding: 8px;
400
- background-color: var(--bg-secondary);
401
  gap: 8px;
 
 
 
 
402
  flex-shrink: 0;
403
  }
404
- .browser-bar input {
 
 
 
 
 
 
 
 
 
 
 
 
405
  flex-grow: 1;
406
  padding: 8px 12px;
407
  border: 1px solid var(--border-color);
408
- background-color: var(--bg-tertiary);
409
  color: var(--text-primary);
410
- border-radius: 6px;
411
  outline: none;
412
  font-size: 0.9rem;
413
  }
414
- .browser-bar button {
415
- padding: 8px 16px;
416
- background: var(--bg-tertiary);
417
- border: 1px solid var(--border-color);
418
- color: var(--text-primary);
419
- border-radius: 6px;
420
- cursor: pointer;
421
- }
422
  #browser-iframe {
423
  flex-grow: 1;
424
  border: none;
425
- width: 100%;
426
- height: 100%; /* Will be managed by flex-grow */
427
  }
428
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
429
 
430
- /* --- Modals --- */
431
  .modal-overlay {
432
  position: fixed;
433
  top: 0;
@@ -453,7 +453,7 @@ def index():
453
  }
454
  .modal-content h3 { margin-bottom: 20px; font-weight: 600; font-size: 1.3rem; }
455
  .modal-content label { display: block; margin-bottom: 8px; font-size: 0.9rem; color: var(--text-secondary); }
456
- .modal-content input[type="text"], .modal-content input[type="password"] {
457
  width: 100%;
458
  padding: 12px;
459
  margin-bottom: 16px;
@@ -464,119 +464,12 @@ def index():
464
  font-size: 1rem;
465
  }
466
  .modal-actions { display: flex; justify-content: flex-end; gap: 12px; margin-top: 8px; }
467
- .modal-btn {
468
- padding: 10px 20px;
469
- border-radius: 6px;
470
- border: none;
471
- cursor: pointer;
472
- font-weight: 500;
473
- transition: background-color 0.2s ease;
474
- }
475
  .secondary-btn { background-color: var(--bg-hover); color: white; }
476
- .secondary-btn:hover { background-color: #3a3c44; }
477
 
478
- #profile-modal .modal-content { text-align: center; }
479
- #profile-avatar-container .avatar {
480
- width: 80px;
481
- height: 80px;
482
- font-size: 2rem;
483
- margin: 0 auto 20px;
484
- }
485
- #profile-qr-code {
486
- background: white;
487
- padding: 10px;
488
- margin: 20px auto;
489
- width: fit-content;
490
- border-radius: 8px;
491
- }
492
- #profile-qr-code canvas, #profile-qr-code img { display: block; } /* Fix QRCode.js margin */
493
-
494
- #scanner-modal .modal-content { text-align: center; }
495
- #qr-reader {
496
- width: 100%;
497
- border: 1px solid var(--border-color);
498
- margin-top: 16px;
499
- border-radius: 8px;
500
- overflow: hidden;
501
- }
502
-
503
- /* --- Navigation Bar --- */
504
- #nav-bar {
505
- position: fixed;
506
- bottom: 0;
507
- left: 0;
508
- right: 0;
509
- height: var(--nav-height);
510
- background-color: var(--bg-secondary);
511
- border-top: 1px solid var(--border-color);
512
- display: flex;
513
- justify-content: space-around;
514
- align-items: center;
515
- z-index: 500; /* Below modals */
516
- box-shadow: 0 -5px 15px rgba(0,0,0,0.2);
517
- }
518
-
519
- .nav-item {
520
- display: flex;
521
- flex-direction: column;
522
- align-items: center;
523
- justify-content: center;
524
- padding: 8px;
525
- color: var(--text-secondary);
526
- font-size: 0.7rem;
527
- cursor: pointer;
528
- transition: color 0.2s ease;
529
- width: calc(100% / 3); /* Simple distribution */
530
- user-select: none;
531
- }
532
-
533
- .nav-item svg {
534
- width: 24px;
535
- height: 24px;
536
- fill: var(--text-secondary);
537
- margin-bottom: 4px;
538
- transition: fill 0.2s ease;
539
- }
540
-
541
- .nav-item.active {
542
- color: var(--accent-blue-light);
543
- }
544
- .nav-item.active svg {
545
- fill: var(--accent-blue-light);
546
- }
547
- .nav-item:hover {
548
- color: var(--text-primary);
549
- }
550
- .nav-item:hover svg {
551
- fill: var(--text-primary);
552
- }
553
-
554
- #nav-scanner-btn {
555
- width: 56px;
556
- height: 56px;
557
- border-radius: 50%;
558
- background: linear-gradient(45deg, var(--accent-blue), var(--accent-blue-light));
559
- display: flex;
560
- align-items: center;
561
- justify-content: center;
562
- margin-bottom: 10px; /* Visually lift it slightly */
563
- box-shadow: 0 4px 15px rgba(0, 136, 204, 0.5);
564
- flex-shrink: 0;
565
- }
566
- #nav-scanner-btn svg {
567
- width: 28px;
568
- height: 28px;
569
- fill: white;
570
- margin-bottom: 0;
571
- }
572
- #nav-scanner-btn:active {
573
- transform: scale(0.95);
574
- }
575
-
576
- /* --- Status Bar --- */
577
  #status-bar {
578
  position: fixed;
579
- bottom: calc(var(--nav-height) + 20px); /* Above nav bar */
580
  left: 50%;
581
  transform: translateX(-50%);
582
  background-color: var(--bg-tertiary);
@@ -586,112 +479,39 @@ def index():
586
  font-size: 0.9rem;
587
  opacity: 0;
588
  visibility: hidden;
589
- transition: opacity 0.3s, visibility 0.3s;
590
  z-index: 2000;
591
  box-shadow: 0 5px 15px rgba(0,0,0,0.3);
592
- max-width: 90%;
593
- word-break: break-word;
594
  }
595
  #status-bar.success { background-color: var(--success-color); }
596
  #status-bar.error { background-color: var(--error-color); }
597
  #status-bar.visible { opacity: 1; visibility: visible; }
598
 
599
- /* --- Desktop Media Query --- */
600
  @media (min-width: 768px) {
601
- body {
602
- padding: 0;
603
- }
604
  .main-container {
605
- max-width: 1000px; /* Slightly smaller overall width */
606
- max-height: 650px; /* Reduced height */
607
  border-radius: 12px;
608
  overflow: hidden;
609
  box-shadow: 0 10px 40px rgba(0,0,0,0.3);
610
  border: 1px solid var(--border-color);
611
- padding-bottom: 0; /* No padding needed on main container */
612
  }
613
- #app-view {
614
- flex-direction: row;
615
  height: 100%;
616
- padding-bottom: 0; /* No padding needed on app-view */
617
- }
618
- #app-content {
619
- flex-direction: row; /* Content lays out horizontally */
620
- height: 100%;
621
- overflow: hidden;
622
  }
623
-
624
  #chatroom-list-view {
625
  width: 320px;
626
  flex-shrink: 0;
627
  border-right: 1px solid var(--border-color);
628
- display: flex !important; /* Always visible on desktop */
629
- height: 100%; /* Takes height of app-content */
630
- }
631
- #chat-window-view {
632
- width: auto;
633
- flex-grow: 1;
634
- height: 100%; /* Takes height of app-content */
635
- display: flex; /* Initially visible on desktop */
636
- }
637
- #chat-window-view.hidden-on-desktop {
638
- display: none !important;
639
- }
640
- #browser-view {
641
- width: auto;
642
- flex-grow: 1;
643
- height: 100%; /* Takes height of app-content */
644
- display: none; /* Hidden initially */
645
- }
646
-
647
- .back-btn { display: none !important; } /* Hide back button on desktop */
648
-
649
- #nav-bar {
650
- position: static; /* Integrate into the layout */
651
- height: auto;
652
- border-top: none;
653
- border-right: 1px solid var(--border-color); /* Border on the right */
654
- flex-direction: column; /* Items stack vertically */
655
- width: 80px; /* Narrow sidebar */
656
- justify-content: flex-start;
657
- padding-top: 20px;
658
- box-shadow: none;
659
  }
660
- .nav-item {
661
- width: 100%;
662
- padding: 12px 0;
663
- margin-bottom: 10px;
664
- }
665
- .nav-item svg { margin-bottom: 2px; }
666
- #nav-scanner-btn {
667
- width: 60px;
668
- height: 60px;
669
- margin-bottom: 20px;
670
- margin-top: 10px;
671
- }
672
- #nav-scanner-btn svg { width: 30px; height: 30px; }
673
-
674
- /* Adjust layout when browser view is active on desktop */
675
- #app-content.browser-active #chatroom-list-view {
676
- display: none !important; /* Hide chat list */
677
- }
678
- #app-content.browser-active #browser-view {
679
- display: flex !important; /* Show browser */
680
- }
681
- #app-content.browser-active #chat-window-view {
682
- display: none !important; /* Hide chat window */
683
- }
684
-
685
- /* Adjust layout when chat view is active on desktop */
686
- #app-content:not(.browser-active) #chatroom-list-view {
687
- display: flex !important; /* Show chat list */
688
- }
689
- #app-content:not(.browser-active) #chat-window-view {
690
- display: flex !important; /* Show chat window */
691
- }
692
- #app-content:not(.browser-active) #browser-view {
693
- display: none !important; /* Hide browser */
694
- }
695
  }
696
  </style>
697
  </head>
@@ -705,78 +525,82 @@ def index():
705
  </div>
706
 
707
  <div id="app-view" class="main-container">
708
- <div id="nav-bar">
709
- <div class="nav-item active" id="nav-chats">
710
- <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M20 2H4c-1.1 0-2 .9-2 2v18l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zM5.67 14l-.67.67V6h14v8H6.33L5.67 14z"/></svg>
711
- <span>Чаты</span>
712
- </div>
713
- <div class="nav-item" id="nav-scanner-btn" title="Сканировать QR">
714
- <svg xmlns="http://www.w3.org/2000/svg" height="16" viewBox="0 0 24 24" width="16" fill="white"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M3 11h8V3H3v8zm2-6h4v4H5V5zM3 21h8v-8H3v8zm2-6h4v4H5v-4zm8-12v8h8V3h-8zm6 6h-4V5h4v4zm-2 10a2 2 0 100-4 2 2 0 000 4z"/></svg>
715
- </div>
716
- <div class="nav-item" id="nav-browser">
717
- <svg xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" height="24" viewBox="0 0 24 24" width="24" fill="currentColor"><g><rect fill="none" height="24" width="24"/></g><g><g><path d="M12,2C6.48,2,2,6.48,2,12c0,5.52,4.48,10,10,10s10-4.48,10-10C22,6.48,17.52,2,12,2z M12,20c-4.42,0-8-3.58-8-8 c0-1.85,0.63-3.55,1.69-4.9L12,12l6.31-4.9C15.55,4.63,13.85,4,12,4c-4.42,0-8,3.58-8,8c0,4.42,3.58,8,8,8s8-3.58,8-8 c0-1.85-0.63-3.55-1.69-4.9L12,12l5.69-4.42C18.61,8.37,19,10.12,19,12C19,16.42,15.42,20,12,20z"/></g></g></svg>
718
- <span>Браузер</span>
719
- </div>
720
- <div class="nav-item" id="nav-profile">
721
- <svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24" fill="currentColor"><path d="M0 0h24v24H0z" fill="none"/><path d="M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z"/></svg>
722
- <span>Профиль</span>
723
- </div>
724
- </div>
 
 
 
 
 
 
 
 
 
 
725
 
726
- <div id="app-content">
727
- <div id="chatroom-list-view">
728
- <div class="list-header">
729
- <div class="list-header-top">
730
- <h2>Чаты</h2>
731
- <button id="create-room-show-modal" class="action-btn small">
732
- <svg xmlns="http://www.w3.org/2000/svg" height="16" viewBox="0 0 24 24" width="16" fill="white"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/></svg>
733
- <span>Новый</span>
734
- </button>
735
  </div>
736
- <div class="user-profile">
737
- <div id="user-wallet"></div>
738
- <div id="user-nickname"></div>
739
- <form class="username-form" id="username-form">
740
- <input type="text" id="username-input" class="username-input" placeholder="Установить никнейм" autocomplete="off" maxlength="20">
741
- <button type="submit" class="action-btn small">✓</button>
 
 
 
 
 
 
 
 
742
  </form>
743
  </div>
744
  </div>
745
- <div id="chatroom-list"></div>
746
  </div>
747
-
748
- <div id="chat-window-view" class="hidden-on-desktop">
749
- <div id="chat-placeholder" class="chat-placeholder">
750
- <img src="https://ton.org/download/ton_symbol.svg" alt="TON Symbol">
751
- <h2>Выберите чат</h2>
752
- <p>Начните общение в одном из существующих чатов или создайте свой собственный.</p>
753
- </div>
754
- <div id="active-chat" style="display: none; width: 100%; height: 100%; flex-direction: column;">
755
- <div class="chat-header">
756
- <button class="back-btn" id="back-to-list-btn">
757
- <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11H7.83l5.59-5.59L12 4l-8 8 8 8 1.41-1.41L7.83 13H20v-2z"/></svg>
758
- </button>
759
- <div id="chat-header-avatar" class="avatar"></div>
760
- <span id="chat-header-title"></span>
761
- </div>
762
- <div id="messages-container"></div>
763
- <form id="message-form" class="message-form">
764
- <input type="text" id="message-input" placeholder="Сообщение..." autocomplete="off">
765
- <button type="submit" class="action-btn send-btn" id="send-btn">
766
- <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24px" height="24px"><path d="M2.01 21L23 12 2.01 3 2 10l15 2-15 2z"/></svg>
767
- </button>
768
- </form>
769
  </div>
770
- </div>
771
-
772
- <div id="browser-view">
773
- <form class="browser-bar" id="browser-form">
774
- <input type="text" id="browser-url-input" value="https://www.google.com" placeholder="Введите URL или поисковый запрос">
775
- <button type="submit">Перейти</button>
776
- </form>
777
- <iframe id="browser-iframe" src="https://www.google.com"></iframe>
778
  </div>
779
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
780
  </div>
781
 
782
  <div id="create-room-modal" class="modal-overlay">
@@ -810,12 +634,12 @@ def index():
810
  </div>
811
 
812
  <div id="profile-modal" class="modal-overlay">
813
- <div class="modal-content">
814
  <h3 id="profile-modal-title">Профиль пользователя</h3>
815
- <div id="profile-avatar-container"></div>
816
- <p id="profile-username"></p>
817
- <p id="profile-address"></p>
818
- <div id="profile-qr-code"></div>
819
  <p style="text-align: center; color: var(--text-secondary); font-size: 0.8rem; margin-top: -10px; margin-bottom: 20px;">Отсканируйте для открытия профиля</p>
820
  <div class="modal-actions" style="flex-direction: column; gap: 12px; align-items: stretch;">
821
  <button id="send-ton-btn" class="modal-btn action-btn">Отправить TON</button>
@@ -827,7 +651,7 @@ def index():
827
  <div id="scanner-modal" class="modal-overlay">
828
  <div class="modal-content">
829
  <h3>Сканировать QR-код</h3>
830
- <div id="qr-reader"></div>
831
  <div class="modal-actions">
832
  <button id="scanner-close-btn" class="modal-btn secondary-btn">Отмена</button>
833
  </div>
@@ -849,23 +673,13 @@ def index():
849
  let chatroomsData = {};
850
  let html5QrCode = null;
851
  let profileQrCode = null;
852
- let currentMainView = 'chatList'; // 'chatList', 'chatWindow', 'browser'
853
-
854
  const loginView = document.getElementById('login-view');
855
  const appView = document.getElementById('app-view');
856
- const appContent = document.getElementById('app-content');
857
  const chatroomListView = document.getElementById('chatroom-list-view');
858
  const chatWindowView = document.getElementById('chat-window-view');
859
  const chatPlaceholder = document.getElementById('chat-placeholder');
860
  const activeChat = document.getElementById('active-chat');
861
- const browserView = document.getElementById('browser-view');
862
-
863
- const navChatsBtn = document.getElementById('nav-chats');
864
- const navScannerBtn = document.getElementById('nav-scanner-btn');
865
- const navBrowserBtn = document.getElementById('nav-browser');
866
- const navProfileBtn = document.getElementById('nav-profile');
867
- const navItems = [navChatsBtn, navBrowserBtn, navProfileBtn];
868
-
869
  const profileModal = document.getElementById('profile-modal');
870
  const scannerModal = document.getElementById('scanner-modal');
871
 
@@ -924,10 +738,6 @@ def index():
924
  if (!newUsername || newUsername.length < 3) {
925
  showStatus('Никнейм должен быть не короче 3 символов.', 'error');
926
  return;
927
- }
928
- if (newUsername.length > 20) {
929
- showStatus('Никнейм должен быть не длиннее 20 символов.', 'error');
930
- return;
931
  }
932
  try {
933
  await apiCall('/api/set_username', {
@@ -939,7 +749,7 @@ def index():
939
  updateUserInfo();
940
  showStatus('Никнейм успешно обновлен!', 'success');
941
  fetchChatrooms();
942
- if (activeChatroomId) fetchMessages(activeChatroomId);
943
  } catch (err) {}
944
  });
945
 
@@ -953,13 +763,13 @@ def index():
953
  });
954
  currentUser.username = data.username;
955
  } catch (err) {
956
- currentUser.username = null; // User might not exist yet
957
  }
958
  updateUserInfo();
959
  loginView.style.display = 'none';
960
  appView.style.display = 'flex';
961
- showView('chatList'); // Default view after login
962
  fetchChatrooms();
 
963
  };
964
 
965
  const renderChatrooms = (rooms) => {
@@ -1002,8 +812,7 @@ def index():
1002
 
1003
  const renderMessages = (messages) => {
1004
  const container = document.getElementById('messages-container');
1005
- // Check if user is near the bottom before rendering
1006
- const isScrolledToBottom = container.scrollHeight - container.clientHeight <= container.scrollTop + 1;
1007
  container.innerHTML = '';
1008
  messages.forEach(msg => {
1009
  const msgDiv = document.createElement('div');
@@ -1034,60 +843,32 @@ def index():
1034
  container.appendChild(msgDiv);
1035
  });
1036
 
1037
- // Scroll to bottom only if they were already there
1038
- if (isScrolledToBottom || messages.length === 0) {
1039
  container.scrollTop = container.scrollHeight;
1040
  }
1041
  };
1042
 
1043
  const fetchMessages = async (roomId) => {
1044
- if (!roomId) return;
1045
  try {
1046
  const data = await apiCall(`/api/messages/${roomId}`);
1047
  renderMessages(data.messages);
1048
  } catch (err) {
1049
- // Error handling already in apiCall
1050
  }
1051
  };
1052
-
1053
- const showView = (viewName) => {
1054
- // Hide all main views
1055
- chatroomListView.style.display = 'none';
1056
- chatWindowView.style.display = 'none';
1057
- browserView.style.display = 'none';
1058
-
1059
- // Remove active class from all nav items
1060
- navItems.forEach(item => item.classList.remove('active'));
1061
- appContent.classList.remove('browser-active'); // Remove browser layout class
1062
-
1063
- // Show the requested view and set active nav item
1064
- if (viewName === 'chatList') {
1065
- chatroomListView.style.display = 'flex';
1066
- navChatsBtn.classList.add('active');
1067
- if (window.innerWidth >= 768) {
1068
- chatWindowView.style.display = 'flex'; // Show window alongside list on desktop
1069
- }
1070
- } else if (viewName === 'chatWindow') {
1071
- chatWindowView.style.display = 'flex';
1072
- navChatsBtn.classList.add('active');
1073
- if (window.innerWidth >= 768) {
1074
- chatroomListView.style.display = 'flex'; // Show list alongside window on desktop
1075
- }
1076
- } else if (viewName === 'browser') {
1077
- browserView.style.display = 'flex';
1078
- navBrowserBtn.classList.add('active');
1079
- if (window.innerWidth >= 768) {
1080
- appContent.classList.add('browser-active'); // Apply browser layout class
1081
- }
1082
  }
 
1083
 
1084
- currentMainView = viewName;
1085
-
1086
- // Hide modals
1087
- profileModal.style.display = 'none';
1088
- scannerModal.style.display = 'none';
1089
- document.getElementById('create-room-modal').style.display = 'none';
1090
- document.getElementById('password-modal').style.display = 'none';
1091
  };
1092
 
1093
  const selectChatroom = (roomId, isPrivate) => {
@@ -1105,8 +886,8 @@ def index():
1105
 
1106
  chatPlaceholder.style.display = 'none';
1107
  activeChat.style.display = 'flex';
 
1108
 
1109
- showView('chatWindow'); // Switch to chat window view
1110
  fetchMessages(roomId);
1111
  messagePollingInterval = setInterval(() => fetchMessages(roomId), 3000);
1112
  };
@@ -1131,10 +912,7 @@ def index():
1131
  body: JSON.stringify({ chatroom_id: roomId, password })
1132
  });
1133
  proceedToRoom();
1134
- } catch (err) {
1135
- // Error is shown in apiCall
1136
- // Re-open password modal or handle error? For now, just error status.
1137
- }
1138
  };
1139
  passwordForm.addEventListener('submit', formSubmitHandler);
1140
 
@@ -1152,8 +930,7 @@ def index():
1152
  const input = document.getElementById('message-input');
1153
  const sendBtn = document.getElementById('send-btn');
1154
  const text = input.value.trim();
1155
- if (text && activeChatroomId && currentUser.address) {
1156
- if (messagePollingInterval) clearInterval(messagePollingInterval); // Pause polling while sending
1157
  input.value = '';
1158
  input.disabled = true;
1159
  sendBtn.disabled = true;
@@ -1167,12 +944,12 @@ def index():
1167
  text: text
1168
  })
1169
  });
1170
- await fetchMessages(activeChatroomId); // Fetch updated messages immediately
 
1171
  } finally {
1172
  input.disabled = false;
1173
  sendBtn.disabled = false;
1174
  input.focus();
1175
- messagePollingInterval = setInterval(() => fetchMessages(activeChatroomId), 3000); // Resume polling
1176
  }
1177
  }
1178
  });
@@ -1181,7 +958,6 @@ def index():
1181
  document.getElementById('create-room-show-modal').addEventListener('click', () => {
1182
  createRoomModal.style.display = 'flex';
1183
  document.getElementById('create-room-form').reset();
1184
- document.getElementById('room-name').focus();
1185
  });
1186
  document.getElementById('create-room-cancel').addEventListener('click', () => {
1187
  createRoomModal.style.display = 'none';
@@ -1190,10 +966,7 @@ def index():
1190
  e.preventDefault();
1191
  const name = document.getElementById('room-name').value.trim();
1192
  const password = document.getElementById('room-password').value;
1193
- if (!name) {
1194
- showStatus('Название чата обязательно.', 'error');
1195
- return;
1196
- }
1197
 
1198
  try {
1199
  await apiCall('/api/create_chatroom', {
@@ -1208,7 +981,6 @@ def index():
1208
  });
1209
 
1210
  const showProfile = async (address) => {
1211
- if (!address) return;
1212
  try {
1213
  const userData = await apiCall('/api/user_data', {
1214
  method: 'POST',
@@ -1216,14 +988,12 @@ def index():
1216
  body: JSON.stringify({ address: address })
1217
  });
1218
 
1219
- const username = userData.username || `Пользователь ${truncateAddress(address)}`;
1220
  const avatarContainer = document.getElementById('profile-avatar-container');
1221
  const usernameEl = document.getElementById('profile-username');
1222
  const addressEl = document.getElementById('profile-address');
1223
  const qrCodeEl = document.getElementById('profile-qr-code');
1224
  const sendTonBtn = document.getElementById('send-ton-btn');
1225
-
1226
- document.getElementById('profile-modal-title').textContent = (address === currentUser.address) ? 'Мой профиль' : 'Профиль пользователя';
1227
 
1228
  avatarContainer.innerHTML = '';
1229
  avatarContainer.appendChild(getAvatar(username));
@@ -1232,9 +1002,7 @@ def index():
1232
  addressEl.textContent = address;
1233
 
1234
  qrCodeEl.innerHTML = '';
1235
- if (profileQrCode) {
1236
- profileQrCode.clear();
1237
- }
1238
  profileQrCode = new QRCode(qrCodeEl, {
1239
  text: address,
1240
  width: 150,
@@ -1249,7 +1017,7 @@ def index():
1249
  showStatus('Подключите кошелек для отправки TON.', 'error');
1250
  return;
1251
  }
1252
- const amountString = prompt(`Введите сумму в TON для отправки на адрес ${truncateAddress(address)}:`, "0.1");
1253
  if (amountString === null) return;
1254
 
1255
  const amount = parseFloat(amountString);
@@ -1258,57 +1026,44 @@ def index():
1258
  return;
1259
  }
1260
 
1261
- // Convert TON to nanoTON
1262
- const amountInNanoTon = (amount * 1_000_000_000).toFixed(0); // Use toFixed(0) to avoid scientific notation for large numbers
1263
 
1264
  const transaction = {
1265
- validUntil: Math.floor(Date.now() / 1000) + 600, // 10 minutes validity
1266
  messages: [ { address: address, amount: amountInNanoTon } ]
1267
  };
1268
 
1269
  try {
1270
- const result = await tonConnectUI.sendTransaction(transaction);
1271
- showStatus(`Транзакция успешно отправлена: ${result.boc}`, 'success');
1272
- // Optional: Close modal after success
1273
  profileModal.style.display = 'none';
1274
  } catch (error) {
1275
- if (error.message.includes('User rejected the action')) {
1276
- showStatus('Транзакция отменена пользователем.', 'info');
1277
- } else {
1278
- showStatus('Ошибка отправки транзакции.', 'error');
1279
- console.error("Send transaction failed:", error);
1280
- }
1281
  }
1282
  };
1283
 
1284
- sendTonBtn.style.display = (address === currentUser.address || !tonConnectUI.connected) ? 'none' : 'flex'; // Hide send button for own profile or if not connected
1285
  profileModal.style.display = 'flex';
1286
  } catch (err) {
1287
- // Error status already shown by apiCall
1288
- showStatus('Не удалось загрузить профиль.', 'error');
1289
  }
1290
  };
1291
 
1292
  const showScanner = () => {
1293
  scannerModal.style.display = 'flex';
1294
- // Ensure qr-reader div is empty before starting
1295
- document.getElementById('qr-reader').innerHTML = '';
1296
-
1297
  html5QrCode = new Html5Qrcode("qr-reader");
1298
  const qrCodeSuccessCallback = (decodedText, decodedResult) => {
1299
  hideScanner();
1300
- // Basic check for potential TON address format (EQ/UQ + length)
1301
  if (decodedText && decodedText.length > 40 && (decodedText.startsWith('EQ') || decodedText.startsWith('UQ'))) {
1302
- showProfile(decodedText); // Show profile of the scanned address
1303
  } else {
1304
- showStatus('Отсканирован недействительный QR-код (не похож на TON адрес).', 'error');
1305
  }
1306
  };
1307
  const config = { fps: 10, qrbox: { width: 250, height: 250 } };
1308
  html5QrCode.start({ facingMode: "environment" }, config, qrCodeSuccessCallback)
1309
  .catch(err => {
1310
  showStatus('Не удалось запустить сканер.', 'error');
1311
- console.error("QR Scanner start failed:", err);
1312
  hideScanner();
1313
  });
1314
  };
@@ -1320,96 +1075,91 @@ def index():
1320
  scannerModal.style.display = 'none';
1321
  };
1322
 
1323
- // --- Event Listeners ---
1324
-
1325
- // Navigation Buttons
1326
- navChatsBtn.addEventListener('click', () => showView('chatList'));
1327
- navBrowserBtn.addEventListener('click', () => showView('browser'));
1328
- navProfileBtn.addEventListener('click', () => {
1329
- if (currentUser.address) showProfile(currentUser.address);
1330
- // No showView call here, profile is a modal over the current view
1331
  });
1332
- navScannerBtn.addEventListener('click', showScanner); // Scanner is also a modal
1333
-
1334
- // Back button (mobile chat view)
1335
- document.getElementById('back-to-list-btn').addEventListener('click', () => {
1336
- if (messagePollingInterval) clearInterval(messagePollingInterval);
1337
- activeChatroomId = null;
1338
- showView('chatList');
1339
- chatPlaceholder.style.display = 'flex'; // Show placeholder again
1340
- activeChat.style.display = 'none';
1341
- });
1342
-
1343
- // Modal Close Buttons
1344
  document.getElementById('profile-close-btn').addEventListener('click', () => profileModal.style.display = 'none');
1345
  document.getElementById('scanner-close-btn').addEventListener('click', hideScanner);
1346
-
1347
- // Browser URL form
1348
- document.getElementById('browser-form').addEventListener('submit', (e) => {
1349
- e.preventDefault();
1350
- const urlInput = document.getElementById('browser-url-input');
1351
- let url = urlInput.value.trim();
1352
-
1353
- if (url) {
1354
- // Simple check: if it doesn't look like a URL (doesn't contain .), assume it's a search term
1355
- if (!url.includes('.') && !url.startsWith('http://') && !url.startsWith('https://')) {
1356
- url = 'https://www.google.com/search?q=' + encodeURIComponent(url);
1357
- } else if (!url.startsWith('http://') && !url.startsWith('https://')) {
1358
- // If it looks like a domain but no protocol, add https
1359
- url = 'https://' + url;
1360
- }
1361
- document.getElementById('browser-iframe').src = url;
1362
- urlInput.value = url; // Update input with the corrected URL
1363
- }
1364
- });
1365
-
1366
-
1367
- // Handle resize events for responsive layout
1368
  const handleResize = () => {
1369
  const isMobile = window.innerWidth < 768;
1370
-
1371
- // Update display of back button based on mobile/desktop
1372
  document.getElementById('back-to-list-btn').style.display = isMobile ? 'block' : 'none';
1373
-
1374
- // Re-apply the current main view based on new screen size
1375
- // This ensures correct visibility of list/window side-by-side or stacked
1376
- showView(currentMainView);
1377
-
1378
- // Ensure modals are centered correctly on resize
1379
- // This is often handled by flexbox centering, but could add specific logic if needed.
 
 
 
 
 
 
1380
  };
1381
 
1382
  window.addEventListener('resize', handleResize);
1383
-
1384
- // Initial check for screen size layout
1385
  handleResize();
1386
 
1387
- // --- TON Connect Integration ---
1388
  tonConnectUI.onStatusChange(wallet => {
1389
  if (wallet) {
1390
- // Wallet connected
1391
- const address = TON_CONNECT_UI.toUserFriendlyAddress(wallet.account.address, false); // Get base64 address
1392
  initializeUser(address);
1393
  } else {
1394
- // Wallet disconnected
1395
  currentUser = { address: null, username: null };
1396
  appView.style.display = 'none';
1397
  loginView.style.display = 'flex';
1398
  if (messagePollingInterval) clearInterval(messagePollingInterval);
1399
- messagePollingInterval = null;
1400
  activeChatroomId = null;
1401
- // Reset views to default mobile state on logout
1402
- chatroomListView.style.display = 'flex';
1403
- chatWindowView.style.display = 'none';
1404
- browserView.style.display = 'none';
1405
- navItems.forEach(item => item.classList.remove('active'));
1406
- navChatsBtn.classList.add('active');
1407
- currentMainView = 'chatList';
1408
- appContent.classList.remove('browser-active');
1409
- document.getElementById('chat-placeholder').style.display = 'flex';
1410
- document.getElementById('active-chat').style.display = 'none';
1411
  }
1412
  });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1413
  });
1414
  </script>
1415
  </body>
@@ -1425,8 +1175,8 @@ def get_user_data():
1425
  if not address:
1426
  return jsonify({'error': 'Address is required'}), 400
1427
  db = read_db()
1428
- user_info = db['users'].get(address, {}) # Return empty dict if user not found
1429
- username = user_info.get('username')
1430
  return jsonify({'username': username})
1431
 
1432
  @app.route('/api/set_username', methods=['POST'])
@@ -1456,7 +1206,7 @@ def get_chatrooms():
1456
  'name': room_data['name'],
1457
  'is_private': room_data['is_private']
1458
  })
1459
- return jsonify({'chatrooms': sorted(chatrooms_list, key=lambda x: x['name'].lower())}) # Sort by name case-insensitively
1460
 
1461
  @app.route('/api/create_chatroom', methods=['POST'])
1462
  def create_chatroom():
@@ -1466,17 +1216,8 @@ def create_chatroom():
1466
  creator_address = data.get('creator_address')
1467
  if not name or not creator_address:
1468
  return jsonify({'error': 'Name and creator address are required'}), 400
1469
- if len(name) < 1 or len(name) > 50:
1470
- return jsonify({'error': 'Chatroom name must be between 1 and 50 characters'}), 400
1471
- if password and len(password) < 4:
1472
- return jsonify({'error': 'Password must be at least 4 characters long'}), 400
1473
 
1474
  db = read_db()
1475
- # Optional: Check for duplicate names (though UUID makes ID unique)
1476
- # for room_data in db['chatrooms'].values():
1477
- # if room_data['name'] == name:
1478
- # return jsonify({'error': 'Chatroom with this name already exists'}), 400
1479
-
1480
  room_id = str(uuid.uuid4())
1481
  db['chatrooms'][room_id] = {
1482
  'name': name,
@@ -1506,34 +1247,19 @@ def join_chatroom():
1506
  def get_messages(chatroom_id):
1507
  db = read_db()
1508
  if chatroom_id not in db['messages']:
1509
- # Return empty list if chatroom exists but has no messages,
1510
- # or 404 if chatroom ID is invalid. Let's assume 404 is safer
1511
- # if the chatroom itself isn't in the chatrooms list.
1512
- if chatroom_id not in db['chatrooms']:
1513
- return jsonify({'error': 'Chatroom not found'}), 404
1514
- # If chatroom exists but messages list is missing (shouldn't happen with init_db)
1515
- # db['messages'][chatroom_id] = [] # Could auto-create
1516
- # return jsonify({'messages': []})
1517
- # Or simply return empty list if key exists but value is empty/null
1518
- pass # Proceed to fetch messages
1519
-
1520
  messages_with_names = []
1521
  room_messages = db['messages'].get(chatroom_id, [])
1522
-
1523
- # Cache usernames for this request to avoid repeated lookups
1524
- user_cache = {}
1525
 
1526
  for msg in room_messages:
1527
  sender_address = msg['sender_address']
 
 
 
1528
 
1529
- if sender_address not in user_cache:
1530
- user_info = db['users'].get(sender_address)
1531
- display_name = (user_info.get('username') if user_info and user_info.get('username')
1532
- else f"{sender_address[:4]}...{sender_address[-4:]}")
1533
- user_cache[sender_address] = display_name
1534
-
1535
  msg_copy = msg.copy()
1536
- msg_copy['display_name'] = user_cache[sender_address]
1537
  messages_with_names.append(msg_copy)
1538
 
1539
  return jsonify({'messages': messages_with_names})
@@ -1547,21 +1273,10 @@ def send_message():
1547
 
1548
  if not all([chatroom_id, sender_address, text]):
1549
  return jsonify({'error': 'Missing data'}), 400
1550
-
1551
- text = text.strip()
1552
- if not text: # Prevent sending empty messages
1553
- return jsonify({'error': 'Message cannot be empty'}), 400
1554
-
1555
- if len(text) > 1000: # Limit message length
1556
- return jsonify({'error': 'Message is too long'}), 400
1557
-
1558
 
1559
  db = read_db()
1560
  if chatroom_id not in db['messages']:
1561
- if chatroom_id not in db['chatrooms']:
1562
- return jsonify({'error': 'Chatroom not found'}), 404
1563
- db['messages'][chatroom_id] = [] # Auto-create messages list if missing
1564
-
1565
 
1566
  message = {
1567
  'id': str(uuid.uuid4()),
@@ -1570,7 +1285,6 @@ def send_message():
1570
  'timestamp': datetime.utcnow().isoformat() + "Z"
1571
  }
1572
 
1573
- # Keep only the last 100 messages per chatroom
1574
  if len(db['messages'][chatroom_id]) >= 100:
1575
  db['messages'][chatroom_id].pop(0)
1576
 
@@ -1581,4 +1295,4 @@ def send_message():
1581
 
1582
  if __name__ == '__main__':
1583
  init_db()
1584
- app.run(host='0.0.0.0', port=7860, debug=True) # Enabled debug for development
 
55
  --success-color: #28a745;
56
  --error-color: #dc3545;
57
  --font-family: 'Inter', sans-serif;
 
58
  }
59
 
60
  * {
 
64
  -webkit-tap-highlight-color: transparent;
65
  }
66
 
67
+ html { font-size: 16px; }
 
 
68
 
69
  body {
70
  font-family: var(--font-family);
 
76
  display: flex;
77
  align-items: center;
78
  justify-content: center;
 
79
  }
80
 
81
  .main-container {
 
86
  transition: opacity 0.3s ease;
87
  }
88
 
 
89
  #login-view {
90
  display: flex;
91
  flex-direction: column;
 
116
  margin-bottom: 40px;
117
  }
118
 
 
119
  #app-view {
120
  display: none;
121
  width: 100%;
122
+ height: 100%;
123
  flex-direction: column;
 
124
  }
125
 
126
+ .view-content {
127
+ height: calc(100% - 60px);
128
+ display: flex;
129
+ flex-direction: row;
130
+ }
131
+ .view-panel {
132
+ display: none;
133
+ height: 100%;
134
+ width: 100%;
135
+ }
136
+ .view-panel.active {
137
  display: flex;
138
+ }
139
+
140
+ #chat-view {
141
+ flex-direction: row;
142
  }
143
 
 
144
  #chatroom-list-view {
145
  display: flex;
146
  flex-direction: column;
147
  height: 100%;
148
+ width: 100%;
149
  background-color: var(--bg-secondary);
 
150
  }
151
 
152
  .list-header {
 
160
  align-items: center;
161
  margin-bottom: 16px;
162
  }
163
+ .list-header-top h2 { font-size: 1.5rem; font-weight: 600; }
 
 
 
164
 
165
  .user-profile {
166
  padding: 12px;
 
200
  align-items: center;
201
  justify-content: center;
202
  gap: 8px;
 
203
  }
204
  .action-btn:hover {
205
  transform: translateY(-2px);
 
210
  font-size: 0.9rem;
211
  }
212
 
213
+ #chatroom-list { flex-grow: 1; overflow-y: auto; }
 
 
 
214
  .chatroom-item {
215
  display: flex;
216
  align-items: center;
 
232
  font-weight: 600;
233
  color: white;
234
  flex-shrink: 0;
 
 
 
 
 
235
  }
236
+ .chatroom-info { flex-grow: 1; overflow: hidden; }
237
  .chatroom-name {
238
  font-weight: 500;
239
  white-space: nowrap;
 
247
  flex-shrink: 0;
248
  }
249
 
 
250
  #chat-window-view {
251
+ display: none;
252
  flex-direction: column;
253
  height: 100%;
254
+ width: 100%;
255
  background-color: var(--bg-primary);
256
  }
257
 
 
264
  border-bottom: 1px solid var(--border-color);
265
  flex-shrink: 0;
266
  }
267
+ .back-btn { background: none; border: none; cursor: pointer; display: none; }
268
+ .back-btn svg { width: 24px; height: 24px; fill: var(--text-primary); }
269
+ #chat-header-title { font-size: 1.2rem; font-weight: 600; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
270
 
271
  #messages-container {
272
  flex-grow: 1;
 
277
  gap: 12px;
278
  }
279
 
280
+ .message { display: flex; gap: 10px; max-width: 80%; }
281
+ .message .avatar { width: 36px; height: 36px; align-self: flex-end; }
282
+ .message-content { display: flex; flex-direction: column; gap: 4px; }
 
 
 
 
 
 
 
 
 
 
 
 
283
  .message-sender {
284
  font-size: 0.8rem;
285
  font-weight: 500;
 
341
  }
342
  #message-input:focus { border-color: var(--accent-blue); }
343
 
344
+ .send-btn { width: 44px; height: 44px; border-radius: 50%; flex-shrink: 0; padding: 0; }
 
 
 
 
 
 
345
  .send-btn svg { width: 20px; height: 20px; fill: white; }
346
 
347
+ #browser-view { flex-direction: column; background-color: var(--bg-primary); }
348
+ .browser-header {
 
 
 
 
 
 
 
349
  display: flex;
 
 
350
  gap: 8px;
351
+ padding: 8px 12px;
352
+ background-color: var(--bg-secondary);
353
+ border-bottom: 1px solid var(--border-color);
354
+ align-items: center;
355
  flex-shrink: 0;
356
  }
357
+ .browser-header .nav-icon-btn {
358
+ background: var(--bg-tertiary);
359
+ border-radius: 50%;
360
+ width: 36px;
361
+ height: 36px;
362
+ display: flex;
363
+ align-items: center;
364
+ justify-content: center;
365
+ border: none;
366
+ cursor: pointer;
367
+ }
368
+ .browser-header .nav-icon-btn svg { fill: var(--text-primary); width: 20px; height: 20px; }
369
+ #url-input {
370
  flex-grow: 1;
371
  padding: 8px 12px;
372
  border: 1px solid var(--border-color);
373
+ background-color: var(--bg-primary);
374
  color: var(--text-primary);
375
+ border-radius: 18px;
376
  outline: none;
377
  font-size: 0.9rem;
378
  }
 
 
 
 
 
 
 
 
379
  #browser-iframe {
380
  flex-grow: 1;
381
  border: none;
382
+ background-color: #fff;
 
383
  }
384
 
385
+ #bottom-nav {
386
+ position: absolute;
387
+ bottom: 0;
388
+ left: 0;
389
+ right: 0;
390
+ height: 60px;
391
+ background-color: var(--bg-secondary);
392
+ border-top: 1px solid var(--border-color);
393
+ display: flex;
394
+ justify-content: space-around;
395
+ align-items: center;
396
+ }
397
+ .nav-btn {
398
+ display: flex;
399
+ flex-direction: column;
400
+ align-items: center;
401
+ justify-content: center;
402
+ gap: 4px;
403
+ background: none;
404
+ border: none;
405
+ color: var(--text-secondary);
406
+ cursor: pointer;
407
+ transition: color 0.2s;
408
+ font-size: 0.7rem;
409
+ flex-grow: 1;
410
+ height: 100%;
411
+ }
412
+ .nav-btn svg {
413
+ width: 24px;
414
+ height: 24px;
415
+ fill: currentColor;
416
+ }
417
+ .nav-btn.active { color: var(--accent-blue); }
418
+ #nav-scan-btn {
419
+ transform: translateY(-15px);
420
+ background: linear-gradient(45deg, var(--accent-blue), var(--accent-blue-light));
421
+ border-radius: 50%;
422
+ width: 56px;
423
+ height: 56px;
424
+ border: 4px solid var(--bg-secondary);
425
+ color: white;
426
+ box-shadow: 0 -4px 15px rgba(0, 136, 204, 0.2);
427
+ flex-grow: 0;
428
+ }
429
+ #nav-scan-btn svg { width: 28px; height: 28px; }
430
 
 
431
  .modal-overlay {
432
  position: fixed;
433
  top: 0;
 
453
  }
454
  .modal-content h3 { margin-bottom: 20px; font-weight: 600; font-size: 1.3rem; }
455
  .modal-content label { display: block; margin-bottom: 8px; font-size: 0.9rem; color: var(--text-secondary); }
456
+ .modal-content input {
457
  width: 100%;
458
  padding: 12px;
459
  margin-bottom: 16px;
 
464
  font-size: 1rem;
465
  }
466
  .modal-actions { display: flex; justify-content: flex-end; gap: 12px; margin-top: 8px; }
467
+ .modal-btn { padding: 10px 20px; border-radius: 6px; border: none; cursor: pointer; font-weight: 500; }
 
 
 
 
 
 
 
468
  .secondary-btn { background-color: var(--bg-hover); color: white; }
 
469
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
470
  #status-bar {
471
  position: fixed;
472
+ bottom: 75px;
473
  left: 50%;
474
  transform: translateX(-50%);
475
  background-color: var(--bg-tertiary);
 
479
  font-size: 0.9rem;
480
  opacity: 0;
481
  visibility: hidden;
482
+ transition: opacity 0.3s, visibility 0.3s, bottom 0.3s;
483
  z-index: 2000;
484
  box-shadow: 0 5px 15px rgba(0,0,0,0.3);
 
 
485
  }
486
  #status-bar.success { background-color: var(--success-color); }
487
  #status-bar.error { background-color: var(--error-color); }
488
  #status-bar.visible { opacity: 1; visibility: visible; }
489
 
 
490
  @media (min-width: 768px) {
 
 
 
491
  .main-container {
492
+ max-width: 1100px;
493
+ max-height: 760px;
494
  border-radius: 12px;
495
  overflow: hidden;
496
  box-shadow: 0 10px 40px rgba(0,0,0,0.3);
497
  border: 1px solid var(--border-color);
 
498
  }
499
+ .view-content {
 
500
  height: 100%;
 
 
 
 
 
 
501
  }
 
502
  #chatroom-list-view {
503
  width: 320px;
504
  flex-shrink: 0;
505
  border-right: 1px solid var(--border-color);
506
+ display: flex !important;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
507
  }
508
+ #chat-window-view { width: auto; flex-grow: 1; display: flex !important; }
509
+ #chat-window-view.hidden-on-desktop { display: flex !important; }
510
+ .back-btn { display: none !important; }
511
+ #bottom-nav { display: none; }
512
+ #status-bar { bottom: 20px; }
513
+ #chat-view.active { display: flex; }
514
+ #browser-view.active { display: flex; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
515
  }
516
  </style>
517
  </head>
 
525
  </div>
526
 
527
  <div id="app-view" class="main-container">
528
+ <div class="view-content">
529
+ <div id="chat-view" class="view-panel active">
530
+ <div id="chatroom-list-view">
531
+ <div class="list-header">
532
+ <div class="list-header-top">
533
+ <h2>Чаты</h2>
534
+ <div style="display: flex; align-items: center; gap: 8px;">
535
+ <button id="my-profile-btn" class="action-btn small" title="Мой профиль">
536
+ <svg xmlns="http://www.w3.org/2000/svg" height="16" viewBox="0 0 24 24" width="16" fill="white"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z"/></svg>
537
+ </button>
538
+ <button id="create-room-show-modal" class="action-btn small">
539
+ <svg xmlns="http://www.w3.org/2000/svg" height="16" viewBox="0 0 24 24" width="16" fill="white"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/></svg>
540
+ <span>Новый</span>
541
+ </button>
542
+ </div>
543
+ </div>
544
+ <div class="user-profile">
545
+ <div id="user-wallet"></div>
546
+ <div id="user-nickname"></div>
547
+ <form class="username-form" id="username-form">
548
+ <input type="text" id="username-input" class="username-input" placeholder="Установить никнейм" autocomplete="off">
549
+ <button type="submit" class="action-btn small">✓</button>
550
+ </form>
551
+ </div>
552
+ </div>
553
+ <div id="chatroom-list"></div>
554
+ </div>
555
 
556
+ <div id="chat-window-view" class="hidden-on-desktop">
557
+ <div id="chat-placeholder" class="chat-placeholder">
558
+ <img src="https://ton.org/download/ton_symbol.svg" alt="TON Symbol">
559
+ <h2>Выберите чат</h2>
560
+ <p>Начните общение в одном из существующих чатов или создайте свой собственный.</p>
 
 
 
 
561
  </div>
562
+ <div id="active-chat" style="display: none; width: 100%; height: 100%; flex-direction: column;">
563
+ <div class="chat-header">
564
+ <button class="back-btn" id="back-to-list-btn">
565
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11H7.83l5.59-5.59L12 4l-8 8 8 8 1.41-1.41L7.83 13H20v-2z"/></svg>
566
+ </button>
567
+ <div id="chat-header-avatar" class="avatar"></div>
568
+ <span id="chat-header-title"></span>
569
+ </div>
570
+ <div id="messages-container"></div>
571
+ <form id="message-form" class="message-form">
572
+ <input type="text" id="message-input" placeholder="Сообщение..." autocomplete="off">
573
+ <button type="submit" class="action-btn send-btn" id="send-btn">
574
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24px" height="24px"><path d="M2.01 21L23 12 2.01 3 2 10l15 2-15 2z"/></svg>
575
+ </button>
576
  </form>
577
  </div>
578
  </div>
 
579
  </div>
580
+ <div id="browser-view" class="view-panel">
581
+ <div class="browser-header">
582
+ <button id="browser-back" class="nav-icon-btn"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11H7.83l5.59-5.59L12 4l-8 8 8 8 1.41-1.41L7.83 13H20v-2z"/></svg></button>
583
+ <button id="browser-forward" class="nav-icon-btn"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 4l-1.41 1.41L16.17 11H4v2h12.17l-5.58 5.59L12 20l8-8-8-8z"/></svg></button>
584
+ <button id="browser-reload" class="nav-icon-btn"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M17.65 6.35C16.2 4.9 14.21 4 12 4c-4.42 0-7.99 3.58-7.99 8s3.57 8 7.99 8c3.73 0 6.84-2.55 7.73-6h-2.08c-.82 2.33-3.04 4-5.65 4-3.31 0-6-2.69-6-6s2.69-6 6-6c1.66 0 3.14.69 4.22 1.78L13 11h7V4l-2.35 2.35z"/></svg></button>
585
+ <form id="url-form" style="flex-grow: 1; display: flex;"><input type="text" id="url-input" placeholder="Поиск или адрес" value="https://www.google.com"></form>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
586
  </div>
587
+ <iframe id="browser-iframe" src="https://www.google.com/webhp?igu=1"></iframe>
 
 
 
 
 
 
 
588
  </div>
589
  </div>
590
+
591
+ <nav id="bottom-nav">
592
+ <button id="nav-chat-btn" class="nav-btn active">
593
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 2H4c-1.1 0-2 .9-2 2v18l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2z"/></svg>
594
+ <span>Чаты</span>
595
+ </button>
596
+ <button id="nav-scan-btn" class="nav-btn">
597
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M3 11h8V3H3v8zm2-6h4v4H5V5zM3 21h8v-8H3v8zm2-6h4v4H5v-4zm8-12v8h8V3h-8zm6 6h-4V5h4v4zm-2 10a2 2 0 100-4 2 2 0 000 4z"/></svg>
598
+ </button>
599
+ <button id="nav-browser-btn" class="nav-btn">
600
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-1 17.93c-3.95-.49-7-3.85-7-7.93 0-.62.08-1.21.21-1.79L9 15v1c0 1.1.9 2 2 2v-.07zM12 10c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2zm7.93-1L15 15v-1c0-1.1-.9-2-2-2V9c0-1.1.9-2 2-2h3.03c.52 1.25.8 2.58.8 4 0 3.95-3.05 7.23-7 7.93v-2.02c1.9-.42 3.48-1.73 4.19-3.45l-2.19-.9v.01z"/></svg>
601
+ <span>Браузер</span>
602
+ </button>
603
+ </nav>
604
  </div>
605
 
606
  <div id="create-room-modal" class="modal-overlay">
 
634
  </div>
635
 
636
  <div id="profile-modal" class="modal-overlay">
637
+ <div class="modal-content" style="text-align: center;">
638
  <h3 id="profile-modal-title">Профиль пользователя</h3>
639
+ <div id="profile-avatar-container" style="margin: 20px auto; display: inline-block;"></div>
640
+ <p id="profile-username" style="font-size: 1.2rem; font-weight: 600;"></p>
641
+ <p id="profile-address" style="color: var(--text-secondary); font-size: 0.9rem; word-break: break-all; margin-top: 8px;"></p>
642
+ <div id="profile-qr-code" style="background: white; padding: 10px; margin: 20px auto; width: fit-content; border-radius: 8px;"></div>
643
  <p style="text-align: center; color: var(--text-secondary); font-size: 0.8rem; margin-top: -10px; margin-bottom: 20px;">Отсканируйте для открытия профиля</p>
644
  <div class="modal-actions" style="flex-direction: column; gap: 12px; align-items: stretch;">
645
  <button id="send-ton-btn" class="modal-btn action-btn">Отправить TON</button>
 
651
  <div id="scanner-modal" class="modal-overlay">
652
  <div class="modal-content">
653
  <h3>Сканировать QR-код</h3>
654
+ <div id="qr-reader" style="width: 100%; border: 1px solid var(--border-color); margin-top: 16px; border-radius: 8px; overflow: hidden;"></div>
655
  <div class="modal-actions">
656
  <button id="scanner-close-btn" class="modal-btn secondary-btn">Отмена</button>
657
  </div>
 
673
  let chatroomsData = {};
674
  let html5QrCode = null;
675
  let profileQrCode = null;
676
+
 
677
  const loginView = document.getElementById('login-view');
678
  const appView = document.getElementById('app-view');
 
679
  const chatroomListView = document.getElementById('chatroom-list-view');
680
  const chatWindowView = document.getElementById('chat-window-view');
681
  const chatPlaceholder = document.getElementById('chat-placeholder');
682
  const activeChat = document.getElementById('active-chat');
 
 
 
 
 
 
 
 
683
  const profileModal = document.getElementById('profile-modal');
684
  const scannerModal = document.getElementById('scanner-modal');
685
 
 
738
  if (!newUsername || newUsername.length < 3) {
739
  showStatus('Никнейм должен быть не короче 3 символов.', 'error');
740
  return;
 
 
 
 
741
  }
742
  try {
743
  await apiCall('/api/set_username', {
 
749
  updateUserInfo();
750
  showStatus('Никнейм успешно обновлен!', 'success');
751
  fetchChatrooms();
752
+ if (activeChatroomId) fetchMessages(activeChatroomId);
753
  } catch (err) {}
754
  });
755
 
 
763
  });
764
  currentUser.username = data.username;
765
  } catch (err) {
766
+ currentUser.username = null;
767
  }
768
  updateUserInfo();
769
  loginView.style.display = 'none';
770
  appView.style.display = 'flex';
 
771
  fetchChatrooms();
772
+ switchView('chat-view');
773
  };
774
 
775
  const renderChatrooms = (rooms) => {
 
812
 
813
  const renderMessages = (messages) => {
814
  const container = document.getElementById('messages-container');
815
+ const shouldScroll = container.scrollTop + container.clientHeight >= container.scrollHeight - 30;
 
816
  container.innerHTML = '';
817
  messages.forEach(msg => {
818
  const msgDiv = document.createElement('div');
 
843
  container.appendChild(msgDiv);
844
  });
845
 
846
+ if(shouldScroll) {
 
847
  container.scrollTop = container.scrollHeight;
848
  }
849
  };
850
 
851
  const fetchMessages = async (roomId) => {
 
852
  try {
853
  const data = await apiCall(`/api/messages/${roomId}`);
854
  renderMessages(data.messages);
855
  } catch (err) {
856
+ if (messagePollingInterval) clearInterval(messagePollingInterval);
857
  }
858
  };
859
+
860
+ const showChatView = () => {
861
+ chatWindowView.style.display = 'flex';
862
+ if (window.innerWidth < 768) {
863
+ chatroomListView.style.display = 'none';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
864
  }
865
+ };
866
 
867
+ const showListView = () => {
868
+ chatroomListView.style.display = 'flex';
869
+ if (window.innerWidth < 768) {
870
+ chatWindowView.style.display = 'none';
871
+ }
 
 
872
  };
873
 
874
  const selectChatroom = (roomId, isPrivate) => {
 
886
 
887
  chatPlaceholder.style.display = 'none';
888
  activeChat.style.display = 'flex';
889
+ showChatView();
890
 
 
891
  fetchMessages(roomId);
892
  messagePollingInterval = setInterval(() => fetchMessages(roomId), 3000);
893
  };
 
912
  body: JSON.stringify({ chatroom_id: roomId, password })
913
  });
914
  proceedToRoom();
915
+ } catch (err) {}
 
 
 
916
  };
917
  passwordForm.addEventListener('submit', formSubmitHandler);
918
 
 
930
  const input = document.getElementById('message-input');
931
  const sendBtn = document.getElementById('send-btn');
932
  const text = input.value.trim();
933
+ if (text && activeChatroomId) {
 
934
  input.value = '';
935
  input.disabled = true;
936
  sendBtn.disabled = true;
 
944
  text: text
945
  })
946
  });
947
+ await fetchMessages(activeChatroomId);
948
+ document.getElementById('messages-container').scrollTop = document.getElementById('messages-container').scrollHeight;
949
  } finally {
950
  input.disabled = false;
951
  sendBtn.disabled = false;
952
  input.focus();
 
953
  }
954
  }
955
  });
 
958
  document.getElementById('create-room-show-modal').addEventListener('click', () => {
959
  createRoomModal.style.display = 'flex';
960
  document.getElementById('create-room-form').reset();
 
961
  });
962
  document.getElementById('create-room-cancel').addEventListener('click', () => {
963
  createRoomModal.style.display = 'none';
 
966
  e.preventDefault();
967
  const name = document.getElementById('room-name').value.trim();
968
  const password = document.getElementById('room-password').value;
969
+ if (!name) return;
 
 
 
970
 
971
  try {
972
  await apiCall('/api/create_chatroom', {
 
981
  });
982
 
983
  const showProfile = async (address) => {
 
984
  try {
985
  const userData = await apiCall('/api/user_data', {
986
  method: 'POST',
 
988
  body: JSON.stringify({ address: address })
989
  });
990
 
991
+ const username = userData.username || `User ${truncateAddress(address)}`;
992
  const avatarContainer = document.getElementById('profile-avatar-container');
993
  const usernameEl = document.getElementById('profile-username');
994
  const addressEl = document.getElementById('profile-address');
995
  const qrCodeEl = document.getElementById('profile-qr-code');
996
  const sendTonBtn = document.getElementById('send-ton-btn');
 
 
997
 
998
  avatarContainer.innerHTML = '';
999
  avatarContainer.appendChild(getAvatar(username));
 
1002
  addressEl.textContent = address;
1003
 
1004
  qrCodeEl.innerHTML = '';
1005
+ if (profileQrCode) profileQrCode.clear();
 
 
1006
  profileQrCode = new QRCode(qrCodeEl, {
1007
  text: address,
1008
  width: 150,
 
1017
  showStatus('Подключите кошелек для отправки TON.', 'error');
1018
  return;
1019
  }
1020
+ const amountString = prompt("Введите сумму в TON для отправки:", "0.1");
1021
  if (amountString === null) return;
1022
 
1023
  const amount = parseFloat(amountString);
 
1026
  return;
1027
  }
1028
 
1029
+ const amountInNanoTon = Math.floor(amount * 1_000_000_000).toString();
 
1030
 
1031
  const transaction = {
1032
+ validUntil: Math.floor(Date.now() / 1000) + 600,
1033
  messages: [ { address: address, amount: amountInNanoTon } ]
1034
  };
1035
 
1036
  try {
1037
+ await tonConnectUI.sendTransaction(transaction);
1038
+ showStatus(`Транзакция отправлена успешно!`, 'success');
 
1039
  profileModal.style.display = 'none';
1040
  } catch (error) {
1041
+ showStatus('Транзакция отклонена.', 'error');
 
 
 
 
 
1042
  }
1043
  };
1044
 
1045
+ sendTonBtn.style.display = (address === currentUser.address) ? 'none' : 'block';
1046
  profileModal.style.display = 'flex';
1047
  } catch (err) {
1048
+ showStatus('Не удалось загрузить профиль.', 'error');
 
1049
  }
1050
  };
1051
 
1052
  const showScanner = () => {
1053
  scannerModal.style.display = 'flex';
 
 
 
1054
  html5QrCode = new Html5Qrcode("qr-reader");
1055
  const qrCodeSuccessCallback = (decodedText, decodedResult) => {
1056
  hideScanner();
 
1057
  if (decodedText && decodedText.length > 40 && (decodedText.startsWith('EQ') || decodedText.startsWith('UQ'))) {
1058
+ showProfile(decodedText);
1059
  } else {
1060
+ showStatus('Отсканирован недействительный QR-код.', 'error');
1061
  }
1062
  };
1063
  const config = { fps: 10, qrbox: { width: 250, height: 250 } };
1064
  html5QrCode.start({ facingMode: "environment" }, config, qrCodeSuccessCallback)
1065
  .catch(err => {
1066
  showStatus('Не удалось запустить сканер.', 'error');
 
1067
  hideScanner();
1068
  });
1069
  };
 
1075
  scannerModal.style.display = 'none';
1076
  };
1077
 
1078
+ document.getElementById('my-profile-btn').addEventListener('click', () => {
1079
+ if (currentUser.address) showProfile(currentUser.address);
 
 
 
 
 
 
1080
  });
 
 
 
 
 
 
 
 
 
 
 
 
1081
  document.getElementById('profile-close-btn').addEventListener('click', () => profileModal.style.display = 'none');
1082
  document.getElementById('scanner-close-btn').addEventListener('click', hideScanner);
1083
+ document.getElementById('back-to-list-btn').addEventListener('click', showListView);
1084
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1085
  const handleResize = () => {
1086
  const isMobile = window.innerWidth < 768;
 
 
1087
  document.getElementById('back-to-list-btn').style.display = isMobile ? 'block' : 'none';
1088
+ if (!isMobile) {
1089
+ chatroomListView.style.display = 'flex';
1090
+ chatWindowView.style.display = 'flex';
1091
+ } else {
1092
+ if (document.querySelector('.view-panel.active').id === 'chat-view') {
1093
+ if (activeChat.style.display === 'flex') {
1094
+ chatroomListView.style.display = 'none';
1095
+ } else {
1096
+ chatWindowView.style.display = 'none';
1097
+ chatroomListView.style.display = 'flex';
1098
+ }
1099
+ }
1100
+ }
1101
  };
1102
 
1103
  window.addEventListener('resize', handleResize);
 
 
1104
  handleResize();
1105
 
 
1106
  tonConnectUI.onStatusChange(wallet => {
1107
  if (wallet) {
1108
+ const address = TON_CONNECT_UI.toUserFriendlyAddress(wallet.account.address, false);
 
1109
  initializeUser(address);
1110
  } else {
 
1111
  currentUser = { address: null, username: null };
1112
  appView.style.display = 'none';
1113
  loginView.style.display = 'flex';
1114
  if (messagePollingInterval) clearInterval(messagePollingInterval);
 
1115
  activeChatroomId = null;
 
 
 
 
 
 
 
 
 
 
1116
  }
1117
  });
1118
+
1119
+ const viewPanels = document.querySelectorAll('.view-panel');
1120
+ const navButtons = document.querySelectorAll('.nav-btn');
1121
+
1122
+ function switchView(targetViewId) {
1123
+ viewPanels.forEach(panel => {
1124
+ panel.classList.remove('active');
1125
+ });
1126
+ document.getElementById(targetViewId).classList.add('active');
1127
+
1128
+ navButtons.forEach(btn => {
1129
+ btn.classList.remove('active');
1130
+ if (btn.id === `nav-${targetViewId.split('-')[0]}-btn`) {
1131
+ btn.classList.add('active');
1132
+ }
1133
+ });
1134
+ handleResize();
1135
+ }
1136
+
1137
+ document.getElementById('nav-chat-btn').addEventListener('click', () => switchView('chat-view'));
1138
+ document.getElementById('nav-browser-btn').addEventListener('click', () => switchView('browser-view'));
1139
+ document.getElementById('nav-scan-btn').addEventListener('click', showScanner);
1140
+
1141
+ const browserIframe = document.getElementById('browser-iframe');
1142
+ const urlForm = document.getElementById('url-form');
1143
+ const urlInput = document.getElementById('url-input');
1144
+
1145
+ urlForm.addEventListener('submit', (e) => {
1146
+ e.preventDefault();
1147
+ let url = urlInput.value.trim();
1148
+ if (!url) return;
1149
+ if (!url.startsWith('http://') && !url.startsWith('https://')) {
1150
+ if (url.includes('.') && !url.includes(' ')) {
1151
+ url = 'https://' + url;
1152
+ } else {
1153
+ url = `https://www.google.com/search?q=${encodeURIComponent(url)}`;
1154
+ }
1155
+ }
1156
+ browserIframe.src = url;
1157
+ });
1158
+
1159
+ document.getElementById('browser-back').onclick = () => { try { browserIframe.contentWindow.history.back(); } catch(e){} };
1160
+ document.getElementById('browser-forward').onclick = () => { try { browserIframe.contentWindow.history.forward(); } catch(e){} };
1161
+ document.getElementById('browser-reload').onclick = () => { try { browserIframe.contentWindow.location.reload(); } catch(e){} };
1162
+
1163
  });
1164
  </script>
1165
  </body>
 
1175
  if not address:
1176
  return jsonify({'error': 'Address is required'}), 400
1177
  db = read_db()
1178
+ user_info = db['users'].get(address)
1179
+ username = user_info.get('username') if user_info else None
1180
  return jsonify({'username': username})
1181
 
1182
  @app.route('/api/set_username', methods=['POST'])
 
1206
  'name': room_data['name'],
1207
  'is_private': room_data['is_private']
1208
  })
1209
+ return jsonify({'chatrooms': sorted(chatrooms_list, key=lambda x: x['name'])})
1210
 
1211
  @app.route('/api/create_chatroom', methods=['POST'])
1212
  def create_chatroom():
 
1216
  creator_address = data.get('creator_address')
1217
  if not name or not creator_address:
1218
  return jsonify({'error': 'Name and creator address are required'}), 400
 
 
 
 
1219
 
1220
  db = read_db()
 
 
 
 
 
1221
  room_id = str(uuid.uuid4())
1222
  db['chatrooms'][room_id] = {
1223
  'name': name,
 
1247
  def get_messages(chatroom_id):
1248
  db = read_db()
1249
  if chatroom_id not in db['messages']:
1250
+ return jsonify({'error': 'Chatroom not found'}), 404
1251
+
 
 
 
 
 
 
 
 
 
1252
  messages_with_names = []
1253
  room_messages = db['messages'].get(chatroom_id, [])
 
 
 
1254
 
1255
  for msg in room_messages:
1256
  sender_address = msg['sender_address']
1257
+ user_info = db['users'].get(sender_address)
1258
+ display_name = (user_info.get('username') if user_info and user_info.get('username')
1259
+ else f"{sender_address[:4]}...{sender_address[-4:]}")
1260
 
 
 
 
 
 
 
1261
  msg_copy = msg.copy()
1262
+ msg_copy['display_name'] = display_name
1263
  messages_with_names.append(msg_copy)
1264
 
1265
  return jsonify({'messages': messages_with_names})
 
1273
 
1274
  if not all([chatroom_id, sender_address, text]):
1275
  return jsonify({'error': 'Missing data'}), 400
 
 
 
 
 
 
 
 
1276
 
1277
  db = read_db()
1278
  if chatroom_id not in db['messages']:
1279
+ return jsonify({'error': 'Chatroom not found'}), 404
 
 
 
1280
 
1281
  message = {
1282
  'id': str(uuid.uuid4()),
 
1285
  'timestamp': datetime.utcnow().isoformat() + "Z"
1286
  }
1287
 
 
1288
  if len(db['messages'][chatroom_id]) >= 100:
1289
  db['messages'][chatroom_id].pop(0)
1290
 
 
1295
 
1296
  if __name__ == '__main__':
1297
  init_db()
1298
+ app.run(host='0.0.0.0', port=7860)