ChandimaPrabath commited on
Commit
7a6978d
·
1 Parent(s): 6fb9a09
frontend/app/Auth.js ADDED
@@ -0,0 +1,277 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useState } from 'react';
2
+
3
+ const Auth = ({
4
+ username,
5
+ setUsername,
6
+ password,
7
+ setPassword,
8
+ confirmPassword,
9
+ setConfirmPassword,
10
+ isLoading,
11
+ setIsLoading,
12
+ error,
13
+ setError,
14
+ isSignup,
15
+ setIsSignup,
16
+ connectAndAuthenticate
17
+ }) => {
18
+ const [usernameValid, setUsernameValid] = useState(false);
19
+ const [passwordValid, setPasswordValid] = useState(false);
20
+ const [passwordsMatch, setPasswordsMatch] = useState(false);
21
+ const [showPassword, setShowPassword] = useState(false);
22
+ const [showConfirmPassword, setShowConfirmPassword] = useState(false);
23
+
24
+ const validateSignup = () => {
25
+ if (!username || !password || !confirmPassword) {
26
+ setError('Please fill in all fields');
27
+ return false;
28
+ }
29
+ if (username.length < 4) {
30
+ setError('Username must be at least 4 characters');
31
+ return false;
32
+ }
33
+ if (password.length <= 8) {
34
+ setError('Password must be longer than 8 characters');
35
+ return false;
36
+ }
37
+ if (password !== confirmPassword) {
38
+ setError('Passwords do not match');
39
+ return false;
40
+ }
41
+ return true;
42
+ };
43
+
44
+ const handleSignup = () => {
45
+ if (!validateSignup()) return;
46
+
47
+ setIsLoading(true);
48
+ const signupData = JSON.stringify({ username, password, action: 'signup' });
49
+ connectAndAuthenticate(signupData);
50
+ };
51
+
52
+ const handleLogin = () => {
53
+ if (!username || !password) {
54
+ setError('Please enter both username and password');
55
+ return;
56
+ }
57
+
58
+ setIsLoading(true);
59
+ const loginData = JSON.stringify({ username, password, action: 'login' });
60
+ connectAndAuthenticate(loginData);
61
+ };
62
+
63
+ // Validations to dynamically update conditions
64
+ const checkUsername = (value) => {
65
+ setUsername(value);
66
+ setUsernameValid(value.length >= 4);
67
+ };
68
+
69
+ const checkPassword = (value) => {
70
+ setPassword(value);
71
+ setPasswordValid(value.length > 8);
72
+ };
73
+
74
+ const checkPasswordsMatch = (value) => {
75
+ setConfirmPassword(value);
76
+ setPasswordsMatch(value === password);
77
+ };
78
+
79
+ return (
80
+ <div style={inputContainerStyle}>
81
+ {isSignup ? (
82
+ // Signup Form
83
+ <div style={inputContainerStyle}>
84
+ <h2 style={headingStyle}>Signup</h2>
85
+ <input
86
+ type="text"
87
+ placeholder="Username"
88
+ value={username}
89
+ onChange={(e) => checkUsername(e.target.value)}
90
+ style={inputStyle}
91
+ />
92
+ <div style={passwordInputContainerStyle}>
93
+ <input
94
+ type={showPassword ? 'text' : 'password'}
95
+ placeholder="Password"
96
+ value={password}
97
+ onChange={(e) => checkPassword(e.target.value)}
98
+ style={inputStyle}
99
+ />
100
+ <span
101
+ onClick={() => setShowPassword(!showPassword)}
102
+ style={eyeIconStyle}
103
+ >
104
+ 👁️
105
+ </span>
106
+ </div>
107
+ <div style={passwordInputContainerStyle}>
108
+ <input
109
+ type={showConfirmPassword ? 'text' : 'password'}
110
+ placeholder="Confirm Password"
111
+ value={confirmPassword}
112
+ onChange={(e) => checkPasswordsMatch(e.target.value)}
113
+ style={inputStyle}
114
+ />
115
+ <span
116
+ onClick={() => setShowConfirmPassword(!showConfirmPassword)}
117
+ style={eyeIconStyle}
118
+ >
119
+ 👁️
120
+ </span>
121
+ </div>
122
+ <div style={buttonContainerStyle}>
123
+ <button
124
+ onClick={handleSignup}
125
+ style={loginButtonStyle}
126
+ >
127
+ {isLoading ? 'Processing...' : 'Signup'}
128
+ </button>
129
+ </div>
130
+ </div>
131
+ ) : (
132
+ // Login Form
133
+ <div style={inputContainerStyle}>
134
+ <h2 style={headingStyle}>Login</h2>
135
+ <input
136
+ type="text"
137
+ placeholder="Username"
138
+ value={username}
139
+ onChange={(e) => setUsername(e.target.value)}
140
+ style={inputStyle}
141
+ />
142
+ <div style={passwordInputContainerStyle}>
143
+ <input
144
+ type={showPassword ? 'text' : 'password'}
145
+ placeholder="Password"
146
+ value={password}
147
+ onChange={(e) => setPassword(e.target.value)}
148
+ style={inputStyle}
149
+ />
150
+ <span
151
+ onClick={() => setShowPassword(!showPassword)}
152
+ style={eyeIconStyle}
153
+ >
154
+ 👁️
155
+ </span>
156
+ </div>
157
+ <div style={buttonContainerStyle}>
158
+ <button
159
+ onClick={handleLogin}
160
+ style={loginButtonStyle}
161
+ >
162
+ {isLoading ? 'Processing...' : 'Login'}
163
+ </button>
164
+ </div>
165
+ </div>
166
+ )}
167
+
168
+ {isSignup && (
169
+ <div style={conditionsContainerStyle}>
170
+ <h3 style={conditionsHeaderStyle}>Signup Conditions</h3>
171
+ <ul>
172
+ <li style={{ color: usernameValid ? 'green' : 'red' }}>
173
+ {usernameValid ? '✔ Username must be at least 4 characters' : '✘ Username must be at least 4 characters'}
174
+ </li>
175
+ <li style={{ color: passwordValid ? 'green' : 'red' }}>
176
+ {passwordValid ? '✔ Password must be longer than 8 characters' : '✘ Password must be longer than 8 characters'}
177
+ </li>
178
+ <li style={{ color: passwordsMatch ? 'green' : 'red' }}>
179
+ {passwordsMatch ? '✔ Passwords match' : '✘ Passwords do not match'}
180
+ </li>
181
+ </ul>
182
+ </div>
183
+ )}
184
+
185
+ <div style={buttonContainerStyle}>
186
+ <button
187
+ onClick={() => setIsSignup(!isSignup)}
188
+ style={switchButtonStyle}
189
+ >
190
+ {isSignup ? 'Already have an Account? Login' : 'Don\'t have an Account? Signup'}
191
+ </button>
192
+ </div>
193
+
194
+ {error && <div style={{ color: 'red', display: 'flex', alignItems: 'center' }}>
195
+ <div style={{ width: '8px', height: '8px', backgroundColor: 'red', borderRadius: '50px', marginRight: '5px' }} />
196
+ {error}
197
+ </div>}
198
+ </div>
199
+ );
200
+ };
201
+
202
+ const inputContainerStyle = {
203
+ display: 'flex',
204
+ flexDirection: 'column',
205
+ gap: '15px',
206
+ maxWidth: '400px',
207
+ width: '100%',
208
+ };
209
+
210
+ const headingStyle = {
211
+ color: '#d4a5ff',
212
+ fontSize: '36px',
213
+ };
214
+
215
+ const inputStyle = {
216
+ padding: '10px',
217
+ fontSize: '16px',
218
+ borderRadius: '5px',
219
+ border: '1px solid #4b2e83',
220
+ marginBottom: '10px',
221
+ backgroundColor: '#2e2e4f',
222
+ color: '#fff',
223
+ };
224
+
225
+ const passwordInputContainerStyle = {
226
+ position: 'relative',
227
+ display: 'grid',
228
+ };
229
+
230
+ const eyeIconStyle = {
231
+ position: 'absolute',
232
+ right: '10px',
233
+ top: '50%',
234
+ transform: 'translateY(-50%)',
235
+ cursor: 'pointer',
236
+ };
237
+
238
+ const loginButtonStyle = {
239
+ padding: '12px 20px',
240
+ backgroundColor: '#6a4c9c',
241
+ color: '#fff',
242
+ border: 'none',
243
+ borderRadius: '5px',
244
+ fontSize: '16px',
245
+ cursor: 'pointer',
246
+ transition: 'background-color 0.3s',
247
+ };
248
+
249
+ const switchButtonStyle = {
250
+ marginTop: '10px',
251
+ color: '#fff',
252
+ background: 'none',
253
+ border: 'none',
254
+ cursor: 'pointer',
255
+ textDecoration: 'underline',
256
+ };
257
+
258
+ const buttonContainerStyle = {
259
+ display: 'flex',
260
+ justifyContent: 'center',
261
+ };
262
+
263
+ const conditionsContainerStyle = {
264
+ display: 'flex',
265
+ flexDirection: 'column',
266
+ gap: '10px',
267
+ marginTop: '15px',
268
+ maxWidth: '300px',
269
+ };
270
+
271
+ const conditionsHeaderStyle = {
272
+ color: '#d4a5ff',
273
+ fontSize: '20px',
274
+ marginBottom: '5px',
275
+ };
276
+
277
+ export default Auth;
frontend/app/WebSocketContext.js CHANGED
@@ -9,43 +9,77 @@ export const useWebSocket = () => {
9
  export const WebSocketProvider = ({ children }) => {
10
  const [ws, setWs] = useState(null);
11
  const [status, setStatus] = useState('Disconnected');
 
 
 
 
12
 
13
  useEffect(() => {
14
  const relayUrl = process.env.NEXT_PUBLIC_RELAY_URL;
15
 
16
- console.log(relayUrl)
17
-
18
  if (!relayUrl) {
19
  console.error('Environment variable NEXT_PUBLIC_RELAY_URL is not defined.');
20
  return;
21
  }
22
 
23
- const socket = new WebSocket(relayUrl);
 
24
 
25
- socket.onopen = () => {
26
- console.log('WebSocket connection opened.');
27
- setStatus('Connected');
28
- };
 
 
29
 
30
- socket.onclose = () => {
31
- console.log('WebSocket connection closed.');
32
- setStatus('Disconnected');
 
 
 
 
 
 
 
 
 
 
33
  };
34
 
35
- socket.onerror = (error) => {
36
- console.error('WebSocket error:', error);
37
- setStatus('Error');
 
 
 
 
 
 
 
 
38
  };
39
 
40
- setWs(socket);
 
 
 
 
 
 
 
 
41
 
42
  return () => {
43
- socket.close();
 
 
 
44
  };
45
- }, []);
46
 
47
  return (
48
- <WebSocketContext.Provider value={{ ws, status }}>
49
  {children}
50
  </WebSocketContext.Provider>
51
  );
 
9
  export const WebSocketProvider = ({ children }) => {
10
  const [ws, setWs] = useState(null);
11
  const [status, setStatus] = useState('Disconnected');
12
+ const [retryCount, setRetryCount] = useState(0); // For retrying WebSocket connection
13
+ const [timeoutError, setTimeoutError] = useState(false); // Timeout error state
14
+
15
+ const maxRetries = 5; // Max retry attempts
16
 
17
  useEffect(() => {
18
  const relayUrl = process.env.NEXT_PUBLIC_RELAY_URL;
19
 
 
 
20
  if (!relayUrl) {
21
  console.error('Environment variable NEXT_PUBLIC_RELAY_URL is not defined.');
22
  return;
23
  }
24
 
25
+ const connectWebSocket = () => {
26
+ const socket = new WebSocket(relayUrl);
27
 
28
+ socket.onopen = () => {
29
+ console.log('WebSocket connection opened.');
30
+ setStatus('Connected');
31
+ setRetryCount(0); // Reset retry count on successful connection
32
+ setTimeoutError(false); // Reset timeout error
33
+ };
34
 
35
+ socket.onclose = () => {
36
+ console.log('WebSocket connection closed.');
37
+ setStatus('Disconnected');
38
+ handleReconnection();
39
+ };
40
+
41
+ socket.onerror = (error) => {
42
+ console.error('WebSocket error:', error);
43
+ setStatus('Error');
44
+ handleReconnection();
45
+ };
46
+
47
+ setWs(socket);
48
  };
49
 
50
+ // Function to handle reconnection with retry
51
+ const handleReconnection = () => {
52
+ if (retryCount < maxRetries) {
53
+ setRetryCount(retryCount + 1);
54
+ setStatus('Reconnecting...');
55
+ console.log(`Attempting reconnect ${retryCount + 1}/${maxRetries}`);
56
+ setTimeout(() => connectWebSocket(), 2000 * (retryCount + 1)); // Retry with increasing delay
57
+ } else {
58
+ console.error('Max retries reached. WebSocket connection failed.');
59
+ setStatus('Failed');
60
+ }
61
  };
62
 
63
+ connectWebSocket();
64
+
65
+ // Timeout mechanism to handle stuck connections
66
+ const timeout = setTimeout(() => {
67
+ if (status === 'Connecting' || status === 'Reconnecting') {
68
+ setTimeoutError(true);
69
+ setStatus('Timeout - Could not connect');
70
+ }
71
+ }, 10000); // 10 seconds timeout
72
 
73
  return () => {
74
+ clearTimeout(timeout); // Cleanup timeout on unmount
75
+ if (ws) {
76
+ ws.close();
77
+ }
78
  };
79
+ }, [status, retryCount]);
80
 
81
  return (
82
+ <WebSocketContext.Provider value={{ ws, status, timeoutError }}>
83
  {children}
84
  </WebSocketContext.Provider>
85
  );
frontend/app/layout.js CHANGED
@@ -4,6 +4,7 @@ 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('');
@@ -77,18 +78,6 @@ function WebSocketLayout({
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) {
@@ -104,15 +93,8 @@ function WebSocketLayout({
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
 
118
  useEffect(() => {
@@ -134,10 +116,6 @@ function WebSocketLayout({
134
  }
135
  }, [ws, username]);
136
 
137
- const handleUserSelect = (recipient) => {
138
- router.push(`/u/${recipient}`);
139
- };
140
-
141
  return (
142
  <div>
143
  {isLoggedIn ? (
@@ -153,47 +131,21 @@ function WebSocketLayout({
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"
161
- value={username}
162
- onChange={(e) => setUsername(e.target.value)}
163
- style={inputStyle}
164
- />
165
- <input
166
- type="password"
167
- placeholder="Password"
168
- value={password}
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>
@@ -207,46 +159,3 @@ const loginContainerStyle = {
207
  height: '100vh',
208
  backgroundColor: '#1c1c37',
209
  };
210
-
211
- const headingStyle = {
212
- color: '#d4a5ff',
213
- fontSize: '36px',
214
- };
215
-
216
- const inputContainerStyle = {
217
- display: 'flex',
218
- flexDirection: 'column',
219
- gap: '15px',
220
- maxWidth: '400px',
221
- width: '100%',
222
- };
223
-
224
- const inputStyle = {
225
- padding: '10px',
226
- fontSize: '16px',
227
- borderRadius: '5px',
228
- border: '1px solid #4b2e83',
229
- marginBottom: '10px',
230
- backgroundColor: '#2e2e4f',
231
- color: '#fff',
232
- };
233
-
234
- const loginButtonStyle = {
235
- padding: '12px 20px',
236
- backgroundColor: '#6a4c9c',
237
- color: '#fff',
238
- border: 'none',
239
- borderRadius: '5px',
240
- fontSize: '16px',
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
- };
 
4
  import { WebSocketProvider, useWebSocket } from './WebSocketContext';
5
  import './globals.css';
6
  import Sidebar from './Sidebar';
7
+ import Auth from './Auth';
8
 
9
  export default function RootLayout({ children }) {
10
  const [username, setUsername] = useState('');
 
78
  const { ws, status } = useWebSocket();
79
  const router = useRouter();
80
 
 
 
 
 
 
 
 
 
 
 
 
 
81
  const connectAndAuthenticate = () => {
82
  setError('');
83
  if (!username || !password) {
 
93
  }
94
  };
95
 
96
+ const handleUserSelect = (recipient) => {
97
+ router.push(`/u/${recipient}`);
 
 
 
 
 
 
 
98
  };
99
 
100
  useEffect(() => {
 
116
  }
117
  }, [ws, username]);
118
 
 
 
 
 
119
  return (
120
  <div>
121
  {isLoggedIn ? (
 
131
  </div>
132
  ) : (
133
  <div style={loginContainerStyle}>
134
+ <Auth
135
+ username={username}
136
+ setUsername={setUsername}
137
+ password={password}
138
+ setPassword={setPassword}
139
+ confirmPassword={confirmPassword}
140
+ setConfirmPassword={setConfirmPassword}
141
+ isLoading={isLoading}
142
+ setIsLoading={setIsLoading}
143
+ error={error}
144
+ setError={setError}
145
+ isSignup={isSignup}
146
+ setIsSignup={setIsSignup}
147
+ connectAndAuthenticate={connectAndAuthenticate}
148
+ />
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
149
  </div>
150
  )}
151
  </div>
 
159
  height: '100vh',
160
  backgroundColor: '#1c1c37',
161
  };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
frontend/app/page.js CHANGED
@@ -3,10 +3,19 @@
3
  import { useState } from "react";
4
 
5
  export default function ChatPage() {
6
-
7
-
8
  return (
9
- <div style={{ width: "100%", maxWidth: "600px" }}>
 
 
 
 
 
 
 
 
 
 
10
  </div>
11
- );
12
- }
 
 
3
  import { useState } from "react";
4
 
5
  export default function ChatPage() {
 
 
6
  return (
7
+ <div style={{ width: "100%", padding: "20px", backgroundColor: "rgb(28, 28, 47)", minHeight: "100vh" }}>
8
+ <div style={{ display: 'flex', flexDirection: 'column', justifyContent: 'center', alignItems: 'center', height: '100%', width: '100%' }}>
9
+ <div style={{ textAlign: 'center' }}>
10
+ <h1 style={{ color: "#4a4a4a", fontSize: "24px", marginBottom: "20px" }}>Welcome to Nexus</h1>
11
+ <p style={{ color: "#7d7d7d", fontSize: "16px", marginBottom: "20px" }}>
12
+ Open the sidebar to find and start conversations with your contacts.
13
+ </p>
14
+ <div style={{ fontSize: "14px", color: "#aaa" }}>
15
+ <p>No chats selected yet. Choose a conversation from the sidebar.</p>
16
+ </div>
17
+ </div>
18
  </div>
19
+ </div>
20
+ );
21
+ }
frontend/app/u/[recipientusername]/page.js CHANGED
@@ -1,6 +1,7 @@
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
 
@@ -12,6 +13,7 @@ export default function ChatPage() {
12
  const { ws } = useWebSocket();
13
 
14
  const [me, setMe] = useState("");
 
15
 
16
  useEffect(() => {
17
  // Retrieve 'me' from localStorage
@@ -84,6 +86,11 @@ export default function ChatPage() {
84
  return { ...message, timestamp: formattedTimestamp };
85
  };
86
 
 
 
 
 
 
87
  return (
88
  <div
89
  style={{
@@ -105,6 +112,7 @@ export default function ChatPage() {
105
  display: "flex",
106
  alignItems: "center",
107
  gap: "10px",
 
108
  }}
109
  >
110
  <Image
@@ -119,6 +127,22 @@ export default function ChatPage() {
119
  <h1 style={{ margin: 0, fontSize: "20px" }}>
120
  {recipient === me ? `${recipient} (Me)` : recipient}
121
  </h1>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
122
  </div>
123
 
124
  {/* Chat Log */}
 
1
  "use client";
2
+
3
  import { useState, useEffect } from "react";
4
+ import { useParams, useRouter } from "next/navigation";
5
  import { useWebSocket } from "../../WebSocketContext";
6
  import Image from "next/image";
7
 
 
13
  const { ws } = useWebSocket();
14
 
15
  const [me, setMe] = useState("");
16
+ const router = useRouter(); // Hook to navigate
17
 
18
  useEffect(() => {
19
  // Retrieve 'me' from localStorage
 
86
  return { ...message, timestamp: formattedTimestamp };
87
  };
88
 
89
+ const handleCloseChat = () => {
90
+ // Redirect to the root page ("/")
91
+ router.push("/");
92
+ };
93
+
94
  return (
95
  <div
96
  style={{
 
112
  display: "flex",
113
  alignItems: "center",
114
  gap: "10px",
115
+ position: "relative",
116
  }}
117
  >
118
  <Image
 
127
  <h1 style={{ margin: 0, fontSize: "20px" }}>
128
  {recipient === me ? `${recipient} (Me)` : recipient}
129
  </h1>
130
+
131
+ {/* Close Button */}
132
+ <button
133
+ onClick={handleCloseChat}
134
+ style={{
135
+ position: "absolute",
136
+ right: "10px",
137
+ background: "transparent",
138
+ border: "none",
139
+ color: "#fff",
140
+ fontSize: "30px",
141
+ cursor: "pointer",
142
+ }}
143
+ >
144
+ &times;
145
+ </button>
146
  </div>
147
 
148
  {/* Chat Log */}