commit b20d65ca96ee5edfd5417ae96ec82c5d4ea00bec Author: kokofixcomputers Date: Thu May 22 09:14:41 2025 -0700 Initial Commit diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..d9c959f Binary files /dev/null and b/.DS_Store differ diff --git a/__pycache__/api.cpython-38.pyc b/__pycache__/api.cpython-38.pyc new file mode 100644 index 0000000..502facb Binary files /dev/null and b/__pycache__/api.cpython-38.pyc differ diff --git a/__pycache__/frontend.cpython-38.pyc b/__pycache__/frontend.cpython-38.pyc new file mode 100644 index 0000000..65af71e Binary files /dev/null and b/__pycache__/frontend.cpython-38.pyc differ diff --git a/api.py b/api.py new file mode 100644 index 0000000..229ef25 --- /dev/null +++ b/api.py @@ -0,0 +1,469 @@ +from flask import Flask, request, jsonify +from flask_sqlalchemy import SQLAlchemy +from flask_bcrypt import Bcrypt +import uuid +import jwt +from datetime import datetime, timedelta + +app = Flask(__name__) +app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///game_save.db' +app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False +app.config['SECRET_KEY'] = 'your_secret_key_here' + +db = SQLAlchemy(app) +bcrypt = Bcrypt(app) + +# Models +class Developer(db.Model): + id = db.Column(db.Integer, primary_key=True) + username = db.Column(db.String(80), nullable=True) + email = db.Column(db.String(120), unique=True, nullable=False) + password = db.Column(db.String(120), nullable=False) + +class User(db.Model): + id = db.Column(db.Integer, primary_key=True) + username = db.Column(db.String(80), nullable=False) + email = db.Column(db.String(120), unique=True, nullable=False) + password = db.Column(db.String(120), nullable=False) + game_password = db.Column(db.String(120), nullable=False) + +class GameSave(db.Model): + id = db.Column(db.Integer, primary_key=True) + user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False) + game_id = db.Column(db.String(80), nullable=False) + slot = db.Column(db.Integer, nullable=False) # Save slot (1-10) + save_data = db.Column(db.Text, nullable=False) + +class Game(db.Model): + id = db.Column(db.Integer, primary_key=True) + ugi = db.Column(db.String(80), unique=True, nullable=False) + name = db.Column(db.String(100), nullable=False) + developer_id = db.Column(db.Integer, db.ForeignKey('developer.id'), nullable=False) + +class Log(db.Model): + id = db.Column(db.Integer, primary_key=True) + user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False) + game_id = db.Column(db.String(80), nullable=False) + action = db.Column(db.String(20), nullable=False) # 'add_points' or 'remove_points' + points = db.Column(db.Integer, nullable=False) + timestamp = db.Column(db.DateTime, nullable=False, default=datetime.utcnow) + +class UserStats(db.Model): + id = db.Column(db.Integer, primary_key=True) + user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False) + games_played = db.Column(db.String(200), nullable=False, default='') + points_earned = db.Column(db.Integer, nullable=False, default=0) + +class GamePlayed(db.Model): + id = db.Column(db.Integer, primary_key=True) + user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False) + game_id = db.Column(db.String(200), nullable=False, default='') + +class AuthToken(db.Model): + id = db.Column(db.Integer, primary_key=True) + token = db.Column(db.String(200), nullable=False) + game_id = db.Column(db.String(80), nullable=False) + user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False) + created_at = db.Column(db.DateTime, nullable=False, default=datetime.utcnow) + +class Session(db.Model): + id = db.Column(db.Integer, primary_key=True) + session_token = db.Column(db.String(200), nullable=False) + user_type = db.Column(db.String(20), nullable=False) # 'user' or 'developer' + user_id = db.Column(db.Integer, nullable=False) + created_at = db.Column(db.DateTime, nullable=False, default=datetime.utcnow) + +# Routes + +@app.route('/api/user/login', methods=['POST']) +def login_user(): + data = request.json + user = User.query.filter_by(email=data['email']).first() + if user and bcrypt.check_password_hash(user.password, data['password']): + session_token = str(uuid.uuid4()) + new_session = Session( + session_token=session_token, + user_type='user', + user_id=user.id + ) + db.session.add(new_session) + db.session.commit() + return jsonify({'session_token': session_token}), 200 + else: + return jsonify({'error': 'Invalid email or password'}), 401 + +@app.route('/api/user/register', methods=['POST']) +def register_user(): + data = request.json + if 'username' not in data or 'email' not in data or 'password' not in data or 'game_password' not in data: + return jsonify({'error': 'Username, email, password, and game password are required'}), 400 + + existing_user = User.query.filter_by(email=data['email']).first() + if existing_user: + return jsonify({'error': 'Email already in use'}), 400 + + hashed_password = bcrypt.generate_password_hash(data['password']).decode('utf-8') + new_user = User( + username=data['username'], + email=data['email'], + password=hashed_password, + game_password=data['game_password'] + ) + db.session.add(new_user) + db.session.commit() + + new_user_stats = UserStats(user_id=new_user.id) + db.session.add(new_user_stats) + db.session.commit() + return jsonify({'message': 'User registered successfully'}), 201 + +@app.route('/api/developer/login', methods=['POST']) +def login_developer(): + data = request.json + developer = Developer.query.filter_by(email=data['email']).first() + if developer and bcrypt.check_password_hash(developer.password, data['password']): + session_token = str(uuid.uuid4()) + new_session = Session( + session_token=session_token, + user_type='developer', + user_id=developer.id + ) + db.session.add(new_session) + db.session.commit() + return jsonify({'session_token': session_token, 'id': developer.id}), 200 + else: + return jsonify({'error': 'Invalid email or password'}), 401 + +@app.route('/api/developer/register', methods=['POST']) +def register_developer(): + data = request.json + if 'email' not in data or 'password' not in data: + return jsonify({'error': 'Email and password are required'}), 400 + + existing_dev = Developer.query.filter_by(email=data['email']).first() + if existing_dev: + return jsonify({'error': 'Email already in use'}), 400 + + hashed_password = bcrypt.generate_password_hash(data['password']).decode('utf-8') + new_dev = Developer( + username=data.get('username'), + email=data['email'], + password=hashed_password + ) + db.session.add(new_dev) + db.session.commit() + return jsonify({'message': 'Developer registered successfully'}), 201 + +@app.route('/api/game/register', methods=['POST']) +def register_game(): + data = request.json + if 'session_token' not in data: + return jsonify({'error': 'Session token is required'}), 401 + + session = Session.query.filter_by(session_token=data['session_token']).first() + if not session or session.user_type != 'developer': + return jsonify({'error': 'Invalid session or not a developer'}), 401 + + if 'name' not in data: + return jsonify({'error': 'Game name is required'}), 400 + + gameexist = Game.query.filter_by(name=data['name']).first() + if gameexist: + return jsonify({'error': 'Game name already taken.'}), 400 + + ugi = str(uuid.uuid4()) + new_game = Game( + ugi=ugi, + name=data['name'], + developer_id=session.user_id + ) + db.session.add(new_game) + db.session.commit() + return jsonify({'message': 'Game registered successfully', 'ugi': ugi}), 201 + +@app.route('/api/game/delete', methods=['POST']) +def delete_game(): + data = request.json + if 'session_token' not in data: + return jsonify({'error': 'Session token is required'}), 401 + + session = Session.query.filter_by(session_token=data['session_token']).first() + if not session or session.user_type != 'developer': + return jsonify({'error': 'Invalid session or not a developer'}), 401 + + if 'ugi' not in data: + return jsonify({'error': 'Game UGI is required'}), 400 + + game_to_delete = Game.query.filter_by(ugi=data['ugi'], developer_id=session.user_id).first() + if not game_to_delete: + return jsonify({'error': 'Game not found or not owned by you'}), 404 + game_saves_to_delete = GameSave.query.filter_by(game_id=data['ugi']).all() + for save in game_saves_to_delete: + db.session.delete(save) + + db.session.delete(game_to_delete) + db.session.commit() + return jsonify({'message': 'Game deleted successfully'}), 200 + +@app.route('/api/game/authenticate', methods=['POST']) +def authenticate_game(): + data = request.json + if 'username' not in data or 'game_password' not in data or 'game_id' not in data: + return jsonify({'error': 'Username, game password, and game ID are required'}), 400 + + user = User.query.filter_by(username=data['username']).first() + if not user: + return jsonify({'error': 'User not found'}), 404 + + if user.game_password != data['game_password']: + return jsonify({'error': 'Incorrect game password'}), 401 + + payload = { + 'exp': datetime.utcnow() + timedelta(minutes=30), + 'iat': datetime.utcnow(), + 'sub': user.id + } + token = jwt.encode(payload, app.config['SECRET_KEY'], algorithm='HS256') + # data['game_id'] is the game's UGI. When storing it in db, first, find the ugi and then find the id of the game then store that id in the db + game_id = Game.query.filter_by(ugi=data['game_id']).first().id + if not game_id: + return jsonify({'error': 'Game not found'}), 404 + new_auth_token = AuthToken( + token=token, + game_id=game_id, + user_id=user.id + ) + db.session.add(new_auth_token) + db.session.commit() + return jsonify({'message': 'Game authenticated successfully', 'token': token}), 200 + +@app.route('/api/game/save-data', methods=['POST']) +def save_game_data(): + data = request.json + if 'save_data' not in data or 'token' not in data or 'slot' not in data: + return jsonify({'error': 'Save data, token, and slot are required'}), 400 + + auth_token = AuthToken.query.filter_by(token=data['token']).first() + if not auth_token: + return jsonify({'error': 'Invalid token'}), 401 + + if not 1 <= int(data['slot']) <= 10: + return jsonify({'error': 'Invalid save slot'}), 400 + + existing_save = GameSave.query.filter_by(user_id=auth_token.user_id, game_id=auth_token.game_id, slot=int(data['slot'])).first() + if existing_save: + existing_save.save_data = data['save_data'] + db.session.commit() + return jsonify({'message': 'Game save data updated successfully'}), 200 + else: + new_save = GameSave( + user_id=auth_token.user_id, + game_id=auth_token.game_id, + slot=data['slot'], + save_data=data['save_data'] + ) + db.session.add(new_save) + db.session.commit() + return jsonify({'message': 'Game save data saved successfully'}), 201 + +@app.route('/api/game/read-data', methods=['POST']) +def read_game_data(): + data = request.json + if 'token' not in data or 'slot' not in data: + return jsonify({'error': 'Token and slot are required'}), 400 + + auth_token = AuthToken.query.filter_by(token=data['token']).first() + if not auth_token: + return jsonify({'error': 'Invalid token'}), 401 + + if not 1 <= int(data['slot']) <= 10: + return jsonify({'error': 'Invalid save slot'}), 400 + + save_data = GameSave.query.filter_by(user_id=auth_token.user_id, game_id=auth_token.game_id, slot=int(data['slot'])).first() + if save_data: + return jsonify({'save_data': save_data.save_data}), 200 + else: + return jsonify({'error': 'No save data found for this user and game'}), 404 + +@app.route('/api/user/stats', methods=['POST']) +def get_user_stats(): + data = request.json + if 'token' not in data: + return jsonify({'error': 'Token is required'}), 400 + + auth_token = AuthToken.query.filter_by(token=data['token']).first() + auth_token2 = Session.query.filter_by(session_token=data['token']).first() + if not auth_token and not auth_token2: + return jsonify({'error': 'Invalid token'}), 401 + if auth_token: + user_id = auth_token.user_id + elif auth_token2: + user_id = auth_token2.user_id + + user_stats = UserStats.query.filter_by(user_id=user_id).first() + gamed = GamePlayed.query.filter_by(user_id=user_id).all() + gamesPld = [] + for game in gamed: + gamesPld.append(game.game_id) + if user_stats: + return jsonify({ + 'games_played': gamesPld, + 'points_earned': user_stats.points_earned + }), 200 + else: + return jsonify({'error': 'User stats not found'}), 404 + +@app.route('/api/user/add-game-played', methods=['POST']) +def add_game_played(): + data = request.json + if 'token' not in data: + return jsonify({'error': 'Token is required'}), 400 + + auth_token = AuthToken.query.filter_by(token=data['token']).first() + if not auth_token: + return jsonify({'error': 'Invalid token'}), 401 + user_id = auth_token.user_id + + user_stats = UserStats.query.filter_by(user_id=user_id).first() + if user_stats: + game_already_played = GamePlayed.query.filter_by(game_id=auth_token.game_id).first() + if not game_already_played: + new_game_played = GamePlayed( + user_id=user_id, + game_id=auth_token.game_id + ) + db.session.add(new_game_played) + db.session.commit() + return jsonify({'message': 'Game added to played games successfully'}), 200 + else: + return jsonify({'error': 'Game is already marked as played'}), 200 + else: + return jsonify({'error': 'User stats not found'}), 404 + +@app.route('/api/user/points/add', methods=['POST']) +def add_points(): + data = request.json + if 'points' not in data or 'token' not in data: + return jsonify({'error': 'Points and token are required'}), 400 + + auth_token = AuthToken.query.filter_by(token=data['token']).first() + auth_token2 = Session.query.filter_by(session_token=data['token']).first() + if not auth_token and not auth_token2: + return jsonify({'error': 'Invalid token'}), 401 + + if auth_token is not None: + user_id = auth_token.user_id + elif auth_token2: + user_id = auth_token2.user_id + + + user_stats = UserStats.query.filter_by(user_id=user_id).first() + if user_stats: + user_stats.points_earned += data['points'] + db.session.commit() + + new_log = Log( + user_id=user_id, + game_id="game_id", + action='add_points', + points=data['points'] + ) + db.session.add(new_log) + db.session.commit() + return jsonify({'message': 'Points added successfully'}), 200 + else: + return jsonify({'error': 'User stats not found'}), 404 + +@app.route('/api/user/points/remove', methods=['POST']) +def remove_points(): + data = request.json + if 'points' not in data or 'token' not in data: + return jsonify({'error': 'Points and token are required'}), 400 + + auth_token = AuthToken.query.filter_by(token=data['token']).first() + auth_token2 = Session.query.filter_by(session_token=data['token']).first() + if not auth_token and not auth_token2: + return jsonify({'error': 'Invalid token'}), 401 + if auth_token: + user_id = auth_token.user_id + elif auth_token2: + user_id = auth_token2.user_id + + user_stats = UserStats.query.filter_by(user_id=user_id).first() + if user_stats: + if user_stats.points_earned >= data['points']: + user_stats.points_earned -= data['points'] + db.session.commit() + + new_log = Log( + user_id=user_id, + game_id="game_id", + action='remove_points', + points=data['points'] + ) + db.session.add(new_log) + db.session.commit() + return jsonify({'message': 'Points removed successfully'}), 200 + else: + return jsonify({'error': 'Not enough points to remove'}), 400 + else: + return jsonify({'error': 'User stats not found'}), 404 + +@app.route('/api/logs', methods=['GET']) +def get_logs(): + session_token = request.cookies.get('session_token') + if not session_token: + return jsonify({'error': 'Session token is required'}), 401 + + session = Session.query.filter_by(session_token=session_token).first() + if not session: + return jsonify({'error': 'Invalid session'}), 401 + + logs = Log.query.filter_by(user_id=session.user_id).all() + log_data = [] + for log in logs: + log_data.append({ + 'id': log.id, + 'user_id': log.user_id, + 'game_id': log.game_id, + 'action': log.action, + 'points': log.points, + 'timestamp': log.timestamp + }) + return jsonify(log_data), 200 + +@app.route('/api/developerstats/games', methods=['POST']) +def games_count(): + data = request.json + if 'token' not in data: + return jsonify({'error': 'token is required'}), 401 + + session = Session.query.filter_by(session_token=data['token']).first() + if not session: + return jsonify({'error': 'Invalid session'}), 401 + + if session.user_type != "developer": + return jsonify({'error': 'Invalid User type. Requested developer, got user.'}) + + total_games = Game.query.filter_by(developer_id=session.user_id).all() + games_data = [ + {'name': game.name, 'ugi': game.ugi} + for game in total_games + ] + + return jsonify({"len": len(total_games), "games": games_data}), 200 + + +# Purge old tokens +#@app.before_first_request +#def purge_old_tokens(): +# old_tokens = AuthToken.query.filter(AuthToken.created_at < datetime.utcnow() - timedelta(days=10)).all() +# for token in old_tokens: +# db.session.delete(token) +# db.session.commit() +with app.app_context(): + db.create_all() +#if __name__ == '__main__': +# with app.app_context(): +# db.create_all() + #app.run(debug=True, host='0.0.0.0', port=5034) diff --git a/frontend.py b/frontend.py new file mode 100644 index 0000000..c83f077 --- /dev/null +++ b/frontend.py @@ -0,0 +1,151 @@ +from flask import Flask, render_template, request, redirect, url_for, make_response, abort, jsonify +import requests + +app = Flask(__name__) +app.config['SECRET_KEY'] = 'your_secret_key_here' + +# API URL +API_URL = 'http://localhost:5023' + +# Routes + +@app.route('/') +def index(): + return render_template('index.html') + + +@app.route('/modal') +def modal(): + id = request.args.get('id') + if id == "scratch": + return render_template('scratch_modal.html') + +@app.route('/login/user', methods=['GET', 'POST']) +def login_user(): + if request.method == 'POST': + data = request.form + response = requests.post(f'{API_URL}/api/user/login', json={'email': data['email'], 'password': data['password']}) + if response.status_code == 200: + session_token = response.json()['session_token'] + resp = make_response("Login Successful!") + resp.set_cookie('session_token', session_token, secure=True, httponly=True) + return resp + else: + abort(400) + return render_template('login_user.html') + +@app.route('/login/developer', methods=['GET', 'POST']) +def login_developer(): + if request.method == 'POST': + data = request.form + response = requests.post(f'{API_URL}/api/developer/login', json={'email': data['email'], 'password': data['password']}) + if response.status_code == 200: + session_token = response.json()['session_token'] + resp = make_response("Login Successful!") + resp.set_cookie('devsession_token', session_token, secure=True, httponly=True) + return resp + else: + return render_template('login_developer.html', error='Invalid email or password') + return render_template('login_developer.html') + +@app.route('/register/user', methods=['GET', 'POST']) +def register_user(): + if request.method == 'POST': + data = request.form + response = requests.post(f'{API_URL}/api/user/register', json={'username': data['username'], 'email': data['email'], 'password': data['password'], 'game_password': data['game_password']}) + if response.status_code == 201: + return render_template('user_registered.html', message=response.json()['message']) + else: + return render_template('register_user.html', error='Failed to register user') + return render_template('register_user.html') + +@app.route('/register/developer', methods=['GET', 'POST']) +def register_developer(): + if request.method == 'POST': + data = request.form + response = requests.post(f'{API_URL}/api/developer/register', json={'email': data['email'], 'password': data['password']}) + if response.status_code == 201: + return render_template('developer_registered.html', message=response.json()['message']) + else: + return render_template('register_developer.html', error='Failed to register developer') + return render_template('register_developer.html') + +@app.route('/user/dashboard') +def user_dashboard(): + session_token = request.cookies.get('session_token') + if not session_token: + return redirect(url_for('login_user')) + + response = requests.post(f'{API_URL}/api/user/stats', json={'token': f'{session_token}'}) + if response.status_code == 200: + user_stats = response.json() + games_played_count = len(user_stats['games_played']) + return render_template('user_dashboard.html', user_stats=user_stats, games_played_count=games_played_count) + else: + return redirect(url_for('login_user')) + +@app.route('/developer/dashboard') +def developer_dashboard(): + session_token = request.cookies.get('devsession_token') + if not session_token: + return redirect(url_for('login_developer')) + + count = requests.post(f'{API_URL}/api/developerstats/games', json={'token': session_token}) + if count.status_code == 200: + return render_template('developer_dashboard.html', session_token=session_token, count=count.json()['len']) + else: + return redirect('login_developer') + +@app.route('/developer/dashboard/games') +def developer_dashboard_games(): + session_token = request.cookies.get('devsession_token') + if not session_token: + return redirect(url_for('login_developer')) + count = requests.post(f'{API_URL}/api/developerstats/games', json={'token': session_token}) + if count.status_code == 200: + return render_template('developer_dashboard_game.html', session_token=session_token, count=count.json()['len'], games=count.json()['games']) + else: + return redirect('login_developer') + +@app.route('/developer/register/game', methods=['GET', 'POST']) +def register_game(): + session_token = request.cookies.get('devsession_token') + if not session_token: + return redirect(url_for('login_developer')) + + if request.method == 'POST': + data = request.form + response = requests.post(f'{API_URL}/api/game/register', json={'session_token': session_token, 'name': data['name']}) + if response.status_code == 201: + return "OK" + else: + return response.json()['error'], 400 + return render_template('register_game.html') + +@app.route('/developer/delete/game', methods=['GET']) +def delete_game(): + gameugi = request.args.get('ugi') + gamename = request.args.get('name') + session_token = request.cookies.get('devsession_token') + if not session_token: + return redirect(url_for('login_developer')) + if not gameugi or not gamename: + return jsonify({"error": "The ugi or the name parameter was not found"}) + return render_template('developer_dashboard_delete_game.html', name=gamename, ugi=gameugi) + +@app.route('/developer/dashboard/games/delete/confirmed', methods=['POST']) +def delete_actually_do_it(): + session_token = request.cookies.get('devsession_token') + ugi = request.args.get('ugi') + if not session_token: + return redirect(url_for('login_developer')) + if not ugi: + return jsonify({"error": "Ugi parameter must be provided."}) + resp = requests.post(f'{API_URL}/api/game/delete', json={'session_token': session_token, 'ugi': ugi}) + if resp.status_code == 200: + return "OK" + +# Other routes... + +#if __name__ == '__main__': + #app.run(debug=True, port=5001) diff --git a/instance/game_save.db b/instance/game_save.db new file mode 100644 index 0000000..801bcc3 Binary files /dev/null and b/instance/game_save.db differ diff --git a/main.py b/main.py new file mode 100644 index 0000000..f65231a --- /dev/null +++ b/main.py @@ -0,0 +1,16 @@ +import threading +from api import app as api_app +from frontend import app as frontend_app + +def run_api(): + api_app.run(debug=False, port=5023) + +def run_frontend(): + frontend_app.run(debug=False, port=5024) + +if __name__ == '__main__': + api_thread = threading.Thread(target=run_api) + frontend_thread = threading.Thread(target=run_frontend) + + api_thread.start() + frontend_thread.start() diff --git a/static/.DS_Store b/static/.DS_Store new file mode 100644 index 0000000..415c1a8 Binary files /dev/null and b/static/.DS_Store differ diff --git a/static/js/color-modes.js b/static/js/color-modes.js new file mode 100644 index 0000000..fef931d --- /dev/null +++ b/static/js/color-modes.js @@ -0,0 +1,24 @@ +/*! + * Color mode toggler with OS change detection + */ + +(() => { + 'use strict'; + + // Function to update theme based on localStorage or system preference + const updateTheme = () => { + if (window.matchMedia('(prefers-color-scheme: dark)').matches) { + document.documentElement.classList.add('dark'); + } else { + document.documentElement.classList.remove('dark'); + } + }; + + // Initialize theme on load + updateTheme(); + + // Listen for OS color scheme changes + window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => { + updateTheme(); + }); +})(); \ No newline at end of file diff --git a/static/js/modals.js b/static/js/modals.js new file mode 100644 index 0000000..d37125b --- /dev/null +++ b/static/js/modals.js @@ -0,0 +1,33 @@ +// Performs basic AJAX functionality to fetch and render a modal. + +function showLoadingOverlay() { + $(`#loadingOverlay`)[0].classList.remove("hidden"); +} + +function hideLoadingOverlay() { + $(`#loadingOverlay`)[0].classList.add("hidden"); +} + +function closeModal() { + $(`#dynamicModal`)[0].classList.add('modal-closing'); + setTimeout(() => { + $("#modalContainer").empty(); + }, 200); +} + +function openModal(id) { + showLoadingOverlay(); // Show loading overlay before fetching content + $("#modalContainer").load(`/modal?id=${id}`, function(responseTxt, statusTxt, xhr){ + hideLoadingOverlay(); // Hide loading overlay after content is fetched + if (statusTxt == "error") { + console.log("Error loading modal: " + xhr.status + ": " + xhr.statusText); + } + }); +} + +// Open a modal when a button is clicked +document.querySelectorAll(".open-modal-button").forEach(button => { + button.addEventListener("click", () => { + openModal(button.dataset.itemId); // Use the item ID from the button instead of the target, as it tends to be undefined + }); +}); \ No newline at end of file diff --git a/static/js/navbar.js b/static/js/navbar.js new file mode 100644 index 0000000..d53c4b4 --- /dev/null +++ b/static/js/navbar.js @@ -0,0 +1,42 @@ +const mobileMenuButton = document.getElementById('mobile-menu-button'); +const mobileMenu = document.getElementById('mobile-menu'); + +mobileMenuButton.addEventListener('click', function () { + const isExpanded = this.getAttribute('aria-expanded') === 'true'; + + this.setAttribute('aria-expanded', !isExpanded); + mobileMenu.classList.toggle('hidden'); + + // Toggle icons + const icons = this.getElementsByTagName('svg'); + icons[0].classList.toggle('hidden'); + icons[1].classList.toggle('hidden'); +}); + +const desktopDropdownButton = document.getElementById('desktop-dropdown-button'); +const desktopDropdownMenu = document.getElementById('desktop-dropdown-menu'); + +desktopDropdownButton.addEventListener('click', function (e) { + e.stopPropagation(); + desktopDropdownMenu.classList.toggle('hidden'); +}); + +const mobileDropdownButton = document.getElementById('mobile-dropdown-button'); +const mobileDropdownMenu = document.getElementById('mobile-dropdown-menu'); + +mobileDropdownButton.addEventListener('click', function () { + mobileDropdownMenu.classList.toggle('hidden'); +}); + +document.addEventListener('click', function (e) { + if (!desktopDropdownButton.contains(e.target)) { + desktopDropdownMenu.classList.add('hidden'); + } +}); + +document.addEventListener('keydown', function (e) { + if (e.key === 'Escape') { + desktopDropdownMenu.classList.add('hidden'); + mobileDropdownMenu.classList.add('hidden'); + } +}); \ No newline at end of file diff --git a/templates/developer_dashboard.html b/templates/developer_dashboard.html new file mode 100644 index 0000000..4f54453 --- /dev/null +++ b/templates/developer_dashboard.html @@ -0,0 +1,237 @@ + + + + + + + + +
+ + + + +
+ +
+

+ Overview +

+
+ + +
+
+

+ Total Games Created +

+

+ {{ count }} +

+
+
+ +
+

+ Logs +

+ + + + + + + + + + + + + + + + + + + + + + + +
+ Timestamp + + Class + + Description + + Status +
nilGame + Game ID 123 has been created! + Transaction Successful
nilgame + Game ID 123 have been deleted! + Transaction Successful
+
+
+
+ + diff --git a/templates/developer_dashboard_delete_game.html b/templates/developer_dashboard_delete_game.html new file mode 100644 index 0000000..214cf7e --- /dev/null +++ b/templates/developer_dashboard_delete_game.html @@ -0,0 +1,267 @@ + + + + + + + + + +
+ + + + +
+ +
+

You are attempting to delete the game {{ name }} with the UGI {{ ugi }}

+

Please confirm your action below.

+

Take a moment to observe the consequences and then confirm below if you really want to do this.

+

Please note that deleting this game is an inreversable action.

+

All game saves from all users will be deleted.

+
+
+ + + + + + + + Cancel + + +
+
+ + + + + + + +
+
+ + diff --git a/templates/developer_dashboard_game.html b/templates/developer_dashboard_game.html new file mode 100644 index 0000000..2c32426 --- /dev/null +++ b/templates/developer_dashboard_game.html @@ -0,0 +1,238 @@ + + + + + + + + +
+ + + + +
+ +
+

+ Games +

+
+ + +
+
+

+ Total Games Created +

+

+ {{ count }} +

+
+
+ + + +
+

+ Your Games +

+ + + + + + + + + {% for game in games %} + + + + + + {% endfor %} + +
+ Name + + UGI +
{{ game.name }}{{ game.ugi }} + + + +
+
+
+
+ + diff --git a/templates/developer_registered.html b/templates/developer_registered.html new file mode 100644 index 0000000..e69de29 diff --git a/templates/game_registered.html b/templates/game_registered.html new file mode 100644 index 0000000..e69de29 diff --git a/templates/index.html b/templates/index.html new file mode 100644 index 0000000..d33078d --- /dev/null +++ b/templates/index.html @@ -0,0 +1,366 @@ + + + + + + + + Home Page + + + + + + + + + + + + + + + + + +