fix card badges to have better rarity visibility and other bugfixes

This commit is contained in:
badblocks 2025-04-06 22:33:01 -07:00
parent 37d8bd5981
commit 262f0ea190
14 changed files with 4515 additions and 1255 deletions

View file

@ -23,7 +23,7 @@ COPY .env.production /code/.env
ENV HOME=/code ENV HOME=/code
# Install NPM & node.js # Install NPM & node.js
RUN apt-get update && apt-get install -y nodejs npm xvfb RUN apt-get update && apt-get install -y nodejs npm xvfb netcat-openbsd
RUN playwright install-deps && playwright install RUN playwright install-deps && playwright install

View file

@ -1,19 +0,0 @@
from django.core.management.base import BaseCommand
from accounts.models import CustomUser, FriendCode
class Command(BaseCommand):
help = "Seed default friend codes for TestUsers after friend codes have been loaded."
def handle(self, *args, **options):
users_updated = 0
for user in CustomUser.objects.all():
# Automatically select the earliest friend code added for the user:
default_code = FriendCode.objects.filter(user=user).order_by('created_at').first()
if default_code:
user.default_friend_code = default_code
user.save(update_fields=["default_friend_code"])
self.stdout.write(f"Set default friend code for user {user.username} to {default_code.friend_code}.")
users_updated += 1
else:
self.stdout.write(f"No friend code found for user {user.username}.")
self.stdout.write(self.style.SUCCESS(f"Seeded default friend codes for {users_updated} user(s)."))

View file

@ -1,4 +1,4 @@
# Generated by Django 5.1.2 on 2025-03-31 22:48 # Generated by Django 5.1.2 on 2025-04-07 04:19
import accounts.models import accounts.models
import django.contrib.auth.models import django.contrib.auth.models

View file

@ -1,4 +1,4 @@
# Generated by Django 5.1.2 on 2025-03-31 22:48 # Generated by Django 5.1.2 on 2025-04-07 04:19
import django.db.models.deletion import django.db.models.deletion
from django.db import migrations, models from django.db import migrations, models

View file

@ -45,5 +45,7 @@ def update_card_style(sender, instance, action, **kwargs):
else: else:
instance.style = "background: linear-gradient(to right, #AAAAAA, #AAAAAA, #AAAAAA);" instance.style = "background: linear-gradient(to right, #AAAAAA, #AAAAAA, #AAAAAA);"
if not color_is_dark(decks.first().hex_color): if not color_is_dark(decks.first().hex_color):
instance.style += "color: var(--color-gray-700);" instance.style += "color: var(--color-gray-700); text-shadow: 0 0 0 var(--color-gray-700);"
else:
instance.style += "text-shadow: 0 0 0 #fff;"
instance.save(update_fields=["style"]) instance.save(update_fields=["style"])

File diff suppressed because it is too large Load diff

2248
seed/M4R9zUMD.txt Normal file

File diff suppressed because it is too large Load diff

View file

@ -104,27 +104,27 @@
<div x-show="activeTab === 'profile'"> <div x-show="activeTab === 'profile'">
<div class="card card-border bg-base-100 shadow-lg mx-auto p-6 mb-4"> <div class="card card-border bg-base-100 shadow-lg mx-auto p-6 mb-4">
{% with gravatar_profile=request.user.email|gravatar_profile_data %} {% with gravatar_profile=request.user.email|gravatar_profile_data %}
{% if gravatar_profile %}
<div class="hovercard-profile mb-4 text-center"> <div class="hovercard-profile mb-4 text-center">
<img src="{{ gravatar_profile.thumbnailUrl }}" alt="{{ gravatar_profile.displayName|default:request.user.username }}" class="rounded-full mb-2 mx-auto" /> <div class="avatar block mx-auto w-32">
<div class="W-32 rounded-full">
{{ request.user.email|gravatar:128 }}
</div>
</div>
<a href="https://gravatar.com/profile/avatars" target="_blank" rel="noopener noreferrer" class="btn btn-primary mt-4"> <a href="https://gravatar.com/profile/avatars" target="_blank" rel="noopener noreferrer" class="btn btn-primary mt-4">
Edit Avatar on Gravatar Edit Avatar on Gravatar
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" class="size-4"> <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" class="size-4">
<path stroke-linecap="round" stroke-linejoin="round" d="M13.5 6H5.25A2.25 2.25 0 0 0 3 8.25v10.5A2.25 2.25 0 0 0 5.25 21h10.5A2.25 2.25 0 0 0 18 18.75V10.5m-10.5 6L21 3m0 0h-5.25M21 3v5.25" /> <path stroke-linecap="round" stroke-linejoin="round" d="M13.5 6H5.25A2.25 2.25 0 0 0 3 8.25v10.5A2.25 2.25 0 0 0 5.25 21h10.5A2.25 2.25 0 0 0 18 18.75V10.5m-10.5 6L21 3m0 0h-5.25M21 3v5.25" />
</svg> </svg>
</a> </a>
</div> </div>
{% else %}
<p class="text-center">{{ _('No Gravatar profile data available.') }}</p>
{% endif %}
{% endwith %} {% endwith %}
<div class="divider"></div> <div class="divider"></div>
<h2 class="text-base font-semibold pt-0">What is Gravatar?</h2> <h2 class="text-base font-semibold pt-0">What is Gravatar?</h2>
<p class="mb-4 text-sm">Gravatar (Globally Recognized Avatar) is a free service that links your email address to a profile picture. Many websites, including this one, use Gravatar to display your preferred avatar 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. Many websites, including this one, use Gravatar to display your preferred avatar automatically.</p>
<h2 class="text-base font-semibold">How does it work?</h2> <h2 class="text-base font-semibold">How does it work?</h2>
<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. 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. If you don't have a Gravatar yet, you'll see a default randomly-generated avatar instead.</p>
<h2 class="text-base 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 text-sm">Gravatar is completely optional, opt-in, and prioritizes your security and privacy. Your email is never visible to anyone and only a hashed version is shown on the page and sent to Gravatar, protecting your identity while ensuring that your email address is not exposed to bots or scrapers.</p> <p class="mb-4 text-sm">Gravatar is completely optional, opt-in, and prioritizes your security and privacy. Your email is never visible to anyone and only a hashed version is shown on the page and sent to Gravatar, protecting your identity while ensuring that your email address is not exposed to bots or scrapers.</p>

View file

@ -1,7 +1,7 @@
{% extends "base.html" %} {% extends "base.html" %}
{% load static card_badge %} {% load static card_badge %}
{% block content %} {% block content %}
<div class="container mx-auto p-4" <div class="container mx-auto"
x-data="{ x-data="{
order: '{{ order }}', order: '{{ order }}',
groupBy: '{{ group_by|default:'none' }}', groupBy: '{{ group_by|default:'none' }}',
@ -15,10 +15,7 @@
.then(html => { this.$refs.cardList.innerHTML = html; }); .then(html => { this.$refs.cardList.innerHTML = html; });
} }
}" }"
x-init="loadCards()" x-on:change-page.window="page = $event.detail.page; loadCards()">
x-on:change-page.window="page = $event.detail.page; loadCards()"
>
<div class="flex flex-wrap items-center justify-between mb-6"> <div class="flex flex-wrap items-center justify-between mb-6">
<div> <div>
<h1 class="text-2xl font-bold">Cards</h1> <h1 class="text-2xl font-bold">Cards</h1>
@ -57,7 +54,7 @@
</div> </div>
<!-- Container for the partial card list --> <!-- Container for the partial card list -->
<div x-ref="cardList"> <div x-ref="cardList">
{% include "cards/_card_list.html" with cards=cards page_obj=page_obj %}
</div> </div>
</div> </div>
{% endblock %} {% endblock %}

View file

@ -4,7 +4,7 @@
For a TradeOffer, we use {% render_trade_offer %}; for a TradeAcceptance, {% render_trade_acceptance %}. For a TradeOffer, we use {% render_trade_offer %}; for a TradeAcceptance, {% render_trade_acceptance %}.
{% endcomment %} {% endcomment %}
<div class="flex flex-row gap-2 flex-wrap justify-center items-start"> <div class="flex flex-row gap-6 md:gap-2 lg:gap-6 flex-wrap justify-center items-start py-6">
{% for offer in offers %} {% for offer in offers %}
<div class="flex flex-none"> <div class="flex flex-none">
{% if offer.accepted_by %} {% if offer.accepted_by %}

View file

@ -5,64 +5,42 @@
{% block content %} {% block content %}
<div x-data="{ <div x-data="{
// Initialize allExpanded from the URL if present, defaulting to false.
allExpanded: new URLSearchParams(window.location.search).get('expanded') === 'true',
page: {{ page_obj.number|default:1 }}, page: {{ page_obj.number|default:1 }},
loadOffers() { loadOffers() {
let url = new URL('{% url 'trade_offer_list' %}', window.location.origin); let url = new URL('{% url 'trade_offer_list' %}', window.location.origin);
let params = new URLSearchParams(window.location.search); let params = new URLSearchParams(window.location.search);
params.set('page', this.page); params.set('page', this.page);
// Include the expanded state if active
if (this.allExpanded) {
params.set('expanded', 'true');
} else {
params.delete('expanded');
}
url.search = params.toString(); url.search = params.toString();
fetch(url, { headers: { 'X-Requested-With': 'XMLHttpRequest' }}) fetch(url, { headers: { 'X-Requested-With': 'XMLHttpRequest' }})
.then(response => response.text()) .then(response => response.text())
.then(html => { this.$refs.offersContainer.innerHTML = html; }); .then(html => { this.$refs.offersContainer.innerHTML = html; });
},
// Update the URL so that navigation preserves our expanded state.
updateUrl() {
let params = new URLSearchParams(window.location.search);
if (this.allExpanded) {
params.set('expanded', 'true');
} else {
params.delete('expanded');
}
history.replaceState(null, '', window.location.pathname + '?' + params.toString());
} }
}" }"
x-init="loadOffers()"
x-on:change-page.window="page = $event.detail.page; loadOffers()"> x-on:change-page.window="page = $event.detail.page; loadOffers()">
<!-- Header with the toggle --> <!-- Header without Expand All -->
<div class="flex justify-between items-center mb-4"> <div class="flex justify-between items-center mb-4">
<h1 class="text-2xl font-bold">Trade Offers</h1> <h1 class="text-2xl font-bold">Trade Offers</h1>
<div> <div class="flex items-center gap-4">
<form method="get" class="flex items-center gap-4" x-data> <form method="get" class="flex items-center gap-4" x-data>
<label class="cursor-pointer flex items-center gap-2">
<span x-text="allExpanded ? 'Collapse All' : 'Expand All'"></span>
<input type="checkbox" name="all_expanded" value="true" class="toggle toggle-primary"
@click="allExpanded = !allExpanded; $dispatch('toggle-all', { expanded: allExpanded }); updateUrl();">
</label>
<label class="cursor-pointer flex items-center gap-2"> <label class="cursor-pointer flex items-center gap-2">
<span>Only Closed</span> <span>Only Closed</span>
<input type="checkbox" name="show_closed" value="true" class="toggle toggle-primary" @change="$el.form.submit()" {% if show_closed %}checked{% endif %}> <input type="checkbox" name="show_closed" value="true" class="toggle toggle-primary"
@change="$el.form.submit()" {% if show_closed %}checked{% endif %}>
</label> </label>
<button type="submit" class="btn btn-primary" x-show="false">Apply</button> <button type="submit" class="btn btn-primary" x-show="false">Apply</button>
</form> </form>
<div>
<a href="{% url 'trade_offer_create' %}" class="btn btn-success">Create New Offer</a>
</div>
</div> </div>
</div> </div>
<!-- Trade Offers --> <!-- Trade Offers -->
<section class="mb-12"> <section class="mb-12">
<div class="flex justify-end mb-4">
<a href="{% url 'trade_offer_create' %}" class="btn btn-success">Create New Offer</a>
</div>
<div id="all-trade-offers" x-ref="offersContainer"> <div id="all-trade-offers" x-ref="offersContainer">
{% include "trades/_trade_offer_list.html" with offers=offers page_obj=page_obj expanded=expanded %} {% include "trades/_trade_offer_list.html" with offers=offers page_obj=page_obj %}
</div> </div>
</section> </section>
</div> </div>
{% endblock content %} {% endblock content %}

View file

@ -11,7 +11,7 @@
<div class="relative block m-1"> <div class="relative block m-1">
<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-40 text-white shadow-lg" style="{{ style }}">
<div class="row-start-1 col-start-1 col-span-3 truncate text-ellipsis self-start font-semibold leading-tight text-sm">{{ name }}</div> <div class="row-start-1 col-start-1 col-span-3 truncate text-ellipsis self-start font-semibold leading-tight text-sm">{{ name }}</div>
<div class="row-start-2 col-start-1 col-span-3 truncate self-end text-xs">{{ rarity }}</div> <div class="row-start-2 col-start-1 col-span-3 truncate self-end text-xs text-transparent">{{ rarity }}</div>
<div class="row-start-2 col-start-4 col-span-1 self-end text-right truncate font-semibold leading-tight text-sm">{{ cardset }}</div> <div class="row-start-2 col-start-4 col-span-1 self-end text-right truncate font-semibold leading-tight text-sm">{{ cardset }}</div>
{% if quantity != None %}<div class="row-start-1 col-start-4 self-start justify-self-end -me-0.5 relative w-fit ps-1"><div class="card-quantity-badge relative bg-gray-600 text-white text-sm font-semibold rounded-full text-center size-max px-1.5">{{ quantity }}</div></div>{% endif %} {% if quantity != None %}<div class="row-start-1 col-start-4 self-start justify-self-end -me-0.5 relative w-fit ps-1"><div class="card-quantity-badge relative bg-gray-600 text-white text-sm font-semibold rounded-full text-center size-max px-1.5">{{ quantity }}</div></div>{% endif %}
</div> </div>

View file

@ -1,11 +1,11 @@
{% load gravatar card_badge cache %} {% load gravatar card_badge cache %}
{% cache 60 trade_offer offer_pk %} {% cache 60 trade_offer offer_pk %}
<div x-data="{ flipped: {{flipped|lower}}, offerExpanded: true, acceptanceExpanded: false }" x-ref="tradeOffer" class="transition-all duration-500 trade-offer-card my-auto h-full w-auto justify-center"> <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 my-auto h-full w-auto justify-center">
<div class="flip-container"> <div class="flip-container">
<div{% if not on_detail_page %} @click="window.location.href = '{% url 'trade_offer_detail' pk=offer_pk %}'"{% endif %} 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 not on_detail_page %} cursor-pointer{% endif %}" <div{% if not on_detail_page %} @click="window.location.href = '{% url 'trade_offer_detail' pk=offer_pk %}'"{% endif %} 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 not on_detail_page %} cursor-pointer{% endif %}{%if flipped %} rotate-y-180{% endif %}"
:class="{'rotate-y-180': flipped}"> :class="{'rotate-y-180': flipped}">
<div class="flip-face front col-start-1 row-start-1 grid grid-cols-1 auto-rows-min gap-2 content-between"> <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"> <div class="flip-face-header self-start">
<div class="relative mt-6 mb-4 mx-2 sm:mx-4"> <div class="relative mt-6 mb-4 mx-2 sm:mx-4">
<div class="grid grid-cols-2 items-center"> <div class="grid grid-cols-2 items-center">
@ -65,7 +65,7 @@
</div> </div>
</div> </div>
</div> </div>
<div class="flip-face back col-start-1 row-start-1 grid grid-cols-1 auto-rows-min gap-2 content-between" style="transform: rotateY(180deg);"> <div class="flip-face back col-start-1 row-start-1 grid grid-cols-1 auto-rows-min gap-2 content-between rotate-y-180">
<div class="flip-face-header self-start"> <div class="flip-face-header self-start">
<div class="relative mt-6 mb-4 mx-2 sm:mx-4"> <div class="relative mt-6 mb-4 mx-2 sm:mx-4">
<div class="grid grid-cols-2 items-center"> <div class="grid grid-cols-2 items-center">
@ -144,7 +144,7 @@
backface-visibility: hidden; backface-visibility: hidden;
-webkit-backface-visibility: hidden; -webkit-backface-visibility: hidden;
} }
.flip-face.front { .rotate-y-0 {
transform: rotateY(0); transform: rotateY(0);
} }
.rotate-y-180 { .rotate-y-180 {

View file

@ -1,4 +1,4 @@
# Generated by Django 5.1.2 on 2025-03-31 22:48 # Generated by Django 5.1.2 on 2025-04-07 04:19
import django.db.models.deletion import django.db.models.deletion
from django.db import migrations, models from django.db import migrations, models