ChandimaPrabath commited on
Commit
6fb9a09
·
1 Parent(s): 55109cc
frontend/app/Sidebar.js CHANGED
@@ -1,5 +1,5 @@
1
- import { useState } from 'react';
2
- import { useRouter } from 'next/navigation';
3
 
4
  export default function Sidebar({
5
  searchQuery,
@@ -9,6 +9,15 @@ export default function Sidebar({
9
  setFilteredUsers,
10
  }) {
11
  const [isSidebarOpen, setIsSidebarOpen] = useState(false);
 
 
 
 
 
 
 
 
 
12
 
13
  const toggleSidebar = () => {
14
  setIsSidebarOpen((prev) => !prev);
@@ -47,14 +56,15 @@ export default function Sidebar({
47
  position: 'fixed',
48
  top: '10px',
49
  left: isSidebarOpen ? '210px' : '10px',
 
50
  zIndex: 1000,
51
- backgroundColor: '#4b2e83',
52
  color: '#fff',
53
  border: 'none',
54
- borderRadius: '5px',
55
  padding: '10px',
56
  cursor: 'pointer',
57
- transition: 'left 0.3s ease',
58
  }}
59
  >
60
 
@@ -65,7 +75,7 @@ export default function Sidebar({
65
  style={{
66
  position: 'fixed',
67
  top: 0,
68
- left: isSidebarOpen ? 0 : '-260px', // Sidebar slides out of view when collapsed
69
  width: '250px',
70
  height: '100%',
71
  backgroundColor: '#333',
@@ -75,7 +85,7 @@ export default function Sidebar({
75
  zIndex: 999,
76
  }}
77
  >
78
- <h3 style={{ color: '#fff', marginBottom: '20px' }}>Select Recipient</h3>
79
 
80
  <input
81
  type="text"
@@ -87,12 +97,39 @@ export default function Sidebar({
87
 
88
  <ul style={{ listStyleType: 'none', padding: 0 }}>
89
  {filteredUsers.map((recipient, index) => (
90
- <li key={index} style={{ color: '#fff', cursor: 'pointer', marginBottom: '10px' }}>
91
- <span
92
- style={{ textDecoration: 'none', color: '#fff' }}
93
- onClick={() => handleUserSelect(recipient)}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
94
  >
95
- {recipient}
 
 
 
 
 
 
 
 
 
 
 
 
96
  </span>
97
  </li>
98
  ))}
 
1
+ import { useState, useEffect } from 'react';
2
+ import Image from 'next/image';
3
 
4
  export default function Sidebar({
5
  searchQuery,
 
9
  setFilteredUsers,
10
  }) {
11
  const [isSidebarOpen, setIsSidebarOpen] = useState(false);
12
+ const [me, setMe] = useState('');
13
+
14
+ useEffect(() => {
15
+ // Retrieve 'me' from localStorage
16
+ const storedMe = localStorage.getItem('me');
17
+ if (storedMe) {
18
+ setMe(storedMe);
19
+ }
20
+ }, []);
21
 
22
  const toggleSidebar = () => {
23
  setIsSidebarOpen((prev) => !prev);
 
56
  position: 'fixed',
57
  top: '10px',
58
  left: isSidebarOpen ? '210px' : '10px',
59
+ transform: isSidebarOpen ? 'rotate(90deg)' : 'none',
60
  zIndex: 1000,
61
+ backgroundColor: '#352e83',
62
  color: '#fff',
63
  border: 'none',
64
+ borderRadius: '10px',
65
  padding: '10px',
66
  cursor: 'pointer',
67
+ transition: 'left .4s ease',
68
  }}
69
  >
70
 
 
75
  style={{
76
  position: 'fixed',
77
  top: 0,
78
+ left: isSidebarOpen ? 0 : '-260px',
79
  width: '250px',
80
  height: '100%',
81
  backgroundColor: '#333',
 
85
  zIndex: 999,
86
  }}
87
  >
88
+ <h3 style={{ color: '#fff', marginBottom: '20px' }}>Find Chats</h3>
89
 
90
  <input
91
  type="text"
 
97
 
98
  <ul style={{ listStyleType: 'none', padding: 0 }}>
99
  {filteredUsers.map((recipient, index) => (
100
+ <li
101
+ key={index}
102
+ style={{
103
+ color: '#fff',
104
+ cursor: 'pointer',
105
+ marginBottom: '15px',
106
+ display: 'flex',
107
+ alignItems: 'center',
108
+ }}
109
+ onClick={() => handleUserSelect(recipient)}
110
+ >
111
+ {/* Profile Picture */}
112
+ <div
113
+ style={{
114
+ width: '40px',
115
+ height: '40px',
116
+ position: 'relative',
117
+ marginRight: '10px',
118
+ }}
119
  >
120
+ <Image
121
+ src={`https://ui-avatars.com/api/?background=random&name=${encodeURIComponent(
122
+ recipient
123
+ )}`}
124
+ alt={`${recipient}'s avatar`}
125
+ layout="fill"
126
+ objectFit="cover"
127
+ style={{ borderRadius: '50%' }}
128
+ />
129
+ </div>
130
+ {/* Username */}
131
+ <span style={{ textDecoration: 'none', color: '#fff', fontSize: '16px' }}>
132
+ {recipient} {recipient === me ? '(Me)' : ''}
133
  </span>
134
  </li>
135
  ))}
frontend/app/layout.js CHANGED
@@ -1,18 +1,21 @@
1
  'use client';
2
  import { useState, useEffect } from 'react';
3
- import { useRouter } from 'next/navigation';
4
  import { WebSocketProvider, useWebSocket } from './WebSocketContext';
5
  import './globals.css';
6
- import Sidebar from './Sidebar'; // Import Sidebar Component
7
 
8
  export default function RootLayout({ children }) {
9
  const [username, setUsername] = useState('');
10
  const [password, setPassword] = useState('');
 
11
  const [isLoading, setIsLoading] = useState(false);
 
12
  const [recipientList, setRecipientList] = useState([]);
13
  const [searchQuery, setSearchQuery] = useState('');
14
  const [filteredUsers, setFilteredUsers] = useState([]);
15
  const [isLoggedIn, setIsLoggedIn] = useState(false);
 
16
 
17
  return (
18
  <html lang="en">
@@ -23,8 +26,12 @@ export default function RootLayout({ children }) {
23
  setUsername={setUsername}
24
  password={password}
25
  setPassword={setPassword}
 
 
26
  isLoading={isLoading}
27
  setIsLoading={setIsLoading}
 
 
28
  recipientList={recipientList}
29
  setRecipientList={setRecipientList}
30
  searchQuery={searchQuery}
@@ -33,6 +40,8 @@ export default function RootLayout({ children }) {
33
  setFilteredUsers={setFilteredUsers}
34
  isLoggedIn={isLoggedIn}
35
  setIsLoggedIn={setIsLoggedIn}
 
 
36
  >
37
  {children}
38
  </WebSocketLayout>
@@ -47,8 +56,12 @@ function WebSocketLayout({
47
  setUsername,
48
  password,
49
  setPassword,
 
 
50
  isLoading,
51
  setIsLoading,
 
 
52
  recipientList,
53
  setRecipientList,
54
  searchQuery,
@@ -57,22 +70,48 @@ function WebSocketLayout({
57
  setFilteredUsers,
58
  isLoggedIn,
59
  setIsLoggedIn,
 
 
60
  children,
61
  }) {
62
- const { ws, status } = useWebSocket();
63
  const router = useRouter();
64
 
 
 
 
 
 
 
 
 
 
 
 
 
65
  const connectAndAuthenticate = () => {
 
66
  if (!username || !password) {
67
- alert('Please enter both username and password');
68
  return;
69
  }
70
 
71
  setIsLoading(true);
72
 
73
- const loginData = JSON.stringify({ username, password });
 
 
 
 
 
 
 
 
 
 
 
74
  if (ws && ws.readyState === WebSocket.OPEN) {
75
- ws.send(loginData);
76
  }
77
  };
78
 
@@ -80,38 +119,23 @@ function WebSocketLayout({
80
  if (ws) {
81
  ws.onmessage = (event) => {
82
  const data = JSON.parse(event.data);
83
- console.log('Received message:', data);
84
 
85
  if (data.status === 'success') {
86
  setIsLoading(false);
87
  setIsLoggedIn(true);
88
  setRecipientList(data.recipients || []);
 
89
  } else if (data.status === 'error') {
90
- console.error(data.message);
91
- alert(data.message);
92
  setIsLoading(false);
93
- } else {
94
- const { from: recipient, message } = data;
95
-
96
- if (recipient && message) {
97
- try {
98
- const storedMessages = JSON.parse(localStorage.getItem(recipient)) || [];
99
- storedMessages.push({ message, timestamp: data.timestamp });
100
- localStorage.setItem(recipient, JSON.stringify(storedMessages));
101
- console.log('LocalStorage after saving:', localStorage.getItem(recipient));
102
- } catch (error) {
103
- console.error('Error accessing localStorage:', error);
104
- }
105
- } else {
106
- console.error('Received message is missing recipient or message.');
107
- }
108
  }
109
  };
110
  }
111
- }, [ws]);
112
 
113
  const handleUserSelect = (recipient) => {
114
- router.push(`/${recipient}`);
115
  };
116
 
117
  return (
@@ -125,14 +149,12 @@ function WebSocketLayout({
125
  filteredUsers={filteredUsers}
126
  handleUserSelect={handleUserSelect}
127
  />
128
- <div style={{ flex: 1, padding: '20px' }}>
129
- {children}
130
- </div>
131
  </div>
132
  ) : (
133
  <div style={loginContainerStyle}>
134
- <h2 style={headingStyle}>Login</h2>
135
  <div style={inputContainerStyle}>
 
136
  <input
137
  type="text"
138
  placeholder="Username"
@@ -147,11 +169,31 @@ function WebSocketLayout({
147
  onChange={(e) => setPassword(e.target.value)}
148
  style={inputStyle}
149
  />
150
- <button onClick={connectAndAuthenticate} style={loginButtonStyle}>
151
- {isLoading ? 'Connecting...' : 'Login'}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
152
  </button>
 
 
153
  </div>
154
- {status === 'Error' && <p style={{ color: 'red' }}>Connection failed. Please try again.</p>}
155
  </div>
156
  )}
157
  </div>
@@ -163,7 +205,7 @@ const loginContainerStyle = {
163
  justifyContent: 'center',
164
  alignItems: 'center',
165
  height: '100vh',
166
- backgroundColor: '#2e2e4f',
167
  };
168
 
169
  const headingStyle = {
@@ -199,3 +241,12 @@ const loginButtonStyle = {
199
  cursor: 'pointer',
200
  transition: 'background-color 0.3s',
201
  };
 
 
 
 
 
 
 
 
 
 
1
  'use client';
2
  import { useState, useEffect } from 'react';
3
+ import { useRouter } from 'next/navigation';
4
  import { WebSocketProvider, useWebSocket } from './WebSocketContext';
5
  import './globals.css';
6
+ import Sidebar from './Sidebar';
7
 
8
  export default function RootLayout({ children }) {
9
  const [username, setUsername] = useState('');
10
  const [password, setPassword] = useState('');
11
+ const [confirmPassword, setConfirmPassword] = useState('');
12
  const [isLoading, setIsLoading] = useState(false);
13
+ const [error, setError] = useState('');
14
  const [recipientList, setRecipientList] = useState([]);
15
  const [searchQuery, setSearchQuery] = useState('');
16
  const [filteredUsers, setFilteredUsers] = useState([]);
17
  const [isLoggedIn, setIsLoggedIn] = useState(false);
18
+ const [isSignup, setIsSignup] = useState(false);
19
 
20
  return (
21
  <html lang="en">
 
26
  setUsername={setUsername}
27
  password={password}
28
  setPassword={setPassword}
29
+ confirmPassword={confirmPassword}
30
+ setConfirmPassword={setConfirmPassword}
31
  isLoading={isLoading}
32
  setIsLoading={setIsLoading}
33
+ error={error}
34
+ setError={setError}
35
  recipientList={recipientList}
36
  setRecipientList={setRecipientList}
37
  searchQuery={searchQuery}
 
40
  setFilteredUsers={setFilteredUsers}
41
  isLoggedIn={isLoggedIn}
42
  setIsLoggedIn={setIsLoggedIn}
43
+ isSignup={isSignup}
44
+ setIsSignup={setIsSignup}
45
  >
46
  {children}
47
  </WebSocketLayout>
 
56
  setUsername,
57
  password,
58
  setPassword,
59
+ confirmPassword,
60
+ setConfirmPassword,
61
  isLoading,
62
  setIsLoading,
63
+ error,
64
+ setError,
65
  recipientList,
66
  setRecipientList,
67
  searchQuery,
 
70
  setFilteredUsers,
71
  isLoggedIn,
72
  setIsLoggedIn,
73
+ isSignup,
74
+ setIsSignup,
75
  children,
76
  }) {
77
+ const { ws, status } = useWebSocket();
78
  const router = useRouter();
79
 
80
+ const validateSignup = () => {
81
+ if (!username || !password || !confirmPassword) {
82
+ setError('Please fill in all fields');
83
+ return false;
84
+ }
85
+ if (password !== confirmPassword) {
86
+ setError('Passwords do not match');
87
+ return false;
88
+ }
89
+ return true;
90
+ };
91
+
92
  const connectAndAuthenticate = () => {
93
+ setError('');
94
  if (!username || !password) {
95
+ setError('Please enter both username and password');
96
  return;
97
  }
98
 
99
  setIsLoading(true);
100
 
101
+ const requestData = JSON.stringify({ username, password });
102
+ if (ws && ws.readyState === WebSocket.OPEN) {
103
+ ws.send(requestData);
104
+ }
105
+ };
106
+
107
+ const handleSignup = () => {
108
+ if (!validateSignup()) return;
109
+
110
+ setIsLoading(true);
111
+
112
+ const signupData = JSON.stringify({ username, password, action: 'signup' });
113
  if (ws && ws.readyState === WebSocket.OPEN) {
114
+ ws.send(signupData);
115
  }
116
  };
117
 
 
119
  if (ws) {
120
  ws.onmessage = (event) => {
121
  const data = JSON.parse(event.data);
122
+ console.log('Received message:', data);
123
 
124
  if (data.status === 'success') {
125
  setIsLoading(false);
126
  setIsLoggedIn(true);
127
  setRecipientList(data.recipients || []);
128
+ localStorage.setItem('me', username);
129
  } else if (data.status === 'error') {
130
+ setError(data.message);
 
131
  setIsLoading(false);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
132
  }
133
  };
134
  }
135
+ }, [ws, username]);
136
 
137
  const handleUserSelect = (recipient) => {
138
+ router.push(`/u/${recipient}`);
139
  };
140
 
141
  return (
 
149
  filteredUsers={filteredUsers}
150
  handleUserSelect={handleUserSelect}
151
  />
152
+ <div style={{ flex: 1 }}>{children}</div>
 
 
153
  </div>
154
  ) : (
155
  <div style={loginContainerStyle}>
 
156
  <div style={inputContainerStyle}>
157
+ <h2 style={headingStyle}>{isSignup ? 'Signup' : 'Login'}</h2>
158
  <input
159
  type="text"
160
  placeholder="Username"
 
169
  onChange={(e) => setPassword(e.target.value)}
170
  style={inputStyle}
171
  />
172
+ {isSignup && (
173
+ <input
174
+ type="password"
175
+ placeholder="Confirm Password"
176
+ value={confirmPassword}
177
+ onChange={(e) => setConfirmPassword(e.target.value)}
178
+ style={inputStyle}
179
+ />
180
+ )}
181
+ <button
182
+ onClick={isSignup ? handleSignup : connectAndAuthenticate}
183
+ style={loginButtonStyle}
184
+ >
185
+ {isLoading ? 'Processing...' : isSignup ? 'Signup' : 'Login'}
186
+ </button>
187
+ <button
188
+ onClick={() => setIsSignup(!isSignup)}
189
+ style={switchButtonStyle}
190
+ >
191
+ {isSignup ? 'Switch to Login' : 'Switch to Signup'}
192
  </button>
193
+ {error && <div style={{ color: 'red', display: 'flex', alignItems: 'center'}}><div style={{ width: '8px', height: '8px', backgroundColor: 'red', borderRadius: '50px', marginRight: '5px' }}/> {error}</div>}
194
+ {status === 'Error' && <p style={{ color: 'red' }}>Connection failed. Please try again.</p>}
195
  </div>
196
+
197
  </div>
198
  )}
199
  </div>
 
205
  justifyContent: 'center',
206
  alignItems: 'center',
207
  height: '100vh',
208
+ backgroundColor: '#1c1c37',
209
  };
210
 
211
  const headingStyle = {
 
241
  cursor: 'pointer',
242
  transition: 'background-color 0.3s',
243
  };
244
+
245
+ const switchButtonStyle = {
246
+ marginTop: '10px',
247
+ color: '#fff',
248
+ background: 'none',
249
+ border: 'none',
250
+ cursor: 'pointer',
251
+ textDecoration: 'underline',
252
+ };
frontend/app/{[recipientusername] → u/[recipientusername]}/page.js RENAMED
@@ -1,45 +1,52 @@
1
  "use client";
2
  import { useState, useEffect } from "react";
3
  import { useParams } from "next/navigation";
4
- import { useWebSocket } from "../WebSocketContext"; // Import the WebSocket hook
 
5
 
6
  export default function ChatPage() {
7
- const { recipientusername } = useParams(); // Use useParams to access the dynamic route parameter
8
  const [recipient, setRecipient] = useState(null);
9
  const [message, setMessage] = useState("");
10
  const [chatLog, setChatLog] = useState([]);
11
- const { ws } = useWebSocket(); // Access WebSocket from context
 
 
 
 
 
 
 
 
 
 
12
 
13
- // Update recipient state when recipientusername changes
14
  useEffect(() => {
15
  if (recipientusername) {
16
  setRecipient(recipientusername);
17
  }
18
  }, [recipientusername]);
19
 
20
- // Load chat history from localStorage when recipient changes
21
  useEffect(() => {
22
  if (recipient) {
23
  const storedMessages = JSON.parse(localStorage.getItem(recipient)) || [];
24
- setChatLog(storedMessages);
25
  }
26
  }, [recipient]);
27
 
28
- // Re-fetch the chat log every 2 seconds
29
  useEffect(() => {
30
  const interval = setInterval(() => {
31
  if (recipient) {
32
- const updatedMessages = JSON.parse(localStorage.getItem(recipient)) || [];
33
- setChatLog(updatedMessages);
 
34
  }
35
- }, 2000); // Fetch chat log every 2 seconds
36
 
37
- // Cleanup the interval when component unmounts
38
  return () => clearInterval(interval);
39
  }, [recipient]);
40
 
41
  const handleSendMessage = () => {
42
- // Check if the WebSocket is connected and the message is not empty
43
  if (!ws) {
44
  alert("WebSocket is not connected.");
45
  return;
@@ -54,91 +61,168 @@ export default function ChatPage() {
54
  const msgData = JSON.stringify({ recipient, message });
55
  ws.send(msgData);
56
 
57
- setChatLog((prev) => [
58
- ...prev,
59
- { from: "You", message, timestamp: new Date().toLocaleString() },
60
- ]);
 
 
 
61
  setMessage("");
62
 
63
- // Update localStorage
64
- const updatedChatLog = [
65
- ...chatLog,
66
- { from: "You", message, timestamp: new Date().toLocaleString() },
67
- ];
68
  localStorage.setItem(recipient, JSON.stringify(updatedChatLog));
69
  }
70
  };
71
 
 
 
 
 
 
 
 
 
 
72
  return (
73
  <div
74
  style={{
75
  backgroundColor: "#1a1a2e",
76
  minHeight: "100vh",
 
 
77
  fontFamily: "Segoe UI, Tahoma, Geneva, Verdana, sans-serif",
78
- padding: "20px",
79
  color: "#fff",
80
  }}
81
  >
82
  {recipient ? (
83
  <>
84
- <h1 style={{ color: "#d4a5ff" }}>Chat with {recipient}</h1>
85
-
86
- <div style={{ width: "100%", maxWidth: "600px" }}>
87
- <div style={{ marginBottom: "10px" }}>
88
- <input
89
- type="text"
90
- placeholder="Message"
91
- value={message}
92
- onChange={(e) => setMessage(e.target.value)}
93
- style={inputStyle}
94
- />
95
- <button onClick={handleSendMessage} style={sendButtonStyle}>
96
- Send
97
- </button>
98
- </div>
99
-
100
- <div
101
- style={{
102
- maxHeight: "400px",
103
- overflowY: "auto",
104
- marginTop: "20px",
105
- padding: "10px",
106
- backgroundColor: "#2e2e4f",
107
- borderRadius: "10px",
108
- boxShadow: "0 2px 5px rgba(0, 0, 0, 0.2)",
109
- }}
110
- >
111
- {chatLog.map((entry, index) => (
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
112
  <div
113
- key={index}
114
  style={{
115
- backgroundColor: entry.from === "You" ? "#4b2e83" : "#3d3d6b",
 
116
  color: "#fff",
117
  padding: "10px",
118
- marginBottom: "10px",
119
  borderRadius: "10px",
120
- maxWidth: "80%",
121
- marginLeft: entry.from === "You" ? "auto" : "0",
122
  wordWrap: "break-word",
123
  }}
124
  >
125
- <p
126
- style={{
127
- margin: 0,
128
- fontWeight: "bold",
129
- fontSize: "14px",
130
- color: "#e0b0ff",
131
- }}
132
- >
133
- {entry.from}
134
- </p>
135
  <p style={{ margin: "5px 0" }}>{entry.message}</p>
136
  <small style={{ fontSize: "12px", color: "#a1a1c7" }}>
137
  {entry.timestamp}
138
  </small>
139
  </div>
140
- ))}
141
- </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
142
  </div>
143
  </>
144
  ) : (
@@ -147,26 +231,3 @@ export default function ChatPage() {
147
  </div>
148
  );
149
  }
150
-
151
- const inputStyle = {
152
- width: "100%",
153
- padding: "10px",
154
- margin: "5px 0",
155
- borderRadius: "5px",
156
- border: "1px solid #4b2e83",
157
- fontSize: "16px",
158
- backgroundColor: "#2e2e4f",
159
- color: "#fff",
160
- };
161
-
162
- const sendButtonStyle = {
163
- padding: "12px 20px",
164
- backgroundColor: "#6a4c9c",
165
- color: "#fff",
166
- border: "none",
167
- borderRadius: "5px",
168
- fontSize: "16px",
169
- cursor: "pointer",
170
- marginLeft: "10px",
171
- transition: "background-color 0.3s",
172
- };
 
1
  "use client";
2
  import { useState, useEffect } from "react";
3
  import { useParams } from "next/navigation";
4
+ import { useWebSocket } from "../../WebSocketContext";
5
+ import Image from "next/image";
6
 
7
  export default function ChatPage() {
8
+ const { recipientusername } = useParams();
9
  const [recipient, setRecipient] = useState(null);
10
  const [message, setMessage] = useState("");
11
  const [chatLog, setChatLog] = useState([]);
12
+ const { ws } = useWebSocket();
13
+
14
+ const [me, setMe] = useState("");
15
+
16
+ useEffect(() => {
17
+ // Retrieve 'me' from localStorage
18
+ const storedMe = localStorage.getItem("me");
19
+ if (storedMe) {
20
+ setMe(storedMe);
21
+ }
22
+ }, []);
23
 
 
24
  useEffect(() => {
25
  if (recipientusername) {
26
  setRecipient(recipientusername);
27
  }
28
  }, [recipientusername]);
29
 
 
30
  useEffect(() => {
31
  if (recipient) {
32
  const storedMessages = JSON.parse(localStorage.getItem(recipient)) || [];
33
+ setChatLog(formatMessages(storedMessages));
34
  }
35
  }, [recipient]);
36
 
 
37
  useEffect(() => {
38
  const interval = setInterval(() => {
39
  if (recipient) {
40
+ const updatedMessages =
41
+ JSON.parse(localStorage.getItem(recipient)) || [];
42
+ setChatLog(formatMessages(updatedMessages));
43
  }
44
+ }, 2000);
45
 
 
46
  return () => clearInterval(interval);
47
  }, [recipient]);
48
 
49
  const handleSendMessage = () => {
 
50
  if (!ws) {
51
  alert("WebSocket is not connected.");
52
  return;
 
61
  const msgData = JSON.stringify({ recipient, message });
62
  ws.send(msgData);
63
 
64
+ const newMessage = {
65
+ from: "You",
66
+ message,
67
+ timestamp: new Date().toISOString(), // Use ISO 8601 for consistency
68
+ };
69
+
70
+ setChatLog((prev) => [...prev, formatMessage(newMessage)]);
71
  setMessage("");
72
 
73
+ const updatedChatLog = [...chatLog, newMessage];
 
 
 
 
74
  localStorage.setItem(recipient, JSON.stringify(updatedChatLog));
75
  }
76
  };
77
 
78
+ const formatMessages = (messages) => {
79
+ return messages.map(formatMessage);
80
+ };
81
+
82
+ const formatMessage = (message) => {
83
+ const formattedTimestamp = new Date(message.timestamp).toLocaleString();
84
+ return { ...message, timestamp: formattedTimestamp };
85
+ };
86
+
87
  return (
88
  <div
89
  style={{
90
  backgroundColor: "#1a1a2e",
91
  minHeight: "100vh",
92
+ display: "flex",
93
+ flexDirection: "column",
94
  fontFamily: "Segoe UI, Tahoma, Geneva, Verdana, sans-serif",
 
95
  color: "#fff",
96
  }}
97
  >
98
  {recipient ? (
99
  <>
100
+ {/* Chat Header */}
101
+ <div
102
+ style={{
103
+ backgroundColor: "#170f3f",
104
+ padding: "10px",
105
+ display: "flex",
106
+ alignItems: "center",
107
+ gap: "10px",
108
+ }}
109
+ >
110
+ <Image
111
+ src={`https://ui-avatars.com/api/?name=${encodeURIComponent(
112
+ recipient
113
+ )}&background=random`}
114
+ alt={`${recipient}'s avatar`}
115
+ width={40}
116
+ height={40}
117
+ style={{ borderRadius: "50%", marginLeft: "40px" }}
118
+ />
119
+ <h1 style={{ margin: 0, fontSize: "20px" }}>
120
+ {recipient === me ? `${recipient} (Me)` : recipient}
121
+ </h1>
122
+ </div>
123
+
124
+ {/* Chat Log */}
125
+ <div
126
+ style={{
127
+ flex: 1,
128
+ overflowY: "auto",
129
+ padding: "20px",
130
+ backgroundColor: "#1c1c2f",
131
+ display: "flex",
132
+ flexDirection: "column",
133
+ gap: "10px",
134
+ borderTop: "1px solid #3d3d6b",
135
+ }}
136
+ >
137
+ {chatLog.map((entry, index) => (
138
+ <div
139
+ key={index}
140
+ style={{
141
+ display: "flex",
142
+ alignItems: "flex-end",
143
+ flexDirection: entry.from === "You" ? "row-reverse" : "row",
144
+ gap: "10px",
145
+ }}
146
+ >
147
+ {/* Avatar */}
148
+ <Image
149
+ src={
150
+ entry.from === "You"
151
+ ? `https://ui-avatars.com/api/?name=${encodeURIComponent(
152
+ me
153
+ )}&background=random`
154
+ : `https://ui-avatars.com/api/?name=${encodeURIComponent(
155
+ recipient
156
+ )}&background=random`
157
+ }
158
+ alt={`${entry.from}'s avatar`}
159
+ width={30}
160
+ height={30}
161
+ style={{ borderRadius: "50%" }}
162
+ />
163
+
164
+ {/* Message */}
165
  <div
 
166
  style={{
167
+ backgroundColor:
168
+ entry.from === "You" ? "#3b2e83" : "#3d446b",
169
  color: "#fff",
170
  padding: "10px",
 
171
  borderRadius: "10px",
172
+ maxWidth: "70%",
 
173
  wordWrap: "break-word",
174
  }}
175
  >
176
+ <p style={{ margin: 0, fontWeight: "bold" }}>{entry.from}</p>
 
 
 
 
 
 
 
 
 
177
  <p style={{ margin: "5px 0" }}>{entry.message}</p>
178
  <small style={{ fontSize: "12px", color: "#a1a1c7" }}>
179
  {entry.timestamp}
180
  </small>
181
  </div>
182
+ </div>
183
+ ))}
184
+ </div>
185
+
186
+ {/* Message Input */}
187
+ <div
188
+ style={{
189
+ backgroundColor: "#171927",
190
+ borderTop: "1px solid rgb(95, 38, 207)",
191
+ padding: "10px",
192
+ display: "flex",
193
+ gap: "10px",
194
+ }}
195
+ >
196
+ <input
197
+ type="text"
198
+ placeholder="Type your message..."
199
+ value={message}
200
+ onChange={(e) => setMessage(e.target.value)}
201
+ style={{
202
+ flex: 1,
203
+ padding: "10px",
204
+ borderRadius: "5px",
205
+ border: "1px solid #4b2e83",
206
+ fontSize: "16px",
207
+ backgroundColor: "#2e2e4f",
208
+ color: "#fff",
209
+ }}
210
+ />
211
+ <button
212
+ onClick={handleSendMessage}
213
+ style={{
214
+ padding: "10px 20px",
215
+ backgroundColor: "#3b2e83",
216
+ color: "#fff",
217
+ border: "none",
218
+ borderRadius: "5px",
219
+ fontSize: "16px",
220
+ cursor: "pointer",
221
+ transition: "background-color 0.3s",
222
+ }}
223
+ >
224
+ Send
225
+ </button>
226
  </div>
227
  </>
228
  ) : (
 
231
  </div>
232
  );
233
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
frontend/next.config.mjs CHANGED
@@ -1,4 +1,22 @@
1
  /** @type {import('next').NextConfig} */
2
- const nextConfig = {};
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3
 
4
  export default nextConfig;
 
1
  /** @type {import('next').NextConfig} */
2
+ const nextConfig = {
3
+ images: {
4
+ dangerouslyAllowSVG: true,
5
+ remotePatterns: [
6
+ {
7
+ protocol: 'https',
8
+ hostname: 'via.placeholder.com',
9
+ port: '',
10
+ pathname: '/**',
11
+ },
12
+ {
13
+ protocol: 'https',
14
+ hostname: 'ui-avatars.com',
15
+ port: '',
16
+ pathname: '/api/**',
17
+ },
18
+ ],
19
+ },
20
+ };
21
 
22
  export default nextConfig;