Fix pagination controls, move mixin to common app, fix pagination invocation on all views, and other random bug fixes

This commit is contained in:
badblocks 2025-04-01 23:01:05 -07:00
parent 7edefe23c3
commit 6a61b79bbe
425 changed files with 51656 additions and 243 deletions

View file

@ -9,11 +9,12 @@
<!-- Tab Navigation -->
<div class="tabs tabs-border mb-8">
<button type="button" :class="{'tab': true, 'tab-active': activeTab === 'dash'}" @click="activeTab = 'dash'">{{ _('Dash') }}</button>
<button type="button" :class="{'tab': true, 'tab-active': activeTab === 'dashboard_offers'}" @click="activeTab = 'dashboard_offers'">{{ _('Your Trade Offers') }} ({{ dashboard_offers_paginated.paginator.count }})</button>
<button type="button" :class="{'tab': true, 'tab-active': activeTab === 'waiting_on_you'}" @click="activeTab = 'waiting_on_you'">{{ _('Waiting on You') }} ({{ trade_acceptances_waiting_paginated.paginator.count }})</button>
<button type="button" :class="{'tab': true, 'tab-active': activeTab === 'waiting_on_them'}" @click="activeTab = 'waiting_on_them'">{{ _('Waiting on Them') }} ({{ other_party_trade_acceptances_paginated.paginator.count }})</button>
<button type="button" :class="{'tab': true, 'tab-active': activeTab === 'dashboard_offers'}" @click="activeTab = 'dashboard_offers'">{{ _('Your Trade Offers') }} ({{ dashboard_offers_paginated.page_obj.count }})</button>
<button type="button" :class="{'tab': true, 'tab-active': activeTab === 'waiting_on_you'}" @click="activeTab = 'waiting_on_you'">{{ _('Waiting on You') }} ({{ trade_acceptances_waiting_paginated.page_obj.count }})</button>
<button type="button" :class="{'tab': true, 'tab-active': activeTab === 'waiting_on_them'}" @click="activeTab = 'waiting_on_them'">{{ _('Waiting on Them') }} ({{ other_party_trade_acceptances_paginated.page_obj.count }})</button>
<button type="button" :class="{'tab': true, 'tab-active': activeTab === 'trade_history'}" @click="activeTab = 'trade_history'">{{ _('Trade History') }}</button>
<button type="button" :class="{'tab': true, 'tab-active': activeTab === 'profile'}" @click="activeTab = 'profile'">{{ _('Profile') }}</button>
<button type="button" :class="{'tab': true, 'tab-active': activeTab === 'friend_codes'}" @click="activeTab = 'friend_codes'">{{ _('Friend Codes') }}</button>
<button type="button" :class="{'tab': true, 'tab-active': activeTab === 'settings'}" @click="activeTab = 'settings'">{{ _('Settings') }}</button>
</div>
@ -21,42 +22,44 @@
<!-- Dash Tab - Dashboard Summary -->
<div x-show="activeTab === 'dash'">
<div class="card bg-base-100 shadow-xl mb-4">
<div class="card-body">
<h2 class="card-title">{{ _('Account Summary') }}</h2>
<p><strong>{{ _('Username:') }}</strong> {{ request.user.username }}</p>
<p><strong>{{ _('Default Friend Code:') }}</strong> {{ selected_friend_code.friend_code }}</p>
<p><strong>{{ _('Reputation Score:') }}</strong> {{ request.user.reputation_score }}</p>
</div>
</div>
<div class="card bg-base-100 shadow-xl mb-4">
<div class="card-body">
<h2 class="card-title">{{ _('Trade Summary') }}</h2>
<div class="stats shadow">
<div class="stat">
<div class="stat-title">{{ _('Your Trade Offers') }}</div>
<div class="stat-value">{{ dashboard_offers_paginated.paginator.count }}</div>
<div class="stat-desc">{{ _('Active Offers') }}</div>
<div class="flex flex-col md:flex-row justify-center gap-4">
<div class="stats shadow">
<div class="stat">
<div class="stat-title">{{ _('Your Reputation') }}</div>
<div class="stat-value">{{ request.user.reputation_score }}</div>
<div class="stat-desc">{{ _('Current Score') }}</div>
</div>
<div class="stat">
<div class="stat-title">{{ _('Your Trade Offers') }}</div>
<div class="stat-value">{{ dashboard_offers_paginated.page_obj.count }}</div>
<div class="stat-desc">{{ _('Active Offers') }}</div>
</div>
</div>
<div class="stat">
<div class="stat-title">{{ _('Waiting on You') }}</div>
<div class="stat-value">{{ trade_acceptances_waiting_paginated.paginator.count }}</div>
<div class="stat-desc">{{ _('Pending Requests') }}</div>
</div>
<div class="stat">
<div class="stat-title">{{ _('Waiting on Them') }}</div>
<div class="stat-value">{{ other_party_trade_acceptances_paginated.paginator.count }}</div>
<div class="stat-desc">{{ _('Pending Responses') }}</div>
<div class="stats shadow">
<div class="stat">
<div class="stat-title">{{ _('Waiting on You') }}</div>
<div class="stat-value">{{ trade_acceptances_waiting_paginated.page_obj.count }}</div>
<div class="stat-desc">{{ _('Pending Requests') }}</div>
</div>
<div class="stat">
<div class="stat-title">{{ _('Waiting on Them') }}</div>
<div class="stat-value">{{ other_party_trade_acceptances_paginated.page_obj.count }}</div>
<div class="stat-desc">{{ _('Pending Responses') }}</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="card bg-base-100 shadow-xl">
<div class="card-body">
<h2 class="card-title">{{ _('Quick Actions') }}</h2>
<div class="flex space-x-4">
<a href="{% url 'trade_offer_create' %}" class="btn btn-primary">{{ _('Create New Offer') }}</a>
<a href="{% url 'trade_offer_list' %}" class="btn btn-secondary">{{ _('View All Offers') }}</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 'account_logout' %}" class="btn btn-warning grow">{{ _('Sign Out') }}</a>
</div>
</div>
</div>
@ -64,36 +67,36 @@
<!-- Your Trade Offers Tab -->
<div x-show="activeTab === 'dashboard_offers'" x-data="tradeOffersPagination('{% url 'dashboard' %}?ajax_section=dashboard_offers')">
{% include 'trades/_trade_offer_list.html' with offers=dashboard_offers_paginated %}
{% include 'trades/_trade_offer_list.html' with offers=dashboard_offers_paginated.object_list page_obj=dashboard_offers_paginated.page_obj %}
</div>
<!-- Waiting on You Tab -->
<div x-show="activeTab === 'waiting_on_you'" x-data="tradeOffersPagination('{% url 'dashboard' %}?ajax_section=waiting_acceptances')">
{% include 'trades/_trade_offer_list.html' with offers=trade_acceptances_waiting_paginated %}
{% include 'trades/_trade_offer_list.html' with offers=trade_acceptances_waiting_paginated.object_list page_obj=trade_acceptances_waiting_paginated.page_obj %}
</div>
<!-- Waiting on Them Tab -->
<div x-show="activeTab === 'waiting_on_them'" x-data="tradeOffersPagination('{% url 'dashboard' %}?ajax_section=other_party_acceptances')">
{% include 'trades/_trade_offer_list.html' with offers=other_party_trade_acceptances_paginated %}
{% include 'trades/_trade_offer_list.html' with offers=other_party_trade_acceptances_paginated.object_list page_obj=other_party_trade_acceptances_paginated.page_obj %}
</div>
<!-- Trade History Tab -->
<div x-show="activeTab === 'trade_history'">
<div class="divider">{{ _('Closed Offers') }} ({{ closed_offers_paginated.paginator.count }})</div>
<div class="divider">{{ _('Closed Offers') }} ({{ closed_offers_paginated.page_obj.count }})</div>
<div class="mb-8">
{% include 'trades/_trade_offer_list.html' with offers=closed_offers_paginated %}
{% include 'trades/_trade_offer_list.html' with offers=closed_offers_paginated.object_list page_obj=closed_offers_paginated.page_obj %}
</div>
<div class="divider">{{ _('Closed Acceptances') }} ({{ closed_acceptances_paginated.paginator.count }})</div>
<div class="divider">{{ _('Closed Acceptances') }} ({{ closed_acceptances_paginated.page_obj.count }})</div>
<div class="mb-8">
{% include 'trades/_trade_offer_list.html' with offers=closed_acceptances_paginated %}
{% include 'trades/_trade_offer_list.html' with offers=closed_acceptances_paginated.object_list page_obj=closed_acceptances_paginated.page_obj %}
</div>
<div class="divider">{{ _('Rejected by Them') }} ({{ rejected_by_them_paginated.paginator.count }})</div>
<div class="divider">{{ _('Rejected by Them') }} ({{ rejected_by_them_paginated.page_obj.count }})</div>
<div class="mb-8">
{% include 'trades/_trade_offer_list.html' with offers=rejected_by_them_paginated %}
{% include 'trades/_trade_offer_list.html' with offers=rejected_by_them_paginated.object_list page_obj=rejected_by_them_paginated.page_obj %}
</div>
<div class="divider">{{ _('Rejected by Me') }} ({{ rejected_by_me_paginated.paginator.count }})</div>
<div class="divider">{{ _('Rejected by Me') }} ({{ rejected_by_me_paginated.page_obj.count }})</div>
<div class="mb-8">
{% include 'trades/_trade_offer_list.html' with offers=rejected_by_me_paginated %}
{% include 'trades/_trade_offer_list.html' with offers=rejected_by_me_paginated.object_list page_obj=rejected_by_me_paginated.page_obj %}
</div>
</div>
@ -105,21 +108,69 @@
<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" />
<h3 class="text-xl mb-2">{{ gravatar_profile.displayName|default:request.user.username }}</h3>
<a href="{{ gravatar_profile.profileUrl }}" target="_blank" class="btn btn-primary">{{ _('View Gravatar Profile') }}</a>
<a href="https://gravatar.com/profile/" target="_blank" rel="noopener noreferrer" class="btn btn-primary">
Edit Profile 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">
<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>
</a>
</div>
{% else %}
<p class="text-center">{{ _('No Gravatar profile data available.') }}</p>
{% endif %}
{% endwith %}
<div class="divider"></div>
<h2 class="text-lg font-semibold">{{ _('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 use Gravatar to display your avatar automatically.') }}</p>
<h2 class="text-lg font-semibold">{{ _('How does it work?') }}</h2>
<p class="mb-4">{{ _('If you have set up a Gravatar, your profile picture will appear whenever you use your email on supported sites. Updates made on Gravatar will reflect here.') }}</p>
<h2 class="text-lg font-semibold">{{ _('Is it safe? What about privacy?') }}</h2>
<p class="mb-4">{{ _('Gravatar is optional, and your email is hashed to maintain privacy. Your personal data remains secure.') }}</p>
<h2 class="text-lg 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 changes will appear here once saved!') }}</p>
<h2 class="text-lg 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>
<h2 class="text-lg 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>
<h2 class="text-lg 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>
<h2 class="text-lg 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>
</div>
</div>
<!-- Friend Codes Tab -->
<div x-show="activeTab === 'friend_codes'">
<div class="card card-border bg-base-100 shadow-lg mx-auto p-6 mb-4">
{% 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 %}" class="link link-hover">{{ 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 justify-end">
<a href="{% url 'add_friend_code' %}" class="btn btn-primary">Add a New Friend Code</a>
</div>
</div>
</div>
@ -129,13 +180,11 @@
<form method="post" action="{% url 'dashboard' %}">
{% csrf_token %}
{{ settings_form|crispy }}
<a href="{% url 'list_friend_codes' %}" class="link link-secondary">{{ _('Edit Friend Codes') }}</a>
<button type="submit" name="update_settings" class="w-full btn btn-success mt-4">{{ _('Save Settings') }}</button>
<div class="flex justify-end">
<button type="submit" name="update_settings" class="btn btn-success mt-4">{{ _('Save Settings') }}</button>
</div>
</form>
</div>
<div class="flex flex-col gap-4">
<a href="{% url 'account_logout' %}" class="btn btn-warning">{{ _('Sign Out') }}</a>
</div>
</div>
</div>