Move profile and settings into the new unified dashboard, showing user info in one place

This commit is contained in:
badblocks 2025-03-31 22:20:59 -07:00
parent 2d826734a0
commit 7edefe23c3
37 changed files with 726 additions and 500 deletions

View file

@ -0,0 +1,179 @@
{% extends "base.html" %}
{% load i18n static crispy_forms_tags gravatar %}
{% block head_title %}{{ _('Dashboard') }}{% endblock %}
{% block content %}
<div class="container mx-auto" x-data="{ activeTab: '{{ active_tab|default:'dash' }}' }">
<!-- 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 === '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 === 'settings'}" @click="activeTab = 'settings'">{{ _('Settings') }}</button>
</div>
<!-- Tab Panels -->
<!-- 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>
<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>
</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>
</div>
</div>
</div>
</div>
<!-- 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 %}
</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 %}
</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 %}
</div>
<!-- Trade History Tab -->
<div x-show="activeTab === 'trade_history'">
<div class="divider">{{ _('Closed Offers') }} ({{ closed_offers_paginated.paginator.count }})</div>
<div class="mb-8">
{% include 'trades/_trade_offer_list.html' with offers=closed_offers_paginated %}
</div>
<div class="divider">{{ _('Closed Acceptances') }} ({{ closed_acceptances_paginated.paginator.count }})</div>
<div class="mb-8">
{% include 'trades/_trade_offer_list.html' with offers=closed_acceptances_paginated %}
</div>
<div class="divider">{{ _('Rejected by Them') }} ({{ rejected_by_them_paginated.paginator.count }})</div>
<div class="mb-8">
{% include 'trades/_trade_offer_list.html' with offers=rejected_by_them_paginated %}
</div>
<div class="divider">{{ _('Rejected by Me') }} ({{ rejected_by_me_paginated.paginator.count }})</div>
<div class="mb-8">
{% include 'trades/_trade_offer_list.html' with offers=rejected_by_me_paginated %}
</div>
</div>
<!-- Profile Tab -->
<div x-show="activeTab === 'profile'">
<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 %}
{% if gravatar_profile %}
<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>
</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>
</div>
</div>
<!-- Settings Tab -->
<div x-show="activeTab === 'settings'">
<div class="card card-border bg-base-100 shadow-lg mx-auto p-6 mb-4">
<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>
</form>
</div>
<div class="flex flex-col gap-4">
<a href="{% url 'account_logout' %}" class="btn btn-warning">{{ _('Sign Out') }}</a>
</div>
</div>
</div>
<script>
function tradeOffersPagination(baseUrl) {
return {
baseUrl: baseUrl,
_hasChangePageListener: false,
loadPage(page) {
let url = new URL(this.baseUrl, window.location.origin);
url.searchParams.set("page", page);
fetch(url, {
headers: { 'X-Requested-With': 'XMLHttpRequest' }
})
.then(response => response.text())
.then(html => {
this.$el.innerHTML = html;
this.init();
});
},
init() {
if (!this._hasChangePageListener) {
this.$el.addEventListener('change-page', event => {
let page = event.detail.page;
this.loadPage(page);
});
this._hasChangePageListener = true;
}
this.$el.querySelectorAll("a.ajax-page-link").forEach(link => {
link.addEventListener("click", (event) => {
event.preventDefault();
let page = link.getAttribute("data-page");
this.loadPage(page);
});
});
}
}
}
</script>
{% endblock %}

View file

@ -1,39 +0,0 @@
{% extends 'base.html' %}
{% load i18n gravatar%}
{% block head_title %}{% trans "Settings" %}{% endblock %}
{% block content %}
<div class="container mx-auto">
<h1 class="text-3xl font-semibold text-center mb-6">{% trans "Profile" %}</h1>
<div class="card card-border bg-base-100 shadow-lg w-4/5 mx-auto">
<div class="card-body">
<div class="hovercard-preview">
<iframe src="https://gravatar.com/{{ user.email|gravatar_hash }}.card" width="100%" height="344px" style="border:0; margin:0; padding:0;" onload="resizeIframe(this)"></iframe>
</div>
<p class="text-center">All profile information is managed through Gravatar.</p>
<div class="text-center mt-4 flex flex-col gap-4 mx-auto">
<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>
<a href="{% url 'settings' %}" class="btn btn-secondary">Back to Settings</a>
</div>
<div class="divider"></div>
<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>
</div>
{% endblock %}

View file

@ -1,35 +0,0 @@
{% extends 'base.html' %}
{% load i18n crispy_forms_tags %}
{% block head_title %}{% trans "Settings" %}{% endblock %}
{% block content %}
<div class="container mx-auto space-y-8">
<h1 class="text-3xl font-bold text-center mb-6">{% trans "Settings" %}</h1>
<!-- Account Navigation Section -->
<div class="card card-border bg-base-100 shadow-lg w-4/5 mx-auto p-6">
<form method="post">
{% csrf_token %}
{{ form|crispy }}
<button type="submit" class="w-full btn btn-success mt-4">
{% trans "Save Settings" %}
</button>
</form>
<div class="divider my-4"></div>
<div class="flex flex-col gap-4">
<div class="flex flex-row gap-4">
<a href="{% url 'profile' %}" class="btn btn-secondary w-20 grow-1">
{% trans "Profile" %}
</a>
<a href="{% url 'list_friend_codes' %}" class="btn btn-primary w-20 grow-1">
{% trans "Friend Codes" %}
</a>
</div>
<a href="{% url 'account_logout' %}" class="btn btn-warning">
{% trans "Sign Out" %}
</a>
</div>
</div>
</div>
{% endblock %}

View file

@ -2,8 +2,7 @@
{% url 'home' as home_url %}
{% url 'trade_offer_list' as trade_offer_list_url %}
{% url 'cards:card_list' as cards_list_url %}
{% url 'settings' as settings_url %}
{% url 'dashboard' as dashboard_url %}
<!DOCTYPE html>
<html lang="en">
<head>
@ -68,13 +67,7 @@
class="menu menu-sm dropdown-content bg-base-100 rounded-box z-1 mt-3 w-52 p-2 shadow">
<li><a href="{% url 'home' %}">Home</a></li>
<li><a href="{% url 'cards:card_list' %}">Cards</a></li>
<li>
<a>Trades</a>
<ul class="p-2">
<li><a href="{% url 'trade_offer_list' %}">All Offers</a></li>
<li><a href="{% url 'trade_offer_my_list' %}">My Trades</a></li>
</ul>
</li>
<li><a href="{% url 'trade_offer_list' %}">Trades</a></li>
</ul>
</div>
<a class="btn btn-ghost text-xl" href="{% url 'home' %}">
@ -89,15 +82,7 @@
<ul class="menu menu-horizontal px-1">
<li><a href="{% url 'home' %}">Home</a></li>
<li><a href="{% url 'cards:card_list' %}">Cards</a></li>
<li>
<details>
<summary>Trades</summary>
<ul class="p-2 w-32 z-10">
<li><a href="{% url 'trade_offer_list' %}">All Offers</a></li>
<li><a href="{% url 'trade_offer_my_list' %}">My Trades</a></li>
</ul>
</details>
</li>
<li><a href="{% url 'trade_offer_list' %}">Trades</a></li>
</ul>
</div>
<div class="navbar-end">
@ -121,8 +106,8 @@
tabindex="0"
class="menu menu-sm dropdown-content bg-base-100 rounded-box z-1 mt-3 w-32 p-2 shadow">
<li>
<a class="justify-between" href="{% url 'settings' %}">
Settings
<a class="justify-between" href="{% url 'dashboard' %}">
Dashboard
</a>
</li>
<li><a href="{% url 'account_logout' %}">Sign Out</a></li>
@ -164,9 +149,9 @@
<svg class="size-[1.2em]" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M3.75 12h16.5m-16.5 3.75h16.5M3.75 19.5h16.5M5.625 4.5h12.75a1.875 1.875 0 0 1 0 3.75H5.625a1.875 1.875 0 0 1 0-3.75Z" /></svg>
<span class="dock-label">Trades</span>
</button>
<button @click="window.location.href = '{{ settings_url }}'" class="{% if request.path == settings_url %}dock-active{% endif %}">
<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: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">Settings</span>
<span class="dock-label">Dashboard</span>
</button>
</div>

View file

@ -7,29 +7,6 @@
</div>
{% endfor %}
</div>
{% if is_paginated %}
<div class="flex justify-between items-center mt-4">
{% if page_obj.has_previous %}
<button type="button" class="btn btn-sm"
@click="$dispatch('change-page-{{ side }}', { page: {{ page_obj.previous_page_number }} })">
Previous
</button>
{% else %}
<span></span>
{% endif %}
{% if paginator.num_pages > 1 %}
<span class="text-sm">Page {{ page_obj.number }} of {{ paginator.num_pages }}</span>
{% endif %}
{% if page_obj.has_next %}
<button type="button" class="btn btn-sm"
@click="$dispatch('change-page-{{ side }}', { page: {{ page_obj.next_page_number }} })">
Next
</button>
{% else %}
<span></span>
{% endif %}
</div>
{% endif %}
{% else %}
<p class="text-gray-500">No trade offers found.</p>
{% endif %}

View file

@ -36,7 +36,7 @@
{% endif %}
<div class="mt-4 flex flex-row justify-between">
<a href="{% url 'settings' %}" class="btn btn-secondary">Back to Settings</a>
<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>

View file

@ -1,4 +1,4 @@
{% load trade_offer_tags %}
{% load trade_offer_tags pagination_tags %}
{% comment %}
This snippet renders a grid of trade offer cards (or acceptance cards) along with pagination controls.
For a TradeOffer, we use {% render_trade_offer %}; for a TradeAcceptance, {% render_trade_acceptance %}.
@ -17,4 +17,5 @@
{% empty %}
<div>No trade offers available.</div>
{% endfor %}
</div>
</div>
{% render_pagination offers %}

View file

@ -1,38 +0,0 @@
{% include "trades/_trade_offer_list.html" %}
{% if offers.has_other_pages %}
<nav aria-label="Trade offers pagination" class="mt-6">
<ul class="flex justify-center space-x-2">
{% if offers.has_previous %}
<li>
<a class="btn btn-outline ajax-page-link" data-page="{{ offers.previous_page_number }}" href="#">
Previous
</a>
</li>
{% else %}
<li>
<span class="btn btn-outline btn-disabled">Previous</span>
</li>
{% endif %}
{% for num in offers.paginator.page_range %}
<li>
<a class="btn btn-outline ajax-page-link {% if offers.number == num %}btn-active{% endif %}" data-page="{{ num }}" href="#">
{{ num }}
</a>
</li>
{% endfor %}
{% if offers.has_next %}
<li>
<a class="btn btn-outline ajax-page-link" data-page="{{ offers.next_page_number }}" href="#">
Next
</a>
</li>
{% else %}
<li>
<span class="btn btn-outline btn-disabled">Next</span>
</li>
{% endif %}
</ul>
</nav>
{% endif %}

View file

@ -0,0 +1,16 @@
Hello {{ recipient_user }},
Great news! {{ acting_user }} ({{ acting_user_friend_code }}) has accepted your trade offer.
Trade Details:
- #{{ hash }}
- They are offering: {{ want_card }}
- They want: {{ has_card }}
What's next? You can now mark the trade as "Sent" once you've offered the card to them in the app, or reject the trade if needed.
Visit your dashboard to manage this trade:
{% url 'dashboard' %}
Happy trading!
PKMN Trade Club

View file

@ -0,0 +1,16 @@
Hello {{ recipient_user }},
{{ acting_user }} ({{ acting_user_friend_code }}) has marked your trade as "Received".
Trade Details:
- #{{ hash }}
- Card you sent: {{ has_card }}
- Card they offered: {{ want_card }}
What's next? Send a thank you to this user to increase their reputation!
Visit your dashboard to send thanks:
{% url 'dashboard' %}
Happy trading!
PKMN Trade Club

View file

@ -0,0 +1,16 @@
Hello {{ recipient_user }},
We're sorry to inform you that {{ acting_user }} ({{ acting_user_friend_code }}) has canceled their trade acceptance.
Trade Details:
- #{{ hash }}
- Card you were going to send: {{ has_card }}
- Card they were offering: {{ want_card }}
Your trade offer is still active and available for other users to accept.
Visit your dashboard to manage your trade offers:
{% url 'dashboard' %}
Happy trading!
PKMN Trade Club

View file

@ -0,0 +1,16 @@
Hello {{ recipient_user }},
We're sorry to inform you that {{ acting_user }} ({{ acting_user_friend_code }}) has rejected the trade.
Trade Details:
- #{{ hash }}
- Card you were going to receive: {{ has_card }}
- Card you were offering: {{ want_card }}
Don't worry - there are plenty of other trade opportunities available! You can browse our marketplace for similar trades.
Visit the marketplace:
https://pkmntrade.club{% url 'trade_offer_list' %}
Better luck with your next trade!
PKMN Trade Club

View file

@ -0,0 +1,16 @@
Hello {{ recipient_user }},
{{ acting_user }} ({{ acting_user_friend_code }}) has marked your trade as "Sent".
Trade Details:
- #{{ hash }}
- Card being sent to you: {{ has_card }}
- Card you're offering: {{ want_card }}
What's next? Once you respond to the trade in the app, please mark the trade as "Received" in your dashboard.
Visit your dashboard to manage this trade:
{% url 'dashboard' %}
Happy trading!
PKMN Trade Club

View file

@ -0,0 +1,16 @@
Hello {{ recipient_user }},
{{ acting_user }} ({{ acting_user_friend_code }}) has sent their thanks for the successful trade!
Trade Details:
- #{{ hash }}
- Card you sent: {{ has_card }}
- Card they offered: {{ want_card }}
What's next? Send a thank you to this user to increase their reputation!
Visit your dashboard to send thanks:
{% url 'dashboard' %}
Happy trading!
PKMN Trade Club

View file

@ -0,0 +1,15 @@
Hello {{ recipient_user }},
{{ acting_user }} ({{ acting_user_friend_code }}) has sent their thanks for the successful trade!
Trade Details:
- #{{ hash }}
- Card {% if is_initiator %}you{% else %}they{% endif %} sent: {{ has_card }}
- Card {% if is_initiator %}they{% else %}you{% endif %} offered: {{ want_card }}
This trade is now completed; no further actions can be made.
Thank you for using PKMN Trade Club.
Happy trading!
PKMN Trade Club

View file

@ -0,0 +1,16 @@
Hello {{ recipient_user }},
{{ acting_user }} ({{ acting_user_friend_code }}) has sent their thanks for the successful trade!
Trade Details:
- #{{ hash }}
- Card they sent: {{ has_card }}
- Card you offered: {{ want_card }}
What's next? Send a thank you to this user to increase their reputation!
Visit your dashboard to send thanks:
{% url 'dashboard' %}
Happy trading!
PKMN Trade Club

View file

@ -1,13 +1,13 @@
{% extends 'base.html' %}
{% load static %}
{% load static pagination_tags %}
{% block title %}All Trade Offers{% endblock title %}
{% block title %}Trade Offers{% endblock title %}
{% block content %}
<div class="container mx-auto max-w-4xl mt-6" x-data="{ allExpanded: false }">
<!-- Header-->
<div class="flex justify-between items-center mb-4">
<h1 class="text-2xl font-bold">All Trade Offers</h1>
<h1 class="text-2xl font-bold">Trade Offers</h1>
<div>
<form method="get" class="flex items-center gap-4" x-data>
<label class="cursor-pointer flex items-center gap-2">
@ -27,47 +27,23 @@
<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-data="tradeOffersPagination('{% url 'trade_offer_list' %}?')"
x-init="init()"
>
{% include "trades/_trade_offer_list_paginated.html" with offers=all_trade_offers_paginated %}
<div id="all-trade-offers"
x-data="{
page: {{ all_trade_offers_paginated.number|default:1 }},
loadOffers() {
let url = new URL('{% url 'trade_offer_list' %}', window.location.origin);
let params = new URLSearchParams(window.location.search);
params.set('page', this.page);
url.search = params.toString();
fetch(url, { headers: { 'X-Requested-With': 'XMLHttpRequest' }})
.then(response => response.text())
.then(html => { this.$el.innerHTML = html; });
}
}"
x-init="loadOffers()"
x-on:change-page.window="page = $event.detail.page; loadOffers()">
{% include "trades/_trade_offer_list.html" with offers=all_trade_offers_paginated %}
</div>
</section>
</div>
<script>
function tradeOffersPagination(baseUrl) {
return {
baseUrl: baseUrl,
loadPage(page) {
let url = new URL(this.baseUrl, window.location.origin);
url.searchParams.set("page", page);
this.$el.innerHTML = '<div class="flex justify-center items-center w-full mt-10"><span class="loading loading-dots loading-xl"></span></div>';
fetch(url, {
headers: {
'X-Requested-With': 'XMLHttpRequest'
}
})
.then(response => response.text())
.then(html => {
this.$el.innerHTML = html;
// Reinitialize the click events after injecting the new fragment.
this.init();
});
},
init() {
// Bind click events for AJAX pagination links within this component.
this.$el.querySelectorAll("a.ajax-page-link").forEach(link => {
link.addEventListener("click", (event) => {
event.preventDefault();
const page = link.getAttribute("data-page");
this.loadPage(page);
});
});
}
}
}
</script>
{% endblock content %}

View file

@ -1,98 +0,0 @@
{% extends 'base.html' %}
{% load static %}
{% block title %}My Trades{% endblock title %}
{% block content %}
<div class="container mx-auto max-w-4xl mt-6" x-data="{ allExpanded: false }">
<!-- Header -->
<div class="flex justify-between items-start mb-4">
<a href="{% url 'trade_offer_create' %}" class="btn btn-success">Create New Offer</a>
<div>
<form method="get" class="flex flex-wrap justify-end space-x-4 gap-2" x-data>
<label class="cursor-pointer flex items-center space-x-2 h-10">
<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 })">
</label>
<label class="cursor-pointer flex items-center space-x-2 h-10">
<span class="font-medium">Only Closed</span>
<input type="checkbox" name="show_closed" value="true" class="toggle toggle-primary" @change="$el.form.submit()" {% if show_closed %}checked{% endif %}>
</label>
{% include "trades/_friend_code_select.html" with friend_codes=friend_codes selected_friend_code=selected_friend_code field_name="friend_code" label="" %}
<button type="submit" class="btn btn-primary" x-show="false">Apply</button>
</form>
</div>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mt-8">
<!-- Section: Waiting for Your Response -->
<section class="mb-12">
<h2 class="text-2xl font-bold mb-4">Waiting for Your Response</h2>
<div
id="waiting-acceptances"
x-data="tradeOffersPagination('{% url 'trade_offer_my_list' %}?ajax_section=waiting_acceptances')"
x-init="init()"
>
{% include "trades/_trade_offer_list_paginated.html" with offers=trade_acceptances_waiting_paginated %}
</div>
</section>
<!-- Section: Waiting for Their Response -->
<section class="mb-12">
<h2 class="text-2xl font-bold mb-4">Waiting for Their Response</h2>
<div
id="other-acceptances"
x-data="tradeOffersPagination('{% url 'trade_offer_my_list' %}?ajax_section=other_party_acceptances')"
x-init="init()"
>
{% include "trades/_trade_offer_list_paginated.html" with offers=other_party_trade_acceptances_paginated %}
</div>
</section>
</div>
<!-- Section: My Trade Offers -->
<section class="mb-12">
<h2 class="text-2xl font-bold mb-4">My Trade Offers</h2>
<div
id="my-trade-offers"
x-data="tradeOffersPagination('{% url 'trade_offer_my_list' %}?ajax_section=my_trade_offers')"
x-init="init()">
{% include "trades/_trade_offer_list_paginated.html" with offers=my_trade_offers_paginated %}
</div>
</section>
</div>
<script>
function tradeOffersPagination(baseUrl) {
return {
baseUrl: baseUrl,
loadPage(page) {
let url = new URL(this.baseUrl, window.location.origin);
url.searchParams.set("page", page);
fetch(url, {
headers: {
'X-Requested-With': 'XMLHttpRequest'
}
})
.then(response => response.text())
.then(html => {
this.$el.innerHTML = html;
// Reinitialize click events after content update.
this.init();
});
},
init() {
// Bind click events on pagination links within the component.
this.$el.querySelectorAll("a.ajax-page-link").forEach(link => {
link.addEventListener("click", (event) => {
event.preventDefault();
let page = link.getAttribute("data-page");
this.loadPage(page);
});
});
}
}
}
</script>
{% endblock content %}

View file

@ -1,38 +1,42 @@
<div class="flex justify-center items-center space-x-2">
<!-- First Button -->
<button class="btn btn-outline btn-md"
{% if not page_obj.has_previous %}disabled{% endif %}
@click="$dispatch('change-page', { page: 1 })">
First
</button>
<!-- Previous Button -->
<button class="btn btn-outline btn-md"
{% if not page_obj.has_previous %}disabled{% endif %}
@click="$dispatch('change-page', { page: {{ page_obj.previous_page }} })">
Prev
</button>
<!-- Goto Page -->
<span class="flex items-center space-x-1 gap-2">
<input type="number" min="1" max="{{ page_obj.paginator.num_pages }}" value="{{ page_obj.number }}"
class="input input-xs text-center" id="gotoPageInput">
{% if not hide_if_one_page or page_obj.paginator.num_pages > 1 %}
<div class="flex flex-col justify-center items-center mt-4">
<div class="flex justify-center items-center space-x-2">
<!-- First Button -->
<button class="btn btn-outline btn-md"
@click="(document.getElementById('gotoPageInput').value >= 1 && document.getElementById('gotoPageInput').value <= {{ page_obj.paginator.num_pages }}) && $dispatch('change-page', { page: parseInt(document.getElementById('gotoPageInput').value) })">
Go
{% if not page_obj.has_previous %}disabled{% endif %}
@click="$dispatch('change-page', { page: 1 })">
First
</button>
</span>
<!-- Next Button -->
<button class="btn btn-outline btn-md"
{% if not page_obj.has_next %}disabled{% endif %}
@click="$dispatch('change-page', { page: {{ page_obj.next_page }} })">
Next
</button>
<!-- Last Button -->
<button class="btn btn-outline btn-md"
{% if page_obj.number == page_obj.paginator.num_pages %}disabled{% endif %}
@click="$dispatch('change-page', { page: {{ page_obj.paginator.num_pages }} })">
Last
</button>
<!-- Previous Button -->
<button class="btn btn-outline btn-md"
{% if not page_obj.has_previous %}disabled{% endif %}
@click="$dispatch('change-page', { page: {% if page_obj.has_previous %}{{ page_obj.previous_page_number }}{% else %}1{% endif %} })">
Prev
</button>
<!-- Goto Page -->
<span class="flex items-center space-x-1 gap-2">
<input type="number" min="1" max="{{ page_obj.paginator.num_pages }}" value="{{ page_obj.number }}"
class="input input-xs text-center" id="gotoPageInput_{{ page_obj.number }}" title="Enter page number">
<button class="btn btn-outline btn-md"
@click="(document.getElementById('gotoPageInput_{{ page_obj.number }}').value >= 1 && document.getElementById('gotoPageInput_{{ page_obj.number }}').value <= {{ page_obj.paginator.num_pages }}) && $dispatch('change-page', { page: parseInt(document.getElementById('gotoPageInput_{{ page_obj.number }}').value) })">
Go
</button>
</span>
<!-- Next Button -->
<button class="btn btn-outline btn-md"
{% if not page_obj.has_next %}disabled{% endif %}
@click="$dispatch('change-page', { page: {% if page_obj.has_next %}{{ page_obj.next_page_number }}{% else %}{{ page_obj.number }}{% endif %} })">
Next
</button>
<!-- Last Button -->
<button class="btn btn-outline btn-md"
{% if page_obj.number == page_obj.paginator.num_pages %}disabled{% endif %}
@click="$dispatch('change-page', { page: {{ page_obj.paginator.num_pages }} })">
Last
</button>
</div>
<div class="flex items-center justify-center mt-2">
<span>Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}</span>
</div>
</div>
<div class="flex items-center justify-center mt-2">
<span>Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}</span>
</div>
{% endif %}