amaye15 commited on
Commit
9e36fdf
·
1 Parent(s): 5c2b936

Feat - Dark & Light

Browse files
Files changed (3) hide show
  1. static/css/style.css +194 -45
  2. static/js/script.js +93 -137
  3. templates/index.html +54 -39
static/css/style.css CHANGED
@@ -1,94 +1,243 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  body {
2
- font-family: sans-serif;
3
  line-height: 1.6;
4
- margin: 20px;
5
- background-color: #f4f4f4;
 
 
 
6
  }
7
 
8
- h1, h2 {
9
- color: #333;
 
 
 
 
 
 
 
 
10
  }
11
 
12
- .auth-section {
13
- background-color: #fff;
 
 
 
 
 
 
 
 
 
14
  padding: 20px;
15
- margin-bottom: 20px;
16
- border: 1px solid #ddd;
17
- border-radius: 5px;
18
- max-width: 400px;
 
 
 
 
 
 
19
  }
20
 
21
- #welcome-section {
22
- background-color: #e7f3fe;
23
- padding: 20px;
24
- border: 1px solid #c8e1f8;
25
- border-radius: 5px;
 
 
 
 
 
 
 
 
26
  }
27
 
 
 
 
 
 
 
 
 
 
 
 
 
28
 
29
  label {
30
- display: inline-block;
31
- margin-bottom: 5px;
 
 
32
  }
33
 
34
  input[type="email"],
35
  input[type="password"] {
36
- width: calc(100% - 22px); /* Account for padding/border */
37
- padding: 10px;
38
- margin-bottom: 10px;
39
- border: 1px solid #ccc;
40
- border-radius: 4px;
 
 
 
 
 
 
 
 
 
 
41
  }
42
 
43
- button {
44
- background-color: #5cb85c;
45
- color: white;
46
- padding: 10px 15px;
 
 
 
 
 
 
47
  border: none;
48
- border-radius: 4px;
49
  cursor: pointer;
50
  font-size: 1em;
 
 
 
 
51
  }
52
 
53
- button:hover {
54
- background-color: #4cae4c;
 
 
 
55
  }
56
 
57
- #logout-button {
58
- background-color: #d9534f;
59
  }
60
- #logout-button:hover {
61
- background-color: #c9302c;
62
  }
63
 
 
 
 
 
 
 
 
 
 
 
 
64
  .status-message {
65
  margin-top: 15px;
66
- color: red;
67
- font-weight: bold;
 
 
 
68
  }
69
  .status-message.success {
70
- color: green;
 
 
71
  }
72
-
73
- hr {
74
- margin: 20px 0;
 
75
  }
76
 
 
 
77
  #notifications {
78
  margin-top: 15px;
79
  padding: 15px;
80
- border: 1px dashed #aaa;
81
- background-color: #f9f9f9;
 
82
  min-height: 100px;
83
  max-height: 300px;
84
  overflow-y: auto;
 
85
  }
86
 
87
  #notifications p {
88
- margin: 5px 0;
89
- padding-bottom: 5px;
90
- border-bottom: 1px solid #eee;
 
91
  }
92
  #notifications p:last-child {
93
  border-bottom: none;
 
 
 
 
 
94
  }
 
1
+ /* --- CSS Variables --- */
2
+ :root {
3
+ --body-bg: #f0f0f0; /* Light gray background */
4
+ --container-bg: #ffffff;
5
+ --text-color: #333333;
6
+ --text-color-secondary: #555555;
7
+ --border-color: #e0e0e0;
8
+ --input-bg: #ffffff;
9
+ --input-border: #cccccc;
10
+ --input-focus-border: #86b7fe; /* Bootstrap focus blue */
11
+ --button-primary-bg: #0d6efd; /* Bootstrap primary */
12
+ --button-primary-hover-bg: #0b5ed7;
13
+ --button-danger-bg: #dc3545; /* Bootstrap danger */
14
+ --button-danger-hover-bg: #bb2d3b;
15
+ --button-text-color: #ffffff;
16
+ --link-color: #0d6efd;
17
+ --status-error-color: #dc3545;
18
+ --status-success-color: #198754; /* Bootstrap success */
19
+ --notification-bg: #f8f9fa; /* Light gray */
20
+ --notification-border: #dee2e6;
21
+ --shadow-color: rgba(0, 0, 0, 0.08);
22
+ --border-radius: 8px; /* Rounded corners */
23
+ }
24
+
25
+ .dark-mode {
26
+ --body-bg: #1a1a1a; /* Dark charcoal */
27
+ --container-bg: #2c2c2c; /* Slightly lighter dark */
28
+ --text-color: #e0e0e0; /* Light gray text */
29
+ --text-color-secondary: #aaaaaa;
30
+ --border-color: #444444; /* Darker border */
31
+ --input-bg: #333333;
32
+ --input-border: #555555;
33
+ --input-focus-border: #0d6efd; /* Keep focus blue */
34
+ --button-primary-bg: #0d6efd;
35
+ --button-primary-hover-bg: #0b5ed7;
36
+ --button-danger-bg: #dc3545;
37
+ --button-danger-hover-bg: #bb2d3b;
38
+ --button-text-color: #ffffff;
39
+ --link-color: #58a6ff; /* Lighter blue for links */
40
+ --status-error-color: #f86c7c;
41
+ --status-success-color: #4cd08a;
42
+ --notification-bg: #333333;
43
+ --notification-border: #444444;
44
+ --shadow-color: rgba(255, 255, 255, 0.05);
45
+ }
46
+
47
+ /* --- Global Styles --- */
48
+ * {
49
+ box-sizing: border-box;
50
+ }
51
+
52
  body {
53
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
54
  line-height: 1.6;
55
+ margin: 0;
56
+ padding: 0;
57
+ background-color: var(--body-bg);
58
+ color: var(--text-color);
59
+ transition: background-color 0.3s ease, color 0.3s ease; /* Smooth theme transition */
60
  }
61
 
62
+ /* --- Layout --- */
63
+ .container {
64
+ max-width: 800px; /* Max width for content */
65
+ margin: 30px auto; /* Center container */
66
+ padding: 25px;
67
+ background-color: var(--container-bg);
68
+ border: 1px solid var(--border-color);
69
+ border-radius: var(--border-radius);
70
+ box-shadow: 0 4px 12px var(--shadow-color);
71
+ transition: background-color 0.3s ease, border-color 0.3s ease;
72
  }
73
 
74
+ .header {
75
+ display: flex;
76
+ justify-content: space-between;
77
+ align-items: center;
78
+ margin-bottom: 25px;
79
+ padding-bottom: 15px;
80
+ border-bottom: 1px solid var(--border-color);
81
+ }
82
+
83
+ .content-section {
84
+ margin-bottom: 25px;
85
  padding: 20px;
86
+ border: 1px solid var(--border-color);
87
+ border-radius: var(--border-radius);
88
+ background-color: var(--container-bg); /* Same as main container or slightly different */
89
+ }
90
+ /* Remove extra padding/border if nested inside container with padding */
91
+ .container > .content-section {
92
+ padding: 0;
93
+ border: none;
94
+ background: none;
95
+ margin-bottom: 20px;
96
  }
97
 
98
+
99
+ h1, h2 {
100
+ color: var(--text-color);
101
+ margin-top: 0;
102
+ margin-bottom: 0.75em;
103
+ }
104
+ h1 { font-size: 1.8em; }
105
+ h2 { font-size: 1.4em; }
106
+
107
+ hr {
108
+ border: none;
109
+ border-top: 1px solid var(--border-color);
110
+ margin: 25px 0;
111
  }
112
 
113
+ a {
114
+ color: var(--link-color);
115
+ text-decoration: none;
116
+ }
117
+ a:hover {
118
+ text-decoration: underline;
119
+ }
120
+
121
+ /* --- Forms --- */
122
+ .form-group {
123
+ margin-bottom: 15px;
124
+ }
125
 
126
  label {
127
+ display: block; /* Label on top */
128
+ margin-bottom: 6px;
129
+ font-weight: 500;
130
+ color: var(--text-color-secondary);
131
  }
132
 
133
  input[type="email"],
134
  input[type="password"] {
135
+ width: 100%;
136
+ padding: 10px 12px;
137
+ border: 1px solid var(--input-border);
138
+ border-radius: var(--border-radius);
139
+ background-color: var(--input-bg);
140
+ color: var(--text-color);
141
+ font-size: 1em;
142
+ transition: border-color 0.2s ease, background-color 0.2s ease;
143
+ }
144
+
145
+ input[type="email"]:focus,
146
+ input[type="password"]:focus {
147
+ outline: none;
148
+ border-color: var(--input-focus-border);
149
+ box-shadow: 0 0 0 2px rgba(13, 110, 253, 0.25);
150
  }
151
 
152
+ .form-switch {
153
+ margin-top: 15px;
154
+ font-size: 0.9em;
155
+ color: var(--text-color-secondary);
156
+ }
157
+
158
+ /* --- Buttons --- */
159
+ .btn {
160
+ display: inline-block;
161
+ padding: 10px 18px;
162
  border: none;
163
+ border-radius: var(--border-radius);
164
  cursor: pointer;
165
  font-size: 1em;
166
+ font-weight: 500;
167
+ text-align: center;
168
+ transition: background-color 0.2s ease;
169
+ color: var(--button-text-color); /* Common text color */
170
  }
171
 
172
+ .btn-primary {
173
+ background-color: var(--button-primary-bg);
174
+ }
175
+ .btn-primary:hover {
176
+ background-color: var(--button-primary-hover-bg);
177
  }
178
 
179
+ .btn-danger {
180
+ background-color: var(--button-danger-bg);
181
  }
182
+ .btn-danger:hover {
183
+ background-color: var(--button-danger-hover-bg);
184
  }
185
 
186
+ #theme-toggle {
187
+ padding: 8px 12px;
188
+ font-size: 0.9em;
189
+ background-color: #6c757d; /* Secondary button color */
190
+ }
191
+ #theme-toggle:hover {
192
+ background-color: #5c636a;
193
+ }
194
+
195
+
196
+ /* --- Status Messages --- */
197
  .status-message {
198
  margin-top: 15px;
199
+ padding: 10px;
200
+ border-radius: var(--border-radius);
201
+ font-weight: 500;
202
+ display: none; /* Hidden by default */
203
+ border: 1px solid transparent;
204
  }
205
  .status-message.success {
206
+ color: var(--status-success-color);
207
+ background-color: rgba(25, 135, 84, 0.1); /* Light green background */
208
+ border-color: rgba(25, 135, 84, 0.2);
209
  }
210
+ .status-message:not(.success) { /* Default to error style */
211
+ color: var(--status-error-color);
212
+ background-color: rgba(220, 53, 69, 0.1); /* Light red background */
213
+ border-color: rgba(220, 53, 69, 0.2);
214
  }
215
 
216
+
217
+ /* --- Notifications --- */
218
  #notifications {
219
  margin-top: 15px;
220
  padding: 15px;
221
+ border: 1px solid var(--notification-border);
222
+ border-radius: var(--border-radius);
223
+ background-color: var(--notification-bg);
224
  min-height: 100px;
225
  max-height: 300px;
226
  overflow-y: auto;
227
+ transition: background-color 0.3s ease, border-color 0.3s ease;
228
  }
229
 
230
  #notifications p {
231
+ margin: 0 0 8px 0; /* Space below each notification */
232
+ padding: 8px 0;
233
+ border-bottom: 1px solid var(--border-color);
234
+ font-size: 0.95em;
235
  }
236
  #notifications p:last-child {
237
  border-bottom: none;
238
+ margin-bottom: 0;
239
+ }
240
+ #notifications p em { /* Style the placeholder */
241
+ color: var(--text-color-secondary);
242
+ font-style: italic;
243
  }
static/js/script.js CHANGED
@@ -1,226 +1,164 @@
 
1
  const registerSection = document.getElementById('register-section');
2
  const loginSection = document.getElementById('login-section');
3
  const welcomeSection = document.getElementById('welcome-section');
4
- const registerForm = document.getElementById('register-form');
5
- const loginForm = document.getElementById('login-form');
6
- const registerStatus = document.getElementById('register-status');
7
- const loginStatus = document.getElementById('login-status');
8
- const welcomeMessage = document.getElementById('welcome-message');
9
- const logoutButton = document.getElementById('logout-button');
10
  const notificationsDiv = document.getElementById('notifications');
11
-
12
  const API_URL = '/api'; // Use relative path
 
13
 
 
14
  let webSocket = null;
15
  let authToken = localStorage.getItem('authToken'); // Load token on script start
16
 
17
- // --- UI Control ---
18
  function showSection(sectionId) {
19
  registerSection.style.display = 'none';
20
  loginSection.style.display = 'none';
21
  welcomeSection.style.display = 'none';
22
-
23
  const sectionToShow = document.getElementById(sectionId);
24
- if (sectionToShow) {
25
- sectionToShow.style.display = 'block';
26
- } else {
27
- loginSection.style.display = 'block'; // Default to login
28
- }
29
  }
30
-
31
  function setStatus(element, message, isSuccess = false) {
32
  element.textContent = message;
33
  element.className = isSuccess ? 'status-message success' : 'status-message';
34
  element.style.display = message ? 'block' : 'none';
35
  }
36
 
37
- // --- API Calls ---
38
  async function apiRequest(endpoint, method = 'GET', body = null, token = null) {
39
  const headers = { 'Content-Type': 'application/json' };
40
- if (token) {
41
- headers['Authorization'] = `Bearer ${token}`;
42
- }
43
-
44
- const options = {
45
- method: method,
46
- headers: headers,
47
- };
48
-
49
- if (body && method !== 'GET') {
50
- options.body = JSON.stringify(body);
51
- }
52
-
53
  try {
54
  const response = await fetch(`${API_URL}${endpoint}`, options);
55
- const data = await response.json(); // Try to parse JSON regardless of status
56
-
57
  if (!response.ok) {
58
- // Use detail from JSON if available, otherwise status text
59
  const errorMessage = data?.detail || response.statusText || `HTTP error ${response.status}`;
60
- console.error("API Error:", errorMessage);
61
- throw new Error(errorMessage);
62
- }
63
- return data;
64
- } catch (error) {
65
- console.error(`Error during API request to ${endpoint}:`, error);
66
- throw error; // Re-throw to be caught by caller
67
- }
68
  }
69
 
70
 
71
- // --- WebSocket Handling ---
72
  function connectWebSocket(token) {
73
- if (!token) {
74
- console.error("No token available for WebSocket connection.");
75
- return;
76
- }
77
- if (webSocket && webSocket.readyState === WebSocket.OPEN) {
78
- console.log("WebSocket already connected.");
79
- return;
80
- }
81
-
82
- // Construct WebSocket URL dynamically (replace http/https with ws/wss)
83
  const wsProtocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
84
  const wsUrl = `${wsProtocol}//${window.location.host}${API_URL}/ws/${token}`;
85
- console.log("Attempting to connect WebSocket:", wsUrl);
86
-
87
-
88
  webSocket = new WebSocket(wsUrl);
89
-
90
  webSocket.onopen = (event) => {
91
- console.log("WebSocket connection opened:", event);
92
- // Clear placeholder message on successful connect
93
- if (notificationsDiv.querySelector('p em')) {
94
- notificationsDiv.innerHTML = '';
95
- }
96
  };
97
-
98
  webSocket.onmessage = (event) => {
99
- console.log("WebSocket message received:", event.data);
100
  try {
101
  const messageData = JSON.parse(event.data);
102
  if (messageData.type === 'new_user' && messageData.message) {
103
  const p = document.createElement('p');
104
  p.textContent = messageData.message;
105
- // Add message to the top
106
  notificationsDiv.insertBefore(p, notificationsDiv.firstChild);
107
- // Limit number of messages shown (optional)
108
- while (notificationsDiv.children.length > 10) {
109
- notificationsDiv.removeChild(notificationsDiv.lastChild);
110
- }
111
-
112
  }
113
- } catch (error) {
114
- console.error("Error parsing WebSocket message:", error);
115
- }
116
- };
117
-
118
- webSocket.onerror = (event) => {
119
- console.error("WebSocket error:", event);
120
- const p = document.createElement('p');
121
- p.textContent = `WebSocket error occurred. Notifications may stop.`;
122
- p.style.color = 'red';
123
- notificationsDiv.insertBefore(p, notificationsDiv.firstChild);
124
  };
125
-
126
  webSocket.onclose = (event) => {
127
- console.log("WebSocket connection closed:", event);
128
- webSocket = null; // Reset WebSocket variable
129
- // Optionally try to reconnect or inform user
130
- // Do not clear notifications on close, user might want to see history
131
- if (!event.wasClean) {
132
- const p = document.createElement('p');
133
- p.textContent = `WebSocket disconnected unexpectedly (Code: ${event.code}). Please refresh or log out/in.`;
134
- p.style.color = 'orange';
135
- notificationsDiv.insertBefore(p, notificationsDiv.firstChild);
136
- }
137
  };
138
  }
139
-
140
  function disconnectWebSocket() {
141
- if (webSocket) {
142
- console.log("Closing WebSocket connection.");
143
- webSocket.close();
144
- webSocket = null;
145
- }
146
  }
 
 
 
 
 
 
 
 
147
 
148
- // --- Authentication Logic ---
149
  async function handleLogin(email, password) {
150
  setStatus(loginStatus, "Logging in...");
151
  try {
152
  const data = await apiRequest('/login', 'POST', { email, password });
153
  if (data.access_token) {
154
  authToken = data.access_token;
155
- localStorage.setItem('authToken', authToken); // Store token
156
- setStatus(loginStatus, ""); // Clear status
157
- await showWelcomePage(); // Fetch user info and show welcome
158
- } else {
159
- throw new Error("Login failed: No token received.");
160
- }
161
- } catch (error) {
162
- setStatus(loginStatus, `Login failed: ${error.message}`);
163
- }
164
  }
165
-
166
  async function handleRegister(email, password) {
167
  setStatus(registerStatus, "Registering...");
168
  try {
169
  const data = await apiRequest('/register', 'POST', { email, password });
170
  setStatus(registerStatus, `Registration successful for ${data.email}! Please log in.`, true);
171
- registerForm.reset(); // Clear form
172
- showSection('login-section'); // Switch to login
173
- } catch (error) {
174
- setStatus(registerStatus, `Registration failed: ${error.message}`);
175
- }
176
  }
177
-
178
  async function showWelcomePage() {
179
- if (!authToken) {
180
- showSection('login-section');
181
- return;
182
- }
183
  try {
184
- // Fetch user details using the token
185
  const user = await apiRequest('/users/me', 'GET', null, authToken);
186
  welcomeMessage.textContent = `Welcome, ${user.email}!`;
187
  showSection('welcome-section');
188
- connectWebSocket(authToken); // Connect WS after successful login and user fetch
189
- } catch (error) {
190
- // If fetching user fails (e.g., invalid/expired token), logout
191
- console.error("Failed to fetch user details:", error);
192
- handleLogout();
193
- }
194
  }
195
-
196
  function handleLogout() {
197
  authToken = null;
198
  localStorage.removeItem('authToken');
199
  disconnectWebSocket();
200
- setStatus(loginStatus, ""); // Clear any previous login errors
 
 
201
  showSection('login-section');
202
  }
203
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
204
 
205
  // --- Event Listeners ---
206
  registerForm.addEventListener('submit', (e) => {
207
  e.preventDefault();
 
208
  const email = document.getElementById('reg-email').value;
209
  const password = document.getElementById('reg-password').value;
210
  const confirmPassword = document.getElementById('reg-confirm-password').value;
211
- if (password !== confirmPassword) {
212
- setStatus(registerStatus, "Passwords do not match.");
213
- return;
214
- }
215
- if (password.length < 8) {
216
- setStatus(registerStatus, "Password must be at least 8 characters.");
217
- return;
218
- }
219
  handleRegister(email, password);
220
  });
221
 
222
  loginForm.addEventListener('submit', (e) => {
223
  e.preventDefault();
 
224
  const email = document.getElementById('login-email').value;
225
  const password = document.getElementById('login-password').value;
226
  handleLogin(email, password);
@@ -228,14 +166,32 @@ loginForm.addEventListener('submit', (e) => {
228
 
229
  logoutButton.addEventListener('click', handleLogout);
230
 
 
 
231
 
232
  // --- Initial Page Load Logic ---
233
  document.addEventListener('DOMContentLoaded', () => {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
234
  if (authToken) {
235
- console.log("Token found, attempting to show welcome page.");
236
- showWelcomePage(); // Try to fetch user info and show welcome
237
  } else {
238
  console.log("No token found, showing login page.");
239
- showSection('login-section'); // Show login if no token
240
  }
241
  });
 
1
+ // --- Existing Constants (keep) ---
2
  const registerSection = document.getElementById('register-section');
3
  const loginSection = document.getElementById('login-section');
4
  const welcomeSection = document.getElementById('welcome-section');
5
+ // ... other element constants ...
 
 
 
 
 
6
  const notificationsDiv = document.getElementById('notifications');
 
7
  const API_URL = '/api'; // Use relative path
8
+ const themeToggleButton = document.getElementById('theme-toggle'); // Get toggle button
9
 
10
+ // --- Existing State Variables (keep) ---
11
  let webSocket = null;
12
  let authToken = localStorage.getItem('authToken'); // Load token on script start
13
 
14
+ // --- UI Control (showSection, setStatus - keep) ---
15
  function showSection(sectionId) {
16
  registerSection.style.display = 'none';
17
  loginSection.style.display = 'none';
18
  welcomeSection.style.display = 'none';
 
19
  const sectionToShow = document.getElementById(sectionId);
20
+ if (sectionToShow) { sectionToShow.style.display = 'block'; }
21
+ else { loginSection.style.display = 'block'; }
 
 
 
22
  }
 
23
  function setStatus(element, message, isSuccess = false) {
24
  element.textContent = message;
25
  element.className = isSuccess ? 'status-message success' : 'status-message';
26
  element.style.display = message ? 'block' : 'none';
27
  }
28
 
29
+ // --- API Calls (apiRequest - keep) ---
30
  async function apiRequest(endpoint, method = 'GET', body = null, token = null) {
31
  const headers = { 'Content-Type': 'application/json' };
32
+ if (token) { headers['Authorization'] = `Bearer ${token}`; }
33
+ const options = { method: method, headers: headers };
34
+ if (body && method !== 'GET') { options.body = JSON.stringify(body); }
 
 
 
 
 
 
 
 
 
 
35
  try {
36
  const response = await fetch(`${API_URL}${endpoint}`, options);
37
+ const data = await response.json();
 
38
  if (!response.ok) {
 
39
  const errorMessage = data?.detail || response.statusText || `HTTP error ${response.status}`;
40
+ console.error("API Error:", errorMessage); throw new Error(errorMessage);
41
+ } return data;
42
+ } catch (error) { console.error(`API Error to ${endpoint}:`, error); throw error; }
 
 
 
 
 
43
  }
44
 
45
 
46
+ // --- WebSocket Handling (connect/disconnectWebSocket - keep) ---
47
  function connectWebSocket(token) {
48
+ if (!token) { console.error("No token for WebSocket."); return; }
49
+ if (webSocket && webSocket.readyState === WebSocket.OPEN) { console.log("WS already open."); return; }
 
 
 
 
 
 
 
 
50
  const wsProtocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
51
  const wsUrl = `${wsProtocol}//${window.location.host}${API_URL}/ws/${token}`;
52
+ console.log("Connecting WebSocket:", wsUrl);
 
 
53
  webSocket = new WebSocket(wsUrl);
 
54
  webSocket.onopen = (event) => {
55
+ console.log("WS opened:", event);
56
+ if (notificationsDiv.querySelector('p em')) { notificationsDiv.innerHTML = ''; }
 
 
 
57
  };
 
58
  webSocket.onmessage = (event) => {
59
+ console.log("WS message received:", event.data);
60
  try {
61
  const messageData = JSON.parse(event.data);
62
  if (messageData.type === 'new_user' && messageData.message) {
63
  const p = document.createElement('p');
64
  p.textContent = messageData.message;
 
65
  notificationsDiv.insertBefore(p, notificationsDiv.firstChild);
66
+ while (notificationsDiv.children.length > 10) { notificationsDiv.removeChild(notificationsDiv.lastChild); }
 
 
 
 
67
  }
68
+ } catch (error) { console.error("WS message parse error:", error); }
 
 
 
 
 
 
 
 
 
 
69
  };
70
+ webSocket.onerror = (event) => { console.error("WS error:", event); displayNotificationError("WebSocket error occurred."); };
71
  webSocket.onclose = (event) => {
72
+ console.log("WS closed:", event);
73
+ if (!event.wasClean) { displayNotificationError(`WebSocket disconnected (Code: ${event.code}). Refresh maybe needed.`); }
74
+ webSocket = null;
 
 
 
 
 
 
 
75
  };
76
  }
 
77
  function disconnectWebSocket() {
78
+ if (webSocket) { console.log("Closing WS."); webSocket.close(); webSocket = null; }
 
 
 
 
79
  }
80
+ function displayNotificationError(message) { // Helper for WS errors
81
+ const p = document.createElement('p');
82
+ p.textContent = message;
83
+ p.style.color = 'orange';
84
+ p.style.fontWeight = 'bold';
85
+ notificationsDiv.insertBefore(p, notificationsDiv.firstChild);
86
+ }
87
+
88
 
89
+ // --- Authentication Logic (handleLogin, handleRegister, showWelcomePage, handleLogout - keep) ---
90
  async function handleLogin(email, password) {
91
  setStatus(loginStatus, "Logging in...");
92
  try {
93
  const data = await apiRequest('/login', 'POST', { email, password });
94
  if (data.access_token) {
95
  authToken = data.access_token;
96
+ localStorage.setItem('authToken', authToken);
97
+ setStatus(loginStatus, "");
98
+ await showWelcomePage();
99
+ } else { throw new Error("No token received."); }
100
+ } catch (error) { setStatus(loginStatus, `Login failed: ${error.message}`); }
 
 
 
 
101
  }
 
102
  async function handleRegister(email, password) {
103
  setStatus(registerStatus, "Registering...");
104
  try {
105
  const data = await apiRequest('/register', 'POST', { email, password });
106
  setStatus(registerStatus, `Registration successful for ${data.email}! Please log in.`, true);
107
+ registerForm.reset();
108
+ showSection('login-section');
109
+ } catch (error) { setStatus(registerStatus, `Registration failed: ${error.message}`); }
 
 
110
  }
 
111
  async function showWelcomePage() {
112
+ if (!authToken) { showSection('login-section'); return; }
 
 
 
113
  try {
 
114
  const user = await apiRequest('/users/me', 'GET', null, authToken);
115
  welcomeMessage.textContent = `Welcome, ${user.email}!`;
116
  showSection('welcome-section');
117
+ connectWebSocket(authToken);
118
+ } catch (error) { console.error("Failed to fetch user:", error); handleLogout(); }
 
 
 
 
119
  }
 
120
  function handleLogout() {
121
  authToken = null;
122
  localStorage.removeItem('authToken');
123
  disconnectWebSocket();
124
+ setStatus(loginStatus, "");
125
+ // Reset notification display on logout
126
+ notificationsDiv.innerHTML = '<p><em>Notifications will appear here...</em></p>';
127
  showSection('login-section');
128
  }
129
 
130
+ // --- Theme Toggling ---
131
+ function applyTheme(theme) {
132
+ if (theme === 'dark') {
133
+ document.body.classList.add('dark-mode');
134
+ } else {
135
+ document.body.classList.remove('dark-mode');
136
+ }
137
+ }
138
+
139
+ function toggleTheme() {
140
+ const isDarkMode = document.body.classList.toggle('dark-mode');
141
+ const newTheme = isDarkMode ? 'dark' : 'light';
142
+ localStorage.setItem('theme', newTheme); // Save preference
143
+ console.log("Theme toggled to:", newTheme);
144
+ }
145
+
146
 
147
  // --- Event Listeners ---
148
  registerForm.addEventListener('submit', (e) => {
149
  e.preventDefault();
150
+ // ... (validation and call handleRegister - keep) ...
151
  const email = document.getElementById('reg-email').value;
152
  const password = document.getElementById('reg-password').value;
153
  const confirmPassword = document.getElementById('reg-confirm-password').value;
154
+ if (password !== confirmPassword) { setStatus(registerStatus, "Passwords do not match."); return; }
155
+ if (password.length < 8) { setStatus(registerStatus, "Password must be >= 8 characters."); return; }
 
 
 
 
 
 
156
  handleRegister(email, password);
157
  });
158
 
159
  loginForm.addEventListener('submit', (e) => {
160
  e.preventDefault();
161
+ // ... (call handleLogin - keep) ...
162
  const email = document.getElementById('login-email').value;
163
  const password = document.getElementById('login-password').value;
164
  handleLogin(email, password);
 
166
 
167
  logoutButton.addEventListener('click', handleLogout);
168
 
169
+ themeToggleButton.addEventListener('click', toggleTheme); // Add listener
170
+
171
 
172
  // --- Initial Page Load Logic ---
173
  document.addEventListener('DOMContentLoaded', () => {
174
+ // Apply stored theme preference first
175
+ const storedTheme = localStorage.getItem('theme');
176
+ if (storedTheme) {
177
+ applyTheme(storedTheme);
178
+ console.log("Applied stored theme:", storedTheme);
179
+ } else {
180
+ // Optional: Detect system preference if no theme stored
181
+ // const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
182
+ // applyTheme(prefersDark ? 'dark' : 'light');
183
+ // console.log("Applied system theme preference.");
184
+ applyTheme('light'); // Default to light if nothing else
185
+ console.log("Applied default light theme.");
186
+ }
187
+
188
+
189
+ // Then check auth token
190
  if (authToken) {
191
+ console.log("Token found, showing welcome page.");
192
+ showWelcomePage();
193
  } else {
194
  console.log("No token found, showing login page.");
195
+ showSection('login-section');
196
  }
197
  });
templates/index.html CHANGED
@@ -7,49 +7,64 @@
7
  <link rel="stylesheet" href="/static/css/style.css">
8
  </head>
9
  <body>
10
- <h1>Auth & Notification App</h1>
 
 
 
 
11
 
12
- <!-- Registration Section -->
13
- <div id="register-section" class="auth-section">
14
- <h2>Register</h2>
15
- <form id="register-form">
16
- <label for="reg-email">Email:</label>
17
- <input type="email" id="reg-email" name="email" required><br><br>
18
- <label for="reg-password">Password:</label>
19
- <input type="password" id="reg-password" name="password" required minlength="8"><br><br>
20
- <label for="reg-confirm-password">Confirm Password:</label>
21
- <input type="password" id="reg-confirm-password" name="confirm_password" required minlength="8"><br><br>
22
- <button type="submit">Register</button>
23
- </form>
24
- <p id="register-status" class="status-message"></p>
25
- <p>Already have an account? <a href="#" onclick="showSection('login-section')">Login here</a></p>
26
- </div>
 
 
 
 
 
 
27
 
28
- <!-- Login Section -->
29
- <div id="login-section" class="auth-section" style="display: none;">
30
- <h2>Login</h2>
31
- <form id="login-form">
32
- <label for="login-email">Email:</label>
33
- <input type="email" id="login-email" name="email" required><br><br>
34
- <label for="login-password">Password:</label>
35
- <input type="password" id="login-password" name="password" required><br><br>
36
- <button type="submit">Login</button>
37
- </form>
38
- <p id="login-status" class="status-message"></p>
39
- <p>Don't have an account? <a href="#" onclick="showSection('register-section')">Register here</a></p>
40
- </div>
 
 
 
 
41
 
42
- <!-- Welcome Section (shown after login) -->
43
- <div id="welcome-section" style="display: none;">
44
- <h2>Welcome!</h2>
45
- <p id="welcome-message">Welcome, user!</p>
46
- <button id="logout-button">Logout</button>
47
- <hr>
48
- <h2>Real-time Notifications</h2>
49
- <div id="notifications">
50
- <p><em>Notifications will appear here...</em></p>
 
51
  </div>
52
- </div>
53
 
54
  <script src="/static/js/script.js"></script>
55
  </body>
 
7
  <link rel="stylesheet" href="/static/css/style.css">
8
  </head>
9
  <body>
10
+ <div class="container">
11
+ <div class="header">
12
+ <h1>Auth & Notification App</h1>
13
+ <button id="theme-toggle">Toggle Theme</button>
14
+ </div>
15
 
16
+ <!-- Registration Section -->
17
+ <div id="register-section" class="content-section auth-section">
18
+ <h2>Register</h2>
19
+ <form id="register-form">
20
+ <div class="form-group">
21
+ <label for="reg-email">Email:</label>
22
+ <input type="email" id="reg-email" name="email" required>
23
+ </div>
24
+ <div class="form-group">
25
+ <label for="reg-password">Password:</label>
26
+ <input type="password" id="reg-password" name="password" required minlength="8">
27
+ </div>
28
+ <div class="form-group">
29
+ <label for="reg-confirm-password">Confirm Password:</label>
30
+ <input type="password" id="reg-confirm-password" name="confirm_password" required minlength="8">
31
+ </div>
32
+ <button type="submit" class="btn btn-primary">Register</button>
33
+ </form>
34
+ <p id="register-status" class="status-message"></p>
35
+ <p class="form-switch">Already have an account? <a href="#" onclick="showSection('login-section')">Login here</a></p>
36
+ </div>
37
 
38
+ <!-- Login Section -->
39
+ <div id="login-section" class="content-section auth-section" style="display: none;">
40
+ <h2>Login</h2>
41
+ <form id="login-form">
42
+ <div class="form-group">
43
+ <label for="login-email">Email:</label>
44
+ <input type="email" id="login-email" name="email" required>
45
+ </div>
46
+ <div class="form-group">
47
+ <label for="login-password">Password:</label>
48
+ <input type="password" id="login-password" name="password" required>
49
+ </div>
50
+ <button type="submit" class="btn btn-primary">Login</button>
51
+ </form>
52
+ <p id="login-status" class="status-message"></p>
53
+ <p class="form-switch">Don't have an account? <a href="#" onclick="showSection('register-section')">Register here</a></p>
54
+ </div>
55
 
56
+ <!-- Welcome Section (shown after login) -->
57
+ <div id="welcome-section" class="content-section" style="display: none;">
58
+ <h2>Welcome!</h2>
59
+ <p id="welcome-message">Welcome, user!</p>
60
+ <button id="logout-button" class="btn btn-danger">Logout</button>
61
+ <hr>
62
+ <h2>Real-time Notifications</h2>
63
+ <div id="notifications">
64
+ <p><em>Notifications will appear here...</em></p>
65
+ </div>
66
  </div>
67
+ </div><!-- end container -->
68
 
69
  <script src="/static/js/script.js"></script>
70
  </body>