@armoyu/ui 1.0.1 → 1.0.2
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.
- package/dist/app/api/proxy/[...path]/route.d.ts +22 -0
- package/dist/app/api/proxy/[...path]/route.d.ts.map +1 -0
- package/dist/app/api/proxy/[...path]/route.js +89 -0
- package/dist/app/api/proxy/[...path]/route.js.map +1 -0
- package/dist/app/layout.d.ts.map +1 -1
- package/dist/app/layout.js +2 -5
- package/dist/app/layout.js.map +1 -1
- package/dist/components/RollingNumber.d.ts +6 -6
- package/dist/components/RollingNumber.js +23 -23
- package/dist/components/RollingNumber.js.map +1 -1
- package/dist/components/StatsGrid.d.ts +5 -5
- package/dist/components/modules/auth/Dashboard.d.ts +1 -1
- package/dist/components/modules/auth/PostCard.d.ts +24 -24
- package/dist/components/modules/auth/PostInteractionsModal.d.ts +11 -11
- package/dist/components/modules/auth/RepostModal.d.ts +21 -21
- package/dist/components/modules/auth/SidebarLeft.d.ts +1 -1
- package/dist/components/modules/auth/Stories.d.ts +1 -1
- package/dist/components/modules/auth/StoryViewer.d.ts +9 -9
- package/dist/components/modules/chat/ChatContainer.d.ts +1 -1
- package/dist/components/modules/chat/ChatInput.d.ts +4 -4
- package/dist/components/modules/chat/ChatList.d.ts +6 -6
- package/dist/components/modules/chat/ChatNotes.d.ts +1 -1
- package/dist/components/modules/community/GroupHeader.d.ts +10 -10
- package/dist/components/modules/community/GroupMenu.d.ts +9 -9
- package/dist/components/modules/community/SchoolCard.d.ts +6 -6
- package/dist/components/modules/community/SurveyCard.d.ts +6 -6
- package/dist/components/modules/guest/Introduction.d.ts +1 -1
- package/dist/components/modules/magaza/BackToStore.d.ts +1 -1
- package/dist/components/modules/magaza/StoreHeader.d.ts +5 -5
- package/dist/components/modules/news/NewsCard.d.ts +11 -11
- package/dist/components/modules/news/NewsComments.d.ts +1 -1
- package/dist/components/modules/profile/ProfileContent.d.ts +4 -4
- package/dist/components/modules/profile/ProfileHeader.d.ts +7 -7
- package/dist/components/modules/profile/ProfileStats.d.ts +1 -1
- package/dist/components/modules/profile/ProfileStats.js +14 -14
- package/dist/components/modules/profile/ProfileStats.js.map +1 -1
- package/dist/components/modules/profile/TeamSelectorModal.d.ts +10 -10
- package/dist/components/modules/stations/StationCard.d.ts +2 -2
- package/dist/components/modules/stations/StationQRModal.d.ts +9 -9
- package/dist/components/shared/FloatingChatButton.d.ts +4 -4
- package/dist/components/shared/Header.d.ts +1 -1
- package/dist/components/shared/LoginModal.d.ts +4 -4
- package/dist/components/shared/LoginModal.d.ts.map +1 -1
- package/dist/components/shared/LoginModal.js +65 -69
- package/dist/components/shared/LoginModal.js.map +1 -1
- package/dist/components/shared/MainLayoutWrapper.d.ts +3 -3
- package/dist/components/showcase/SocialTab.d.ts.map +1 -1
- package/dist/components/showcase/SocialTab.js +47 -2
- package/dist/components/showcase/SocialTab.js.map +1 -1
- package/dist/context/AuthContext.d.ts +18 -18
- package/dist/context/AuthContext.d.ts.map +1 -1
- package/dist/context/AuthContext.js +93 -74
- package/dist/context/AuthContext.js.map +1 -1
- package/dist/context/CartContext.d.ts +16 -16
- package/dist/context/SocketContext.d.ts +12 -12
- package/dist/index.d.ts +65 -65
- package/dist/lib/constants/educationData.d.ts +7 -7
- package/dist/lib/constants/seedData.d.ts +164 -164
- package/dist/lib/constants/stationData.d.ts +13 -13
- package/dist/lib/constants/surveyData.d.ts +2 -2
- package/dist/lib/constants/teamData.d.ts +12 -12
- package/package.json +13 -11
- package/next.config.ts +0 -13
- package/postcss.config.js +0 -6
- package/src/app/layout.tsx +0 -67
- package/src/app/page.tsx +0 -101
- package/src/components/Button.tsx +0 -41
- package/src/components/GenderStatsBar.tsx +0 -66
- package/src/components/RollingNumber.tsx +0 -50
- package/src/components/Slider.tsx +0 -114
- package/src/components/StatsGrid.tsx +0 -35
- package/src/components/ViewModeToggle.tsx +0 -39
- package/src/components/modules/auth/Dashboard.tsx +0 -649
- package/src/components/modules/auth/MediaLightbox.tsx +0 -112
- package/src/components/modules/auth/PostCard.tsx +0 -556
- package/src/components/modules/auth/PostInteractionsModal.tsx +0 -138
- package/src/components/modules/auth/RepostModal.tsx +0 -167
- package/src/components/modules/auth/SidebarLeft.tsx +0 -237
- package/src/components/modules/auth/Stories.tsx +0 -69
- package/src/components/modules/auth/StoryViewer.tsx +0 -146
- package/src/components/modules/chat/ChatContainer.tsx +0 -332
- package/src/components/modules/chat/ChatInput.tsx +0 -57
- package/src/components/modules/chat/ChatList.tsx +0 -179
- package/src/components/modules/chat/ChatMessage.tsx +0 -43
- package/src/components/modules/chat/ChatNotes.tsx +0 -58
- package/src/components/modules/community/GroupHeader.tsx +0 -111
- package/src/components/modules/community/GroupMenu.tsx +0 -82
- package/src/components/modules/community/SchoolCard.tsx +0 -104
- package/src/components/modules/community/SurveyCard.tsx +0 -149
- package/src/components/modules/forum/ForumBoard.tsx +0 -78
- package/src/components/modules/forum/ForumPost.tsx +0 -71
- package/src/components/modules/forum/NewTopicModal.tsx +0 -112
- package/src/components/modules/forum/TopicItem.tsx +0 -72
- package/src/components/modules/galleries/GalleryCard.tsx +0 -66
- package/src/components/modules/giveaways/GiveawayCard.tsx +0 -76
- package/src/components/modules/groups/ApplicationModal.tsx +0 -133
- package/src/components/modules/groups/GroupCard.tsx +0 -96
- package/src/components/modules/guest/Introduction.tsx +0 -60
- package/src/components/modules/magaza/BackToStore.tsx +0 -53
- package/src/components/modules/magaza/StoreHeader.tsx +0 -74
- package/src/components/modules/news/NewsCard.tsx +0 -66
- package/src/components/modules/news/NewsComments.tsx +0 -141
- package/src/components/modules/profile/CloudStorageModal.tsx +0 -200
- package/src/components/modules/profile/EditProfileModal.tsx +0 -191
- package/src/components/modules/profile/ProfileContent.tsx +0 -491
- package/src/components/modules/profile/ProfileHeader.tsx +0 -128
- package/src/components/modules/profile/ProfileStats.tsx +0 -72
- package/src/components/modules/profile/TeamSelectorModal.tsx +0 -129
- package/src/components/modules/stations/StationCard.tsx +0 -95
- package/src/components/modules/stations/StationQRModal.tsx +0 -102
- package/src/components/shared/FloatingChatButton.tsx +0 -57
- package/src/components/shared/Footer.tsx +0 -77
- package/src/components/shared/Header.tsx +0 -799
- package/src/components/shared/LoginModal.tsx +0 -208
- package/src/components/shared/MainLayoutWrapper.tsx +0 -15
- package/src/components/shared/PageWidth.tsx +0 -22
- package/src/components/showcase/CommunityTab.tsx +0 -22
- package/src/components/showcase/CorporateTab.tsx +0 -38
- package/src/components/showcase/GeneralTab.tsx +0 -41
- package/src/components/showcase/MessagesTab.tsx +0 -26
- package/src/components/showcase/ProfileTab.tsx +0 -20
- package/src/components/showcase/ShopTab.tsx +0 -24
- package/src/components/showcase/SocialTab.tsx +0 -28
- package/src/context/AuthContext.tsx +0 -104
- package/src/context/CartContext.tsx +0 -93
- package/src/context/ChatContext.tsx +0 -32
- package/src/context/LayoutContext.tsx +0 -29
- package/src/context/SocketContext.tsx +0 -50
- package/src/context/ThemeContext.tsx +0 -52
- package/src/index.ts +0 -96
- package/src/lib/constants/educationData.ts +0 -124
- package/src/lib/constants/punishmentData.ts +0 -201
- package/src/lib/constants/seedData.ts +0 -758
- package/src/lib/constants/stationData.ts +0 -170
- package/src/lib/constants/surveyData.ts +0 -53
- package/src/lib/constants/teamData.ts +0 -69
- package/src/lib/utils/numberFormat.ts +0 -16
- package/src/lib/utils/odpUtils.ts +0 -51
- package/src/types/index.ts +0 -1
- package/src/types/stats.ts +0 -17
|
@@ -1,146 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
import React, { useState, useEffect } from 'react';
|
|
4
|
-
import { createPortal } from 'react-dom';
|
|
5
|
-
import { Story, User } from '@armoyu/core';
|
|
6
|
-
|
|
7
|
-
interface StoryViewerProps {
|
|
8
|
-
stories: Story[];
|
|
9
|
-
initialStoryIndex: number;
|
|
10
|
-
onClose: () => void;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export function StoryViewer({ stories, initialStoryIndex, onClose }: StoryViewerProps) {
|
|
14
|
-
const [currentIndex, setCurrentIndex] = useState(initialStoryIndex);
|
|
15
|
-
const [progress, setProgress] = useState(0);
|
|
16
|
-
const story = stories[currentIndex];
|
|
17
|
-
|
|
18
|
-
useEffect(() => {
|
|
19
|
-
// Reset progress when story changes
|
|
20
|
-
setProgress(0);
|
|
21
|
-
|
|
22
|
-
const duration = 5000; // 5 seconds per story
|
|
23
|
-
const interval = 50;
|
|
24
|
-
const step = (interval / duration) * 100;
|
|
25
|
-
|
|
26
|
-
const timer = setInterval(() => {
|
|
27
|
-
setProgress((prev) => {
|
|
28
|
-
if (prev >= 100) {
|
|
29
|
-
handleNext();
|
|
30
|
-
return 0;
|
|
31
|
-
}
|
|
32
|
-
return prev + step;
|
|
33
|
-
});
|
|
34
|
-
}, interval);
|
|
35
|
-
|
|
36
|
-
return () => clearInterval(timer);
|
|
37
|
-
}, [currentIndex]);
|
|
38
|
-
|
|
39
|
-
const handleNext = () => {
|
|
40
|
-
if (currentIndex < stories.length - 1) {
|
|
41
|
-
setCurrentIndex(prev => prev + 1);
|
|
42
|
-
} else {
|
|
43
|
-
onClose();
|
|
44
|
-
}
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
const handlePrev = () => {
|
|
48
|
-
if (currentIndex > 0) {
|
|
49
|
-
setCurrentIndex(prev => prev - 1);
|
|
50
|
-
}
|
|
51
|
-
};
|
|
52
|
-
|
|
53
|
-
// Scroll kilitleme
|
|
54
|
-
useEffect(() => {
|
|
55
|
-
document.body.style.overflow = 'hidden';
|
|
56
|
-
return () => { document.body.style.overflow = 'unset'; };
|
|
57
|
-
}, []);
|
|
58
|
-
|
|
59
|
-
return createPortal(
|
|
60
|
-
<div className="fixed inset-0 z-[9999] bg-black flex items-center justify-center animate-in fade-in duration-300">
|
|
61
|
-
|
|
62
|
-
{/* Background Blur */}
|
|
63
|
-
<div className="absolute inset-0 overflow-hidden opacity-50">
|
|
64
|
-
<img src={story.media} className="w-full h-full object-cover blur-3xl scale-110" alt="" />
|
|
65
|
-
</div>
|
|
66
|
-
|
|
67
|
-
<div className="relative w-full h-full max-w-[450px] md:h-[90%] md:aspect-[9/16] bg-[#1a1a1a] md:rounded-3xl shadow-2xl overflow-hidden flex flex-col">
|
|
68
|
-
|
|
69
|
-
{/* Progress Bars */}
|
|
70
|
-
<div className="absolute top-4 left-4 right-4 z-20 flex gap-1">
|
|
71
|
-
{stories.map((_, idx) => (
|
|
72
|
-
<div key={idx} className="h-1 flex-1 bg-white/20 rounded-full overflow-hidden">
|
|
73
|
-
<div
|
|
74
|
-
className="h-full bg-white transition-all duration-50"
|
|
75
|
-
style={{
|
|
76
|
-
width: idx === currentIndex ? `${progress}%` : idx < currentIndex ? '100%' : '0%'
|
|
77
|
-
}}
|
|
78
|
-
/>
|
|
79
|
-
</div>
|
|
80
|
-
))}
|
|
81
|
-
</div>
|
|
82
|
-
|
|
83
|
-
{/* Header */}
|
|
84
|
-
<div className="absolute top-8 left-4 right-4 z-20 flex items-center justify-between">
|
|
85
|
-
<div className="flex items-center gap-3">
|
|
86
|
-
<img src={story.user?.avatar} className="w-9 h-9 rounded-full border border-white/20" alt="" />
|
|
87
|
-
<div className="flex flex-col">
|
|
88
|
-
<span className="text-white text-sm font-bold shadow-sm">{story.isMe ? 'Hikayen' : story.user?.displayName || story.user?.username}</span>
|
|
89
|
-
<span className="text-white/60 text-[10px]">2 saat önce</span>
|
|
90
|
-
</div>
|
|
91
|
-
</div>
|
|
92
|
-
<button onClick={onClose} className="p-2 text-white hover:bg-white/10 rounded-full transition-colors">
|
|
93
|
-
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>
|
|
94
|
-
</button>
|
|
95
|
-
</div>
|
|
96
|
-
|
|
97
|
-
{/* Content Area */}
|
|
98
|
-
<div className="flex-1 relative">
|
|
99
|
-
<img src={story.media} className="w-full h-full object-cover" alt="Story Content" />
|
|
100
|
-
|
|
101
|
-
{/* Navigation Overlays */}
|
|
102
|
-
<div className="absolute inset-0 flex">
|
|
103
|
-
<div className="w-1/3 h-full cursor-w-resize" onClick={handlePrev}></div>
|
|
104
|
-
<div className="w-2/3 h-full cursor-e-resize" onClick={handleNext}></div>
|
|
105
|
-
</div>
|
|
106
|
-
</div>
|
|
107
|
-
|
|
108
|
-
{/* Footer (Reply) */}
|
|
109
|
-
<div className="p-4 bg-black/40 backdrop-blur-md flex gap-3 items-center">
|
|
110
|
-
<input
|
|
111
|
-
type="text"
|
|
112
|
-
placeholder={`@${story.user?.username} kişisine yanıt ver...`}
|
|
113
|
-
className="flex-1 bg-white/10 border border-white/10 rounded-full px-5 py-2.5 text-sm text-white placeholder-white/50 focus:outline-none focus:bg-white/20 transition-all shadow-sm"
|
|
114
|
-
/>
|
|
115
|
-
<button className="text-white p-2">
|
|
116
|
-
<svg xmlns="http://www.w3.org/2000/svg" width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"></path></svg>
|
|
117
|
-
</button>
|
|
118
|
-
<button className="text-white p-2">
|
|
119
|
-
<svg xmlns="http://www.w3.org/2000/svg" width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><line x1="22" y1="2" x2="11" y2="13"></line><polygon points="22 2 15 22 11 13 2 9 22 2"></polygon></svg>
|
|
120
|
-
</button>
|
|
121
|
-
</div>
|
|
122
|
-
|
|
123
|
-
</div>
|
|
124
|
-
|
|
125
|
-
{/* Side Navigation for Desktop */}
|
|
126
|
-
<div className="hidden md:flex absolute inset-x-0 top-1/2 -translate-y-1/2 justify-between px-10 pointer-events-none">
|
|
127
|
-
<button
|
|
128
|
-
onClick={handlePrev}
|
|
129
|
-
disabled={currentIndex === 0}
|
|
130
|
-
className={`pointer-events-auto p-4 bg-white/10 hover:bg-white/20 text-white rounded-full backdrop-blur-md transition-all ${currentIndex === 0 ? 'opacity-0 cursor-default' : 'opacity-100'}`}
|
|
131
|
-
>
|
|
132
|
-
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5"><polyline points="15 18 9 12 15 6"></polyline></svg>
|
|
133
|
-
</button>
|
|
134
|
-
<button
|
|
135
|
-
onClick={handleNext}
|
|
136
|
-
className="pointer-events-auto p-4 bg-white/10 hover:bg-white/20 text-white rounded-full backdrop-blur-md transition-all"
|
|
137
|
-
>
|
|
138
|
-
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5"><polyline points="9 18 15 12 9 6"></polyline></svg>
|
|
139
|
-
</button>
|
|
140
|
-
</div>
|
|
141
|
-
|
|
142
|
-
</div>,
|
|
143
|
-
document.body
|
|
144
|
-
);
|
|
145
|
-
}
|
|
146
|
-
|
|
@@ -1,332 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
import React, { useState, useEffect } from 'react';
|
|
4
|
-
import { useAuth } from '../../../context/AuthContext';
|
|
5
|
-
import { ChatList } from './ChatList';
|
|
6
|
-
import { Chat, User, Session, ChatMessage as ChatMessageModel } from '@armoyu/core';
|
|
7
|
-
|
|
8
|
-
import { ChatMessage } from './ChatMessage';
|
|
9
|
-
import { ChatInput } from './ChatInput';
|
|
10
|
-
import { useChat } from '../../../context/ChatContext';
|
|
11
|
-
import { useSocket } from '../../../context/SocketContext';
|
|
12
|
-
|
|
13
|
-
// Mock Data
|
|
14
|
-
import { userList } from '../../../lib/constants/seedData';
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
export function ChatContainer() {
|
|
18
|
-
const { user, session, updateSession } = useAuth();
|
|
19
|
-
const { closeChat } = useChat();
|
|
20
|
-
const { emit, on, isConnected } = useSocket();
|
|
21
|
-
|
|
22
|
-
// Eğer null ise liste görünümü açık, ID var ise mesajlaşma açık.
|
|
23
|
-
const [activeContactId, setActiveContactId] = useState<string | null>(null);
|
|
24
|
-
const [localMessages, setLocalMessages] = useState<ChatMessageModel[]>([]);
|
|
25
|
-
const [localContacts, setLocalContacts] = useState<Chat[]>([]);
|
|
26
|
-
|
|
27
|
-
const [isTyping, setIsTyping] = useState(false);
|
|
28
|
-
const messagesEndRef = React.useRef<HTMLDivElement>(null);
|
|
29
|
-
|
|
30
|
-
const scrollToBottom = () => {
|
|
31
|
-
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
// Scroll to bottom when messages or typing status changes
|
|
35
|
-
useEffect(() => {
|
|
36
|
-
scrollToBottom();
|
|
37
|
-
}, [localMessages, isTyping]);
|
|
38
|
-
|
|
39
|
-
// Sync with session's chatList (chatList lives on Session, not User)
|
|
40
|
-
useEffect(() => {
|
|
41
|
-
if (session?.chatList) {
|
|
42
|
-
setLocalContacts(session.chatList);
|
|
43
|
-
}
|
|
44
|
-
}, [session?.chatList]);
|
|
45
|
-
|
|
46
|
-
// Socket Connection for Real-time Messages & Typing
|
|
47
|
-
useEffect(() => {
|
|
48
|
-
const offMsg = on('message', (incomingMsg: any) => {
|
|
49
|
-
console.log('[ChatContainer] Incoming socket message:', incomingMsg);
|
|
50
|
-
|
|
51
|
-
const msgModel = new ChatMessageModel({
|
|
52
|
-
id: incomingMsg.id,
|
|
53
|
-
sender: incomingMsg.sender ? new User(incomingMsg.sender) : undefined,
|
|
54
|
-
content: incomingMsg.content,
|
|
55
|
-
timestamp: incomingMsg.timestamp,
|
|
56
|
-
isSystem: incomingMsg.isSystem || false
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
// Update contacts list
|
|
60
|
-
setLocalContacts(prev => {
|
|
61
|
-
const contactId = incomingMsg.chatId || incomingMsg.sender?.username;
|
|
62
|
-
const contactExists = prev.some(c => c.id === contactId);
|
|
63
|
-
|
|
64
|
-
if (!contactExists && incomingMsg.sender?.username !== user?.username) {
|
|
65
|
-
// CREATE NEW CHAT: If sender isn't in our list, create the chat box for them!
|
|
66
|
-
const newChat = new Chat({
|
|
67
|
-
id: contactId,
|
|
68
|
-
name: incomingMsg.sender?.displayName || incomingMsg.sender?.username || 'Bilinmeyen',
|
|
69
|
-
avatar: incomingMsg.sender?.avatar || '',
|
|
70
|
-
lastMessage: msgModel,
|
|
71
|
-
time: msgModel.timestamp,
|
|
72
|
-
updatedAt: Date.now(),
|
|
73
|
-
messages: [msgModel],
|
|
74
|
-
unreadCount: (activeContactId !== contactId) ? 1 : 0,
|
|
75
|
-
isOnline: true // Assume online since they just sent a message
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
// PERSIST to Session!
|
|
79
|
-
if (session) {
|
|
80
|
-
const updatedChatList = [newChat, ...(session.chatList || [])];
|
|
81
|
-
updateSession(new Session({ ...session, chatList: updatedChatList }));
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
return [newChat, ...prev];
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
// UPDATE EXISTING CHAT
|
|
88
|
-
return prev.map(c => {
|
|
89
|
-
if (c.id === contactId) {
|
|
90
|
-
// Only add if not already in messages to avoid duplicates from echo
|
|
91
|
-
const messageExists = c.messages.some(m => m.id === msgModel.id);
|
|
92
|
-
|
|
93
|
-
const updatedChat = new Chat({
|
|
94
|
-
...c,
|
|
95
|
-
lastMessage: msgModel,
|
|
96
|
-
time: msgModel.timestamp,
|
|
97
|
-
updatedAt: Date.now(),
|
|
98
|
-
messages: messageExists ? c.messages : [...(c.messages || []), msgModel],
|
|
99
|
-
unreadCount: (activeContactId !== c.id && incomingMsg.sender?.username !== user?.username) ? c.unreadCount + 1 : c.unreadCount
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
// Note: We don't necessarily need to update the whole session on every message to avoid excessive renders,
|
|
103
|
-
// as local state handles the UI. But for NEW chats, we must.
|
|
104
|
-
|
|
105
|
-
return updatedChat;
|
|
106
|
-
}
|
|
107
|
-
return c;
|
|
108
|
-
});
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
// If this is the active chat, update visible messages
|
|
112
|
-
if (activeContactId === incomingMsg.chatId || activeContactId === incomingMsg.sender?.username) {
|
|
113
|
-
setLocalMessages(prev => {
|
|
114
|
-
if (prev.some(m => m.id === incomingMsg.id)) return prev;
|
|
115
|
-
return [...prev, msgModel];
|
|
116
|
-
});
|
|
117
|
-
setIsTyping(false); // Stop typing on message receive
|
|
118
|
-
}
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
const offTyping = on('typing', (data: any) => {
|
|
122
|
-
// console.log('[ChatContainer] Incoming typing event:', data);
|
|
123
|
-
|
|
124
|
-
// If we are recipient (data.chatId is US) and the sender IS the one we are currently viewing
|
|
125
|
-
const isChattingWithSender = data.username === activeContactId && data.chatId === user?.username;
|
|
126
|
-
|
|
127
|
-
// Special case for system bot
|
|
128
|
-
const isBotTyping = data.username === 'system' && data.chatId === user?.username;
|
|
129
|
-
|
|
130
|
-
if (isChattingWithSender || isBotTyping) {
|
|
131
|
-
setIsTyping(data.isTyping);
|
|
132
|
-
}
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
return () => {
|
|
136
|
-
offMsg();
|
|
137
|
-
offTyping();
|
|
138
|
-
};
|
|
139
|
-
}, [on, activeContactId, user?.username]);
|
|
140
|
-
|
|
141
|
-
const handleSelectContact = (id: string) => {
|
|
142
|
-
// Check if the contact is already in our list
|
|
143
|
-
let contact = localContacts.find((c: Chat) => c.id === id);
|
|
144
|
-
|
|
145
|
-
// If NOT in our list (clicked from "New Contacts" search), we must create it!
|
|
146
|
-
if (!contact) {
|
|
147
|
-
const newUser = userList.find((u: any) => u.username === id);
|
|
148
|
-
if (newUser) {
|
|
149
|
-
contact = new Chat({
|
|
150
|
-
id: newUser.username,
|
|
151
|
-
name: newUser.displayName,
|
|
152
|
-
avatar: newUser.avatar,
|
|
153
|
-
updatedAt: Date.now(),
|
|
154
|
-
messages: [],
|
|
155
|
-
isOnline: true, // Mock online status for now
|
|
156
|
-
unreadCount: 0
|
|
157
|
-
});
|
|
158
|
-
|
|
159
|
-
const updatedContacts = [contact, ...localContacts];
|
|
160
|
-
setLocalContacts(updatedContacts);
|
|
161
|
-
|
|
162
|
-
// Persist to session so it stays in the list!
|
|
163
|
-
if (session) {
|
|
164
|
-
const updatedChatList = [contact, ...(session.chatList || [])];
|
|
165
|
-
updateSession(new Session({ ...session, chatList: updatedChatList }));
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
setActiveContactId(id);
|
|
171
|
-
setLocalMessages(contact?.messages || []);
|
|
172
|
-
setIsTyping(false);
|
|
173
|
-
|
|
174
|
-
// Clear unread on select (if it was an existing contact)
|
|
175
|
-
if (contact && contact.unreadCount > 0) {
|
|
176
|
-
setLocalContacts(prev => prev.map(c => c.id === id ? { ...c, unreadCount: 0 } as Chat : c));
|
|
177
|
-
}
|
|
178
|
-
};
|
|
179
|
-
|
|
180
|
-
const handleSendMessage = (text: string) => {
|
|
181
|
-
if (!text.trim() || !activeContactId) return;
|
|
182
|
-
|
|
183
|
-
const messageData = {
|
|
184
|
-
id: Date.now().toString(),
|
|
185
|
-
chatId: activeContactId,
|
|
186
|
-
sender: {
|
|
187
|
-
username: user?.username,
|
|
188
|
-
displayName: user?.displayName,
|
|
189
|
-
avatar: user?.avatar
|
|
190
|
-
},
|
|
191
|
-
content: text,
|
|
192
|
-
timestamp: new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }),
|
|
193
|
-
};
|
|
194
|
-
|
|
195
|
-
// Update local state immediately for instant feedback
|
|
196
|
-
const newMessage = new ChatMessageModel({
|
|
197
|
-
id: messageData.id,
|
|
198
|
-
sender: user || undefined,
|
|
199
|
-
content: text,
|
|
200
|
-
timestamp: messageData.timestamp,
|
|
201
|
-
isSystem: false
|
|
202
|
-
});
|
|
203
|
-
|
|
204
|
-
setLocalMessages(prev => [...prev, newMessage]);
|
|
205
|
-
setLocalContacts(prev => prev.map(c => {
|
|
206
|
-
if (c.id === activeContactId) {
|
|
207
|
-
return new Chat({
|
|
208
|
-
...c,
|
|
209
|
-
lastMessage: newMessage,
|
|
210
|
-
time: newMessage.timestamp,
|
|
211
|
-
updatedAt: Date.now(),
|
|
212
|
-
messages: [...(c.messages || []), newMessage]
|
|
213
|
-
});
|
|
214
|
-
}
|
|
215
|
-
return c;
|
|
216
|
-
}));
|
|
217
|
-
|
|
218
|
-
// Emit via socket for others
|
|
219
|
-
emit('message', messageData);
|
|
220
|
-
};
|
|
221
|
-
|
|
222
|
-
if (!user) {
|
|
223
|
-
return (
|
|
224
|
-
<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">
|
|
225
|
-
Sohbetleri görmek için giriş yapmalısınız.
|
|
226
|
-
</div>
|
|
227
|
-
);
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
const activeContact = activeContactId ? localContacts.find((c: Chat) => c.id === activeContactId) : null;
|
|
231
|
-
|
|
232
|
-
return (
|
|
233
|
-
<div className="flex h-full w-full bg-armoyu-header-bg overflow-hidden relative z-10">
|
|
234
|
-
|
|
235
|
-
{/* Görünüm 1: Sohbet Listesi (Biri seçili değilse tam ekran gösterilir) */}
|
|
236
|
-
{!activeContactId && (
|
|
237
|
-
<div className="w-full h-full flex flex-col animate-in fade-in slide-in-from-left-4 duration-300">
|
|
238
|
-
<ChatList contacts={localContacts} activeId={''} onSelect={handleSelectContact} />
|
|
239
|
-
</div>
|
|
240
|
-
)}
|
|
241
|
-
|
|
242
|
-
{/* Görünüm 2: Seçilen Sohbet Paneli (İçerik) */}
|
|
243
|
-
{activeContactId && activeContact && (
|
|
244
|
-
<div className="w-full h-full flex flex-col bg-armoyu-bg relative animate-in fade-in slide-in-from-right-4 duration-300">
|
|
245
|
-
|
|
246
|
-
{/* İçerik Header */}
|
|
247
|
-
<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">
|
|
248
|
-
|
|
249
|
-
{/* Listeye Geri Dönüş butonu! */}
|
|
250
|
-
<button
|
|
251
|
-
onClick={() => setActiveContactId(null)}
|
|
252
|
-
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"
|
|
253
|
-
>
|
|
254
|
-
<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"><polyline points="15 18 9 12 15 6"></polyline></svg>
|
|
255
|
-
</button>
|
|
256
|
-
|
|
257
|
-
<img src={activeContact.avatar} className="w-10 h-10 rounded-full border border-black/5 dark:border-white/10 shadow-sm" alt="" />
|
|
258
|
-
<div className="flex-1 min-w-0">
|
|
259
|
-
<h3 className="font-bold text-armoyu-text truncate text-base">{activeContact.name}</h3>
|
|
260
|
-
<div className="flex items-center gap-1.5 mt-0.5">
|
|
261
|
-
{activeContact.isOnline ? (
|
|
262
|
-
<>
|
|
263
|
-
<span className="w-2 h-2 rounded-full bg-emerald-500 shadow-[0_0_5px_rgba(16,185,129,0.6)] animate-pulse"></span>
|
|
264
|
-
<span className="text-xs font-bold text-emerald-500 shadow-sm">Çevrimiçi</span>
|
|
265
|
-
</>
|
|
266
|
-
) : (
|
|
267
|
-
<>
|
|
268
|
-
<span className="w-2 h-2 rounded-full bg-gray-400 dark:bg-gray-600"></span>
|
|
269
|
-
<span className="text-xs font-bold text-armoyu-text-muted">Son görülme {activeContact.lastSeen}</span>
|
|
270
|
-
</>
|
|
271
|
-
)}
|
|
272
|
-
</div>
|
|
273
|
-
</div>
|
|
274
|
-
|
|
275
|
-
<div className="flex gap-1">
|
|
276
|
-
<button className="p-2 text-armoyu-text-muted hover:text-blue-500 rounded-full hover:bg-blue-500/10 transition-colors" title="Ara">
|
|
277
|
-
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72 12.84 12.84 0 0 0 .7 2.81 2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7A2 2 0 0 1 22 16.92z"></path></svg>
|
|
278
|
-
</button>
|
|
279
|
-
<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">
|
|
280
|
-
<svg xmlns="http://www.w3.org/2000/svg" width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>
|
|
281
|
-
</button>
|
|
282
|
-
</div>
|
|
283
|
-
</div>
|
|
284
|
-
|
|
285
|
-
{/* Messages Area */}
|
|
286
|
-
<div className="flex-1 overflow-y-auto p-4 md:p-6 space-y-2 relative hide-scrollbar">
|
|
287
|
-
<div className="text-center mb-8">
|
|
288
|
-
<span className="inline-block bg-black/5 dark:bg-white/5 text-armoyu-text-muted text-xs font-bold px-3 py-1 rounded-full border border-black/5 dark:border-white/5">
|
|
289
|
-
Bugün
|
|
290
|
-
</span>
|
|
291
|
-
</div>
|
|
292
|
-
|
|
293
|
-
{localMessages.map(msg => (
|
|
294
|
-
<ChatMessage
|
|
295
|
-
key={msg.id}
|
|
296
|
-
id={msg.id}
|
|
297
|
-
sender={{
|
|
298
|
-
name: msg.sender?.displayName || 'Bilinmiyor',
|
|
299
|
-
avatar: msg.sender?.avatar || '',
|
|
300
|
-
isSelf: msg.sender?.username === user?.username
|
|
301
|
-
}}
|
|
302
|
-
content={msg.content}
|
|
303
|
-
timestamp={msg.timestamp}
|
|
304
|
-
/>
|
|
305
|
-
))}
|
|
306
|
-
|
|
307
|
-
{isTyping && (
|
|
308
|
-
<div className="flex gap-2 items-center px-4 animate-in fade-in slide-in-from-bottom-2 duration-300">
|
|
309
|
-
<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">
|
|
310
|
-
<div className="flex gap-0.5 mt-0.5">
|
|
311
|
-
<span className="w-1 h-1 rounded-full bg-blue-500 animate-bounce [animation-delay:-0.3s]"></span>
|
|
312
|
-
<span className="w-1 h-1 rounded-full bg-blue-500 animate-bounce [animation-delay:-0.15s]"></span>
|
|
313
|
-
<span className="w-1 h-1 rounded-full bg-blue-500 animate-bounce"></span>
|
|
314
|
-
</div>
|
|
315
|
-
<span className="text-[10px] font-bold text-armoyu-text-muted italic ml-1">Yazıyor...</span>
|
|
316
|
-
</div>
|
|
317
|
-
</div>
|
|
318
|
-
)}
|
|
319
|
-
|
|
320
|
-
<div ref={messagesEndRef} className="h-2" />
|
|
321
|
-
</div>
|
|
322
|
-
|
|
323
|
-
{/* Input Area */}
|
|
324
|
-
<ChatInput onSend={handleSendMessage} chatId={activeContactId} />
|
|
325
|
-
|
|
326
|
-
</div>
|
|
327
|
-
)}
|
|
328
|
-
|
|
329
|
-
</div>
|
|
330
|
-
);
|
|
331
|
-
}
|
|
332
|
-
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
import React, { useState } from 'react';
|
|
2
|
-
import { Button } from '../../Button';
|
|
3
|
-
import { useSocket } from '../../../context/SocketContext';
|
|
4
|
-
|
|
5
|
-
export function ChatInput({ onSend, chatId }: { onSend: (text: string) => void, chatId?: string }) {
|
|
6
|
-
const [text, setText] = useState('');
|
|
7
|
-
const { emit } = useSocket();
|
|
8
|
-
|
|
9
|
-
const handleSend = (e: React.FormEvent) => {
|
|
10
|
-
e.preventDefault();
|
|
11
|
-
if (text.trim()) {
|
|
12
|
-
onSend(text);
|
|
13
|
-
setText('');
|
|
14
|
-
if (chatId) emit('typing', { chatId, isTyping: false });
|
|
15
|
-
}
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
const handleChange = (val: string) => {
|
|
19
|
-
setText(val);
|
|
20
|
-
if (chatId) {
|
|
21
|
-
const isTyping = val.length > 0;
|
|
22
|
-
// Include username to help receiver filter out self
|
|
23
|
-
emit('typing', {
|
|
24
|
-
chatId,
|
|
25
|
-
isTyping,
|
|
26
|
-
username: typeof window !== 'undefined' ? localStorage.getItem('armoyu_username') : undefined
|
|
27
|
-
});
|
|
28
|
-
}
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
return (
|
|
32
|
-
<form onSubmit={handleSend} className="p-3 border-t border-gray-200 dark:border-white/5 flex items-center gap-3 bg-armoyu-card-bg">
|
|
33
|
-
|
|
34
|
-
{/* Ekstra Butonlar (Emoji, Dosya Ekle vb.) */}
|
|
35
|
-
<button type="button" className="text-gray-400 hover:text-blue-500 p-2 transition-colors shrink-0">
|
|
36
|
-
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><circle cx="12" cy="12" r="10"></circle><path d="M8 14s1.5 2 4 2 4-2 4-2"></path><line x1="9" y1="9" x2="9.01" y2="9"></line><line x1="15" y1="9" x2="15.01" y2="9"></line></svg>
|
|
37
|
-
</button>
|
|
38
|
-
<button type="button" className="text-gray-400 hover:text-blue-500 p-2 transition-colors hidden sm:block shrink-0">
|
|
39
|
-
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="M21.44 11.05l-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48"></path></svg>
|
|
40
|
-
</button>
|
|
41
|
-
|
|
42
|
-
{/* Mesaj Girdisi */}
|
|
43
|
-
<input
|
|
44
|
-
type="text"
|
|
45
|
-
value={text}
|
|
46
|
-
onChange={(e) => handleChange(e.target.value)}
|
|
47
|
-
placeholder="Bir mesaj yazın..."
|
|
48
|
-
className="flex-1 min-w-0 bg-black/5 dark:bg-black/40 border border-black/10 dark:border-white/10 rounded-full px-4 py-2.5 text-sm text-armoyu-text placeholder-armoyu-text-muted focus:outline-none focus:border-blue-500 focus:ring-1 focus:ring-blue-500 transition-all shadow-inner"
|
|
49
|
-
/>
|
|
50
|
-
|
|
51
|
-
{/* Gönder Butonu */}
|
|
52
|
-
<Button variant="primary" className="rounded-full w-10 h-10 p-0 flex items-center justify-center shadow-[0_0_15px_rgba(37,99,235,0.3)] shrink-0 group">
|
|
53
|
-
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round" className="translate-x-0.5 group-hover:translate-x-1 group-hover:-translate-y-0.5 transition-transform"><line x1="22" y1="2" x2="11" y2="13"></line><polygon points="22 2 15 22 11 13 2 9 22 2"></polygon></svg>
|
|
54
|
-
</Button>
|
|
55
|
-
</form>
|
|
56
|
-
);
|
|
57
|
-
}
|