game shelf rather than cards
This commit is contained in:
100
app/routes.py
100
app/routes.py
@@ -24,105 +24,13 @@ def index():
|
|||||||
games = Game.query.all()
|
games = Game.query.all()
|
||||||
return render_template('index.html', games=games)
|
return render_template('index.html', games=games)
|
||||||
|
|
||||||
@main_bp.route('/search', methods=['GET', 'POST'])
|
# Search functionality moved to admin area only
|
||||||
def search_games():
|
|
||||||
"""Search for games using IGDB API"""
|
|
||||||
results = []
|
|
||||||
query = ''
|
|
||||||
|
|
||||||
if request.method == 'POST':
|
|
||||||
query = request.form.get('query', '')
|
|
||||||
|
|
||||||
if query:
|
|
||||||
try:
|
|
||||||
results = igdb.search_games(query)
|
|
||||||
except Exception as e:
|
|
||||||
flash(f"Oopsie! Couldn't search IGDB: {str(e)}", 'error')
|
|
||||||
|
|
||||||
return render_template('search.html', results=results, query=query)
|
|
||||||
|
|
||||||
@main_bp.route('/add_game/<int:igdb_id>', methods=['GET', 'POST'])
|
# Add game functionality moved to admin area only
|
||||||
def add_game(igdb_id):
|
|
||||||
"""Add a game to tracking list"""
|
|
||||||
# Check if we already have this game
|
|
||||||
existing_game = Game.query.filter_by(igdb_id=igdb_id).first()
|
|
||||||
|
|
||||||
if existing_game:
|
|
||||||
flash(f"You'we alweady twacking {existing_game.name}!", 'info')
|
|
||||||
return redirect(url_for('main.index'))
|
|
||||||
|
|
||||||
# Check if we're at the 5 game limit
|
|
||||||
if Game.query.count() >= 5:
|
|
||||||
flash("You can onwy twack up to 5 games at once! Pwease wemove a game fiwst~", 'error')
|
|
||||||
return redirect(url_for('main.index'))
|
|
||||||
|
|
||||||
# Get game details from IGDB
|
|
||||||
try:
|
|
||||||
game_data = igdb.get_game_by_id(igdb_id)
|
|
||||||
|
|
||||||
# Create new Game object
|
|
||||||
new_game = Game(
|
|
||||||
igdb_id=game_data['id'],
|
|
||||||
name=game_data['name'],
|
|
||||||
cover_url=game_data['cover_url'],
|
|
||||||
description=game_data['description'],
|
|
||||||
release_date=game_data['release_date'],
|
|
||||||
platform=', '.join(game_data['platforms']) if game_data['platforms'] else None,
|
|
||||||
developer=game_data['developer'],
|
|
||||||
publisher=game_data['publisher'],
|
|
||||||
status="Playing",
|
|
||||||
progress=0
|
|
||||||
)
|
|
||||||
|
|
||||||
db.session.add(new_game)
|
|
||||||
db.session.commit()
|
|
||||||
|
|
||||||
flash(f"Added {new_game.name} to youw twacking wist! ^w^", 'success')
|
|
||||||
return redirect(url_for('main.index'))
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
flash(f"Faiwed to add game: {str(e)}", 'error')
|
|
||||||
return redirect(url_for('main.search_games'))
|
|
||||||
|
|
||||||
@main_bp.route('/update_game/<int:game_id>', methods=['POST'])
|
# Update game functionality moved to admin area only
|
||||||
def update_game(game_id):
|
|
||||||
"""Update game progress and status"""
|
|
||||||
game = Game.query.get_or_404(game_id)
|
|
||||||
|
|
||||||
if request.method == 'POST':
|
|
||||||
# Update status if provided
|
|
||||||
if 'status' in request.form:
|
|
||||||
game.status = request.form['status']
|
|
||||||
|
|
||||||
# Update progress if provided
|
|
||||||
if 'progress' in request.form:
|
|
||||||
try:
|
|
||||||
progress = int(request.form['progress'])
|
|
||||||
if 0 <= progress <= 100:
|
|
||||||
game.progress = progress
|
|
||||||
except ValueError:
|
|
||||||
flash("Pwogwess must be a numbew between 0 and 100!", 'error')
|
|
||||||
|
|
||||||
# Update notes if provided
|
|
||||||
if 'notes' in request.form:
|
|
||||||
game.notes = request.form['notes']
|
|
||||||
|
|
||||||
db.session.commit()
|
|
||||||
flash(f"Updated {game.name}! (。・ω・。)", 'success')
|
|
||||||
|
|
||||||
return redirect(url_for('main.index'))
|
|
||||||
|
|
||||||
@main_bp.route('/remove_game/<int:game_id>', methods=['POST'])
|
# Remove game functionality moved to admin area only
|
||||||
def remove_game(game_id):
|
|
||||||
"""Remove a game from tracking list"""
|
|
||||||
game = Game.query.get_or_404(game_id)
|
|
||||||
name = game.name
|
|
||||||
|
|
||||||
db.session.delete(game)
|
|
||||||
db.session.commit()
|
|
||||||
|
|
||||||
flash(f"Removed {name} from tracking list!", 'success')
|
|
||||||
return redirect(url_for('main.index'))
|
|
||||||
|
|
||||||
# Admin Routes
|
# Admin Routes
|
||||||
@main_bp.route('/admin/login', methods=['GET', 'POST'])
|
@main_bp.route('/admin/login', methods=['GET', 'POST'])
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
:root {
|
:root {
|
||||||
--primary-color: #3a0570;
|
--primary-color: #3a0570;
|
||||||
--primary-light: #6a35a0;
|
--primary-light: #6a35a0;
|
||||||
--primary-dark: #260050;
|
--primary-dark: #333333;
|
||||||
--accent-color: #9c27b0;
|
--accent-color: #b344c7;
|
||||||
--text-light: #f0f0f0;
|
--text-light: #f0f0f0;
|
||||||
--text-dark: #121212;
|
--text-dark: #121212;
|
||||||
--bg-dark: #121212;
|
--bg-dark: #121212;
|
||||||
@@ -11,7 +11,7 @@
|
|||||||
--border-color: #333333;
|
--border-color: #333333;
|
||||||
--danger-color: #f44336;
|
--danger-color: #f44336;
|
||||||
--success-color: #4caf50;
|
--success-color: #4caf50;
|
||||||
--info-color: #2196f3;
|
--info-color: #a621f3;
|
||||||
--warning-color: #ff9800;
|
--warning-color: #ff9800;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -22,7 +22,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
font-family: 'Nunito', sans-serif;
|
font-family: monospace;
|
||||||
background-color: var(--bg-dark);
|
background-color: var(--bg-dark);
|
||||||
color: var(--text-light);
|
color: var(--text-light);
|
||||||
line-height: 1.6;
|
line-height: 1.6;
|
||||||
@@ -152,11 +152,11 @@ footer .heart {
|
|||||||
/* Page header */
|
/* Page header */
|
||||||
.page-header {
|
.page-header {
|
||||||
margin-bottom: 2rem;
|
margin-bottom: 2rem;
|
||||||
|
margin-top: 2rem;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.page-header h1 {
|
.page-header h1 {
|
||||||
color: var(--accent-color);
|
|
||||||
font-size: 2.2rem;
|
font-size: 2.2rem;
|
||||||
margin-bottom: 0.2rem;
|
margin-bottom: 0.2rem;
|
||||||
}
|
}
|
||||||
@@ -222,9 +222,9 @@ footer .heart {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Game cards */
|
/* Game cards */
|
||||||
.game-cards {
|
.game-list {
|
||||||
display: grid;
|
display: flex;
|
||||||
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
flex-direction: column;
|
||||||
gap: 1.5rem;
|
gap: 1.5rem;
|
||||||
margin-bottom: 2rem;
|
margin-bottom: 2rem;
|
||||||
}
|
}
|
||||||
@@ -239,40 +239,49 @@ footer .heart {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.game-card:hover {
|
.game-card:hover {
|
||||||
transform: translateY(-5px);
|
transform: translateY(-3px);
|
||||||
box-shadow: 0 8px 15px rgba(0, 0, 0, 0.3);
|
box-shadow: 0 8px 15px rgba(0, 0, 0, 0.3);
|
||||||
background-color: var(--bg-card-hover);
|
background-color: var(--bg-card-hover);
|
||||||
}
|
}
|
||||||
|
|
||||||
.game-cover {
|
/* Horizontal card layout */
|
||||||
height: 200px;
|
.game-card.horizontal {
|
||||||
overflow: hidden;
|
display: flex;
|
||||||
|
align-items: stretch;
|
||||||
|
}
|
||||||
|
|
||||||
|
.game-cover-side {
|
||||||
|
width: 120px;
|
||||||
|
min-width: 120px;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
overflow: hidden;
|
||||||
|
margin: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.game-cover img {
|
.game-cover-side img {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: auto;
|
||||||
object-fit: cover;
|
object-fit: cover;
|
||||||
transition: transform 0.5s;
|
display: block;
|
||||||
|
border-radius: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.game-card:hover .game-cover img {
|
.no-cover-medium {
|
||||||
transform: scale(1.1);
|
width: 100%;
|
||||||
}
|
|
||||||
|
|
||||||
.no-cover {
|
|
||||||
height: 100%;
|
height: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
font-size: 4rem;
|
font-size: 2.5rem;
|
||||||
background: linear-gradient(45deg, var(--primary-dark), var(--primary-light));
|
|
||||||
color: rgba(255, 255, 255, 0.5);
|
color: rgba(255, 255, 255, 0.5);
|
||||||
}
|
}
|
||||||
|
|
||||||
.game-info {
|
.game-info {
|
||||||
padding: 1.2rem;
|
padding: 1.2rem;
|
||||||
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.game-info h2 {
|
.game-info h2 {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>{% block title %}Game Twacker{% endblock %}</title>
|
<title>{% block title %}Game Tracker{% endblock %}</title>
|
||||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/styles.css') }}">
|
<link rel="stylesheet" href="{{ url_for('static', filename='css/styles.css') }}">
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Nunito:wght@400;600;700&display=swap" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/css2?family=Nunito:wght@400;600;700&display=swap" rel="stylesheet">
|
||||||
<script src="https://kit.fontawesome.com/a076d05399.js" crossorigin="anonymous"></script>
|
<script src="https://kit.fontawesome.com/a076d05399.js" crossorigin="anonymous"></script>
|
||||||
@@ -11,11 +11,11 @@
|
|||||||
<body>
|
<body>
|
||||||
<header>
|
<header>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<h1><i class="fas fa-gamepad"></i> Game Twacker</h1>
|
<h1><i class="fas fa-gamepad"></i> Game Tracker</h1>
|
||||||
<nav>
|
<nav>
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="{{ url_for('main.index') }}">My Games</a></li>
|
<li><a href="{{ url_for('main.index') }}">Home</a></li>
|
||||||
<li><a href="{{ url_for('main.search_games') }}">Add New Game</a></li>
|
<li><a href="{{ url_for('main.admin_login') }}">Admin</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,28 +1,29 @@
|
|||||||
{% extends 'base.html' %}
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
{% block title %}My Games ~ Game Twacker{% endblock %}
|
{% block title %}What am I playing? ~ Game Tracker{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="page-header">
|
<div class="page-header">
|
||||||
<h1>My Pwaying Games</h1>
|
<h1>What am I playing?</h1>
|
||||||
<p class="subtitle">Twacking {{ games|length }}/5 games</p>
|
<p class="subtitle">Tracking {{ games|length }}/5 games</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% if games %}
|
{% if games %}
|
||||||
<div class="game-cards">
|
<div class="game-list">
|
||||||
{% for game in games %}
|
{% for game in games %}
|
||||||
<div class="game-card">
|
<div class="game-card horizontal">
|
||||||
<div class="game-cover">
|
<div class="game-cover-side">
|
||||||
{% if game.cover_url %}
|
{% if game.cover_url %}
|
||||||
<img src="{{ game.cover_url }}" alt="{{ game.name }} cover">
|
<img src="{{ game.cover_url }}" alt="{{ game.name }} cover">
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="no-cover">
|
<div class="no-cover-medium">
|
||||||
<i class="fas fa-gamepad"></i>
|
<i class="fas fa-gamepad"></i>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<div class="game-info">
|
<div class="game-info">
|
||||||
<h2>{{ game.name }}</h2>
|
<h2>{{ game.name }}</h2>
|
||||||
|
|
||||||
<div class="game-meta">
|
<div class="game-meta">
|
||||||
{% if game.developer %}
|
{% if game.developer %}
|
||||||
<span><i class="fas fa-code"></i> {{ game.developer }}</span>
|
<span><i class="fas fa-code"></i> {{ game.developer }}</span>
|
||||||
@@ -31,61 +32,22 @@
|
|||||||
<span><i class="fas fa-desktop"></i> {{ game.platform }}</span>
|
<span><i class="fas fa-desktop"></i> {{ game.platform }}</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="progress-container">
|
<div class="progress-container">
|
||||||
<div class="progress-label">
|
<div class="progress-label">
|
||||||
<span class="status-badge status-{{ game.status|lower }}">{{ game.status }}</span>
|
<span class="status-badge status-{{ game.status|lower|replace(' ', '-') }}">{{ game.status }}</span>
|
||||||
<span class="progress-value">{{ game.progress }}%</span>
|
<span class="progress-value">{{ game.progress }}%</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="progress-bar">
|
<div class="progress-bar">
|
||||||
<div class="progress-fill" style="width: {{ game.progress }}%"></div>
|
<div class="progress-fill" style="width: {{ game.progress }}%;"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<details class="game-details">
|
{% if game.notes %}
|
||||||
<summary>Update Pwogwess</summary>
|
<div class="game-notes">
|
||||||
<form action="{{ url_for('main.update_game', game_id=game.id) }}" method="POST" class="update-form">
|
<p><strong>Notes:</strong> {{ game.notes }}</p>
|
||||||
<div class="form-group">
|
|
||||||
<label for="status-{{ game.id }}">Status:</label>
|
|
||||||
<select name="status" id="status-{{ game.id }}">
|
|
||||||
<option value="Playing" {% if game.status == 'Playing' %}selected{% endif %}>Playing</option>
|
|
||||||
<option value="Completed" {% if game.status == 'Completed' %}selected{% endif %}>Completed</option>
|
|
||||||
<option value="On Hold" {% if game.status == 'On Hold' %}selected{% endif %}>On Hold</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="progress-{{ game.id }}">Pwogwess:</label>
|
|
||||||
<input type="range" name="progress" id="progress-{{ game.id }}" min="0" max="100" value="{{ game.progress }}" class="progress-slider">
|
|
||||||
<span class="range-value">{{ game.progress }}%</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="notes-{{ game.id }}">Notes:</label>
|
|
||||||
<textarea name="notes" id="notes-{{ game.id }}" rows="2">{{ game.notes or '' }}</textarea>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-actions">
|
|
||||||
<button type="submit" class="btn btn-primary">Update</button>
|
|
||||||
<button type="button" class="btn btn-danger remove-btn" data-toggle="modal" data-target="#remove-modal-{{ game.id }}">
|
|
||||||
Wemove Game
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</details>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Remove confirmation modal -->
|
|
||||||
<div class="modal" id="remove-modal-{{ game.id }}">
|
|
||||||
<div class="modal-content">
|
|
||||||
<h3>Wemove Game</h3>
|
|
||||||
<p>Awe you suwe you want to wemove <strong>{{ game.name }}</strong> from youw twacking wist?</p>
|
|
||||||
<div class="modal-actions">
|
|
||||||
<button class="btn btn-secondary close-modal">Cancew</button>
|
|
||||||
<form action="{{ url_for('main.remove_game', game_id=game.id) }}" method="POST">
|
|
||||||
<button type="submit" class="btn btn-danger">Wemove</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
@@ -96,58 +58,12 @@
|
|||||||
<div class="empty-icon">
|
<div class="empty-icon">
|
||||||
<i class="fas fa-gamepad"></i>
|
<i class="fas fa-gamepad"></i>
|
||||||
</div>
|
</div>
|
||||||
<h2>No games being twacked yet~ >w<</h2>
|
<h2>No games being tracked yet</h2>
|
||||||
<p>Click the button bewow to seawch and add games to twack!</p>
|
<p>Ask an administrator to add games to your tracking list</p>
|
||||||
<a href="{{ url_for('main.search_games') }}" class="btn btn-primary">
|
|
||||||
<i class="fas fa-plus"></i> Add Youw Fiwst Game
|
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if games|length < 5 %}
|
|
||||||
<div class="add-more">
|
|
||||||
<a href="{{ url_for('main.search_games') }}" class="btn btn-primary">
|
|
||||||
<i class="fas fa-plus"></i> Add Anothew Game
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<script>
|
|
||||||
// Handle range sliders
|
<!-- No scripts needed for read-only view -->
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
|
||||||
const sliders = document.querySelectorAll('.progress-slider');
|
|
||||||
sliders.forEach(slider => {
|
|
||||||
const valueDisplay = slider.nextElementSibling;
|
|
||||||
|
|
||||||
slider.addEventListener('input', function() {
|
|
||||||
valueDisplay.textContent = this.value + '%';
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Modal handling
|
|
||||||
const modals = document.querySelectorAll('.modal');
|
|
||||||
const removeBtns = document.querySelectorAll('.remove-btn');
|
|
||||||
const closeBtns = document.querySelectorAll('.close-modal');
|
|
||||||
|
|
||||||
removeBtns.forEach((btn, index) => {
|
|
||||||
btn.addEventListener('click', function() {
|
|
||||||
modals[index].classList.add('active');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
closeBtns.forEach((btn, index) => {
|
|
||||||
btn.addEventListener('click', function() {
|
|
||||||
modals[index].classList.remove('active');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
window.addEventListener('click', function(e) {
|
|
||||||
modals.forEach(modal => {
|
|
||||||
if (e.target === modal) {
|
|
||||||
modal.classList.remove('active');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
Reference in New Issue
Block a user