@armoyu/ui 1.1.40 → 1.1.41

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/dist/components/modules/chat/ChatLayout.d.ts +7 -1
  2. package/dist/components/modules/chat/ChatLayout.d.ts.map +1 -1
  3. package/dist/components/modules/chat/ChatLayout.js +347 -59
  4. package/dist/components/modules/chat/ChatLayout.js.map +1 -1
  5. package/dist/components/modules/chat/widgets/ChatInput.d.ts.map +1 -1
  6. package/dist/components/modules/chat/widgets/ChatInput.js +24 -8
  7. package/dist/components/modules/chat/widgets/ChatInput.js.map +1 -1
  8. package/dist/components/modules/chat/widgets/ChatList.d.ts +2 -1
  9. package/dist/components/modules/chat/widgets/ChatList.d.ts.map +1 -1
  10. package/dist/components/modules/chat/widgets/ChatList.js +21 -10
  11. package/dist/components/modules/chat/widgets/ChatList.js.map +1 -1
  12. package/dist/components/modules/chat/widgets/VoiceCallWidget.d.ts +16 -0
  13. package/dist/components/modules/chat/widgets/VoiceCallWidget.d.ts.map +1 -0
  14. package/dist/components/modules/chat/widgets/VoiceCallWidget.js +38 -0
  15. package/dist/components/modules/chat/widgets/VoiceCallWidget.js.map +1 -0
  16. package/dist/components/modules/education/pages/EducationPage.d.ts.map +1 -1
  17. package/dist/components/modules/education/pages/EducationPage.js +0 -2
  18. package/dist/components/modules/education/pages/EducationPage.js.map +1 -1
  19. package/dist/components/modules/news/widgets/NewsComments.d.ts +2 -1
  20. package/dist/components/modules/news/widgets/NewsComments.d.ts.map +1 -1
  21. package/dist/components/modules/news/widgets/NewsComments.js +13 -3
  22. package/dist/components/modules/news/widgets/NewsComments.js.map +1 -1
  23. package/dist/components/modules/posts/widgets/MediaLightbox.d.ts +2 -1
  24. package/dist/components/modules/posts/widgets/MediaLightbox.d.ts.map +1 -1
  25. package/dist/components/modules/posts/widgets/MediaLightbox.js +3 -3
  26. package/dist/components/modules/posts/widgets/MediaLightbox.js.map +1 -1
  27. package/dist/components/modules/posts/widgets/PostCard.d.ts.map +1 -1
  28. package/dist/components/modules/posts/widgets/PostCard.js +17 -3
  29. package/dist/components/modules/posts/widgets/PostCard.js.map +1 -1
  30. package/dist/components/modules/profile/widgets/ProfileHeader.d.ts.map +1 -1
  31. package/dist/components/modules/profile/widgets/ProfileHeader.js +66 -5
  32. package/dist/components/modules/profile/widgets/ProfileHeader.js.map +1 -1
  33. package/dist/components/modules/social/widgets/PostComposer.d.ts.map +1 -1
  34. package/dist/components/modules/social/widgets/PostComposer.js +3 -2
  35. package/dist/components/modules/social/widgets/PostComposer.js.map +1 -1
  36. package/dist/components/shared/EmojiPicker.d.ts +10 -0
  37. package/dist/components/shared/EmojiPicker.d.ts.map +1 -0
  38. package/dist/components/shared/EmojiPicker.js +205 -0
  39. package/dist/components/shared/EmojiPicker.js.map +1 -0
  40. package/dist/components/shared/FloatingChatButton.d.ts.map +1 -1
  41. package/dist/components/shared/FloatingChatButton.js +3 -2
  42. package/dist/components/shared/FloatingChatButton.js.map +1 -1
  43. package/dist/components/showcase/GroupTab.d.ts.map +1 -1
  44. package/dist/components/showcase/GroupTab.js +1 -10
  45. package/dist/components/showcase/GroupTab.js.map +1 -1
  46. package/dist/components/showcase/ProfileTab.d.ts.map +1 -1
  47. package/dist/components/showcase/ProfileTab.js +0 -5
  48. package/dist/components/showcase/ProfileTab.js.map +1 -1
  49. package/dist/components/showcase/SocialTab.d.ts.map +1 -1
  50. package/dist/components/showcase/SocialTab.js +0 -4
  51. package/dist/components/showcase/SocialTab.js.map +1 -1
  52. package/dist/context/ChatContext.js +2 -2
  53. package/dist/context/ChatContext.js.map +1 -1
  54. package/dist/index.d.ts +1 -0
  55. package/dist/index.d.ts.map +1 -1
  56. package/dist/index.js +1 -0
  57. package/dist/index.js.map +1 -1
  58. package/dist/models/auth/User.d.ts +1 -0
  59. package/dist/models/auth/User.d.ts.map +1 -1
  60. package/dist/models/auth/User.js +2 -0
  61. package/dist/models/auth/User.js.map +1 -1
  62. package/dist/models/social/chat/Chat.d.ts +1 -0
  63. package/dist/models/social/chat/Chat.d.ts.map +1 -1
  64. package/dist/models/social/chat/Chat.js +21 -47
  65. package/dist/models/social/chat/Chat.js.map +1 -1
  66. package/dist/models/social/chat/Message.d.ts.map +1 -1
  67. package/dist/models/social/chat/Message.js +26 -1
  68. package/dist/models/social/chat/Message.js.map +1 -1
  69. package/dist/types/socket.d.ts +1 -1
  70. package/dist/types/socket.d.ts.map +1 -1
  71. package/package.json +2 -2
@@ -1,2 +1,8 @@
1
- export declare function ChatLayout(): import("react/jsx-runtime").JSX.Element;
1
+ import { Chat } from '../../../models/social/chat/Chat';
2
+ interface ChatLayoutProps {
3
+ onVoiceCall?: (contact: Chat) => void;
4
+ onAvatarClick?: (contact: Chat) => void;
5
+ }
6
+ export declare function ChatLayout({ onVoiceCall, onAvatarClick }?: ChatLayoutProps): import("react/jsx-runtime").JSX.Element;
7
+ export {};
2
8
  //# sourceMappingURL=ChatLayout.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ChatLayout.d.ts","sourceRoot":"","sources":["../../../../src/components/modules/chat/ChatLayout.tsx"],"names":[],"mappings":"AAsBA,wBAAgB,UAAU,4CA2bzB"}
1
+ {"version":3,"file":"ChatLayout.d.ts","sourceRoot":"","sources":["../../../../src/components/modules/chat/ChatLayout.tsx"],"names":[],"mappings":"AAIA,OAAO,EAAE,IAAI,EAAE,MAAM,kCAAkC,CAAC;AAmBxD,UAAU,eAAe;IACvB,WAAW,CAAC,EAAE,CAAC,OAAO,EAAE,IAAI,KAAK,IAAI,CAAC;IACtC,aAAa,CAAC,EAAE,CAAC,OAAO,EAAE,IAAI,KAAK,IAAI,CAAC;CACzC;AAED,wBAAgB,UAAU,CAAC,EAAE,WAAW,EAAE,aAAa,EAAE,GAAE,eAAoB,2CA+yB9E"}
@@ -1,21 +1,25 @@
1
1
  'use client';
2
2
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
- import React, { useState, useEffect } from 'react';
3
+ import React, { useState, useEffect, useCallback } from 'react';
4
4
  import { Chat } from '../../../models/social/chat/Chat';
5
5
  import { ChatMessage as ChatMessageModel } from '../../../models/social/chat/Message';
6
- import { Search, X } from 'lucide-react';
6
+ import { Phone, Search, X } from 'lucide-react';
7
7
  import { useArmoyu } from '../../../context/ArmoyuContext';
8
8
  // Shared Contexts / Utils from main index
9
- import { useAuth, useChat, useSocket, userList } from '../../../index';
9
+ import { useAuth, useChat, useSocket, userList, VoiceCallWidget } from '../../../index';
10
10
  // Module specific widgets
11
11
  import { ChatList } from './widgets/ChatList';
12
12
  import { ChatMessage } from './widgets/ChatMessage';
13
13
  import { ChatInput } from './widgets/ChatInput';
14
- export function ChatLayout() {
14
+ export function ChatLayout({ onVoiceCall, onAvatarClick } = {}) {
15
15
  const { user, session } = useAuth();
16
16
  const { setMockEnabled } = useArmoyu();
17
- const { closeChat, isLiveMode, setLiveMode, chatList: liveContacts, setChatList, activeMessages, setActiveMessages, fetchMessages, fetchChatList, sendMessage: sendApiMessage } = useChat();
17
+ const { isChatOpen, closeChat, isLiveMode, setLiveMode, chatList: liveContacts, searchResults, setChatList, activeMessages, setActiveMessages, fetchMessages, fetchChatList, sendMessage: sendApiMessage } = useChat();
18
18
  const { emit, on, isConnected } = useSocket();
19
+ const isChatOpenRef = React.useRef(isChatOpen);
20
+ useEffect(() => {
21
+ isChatOpenRef.current = isChatOpen;
22
+ }, [isChatOpen]);
19
23
  const handleFetchFromApi = async () => {
20
24
  setMockEnabled(false);
21
25
  setLiveMode(true);
@@ -27,10 +31,114 @@ export function ChatLayout() {
27
31
  const [localMessages, setLocalMessages] = useState([]);
28
32
  const [localContacts, setLocalContacts] = useState([]);
29
33
  const [isTyping, setIsTyping] = useState(false);
34
+ const [typingUsers, setTypingUsers] = useState([]);
30
35
  const [isSearchVisible, setIsSearchVisible] = useState(false);
31
36
  const [msgSearchQuery, setMsgSearchQuery] = useState('');
32
37
  const scrollContainerRef = React.useRef(null);
33
38
  const isFirstRender = React.useRef(true);
39
+ // Call State
40
+ const [activeCall, setActiveCall] = useState(null);
41
+ // Handle call response callbacks
42
+ const handleAcceptCall = useCallback(() => {
43
+ if (!activeCall)
44
+ return;
45
+ const acceptData = {
46
+ caller: activeCall.caller,
47
+ recipientUsername: activeCall.caller.username,
48
+ recipient: activeCall.recipient,
49
+ status: 'accepted'
50
+ };
51
+ emit('chat_call', acceptData);
52
+ setActiveCall(prev => prev ? { ...prev, status: 'connected' } : null);
53
+ }, [activeCall, emit]);
54
+ const handleDeclineCall = useCallback(() => {
55
+ if (!activeCall)
56
+ return;
57
+ const declineData = {
58
+ caller: activeCall.caller,
59
+ recipientUsername: activeCall.caller.username,
60
+ recipient: activeCall.recipient,
61
+ status: 'rejected'
62
+ };
63
+ emit('chat_call', declineData);
64
+ setActiveCall(prev => prev ? { ...prev, status: 'rejected' } : null);
65
+ setTimeout(() => setActiveCall(null), 1500);
66
+ }, [activeCall, emit]);
67
+ const handleHangUpCall = useCallback(() => {
68
+ if (!activeCall)
69
+ return;
70
+ const targetUsername = activeCall.isIncoming ? activeCall.caller.username : activeCall.recipient.username;
71
+ const hangupData = {
72
+ caller: activeCall.caller,
73
+ recipientUsername: targetUsername,
74
+ recipient: activeCall.recipient,
75
+ status: 'hung_up'
76
+ };
77
+ emit('chat_call', hangupData);
78
+ setActiveCall(prev => prev ? { ...prev, status: 'hung_up' } : null);
79
+ setTimeout(() => setActiveCall(null), 1500);
80
+ }, [activeCall, emit]);
81
+ // Listen for socket call events
82
+ useEffect(() => {
83
+ if (!on)
84
+ return;
85
+ const offCall = on('chat_call', (data) => {
86
+ console.log('[Socket] Incoming call event:', data);
87
+ if (data.status === 'ringing') {
88
+ if (user && data.recipientUsername === user.username) {
89
+ if (activeCall && activeCall.status === 'connected') {
90
+ emit('chat_call', {
91
+ caller: data.caller,
92
+ recipientUsername: data.caller.username,
93
+ recipient: data.recipient,
94
+ status: 'busy'
95
+ });
96
+ return;
97
+ }
98
+ setActiveCall({
99
+ caller: data.caller,
100
+ recipient: data.recipient,
101
+ isIncoming: true,
102
+ status: 'ringing'
103
+ });
104
+ }
105
+ }
106
+ else if (data.status === 'accepted') {
107
+ if (activeCall && !activeCall.isIncoming && activeCall.status === 'ringing') {
108
+ setActiveCall(prev => prev ? { ...prev, status: 'connected' } : null);
109
+ }
110
+ }
111
+ else if (data.status === 'rejected') {
112
+ if (activeCall && !activeCall.isIncoming && activeCall.status === 'ringing') {
113
+ setActiveCall(prev => prev ? { ...prev, status: 'rejected' } : null);
114
+ setTimeout(() => setActiveCall(null), 1500);
115
+ }
116
+ }
117
+ else if (data.status === 'busy') {
118
+ if (activeCall && !activeCall.isIncoming && activeCall.status === 'ringing') {
119
+ setActiveCall(prev => prev ? { ...prev, status: 'busy' } : null);
120
+ setTimeout(() => setActiveCall(null), 1500);
121
+ }
122
+ }
123
+ else if (data.status === 'hung_up') {
124
+ setActiveCall(prev => prev ? { ...prev, status: 'hung_up' } : null);
125
+ setTimeout(() => setActiveCall(null), 1500);
126
+ }
127
+ });
128
+ return () => {
129
+ offCall();
130
+ };
131
+ }, [on, user, activeCall, emit]);
132
+ // Soket bağlantısı sağlandığında sunucuya kayıt ol
133
+ useEffect(() => {
134
+ if (isConnected && user?.id) {
135
+ emit('register_user', {
136
+ userId: user.id,
137
+ username: user.username
138
+ });
139
+ console.log(`[Socket] Kayıt isteği gönderildi: ${user.username} (${user.id})`);
140
+ }
141
+ }, [isConnected, user, emit]);
34
142
  // Tarihleri karşılaştırmak ve "Bugün", "Dün" gibi etiketler üretmek için yardımcı fonksiyon
35
143
  const getDateLabel = (dateStr) => {
36
144
  try {
@@ -40,12 +148,8 @@ export function ChatLayout() {
40
148
  if (isNaN(date.getTime()))
41
149
  return dateStr; // Geçersiz tarihse olduğu gibi bırak
42
150
  const today = new Date();
43
- const yesterday = new Date();
44
- yesterday.setDate(today.getDate() - 1);
45
151
  if (date.toDateString() === today.toDateString())
46
152
  return 'Bugün';
47
- if (date.toDateString() === yesterday.toDateString())
48
- return 'Dün';
49
153
  return date.toLocaleDateString('tr-TR', { day: 'numeric', month: 'long', year: 'numeric' });
50
154
  }
51
155
  catch (e) {
@@ -96,8 +200,8 @@ export function ChatLayout() {
96
200
  // Handle Contact Selection in LIVE mode
97
201
  useEffect(() => {
98
202
  if (isLiveMode && activeContactId) {
99
- // Find the contact in the list to get its chatType and raw ID
100
- const contact = (isLiveMode ? liveContacts : localContacts).find(c => c.uid === activeContactId);
203
+ // Find the contact in the list (or search results) to get its chatType and raw ID
204
+ const contact = (isLiveMode ? [...liveContacts, ...searchResults] : localContacts).find(c => c.uid === activeContactId);
101
205
  if (contact) {
102
206
  const idNum = parseInt(contact.id);
103
207
  if (!isNaN(idNum)) {
@@ -109,30 +213,118 @@ export function ChatLayout() {
109
213
  }
110
214
  }, [activeContactId, isLiveMode, fetchMessages]);
111
215
  // Auto-select logic removed to prevent automatic chat opening on load
216
+ const playNotificationSound = () => {
217
+ try {
218
+ const audioContext = new (window.AudioContext || window.webkitAudioContext)();
219
+ const osc = audioContext.createOscillator();
220
+ const gainNode = audioContext.createGain();
221
+ osc.connect(gainNode);
222
+ gainNode.connect(audioContext.destination);
223
+ osc.type = 'sine';
224
+ const now = audioContext.currentTime;
225
+ // Ping 1 (587.33Hz - D5)
226
+ osc.frequency.setValueAtTime(587.33, now);
227
+ gainNode.gain.setValueAtTime(0, now);
228
+ gainNode.gain.linearRampToValueAtTime(0.06, now + 0.04);
229
+ gainNode.gain.exponentialRampToValueAtTime(0.001, now + 0.15);
230
+ // Ping 2 (880Hz - A5)
231
+ osc.frequency.setValueAtTime(880, now + 0.1);
232
+ gainNode.gain.setValueAtTime(0.06, now + 0.1);
233
+ gainNode.gain.exponentialRampToValueAtTime(0.001, now + 0.3);
234
+ osc.start(now);
235
+ osc.stop(now + 0.35);
236
+ }
237
+ catch (err) {
238
+ console.warn("Ses çalınamadı:", err);
239
+ }
240
+ };
112
241
  useEffect(() => {
113
242
  const offMsg = on('chat_message', (incomingMsg) => {
243
+ console.log(`[Socket] Yeni mesaj alındı (ID: ${incomingMsg.id})`);
244
+ // Optimistic Update: Ignore if we just sent this message
245
+ if (incomingMsg.sender?.username === user?.username) {
246
+ return;
247
+ }
248
+ // Check if we are actively chatting with the sender or group
249
+ const currentActiveContact = (isLiveMode ? [...liveContacts, ...searchResults] : localContacts).find(c => c.uid === activeContactId);
250
+ const isGroup = incomingMsg.chatType === 'grup' || String(incomingMsg.chatId) === '0';
251
+ const isChattingWithSender = currentActiveContact && (isGroup
252
+ ? (currentActiveContact.chatType === 'grup' && (String(incomingMsg.chatId) === currentActiveContact.id || (incomingMsg.chatName && currentActiveContact.name === incomingMsg.chatName)))
253
+ : (currentActiveContact.chatType !== 'grup' && (String(incomingMsg.sender?.id) === currentActiveContact.id ||
254
+ incomingMsg.sender?.username === currentActiveContact.username ||
255
+ incomingMsg.sender?.username === currentActiveContact.id)));
256
+ // Play sound if not active in this chat or if chat list is closed
257
+ if (!isChatOpenRef.current || !isChattingWithSender) {
258
+ playNotificationSound();
259
+ }
114
260
  const msgModel = ChatMessageModel.fromAPI(incomingMsg);
115
261
  // 1. UPDATE GLOBAL CHAT LIST CACHE (For both Live and Mock modes)
116
- setChatList(prev => prev.map(c => {
117
- if (c.id === String(incomingMsg.chatId)) {
118
- const messageExists = c.messages.some(m => m.id === msgModel.id);
119
- return new Chat({
120
- ...c,
262
+ setChatList(prev => {
263
+ const contactId = String(incomingMsg.chatId);
264
+ const senderUsername = incomingMsg.sender?.username;
265
+ const isGroupMsg = incomingMsg.chatType === 'grup' || contactId === '0';
266
+ // EŞLEŞTİRME MANTIĞI: c.id (örn: 12138) veya c.username (örn: kkadirgumus) eşleşiyorsa bu sohbet mevcuttur!
267
+ const matched = prev.filter(c => {
268
+ if (isGroupMsg) {
269
+ return c.chatType === 'grup' && (c.id === contactId || (incomingMsg.chatName && c.name === incomingMsg.chatName));
270
+ }
271
+ return c.chatType !== 'grup' && (c.id === String(incomingMsg.sender?.id) || c.username === senderUsername || c.id === senderUsername);
272
+ });
273
+ console.log(`[Socket] Etkilenen Sohbet Sayısı: ${matched.length}`);
274
+ matched.forEach(c => {
275
+ console.log(`[Socket] Etkilenen Sohbet Bilgisi -> Ad: ${c.name}, ID: ${c.id}, Tip: ${c.chatType}`);
276
+ });
277
+ const exists = matched.length > 0;
278
+ // Eğer listede yoksa ve kendi mesajımız değilse YENİDEN OLUŞTUR (Sohbeti silenler için)
279
+ if (!exists && senderUsername && senderUsername !== user?.username) {
280
+ const finalId = isGroupMsg ? contactId : (incomingMsg.sender?.id || senderUsername);
281
+ const newChat = new Chat({
282
+ id: finalId,
283
+ uid: isGroupMsg ? `grup-${finalId}` : `ozel-${finalId}`,
284
+ username: isGroupMsg ? '' : senderUsername,
285
+ name: isGroupMsg ? (incomingMsg.chatName || 'Grup Sohbeti') : (incomingMsg.sender?.displayName || senderUsername),
286
+ avatar: isGroupMsg ? '' : (incomingMsg.sender?.avatar || ''),
121
287
  lastMessage: msgModel,
122
288
  time: msgModel.timestamp,
123
289
  updatedAt: Date.now(),
124
- messages: messageExists ? c.messages : [...(c.messages || []), msgModel],
125
- unreadCount: (activeContactId !== c.uid && incomingMsg.sender?.username !== user?.username) ? c.unreadCount + 1 : c.unreadCount
290
+ messages: [msgModel],
291
+ unreadCount: (activeContactId !== (isGroupMsg ? `grup-${finalId}` : `ozel-${finalId}`)) ? 1 : 0,
292
+ isOnline: !isGroupMsg,
293
+ chatType: isGroupMsg ? 'grup' : 'ozel'
126
294
  });
295
+ return [newChat, ...prev];
127
296
  }
128
- return c;
129
- }));
297
+ // Varsa mevcut olanı güncelle
298
+ return prev.map(c => {
299
+ const isMatch = isGroupMsg
300
+ ? (c.chatType === 'grup' && (c.id === contactId || (incomingMsg.chatName && c.name === incomingMsg.chatName)))
301
+ : (c.chatType !== 'grup' && (c.id === String(incomingMsg.sender?.id) || c.username === senderUsername || c.id === senderUsername));
302
+ if (isMatch) {
303
+ const messageExists = c.messages.some(m => m.id === msgModel.id);
304
+ return new Chat({
305
+ ...c,
306
+ lastMessage: msgModel,
307
+ time: msgModel.timestamp,
308
+ updatedAt: Date.now(),
309
+ messages: messageExists ? c.messages : [...(c.messages || []), msgModel],
310
+ unreadCount: (activeContactId !== c.uid && incomingMsg.sender?.username !== user?.username) ? c.unreadCount + 1 : c.unreadCount
311
+ });
312
+ }
313
+ return c;
314
+ });
315
+ });
130
316
  // 2. UPDATE ACTIVE MESSAGES IF OPEN
131
- const currentActiveContact = (isLiveMode ? liveContacts : localContacts).find(c => c.uid === activeContactId);
132
- if (currentActiveContact && (incomingMsg.chatId === currentActiveContact.id || incomingMsg.sender?.username === currentActiveContact.id)) {
133
- // Since we are updating the global chatList, activeMessages should ideally sync if using Context well,
134
- // but for immediate UI feedback we update it here too.
135
- fetchMessages(parseInt(currentActiveContact.id), currentActiveContact.chatType);
317
+ if (isChattingWithSender) {
318
+ setTypingUsers(prev => {
319
+ const filtered = prev.filter(u => u.username !== incomingMsg.sender?.username);
320
+ setIsTyping(filtered.length > 0);
321
+ return filtered;
322
+ });
323
+ setActiveMessages(prev => {
324
+ if (prev.some(m => m.id === msgModel.id))
325
+ return prev;
326
+ return [...prev, msgModel];
327
+ });
136
328
  }
137
329
  // 3. MOCK MODE UPDATES
138
330
  if (!isLiveMode) {
@@ -170,14 +362,43 @@ export function ChatLayout() {
170
362
  });
171
363
  });
172
364
  }
173
- setIsTyping(false);
365
+ if (incomingMsg.sender?.username) {
366
+ setTypingUsers(prev => {
367
+ const filtered = prev.filter(u => u.username !== incomingMsg.sender.username);
368
+ setIsTyping(filtered.length > 0);
369
+ return filtered;
370
+ });
371
+ }
174
372
  });
175
- const offTyping = on('typing', (data) => {
176
- const currentActiveContact = (isLiveMode ? liveContacts : localContacts).find(c => c.uid === activeContactId);
177
- const isChattingWithSender = currentActiveContact && data.username === currentActiveContact.id && data.chatId === user?.username;
373
+ const offTyping = on('chat_typing', (data) => {
374
+ const currentActiveContact = (isLiveMode ? [...liveContacts, ...searchResults] : localContacts).find(c => c.uid === activeContactId);
375
+ if (!currentActiveContact)
376
+ return;
377
+ const isGroupChat = currentActiveContact.chatType === 'grup' || currentActiveContact.id === '0';
378
+ const isTypingHere = isGroupChat
379
+ ? ((String(data.chatId) === currentActiveContact.id || data.chatId === currentActiveContact.username) && data.username !== user?.username)
380
+ : (data.username === currentActiveContact.username && data.chatId === user?.username);
178
381
  const isBotTyping = data.username === 'system' && data.chatId === user?.username;
179
- if (isChattingWithSender || isBotTyping) {
180
- setIsTyping(data.isTyping);
382
+ if (isTypingHere || isBotTyping) {
383
+ if (data.isTyping) {
384
+ setTypingUsers(prev => {
385
+ if (prev.some(u => u.username === data.username))
386
+ return prev;
387
+ const updated = [...prev, {
388
+ username: data.username,
389
+ avatar: data.avatar || (isBotTyping ? 'https://api.dicebear.com/7.x/avataaars/svg?seed=System' : currentActiveContact.avatar)
390
+ }];
391
+ setIsTyping(true);
392
+ return updated;
393
+ });
394
+ }
395
+ else {
396
+ setTypingUsers(prev => {
397
+ const updated = prev.filter(u => u.username !== data.username);
398
+ setIsTyping(updated.length > 0);
399
+ return updated;
400
+ });
401
+ }
181
402
  }
182
403
  });
183
404
  return () => {
@@ -186,7 +407,7 @@ export function ChatLayout() {
186
407
  };
187
408
  }, [on, activeContactId, user?.username, isLiveMode]);
188
409
  const handleSelectContact = (uid) => {
189
- const contact = (isLiveMode ? liveContacts : localContacts).find(c => c.uid === uid);
410
+ const contact = (isLiveMode ? [...liveContacts, ...searchResults] : localContacts).find(c => c.uid === uid);
190
411
  // Caching: If we already have messages for this contact, show them immediately
191
412
  if (contact && contact.messages && contact.messages.length > 0) {
192
413
  setActiveMessages(contact.messages);
@@ -216,12 +437,37 @@ export function ChatLayout() {
216
437
  }
217
438
  setActiveContactId(uid);
218
439
  setIsTyping(false);
440
+ setTypingUsers([]);
219
441
  };
220
442
  const handleSendMessage = async (text) => {
221
443
  if (!text.trim() || !activeContactId)
222
444
  return;
223
445
  if (isLiveMode) {
224
- const contact = (isLiveMode ? liveContacts : localContacts).find(c => c.uid === activeContactId);
446
+ const contact = (isLiveMode ? [...liveContacts, ...searchResults] : localContacts).find(c => c.uid === activeContactId);
447
+ // Optimistic Update: Anında UI'a ekle ki bekleme olmasın
448
+ const newMessage = new ChatMessageModel({
449
+ id: Date.now().toString(),
450
+ sender: user,
451
+ content: text,
452
+ timestamp: new Date().toLocaleTimeString('tr-TR', { hour: '2-digit', minute: '2-digit' }),
453
+ fullDate: new Date().toISOString(),
454
+ isSystem: false,
455
+ isSelf: true
456
+ });
457
+ setActiveMessages(prev => [...prev, newMessage]);
458
+ // Global chat list'in son mesajını da anında güncelle
459
+ setChatList(prev => prev.map(c => {
460
+ if (c.uid === activeContactId) {
461
+ return new Chat({
462
+ ...c,
463
+ lastMessage: newMessage,
464
+ time: newMessage.timestamp,
465
+ updatedAt: Date.now(),
466
+ messages: [...(c.messages || []), newMessage]
467
+ });
468
+ }
469
+ return c;
470
+ }));
225
471
  if (contact) {
226
472
  const idNum = parseInt(contact.id);
227
473
  if (!isNaN(idNum)) {
@@ -252,48 +498,90 @@ export function ChatLayout() {
252
498
  return c;
253
499
  }));
254
500
  }
255
- const currentContact = (isLiveMode ? liveContacts : localContacts).find(c => c.uid === activeContactId);
501
+ const currentContact = (isLiveMode ? [...liveContacts, ...searchResults] : localContacts).find(c => c.uid === activeContactId);
502
+ // Eğer contact listede yoksa (örn. arama sonuçlarından seçilmişse), activeContactId "ozel-11107" gibi gelir.
503
+ // ID kısmını ayıklayıp göndermeliyiz.
504
+ const finalChatId = currentContact?.id || activeContactId.replace('ozel-', '').replace('grup-', '');
256
505
  emit('chat_message', {
257
506
  id: Date.now().toString(),
258
- chatId: currentContact?.id || activeContactId,
507
+ chatId: finalChatId,
508
+ chatType: currentContact?.chatType || (activeContactId.startsWith('grup-') ? 'grup' : 'ozel'),
509
+ chatName: currentContact?.name || '',
259
510
  sender: {
511
+ id: user?.id,
260
512
  username: user?.username,
261
- displayName: user?.displayName,
513
+ displayName: user?.getName?.() || user?.displayName || user?.username,
262
514
  avatar: user?.avatar
263
515
  },
264
516
  content: text,
265
- timestamp: new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }),
517
+ timestamp: new Date().toLocaleTimeString('tr-TR', { hour: '2-digit', minute: '2-digit' }),
266
518
  tarih: new Date().toISOString()
267
519
  });
268
520
  };
269
521
  if (!user) {
270
522
  return (_jsx("div", { className: "flex items-center justify-center h-full text-armoyu-text-muted bg-armoyu-card-bg rounded-3xl border border-gray-200 dark:border-white/10", children: "Sohbetleri g\u00F6rmek i\u00E7in giri\u015F yapmal\u0131s\u0131n\u0131z." }));
271
523
  }
272
- const currentContacts = isLiveMode ? liveContacts : localContacts;
524
+ const currentContacts = isLiveMode ? [...liveContacts, ...searchResults] : localContacts;
273
525
  const activeContact = activeContactId ? currentContacts.find((c) => c.uid === activeContactId) : null;
274
- return (_jsxs("div", { className: "flex h-full w-full overflow-hidden relative z-10", children: [!activeContactId && (_jsxs("div", { className: "w-full h-full flex flex-col animate-in fade-in slide-in-from-left-4 duration-300", children: [_jsx("div", { className: "p-4 border-b border-gray-200 dark:border-white/5 flex items-center justify-between", children: _jsx("h2", { className: "font-black text-armoyu-text uppercase tracking-widest text-sm", children: "Sohbetler" }) }), _jsx(ChatList, { contacts: currentContacts, activeId: activeContactId || '', onSelect: handleSelectContact })] })), activeContactId && activeContact && (_jsxs("div", { className: "w-full h-full flex flex-col bg-armoyu-bg relative animate-in fade-in slide-in-from-right-4 duration-300", children: [_jsxs("div", { className: "h-[76px] border-b border-gray-200 dark:border-white/5 bg-armoyu-card-bg flex items-center px-4 gap-3 z-10 shrink-0", children: [_jsx("button", { onClick: () => setActiveContactId(null), className: "p-2 -ml-2 text-armoyu-text-muted hover:text-armoyu-text hover:bg-black/5 dark:hover:bg-white/5 transition-colors rounded-full focus:outline-none", children: _jsx("svg", { xmlns: "http://www.w3.org/2000/svg", width: "28", height: "28", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", children: _jsx("polyline", { points: "15 18 9 12 15 6" }) }) }), _jsx("img", { src: activeContact.avatar, className: "w-10 h-10 rounded-full border border-black/5 dark:border-white/10 shadow-sm", alt: "" }), _jsx("div", { className: "flex-1 min-w-0", children: isSearchVisible ? (_jsxs("div", { className: "flex items-center bg-black/5 dark:bg-white/5 border border-black/10 dark:border-white/10 rounded-xl px-3 py-1.5 animate-in fade-in slide-in-from-right-2 duration-300", children: [_jsx(Search, { className: "w-4 h-4 text-armoyu-text-muted mr-2" }), _jsx("input", { type: "text", autoFocus: true, placeholder: "Mesajlarda ara...", value: msgSearchQuery, onChange: (e) => setMsgSearchQuery(e.target.value), className: "flex-1 bg-transparent border-none outline-none text-sm text-armoyu-text placeholder:text-armoyu-text-muted font-medium" }), _jsx("button", { onClick: () => { setIsSearchVisible(false); setMsgSearchQuery(''); }, className: "p-1 hover:bg-black/5 rounded-full transition-colors", children: _jsx(X, { className: "w-4 h-4 text-armoyu-text-muted" }) })] })) : (_jsxs(_Fragment, { children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("h3", { className: "font-bold text-armoyu-text truncate text-base", children: activeContact.name }), activeContact.isGroup && (_jsx("span", { className: "px-2 py-0.5 rounded-md bg-armoyu-primary/10 text-armoyu-primary text-[9px] font-black uppercase tracking-widest border border-armoyu-primary/20 flex-shrink-0", children: "Topluluk" }))] }), _jsx("div", { className: "flex items-center gap-1.5 mt-0.5", children: activeContact.isOnline ? (_jsxs(_Fragment, { children: [_jsx("span", { className: "w-2 h-2 rounded-full bg-emerald-500 shadow-[0_0_5px_rgba(16,185,129,0.6)] animate-pulse" }), _jsx("span", { className: "text-xs font-bold text-emerald-500 shadow-sm", children: "\u00C7evrimi\u00E7i" })] })) : (_jsxs(_Fragment, { children: [_jsx("span", { className: "w-2 h-2 rounded-full bg-gray-400 dark:bg-gray-600" }), _jsxs("span", { className: "text-xs font-bold text-armoyu-text-muted", children: ["Son g\u00F6r\u00FClme ", activeContact.lastSeen] })] })) })] })) }), _jsxs("div", { className: "flex gap-1", children: [_jsx("button", { onClick: () => setIsSearchVisible(!isSearchVisible), className: `p-2 rounded-full transition-colors ${isSearchVisible ? 'text-armoyu-primary bg-armoyu-primary/10' : 'text-armoyu-text-muted hover:text-armoyu-primary hover:bg-armoyu-primary/10'}`, title: "Sohbet \u0130\u00E7i Ara", children: _jsx(Search, { className: "w-5 h-5" }) }), _jsx("button", { onClick: closeChat, className: "p-2 text-armoyu-text-muted hover:text-red-500 rounded-full hover:bg-red-500/10 transition-colors", title: "Sohbeti Kapat", children: _jsx(X, { className: "w-5 h-5" }) })] })] }), _jsxs("div", { ref: scrollContainerRef, className: "flex-1 overflow-y-auto p-4 md:p-6 flex flex-col-reverse gap-2 relative hide-scrollbar scroll-smooth", children: [isTyping && (_jsx("div", { className: "flex gap-2 items-center px-4 mb-2", children: _jsx("div", { className: "flex gap-1 items-center bg-black/5 dark:bg-white/5 px-3 py-1.5 rounded-2xl border border-black/5 dark:border-white/5", children: _jsxs("div", { className: "flex gap-0.5 mt-0.5", children: [_jsx("span", { className: "w-1 h-1 rounded-full bg-armoyu-primary animate-bounce [animation-delay:-0.3s]" }), _jsx("span", { className: "w-1 h-1 rounded-full bg-armoyu-primary animate-bounce [animation-delay:-0.15s]" }), _jsx("span", { className: "w-1 h-1 rounded-full bg-armoyu-primary animate-bounce" })] }) }) })), (() => {
526
+ return (_jsxs("div", { className: "flex h-full w-full overflow-hidden relative z-10", children: [!activeContactId && (_jsxs("div", { className: "w-full h-full flex flex-col animate-in fade-in slide-in-from-left-4 duration-300", children: [_jsx("div", { className: "p-4 border-b border-gray-200 dark:border-white/5 flex items-center justify-between shrink-0", children: _jsx("h2", { className: "font-black text-armoyu-text uppercase tracking-widest text-sm", children: "Sohbetler" }) }), _jsx(ChatList, { contacts: currentContacts, activeId: activeContactId || '', onSelect: handleSelectContact, className: "flex-1 min-h-0" })] })), activeContactId && activeContact && (_jsxs("div", { className: "w-full h-full flex flex-col bg-armoyu-bg relative animate-in fade-in slide-in-from-right-4 duration-300", children: [_jsxs("div", { className: "h-[76px] border-b border-gray-200 dark:border-white/5 bg-armoyu-card-bg flex items-center px-4 gap-3 z-10 shrink-0", children: [_jsx("button", { onClick: () => setActiveContactId(null), className: "p-2 -ml-2 text-armoyu-text-muted hover:text-armoyu-text hover:bg-black/5 dark:hover:bg-white/5 transition-colors rounded-full focus:outline-none", children: _jsx("svg", { xmlns: "http://www.w3.org/2000/svg", width: "28", height: "28", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", children: _jsx("polyline", { points: "15 18 9 12 15 6" }) }) }), _jsx("img", { src: activeContact.avatar, onClick: () => {
527
+ if (onAvatarClick) {
528
+ onAvatarClick(activeContact);
529
+ }
530
+ else {
531
+ alert(`${activeContact.name} profil sayfası açılıyor (ID: ${activeContact.id}, Username: ${activeContact.username}).`);
532
+ }
533
+ }, className: "w-10 h-10 rounded-full border border-black/5 dark:border-white/10 shadow-sm cursor-pointer hover:opacity-80 transition-opacity", alt: "" }), _jsx("div", { className: "flex-1 min-w-0", children: isSearchVisible ? (_jsxs("div", { className: "flex items-center bg-black/5 dark:bg-white/5 border border-black/10 dark:border-white/10 rounded-xl px-3 py-1.5 animate-in fade-in slide-in-from-right-2 duration-300", children: [_jsx(Search, { className: "w-4 h-4 text-armoyu-text-muted mr-2" }), _jsx("input", { type: "text", autoFocus: true, placeholder: "Mesajlarda ara...", value: msgSearchQuery, onChange: (e) => setMsgSearchQuery(e.target.value), className: "flex-1 bg-transparent border-none outline-none text-sm text-armoyu-text placeholder:text-armoyu-text-muted font-medium" }), _jsx("button", { onClick: () => { setIsSearchVisible(false); setMsgSearchQuery(''); }, className: "p-1 hover:bg-black/5 rounded-full transition-colors", children: _jsx(X, { className: "w-4 h-4 text-armoyu-text-muted" }) })] })) : (_jsxs(_Fragment, { children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("h3", { className: "font-bold text-armoyu-text truncate text-base", children: activeContact.name }), activeContact.isGroup && (_jsx("span", { className: "px-2 py-0.5 rounded-md bg-armoyu-primary/10 text-armoyu-primary text-[9px] font-black uppercase tracking-widest border border-armoyu-primary/20 flex-shrink-0", children: "Topluluk" }))] }), _jsx("div", { className: "flex items-center gap-1.5 mt-0.5", children: activeContact.isOnline ? (_jsxs(_Fragment, { children: [_jsx("span", { className: "w-2 h-2 rounded-full bg-emerald-500 shadow-[0_0_5px_rgba(16,185,129,0.6)] animate-pulse" }), _jsx("span", { className: "text-xs font-bold text-emerald-500 shadow-sm", children: "\u00C7evrimi\u00E7i" })] })) : (_jsxs(_Fragment, { children: [_jsx("span", { className: "w-2 h-2 rounded-full bg-gray-400 dark:bg-gray-600" }), _jsxs("span", { className: "text-xs font-bold text-armoyu-text-muted", children: ["Son g\u00F6r\u00FClme ", activeContact.lastSeen] })] })) })] })) }), _jsxs("div", { className: "flex gap-1", children: [_jsx("button", { onClick: () => {
534
+ if (activeContact) {
535
+ const callData = {
536
+ caller: {
537
+ username: user?.username || 'user',
538
+ displayName: user?.displayName || 'Kullanıcı',
539
+ avatar: user?.avatar || 'https://api.dicebear.com/7.x/avataaars/svg'
540
+ },
541
+ recipientUsername: activeContact.username || activeContact.id,
542
+ recipient: {
543
+ username: activeContact.username || activeContact.id,
544
+ displayName: activeContact.name,
545
+ avatar: activeContact.avatar
546
+ },
547
+ status: 'ringing'
548
+ };
549
+ emit('chat_call', callData);
550
+ setActiveCall({
551
+ caller: callData.caller,
552
+ recipient: callData.recipient,
553
+ isIncoming: false,
554
+ status: 'ringing'
555
+ });
556
+ }
557
+ }, className: "p-2 text-armoyu-text-muted hover:text-emerald-500 rounded-full hover:bg-emerald-500/10 transition-colors", title: "Sesli Arama Ba\u015Flat", children: _jsx(Phone, { className: "w-5 h-5" }) }), _jsx("button", { onClick: () => setIsSearchVisible(!isSearchVisible), className: `p-2 rounded-full transition-colors ${isSearchVisible ? 'text-armoyu-primary bg-armoyu-primary/10' : 'text-armoyu-text-muted hover:text-armoyu-primary hover:bg-armoyu-primary/10'}`, title: "Sohbet \u0130\u00E7i Ara", children: _jsx(Search, { className: "w-5 h-5" }) }), _jsx("button", { onClick: closeChat, className: "p-2 text-armoyu-text-muted hover:text-red-500 rounded-full hover:bg-red-500/10 transition-colors", title: "Sohbeti Kapat", children: _jsx(X, { className: "w-5 h-5" }) })] })] }), _jsxs("div", { ref: scrollContainerRef, className: "flex-1 overflow-y-auto p-4 md:p-6 flex flex-col-reverse gap-2 relative hide-scrollbar scroll-smooth", children: [typingUsers.length > 0 && (_jsxs("div", { className: "flex gap-2 items-center px-4 mb-2 animate-in fade-in slide-in-from-bottom-2 duration-300", children: [_jsxs("div", { className: "flex -space-x-2 overflow-hidden shrink-0", children: [typingUsers.slice(0, 3).map((tUser, idx) => (_jsx("img", { src: tUser.avatar || 'https://api.dicebear.com/7.x/avataaars/svg?seed=Anonymous', alt: tUser.username, className: "inline-block w-6 h-6 rounded-full ring-2 ring-white dark:ring-[#0a0a0e] object-cover bg-white/5", style: { zIndex: 10 - idx }, onError: (e) => { e.target.src = 'https://api.dicebear.com/7.x/avataaars/svg?seed=Anonymous'; } }, tUser.username))), typingUsers.length > 3 && (_jsxs("div", { className: "flex items-center justify-center w-6 h-6 rounded-full bg-armoyu-primary text-white text-[8px] font-black ring-2 ring-white dark:ring-[#0a0a0e] z-0", children: ["+", typingUsers.length - 3] }))] }), _jsx("div", { className: "flex gap-1 items-center bg-black/5 dark:bg-white/5 px-3 py-1.5 rounded-2xl border border-black/5 dark:border-white/5", children: _jsxs("div", { className: "flex gap-0.5 mt-0.5", children: [_jsx("span", { className: "w-1.5 h-1.5 rounded-full bg-armoyu-primary animate-bounce [animation-delay:-0.3s]" }), _jsx("span", { className: "w-1.5 h-1.5 rounded-full bg-armoyu-primary animate-bounce [animation-delay:-0.15s]" }), _jsx("span", { className: "w-1.5 h-1.5 rounded-full bg-armoyu-primary animate-bounce" })] }) })] })), (() => {
275
558
  const msgs = (isLiveMode ? activeMessages : localMessages)
276
559
  .filter(m => m.content.toLowerCase().includes(msgSearchQuery.toLowerCase()));
277
- const renderedItems = [];
278
- for (let i = 0; i < msgs.length; i++) {
279
- const msg = msgs[i];
280
- const prevMsg = msgs[i - 1];
281
- // Mesajı ekle
282
- renderedItems.push(_jsx(ChatMessage, { id: msg.id, sender: {
283
- name: msg.sender?.displayName || 'Bilinmiyor',
284
- avatar: msg.sender?.avatar || '',
285
- isSelf: msg.isSelf
286
- }, content: msg.content, timestamp: msg.timestamp }, msg.id));
287
- // Eğer bir sonraki mesajın tarihi farklıysa veya ilk mesajsa (flex-col-reverse olduğu için mantık ters)
288
- // Ama biz burada düz listede gidip en son reverse yapacağız.
289
- const currentDate = new Date(msg.fullDate).toDateString();
290
- const nextMsg = msgs[i + 1];
291
- const nextDate = nextMsg ? new Date(nextMsg.fullDate).toDateString() : null;
292
- if (currentDate !== nextDate) {
293
- renderedItems.push(_jsx("div", { className: "flex justify-center my-6 sticky top-0 z-10", children: _jsx("span", { className: "px-4 py-1.5 rounded-full bg-black/5 dark:bg-white/5 text-[10px] font-black uppercase tracking-widest text-armoyu-text-muted border border-black/5 dark:border-white/5 backdrop-blur-md", children: getDateLabel(msg.fullDate) }) }, `date-${currentDate}`));
560
+ const groups = [];
561
+ let currentGroup = null;
562
+ msgs.forEach(msg => {
563
+ const dateKey = new Date(msg.fullDate).toDateString();
564
+ const label = getDateLabel(msg.fullDate) || '';
565
+ if (!currentGroup || currentGroup.dateKey !== dateKey) {
566
+ currentGroup = {
567
+ dateKey,
568
+ dateLabel: label,
569
+ messages: []
570
+ };
571
+ groups.push(currentGroup);
294
572
  }
295
- }
296
- return renderedItems.reverse();
297
- })()] }), _jsx(ChatInput, { onSend: handleSendMessage, chatId: activeContactId })] }))] }));
573
+ currentGroup.messages.push(msg);
574
+ });
575
+ // flex-col-reverse kullandığımız için:
576
+ // Yeni günün grubu DOM'da ilk sırada (dolayısıyla ekranda en altta) olmalı.
577
+ // Bu yüzden grupları ters çevirip basıyoruz.
578
+ return groups.reverse().map(group => (_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx("div", { className: "flex justify-center my-4 sticky top-0 z-10", children: _jsx("span", { className: "px-4 py-1.5 rounded-full bg-black/5 dark:bg-white/5 text-[10px] font-black uppercase tracking-widest text-armoyu-text-muted border border-black/5 dark:border-white/5 backdrop-blur-md", children: group.dateLabel }) }), group.messages.map(msg => (_jsx(ChatMessage, { id: msg.id, sender: {
579
+ name: msg.sender?.displayName || 'Bilinmiyor',
580
+ avatar: msg.sender?.avatar || '',
581
+ isSelf: msg.isSelf
582
+ }, content: msg.content, timestamp: msg.timestamp }, msg.id)))] }, `group-${group.dateKey}`)));
583
+ })()] }), _jsx(ChatInput, { onSend: handleSendMessage, chatId: (activeContact?.chatType === 'grup' || activeContact?.id === '0')
584
+ ? activeContact?.id?.toString()
585
+ : (activeContact?.username || activeContact?.id?.toString()) })] })), activeCall && (_jsx(VoiceCallWidget, { caller: activeCall.caller, recipient: activeCall.recipient, isIncoming: activeCall.isIncoming, status: activeCall.status, onAccept: handleAcceptCall, onDecline: handleDeclineCall, onHangUp: handleHangUpCall }))] }));
298
586
  }
299
587
  //# sourceMappingURL=ChatLayout.js.map