Fixes to trade expansion and trade acceptance appearance

This commit is contained in:
badblocks 2025-04-03 17:09:51 -07:00
parent 63e20bace6
commit 01becbee48
20 changed files with 105 additions and 148 deletions

View file

@ -1,6 +1,5 @@
from django.urls import path from django.urls import path
from .views import ( from .views import (
ListFriendCodesView,
AddFriendCodeView, AddFriendCodeView,
DeleteFriendCodeView, DeleteFriendCodeView,
ChangeDefaultFriendCodeView, ChangeDefaultFriendCodeView,
@ -9,8 +8,6 @@ from .views import (
) )
urlpatterns = [ urlpatterns = [
# ... other account URLs ...
path("friend-codes/", ListFriendCodesView.as_view(), name="list_friend_codes"),
path("friend-codes/add/", AddFriendCodeView.as_view(), name="add_friend_code"), path("friend-codes/add/", AddFriendCodeView.as_view(), name="add_friend_code"),
path("friend-codes/edit/<int:pk>/", EditFriendCodeView.as_view(), name="edit_friend_code"), path("friend-codes/edit/<int:pk>/", EditFriendCodeView.as_view(), name="edit_friend_code"),
path("friend-codes/delete/<int:pk>/", DeleteFriendCodeView.as_view(), name="delete_friend_code"), path("friend-codes/delete/<int:pk>/", DeleteFriendCodeView.as_view(), name="delete_friend_code"),

View file

@ -10,27 +10,8 @@ from trades.models import TradeOffer, TradeAcceptance
from django.core.exceptions import PermissionDenied from django.core.exceptions import PermissionDenied
from trades.mixins import FriendCodeRequiredMixin from trades.mixins import FriendCodeRequiredMixin
from common.mixins import ReusablePaginationMixin from common.mixins import ReusablePaginationMixin
from django.urls import reverse
class ListFriendCodesView(LoginRequiredMixin, ListView): from django.utils.http import urlencode
"""
Display the current user's friend codes.
"""
model = FriendCode
template_name = "friend_codes/list_friend_codes.html"
context_object_name = "friend_codes"
def get_queryset(self):
# Get the default friend code's primary key if it exists.
default_pk = getattr(self.request.user.default_friend_code, "pk", None)
# Annotate each friend code with is_default=True if its pk matches.
return self.request.user.friend_codes.all().annotate(
is_default=Case(
When(pk=default_pk, then=Value(True)),
default=Value(False),
output_field=BooleanField()
)
)
class AddFriendCodeView(LoginRequiredMixin, CreateView): class AddFriendCodeView(LoginRequiredMixin, CreateView):
""" """
@ -40,7 +21,9 @@ class AddFriendCodeView(LoginRequiredMixin, CreateView):
model = FriendCode model = FriendCode
form_class = FriendCodeForm form_class = FriendCodeForm
template_name = "friend_codes/add_friend_code.html" template_name = "friend_codes/add_friend_code.html"
success_url = reverse_lazy("list_friend_codes") def get_success_url(self):
base_url = reverse("dashboard")
return f"{base_url}?{urlencode({'tab': 'friend_codes'})}"
def form_valid(self, form): def form_valid(self, form):
form.instance.user = self.request.user form.instance.user = self.request.user
@ -57,7 +40,9 @@ class DeleteFriendCodeView(LoginRequiredMixin, DeleteView):
model = FriendCode model = FriendCode
template_name = "friend_codes/confirm_delete_friend_code.html" template_name = "friend_codes/confirm_delete_friend_code.html"
context_object_name = "friend_code" context_object_name = "friend_code"
success_url = reverse_lazy("list_friend_codes") def get_success_url(self):
base_url = reverse("dashboard")
return f"{base_url}?{urlencode({'tab': 'friend_codes'})}"
def get_queryset(self): def get_queryset(self):
# Only allow deletion of friend codes owned by the current user. # Only allow deletion of friend codes owned by the current user.
@ -90,20 +75,16 @@ class DeleteFriendCodeView(LoginRequiredMixin, DeleteView):
self.object = self.get_object() self.object = self.get_object()
user = self.object.user user = self.object.user
# Check if the friend code is the only one; prevent deletion.
if user.friend_codes.count() == 1: if user.friend_codes.count() == 1:
messages.error(request, "Cannot remove your only friend code.") messages.error(request, "Cannot remove your only friend code.")
return redirect(self.success_url) return redirect(self.get_success_url())
# Check if the friend code is set as default; prevent deletion.
if user.default_friend_code == self.object: if user.default_friend_code == self.object:
messages.error( messages.error(
request, request,
"Cannot delete your default friend code. Please set a different default first." "Cannot delete your default friend code. Please set a different default first."
) )
return redirect(self.success_url) return redirect(self.get_success_url())
# Use the unfiltered manager and filter by the friend code's primary key
trade_offer_exists = TradeOffer.all_offers.filter(initiated_by_id=self.object.pk).exists() trade_offer_exists = TradeOffer.all_offers.filter(initiated_by_id=self.object.pk).exists()
trade_acceptance_exists = TradeAcceptance.objects.filter(accepted_by_id=self.object.pk).exists() trade_acceptance_exists = TradeAcceptance.objects.filter(accepted_by_id=self.object.pk).exists()
@ -112,12 +93,11 @@ class DeleteFriendCodeView(LoginRequiredMixin, DeleteView):
request, request,
"Cannot remove this friend code because there are existing trade offers associated with it." "Cannot remove this friend code because there are existing trade offers associated with it."
) )
return redirect(self.success_url) return redirect(self.get_success_url())
# Proceed to safe deletion.
self.object.delete() self.object.delete()
messages.success(request, "Friend code removed successfully.") messages.success(request, "Friend code removed successfully.")
return redirect(self.success_url) return redirect(self.get_success_url())
class ChangeDefaultFriendCodeView(LoginRequiredMixin, View): class ChangeDefaultFriendCodeView(LoginRequiredMixin, View):
""" """
@ -128,7 +108,9 @@ class ChangeDefaultFriendCodeView(LoginRequiredMixin, View):
friend_code = get_object_or_404(FriendCode, pk=friend_code_id, user=request.user) friend_code = get_object_or_404(FriendCode, pk=friend_code_id, user=request.user)
request.user.set_default_friend_code(friend_code) request.user.set_default_friend_code(friend_code)
messages.success(request, "Default friend code updated successfully.") messages.success(request, "Default friend code updated successfully.")
return redirect("list_friend_codes") base_url = reverse("dashboard")
query_string = urlencode({"tab": "friend_codes"})
return redirect(f"{base_url}?{query_string}")
class EditFriendCodeView(LoginRequiredMixin, UpdateView): class EditFriendCodeView(LoginRequiredMixin, UpdateView):
""" """
@ -141,7 +123,9 @@ class EditFriendCodeView(LoginRequiredMixin, UpdateView):
fields = ['in_game_name'] fields = ['in_game_name']
template_name = "friend_codes/edit_friend_code.html" template_name = "friend_codes/edit_friend_code.html"
context_object_name = "friend_code" context_object_name = "friend_code"
success_url = reverse_lazy("list_friend_codes") def get_success_url(self):
base_url = reverse("dashboard")
return f"{base_url}?{urlencode({'tab': 'friend_codes'})}"
def get_queryset(self): def get_queryset(self):
# Ensure the user can only edit their own friend codes # Ensure the user can only edit their own friend codes

View file

@ -7,7 +7,6 @@ from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
from trades.models import TradeOffer, TradeAcceptance, TradeOfferHaveCard, TradeOfferWantCard from trades.models import TradeOffer, TradeAcceptance, TradeOfferHaveCard, TradeOfferWantCard
from cards.models import Card from cards.models import Card
from django.utils.decorators import method_decorator from django.utils.decorators import method_decorator
from django.views.decorators.cache import cache_page
from django.template.response import TemplateResponse from django.template.response import TemplateResponse
from django.http import HttpResponseRedirect from django.http import HttpResponseRedirect
import logging import logging
@ -112,7 +111,6 @@ class HomePageView(TemplateView):
return context return context
@method_decorator(cache_page(60 * 10)) # Cache for 10 minutes
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
"""Override get method to add caching""" """Override get method to add caching"""
return super().get(request, *args, **kwargs) return super().get(request, *args, **kwargs)

View file

@ -1,5 +1,16 @@
[x-cloak] { display: none !important; } [x-cloak] { display: none !important; }
/* Beta Badge */
#navbar-logo::after {
content: 'BETA';
font-size: 12px;
font-weight: bold;
color: var(--color-base-content);
background-color: var(--color-base-300);
padding: 4px 8px;
border-radius: 4px;
}
select.card-multiselect { select.card-multiselect {
height: calc(var(--spacing) * 35); height: calc(var(--spacing) * 35);
/*background-image: linear-gradient(45deg, #0000 50%, currentColor 50%), linear-gradient(135deg, currentColor 50%, #0000 50%); */ /*background-image: linear-gradient(45deg, #0000 50%, currentColor 50%), linear-gradient(135deg, currentColor 50%, #0000 50%); */

View file

@ -56,7 +56,7 @@
<div class="card bg-base-100 shadow-xl"> <div class="card bg-base-100 shadow-xl">
<div class="card-body"> <div class="card-body">
<h2 class="card-title mb-2">{{ _('Quick Actions') }}</h2> <h2 class="card-title mb-2">{{ _('Quick Actions') }}</h2>
<div class="flex space-x-4"> <div class="flex flex-wrap gap-4">
<a href="{% url 'trade_offer_create' %}" class="btn btn-primary grow">{{ _('Create New Offer') }}</a> <a href="{% url 'trade_offer_create' %}" class="btn btn-primary grow">{{ _('Create New Offer') }}</a>
<a href="{% url 'trade_offer_list' %}" class="btn btn-secondary grow">{{ _('View All Offers') }}</a> <a href="{% url 'trade_offer_list' %}" class="btn btn-secondary grow">{{ _('View All Offers') }}</a>
<a href="{% url 'account_logout' %}" class="btn btn-warning grow">{{ _('Sign Out') }}</a> <a href="{% url 'account_logout' %}" class="btn btn-warning grow">{{ _('Sign Out') }}</a>
@ -121,17 +121,17 @@
{% endif %} {% endif %}
{% endwith %} {% endwith %}
<div class="divider"></div> <div class="divider"></div>
<h2 class="text-lg font-semibold pt-0">What is Gravatar?</h2> <h2 class="text-base font-semibold pt-0">What is Gravatar?</h2>
<p class="mb-4">Gravatar (Globally Recognized Avatar) is a free service that links your email address to a profile picture and, optionally, a profile. Many websites, including this one, use Gravatar to display your avatar and profile automatically.</p> <p class="mb-4 text-sm">Gravatar (Globally Recognized Avatar) is a free service that links your email address to a profile picture and, optionally, a profile. Many websites, including this one, use Gravatar to display your avatar and profile automatically.</p>
<h2 class="text-lg font-semibold">How does it work?</h2> <h2 class="text-base font-semibold">How does it work?</h2>
<p class="mb-4">If you've set up a Gravatar, your profile picture will appear here whenever you use your email on supported sites. When someone hovers over or clicks on your avatar, your Gravatar profile will also appear if you have one. If you don't have a Gravatar yet, you'll see a default image instead.</p> <p class="mb-4 text-sm">If you've set up a Gravatar, your profile picture will appear here whenever you use your email on supported sites. When someone hovers over or clicks on your avatar, your Gravatar profile will also appear if you have one. If you don't have a Gravatar yet, you'll see a default image instead.</p>
<h2 class="text-lg font-semibold">Is it safe? What about privacy?</h2> <h2 class="text-base font-semibold">Is it safe? What about privacy?</h2>
<p class="mb-4">Gravatar is completely optional, opt-in, and prioritizes your security and privacy. Your email is never shared and only a hashed version is sent to Gravatar, protecting your identity while ensuring that your email address is not exposed to bots or scrapers. Your personal data remains secure, and you maintain full control over your public profile.</p> <p class="mb-4 text-sm">Gravatar is completely optional, opt-in, and prioritizes your security and privacy. Your email is never shared and only a hashed version is sent to Gravatar, protecting your identity while ensuring that your email address is not exposed to bots or scrapers. Your personal data remains secure, and you maintain full control over your public profile.</p>
<h2 class="text-lg font-semibold">Want to update or add a Gravatar?</h2> <h2 class="text-base font-semibold">Want to update or add a Gravatar?</h2>
<p class="mb-4">Go to Gravatar.com to set up or change your avatar or profile. Your updates will appear here once saved!</p> <p class="mb-4 text-sm">Go to Gravatar.com to set up or change your avatar or profile. Your updates will appear here once saved!</p>
</div> </div>
</div> </div>

View file

@ -54,11 +54,11 @@
{% block javascript_head %}{% endblock %} {% block javascript_head %}{% endblock %}
</head> </head>
<body class="min-h-screen bg-base-200" id="body"> <body class="min-h-screen bg-base-200 dark:bg-base-300" id="body">
<!-- Header and Navigation --> <!-- Header and Navigation -->
<div class="navbar bg-base-100 shadow-sm"> <div class="navbar bg-base-100 shadow-sm">
<div class="navbar-start"> <div class="navbar-start">
<a class="btn btn-ghost text-xl" href="{% url 'home' %}"> <a id="navbar-logo" class="btn btn-ghost text-xl" href="{% url 'home' %}">
<span class="inline leading-none" aria-hidden="true"> <span class="inline leading-none" aria-hidden="true">
<span class="inline-block relative align-text-top">P</span><span class="inline-block relative align-text-bottom">K</span><span class="inline-block relative align-text-top">M</span><span class="inline-block relative align-text-bottom">N</span> <span class="inline-block relative align-text-top">P</span><span class="inline-block relative align-text-bottom">K</span><span class="inline-block relative align-text-top">M</span><span class="inline-block relative align-text-bottom">N</span>
<span class="inline-block relative">Trade Club</span> <span class="inline-block relative">Trade Club</span>
@ -111,13 +111,13 @@
</div> </div>
<!-- Main Content --> <!-- Main Content -->
<main class="container mx-auto p-4 sm:w-4/5 md:w-full xl:w-256"> <main class="container mx-auto p-4 w-full xl:w-256">
{% include '_messages.html' %} {% include '_messages.html' %}
{% block content %}{% endblock %} {% block content %}{% endblock %}
</main> </main>
<!-- Footer --> <!-- Footer -->
<footer class="bg-base-200 text-base-content p-4"> <footer class="bg-base-200 dark:bg-base-300 text-base-content p-4">
<div class="container mx-auto text-center text-sm"> <div class="container mx-auto text-center text-sm">
<p>&copy; {% now "Y" %} PKMNTrade.Club. All rights reserved.</p> <p>&copy; {% now "Y" %} PKMNTrade.Club. All rights reserved.</p>
</div> </div>

View file

@ -31,7 +31,11 @@
<h2 class="text-xl font-semibold">Have this Card ({{ trade_offer_have_count }})</h2> <h2 class="text-xl font-semibold">Have this Card ({{ trade_offer_have_count }})</h2>
<!-- DaisyUI dropdown replacing the select --> <!-- DaisyUI dropdown replacing the select -->
<div class="dropdown dropdown-end"> <div class="dropdown dropdown-end">
<div tabindex="0" role="button" class="btn m-1" x-text="order === 'newest' ? 'Newest 🞃' : 'Oldest 🞃'"></div> <div tabindex="0" role="button" class="btn m-1">Sort by: <span x-text="order === 'newest' ? 'Newest' : 'Oldest'"></span>
<svg class="size-4" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
</svg>
</div>
<ul tabindex="0" class="dropdown-content menu bg-base-100 rounded-box z-1 w-26 p-2 shadow-sm"> <ul tabindex="0" class="dropdown-content menu bg-base-100 rounded-box z-1 w-26 p-2 shadow-sm">
<li> <li>
<a href="#" @click.prevent="order = 'newest'; page = 1; loadOffers()"> <a href="#" @click.prevent="order = 'newest'; page = 1; loadOffers()">
@ -67,7 +71,11 @@
<h2 class="text-xl font-semibold">Want this Card ({{ trade_offer_want_count }})</h2> <h2 class="text-xl font-semibold">Want this Card ({{ trade_offer_want_count }})</h2>
<!-- DaisyUI dropdown replacing the select --> <!-- DaisyUI dropdown replacing the select -->
<div class="dropdown dropdown-end"> <div class="dropdown dropdown-end">
<div tabindex="0" role="button" class="btn m-1" x-text="order === 'newest' ? 'Newest 🞃' : 'Oldest 🞃'"></div> <div tabindex="0" role="button" class="btn m-1">Sort by: <span x-text="order === 'newest' ? 'Newest' : 'Oldest'"></span>
<svg class="size-4" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
</svg>
</div>
<ul tabindex="0" class="dropdown-content menu bg-base-100 rounded-box z-1 w-26 p-2 shadow-sm"> <ul tabindex="0" class="dropdown-content menu bg-base-100 rounded-box z-1 w-26 p-2 shadow-sm">
<li> <li>
<a href="#" @click.prevent="order = 'newest'; page = 1; loadOffers()"> <a href="#" @click.prevent="order = 'newest'; page = 1; loadOffers()">

View file

@ -27,7 +27,10 @@
<!-- Sort Dropdown --> <!-- Sort Dropdown -->
<div class="dropdown dropdown-end m-1"> <div class="dropdown dropdown-end m-1">
<div tabindex="0" class="btn"> <div tabindex="0" class="btn">
Sort by: <span x-text="order === 'absolute' ? 'None' : (order === 'alphabetical' ? 'Alphabetical' : 'Rarity')"></span> 🞃 Sort by: <span x-text="order === 'absolute' ? 'None' : (order === 'alphabetical' ? 'Alphabetical' : 'Rarity')"></span>
<svg class="size-4" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
</svg>
</div> </div>
<ul tabindex="0" class="dropdown-content menu p-2 shadow bg-base-100 rounded-box w-52"> <ul tabindex="0" class="dropdown-content menu p-2 shadow bg-base-100 rounded-box w-52">
<li><a href="#" @click.prevent="order = 'absolute'; page = 1; loadCards()">None</a></li> <li><a href="#" @click.prevent="order = 'absolute'; page = 1; loadCards()">None</a></li>
@ -38,7 +41,10 @@
<!-- Grouping Dropdown --> <!-- Grouping Dropdown -->
<div class="dropdown dropdown-end m-1"> <div class="dropdown dropdown-end m-1">
<div tabindex="0" class="btn"> <div tabindex="0" class="btn">
Group by: <span x-text="groupBy === 'none' ? 'None' : (groupBy.charAt(0).toUpperCase() + groupBy.slice(1))"></span> 🞃 Group by: <span x-text="groupBy === 'none' ? 'None' : (groupBy.charAt(0).toUpperCase() + groupBy.slice(1))"></span>
<svg class="size-4" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
</svg>
</div> </div>
<ul tabindex="0" class="dropdown-content menu p-2 shadow bg-base-100 rounded-box w-52"> <ul tabindex="0" class="dropdown-content menu p-2 shadow bg-base-100 rounded-box w-52">
<li><a href="#" @click.prevent="groupBy = 'none'; page = 1; loadCards()">None</a></li> <li><a href="#" @click.prevent="groupBy = 'none'; page = 1; loadCards()">None</a></li>

View file

@ -22,9 +22,6 @@
<button type="submit" class="btn btn-primary w-full">{% trans "Add Friend Code" %}</button> <button type="submit" class="btn btn-primary w-full">{% trans "Add Friend Code" %}</button>
</form> </form>
<div class="mt-4">
<a href="{% url 'list_friend_codes' %}" class="btn btn-secondary">Back to Friend Codes</a>
</div>
</div> </div>
<!-- Include Cleave Zen from a CDN --> <!-- Include Cleave Zen from a CDN -->

View file

@ -27,7 +27,7 @@
Confirm Delete Confirm Delete
{% endif %} {% endif %}
</button> </button>
<a href="{% url 'list_friend_codes' %}" class="btn btn-secondary">Cancel</a> <a href="{% url 'dashboard' %}?tab=friend_codes" class="btn btn-secondary">Cancel</a>
</form> </form>
</div> </div>
{% endblock %} {% endblock %}

View file

@ -41,7 +41,7 @@
<!-- Right group: Cancel & Update --> <!-- Right group: Cancel & Update -->
<div class="flex items-center space-x-4 mt-4 md:mt-0"> <div class="flex items-center space-x-4 mt-4 md:mt-0">
<a href="{% url 'list_friend_codes' %}" class="btn btn-secondary">Cancel</a> <a href="{% url 'dashboard' %}?tab=friend_codes" class="btn btn-secondary">Cancel</a>
<!-- This update button is outside the form but tied to it with the HTML5 'form' attribute --> <!-- This update button is outside the form but tied to it with the HTML5 'form' attribute -->
<button type="submit" form="edit-friendcode-form" class="btn btn-primary">Update</button> <button type="submit" form="edit-friendcode-form" class="btn btn-primary">Update</button>
</div> </div>

View file

@ -1,43 +0,0 @@
{% extends 'base.html' %}
{% block title %}My Friend Codes{% endblock %}
{% block content %}
<div class="container mx-auto max-w-3xl mt-6">
<h1 class="text-3xl font-bold mb-4">My Friend Codes</h1>
{% if friend_codes %}
<ul class="space-y-2">
{% for code in friend_codes %}
<li class="w-full grid grid-cols-2 grid-rows-2 md:grid-cols-8 md:grid-rows-1 items-center {% if code.is_default %}bg-green-200 dark:bg-green-300 dark:text-base-100{% else %}bg-base-100 dark:bg-base-900 dark:text-white{% endif %} p-4 rounded shadow">
<div class="row-start-1 md:col-span-3">
<span class="align-baseline"><a href="{% url 'edit_friend_code' code.id %}">{{ code.in_game_name }}</a></span>
{% if code.is_default %}
<span class="badge badge-success ml-2 align-baseline">Default</span>
{% endif %}
</div>
<div class="row-start-2 col-start-1 md:row-start-1 md:col-span-3 {% if not code.is_default %}mr-4{% endif %}">
<span class="font-mono text-sm sm:text-base align-baseline">{{ code.friend_code }}</span>
</div>
<div class="row-start-2 col-start-2 md:row-start-1 md:col-span-2 flex justify-end space-x-2">
{% if not code.is_default %}
<form method="post" action="{% url 'change_default_friend_code' code.id %}">
{% csrf_token %}
<button type="submit" class="btn btn-secondary btn-sm align-baseline">Set Default</button>
</form>
{% endif %}
<a href="{% url 'delete_friend_code' code.id %}" class="btn btn-error btn-sm align-baseline">Delete</a>
</div>
</li>
{% endfor %}
</ul>
{% else %}
<p>You do not have any friend codes added yet.</p>
{% endif %}
<div class="mt-4 flex flex-row justify-between">
<a href="{% url 'dashboard' %}" class="btn btn-secondary">Back to Dashboard</a>
<a href="{% url 'add_friend_code' %}" class="btn btn-primary">Add a New Friend Code</a>
</div>
</div>
{% endblock %}

View file

@ -87,7 +87,7 @@
<div class="grid grid-cols-1 md:grid-cols-2 gap-4"> <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<!-- Featured Offers --> <!-- Featured Offers -->
<div> <div>
{% cache 60 featured_offers %} {% cache 3600 featured_offers %}
<div class="p-4 text-center "> <div class="p-4 text-center ">
<h5 class="text-xl font-semibold whitespace-nowrap truncate mb-0">Featured Offers</h5> <h5 class="text-xl font-semibold whitespace-nowrap truncate mb-0">Featured Offers</h5>
</div> </div>
@ -140,7 +140,7 @@
<!-- Recent Offers --> <!-- Recent Offers -->
<div> <div>
{% cache 60 recent_offers %} {% cache 3600 recent_offers %}
<div class="text-center p-4"> <div class="text-center p-4">
<h5 class="text-xl font-semibold whitespace-nowrap truncate mb-0">Recent Offers</h5> <h5 class="text-xl font-semibold whitespace-nowrap truncate mb-0">Recent Offers</h5>
</div> </div>

View file

@ -25,8 +25,5 @@
</ul> </ul>
</div> </div>
{% endif %} {% endif %}
<div class="mt-6">
<a href="{% url 'trade_offer_detail' pk=trade_offer.pk %}" class="btn btn-secondary">Back to Offer Details</a>
</div>
</div> </div>
{% endblock content %} {% endblock content %}

View file

@ -84,11 +84,5 @@
</ul> </ul>
</div> </div>
{% endif %} {% endif %}
<div class="mt-6">
<a href="{% url 'trade_offer_detail' pk=object.trade_offer.pk %}" class="btn btn-secondary">
Back to Offer Details
</a>
</div>
</div> </div>
{% endblock content %} {% endblock content %}

View file

@ -47,11 +47,10 @@
{% endif %} {% endif %}
<div class="mt-6 flex justify-between"> <div class="mt-6 flex justify-between">
<a href="{% url 'trade_offer_list' %}" class="btn btn-secondary">Back to Trade Offers</a>
{% if is_initiator %} {% if is_initiator %}
<a href="{{ delete_close_url }}" class="btn btn-danger">Delete/Close Trade Offer</a> <a href="{{ delete_close_url }}" class="btn btn-danger">Delete/Close Trade Offer</a>
{% elif request.user.is_authenticated %} {% elif request.user.is_authenticated %}
<button type="submit" class="btn btn-primary">Accept Offer</button> <button type="submit" class="btn btn-primary">Accept Trade</button>
{% endif %} {% endif %}
</form> </form>
</div> </div>

View file

@ -80,7 +80,6 @@
{% endif %} {% endif %}
<div class="flex space-x-4"> <div class="flex space-x-4">
<a href="{% url 'trade_offer_list' %}" class="btn btn-secondary">Back to Trade Offers</a>
{% if can_delete %} {% if can_delete %}
<a href="{% url 'trade_offer_delete' object.pk %}" class="btn btn-error">Delete Trade Offer</a> <a href="{% url 'trade_offer_delete' object.pk %}" class="btn btn-error">Delete Trade Offer</a>
{% endif %} {% endif %}

View file

@ -1,12 +1,11 @@
{% load gravatar card_badge cache %} {% load gravatar card_badge %}
{% cache 60 trade_acceptance acceptance.pk %} <div class="card card-border bg-base-100 shadow-lg w-84 mx-auto">
<div class="card card-border bg-base-100 shadow-lg w-96 md:w-80 lg:w-96 mx-auto">
<!-- Header --> <!-- Header -->
<div class="py-4 mx-2 sm:mx-4"> <div class="py-4 mx-4">
<div class="flex justify-between items-center"> <div class="flex justify-between items-center">
<!-- Left: Initiator's avatar (moved from center) and "Has" --> <!-- Left: Initiator's avatar (moved from center) and "Has" -->
<div class="flex items-center"> <div class="flex items-center ms-8">
<div class="avatar mr-2"> <div class="avatar mr-2">
<div class="w-10 rounded-full"> <div class="w-10 rounded-full">
{{ acceptance.trade_offer.initiated_by.user.email|gravatar:40 }} {{ acceptance.trade_offer.initiated_by.user.email|gravatar:40 }}
@ -15,7 +14,7 @@
<span class="text-sm font-semibold">Has</span> <span class="text-sm font-semibold">Has</span>
</div> </div>
<!-- Right: "Wants" with the acceptor's avatar --> <!-- Right: "Wants" with the acceptor's avatar -->
<div class="flex items-center"> <div class="flex items-center me-8">
<span class="text-sm font-semibold mr-2">Wants</span> <span class="text-sm font-semibold mr-2">Wants</span>
<div class="avatar"> <div class="avatar">
<div class="w-10 rounded-full"> <div class="w-10 rounded-full">
@ -29,7 +28,7 @@
<!-- Main Card Row: Single row with the acceptance's cards --> <!-- Main Card Row: Single row with the acceptance's cards -->
<a href="{% url 'trade_acceptance_update' pk=acceptance.pk %}" class="no-underline block"> <a href="{% url 'trade_acceptance_update' pk=acceptance.pk %}" class="no-underline block">
<div class="px-2 pb-0"> <div class="px-2 pb-0">
<div class="grid grid-cols-2 items-center border-t border-gray-300"> <div class="grid grid-cols-2 items-center">
<div class="flex flex-col items-center"> <div class="flex flex-col items-center">
{% card_badge acceptance.requested_card %} {% card_badge acceptance.requested_card %}
</div> </div>
@ -41,8 +40,11 @@
</a> </a>
<!-- Footer: Only info button with acceptance hash --> <!-- Footer: Only info button with acceptance hash -->
<div class="flex justify-end px-2 pb-2"> <div class="flex justify-between px-2 pb-2">
<div class="text-gray-500 text-sm tooltip tooltip-left" data-tip="Acceptance ID: {{ acceptance.hash }}"> <div class="text-gray-500 text-sm">
{{ acceptance.get_state_display }}
</div>
<div class="text-gray-500 text-sm tooltip tooltip-left" data-tip="Trade ID: {{ acceptance.hash }}">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5 h-5"> <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5 h-5">
<path stroke-linecap="round" stroke-linejoin="round" <path stroke-linecap="round" stroke-linejoin="round"
d="M11.25 11.25l.041-.02a.75.75 0 0 1 1.063.852l-.708 2.836a.75.75 0 0 0 1.063.853l.041-.021M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0zm-9-3.75h.008v.008H12V8.25z" /> d="M11.25 11.25l.041-.02a.75.75 0 0 1 1.063.852l-.708 2.836a.75.75 0 0 0 1.063.853l.041-.021M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0zm-9-3.75h.008v.008H12V8.25z" />
@ -50,4 +52,3 @@
</div> </div>
</div> </div>
</div> </div>
{% endcache %}

View file

@ -40,6 +40,7 @@
</div> </div>
<!-- Main Trade Offer Row --> <!-- Main Trade Offer Row -->
<div class="flip-face-body self-start"> <div class="flip-face-body self-start">
{% if not flipped %}
<a href="{% url 'trade_offer_detail' pk=offer_pk %}" class="no-underline block"> <a href="{% url 'trade_offer_detail' pk=offer_pk %}" class="no-underline block">
<div class="px-2 main-badges pb-0"> <div class="px-2 main-badges pb-0">
<!-- Normal mode: just use an outer grid with 2 columns --> <!-- Normal mode: just use an outer grid with 2 columns -->
@ -59,7 +60,13 @@
</div> </div>
</div> </div>
</a> </a>
{% else %}
<div class="flex justify-center mt-8">
<div class="text-sm">
All cards have been accepted.
</div>
</div>
{% endif %}
<!-- Extra Card Badges (Collapsible) --> <!-- Extra Card Badges (Collapsible) -->
<div x-show="badgeExpanded" x-collapse.duration.500ms x-cloak class="px-2 extra-badges"> <div x-show="badgeExpanded" x-collapse.duration.500ms x-cloak class="px-2 extra-badges">
<a href="{% url 'trade_offer_detail' pk=offer_pk %}" class="no-underline block"> <a href="{% url 'trade_offer_detail' pk=offer_pk %}" class="no-underline block">
@ -80,6 +87,7 @@
</a> </a>
</div> </div>
</div> </div>
{% if have_cards_available|length > 1 or want_cards_available|length > 1 %}
<div @click="badgeExpanded = !badgeExpanded" class="flex justify-center h-5 cursor-pointer"> <div @click="badgeExpanded = !badgeExpanded" class="flex justify-center h-5 cursor-pointer">
<svg x-bind:class="{ 'rotate-180': badgeExpanded }" <svg x-bind:class="{ 'rotate-180': badgeExpanded }"
class="transition-transform duration-500 h-5 w-5 cursor-pointer" class="transition-transform duration-500 h-5 w-5 cursor-pointer"
@ -88,6 +96,9 @@
d="M19 9l-7 7-7-7" /> d="M19 9l-7 7-7-7" />
</svg> </svg>
</div> </div>
{% else %}
<div class="h-5"></div>
{% endif %}
<div class="flip-face-footer self-end"> <div class="flip-face-footer self-end">
<div class="flex justify-between px-2 pb-2"> <div class="flex justify-between px-2 pb-2">
<div class="text-gray-500 text-sm tooltip tooltip-right" data-tip="ID: {{ offer_hash }}"> <div class="text-gray-500 text-sm tooltip tooltip-right" data-tip="ID: {{ offer_hash }}">
@ -102,7 +113,6 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-5"> <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-5">
<path stroke-linecap="round" stroke-linejoin="round" d="M3 8.689c0-.864.933-1.406 1.683-.977l7.108 4.061a1.125 1.125 0 0 1 0 1.954l-7.108 4.061A1.125 1.125 0 0 1 3 16.811V8.69ZM12.75 8.689c0-.864.933-1.406 1.683-.977l7.108 4.061a1.125 1.125 0 0 1 0 1.954l-7.108 4.061a1.125 1.125 0 0 1-1.683-.977V8.69Z" /> <path stroke-linecap="round" stroke-linejoin="round" d="M3 8.689c0-.864.933-1.406 1.683-.977l7.108 4.061a1.125 1.125 0 0 1 0 1.954l-7.108 4.061A1.125 1.125 0 0 1 3 16.811V8.69ZM12.75 8.689c0-.864.933-1.406 1.683-.977l7.108 4.061a1.125 1.125 0 0 1 0 1.954l-7.108 4.061a1.125 1.125 0 0 1-1.683-.977V8.69Z" />
</svg> </svg>
</div> </div>
</div> </div>
</div> </div>

View file

@ -1,5 +1,4 @@
from django.urls import path from django.urls import path
from django.views.decorators.cache import cache_page
from .views import ( from .views import (
TradeOfferCreateView, TradeOfferCreateView,
TradeOfferCreateConfirmView, TradeOfferCreateConfirmView,