Add Spotify integration with improved token management and user interface
- Implemented Spotify OAuth handling in spotify_client_manager.py to ensure valid access tokens for users. - Created helper functions in spotify_helper.py for refreshing tokens and retrieving user information. - Developed manage_spotify.html template for connecting and managing Spotify accounts, displaying connection status and token information. - Added logging for token management processes to enhance debugging and monitoring. - Introduced a debug client for Spotify interactions to facilitate easier testing and development.
This commit is contained in:
@@ -201,9 +201,23 @@
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<main class="flex-grow container mx-auto px-4 py-6">
|
||||
</header> <main class="flex-grow container mx-auto px-4 py-6">
|
||||
<!-- Flash Messages - Global display for all pages -->
|
||||
{% with messages = get_flashed_messages(with_categories=true) %}
|
||||
{% if messages %}
|
||||
<div class="mb-6">
|
||||
{% for category, message in messages %}
|
||||
<div class="mb-4 p-3 {% if category == 'danger' %}bg-red-100 text-red-700{% elif category == 'success' %}bg-green-100 text-green-700{% elif category == 'warning' %}bg-yellow-100 text-yellow-700{% elif category == 'info' %}bg-blue-100 text-blue-700{% else %}bg-blue-100 text-blue-700{% endif %} rounded shadow-sm">
|
||||
<div class="flex items-center justify-between">
|
||||
<span>{{ message }}</span>
|
||||
<button onclick="this.parentElement.parentElement.style.display='none'" class="text-lg leading-none hover:opacity-75 ml-2">×</button>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
|
||||
{% block content %}
|
||||
{% endblock %}
|
||||
</main>
|
||||
|
||||
@@ -7,19 +7,7 @@
|
||||
<div class="mb-8">
|
||||
<h1 class="text-3xl font-bold text-navy-800 font-montserrat mb-2">Import Playlist</h1>
|
||||
<p class="text-gray-600">Create a music quiz round from a Spotify or Deezer playlist.</p>
|
||||
</div>
|
||||
|
||||
<div class="bg-white shadow-md rounded-lg p-6">
|
||||
{% with messages = get_flashed_messages(with_categories=true) %}
|
||||
{% if messages %}
|
||||
{% for category, message in messages %}
|
||||
<div class="mb-4 p-4 border-l-4 {% if category == 'error' %}border-red-500 bg-red-50 text-red-700{% else %}border-green-500 bg-green-50 text-green-700{% endif %}">
|
||||
{{ message }}
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
|
||||
</div> <div class="bg-white shadow-md rounded-lg p-6">
|
||||
<form method="POST" action="{{ url_for('generate.import_playlist') }}" id="importPlaylistForm">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
|
||||
|
||||
|
||||
@@ -538,17 +538,7 @@
|
||||
|
||||
// Close toast button event
|
||||
toastCloseBtn.addEventListener('click', hideToast);
|
||||
|
||||
// Process flash messages from server
|
||||
{% with messages = get_flashed_messages(with_categories=true) %}
|
||||
{% if messages %}
|
||||
{% for category, message in messages %}
|
||||
showToast('{{ message|safe }}', '{{ category }}');
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
|
||||
// Process email error if any
|
||||
// Process email error if any
|
||||
{% if email_error %}
|
||||
showToast('{{ email_error }}', 'error');
|
||||
{% endif %}
|
||||
|
||||
@@ -1,172 +0,0 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block title %}Spotify Client Test{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="max-w-7xl mx-auto px-4 py-8">
|
||||
<h1 class="text-3xl font-bold mb-2 text-navy-800 font-montserrat">Spotify Client Comparison</h1>
|
||||
<p class="text-gray-600 mb-6">Comparing spotipy library vs direct API implementation.</p>
|
||||
|
||||
<!-- Account Selection -->
|
||||
<div class="bg-white shadow-md rounded-lg p-6 mb-8">
|
||||
<form method="GET" action="{{ url_for('import.test_spotify_client') }}" class="space-y-4">
|
||||
<div>
|
||||
<label for="account" class="block text-sm font-medium text-gray-700 mb-1">Spotify Account</label>
|
||||
<select name="account" id="account"
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-teal-500 focus:border-teal-500">
|
||||
<option value="spotify" {% if account == 'spotify' %}selected{% endif %}>spotify</option>
|
||||
<option value="spotifycharts" {% if account == 'spotifycharts' %}selected{% endif %}>spotifycharts</option>
|
||||
<option value="spotifymaps" {% if account == 'spotifymaps' %}selected{% endif %}>spotifymaps</option>
|
||||
<option value="spotifyuk" {% if account == 'spotifyuk' %}selected{% endif %}>spotifyuk</option>
|
||||
<option value="spotifyusa" {% if account == 'spotifyusa' %}selected{% endif %}>spotifyusa</option>
|
||||
<option value="spotify_germany" {% if account == 'spotify_germany' %}selected{% endif %}>spotify_germany</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<button type="submit" class="bg-orange-500 hover:bg-orange-600 text-white py-2 px-4 rounded-md shadow-sm transition-colors">
|
||||
Test Account
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Results Summary -->
|
||||
<div class="bg-white shadow-md rounded-lg p-6 mb-8">
|
||||
<h2 class="text-xl font-bold mb-4">Results Summary</h2>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<!-- Spotipy Results -->
|
||||
<div class="border border-gray-200 rounded-lg p-4">
|
||||
<h3 class="text-lg font-semibold mb-2">Spotipy Implementation</h3>
|
||||
<div class="space-y-2">
|
||||
<div class="flex justify-between border-b border-gray-100 pb-1">
|
||||
<span class="text-gray-700">Playlists Retrieved:</span>
|
||||
<span class="font-medium">{{ results.spotipy.count }}</span>
|
||||
</div>
|
||||
<div class="flex justify-between border-b border-gray-100 pb-1">
|
||||
<span class="text-gray-700">Expected Total:</span>
|
||||
<span class="font-medium">{{ results.spotipy.total }}</span>
|
||||
</div>
|
||||
<div class="flex justify-between border-b border-gray-100 pb-1">
|
||||
<span class="text-gray-700">Execution Time:</span>
|
||||
<span class="font-medium">{{ results.spotipy.time_ms }} ms</span>
|
||||
</div>
|
||||
{% if results.spotipy.error %}
|
||||
<div class="mt-2 p-2 bg-red-50 text-red-700 rounded">
|
||||
<strong>Error:</strong> {{ results.spotipy.error }}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Direct API Results -->
|
||||
<div class="border border-gray-200 rounded-lg p-4">
|
||||
<h3 class="text-lg font-semibold mb-2">Direct API Implementation</h3>
|
||||
<div class="space-y-2">
|
||||
<div class="flex justify-between border-b border-gray-100 pb-1">
|
||||
<span class="text-gray-700">Playlists Retrieved:</span>
|
||||
<span class="font-medium">{{ results.direct.count }}</span>
|
||||
</div>
|
||||
<div class="flex justify-between border-b border-gray-100 pb-1">
|
||||
<span class="text-gray-700">Expected Total:</span>
|
||||
<span class="font-medium">{{ results.direct.total }}</span>
|
||||
</div>
|
||||
<div class="flex justify-between border-b border-gray-100 pb-1">
|
||||
<span class="text-gray-700">Execution Time:</span>
|
||||
<span class="font-medium">{{ results.direct.time_ms }} ms</span>
|
||||
</div>
|
||||
{% if results.direct.error %}
|
||||
<div class="mt-2 p-2 bg-red-50 text-red-700 rounded">
|
||||
<strong>Error:</strong> {{ results.direct.error }}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Comparison -->
|
||||
<div class="bg-white shadow-md rounded-lg p-6 mb-8">
|
||||
<h2 class="text-xl font-bold mb-4">Implementation Comparison</h2>
|
||||
|
||||
<div class="space-y-4">
|
||||
<div class="flex items-center">
|
||||
<div class="w-1/3 font-medium">Playlists in both implementations:</div>
|
||||
<div class="w-2/3">{{ comparison.in_both|length }}</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center">
|
||||
<div class="w-1/3 font-medium">Only in spotipy:</div>
|
||||
<div class="w-2/3">{{ comparison.only_in_spotipy|length }}</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center">
|
||||
<div class="w-1/3 font-medium">Only in direct API:</div>
|
||||
<div class="w-2/3">{{ comparison.only_in_direct|length }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Playlist Details -->
|
||||
<div class="flex flex-col md:flex-row gap-6">
|
||||
<!-- Spotipy Playlists -->
|
||||
<div class="w-full md:w-1/2">
|
||||
<div class="bg-white shadow-md rounded-lg p-6">
|
||||
<h3 class="text-lg font-semibold mb-4">Spotipy Playlists ({{ results.spotipy.count }})</h3>
|
||||
{% if results.spotipy.playlists %}
|
||||
<div class="overflow-y-auto max-h-96">
|
||||
<table class="min-w-full">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="px-4 py-2 border-b text-left">#</th>
|
||||
<th class="px-4 py-2 border-b text-left">Name</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for playlist in results.spotipy.playlists %}
|
||||
<tr>
|
||||
<td class="px-4 py-2 border-b">{{ loop.index }}</td>
|
||||
<td class="px-4 py-2 border-b">{{ playlist.name }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% else %}
|
||||
<p class="text-gray-500">No playlists retrieved.</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Direct API Playlists -->
|
||||
<div class="w-full md:w-1/2">
|
||||
<div class="bg-white shadow-md rounded-lg p-6">
|
||||
<h3 class="text-lg font-semibold mb-4">Direct API Playlists ({{ results.direct.count }})</h3>
|
||||
{% if results.direct.playlists %}
|
||||
<div class="overflow-y-auto max-h-96">
|
||||
<table class="min-w-full">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="px-4 py-2 border-b text-left">#</th>
|
||||
<th class="px-4 py-2 border-b text-left">Name</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for playlist in results.direct.playlists %}
|
||||
<tr>
|
||||
<td class="px-4 py-2 border-b">{{ loop.index }}</td>
|
||||
<td class="px-4 py-2 border-b">{{ playlist.name }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% else %}
|
||||
<p class="text-gray-500">No playlists retrieved.</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -9,12 +9,6 @@
|
||||
<p class="text-gray-600">Upload or generate your own intro, outro, and replay announcements for quizzes.</p>
|
||||
</div>
|
||||
|
||||
{% for category, message in get_flashed_messages(with_categories=true) %}
|
||||
<div class="mb-6 p-4 rounded {% if category == 'danger' %}bg-red-50 text-red-700 border border-red-300{% elif category == 'success' %}bg-green-50 text-green-700 border border-green-300{% else %}bg-blue-50 text-blue-700 border border-blue-300{% endif %}">
|
||||
{{ message }}
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
<!-- Intro Audio Section -->
|
||||
<div class="bg-white rounded-lg shadow-md overflow-hidden mb-8">
|
||||
<div class="bg-navy-50 p-4 border-b">
|
||||
|
||||
@@ -6,16 +6,6 @@
|
||||
<div class="max-w-md mx-auto my-8 p-6 bg-white rounded-lg shadow-md">
|
||||
<h2 class="text-2xl font-bold mb-6 text-navy-800">Change Password</h2>
|
||||
|
||||
{% with messages = get_flashed_messages(with_categories=true) %}
|
||||
{% if messages %}
|
||||
{% for category, message in messages %}
|
||||
<div class="mb-4 p-3 {% if category == 'danger' %}bg-red-100 text-red-700{% elif category == 'success' %}bg-green-100 text-green-700{% else %}bg-blue-100 text-blue-700{% endif %} rounded">
|
||||
{{ message }}
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
|
||||
<form method="POST" action="{{ url_for('users.change_password') }}">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
|
||||
|
||||
|
||||
@@ -6,16 +6,6 @@
|
||||
<div class="max-w-md mx-auto my-8 p-6 bg-white rounded-lg shadow-md">
|
||||
<h2 class="text-2xl font-bold mb-6 text-navy-800">Edit Profile</h2>
|
||||
|
||||
{% with messages = get_flashed_messages(with_categories=true) %}
|
||||
{% if messages %}
|
||||
{% for category, message in messages %}
|
||||
<div class="mb-4 p-3 {% if category == 'danger' %}bg-red-100 text-red-700{% elif category == 'success' %}bg-green-100 text-green-700{% else %}bg-blue-100 text-blue-700{% endif %} rounded">
|
||||
{{ message }}
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
|
||||
<form method="POST" action="{{ url_for('users.edit_profile') }}">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
|
||||
|
||||
|
||||
@@ -13,14 +13,7 @@
|
||||
Enter your email address and we'll send a reset link
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="mt-8 bg-white py-8 px-6 shadow-md rounded-lg">
|
||||
{% for category, message in get_flashed_messages(with_categories=true) %}
|
||||
<div class="mb-4 p-4 rounded-md {% if category == 'danger' %}bg-red-50 text-red-700{% elif category == 'success' %}bg-green-50 text-green-700{% else %}bg-blue-50 text-blue-700{% endif %}">
|
||||
{{ message }}
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
<div class="mt-8 bg-white py-8 px-6 shadow-md rounded-lg">
|
||||
<form class="space-y-6" action="{{ url_for('users.forgot_password') }}" method="POST">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
|
||||
|
||||
|
||||
@@ -6,16 +6,6 @@
|
||||
<div class="max-w-md mx-auto my-8 p-6 bg-white rounded-lg shadow-md">
|
||||
<h2 class="text-2xl font-bold mb-6 text-navy-800">Login to Quizzical Beats</h2>
|
||||
|
||||
{% with messages = get_flashed_messages(with_categories=true) %}
|
||||
{% if messages %}
|
||||
{% for category, message in messages %}
|
||||
<div class="mb-4 p-3 {% if category == 'danger' %}bg-red-100 text-red-700{% elif category == 'success' %}bg-green-100 text-green-700{% else %}bg-blue-100 text-blue-700{% endif %} rounded">
|
||||
{{ message }}
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
|
||||
<form method="POST" action="{{ url_for('users.login') }}">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
|
||||
|
||||
|
||||
@@ -0,0 +1,142 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block title %}Connect Spotify - Quizzical Beats{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="max-w-4xl mx-auto my-8 p-6 bg-white rounded-lg shadow-md">
|
||||
<h2 class="text-2xl font-bold mb-6 text-navy-800">Connect Spotify Account</h2>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-8">
|
||||
<!-- Left column: Current status -->
|
||||
<div class="bg-gray-50 p-6 rounded-lg">
|
||||
<h3 class="text-lg font-semibold mb-4 text-navy-700">Spotify Connection Status</h3>
|
||||
|
||||
{% if current_user.spotify_token %}
|
||||
<div class="mb-6 flex items-center">
|
||||
<div class="mr-4 bg-green-100 p-3 rounded-full">
|
||||
<i class="fab fa-spotify text-2xl text-green-600"></i>
|
||||
</div>
|
||||
<div>
|
||||
<p class="font-medium text-green-700">Connected to Spotify</p>
|
||||
<p class="text-sm text-gray-600">Your account is linked to Spotify</p>
|
||||
{% if spotify_user_info %}
|
||||
<p class="text-sm text-gray-600 mt-1">Spotify ID: {{ spotify_user_info.id }}</p>
|
||||
<p class="text-sm text-gray-600 mt-1">Display Name: {{ spotify_user_info.display_name }}</p>
|
||||
{% if spotify_user_info.email %}
|
||||
<p class="text-sm text-gray-600 mt-1">Email: {{ spotify_user_info.email }}</p>
|
||||
{% endif %}
|
||||
{% if spotify_user_info.images and spotify_user_info.images[0] %}
|
||||
<img src="{{ spotify_user_info.images[0].url }}" alt="Spotify Profile Image" class="rounded-full mt-2" style="width:64px;height:64px;">
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h4 class="text-md font-medium mb-2">Token Information</h4>
|
||||
<div class="bg-white p-4 rounded-lg border border-gray-200 text-sm">
|
||||
<p class="flex justify-between mb-2">
|
||||
<span class="font-medium">Token Status:</span>
|
||||
<span class="{% if current_user.spotify_token_expiry and current_user.spotify_token_expiry > now %}text-green-600{% else %}text-red-600{% endif %}">
|
||||
{% if current_user.spotify_token_expiry and current_user.spotify_token_expiry > now %}
|
||||
Valid
|
||||
{% else %}
|
||||
Expired
|
||||
{% endif %}
|
||||
</span>
|
||||
</p>
|
||||
{% if current_user.spotify_token_expiry %}
|
||||
<p class="flex justify-between">
|
||||
<span class="font-medium">Expires:</span>
|
||||
<span>{{ current_user.spotify_token_expiry.strftime('%Y-%m-%d %H:%M:%S') }}</span>
|
||||
</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form method="POST" action="{{ url_for('users.spotify_disconnect') }}" class="mt-6">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
|
||||
<button type="submit" class="bg-red-500 hover:bg-red-600 text-white py-2 px-4 rounded-md font-medium">
|
||||
<i class="fas fa-unlink mr-2"></i> Disconnect Spotify
|
||||
</button>
|
||||
</form>
|
||||
|
||||
{% else %}
|
||||
<div class="mb-6 flex items-center">
|
||||
<div class="mr-4 bg-gray-200 p-3 rounded-full">
|
||||
<i class="fab fa-spotify text-2xl text-gray-500"></i>
|
||||
</div>
|
||||
<div>
|
||||
<p class="font-medium text-gray-700">Not Connected</p>
|
||||
<p class="text-sm text-gray-600">Your account is not linked to Spotify</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form method="POST" action="{{ url_for('users.spotify_link') }}">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
|
||||
<button type="submit" class="inline-block bg-[#1DB954] hover:bg-[#1ed760] text-white font-medium py-2 px-4 rounded-md">
|
||||
<i class="fab fa-spotify mr-2"></i> Connect with Spotify
|
||||
</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- Right column: Info and benefits -->
|
||||
<div>
|
||||
<h3 class="text-lg font-semibold mb-4 text-navy-700">Why Connect Spotify?</h3>
|
||||
|
||||
<div class="bg-teal-50 border-l-4 border-teal-500 p-4 rounded mb-6">
|
||||
<p class="text-teal-700">
|
||||
Connecting your Spotify account enhances your music quiz creation experience
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="space-y-4">
|
||||
<div class="flex">
|
||||
<div class="mr-3 text-teal-500">
|
||||
<i class="fas fa-music"></i>
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="font-medium">Access Your Playlists</h4>
|
||||
<p class="text-gray-600 text-sm">Import songs from your personal Spotify playlists</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex">
|
||||
<div class="mr-3 text-teal-500">
|
||||
<i class="fas fa-search"></i>
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="font-medium">Search the Spotify Catalog</h4>
|
||||
<p class="text-gray-600 text-sm">Find and import any track from Spotify's huge library</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex">
|
||||
<div class="mr-3 text-teal-500">
|
||||
<i class="fas fa-chart-line"></i>
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="font-medium">Trending Playlists</h4>
|
||||
<p class="text-gray-600 text-sm">Access Spotify's official and trending playlists</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-8 bg-gray-50 p-4 rounded-lg">
|
||||
<h4 class="font-medium mb-2">Privacy Note</h4>
|
||||
<p class="text-sm text-gray-600">
|
||||
We only access your Spotify data to help you create music quizzes.
|
||||
We don't share your information or post to your account.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-6 pt-6 border-t border-gray-200">
|
||||
<a href="{{ url_for('users.profile') }}" class="text-teal-600 hover:text-teal-800">
|
||||
<i class="fas fa-arrow-left mr-2"></i> Back to Profile
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -6,16 +6,6 @@
|
||||
<div class="max-w-4xl mx-auto my-8 p-6 bg-white rounded-lg shadow-md">
|
||||
<h2 class="text-2xl font-bold mb-6 text-navy-800">My Profile</h2>
|
||||
|
||||
{% with messages = get_flashed_messages(with_categories=true) %}
|
||||
{% if messages %}
|
||||
{% for category, message in messages %}
|
||||
<div class="mb-4 p-3 {% if category == 'danger' %}bg-red-100 text-red-700{% elif category == 'success' %}bg-green-100 text-green-700{% else %}bg-blue-100 text-blue-700{% endif %} rounded">
|
||||
{{ message }}
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||
<!-- Profile Info -->
|
||||
<div class="md:col-span-2">
|
||||
|
||||
@@ -6,16 +6,6 @@
|
||||
<div class="max-w-md mx-auto my-8 p-6 bg-white rounded-lg shadow-md">
|
||||
<h2 class="text-2xl font-bold mb-6 text-navy-800">Create an Account</h2>
|
||||
|
||||
{% with messages = get_flashed_messages(with_categories=true) %}
|
||||
{% if messages %}
|
||||
{% for category, message in messages %}
|
||||
<div class="mb-4 p-3 {% if category == 'danger' %}bg-red-100 text-red-700{% elif category == 'success' %}bg-green-100 text-green-700{% else %}bg-blue-100 text-blue-700{% endif %} rounded">
|
||||
{{ message }}
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
|
||||
<form method="POST" action="{{ url_for('users.register') }}">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
|
||||
|
||||
|
||||
@@ -13,14 +13,7 @@
|
||||
Please enter your new password below
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="mt-8 bg-white py-8 px-6 shadow-md rounded-lg">
|
||||
{% for category, message in get_flashed_messages(with_categories=true) %}
|
||||
<div class="mb-4 p-4 rounded-md {% if category == 'danger' %}bg-red-50 text-red-700{% elif category == 'success' %}bg-green-50 text-green-700{% else %}bg-blue-50 text-blue-700{% endif %}">
|
||||
{{ message }}
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
<div class="mt-8 bg-white py-8 px-6 shadow-md rounded-lg">
|
||||
<form class="space-y-6" action="{{ url_for('users.reset_password', token=token) }}" method="POST">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
|
||||
|
||||
|
||||
@@ -1,143 +0,0 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block title %}Connect Spotify - Quizzical Beats{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="max-w-4xl mx-auto my-8 p-6 bg-white rounded-lg shadow-md">
|
||||
<h2 class="text-2xl font-bold mb-6 text-navy-800">Connect Spotify Account</h2>
|
||||
|
||||
{% with messages = get_flashed_messages(with_categories=true) %}
|
||||
{% if messages %}
|
||||
{% for category, message in messages %}
|
||||
<div class="mb-4 p-3 {% if category == 'danger' %}bg-red-100 text-red-700{% elif category == 'success' %}bg-green-100 text-green-700{% else %}bg-blue-100 text-blue-700{% endif %} rounded">
|
||||
{{ message }}
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-8">
|
||||
<!-- Left column: Current status -->
|
||||
<div class="bg-gray-50 p-6 rounded-lg">
|
||||
<h3 class="text-lg font-semibold mb-4 text-navy-700">Spotify Connection Status</h3>
|
||||
|
||||
{% if current_user.spotify_token %}
|
||||
<div class="mb-6 flex items-center">
|
||||
<div class="mr-4 bg-green-100 p-3 rounded-full">
|
||||
<i class="fab fa-spotify text-2xl text-green-600"></i>
|
||||
</div>
|
||||
<div>
|
||||
<p class="font-medium text-green-700">Connected to Spotify</p>
|
||||
<p class="text-sm text-gray-600">Your account is linked to Spotify</p>
|
||||
{% if current_user.oauth_id %}
|
||||
<p class="text-sm text-gray-600 mt-1">Spotify ID: {{ current_user.oauth_id }}</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h4 class="text-md font-medium mb-2">Token Information</h4>
|
||||
<div class="bg-white p-4 rounded-lg border border-gray-200 text-sm">
|
||||
<p class="flex justify-between mb-2">
|
||||
<span class="font-medium">Token Status:</span>
|
||||
<span class="{% if current_user.spotify_token_expiry and current_user.spotify_token_expiry > now %}text-green-600{% else %}text-red-600{% endif %}">
|
||||
{% if current_user.spotify_token_expiry and current_user.spotify_token_expiry > now %}
|
||||
Valid
|
||||
{% else %}
|
||||
Expired
|
||||
{% endif %}
|
||||
</span>
|
||||
</p>
|
||||
{% if current_user.spotify_token_expiry %}
|
||||
<p class="flex justify-between">
|
||||
<span class="font-medium">Expires:</span>
|
||||
<span>{{ current_user.spotify_token_expiry.strftime('%Y-%m-%d %H:%M:%S') }}</span>
|
||||
</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form method="POST" action="{{ url_for('users.spotify_link') }}" class="mt-6">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
|
||||
<input type="hidden" name="action" value="disconnect">
|
||||
<button type="submit" class="bg-red-500 hover:bg-red-600 text-white py-2 px-4 rounded-md font-medium">
|
||||
<i class="fas fa-unlink mr-2"></i> Disconnect Spotify
|
||||
</button>
|
||||
</form>
|
||||
|
||||
{% else %}
|
||||
<div class="mb-6 flex items-center">
|
||||
<div class="mr-4 bg-gray-200 p-3 rounded-full">
|
||||
<i class="fab fa-spotify text-2xl text-gray-500"></i>
|
||||
</div>
|
||||
<div>
|
||||
<p class="font-medium text-gray-700">Not Connected</p>
|
||||
<p class="text-sm text-gray-600">Your account is not linked to Spotify</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<a href="{{ url_for('users.spotify_auth') }}" class="inline-block bg-[#1DB954] hover:bg-[#1ed760] text-white font-medium py-2 px-4 rounded-md">
|
||||
<i class="fab fa-spotify mr-2"></i> Connect with Spotify
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- Right column: Info and benefits -->
|
||||
<div>
|
||||
<h3 class="text-lg font-semibold mb-4 text-navy-700">Why Connect Spotify?</h3>
|
||||
|
||||
<div class="bg-teal-50 border-l-4 border-teal-500 p-4 rounded mb-6">
|
||||
<p class="text-teal-700">
|
||||
Connecting your Spotify account enhances your music quiz creation experience
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="space-y-4">
|
||||
<div class="flex">
|
||||
<div class="mr-3 text-teal-500">
|
||||
<i class="fas fa-music"></i>
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="font-medium">Access Your Playlists</h4>
|
||||
<p class="text-gray-600 text-sm">Import songs from your personal Spotify playlists</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex">
|
||||
<div class="mr-3 text-teal-500">
|
||||
<i class="fas fa-search"></i>
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="font-medium">Search the Spotify Catalog</h4>
|
||||
<p class="text-gray-600 text-sm">Find and import any track from Spotify's huge library</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex">
|
||||
<div class="mr-3 text-teal-500">
|
||||
<i class="fas fa-chart-line"></i>
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="font-medium">Trending Playlists</h4>
|
||||
<p class="text-gray-600 text-sm">Access Spotify's official and trending playlists</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-8 bg-gray-50 p-4 rounded-lg">
|
||||
<h4 class="font-medium mb-2">Privacy Note</h4>
|
||||
<p class="text-sm text-gray-600">
|
||||
We only access your Spotify data to help you create music quizzes.
|
||||
We don't share your information or post to your account.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-6 pt-6 border-t border-gray-200">
|
||||
<a href="{{ url_for('users.profile') }}" class="text-teal-600 hover:text-teal-800">
|
||||
<i class="fas fa-arrow-left mr-2"></i> Back to Profile
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user