Commit
·
7a6978d
1
Parent(s):
6fb9a09
update 2
Browse files- frontend/app/Auth.js +277 -0
- frontend/app/WebSocketContext.js +51 -17
- frontend/app/layout.js +18 -109
- frontend/app/page.js +14 -5
- frontend/app/u/[recipientusername]/page.js +25 -1
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
|
|
|
24 |
|
25 |
-
|
26 |
-
|
27 |
-
|
28 |
-
|
|
|
|
|
29 |
|
30 |
-
|
31 |
-
|
32 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
33 |
};
|
34 |
|
35 |
-
|
36 |
-
|
37 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
38 |
};
|
39 |
|
40 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
41 |
|
42 |
return () => {
|
43 |
-
|
|
|
|
|
|
|
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
|
108 |
-
|
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 |
-
<
|
157 |
-
|
158 |
-
|
159 |
-
|
160 |
-
|
161 |
-
|
162 |
-
|
163 |
-
|
164 |
-
|
165 |
-
|
166 |
-
|
167 |
-
|
168 |
-
|
169 |
-
|
170 |
-
|
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%",
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
+
×
|
145 |
+
</button>
|
146 |
</div>
|
147 |
|
148 |
{/* Chat Log */}
|