use <a> tags for card_badge and trade_offer clickable areas (except for main card_badge row on trade_offers, still uses @click for now because the a tag can't wrap that content for some reason). closes #14

This commit is contained in:
badblocks 2025-04-15 00:15:08 -07:00
parent 86b061c971
commit afaa392b2f
22 changed files with 247 additions and 227 deletions

View file

@ -1,4 +1,4 @@
# Generated by Django 5.1.2 on 2025-04-14 04:07
# Generated by Django 5.1.2 on 2025-04-14 20:58
import accounts.models
import django.contrib.auth.models

View file

@ -20,7 +20,7 @@ class CustomUser(AbstractUser):
enable_email_notifications = models.BooleanField(
default=True,
verbose_name="Enable Email Notifications",
help_text="Receive new trade notifications via email."
help_text="Receive trade notifications via email."
)
reputation_score = models.IntegerField(default=0)

View file

@ -1,4 +1,4 @@
# Generated by Django 5.1.2 on 2025-04-14 04:07
# Generated by Django 5.1.2 on 2025-04-14 20:58
import django.db.models.deletion
from django.db import migrations, models

View file

@ -22,8 +22,8 @@ class HomePageView(TemplateView):
context = super().get_context_data(**kwargs)
try:
# Get all cards ordered by name
context["cards"] = Card.objects.all().order_by("name")
# Get all cards ordered by name, exclude cards with rarity level > 5
context["cards"] = Card.objects.filter(rarity_level__lte=5).order_by("name", "rarity_level")
# Reuse base trade offer queryset for market stats
base_offer_qs = TradeOffer.objects.filter(is_closed=False)
@ -39,7 +39,7 @@ class HomePageView(TemplateView):
# Most Offered Cards
try:
context["most_offered_cards"] = (
Card.objects.filter(tradeofferhavecard__isnull=False)
Card.objects.filter(tradeofferhavecard__isnull=False).filter(rarity_level__lte=5)
.annotate(offer_count=Sum("tradeofferhavecard__quantity"))
.order_by("-offer_count")[:6]
)
@ -50,7 +50,7 @@ class HomePageView(TemplateView):
# Most Wanted Cards
try:
context["most_wanted_cards"] = (
Card.objects.filter(tradeofferwantcard__isnull=False)
Card.objects.filter(tradeofferwantcard__isnull=False).filter(rarity_level__lte=5)
.annotate(offer_count=Sum("tradeofferwantcard__quantity"))
.order_by("-offer_count")[:6]
)
@ -61,7 +61,7 @@ class HomePageView(TemplateView):
# Least Offered Cards
try:
context["least_offered_cards"] = (
Card.objects.annotate(
Card.objects.filter(rarity_level__lte=5).annotate(
offer_count=Coalesce(Sum("tradeofferhavecard__quantity"), 0)
)
.order_by("offer_count")[:6]

View file

@ -23,7 +23,9 @@
*
*/
@theme {}
@theme {
--breakpoint-xs: 24rem;
}
/*

View file

@ -12,19 +12,19 @@ module.exports = {
*/
/* Templates within theme app (<tailwind_app_name>/templates), e.g. base.html. */
'../templates/**/*.html',
"../templates/**/*.html",
/*
* Main templates directory of the project (BASE_DIR/templates).
* Adjust the following line to match your project structure.
*/
'../../templates/**/*.html',
"../../templates/**/*.html",
/*
* Templates in other django apps (BASE_DIR/<any_app_name>/templates).
* Adjust the following line to match your project structure.
*/
'../../**/templates/**/*.html',
"../../**/templates/**/*.html",
/**
* JS: If you use Tailwind CSS in JavaScript, uncomment the following lines and make sure
@ -42,19 +42,19 @@ module.exports = {
// '../../**/*.py'
],
safelist: [
'alert-info',
'alert-success',
'alert-warning',
'alert-error',
'btn-info',
'btn-success',
'btn-warning',
'btn-error',
'bg-info',
'bg-success',
'bg-warning',
'bg-error',
'text-gray-700'
"alert-info",
"alert-success",
"alert-warning",
"alert-error",
"btn-info",
"btn-success",
"btn-warning",
"btn-error",
"bg-info",
"bg-success",
"bg-warning",
"bg-error",
"text-gray-700",
],
theme: {
extend: {},
@ -65,9 +65,9 @@ module.exports = {
* for forms. If you don't like it or have own styling for forms,
* comment the line below to disable '@tailwindcss/forms'.
*/
require('@tailwindcss/forms'),
require('@tailwindcss/typography'),
require('@tailwindcss/aspect-ratio'),
require("@tailwindcss/forms"),
require("@tailwindcss/typography"),
require("@tailwindcss/aspect-ratio"),
],
darkMode: 'class',
}
darkMode: "class",
};

View file

@ -106,8 +106,8 @@
{% with gravatar_profile=request.user.email|gravatar_profile_data %}
<div class="hovercard-profile mb-4 text-center">
<div class="avatar block mx-auto w-32">
<div class="W-32 rounded-full">
<div class="avatar block mx-auto max-w-32">
<div class="rounded-full">
{{ request.user.email|gravatar:128 }}
</div>
</div>

View file

@ -79,13 +79,13 @@
{% if user.is_authenticated %}
<div class="hidden sm:block">
<div class="btn btn-ghost btn-circle avatar">
<div class="w-10 rounded-full">
<div class="max-w-10 rounded-full">
<a tabindex="0" role="button" href="{% url 'dashboard' %}">{{ user.email|gravatar_no_hover:40 }}</a>
</div>
</div>
</div>
{% else %}
<div class="flex gap-2">
<div class="gap-2 hidden sm:flex">
<a tabindex="0" role="button" class="btn btn-primary" href="{% url 'account_login' %}">Login</a>
<a tabindex="1" role="button" class="btn btn-secondary" href="{% url 'account_signup' %}">Sign Up</a>
</div>
@ -94,7 +94,7 @@
</div>
<!-- Main Content -->
<main class="container mx-auto p-4 w-full xl:w-256">
<main class="container mx-auto p-4 w-full xl:max-w-256">
{% include '_messages.html' %}
{% block content %}{% endblock %}
</main>
@ -121,7 +121,7 @@
<span class="dock-label">Trades</span>
</button>
<button @click="window.location.href = '{{ dashboard_url }}'" class="{% if request.path == dashboard_url or request.path == settings_url %}dock-active{% endif %}">
{% if user.is_authenticated %}<div tabindex="0" role="button" class="avatar"><div class="w-6 rounded-full">{{ user.email|gravatar_no_hover:40 }}</div></div>{% else %}<svg class="size-[1.2em]" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><g fill="currentColor" stroke-linejoin="miter" stroke-linecap="butt"><circle cx="12" cy="12" r="3" fill="none" stroke="currentColor" stroke-linecap="square" stroke-miterlimit="10" stroke-width="2"></circle><path d="m22,13.25v-2.5l-2.318-.966c-.167-.581-.395-1.135-.682-1.654l.954-2.318-1.768-1.768-2.318.954c-.518-.287-1.073-.515-1.654-.682l-.966-2.318h-2.5l-.966,2.318c-.581.167-1.135.395-1.654.682l-2.318-.954-1.768,1.768.954,2.318c-.287.518-.515,1.073-.682,1.654l-2.318.966v2.5l2.318.966c.167.581.395,1.135.682,1.654l-.954,2.318,1.768,1.768,2.318-.954c.518.287,1.073.515,1.654.682l.966,2.318h2.5l.966-2.318c.581-.167,1.135-.395,1.654-.682l2.318.954,1.768-1.768-.954-2.318c.287-.518.515-1.073.682-1.654l2.318-.966Z" fill="none" stroke="currentColor" stroke-linecap="square" stroke-miterlimit="10" stroke-width="2"></path></g></svg>{% endif %}
{% if user.is_authenticated %}<div tabindex="0" role="button" class="avatar"><div class="max-w-6 rounded-full">{{ user.email|gravatar_no_hover:40 }}</div></div>{% else %}<svg class="size-[1.2em]" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><g fill="currentColor" stroke-linejoin="miter" stroke-linecap="butt"><circle cx="12" cy="12" r="3" fill="none" stroke="currentColor" stroke-linecap="square" stroke-miterlimit="10" stroke-width="2"></circle><path d="m22,13.25v-2.5l-2.318-.966c-.167-.581-.395-1.135-.682-1.654l.954-2.318-1.768-1.768-2.318.954c-.518-.287-1.073-.515-1.654-.682l-.966-2.318h-2.5l-.966,2.318c-.581.167-1.135.395-1.654.682l-2.318-.954-1.768,1.768.954,2.318c-.287.518-.515,1.073-.682,1.654l-2.318.966v2.5l2.318.966c.167.581.395,1.135.682,1.654l-.954,2.318,1.768,1.768,2.318-.954c.518.287,1.073.515,1.654.682l.966,2.318h2.5l.966-2.318c.581-.167,1.135-.395,1.654-.682l2.318.954,1.768-1.768-.954-2.318c.287-.518.515-1.073.682-1.654l2.318-.966Z" fill="none" stroke="currentColor" stroke-linecap="square" stroke-miterlimit="10" stroke-width="2"></path></g></svg>{% endif %}
<span class="dock-label">Dashboard</span>
</button>
</div>

View file

@ -38,7 +38,7 @@
<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 max-w-26 p-2 shadow-sm">
<li>
<a href="#" @click.prevent="order = 'newest'; page = 1; loadOffers()">
Newest
@ -81,7 +81,7 @@
<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 max-w-26 p-2 shadow-sm">
<li>
<a href="#" @click.prevent="order = 'newest'; page = 1; loadOffers()">
Newest

View file

@ -32,7 +32,7 @@
<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 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 max-w-52">
<li><a href="#" @click.prevent="order = 'absolute'; page = 1; loadCards()">Cardset</a></li>
<li><a href="#" @click.prevent="order = 'alphabetical'; page = 1; loadCards()">Alphabetical</a></li>
<li><a href="#" @click.prevent="order = 'rarity'; page = 1; loadCards()">Rarity</a></li>
@ -46,7 +46,7 @@
<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 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 max-w-52">
<li><a href="#" @click.prevent="groupBy = 'none'; page = 1; loadCards()">None</a></li>
<li><a href="#" @click.prevent="groupBy = 'deck'; page = 1; loadCards()">Deck</a></li>
<li><a href="#" @click.prevent="groupBy = 'cardset'; page = 1; loadCards()">Cardset</a></li>

View file

@ -4,7 +4,6 @@
{% block title %}Edit Friend Code{% endblock %}
{% block content %}
<div class="container mx-auto max-w-3xl mt-6">
<h1 class="text-3xl font-bold mb-4">Edit Friend Code</h1>
<!-- Display the friend code as plain text -->
@ -46,5 +45,4 @@
<button type="submit" form="edit-friendcode-form" class="btn btn-primary">Update</button>
</div>
</div>
</div>
{% endblock %}

View file

@ -4,11 +4,10 @@
{% block title %}{{title}}{% endblock title %}
{% block content %}
<div class="container mx-auto mt-6">
<h2 class="text-2xl font-bold">Trade Offer Details</h2>
<div class="card card-border bg-base-100 shadow-lg mx-auto p-6 m-4 text-sm">
<div class="card card-border bg-base-100 shadow-lg mx-auto px-2 py-4 m-2 text-sm">
{% if acceptance_form %}
<div class="col-span-2 w-3/4 mx-auto card card-border bg-base-200 shadow-lg p-6 mb-8">
<div class="col-span-2 max-w-3/4 mx-auto card card-border bg-base-200 shadow-lg p-6 mb-8">
<h3 class="text-xl font-semibold mb-2">Accept A Trade</h3>
<form method="post" action="{% url 'trade_acceptance_create' offer_pk=object.pk %}">
{% csrf_token %}
@ -53,15 +52,15 @@
</div>
{% endif %}
<div class="grid grid-cols-2 justify-items-end items-baseline gap-4">
<div class="font-semibold text-right text-lg">Cards They Have:
<div class="flex flex-wrap flex-col gap-2 text-left mt-4">
<div class="font-semibold text-right xs:text-lg">Cards They Have:
<div class="flex flex-wrap flex-col gap-2 text-left mt-4 items-center">
{% for card in object.have_cards_available %}
{% card_badge card.card card.qty_available %}
{% endfor %}
</div>
</div>
<div class="font-semibold justify-self-start text-lg">Cards They Want:
<div class="flex flex-wrap flex-col gap-2 mt-4">
<div class="font-semibold justify-self-start xs:text-lg">Cards They Want:
<div class="flex flex-wrap flex-col gap-2 mt-4 items-center">
{% for card in object.want_cards_available %}
{% card_badge card.card card.qty_available %}
{% endfor %}
@ -80,18 +79,17 @@
<div class="justify-self-start">{{ object.updated_at }}</div>
</div>
<div class="divider"></div>
<div class="font-semibold text-center text-lg mb-2">Trade History:</div>
<div class="flex flex-row flex-wrap justify-start items-baseline w-90 lg:w-181 mx-auto">
<div class="font-semibold text-center sm:text-lg mb-2">Trade History:</div>
<div class="flex flex-row flex-wrap justify-start items-baseline gap-y-2 w-65 xs:w-88 sm:w-90 lg:w-181 mx-auto">
{% for trade in object.acceptances.all %}
<div class="p-2 grid grid-cols-2 justify-items-center items-baseline gap-1 w-90">
<div class="grid grid-cols-2 justify-items-between items-baseline w-65 xs:w-88 sm:w-90 lg:w-181">
<div class="">{% card_badge trade.offered_card %}</div>
<div class="">{% card_badge trade.requested_card %}</div>
<div class="col-span-2 text-center">{{ trade.accepted_by.user.username }} &bull; <a class="link link-hover" href="{% url 'trade_acceptance_update' pk=trade.pk %}">#{{ trade.hash }}</a> &bull; {{ trade.get_state_display }}</div>
<div class="col-span-2 text-center mt-1">{{ trade.accepted_by.user.username }} &bull; <a class="link link-hover" href="{% url 'trade_acceptance_update' pk=trade.pk %}">#{{ trade.hash }}</a> &bull; {{ trade.get_state_display }}</div>
</div>
{% empty %}
<div class="text-center justify-center w-full text-center">No trades yet.</div>
{% endfor %}
</div>
</div>
</div>
{% endblock content %}

View file

@ -10,10 +10,10 @@
{% csrf_token %}
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
{% card_multiselect "have_cards" "I Have:" "Select zero or more cards..." cards have_cards %}
{% card_multiselect "have_cards" "I Have:" "Select some cards..." cards have_cards %}
</div>
<div>
{% card_multiselect "want_cards" "I Want:" "Select zero or more cards..." cards want_cards %}
{% card_multiselect "want_cards" "I Want:" "Select some cards..." cards want_cards %}
</div>
</div>
<div class="flex flex-col md:flex-row gap-4">

View file

@ -1,8 +1,8 @@
<a href="{{ url }}" @click.stop>
<div class="relative block">
{% if not expanded %}
<div class="flex flex-row items-center h-[32px] p-1.5 w-40 text-white shadow-lg" style="{{ style }}">
<div class="grow"><div class="truncate text-ellipsis font-semibold leading-tight text-sm marquee-calc max-w-24">{{ name }}</div></div>
<div class="flex flex-row items-center h-[32px] p-1.5 w-30 sm:w-40 text-white shadow-lg" style="{{ style }}">
<div class="grow"><div class="truncate text-ellipsis font-semibold leading-tight text-sm marquee-calc w-4/5 sm:w-24">{{ name }}</div></div>
<div class="grow-0 shrink-0 text-right truncate font-semibold leading-tight text-sm">{{ cardset }}</div>
{% if quantity != None %}
<div class="grow-0 shrink-0 relative w-fit ps-1">
@ -11,7 +11,7 @@
{% endif %}
</div>
{% else %}
<div class="grid grid-rows-2 grid-cols-4 h-[52px] p-1.5 w-40 text-white shadow-lg" style="{{ style }}">
<div class="grid grid-rows-2 grid-cols-4 h-[52px] p-1.5 w-30 sm:w-40 text-white shadow-lg" style="{{ style }}">
<div class="row-start-1 col-start-1 {% if quantity != None %}col-span-3{% else %}col-span-4{% endif %} self-start leading-tight truncate text-ellipsis"><span class="font-semibold text-sm marquee-calc">{{ name }}</span></div>
{% if quantity != None %}
<div class="row-start-1 col-start-4 col-span-1 self-start ms-auto leading-tight relative w-fit ps-1">

View file

@ -1,14 +1,14 @@
{% load gravatar card_badge %}
<div class="card card-border bg-base-100 shadow-lg w-90 mx-auto">
<div class="cursor-pointer" @click.stop="window.location.href='{% url 'trade_acceptance_update' pk=acceptance.pk %}'">
<div class="card card-border bg-base-100 shadow-lg max-w-90 mx-auto">
<!-- Header -->
<a href="{% url 'trade_acceptance_update' pk=acceptance.pk %}">
<div class="my-4 mx-4">
<div class="flex justify-start items-center">
<!-- Left: Initiator's avatar and "Has" -->
<div class="flex items-center">
<div class="avatar me-2 tooltip tooltip-top cursor-default" @click.stop.prevent data-tip="{{ acceptance.trade_offer.initiated_by.user.username }} | {{ acceptance.trade_offer.initiated_by.user.reputation_score }} rep">
<div class="w-10 rounded-full">
<div class="max-w-10 rounded-full">
{{ acceptance.trade_offer.initiated_by.user.email|gravatar:40 }}
</div>
</div>
@ -22,6 +22,7 @@
</div>
</div>
</div>
</a>
<!-- Main Card Row: Single row with the acceptance's cards -->
<div class="px-2 pb-0">
<div class="grid grid-cols-2 items-center">
@ -33,6 +34,7 @@
</div>
</div>
</div>
<a href="{% url 'trade_acceptance_update' pk=acceptance.pk %}">
<div class="my-4 mx-4">
<div class="flex justify-end items-center">
<!-- Right: "Wants" with the acceptor's avatar -->
@ -45,12 +47,12 @@
{% endif %}
</div>
<div class="avatar ms-2 tooltip tooltip-bottom cursor-default" @click.stop.prevent data-tip="{{ acceptance.accepted_by.user.username}} | {{ acceptance.accepted_by.user.reputation_score }} rep">
<div class="w-10 rounded-full">
<div class="max-w-10 rounded-full">
{{ acceptance.accepted_by.user.email|gravatar:40 }}
</div>
</div>
</div>
</div>
</div>
</div>
</a>
</div>

View file

@ -3,7 +3,7 @@
{% cache 60 trade_offer offer_pk %}
<div x-data="{ flipped: {{flipped|lower}}, offerExpanded: {{flipped|yesno:'false,true'}}, acceptanceExpanded: {{flipped|lower}} }" x-ref="tradeOffer" class="transition-all duration-500 trade-offer-card">
<div class="flip-container">
<div class="flip-inner grid grid-cols-1 grid-rows-1 card bg-base-100 card-border shadow-lg w-90 transform transition-transform duration-500 ease-in-out{%if flipped %} rotate-y-180{% endif %}"
<div class="flip-inner grid grid-cols-1 grid-rows-1 card bg-base-100 card-border shadow-lg max-w-90 transform transition-transform duration-500 ease-in-out{%if flipped %} rotate-y-180{% endif %}"
:class="{'rotate-y-180': flipped}">
<div class="flip-face front rotate-y-0 col-start-1 row-start-1 grid grid-cols-1 auto-rows-min gap-2 content-between">
<div class="flip-face-header self-start">
@ -15,7 +15,7 @@
</div>
<div class="absolute inset-x-0 top-1/2 transform -translate-y-1/2 flex justify-center">
<div class="avatar tooltip tooltip-top cursor-default" @click.stop.prevent data-tip="{{ initiated_by_username }} | {{ initiated_reputation }} rep">
<div class="w-10 rounded-full">
<div class="max-w-10 rounded-full">
{{ initiated_by_email|gravatar:40 }}
</div>
</div>
@ -79,7 +79,7 @@
</div>
<div class="absolute inset-x-0 top-1/2 transform -translate-y-1/2 flex justify-center">
<div class="avatar tooltip tooltip-top cursor-default" @click.stop.prevent data-tip="{{ initiated_by_username }} | {{ initiated_reputation }} rep">
<div class="w-10 rounded-full">
<div class="max-w-10 rounded-full">
{{ initiated_by_email|gravatar:40 }}
</div>
</div>
@ -88,16 +88,16 @@
</a>
</div>
<div class="flip-face-body self-start">
<div class="px-2 space-y-3 text-xs">
<div x-show="acceptanceExpanded" x-collapse.duration.500ms class="flex flex-row flex-wrap justify-start items-baseline w-90 lg:w-181 mx-auto">
<div class="text-xs">
<div x-show="acceptanceExpanded" x-collapse.duration.500ms class="flex flex-row flex-wrap justify-center items-baseline mx-auto">
{% for trade in acceptances %}
<div class="p-2 grid grid-cols-2 justify-items-center items-baseline gap-1">
<div class="mb-2 grid grid-cols-2 justify-items-center items-baseline gap-x-3 gap-y-1">
<div class="">{% card_badge trade.offered_card %}</div>
<div class="">{% card_badge trade.requested_card %}</div>
<div class="col-span-2 text-center">{{ trade.accepted_by.user.username }} &bull; <a class="link link-hover" href="{% url 'trade_acceptance_update' pk=trade.pk %}">#{{ trade.hash }}</a> &bull; {{ trade.get_state_display }}</div>
<div class="col-span-2 text-center tooltip tooltip-bottom" data-tip="{{ trade.accepted_by.user.username }} | {{ trade.accepted_by.user.reputation_score }} rep">{{ trade.accepted_by.user.username }} &bull; <a class="link link-hover" href="{% url 'trade_acceptance_update' pk=trade.pk %}">#{{ trade.hash }}</a> &bull; {{ trade.get_state_display }}</div>
</div>
{% empty %}
<div class="text-center justify-center w-full text-center">No trades yet.</div>
<div class="w-full text-center">No trades yet.</div>
{% endfor %}
</div>
</div>

View file

@ -1,51 +1,63 @@
{% load gravatar card_badge tailwind_tags %}<!DOCTYPE html>
<html style="background-color: transparent !important;">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<html style="background-color: transparent !important">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style>
{% include 'static/css/dist/styles.css' %}
</style>
</head>
<body style="background-color: transparent !important;">
<div class="trade-offer-card-screenshot p-4 h-full w-auto flex justify-center" style="background-color: transparent !important;">
</head>
<body style="background-color: transparent !important">
<div
class="trade-offer-card-screenshot p-4 h-full w-auto flex justify-center"
style="background-color: transparent !important"
>
<div class="transition-all duration-500 trade-offer-card my-auto">
<!-- Flip container providing perspective -->
<div class="flip-container">
<div class="flip-inner grid grid-cols-1 grid-rows-1 card bg-base-100 card-border shadow-lg {% if expanded %}w-180{% else %}w-96{% endif %} transform transition-transform duration-700 ease-in-out">
<div
class="flip-inner grid grid-cols-1 grid-rows-1 card bg-base-100 card-border shadow-lg {% if expanded %}w-180{% else %}w-96{% endif %} transform transition-transform duration-700 ease-in-out"
>
<!-- Front Face: Trade Offer -->
<div class="flip-face front mb-2 col-start-1 row-start-1 grid grid-cols-1 auto-rows-min gap-2 content-between">
<div
class="flip-face front mb-2 col-start-1 row-start-1 grid grid-cols-1 auto-rows-min gap-2 content-between"
>
<!-- Header -->
<div class="flip-face-header self-start">
<a href="{% url 'trade_offer_detail' pk=offer_pk %}" class="no-underline block">
<div class="mt-6 mb-4 mx-2 sm:mx-4">
<!-- Two-column grid for the labels -->
<div class="flex flex-row justify-around w-full relative">
<div class="text-sm font-semibold text-center ms-3">Has</div>
<div class="absolute inset-x-0 top-1/2 transform -translate-y-1/2 flex justify-center">
<div class="text-sm font-semibold text-center ms-2">
Has
</div>
<div
class="absolute inset-x-0 top-1/2 transform -translate-y-1/2 flex justify-center"
>
<div class="avatar">
<div class="w-10 rounded-full">
{{ initiated_by_email|gravatar:40 }}
</div>
</div>
</div>
<div class="text-sm font-semibold text-center {% if expanded %}ms-8{% else %}me-2{% endif %}">Wants</div>
<div
class="text-sm font-semibold text-center {% if expanded %}ms-5{% endif %}"
>
Wants
</div>
</div>
</div>
</a>
</div>
<!-- Main Trade Offer Row -->
<div class="flip-face-body self-start">
<a href="{% url 'trade_offer_detail' pk=offer_pk %}" class="no-underline block">
<div class="px-4 main-badges pb-0">
{% if expanded %}
<div class="flex flex-row gap-1 justify-around">
<div class="flex flex-row justify-around">
<!-- Has Side (inner grid of 2 columns) -->
<div class="grid grid-cols-2 content-start">
{% for card in have_cards_available %}
{% card_badge card.card card.quantity %}
<div class="grid grid-cols-2 content-start gap-1">
{% for card in have_cards_available %} {% card_badge card.card card.quantity %} {% empty %}
<div class="text-xs text-center mb-2 ms-3 col-span-2">
None left.
</div>
{% endfor %}
</div>
<!-- Vertical Divider -->
@ -53,9 +65,11 @@
<div class="w-px bg-gray-300 h-full"></div>
</div>
<!-- Wants Side (inner grid of 2 columns) -->
<div class="grid grid-cols-2 content-start">
{% for card in want_cards_available %}
{% card_badge card.card card.quantity %}
<div class="grid grid-cols-2 content-start gap-1">
{% for card in want_cards_available %} {% card_badge card.card card.quantity %} {% empty %}
<div class="text-xs text-center mb-2 ms-3 col-span-2">
None left.
</div>
{% endfor %}
</div>
</div>
@ -63,31 +77,36 @@
<!-- Normal mode: just use an outer grid with 2 columns -->
<div class="flex flex-row gap-1 justify-around">
<!-- Has Side -->
<div class="flex flex-col content-start">
{% for card in have_cards_available %}
{% card_badge card.card card.quantity %}
{% empty %}
<div class="text-xs text-center mb-2 ms-3">None left.</div>
<div class="flex flex-col content-start gap-1">
{% for card in have_cards_available %} {% card_badge card.card card.quantity %} {% empty %}
<div class="text-xs text-center mb-2 ms-3">
None left.
</div>
{% endfor %}
</div>
<!-- Wants Side -->
<div class="flex flex-col content-start">
{% for card in want_cards_available %}
{% card_badge card.card card.quantity %}
{% empty %}
<div class="text-xs text-center mb-2{% if expanded %} ms-8{% else %} me-4{% endif %}">None left.</div>
<div class="flex flex-col content-start gap-1">
{% for card in want_cards_available %} {% card_badge card.card card.quantity %} {% empty %}
<div
class="text-xs text-center mb-2{% if expanded %} ms-8{% else %} me-4{% endif %}"
>
None left.
</div>
{% endfor %}
</div>
</div>
{% endif %}
</div>
</a>
</div>
<div class="flip-face-footer self-end">
<div class="flex flex-col gap-2 text-center">
<div class="text-sm font-semibold text-base-content">
{{ in_game_name }} {% if show_friend_code %}<span class="text-base-content/50"></span> {{ friend_code }}{% endif %}
{% if show_friend_code %}{{ in_game_name }} <span
class="text-base-content/50"
>•</span
>
{{ friend_code }}{% endif %}
</div>
</div>
</div>
@ -96,5 +115,5 @@
</div>
</div>
</div>
</body>
</body>
</html>

View file

@ -55,15 +55,15 @@ class TradeAcceptanceCreateForm(forms.ModelForm):
elif default_friend_code and friend_codes.filter(pk=default_friend_code.pk).exists():
self.initial["accepted_by"] = default_friend_code.pk
# Update available requested_card choices from the TradeOffer's "have" side.
active_states = [
TradeAcceptance.AcceptanceState.ACCEPTED,
TradeAcceptance.AcceptanceState.SENT,
TradeAcceptance.AcceptanceState.RECEIVED,
]
self.fields["requested_card"].queryset = trade_offer.have_cards_available_qs
# Fix: Convert available 'have' cards (from through model) to Card objects.
self.fields["requested_card"].queryset = Card.objects.filter(
pk__in=trade_offer.have_cards_available_qs.values_list("card_id", flat=True)
)
self.fields["offered_card"].queryset = trade_offer.want_cards_available_qs
# Similarly for offered_card.
self.fields["offered_card"].queryset = Card.objects.filter(
pk__in=trade_offer.want_cards_available_qs.values_list("card_id", flat=True)
)
def clean(self):
"""

View file

@ -1,4 +1,4 @@
# Generated by Django 5.1.2 on 2025-04-14 04:07
# Generated by Django 5.1.2 on 2025-04-14 20:58
import django.db.models.deletion
from django.db import migrations, models

View file

@ -94,6 +94,8 @@ class TradeOffer(models.Model):
if len(rarity_levels) > 1:
raise ValidationError("All cards in a trade offer must have the same rarity.")
first_card = cards[0]
if first_card.rarity_level > 5:
raise ValidationError("Cannot trade cards above one-star rarity.")
if self.rarity_level != first_card.rarity_level or self.rarity_icon != first_card.rarity_icon:
self.rarity_level = first_card.rarity_level
self.rarity_icon = first_card.rarity_icon
@ -140,11 +142,11 @@ class TradeOfferHaveCard(models.Model):
return self.quantity - self.qty_accepted
def __str__(self):
return f"{self.card.name} x{self.quantity} (Accepted: {self.qty_accepted})"
return f"#{self.card.cardnum} {self.card.cardset} {self.card.rarity_icon} {self.card.name}"
def save(self, *args, **kwargs):
super().save(*args, **kwargs)
self.trade_offer.update_rarity_fields()
super().save(*args, **kwargs)
def delete(self, *args, **kwargs):
trade_offer = self.trade_offer
@ -174,7 +176,7 @@ class TradeOfferWantCard(models.Model):
return self.quantity - self.qty_accepted
def __str__(self):
return f"{self.card.name} x{self.quantity} (Accepted: {self.qty_accepted})"
return f"#{self.card.cardnum} {self.card.cardset} {self.card.rarity_icon} {self.card.name}"
def save(self, *args, **kwargs):
super().save(*args, **kwargs)

View file

@ -40,7 +40,7 @@ def adjust_qty_for_trade_offer(trade_offer, card, side, delta):
def update_trade_offer_closed_status(trade_offer):
"""
Check if both sides of the trade offer meet the quantity requirement.
Mark the trade_offer as closed if all cards have qty_accepted
Mark the trade_offer as closed if all cards on one side have qty_accepted
greater than or equal to quantity; otherwise, mark it as open.
"""
have_complete = not TradeOfferHaveCard.objects.filter(
@ -107,6 +107,8 @@ def trade_acceptance_email_notification(sender, instance, created, **kwargs):
# return
acting_user = instance._actioning_user
del instance._actioning_user
state = instance.state
if state == TradeAcceptance.AcceptanceState.ACCEPTED:
@ -169,9 +171,6 @@ def trade_acceptance_email_notification(sender, instance, created, **kwargs):
[recipient_user.email],
)
# Clean up the temporary attribute.
del instance._actioning_user
@receiver(post_save, sender=TradeAcceptance)
def trade_acceptance_reputation_update(sender, instance, created, **kwargs):
"""

View file

@ -42,7 +42,7 @@ class TradeOfferCreateView(LoginRequiredMixin, CreateView):
context = super().get_context_data(**kwargs)
from cards.models import Card
# Ensure available_cards is a proper QuerySet
context["cards"] = Card.objects.all().order_by("name", "rarity_level")
context["cards"] = Card.objects.filter(rarity_level__lte=5).order_by("name", "rarity_level")
friend_codes = self.request.user.friend_codes.all()
if "initiated_by" in self.request.GET:
try:
@ -245,8 +245,8 @@ class TradeOfferSearchView(ListView):
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
from cards.models import Card
# Populate available_cards to re-populate the multiselects.
context["cards"] = Card.objects.all().order_by("name")
# Populate available_cards to re-populate the multiselects. Exclude cards with rarity level > 5.
context["cards"] = Card.objects.filter(rarity_level__lte=5).order_by("name", "rarity_level")
if self.request.method == "POST":
context["have_cards"] = self.request.POST.getlist("have_cards")
context["want_cards"] = self.request.POST.getlist("want_cards")
@ -515,7 +515,7 @@ class TradeOfferPNGView(View):
return HttpResponse(trade_offer.image.read(), content_type="image/png")
tag_context = render_trade_offer_png(
{'request': request}, trade_offer, show_friend_code=True
{'request': request}, trade_offer, show_friend_code=trade_offer.initiated_by.user.show_friend_code_on_link_previews
)
image_width = tag_context.get('image_width')
image_height = tag_context.get('image_height')
@ -547,7 +547,7 @@ class TradeOfferPNGView(View):
page.on("console", lambda msg: print(f"Console {msg.type}: {msg.text}"))
page.on("pageerror", lambda err: print(f"Page error: {err}"))
page.on("requestfailed", lambda req: print(f"Failed to load: {req.url} - {req.failure.error_text}"))
page.set_content(html, wait_until="domcontentloaded")
page.set_content(html, wait_until="networkidle")
element = page.wait_for_selector(".trade-offer-card-screenshot")
screenshot_bytes = element.screenshot(type="png", omit_background=True)
browser.close()