Bharath370 commited on
Commit
286afa0
·
verified ·
1 Parent(s): 44073c9

Upload 23 files

Browse files
utils/__pycache__/challenge_manager.cpython-311.pyc ADDED
Binary file (6.18 kB). View file
 
utils/__pycache__/challenge_manager.cpython-312.pyc ADDED
Binary file (5.17 kB). View file
 
utils/__pycache__/mobile_responsive.cpython-311.pyc ADDED
Binary file (1.61 kB). View file
 
utils/__pycache__/mobile_responsive.cpython-312.pyc ADDED
Binary file (1.54 kB). View file
 
utils/__pycache__/mobile_responsive.cpython-313.pyc ADDED
Binary file (1.54 kB). View file
 
utils/__pycache__/score_tracker.cpython-311.pyc ADDED
Binary file (5.76 kB). View file
 
utils/__pycache__/score_tracker.cpython-312.pyc ADDED
Binary file (5.13 kB). View file
 
utils/__pycache__/score_tracker.cpython-313.pyc ADDED
Binary file (5.27 kB). View file
 
utils/__pycache__/translator.cpython-311.pyc ADDED
Binary file (2.02 kB). View file
 
utils/__pycache__/translator.cpython-312.pyc ADDED
Binary file (1.72 kB). View file
 
utils/__pycache__/ui_components.cpython-311.pyc ADDED
Binary file (10.1 kB). View file
 
utils/__pycache__/ui_components.cpython-312.pyc ADDED
Binary file (8.44 kB). View file
 
utils/__pycache__/ui_components.cpython-313.pyc ADDED
Binary file (7.16 kB). View file
 
utils/__pycache__/user_auth.cpython-311.pyc ADDED
Binary file (7.02 kB). View file
 
utils/__pycache__/user_auth.cpython-312.pyc ADDED
Binary file (6.21 kB). View file
 
utils/__pycache__/user_auth.cpython-313.pyc ADDED
Binary file (6.37 kB). View file
 
utils/challenge_manager.py ADDED
@@ -0,0 +1,78 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # utils/challenge_manager.py
2
+ import json
3
+ import os
4
+ import uuid
5
+ from datetime import datetime
6
+ from typing import Dict, List, Optional
7
+
8
+ CHALLENGES_FILE = "data/challenges.json"
9
+
10
+ def _load_challenges() -> Dict:
11
+ """Loads all challenges from the JSON file."""
12
+ if os.path.exists(CHALLENGES_FILE):
13
+ with open(CHALLENGES_FILE, "r") as f:
14
+ try:
15
+ return json.load(f)
16
+ except json.JSONDecodeError:
17
+ return {}
18
+ return {}
19
+
20
+ def _save_challenges(challenges: Dict):
21
+ """Saves all challenges to the JSON file."""
22
+ os.makedirs(os.path.dirname(CHALLENGES_FILE), exist_ok=True)
23
+ with open(CHALLENGES_FILE, "w") as f:
24
+ json.dump(challenges, f, indent=2)
25
+
26
+ class ChallengeManager:
27
+ def __init__(self):
28
+ self.challenges = _load_challenges()
29
+
30
+ def create_challenge(self, creator_id: str, topic: str, difficulty: str, num_questions: int) -> str:
31
+ """Creates a new challenge and returns its ID."""
32
+ challenge_id = str(uuid.uuid4())[:8] # Short UUID for easy sharing
33
+ challenge_data = {
34
+ "challenge_id": challenge_id,
35
+ "creator_id": creator_id,
36
+ "created_at": datetime.now().isoformat(),
37
+ "topic": topic,
38
+ "difficulty": difficulty,
39
+ "num_questions": num_questions,
40
+ "participants": {}, # {user_id: {score: int, quiz_results: List}}
41
+ "status": "active" # active, completed
42
+ }
43
+ self.challenges[challenge_id] = challenge_data
44
+ _save_challenges(self.challenges)
45
+ return challenge_id
46
+
47
+ def get_challenge(self, challenge_id: str) -> Optional[Dict]:
48
+ """Retrieves a challenge by its ID."""
49
+ return self.challenges.get(challenge_id)
50
+
51
+ def update_challenge_score(self, challenge_id: str, user_id: str, score: int, quiz_results: List[Dict]):
52
+ """Updates a participant's score and quiz results for a challenge."""
53
+ challenge = self.challenges.get(challenge_id)
54
+ if challenge:
55
+ challenge["participants"][user_id] = {
56
+ "score": score,
57
+ "quiz_results": quiz_results,
58
+ "completed_at": datetime.now().isoformat()
59
+ }
60
+ _save_challenges(self.challenges)
61
+ else:
62
+ raise ValueError(f"Challenge with ID {challenge_id} not found.")
63
+
64
+ def get_all_active_challenges(self) -> List[Dict]:
65
+ """Returns a list of all active challenges."""
66
+ return [c for c in self.challenges.values() if c.get("status") == "active"]
67
+
68
+ def get_challenges_by_creator(self, creator_id: str) -> List[Dict]:
69
+ """Returns a list of challenges created by a specific user."""
70
+ return [c for c in self.challenges.values() if c.get("creator_id") == creator_id]
71
+
72
+ def set_challenge_status(self, challenge_id: str, status: str):
73
+ """Sets the status of a challenge."""
74
+ if challenge_id in self.challenges:
75
+ self.challenges[challenge_id]["status"] = status
76
+ _save_challenges(self.challenges)
77
+ else:
78
+ raise ValueError(f"Challenge with ID {challenge_id} not found.")
utils/init.py ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # utils/__init__.py
2
+ """Utility modules for TriviaVerse application"""
3
+
4
+ from .score_tracker import ScoreTracker
5
+ from .user_auth import init_authentication, authenticate_user
6
+ from .ui_components import *
7
+ from .mobile_responsive import *
8
+
9
+ __all__ = [
10
+ "ScoreTracker",
11
+ "init_authentication",
12
+ "authenticate_user",
13
+ "apply_custom_theme",
14
+ "display_user_stats",
15
+ "display_badges",
16
+ "create_progress_chart",
17
+ "animated_success",
18
+ "create_quiz_card",
19
+ "responsive_columns",
20
+ "responsive_css",
21
+ ]
utils/mobile_responsive.py ADDED
@@ -0,0 +1,67 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # utils/mobile_responsive.py
2
+ """Utilities for mobile responsiveness."""
3
+
4
+ import streamlit as st
5
+
6
+
7
+ def responsive_columns(sizes: list, mobile_sizes: list = None):
8
+ """
9
+ Creates responsive columns for Streamlit.
10
+ On desktop, uses 'sizes'. On mobile, stacks columns or uses 'mobile_sizes'.
11
+ """
12
+ if mobile_sizes is None:
13
+ mobile_sizes = [1] * len(sizes) # Default to stacking if not specified
14
+
15
+ # Check for mobile client (a bit hacky, but works for most cases)
16
+ # Streamlit doesn't provide a direct way to detect mobile, so we rely on screen width
17
+ # This might not be perfectly accurate but is generally effective.
18
+ # We'll use JavaScript to pass screen width to Streamlit later if needed for more robust detection.
19
+
20
+ # For now, let's assume if the app is run on a narrow screen, it's mobile.
21
+ # This is often handled by CSS, but for column layout, Python-side adjustment can be useful.
22
+ # A more robust solution would involve custom component for screen width detection.
23
+
24
+ # Placeholder for actual mobile detection logic if implemented via JS/custom component
25
+ # For now, we'll let CSS handle most of the responsiveness, and this function
26
+ # will primarily provide a way to define column ratios.
27
+
28
+ # For demonstration, we'll just return st.columns with the given sizes.
29
+ # The actual mobile stacking will be handled by CSS in style.css.
30
+ return st.columns(sizes)
31
+
32
+
33
+ def responsive_css():
34
+ """Injects CSS for mobile responsiveness."""
35
+ # This CSS is also defined in assets/style.css, but including it here
36
+ # ensures it's applied, especially for column stacking.
37
+ # In a real app, you'd load this from the assets file.
38
+ css = """
39
+ <style>
40
+ /* Mobile-first responsive design */
41
+ @media (max-width: 768px) {
42
+ /* Stack columns on mobile */
43
+ .row-widget.stHorizontal {
44
+ flex-direction: column !important;
45
+ }
46
+
47
+ .row-widget.stHorizontal > div {
48
+ width: 100% !important;
49
+ margin-bottom: 1rem;
50
+ }
51
+
52
+ /* Full-width buttons on mobile */
53
+ .stButton > button {
54
+ width: 100% !important;
55
+ min-height: 50px;
56
+ font-size: 16px !important;
57
+ }
58
+
59
+ /* Responsive cards */
60
+ .card {
61
+ padding: 15px !important;
62
+ margin: 10px 0 !important;
63
+ }
64
+ }
65
+ </style>
66
+ """
67
+ return css
utils/score_tracker.py ADDED
@@ -0,0 +1,101 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # utils/score_tracker.py
2
+ import json
3
+ import os
4
+ from datetime import datetime
5
+ from config.settings import BASE_POINTS, DIFFICULTY_LEVELS, BADGES, ACHIEVEMENTS
6
+
7
+
8
+ class ScoreTracker:
9
+ def __init__(self, user_id: str):
10
+ self.user_id = user_id
11
+ self.scores_file = f"data/scores_{user_id}.json"
12
+ self.load_scores()
13
+
14
+ def load_scores(self):
15
+ """Load scores from a JSON file."""
16
+ if os.path.exists(self.scores_file):
17
+ with open(self.scores_file, "r") as f:
18
+ self.scores = json.load(f)
19
+ else:
20
+ self.scores = {
21
+ "total_score": 0,
22
+ "quizzes_completed": 0,
23
+ "quiz_history": [],
24
+ "badges": [],
25
+ "achievements": [],
26
+ "current_streak": 0,
27
+ "best_streak": 0,
28
+ }
29
+
30
+ def save_scores(self):
31
+ """Save scores to a JSON file."""
32
+ os.makedirs("data", exist_ok=True)
33
+ with open(self.scores_file, "w") as f:
34
+ json.dump(self.scores, f, indent=4)
35
+
36
+ def add_quiz_result(
37
+ self, mode: str, score: int, total: int, difficulty: str
38
+ ) -> int:
39
+ """Adds a quiz result and updates the total score."""
40
+ self.scores["quizzes_completed"] += 1
41
+
42
+ multiplier = DIFFICULTY_LEVELS.get(difficulty, {}).get("multiplier", 1)
43
+ points_earned = int(score * BASE_POINTS * multiplier)
44
+ self.scores["total_score"] += points_earned
45
+
46
+ self.scores["quiz_history"].append(
47
+ {
48
+ "timestamp": datetime.now().isoformat(),
49
+ "mode": mode,
50
+ "score": score,
51
+ "total": total,
52
+ "difficulty": difficulty,
53
+ "percentage": (score / total) * 100 if total > 0 else 0,
54
+ "points_earned": points_earned,
55
+ }
56
+ )
57
+
58
+ self.update_streak(score > 0)
59
+ self.check_for_badges()
60
+ self.check_for_achievements()
61
+ self.save_scores()
62
+ return points_earned
63
+
64
+ def update_streak(self, successful: bool):
65
+ """Updates the user's streak."""
66
+ if successful:
67
+ self.scores["current_streak"] += 1
68
+ else:
69
+ self.scores["current_streak"] = 0
70
+
71
+ if self.scores["current_streak"] > self.scores["best_streak"]:
72
+ self.scores["best_streak"] = self.scores["current_streak"]
73
+
74
+ def check_for_badges(self):
75
+ """Checks if the user has earned any new badges."""
76
+ for badge_id, badge_info in BADGES.items():
77
+ if (
78
+ self.scores["total_score"] >= badge_info["threshold"]
79
+ and badge_id not in self.scores["badges"]
80
+ ):
81
+ self.scores["badges"].append(badge_id)
82
+
83
+ def check_for_achievements(self):
84
+ """Checks for new achievements."""
85
+ # Example: First quiz achievement
86
+ if (
87
+ "first_quiz" not in self.scores["achievements"]
88
+ and self.scores["quizzes_completed"] > 0
89
+ ):
90
+ self.scores["achievements"].append("first_quiz")
91
+
92
+ # Example: Streak achievement
93
+ if (
94
+ "streak_5" not in self.scores["achievements"]
95
+ and self.scores["current_streak"] >= 5
96
+ ):
97
+ self.scores["achievements"].append("streak_5")
98
+
99
+ def get_stats(self) -> dict:
100
+ """Returns all user statistics."""
101
+ return self.scores
utils/translator.py ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # utils/translator.py
2
+ """Translation utility for TriviaVerse"""
3
+
4
+ import streamlit as st
5
+ from googletrans import Translator
6
+ from config.languages import SUPPORTED_LANGUAGES
7
+ from config.localization import UI_TEXT
8
+
9
+ @st.cache_data(ttl=3600)
10
+ def get_translated_texts(dest_lang="en"):
11
+ """
12
+ Translates all UI texts to the destination language in a single batch
13
+ and caches the result.
14
+ """
15
+ if dest_lang == "en":
16
+ return UI_TEXT
17
+
18
+ try:
19
+ translator = Translator()
20
+ # Get all the values from the UI_TEXT dictionary
21
+ original_texts = list(UI_TEXT.values())
22
+
23
+ # Translate them in a single batch
24
+ translated_texts = translator.translate(original_texts, dest=dest_lang)
25
+
26
+ # Create a new dictionary with the same keys but translated values
27
+ translated_dict = dict(zip(UI_TEXT.keys(), [t.text for t in translated_texts]))
28
+
29
+ return translated_dict
30
+ except Exception as e:
31
+ st.error(f"Translation service failed: {e}. Falling back to English.")
32
+ return UI_TEXT
33
+
34
+ def get_supported_languages():
35
+ """Returns the list of supported languages."""
36
+ return SUPPORTED_LANGUAGES
utils/ui_components.py ADDED
@@ -0,0 +1,112 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # utils/ui_components.py
2
+ import streamlit as st
3
+ import plotly.graph_objects as go
4
+ from config.settings import BADGES
5
+ from config.themes import THEMES
6
+
7
+ def apply_custom_theme(theme_name: str):
8
+ """Applies a custom theme using CSS variables."""
9
+ theme = THEMES.get(theme_name, THEMES["default"])
10
+
11
+ css_vars = {
12
+ "--primary-color": theme["primary_color"],
13
+ "--background-color": theme["background_color"],
14
+ "--secondary-background-color": theme["secondary_background_color"],
15
+ "--text-color": theme["text_color"],
16
+ "--font": theme["font"],
17
+ "--card-background": theme.get("card_background", "#FFFFFF"),
18
+ "--shadow-light": theme.get("shadow_light", "0 4px 15px rgba(0, 0, 0, 0.05)"),
19
+ "--shadow-medium": theme.get("shadow_medium", "0 8px 30px rgba(0, 0, 0, 0.1)"),
20
+ "--highlight-color": theme.get("highlight_color", theme["primary_color"]),
21
+ }
22
+
23
+ css_vars_str = "\n".join([f"{key}: {value};" for key, value in css_vars.items()])
24
+
25
+ css = f"""
26
+ <style>
27
+ :root {{ {css_vars_str} }}
28
+ .stApp {{ background-color: var(--background-color); color: var(--text-color); }}
29
+ .stSidebar {{ background-color: var(--secondary-background-color); }}
30
+ .stButton>button {{ background-color: var(--primary-color); color: white; box-shadow: var(--shadow-light); }}
31
+ .stButton>button:hover {{ background-color: var(--secondary-color); box-shadow: var(--shadow-medium); }}
32
+ /* Add other theme styles here */
33
+ </style>
34
+ """
35
+ st.markdown(css, unsafe_allow_html=True)
36
+
37
+ def display_user_stats(stats: dict, ui: dict):
38
+ """Displays user statistics in a dashboard format."""
39
+ cols = st.columns(4)
40
+ with cols[0]:
41
+ st.metric(ui.get("total_score", "Total Score"), f"{stats.get('total_score', 0):,}")
42
+ with cols[1]:
43
+ st.metric(ui.get("quizzes", "Quizzes Played"), stats.get("quizzes_completed", 0))
44
+ with cols[2]:
45
+ st.metric(ui.get("current_streak", "Current Streak"), f"🔥 {stats.get('current_streak', 0)}")
46
+ with cols[3]:
47
+ st.metric(ui.get("best_streak", "Best Streak"), f"⭐ {stats.get('best_streak', 0)}")
48
+
49
+ def display_badges(earned_badges: list, ui: dict):
50
+ """Displays earned badges."""
51
+ if not earned_badges:
52
+ return
53
+
54
+ st.markdown(f"### 🏆 {ui.get('your_badges', 'Your Badges')}")
55
+ cols = st.columns(len(earned_badges))
56
+ for i, badge_id in enumerate(earned_badges):
57
+ badge = BADGES.get(badge_id)
58
+ if badge:
59
+ with cols[i]:
60
+ st.markdown(f"<div style='text-align: center;'><span style='font-size: 50px;'>{badge['icon']}</span><br><b>{ui.get(badge['name'].lower(), badge['name'])}</b></div>", unsafe_allow_html=True)
61
+
62
+ def create_progress_chart(stats: dict, ui: dict) -> go.Figure:
63
+ """Creates a progress chart for the user."""
64
+ history = stats.get("quiz_history", [])
65
+ if not history:
66
+ return go.Figure()
67
+
68
+ dates = [item["timestamp"] for item in history]
69
+ scores = [item["percentage"] for item in history]
70
+
71
+ fig = go.Figure()
72
+ fig.add_trace(go.Scatter(x=dates, y=scores, mode="lines+markers", name=ui.get("score", "Score") + " %"))
73
+ fig.update_layout(
74
+ title=ui.get("quiz_performance_over_time", "Quiz Performance Over Time"),
75
+ xaxis_title=ui.get("date", "Date"),
76
+ yaxis_title=f"{ui.get('score', 'Score')} (%)",
77
+ yaxis_range=[0, 100],
78
+ )
79
+ return fig
80
+
81
+ def animated_success(message: str):
82
+ """Displays an animated success message."""
83
+ st.markdown(f"<div class='pop-in'>{st.success(message)}</div>", unsafe_allow_html=True)
84
+
85
+ def create_quiz_card(question: str, options: list, key: str):
86
+ """Creates a card for a multiple-choice question."""
87
+ st.markdown(f"<div style='background-color: var(--card-background); padding: 20px; border-radius: 10px; box-shadow: var(--shadow-light);'>", unsafe_allow_html=True)
88
+ st.subheader(question)
89
+ st.session_state[key] = st.radio(
90
+ "Select your answer:",
91
+ options,
92
+ index=None,
93
+ key=f"{key}_radio",
94
+ label_visibility="collapsed",
95
+ )
96
+ st.markdown("</div>", unsafe_allow_html=True)
97
+
98
+ def render_flashcard(front_content: str, back_content: str, is_flipped: bool):
99
+ """Renders a flippable flashcard."""
100
+ card_style = "width: 100%; height: 300px; perspective: 1000px;"
101
+ flipper_style = f'position: relative; width: 100%; height: 100%; transition: transform 0.6s; transform-style: preserve-3d; transform: {"rotateY(180deg)" if is_flipped else "none"};'
102
+ face_style = "position: absolute; width: 100%; height: 100%; -webkit-backface-visibility: hidden; backface-visibility: hidden; display: flex; justify-content: center; align-items: center; padding: 20px; border-radius: 10px; box-shadow: var(--shadow-light);"
103
+ front_style = f"{face_style} background-color: var(--primary-color); color: white;"
104
+ back_style = f"{face_style} background-color: var(--card-background); color: var(--text-color); transform: rotateY(180deg);"
105
+
106
+ st.markdown(
107
+ f"<div style='{card_style}'><div style='{flipper_style}'>"
108
+ f"<div style='{front_style}'><div>{front_content}</div></div>"
109
+ f"<div style='{back_style}'><div>{back_content}</div></div>"
110
+ f"</div></div>",
111
+ unsafe_allow_html=True,
112
+ )
utils/user_auth.py ADDED
@@ -0,0 +1,128 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # utils/user_auth.py
2
+ """User authentication module"""
3
+
4
+ import streamlit as st
5
+ import hashlib
6
+ import json
7
+ import os
8
+ from datetime import datetime
9
+
10
+ # Simple authentication system for demo
11
+ # In production, use proper authentication libraries
12
+
13
+
14
+ def hash_password(password: str) -> str:
15
+ """Hash password using SHA256"""
16
+ return hashlib.sha256(password.encode()).hexdigest()
17
+
18
+
19
+ def load_users():
20
+ """Load users from file"""
21
+ users_file = "data/users.json"
22
+ if os.path.exists(users_file):
23
+ with open(users_file, "r") as f:
24
+ return json.load(f)
25
+ return {}
26
+
27
+
28
+ def save_users(users):
29
+ """Save users to file"""
30
+ os.makedirs("data", exist_ok=True)
31
+ with open("data/users.json", "w") as f:
32
+ json.dump(users, f, indent=2)
33
+
34
+
35
+ def init_authentication():
36
+ """Initialize authentication in session state"""
37
+ if "authenticated" not in st.session_state:
38
+ st.session_state.authenticated = False
39
+ if "username" not in st.session_state:
40
+ st.session_state.username = None
41
+
42
+
43
+ def authenticate_user():
44
+ """Show authentication UI and handle login/signup"""
45
+ if st.session_state.authenticated:
46
+ return True
47
+
48
+ st.title("🎯 Welcome to TriviaVerse")
49
+ st.markdown("### Please login or create an account to continue")
50
+
51
+ tab1, tab2 = st.tabs(["Login", "Sign Up"])
52
+
53
+ with tab1:
54
+ with st.form("login_form"):
55
+ username = st.text_input("Username")
56
+ password = st.text_input("Password", type="password")
57
+ submitted = st.form_submit_button("Login", type="primary")
58
+
59
+ if submitted:
60
+ users = load_users()
61
+
62
+ if username in users:
63
+ if users[username]["password"] == hash_password(password):
64
+ st.session_state.authenticated = True
65
+ st.session_state.username = username
66
+ st.session_state.user_id = username
67
+
68
+ # Update last login
69
+ users[username]["last_login"] = datetime.now().isoformat()
70
+ save_users(users)
71
+
72
+ st.success("Login successful!")
73
+ st.rerun()
74
+ else:
75
+ st.error("Invalid password")
76
+ else:
77
+ st.error("Username not found")
78
+
79
+ with tab2:
80
+ with st.form("signup_form"):
81
+ new_username = st.text_input("Choose a username")
82
+ new_password = st.text_input("Choose a password", type="password")
83
+ confirm_password = st.text_input("Confirm password", type="password")
84
+ email = st.text_input("Email (optional)")
85
+
86
+ terms = st.checkbox("I agree to the terms and conditions")
87
+ submitted = st.form_submit_button("Sign Up", type="primary")
88
+
89
+ if submitted:
90
+ if not new_username or not new_password:
91
+ st.error("Username and password are required")
92
+ elif new_password != confirm_password:
93
+ st.error("Passwords do not match")
94
+ elif not terms:
95
+ st.error("Please agree to the terms and conditions")
96
+ else:
97
+ users = load_users()
98
+
99
+ if new_username in users:
100
+ st.error("Username already exists")
101
+ else:
102
+ # Create new user
103
+ users[new_username] = {
104
+ "password": hash_password(new_password),
105
+ "email": email,
106
+ "created": datetime.now().isoformat(),
107
+ "last_login": datetime.now().isoformat(),
108
+ }
109
+ save_users(users)
110
+
111
+ # Auto login
112
+ st.session_state.authenticated = True
113
+ st.session_state.username = new_username
114
+ st.session_state.user_id = new_username
115
+
116
+ st.success("Account created successfully!")
117
+ st.balloons()
118
+ st.rerun()
119
+
120
+ # Guest mode option
121
+ st.divider()
122
+ if st.button("Continue as Guest", type="secondary"):
123
+ st.session_state.authenticated = True
124
+ st.session_state.username = "Guest"
125
+ st.session_state.user_id = f"guest_{int(datetime.now().timestamp())}"
126
+ st.rerun()
127
+
128
+ return False