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 */}
|