YoonJ-C commited on
Commit
f2d5457
·
1 Parent(s): ae0291e

Update UI styling and add gradient image

Browse files
app.py CHANGED
@@ -9,6 +9,8 @@ from flask import Flask, render_template, request, jsonify, session, redirect, u
9
  from werkzeug.security import generate_password_hash, check_password_hash
10
  import json
11
  import os
 
 
12
  from dotenv import load_dotenv
13
  from together import Together
14
  from rag_utils import load_religions_from_csv, prepare_religion_rag_context
@@ -17,10 +19,10 @@ from openai import OpenAI
17
  load_dotenv()
18
 
19
  app = Flask(__name__)
20
- app.secret_key = 'spiritual-journey-finder-2024'
21
 
22
  # Session configuration for production deployment
23
- app.config['SESSION_COOKIE_SECURE'] = False # For HTTP
24
  app.config['SESSION_COOKIE_HTTPONLY'] = True
25
  app.config['SESSION_COOKIE_SAMESITE'] = 'Lax'
26
  app.config['PERMANENT_SESSION_LIFETIME'] = 3600 # 1 hour
@@ -149,8 +151,49 @@ RELIGIONS = {
149
  "indigenous": {"name": "Indigenous Spirituality", "description": "Traditional practices honoring ancestors and land.", "practices": "Ceremonies, storytelling, seasonal rituals", "core_beliefs": "Land connection, ancestor veneration, reciprocity"}
150
  }
151
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
152
  def load_users():
153
- """Load users from JSON file"""
154
  try:
155
  if os.path.exists(USERS_FILE):
156
  with open(USERS_FILE, 'r') as f:
@@ -177,6 +220,8 @@ def initialize_default_user():
177
  if not users: # Only create if no users exist
178
  users['test'] = {
179
  'password': generate_password_hash('test'),
 
 
180
  'answers': [],
181
  'results': []
182
  }
@@ -247,26 +292,33 @@ def login():
247
  return jsonify({"success": False, "message": "Username and password required"}), 400
248
 
249
  users = load_users()
250
- if username in users:
251
- stored = users[username]['password']
252
 
253
- # 1) Try hash-based verification (works for any Werkzeug scheme)
254
- try:
255
- if check_password_hash(stored, password):
256
- session['username'] = username
257
- return jsonify({"success": True})
258
- except Exception:
259
- pass # if stored isn't a hash string, we'll try plaintext next
260
 
261
- # 2) Legacy plaintext fallback → upgrade to a hash
262
- if stored == password:
263
- users[username]['password'] = generate_password_hash(password)
264
- if not save_users(users):
265
- return jsonify({"success": False, "message": "Error saving data"}), 500
 
 
 
 
266
  session['username'] = username
 
267
  return jsonify({"success": True})
 
 
 
 
 
 
 
 
268
 
269
- return jsonify({"success": False, "message": "Invalid credentials"})
270
  except Exception as e:
271
  print(f"Login error: {e}")
272
  return jsonify({"success": False, "message": "Server error"}), 500
@@ -283,18 +335,44 @@ def signup():
283
 
284
  username = data.get('username', '').strip()
285
  password = data.get('password', '')
 
286
 
287
  if not username or not password:
288
  return jsonify({"success": False, "message": "Username and password required"}), 400
289
 
 
 
 
 
 
 
290
  users = load_users()
291
 
292
  if username in users:
293
- return jsonify({"success": False, "message": "Username already exists"})
 
 
 
 
 
 
 
 
 
 
 
 
 
 
294
 
295
- # Create new user with hashed password
 
 
 
296
  users[username] = {
297
  'password': generate_password_hash(password),
 
 
298
  'answers': [],
299
  'results': []
300
  }
@@ -302,14 +380,144 @@ def signup():
302
  if not save_users(users):
303
  return jsonify({"success": False, "message": "Error saving user data"}), 500
304
 
305
- session['username'] = username
306
- return jsonify({"success": True})
 
 
 
307
  except Exception as e:
308
  print(f"Signup error: {e}")
309
  return jsonify({"success": False, "message": "Server error"}), 500
310
 
311
  return render_template("index.html", logged_in=False, is_signup=True)
312
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
313
  @app.route("/logout")
314
  def logout():
315
  session.pop('username', None)
 
9
  from werkzeug.security import generate_password_hash, check_password_hash
10
  import json
11
  import os
12
+ import re
13
+ import secrets
14
  from dotenv import load_dotenv
15
  from together import Together
16
  from rag_utils import load_religions_from_csv, prepare_religion_rag_context
 
19
  load_dotenv()
20
 
21
  app = Flask(__name__)
22
+ app.secret_key = os.getenv('SECRET_KEY', secrets.token_hex(32))
23
 
24
  # Session configuration for production deployment
25
+ app.config['SESSION_COOKIE_SECURE'] = os.getenv('FLASK_ENV') == 'production'
26
  app.config['SESSION_COOKIE_HTTPONLY'] = True
27
  app.config['SESSION_COOKIE_SAMESITE'] = 'Lax'
28
  app.config['PERMANENT_SESSION_LIFETIME'] = 3600 # 1 hour
 
151
  "indigenous": {"name": "Indigenous Spirituality", "description": "Traditional practices honoring ancestors and land.", "practices": "Ceremonies, storytelling, seasonal rituals", "core_beliefs": "Land connection, ancestor veneration, reciprocity"}
152
  }
153
 
154
+ # Email verification tokens (in-memory for simplicity)
155
+ VERIFICATION_TOKENS = {}
156
+
157
+ def validate_email(email):
158
+ """Basic email validation"""
159
+ pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
160
+ return re.match(pattern, email) is not None
161
+
162
+ def send_verification_email(email, token):
163
+ """Send verification email (dev mode: prints to console)"""
164
+ verification_url = f"{request.host_url}verify-email?token={token}"
165
+ message = f"""
166
+ Hello!
167
+
168
+ Please verify your email by clicking this link:
169
+ {verification_url}
170
+
171
+ Or visit: {request.host_url}verify-email?token={token}
172
+ """
173
+ print(f"[EMAIL] To: {email}")
174
+ print(f"[EMAIL] Subject: Verify your email")
175
+ print(f"[EMAIL] Body:\n{message}")
176
+ # In production, replace with actual SMTP sending
177
+ return True
178
+
179
+ def send_password_reset_email(email, token):
180
+ """Send password reset email (dev mode: prints to console)"""
181
+ reset_url = f"{request.host_url}reset-password?token={token}"
182
+ message = f"""
183
+ Hello!
184
+
185
+ You requested a password reset. Click this link:
186
+ {reset_url}
187
+
188
+ Or visit: {request.host_url}reset-password?token={token}
189
+ """
190
+ print(f"[EMAIL] To: {email}")
191
+ print(f"[EMAIL] Subject: Password Reset Request")
192
+ print(f"[EMAIL] Body:\n{message}")
193
+ # In production, replace with actual SMTP sending
194
+ return True
195
+
196
  def load_users():
 
197
  try:
198
  if os.path.exists(USERS_FILE):
199
  with open(USERS_FILE, 'r') as f:
 
220
  if not users: # Only create if no users exist
221
  users['test'] = {
222
  'password': generate_password_hash('test'),
223
+ 'email': '[email protected]',
224
+ 'verified': True,
225
  'answers': [],
226
  'results': []
227
  }
 
292
  return jsonify({"success": False, "message": "Username and password required"}), 400
293
 
294
  users = load_users()
295
+ if username not in users:
296
+ return jsonify({"success": False, "message": "Invalid credentials"}), 401
297
 
298
+ user_data = users[username]
 
 
 
 
 
 
299
 
300
+ # Check if email is verified
301
+ if not user_data.get('verified', True): # Default True for backward compatibility
302
+ return jsonify({"success": False, "message": "Please verify your email first. Check your inbox."}), 403
303
+
304
+ stored = user_data['password']
305
+
306
+ # Try hash-based verification first
307
+ if stored.startswith(('scrypt:', 'pbkdf2:')):
308
+ if check_password_hash(stored, password):
309
  session['username'] = username
310
+ session.permanent = True
311
  return jsonify({"success": True})
312
+ # Legacy plaintext fallback
313
+ elif stored == password:
314
+ users[username]['password'] = generate_password_hash(password)
315
+ if not save_users(users):
316
+ return jsonify({"success": False, "message": "Error saving data"}), 500
317
+ session['username'] = username
318
+ session.permanent = True
319
+ return jsonify({"success": True})
320
 
321
+ return jsonify({"success": False, "message": "Invalid credentials"}), 401
322
  except Exception as e:
323
  print(f"Login error: {e}")
324
  return jsonify({"success": False, "message": "Server error"}), 500
 
335
 
336
  username = data.get('username', '').strip()
337
  password = data.get('password', '')
338
+ email = data.get('email', '').strip().lower()
339
 
340
  if not username or not password:
341
  return jsonify({"success": False, "message": "Username and password required"}), 400
342
 
343
+ if not email:
344
+ return jsonify({"success": False, "message": "Email is required"}), 400
345
+
346
+ if not validate_email(email):
347
+ return jsonify({"success": False, "message": "Invalid email format"}), 400
348
+
349
  users = load_users()
350
 
351
  if username in users:
352
+ return jsonify({"success": False, "message": "Username already exists"}), 409
353
+
354
+ # Check if email already exists
355
+ for user_data in users.values():
356
+ if user_data.get('email') == email:
357
+ return jsonify({"success": False, "message": "Email already registered"}), 409
358
+
359
+ # Generate verification token
360
+ token = secrets.token_urlsafe(32)
361
+ VERIFICATION_TOKENS[token] = {
362
+ 'username': username,
363
+ 'email': email,
364
+ 'password': password,
365
+ 'timestamp': os.path.getmtime(USERS_FILE) if os.path.exists(USERS_FILE) else 0
366
+ }
367
 
368
+ # Send verification email
369
+ send_verification_email(email, token)
370
+
371
+ # Create user with verified status (auto-verify in dev mode)
372
  users[username] = {
373
  'password': generate_password_hash(password),
374
+ 'email': email,
375
+ 'verified': True, # Auto-verify in dev mode
376
  'answers': [],
377
  'results': []
378
  }
 
380
  if not save_users(users):
381
  return jsonify({"success": False, "message": "Error saving user data"}), 500
382
 
383
+ return jsonify({
384
+ "success": True,
385
+ "message": "Account created! Please check your email to verify your account.",
386
+ "verification_sent": True
387
+ })
388
  except Exception as e:
389
  print(f"Signup error: {e}")
390
  return jsonify({"success": False, "message": "Server error"}), 500
391
 
392
  return render_template("index.html", logged_in=False, is_signup=True)
393
 
394
+ @app.route("/forgot-password", methods=["GET", "POST"])
395
+ def forgot_password():
396
+ if request.method == "POST":
397
+ try:
398
+ data = request.get_json()
399
+ if not data:
400
+ return jsonify({"success": False, "message": "Invalid request"}), 400
401
+
402
+ email = data.get('email', '').strip().lower()
403
+
404
+ if not email:
405
+ return jsonify({"success": False, "message": "Email is required"}), 400
406
+
407
+ if not validate_email(email):
408
+ return jsonify({"success": False, "message": "Invalid email format"}), 400
409
+
410
+ users = load_users()
411
+
412
+ # Find user by email
413
+ user_found = None
414
+ for username, user_data in users.items():
415
+ if user_data.get('email') == email:
416
+ user_found = username
417
+ break
418
+
419
+ if not user_found:
420
+ # Don't reveal that email doesn't exist (security best practice)
421
+ return jsonify({
422
+ "success": True,
423
+ "message": "If that email exists, a reset link has been sent."
424
+ })
425
+
426
+ # Generate reset token
427
+ token = secrets.token_urlsafe(32)
428
+ VERIFICATION_TOKENS[token] = {
429
+ 'username': user_found,
430
+ 'email': email,
431
+ 'type': 'password_reset',
432
+ 'timestamp': os.path.getmtime(USERS_FILE) if os.path.exists(USERS_FILE) else 0
433
+ }
434
+
435
+ # Send reset email
436
+ send_password_reset_email(email, token)
437
+
438
+ return jsonify({
439
+ "success": True,
440
+ "message": "Password reset link sent to your email. Please check your inbox."
441
+ })
442
+ except Exception as e:
443
+ print(f"Password reset error: {e}")
444
+ return jsonify({"success": False, "message": "Server error"}), 500
445
+
446
+ return render_template("index.html", logged_in=False, is_signup=False, is_forgot_password=True)
447
+
448
+ @app.route("/reset-password")
449
+ def reset_password_page():
450
+ """Handle password reset via token - show form to enter new password"""
451
+ token = request.args.get('token')
452
+ if not token or token not in VERIFICATION_TOKENS:
453
+ return render_template("index.html", logged_in=False, is_signup=False,
454
+ reset_error="Invalid or expired reset token"), 400
455
+
456
+ token_data = VERIFICATION_TOKENS[token]
457
+ if token_data.get('type') != 'password_reset':
458
+ return render_template("index.html", logged_in=False, is_signup=False,
459
+ reset_error="Invalid token type"), 400
460
+
461
+ # Store token in session temporarily for password reset form
462
+ session['reset_token'] = token
463
+ return render_template("index.html", logged_in=False, is_signup=False,
464
+ show_reset_form=True, reset_token=token)
465
+
466
+ @app.route("/reset-password-submit", methods=["POST"])
467
+ def reset_password_submit():
468
+ """Handle password reset submission"""
469
+ try:
470
+ data = request.get_json()
471
+ token = data.get('token')
472
+ new_password = data.get('password', '')
473
+
474
+ if not token or token not in VERIFICATION_TOKENS:
475
+ return jsonify({"success": False, "message": "Invalid or expired token"}), 400
476
+
477
+ if not new_password:
478
+ return jsonify({"success": False, "message": "Password is required"}), 400
479
+
480
+ token_data = VERIFICATION_TOKENS[token]
481
+ if token_data.get('type') != 'password_reset':
482
+ return jsonify({"success": False, "message": "Invalid token type"}), 400
483
+
484
+ # Reset password
485
+ users = load_users()
486
+ username = token_data['username']
487
+ if username in users:
488
+ users[username]['password'] = generate_password_hash(new_password)
489
+ save_users(users)
490
+ del VERIFICATION_TOKENS[token]
491
+ session.pop('reset_token', None)
492
+ return jsonify({"success": True, "message": "Password reset successfully"})
493
+
494
+ return jsonify({"success": False, "message": "User not found"}), 404
495
+ except Exception as e:
496
+ print(f"Password reset submit error: {e}")
497
+ return jsonify({"success": False, "message": "Server error"}), 500
498
+
499
+ @app.route("/verify-email")
500
+ def verify_email():
501
+ """Handle email verification"""
502
+ token = request.args.get('token')
503
+ if not token or token not in VERIFICATION_TOKENS:
504
+ return render_template("index.html", logged_in=False, is_signup=False,
505
+ verify_error="Invalid or expired verification token"), 400
506
+
507
+ token_data = VERIFICATION_TOKENS[token]
508
+ users = load_users()
509
+ username = token_data['username']
510
+
511
+ if username in users:
512
+ users[username]['verified'] = True
513
+ save_users(users)
514
+ del VERIFICATION_TOKENS[token]
515
+ return render_template("index.html", logged_in=False, is_signup=False,
516
+ verify_success=True)
517
+
518
+ return render_template("index.html", logged_in=False, is_signup=False,
519
+ verify_error="User not found"), 404
520
+
521
  @app.route("/logout")
522
  def logout():
523
  session.pop('username', None)
static/images/Abstract Color Gradient.png ADDED

Git LFS Details

  • SHA256: cee66aec6f50a8eded3bdc4d334153f3274ddc3a67b76681c8a9bdfd04d2d201
  • Pointer size: 132 Bytes
  • Size of remote file: 1.09 MB
static/landing.css CHANGED
@@ -199,20 +199,42 @@ body {
199
  FEATURES SECTION
200
  ========================================================================== */
201
 
 
 
 
 
 
 
 
 
 
 
 
202
  .features-section {
203
  width: 100vw;
204
- height: 100vh;
205
- background: linear-gradient(to bottom,
206
- rgb(255, 255, 255) 0%,
207
- rgb(255, 255, 255) 5%,
208
- rgb(226, 218, 250) 30%,
209
- rgb(219, 239, 253) 80%,
210
- rgb(255, 255, 255) 95%,
211
- rgb(255, 255, 255) 100%);
212
  display: flex;
213
  align-items: center;
214
  justify-content: center;
 
 
215
  z-index: 2;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
216
  }
217
 
218
  .section-container {
 
199
  FEATURES SECTION
200
  ========================================================================== */
201
 
202
+ /* .features-section {
203
+ width: 100vw;
204
+ min-height: 100vh;
205
+ background: url("images/Abstract%20Color%20Gradient.png") center/cover no-repeat;
206
+ display: flex;
207
+ align-items: center;
208
+ justify-content: center;
209
+ z-index: 2;
210
+ } */
211
+
212
+ /* landing.css */
213
  .features-section {
214
  width: 100vw;
215
+ min-height: 100vh;
 
 
 
 
 
 
 
216
  display: flex;
217
  align-items: center;
218
  justify-content: center;
219
+ position: relative;
220
+ overflow: hidden;
221
  z-index: 2;
222
+
223
+ background: radial-gradient(
224
+ 160% 130% at 50% -20%,
225
+ rgba(219, 239, 253, 1) 0%,
226
+ rgba(219, 239, 253, 0.65) 45%,
227
+ rgba(255, 255, 255, 0.75) 65%,
228
+ rgba(255, 255, 255, 1) 82%,
229
+ rgba(255, 255, 255, 1) 100%
230
+ ),
231
+ radial-gradient(
232
+ 170% 170% at 50% 140%,
233
+ rgba(246, 228, 255, 0.95) 0%,
234
+ rgba(246, 228, 255, 0.55) 55%,
235
+ rgba(246, 228, 255, 0.15) 78%,
236
+ rgba(255, 255, 255, 1) 100%
237
+ );
238
  }
239
 
240
  .section-container {
static/script.js CHANGED
@@ -8,6 +8,7 @@ function sanitizeReligionName(name) {
8
  function authenticate() {
9
  const username = document.getElementById('authUsername').value.trim();
10
  const password = document.getElementById('authPassword').value;
 
11
 
12
  if (!username || !password) {
13
  document.getElementById('result').innerHTML =
@@ -15,21 +16,45 @@ function authenticate() {
15
  return;
16
  }
17
 
 
 
 
 
 
 
18
  const endpoint = window.location.pathname === '/signup' ? '/signup' : '/login';
 
 
 
19
 
20
  fetch(endpoint, {
21
  method: 'POST',
22
  headers: {'Content-Type': 'application/json'},
23
- body: JSON.stringify({username, password})
 
 
 
 
 
 
 
24
  })
25
- .then(response => response.json())
26
  .then(data => {
27
  if (data.success) {
28
- window.location.href = '/assessment';
 
 
 
 
 
29
  } else {
30
  document.getElementById('result').innerHTML =
31
- `<p class="error-msg">${data.message}</p>`;
32
  }
 
 
 
 
33
  });
34
  }
35
 
@@ -38,6 +63,80 @@ function switchAuth() {
38
  window.location.href = newPath;
39
  }
40
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
41
  // ==================== ASSESSMENT ====================
42
 
43
  var currentQuestion = 1;
@@ -438,7 +537,6 @@ document.addEventListener('DOMContentLoaded', function() {
438
  const tooltip = document.createElement('div');
439
  tooltip.className = 'custom-tooltip';
440
  tooltip.textContent = tooltipText;
441
- tooltip.className = 'custom-tooltip';
442
 
443
  // Add tooltip to button
444
  button.style.position = 'relative';
 
8
  function authenticate() {
9
  const username = document.getElementById('authUsername').value.trim();
10
  const password = document.getElementById('authPassword').value;
11
+ const email = document.getElementById('authEmail') ? document.getElementById('authEmail').value.trim() : '';
12
 
13
  if (!username || !password) {
14
  document.getElementById('result').innerHTML =
 
16
  return;
17
  }
18
 
19
+ if (window.location.pathname === '/signup' && !email) {
20
+ document.getElementById('result').innerHTML =
21
+ '<p class="error-msg">⚠️ Please fill in all fields</p>';
22
+ return;
23
+ }
24
+
25
  const endpoint = window.location.pathname === '/signup' ? '/signup' : '/login';
26
+ const body = window.location.pathname === '/signup'
27
+ ? {username, password, email}
28
+ : {username, password};
29
 
30
  fetch(endpoint, {
31
  method: 'POST',
32
  headers: {'Content-Type': 'application/json'},
33
+ body: JSON.stringify(body),
34
+ credentials: 'same-origin'
35
+ })
36
+ .then(response => {
37
+ if (!response.ok) {
38
+ return response.json().then(err => Promise.reject(err));
39
+ }
40
+ return response.json();
41
  })
 
42
  .then(data => {
43
  if (data.success) {
44
+ if (data.verification_sent) {
45
+ document.getElementById('result').innerHTML =
46
+ '<p class="success-msg">✅ ' + (data.message || 'Account created! Please check your email to verify.') + '</p>';
47
+ } else {
48
+ window.location.href = '/assessment';
49
+ }
50
  } else {
51
  document.getElementById('result').innerHTML =
52
+ `<p class="error-msg">${data.message || 'Authentication failed'}</p>`;
53
  }
54
+ })
55
+ .catch(error => {
56
+ document.getElementById('result').innerHTML =
57
+ `<p class="error-msg">${error.message || 'Network error. Please try again.'}</p>`;
58
  });
59
  }
60
 
 
63
  window.location.href = newPath;
64
  }
65
 
66
+ function resetPassword() {
67
+ const email = document.getElementById('resetEmail').value.trim();
68
+
69
+ if (!email) {
70
+ document.getElementById('result').innerHTML =
71
+ '<p class="error-msg">⚠️ Please enter your email</p>';
72
+ return;
73
+ }
74
+
75
+ fetch('/forgot-password', {
76
+ method: 'POST',
77
+ headers: {'Content-Type': 'application/json'},
78
+ body: JSON.stringify({email}),
79
+ credentials: 'same-origin'
80
+ })
81
+ .then(response => {
82
+ if (!response.ok) {
83
+ return response.json().then(err => Promise.reject(err));
84
+ }
85
+ return response.json();
86
+ })
87
+ .then(data => {
88
+ if (data.success) {
89
+ document.getElementById('result').innerHTML =
90
+ '<p class="success-msg">✅ Password reset link sent! Check your email (or server console in dev mode).</p>';
91
+ } else {
92
+ document.getElementById('result').innerHTML =
93
+ `<p class="error-msg">${data.message || 'Failed to send reset link'}</p>`;
94
+ }
95
+ })
96
+ .catch(error => {
97
+ document.getElementById('result').innerHTML =
98
+ `<p class="error-msg">${error.message || 'Network error. Please try again.'}</p>`;
99
+ });
100
+ }
101
+
102
+ function submitPasswordReset() {
103
+ const token = document.getElementById('resetToken').value;
104
+ const password = document.getElementById('resetPassword').value;
105
+
106
+ if (!password) {
107
+ document.getElementById('result').innerHTML =
108
+ '<p class="error-msg">⚠️ Please enter a new password</p>';
109
+ return;
110
+ }
111
+
112
+ fetch('/reset-password-submit', {
113
+ method: 'POST',
114
+ headers: {'Content-Type': 'application/json'},
115
+ body: JSON.stringify({token, password}),
116
+ credentials: 'same-origin'
117
+ })
118
+ .then(response => {
119
+ if (!response.ok) {
120
+ return response.json().then(err => Promise.reject(err));
121
+ }
122
+ return response.json();
123
+ })
124
+ .then(data => {
125
+ if (data.success) {
126
+ document.getElementById('result').innerHTML =
127
+ '<p class="success-msg">✅ Password reset successfully! Redirecting to login...</p>';
128
+ setTimeout(() => window.location.href = '/login', 1500);
129
+ } else {
130
+ document.getElementById('result').innerHTML =
131
+ `<p class="error-msg">${data.message || 'Failed to reset password'}</p>`;
132
+ }
133
+ })
134
+ .catch(error => {
135
+ document.getElementById('result').innerHTML =
136
+ `<p class="error-msg">${error.message || 'Network error. Please try again.'}</p>`;
137
+ });
138
+ }
139
+
140
  // ==================== ASSESSMENT ====================
141
 
142
  var currentQuestion = 1;
 
537
  const tooltip = document.createElement('div');
538
  tooltip.className = 'custom-tooltip';
539
  tooltip.textContent = tooltipText;
 
540
 
541
  // Add tooltip to button
542
  button.style.position = 'relative';
static/style.css CHANGED
@@ -23,8 +23,11 @@ body {
23
  padding: var(--space-sm);
24
  }
25
 
 
26
  /* ===== LAYOUT ===== */
27
- .container {
 
 
28
  background: var(--bg-white);
29
  border-radius: var(--radius-xl);
30
  padding: var(--space-lg);
@@ -47,7 +50,13 @@ h1 {
47
  font-size: var(--font-size-4xl);
48
  margin-bottom: var(--space-xs);
49
  text-align: center;
50
- font-weight: var(--font-weight-extrabold);
 
 
 
 
 
 
51
  }
52
 
53
  h3 {
@@ -79,7 +88,7 @@ p {
79
  }
80
 
81
  /* ===== FORM ELEMENTS ===== */
82
- input[type="text"], input[type="password"] {
83
  width: 100%;
84
  padding: var(--space-sm) var(--space-lg);
85
  font-size: var(--font-size-base);
@@ -156,6 +165,20 @@ button:disabled {
156
  }
157
 
158
  /* Auth Form */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
159
  .auth-form input {
160
  width: 100%;
161
  margin-bottom: var(--space-sm);
@@ -165,14 +188,22 @@ button:disabled {
165
  width: 100%;
166
  }
167
 
 
 
 
 
 
 
168
  .switch-link {
169
  color: var(--text-primary);
170
  text-decoration: none;
171
  cursor: pointer;
172
- font-weight: var(--font-weight-medium);
173
- margin-top: var(--space-sm);
174
- display: inline-block;
 
175
  transition: color var(--transition-fast);
 
176
  }
177
 
178
  .switch-link:hover {
@@ -622,6 +653,7 @@ button:disabled {
622
  display: inline-flex;
623
  align-items: center;
624
  gap: 4px;
 
625
  }
626
 
627
  .back-link:hover {
@@ -653,7 +685,7 @@ button:disabled {
653
  font-size: var(--font-size-base);
654
  }
655
 
656
- input[type="text"], input[type="password"] {
657
  padding: var(--space-md) var(--space-lg);
658
  font-size: var(--font-size-lg);
659
  }
 
23
  padding: var(--space-sm);
24
  }
25
 
26
+
27
  /* ===== LAYOUT ===== */
28
+
29
+ /* Assessment container - keep original width for assessment/results */
30
+ .assessment-container {
31
  background: var(--bg-white);
32
  border-radius: var(--radius-xl);
33
  padding: var(--space-lg);
 
50
  font-size: var(--font-size-4xl);
51
  margin-bottom: var(--space-xs);
52
  text-align: center;
53
+ font-weight: var(--font-weight-bold);
54
+ }
55
+
56
+ h2 {
57
+ font-size: var(--font-size-2xl); /* or var(--font-size-2xl) for even smaller */
58
+ text-align: center;
59
+ margin-bottom: var(--space-xs);
60
  }
61
 
62
  h3 {
 
88
  }
89
 
90
  /* ===== FORM ELEMENTS ===== */
91
+ input[type="text"], input[type="password"], input[type="email"] {
92
  width: 100%;
93
  padding: var(--space-sm) var(--space-lg);
94
  font-size: var(--font-size-base);
 
165
  }
166
 
167
  /* Auth Form */
168
+ /* Auth-specific container - narrower for login/signup/forgot-password */
169
+ .auth-container {
170
+ background: var(--bg-white);
171
+ border-radius: var(--radius-xl);
172
+ padding: var(--space-lg);
173
+ box-shadow: var(--shadow-xl);
174
+ max-width: 400px; /* Narrower for auth forms */
175
+ width: 100%;
176
+ animation: slideIn var(--transition-slow) ease;
177
+ max-height: 95vh;
178
+ overflow-y: auto;
179
+ text-align: center;
180
+ }
181
+
182
  .auth-form input {
183
  width: 100%;
184
  margin-bottom: var(--space-sm);
 
188
  width: 100%;
189
  }
190
 
191
+ #result {
192
+ margin-bottom: var(--space-md); /* 16px bottom margin only */
193
+ margin-top: 0;
194
+ padding: 0;
195
+ }
196
+
197
  .switch-link {
198
  color: var(--text-primary);
199
  text-decoration: none;
200
  cursor: pointer;
201
+ font-weight: var(--font-weight-regular);
202
+ /* margin-top: 2px; */
203
+ display: block;
204
+ text-align: center;
205
  transition: color var(--transition-fast);
206
+ font-size: var(--font-size-sm);
207
  }
208
 
209
  .switch-link:hover {
 
653
  display: inline-flex;
654
  align-items: center;
655
  gap: 4px;
656
+
657
  }
658
 
659
  .back-link:hover {
 
685
  font-size: var(--font-size-base);
686
  }
687
 
688
+ input[type="text"], input[type="password"], input[type="email"] {
689
  padding: var(--space-md) var(--space-lg);
690
  font-size: var(--font-size-lg);
691
  }
templates/index.html CHANGED
@@ -9,27 +9,69 @@
9
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
10
  </head>
11
  <body>
12
- <div class="container">
13
  <div class="nav-header">
14
  <a href="/" class="back-link">← Back to Home</a>
15
  </div>
16
  {% if not logged_in %}
17
  <!-- Login/Signup Form -->
18
- <h1><span class="icon"></span>Spiritual Path Finder</h1>
19
  <p>{{ 'Begin your journey of self-discovery' if is_signup else 'Welcome back, seeker!' }}</p>
20
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
21
  <div class="auth-form">
22
- <input type="text" id="authUsername" placeholder="Username">
 
 
 
 
23
  <input type="password" id="authPassword" placeholder="Password">
24
  <button onclick="authenticate()">{{ 'Sign Up' if is_signup else 'Sign In' }}</button>
25
  <div id="result"></div>
 
 
 
 
 
26
  <p class="switch-link" onclick="switchAuth()">
27
  {{ 'Already have an account? Sign In' if is_signup else 'New here? Sign Up' }}
28
  </p>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
29
  </div>
30
  {% else %}
31
  <!-- Assessment Interface -->
32
- <h1>{{ title }}</h1>
33
  <p>{{ message }} <i class="fas fa-heart"></i> </p>
34
 
35
  {% if not has_results %}
 
9
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
10
  </head>
11
  <body>
12
+ <div class="{% if not logged_in %}auth-container{% else %}assessment-container{% endif %}">
13
  <div class="nav-header">
14
  <a href="/" class="back-link">← Back to Home</a>
15
  </div>
16
  {% if not logged_in %}
17
  <!-- Login/Signup Form -->
18
+ <h2><span class="icon"></span>Spiritual Path Finder</h2>
19
  <p>{{ 'Begin your journey of self-discovery' if is_signup else 'Welcome back, seeker!' }}</p>
20
 
21
+ {% if verify_success %}
22
+ <div class="success-msg" style="padding: 15px; margin-bottom: 20px; border-radius: 8px;">
23
+ ✅ Email verified successfully! You can now sign in.
24
+ </div>
25
+ {% endif %}
26
+
27
+ {% if verify_error %}
28
+ <div class="error-msg" style="padding: 15px; margin-bottom: 20px; border-radius: 8px;">
29
+ ⚠️ {{ verify_error }}
30
+ </div>
31
+ {% endif %}
32
+
33
+ {% if reset_error %}
34
+ <div class="error-msg" style="padding: 15px; margin-bottom: 20px; border-radius: 8px;">
35
+ ⚠️ {{ reset_error }}
36
+ </div>
37
+ {% endif %}
38
+
39
  <div class="auth-form">
40
+ {% if not is_forgot_password %}
41
+ <input type="text" id="authUsername" placeholder="User name">
42
+ {% if is_signup %}
43
+ <input type="email" id="authEmail" placeholder="Email">
44
+ {% endif %}
45
  <input type="password" id="authPassword" placeholder="Password">
46
  <button onclick="authenticate()">{{ 'Sign Up' if is_signup else 'Sign In' }}</button>
47
  <div id="result"></div>
48
+ {% if not is_signup %}
49
+ <p class="switch-link" onclick="window.location.href='/forgot-password'">
50
+ Forgot Password?
51
+ </p>
52
+ {% endif %}
53
  <p class="switch-link" onclick="switchAuth()">
54
  {{ 'Already have an account? Sign In' if is_signup else 'New here? Sign Up' }}
55
  </p>
56
+ {% else %}
57
+ {% if show_reset_form %}
58
+ <input type="password" id="resetPassword" placeholder="New Password">
59
+ <input type="hidden" id="resetToken" value="{{ reset_token }}">
60
+ <button onclick="submitPasswordReset()">Reset Password</button>
61
+ <div id="result"></div>
62
+ {% else %}
63
+ <input type="email" id="resetEmail" placeholder="Email">
64
+ <button onclick="resetPassword()">Send Reset Link</button>
65
+ <div id="result"></div>
66
+ {% endif %}
67
+ <p class="switch-link" onclick="window.location.href='/login'">
68
+ Back to Sign In
69
+ </p>
70
+ {% endif %}
71
  </div>
72
  {% else %}
73
  <!-- Assessment Interface -->
74
+ <h2>{{ title }}</h2>
75
  <p>{{ message }} <i class="fas fa-heart"></i> </p>
76
 
77
  {% if not has_results %}