add email setting, closes #5. other misc small fixes
This commit is contained in:
parent
135bd95a6a
commit
86b061c971
11 changed files with 85 additions and 99 deletions
|
|
@ -87,4 +87,4 @@ class CustomUserCreationForm(SignupForm):
|
||||||
class UserSettingsForm(forms.ModelForm):
|
class UserSettingsForm(forms.ModelForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = CustomUser
|
model = CustomUser
|
||||||
fields = ['show_friend_code_on_link_previews']
|
fields = ['show_friend_code_on_link_previews', 'enable_email_notifications']
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
# Generated by Django 5.1.2 on 2025-04-13 05:10
|
# Generated by Django 5.1.2 on 2025-04-14 04:07
|
||||||
|
|
||||||
import accounts.models
|
import accounts.models
|
||||||
import django.contrib.auth.models
|
import django.contrib.auth.models
|
||||||
|
|
@ -33,6 +33,7 @@ class Migration(migrations.Migration):
|
||||||
('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')),
|
('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')),
|
||||||
('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')),
|
('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')),
|
||||||
('show_friend_code_on_link_previews', models.BooleanField(default=False, help_text='This will primarily affect share link previews on X, Discord, etc.', verbose_name='Show Friend Code on Link Previews')),
|
('show_friend_code_on_link_previews', models.BooleanField(default=False, help_text='This will primarily affect share link previews on X, Discord, etc.', verbose_name='Show Friend Code on Link Previews')),
|
||||||
|
('enable_email_notifications', models.BooleanField(default=True, help_text='Receive new trade notifications via email.', verbose_name='Enable Email Notifications')),
|
||||||
('reputation_score', models.IntegerField(default=0)),
|
('reputation_score', models.IntegerField(default=0)),
|
||||||
('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.group', verbose_name='groups')),
|
('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.group', verbose_name='groups')),
|
||||||
('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.permission', verbose_name='user permissions')),
|
('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.permission', verbose_name='user permissions')),
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,11 @@ class CustomUser(AbstractUser):
|
||||||
verbose_name="Show Friend Code on Link Previews",
|
verbose_name="Show Friend Code on Link Previews",
|
||||||
help_text="This will primarily affect share link previews on X, Discord, etc."
|
help_text="This will primarily affect share link previews on X, Discord, etc."
|
||||||
)
|
)
|
||||||
|
enable_email_notifications = models.BooleanField(
|
||||||
|
default=True,
|
||||||
|
verbose_name="Enable Email Notifications",
|
||||||
|
help_text="Receive new trade notifications via email."
|
||||||
|
)
|
||||||
reputation_score = models.IntegerField(default=0)
|
reputation_score = models.IntegerField(default=0)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
# Generated by Django 5.1.2 on 2025-04-13 05:10
|
# Generated by Django 5.1.2 on 2025-04-14 04:07
|
||||||
|
|
||||||
import django.db.models.deletion
|
import django.db.models.deletion
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
|
|
|
||||||
|
|
@ -46,6 +46,10 @@ module.exports = {
|
||||||
'alert-success',
|
'alert-success',
|
||||||
'alert-warning',
|
'alert-warning',
|
||||||
'alert-error',
|
'alert-error',
|
||||||
|
'btn-info',
|
||||||
|
'btn-success',
|
||||||
|
'btn-warning',
|
||||||
|
'btn-error',
|
||||||
'bg-info',
|
'bg-info',
|
||||||
'bg-success',
|
'bg-success',
|
||||||
'bg-warning',
|
'bg-warning',
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
<a href="{{ url }}">
|
<a href="{{ url }}" @click.stop>
|
||||||
<div class="relative block">
|
<div class="relative block">
|
||||||
{% if not expanded %}
|
{% 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="flex flex-row items-center h-[32px] p-1.5 w-40 text-white shadow-lg" style="{{ style }}">
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,6 @@
|
||||||
<script defer>
|
<script defer>
|
||||||
if (!window.updateGlobalCardFilters) {
|
if (!window.updateGlobalCardFilters) {
|
||||||
window.updateGlobalCardFilters = function() {
|
window.updateGlobalCardFilters = function() {
|
||||||
console.log("updateGlobalCardFilters called.");
|
|
||||||
const selects = document.querySelectorAll('.card-multiselect');
|
const selects = document.querySelectorAll('.card-multiselect');
|
||||||
|
|
||||||
// Rebuild global selections and rarity filtering.
|
// Rebuild global selections and rarity filtering.
|
||||||
|
|
@ -46,8 +45,6 @@ if (!window.updateGlobalCardFilters) {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log("Global selected card IDs:", globalSelectedIds, "Current Global rarity:", globalRarity);
|
|
||||||
|
|
||||||
selects.forEach(select => {
|
selects.forEach(select => {
|
||||||
if (select.choicesInstance && select.choicesInstance.dropdown.element) {
|
if (select.choicesInstance && select.choicesInstance.dropdown.element) {
|
||||||
// Reset all options to enabled.
|
// Reset all options to enabled.
|
||||||
|
|
@ -82,7 +79,6 @@ if (!window.updateGlobalCardFilters) {
|
||||||
}
|
}
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
console.log("DOM fully loaded. Initializing card multiselect for field '{{ field_id }}'");
|
|
||||||
const selectField = document.getElementById('{{ field_id }}');
|
const selectField = document.getElementById('{{ field_id }}');
|
||||||
const placeholder = selectField.getAttribute('data-placeholder') || '';
|
const placeholder = selectField.getAttribute('data-placeholder') || '';
|
||||||
|
|
||||||
|
|
@ -156,7 +152,6 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log("Initialized Choices.js instance for field '{{ field_id }}':", choicesInstance);
|
|
||||||
// Associate the Choices instance with the select field.
|
// Associate the Choices instance with the select field.
|
||||||
selectField.choicesInstance = choicesInstance;
|
selectField.choicesInstance = choicesInstance;
|
||||||
|
|
||||||
|
|
@ -166,7 +161,6 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||||
window.cardMultiselectInstances.push(selectField);
|
window.cardMultiselectInstances.push(selectField);
|
||||||
|
|
||||||
selectField.addEventListener('change', function() {
|
selectField.addEventListener('change', function() {
|
||||||
console.log("Select field changed. Current selected values:", selectField.choicesInstance.getValue(true));
|
|
||||||
if (window.updateGlobalCardFilters) {
|
if (window.updateGlobalCardFilters) {
|
||||||
window.updateGlobalCardFilters();
|
window.updateGlobalCardFilters();
|
||||||
}
|
}
|
||||||
|
|
@ -177,7 +171,6 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
|
||||||
choicesContainer.addEventListener('click', function(e) {
|
choicesContainer.addEventListener('click', function(e) {
|
||||||
if (e.target.classList.contains('increment')) {
|
if (e.target.classList.contains('increment')) {
|
||||||
console.log("Increment button clicked.");
|
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
e.stopImmediatePropagation();
|
e.stopImmediatePropagation();
|
||||||
|
|
@ -185,14 +178,12 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||||
if (container) {
|
if (container) {
|
||||||
let quantityBadge = container.querySelector('.card-quantity-badge');
|
let quantityBadge = container.querySelector('.card-quantity-badge');
|
||||||
let quantity = getOptionQuantity(container);
|
let quantity = getOptionQuantity(container);
|
||||||
console.log("Increment action on card", container.getAttribute('data-card-id'), "original quantity:", quantity, "new quantity:", quantity + 1);
|
|
||||||
quantity = quantity + 1;
|
quantity = quantity + 1;
|
||||||
quantityBadge.innerText = quantity;
|
quantityBadge.innerText = quantity;
|
||||||
updateOptionQuantity(container, quantity);
|
updateOptionQuantity(container, quantity);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (e.target.classList.contains('decrement')) {
|
if (e.target.classList.contains('decrement')) {
|
||||||
console.log("Decrement button clicked.");
|
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
e.stopImmediatePropagation();
|
e.stopImmediatePropagation();
|
||||||
|
|
@ -202,20 +193,15 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||||
let quantity = getOptionQuantity(container);
|
let quantity = getOptionQuantity(container);
|
||||||
const cardId = container.getAttribute('data-card-id');
|
const cardId = container.getAttribute('data-card-id');
|
||||||
if (quantity === 1) {
|
if (quantity === 1) {
|
||||||
console.log("Decrement action: quantity is 1 for card", cardId, "initiating removal.");
|
|
||||||
const option = selectField.querySelector('option[value="' + cardId + '"]');
|
const option = selectField.querySelector('option[value="' + cardId + '"]');
|
||||||
if (option) {
|
if (option) {
|
||||||
console.log("Removing card from Choices.js instance. Value removed:", option.value);
|
|
||||||
choicesInstance.removeActiveItemsByValue(option.value);
|
choicesInstance.removeActiveItemsByValue(option.value);
|
||||||
option.selected = false;
|
option.selected = false;
|
||||||
} else {
|
|
||||||
console.log("No active item found for card", cardId);
|
|
||||||
}
|
}
|
||||||
if (window.updateGlobalCardFilters) {
|
if (window.updateGlobalCardFilters) {
|
||||||
window.updateGlobalCardFilters();
|
window.updateGlobalCardFilters();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
console.log("Decrement action on card", cardId, "reducing quantity from", quantity, "to", quantity - 1);
|
|
||||||
quantity = quantity - 1;
|
quantity = quantity - 1;
|
||||||
quantityBadge.innerText = quantity;
|
quantityBadge.innerText = quantity;
|
||||||
updateOptionQuantity(container, quantity);
|
updateOptionQuantity(container, quantity);
|
||||||
|
|
@ -232,18 +218,16 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
|
||||||
function updateOptionQuantity(item, quantity) {
|
function updateOptionQuantity(item, quantity) {
|
||||||
const cardId = item.getAttribute('data-card-id');
|
const cardId = item.getAttribute('data-card-id');
|
||||||
console.log("Updating option quantity for card", cardId, "to", quantity);
|
|
||||||
const option = item.closest('.choices__inner').querySelector('option[value="' + cardId + '"]');
|
const option = item.closest('.choices__inner').querySelector('option[value="' + cardId + '"]');
|
||||||
if (option) {
|
if (option) {
|
||||||
option.setAttribute('data-quantity', quantity);
|
option.setAttribute('data-quantity', quantity);
|
||||||
console.log("Updated data-quantity for card", cardId, "to", quantity);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getOptionQuantity(item) {
|
function getOptionQuantity(item) {
|
||||||
const cardId = item.getAttribute('data-card-id');
|
const cardId = item.getAttribute('data-card-id');
|
||||||
const option = item.closest('.choices__inner').querySelector('option[value="' + cardId + '"]');
|
const option = item.closest('.choices__inner').querySelector('option[value="' + cardId + '"]');
|
||||||
return option ? parseInt(option.getAttribute('data-quantity')) : 1;
|
return option ? parseInt(option.getAttribute('data-quantity')) : "";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (choicesInstance.getValue(true).length > 0 && window.updateGlobalCardFilters) {
|
if (choicesInstance.getValue(true).length > 0 && window.updateGlobalCardFilters) {
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@
|
||||||
<div class="flex justify-start items-center">
|
<div class="flex justify-start items-center">
|
||||||
<!-- Left: Initiator's avatar and "Has" -->
|
<!-- Left: Initiator's avatar and "Has" -->
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<div class="avatar me-2">
|
<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="w-10 rounded-full">
|
||||||
{{ acceptance.trade_offer.initiated_by.user.email|gravatar:40 }}
|
{{ acceptance.trade_offer.initiated_by.user.email|gravatar:40 }}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -44,7 +44,7 @@
|
||||||
Waiting on {{ acceptance.accepted_by.user.username }} to {{ acceptance.next_action_label }}...
|
Waiting on {{ acceptance.accepted_by.user.username }} to {{ acceptance.next_action_label }}...
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<div class="avatar ms-2">
|
<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="w-10 rounded-full">
|
||||||
{{ acceptance.accepted_by.user.email|gravatar:40 }}
|
{{ acceptance.accepted_by.user.email|gravatar:40 }}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -3,25 +3,27 @@
|
||||||
{% cache 60 trade_offer offer_pk %}
|
{% 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 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-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 %}{%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 w-90 transform transition-transform duration-500 ease-in-out{%if flipped %} rotate-y-180{% endif %}"
|
||||||
:class="{'rotate-y-180': flipped}">
|
: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 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">
|
||||||
|
<a{% if not on_detail_page %} href="{% url 'trade_offer_detail' pk=offer_pk %}"{% endif %}>
|
||||||
<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">
|
||||||
<span class="text-sm font-semibold text-center">Has</span>
|
<span class="text-sm font-semibold text-center">Has</span>
|
||||||
<span class="text-sm font-semibold text-center">Wants</span>
|
<span class="text-sm font-semibold text-center">Wants</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="absolute inset-x-0 top-1/2 transform -translate-y-1/2 flex justify-center">
|
<div class="absolute inset-x-0 top-1/2 transform -translate-y-1/2 flex justify-center">
|
||||||
<div class="avatar tooltip tooltip-top" data-tip="{{ initiated_by_username }} | {{ initiated_reputation }} rep">
|
<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="w-10 rounded-full">
|
||||||
{{ initiated_by_email|gravatar:40 }}
|
{{ initiated_by_email|gravatar:40 }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="flip-face-body self-start">
|
<div class="flip-face-body self-start{% if not on_detail_page %} cursor-pointer{% endif %}"{% if not on_detail_page %} @click.stop.prevent="window.location.href = '{% url 'trade_offer_detail' pk=offer_pk %}'"{% endif %}>
|
||||||
<div x-show="offerExpanded" x-collapse.duration.500ms class="px-2 badges">
|
<div x-show="offerExpanded" x-collapse.duration.500ms class="px-2 badges">
|
||||||
<div class="flex flex-row justify-around">
|
<div class="flex flex-row justify-around">
|
||||||
{% if num_cards_available > 0 %}
|
{% if num_cards_available > 0 %}
|
||||||
|
|
@ -42,92 +44,81 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flip-face-footer self-end">
|
<div class="flip-face-footer self-end">
|
||||||
<div class="flex justify-between px-2 pb-2">
|
<a{% if not on_detail_page %} href="{% url 'trade_offer_detail' pk=offer_pk %}"{% endif %}>
|
||||||
<div class="text-gray-500 text-sm tooltip tooltip-right" data-tip="ID: {{ offer_hash }}">
|
<div class="flex justify-between px-2 pb-2">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
|
<div class="text-gray-500 text-sm tooltip tooltip-right" data-tip="ID: {{ offer_hash }}">
|
||||||
stroke="currentColor" class="size-5">
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
|
||||||
<path stroke-linecap="round" stroke-linejoin="round"
|
stroke="currentColor" class="size-5">
|
||||||
d="m11.25 11.25.041-.02a.75.75 0 0 1 1.063.852l-.708 2.836a.75.75 0 0 0 1.063.853l.041-.021M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-9-3.75h.008v.008H12V8.25Z" />
|
<path stroke-linecap="round" stroke-linejoin="round"
|
||||||
</svg>
|
d="m11.25 11.25.041-.02a.75.75 0 0 1 1.063.852l-.708 2.836a.75.75 0 0 0 1.063.853l.041-.021M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-9-3.75h.008v.008H12V8.25Z" />
|
||||||
</div>
|
|
||||||
<div class="text-xs">{{ rarity_icon }}</div>
|
|
||||||
<div class="cursor-pointer text-gray-500"
|
|
||||||
@click.stop="$refs.tradeOffer.scrollIntoView({ behavior: 'auto', block: 'start' });
|
|
||||||
offerExpanded = false;
|
|
||||||
setTimeout(() => {
|
|
||||||
flipped = true;
|
|
||||||
setTimeout(() => { acceptanceExpanded = true; }, 500);
|
|
||||||
}, 500);">
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-5">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M3 8.689c0-.864.933-1.406 1.683-.977l7.108 4.061a1.125 1.125 0 0 1 0 1.954l-7.108 4.061A1.125 1.125 0 0 1 3 16.811V8.69ZM12.75 8.689c0-.864.933-1.406 1.683-.977l7.108 4.061a1.125 1.125 0 0 1 0 1.954l-7.108 4.061a1.125 1.125 0 0 1-1.683-.977V8.69Z" />
|
|
||||||
</svg>
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div class="text-xs text-transparent shadow-gray-700 dark:shadow-white" style="text-shadow: 0 0 0 var(--tw-shadow-color);">{{ rarity_icon }}</div>
|
||||||
|
<div class="cursor-pointer text-gray-500"
|
||||||
|
@click.stop.prevent="$refs.tradeOffer.scrollIntoView({ behavior: 'auto', block: 'start' });
|
||||||
|
offerExpanded = false;
|
||||||
|
setTimeout(() => {
|
||||||
|
flipped = true;
|
||||||
|
setTimeout(() => { acceptanceExpanded = true; }, 500);
|
||||||
|
}, 500);">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-5">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" d="M3 8.689c0-.864.933-1.406 1.683-.977l7.108 4.061a1.125 1.125 0 0 1 0 1.954l-7.108 4.061A1.125 1.125 0 0 1 3 16.811V8.69ZM12.75 8.689c0-.864.933-1.406 1.683-.977l7.108 4.061a1.125 1.125 0 0 1 0 1.954l-7.108 4.061a1.125 1.125 0 0 1-1.683-.977V8.69Z" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</a>
|
||||||
</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 rotate-y-180">
|
<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">
|
<a{% if not on_detail_page %} href="{% url 'trade_offer_detail' pk=offer_pk %}"{% endif %}>
|
||||||
<div class="grid grid-cols-2 items-center">
|
<div class="relative mt-6 mb-4 mx-2 sm:mx-4">
|
||||||
<span class="text-sm font-semibold text-center">Has</span>
|
<div class="grid grid-cols-2 items-center">
|
||||||
<span class="text-sm font-semibold text-center">Wants</span>
|
<span class="text-sm font-semibold text-center">Has</span>
|
||||||
</div>
|
<span class="text-sm font-semibold text-center">Wants</span>
|
||||||
<div class="absolute inset-x-0 top-1/2 transform -translate-y-1/2 flex justify-center">
|
</div>
|
||||||
<div class="avatar tooltip tooltip-top" data-tip="{{ initiated_by_username }} | {{ initiated_reputation }} rep">
|
<div class="absolute inset-x-0 top-1/2 transform -translate-y-1/2 flex justify-center">
|
||||||
<div class="w-10 rounded-full">
|
<div class="avatar tooltip tooltip-top cursor-default" @click.stop.prevent data-tip="{{ initiated_by_username }} | {{ initiated_reputation }} rep">
|
||||||
{{ initiated_by_email|gravatar:40 }}
|
<div class="w-10 rounded-full">
|
||||||
|
{{ initiated_by_email|gravatar:40 }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="flip-face-body self-start">
|
<div class="flip-face-body self-start">
|
||||||
<div class="px-2">
|
<div class="px-2 space-y-3 text-xs">
|
||||||
<div x-show="acceptanceExpanded" x-collapse.duration.500ms class="space-y-3">
|
<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">
|
||||||
{% for acceptance in acceptances %}
|
{% for trade in acceptances %}
|
||||||
<a href="{% url 'trade_acceptance_update' pk=acceptance.pk %}" class="no-underline"
|
<div class="p-2 grid grid-cols-2 justify-items-center items-baseline gap-1">
|
||||||
data-tooltip-html='<div class="flex items-center space-x-2">
|
<div class="">{% card_badge trade.offered_card %}</div>
|
||||||
<div class="avatar">
|
<div class="">{% card_badge trade.requested_card %}</div>
|
||||||
<div class="w-10 rounded-full">
|
<div class="col-span-2 text-center">{{ trade.accepted_by.user.username }} • <a class="link link-hover" href="{% url 'trade_acceptance_update' pk=trade.pk %}">#{{ trade.hash }}</a> • {{ trade.get_state_display }}</div>
|
||||||
{{ acceptance.accepted_by.user.email|gravatar:"40" }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-col">
|
|
||||||
<span class="text-sm">Accepted by: {{ acceptance.accepted_by.user.username }}</span>
|
|
||||||
<span class="text-sm">State: {{ acceptance.state }}</span>
|
|
||||||
<span class="text-sm">ID: {{ acceptance.hash }}</span>
|
|
||||||
</div>
|
|
||||||
</div>'>
|
|
||||||
<div class="grid grid-cols-2 gap-4 items-center">
|
|
||||||
<div>
|
|
||||||
{% card_badge acceptance.requested_card %}
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
{% card_badge acceptance.offered_card %}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</a>
|
{% empty %}
|
||||||
{% empty %}
|
<div class="text-center justify-center w-full text-center">No trades yet.</div>
|
||||||
<div class="text-center text-sm mb-2">No trades yet!</div>
|
{% endfor %}
|
||||||
{% endfor %}
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flip-face-footer self-end">
|
<div class="flip-face-footer self-end">
|
||||||
<div class="flex justify-between px-2 pb-2">
|
<a{% if not on_detail_page %} href="{% url 'trade_offer_detail' pk=offer_pk %}"{% endif %}></a>
|
||||||
<div class="text-gray-500 cursor-pointer"
|
<div class="flex justify-between px-2 pb-2">
|
||||||
@click.stop="$refs.tradeOffer.scrollIntoView({ behavior: 'auto', block: 'start' }); acceptanceExpanded = false; setTimeout(() => { flipped = false; setTimeout(() => { offerExpanded = true; }, 500); }, 500);">
|
<div class="text-gray-500 cursor-pointer"
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-5">
|
@click.stop.prevent="$refs.tradeOffer.scrollIntoView({ behavior: 'auto', block: 'start' }); acceptanceExpanded = false; setTimeout(() => { flipped = false; setTimeout(() => { offerExpanded = true; }, 500); }, 500);">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M21 16.811c0 .864-.933 1.406-1.683.977l-7.108-4.061a1.125 1.125 0 0 1 0-1.954l7.108-4.061A1.125 1.125 0 0 1 21 8.689v8.122ZM11.25 16.811c0 .864-.933 1.406-1.683.977l-7.108-4.061a1.125 1.125 0 0 1 0-1.954l7.108-4.061a1.125 1.125 0 0 1 1.683.977v8.122Z" />
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-5">
|
||||||
</svg>
|
<path stroke-linecap="round" stroke-linejoin="round" d="M21 16.811c0 .864-.933 1.406-1.683.977l-7.108-4.061a1.125 1.125 0 0 1 0-1.954l7.108-4.061A1.125 1.125 0 0 1 21 8.689v8.122ZM11.25 16.811c0 .864-.933 1.406-1.683.977l-7.108-4.061a1.125 1.125 0 0 1 0-1.954l7.108-4.061a1.125 1.125 0 0 1 1.683.977v8.122Z" />
|
||||||
</div>
|
</svg>
|
||||||
<div class="text-xs">{{ rarity_icon }}</div>
|
|
||||||
<div class="px-1 text-center text-gray-500">
|
|
||||||
<span class="text-sm font-semibold">
|
|
||||||
({{ acceptances|length }})
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="text-xs text-transparent shadow-gray-700 dark:shadow-white" style="text-shadow: 0 0 0 var(--tw-shadow-color);">{{ rarity_icon }}</div>
|
||||||
|
<div class="px-1 text-center text-gray-500">
|
||||||
|
<span class="text-sm font-semibold">
|
||||||
|
({{ acceptances|length }})
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
# Generated by Django 5.1.2 on 2025-04-13 05:10
|
# Generated by Django 5.1.2 on 2025-04-14 04:07
|
||||||
|
|
||||||
import django.db.models.deletion
|
import django.db.models.deletion
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
|
|
|
||||||
|
|
@ -139,6 +139,9 @@ def trade_acceptance_email_notification(sender, instance, created, **kwargs):
|
||||||
else:
|
else:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
if not recipient_user.enable_email_notifications:
|
||||||
|
return
|
||||||
|
|
||||||
is_initiator = instance.trade_offer.initiated_by.user.pk == acting_user.pk
|
is_initiator = instance.trade_offer.initiated_by.user.pk == acting_user.pk
|
||||||
|
|
||||||
email_context = {
|
email_context = {
|
||||||
|
|
@ -159,8 +162,6 @@ def trade_acceptance_email_notification(sender, instance, created, **kwargs):
|
||||||
email_subject += render_to_string("email/trades/trade_update_" + state + "_subject.txt", email_context)
|
email_subject += render_to_string("email/trades/trade_update_" + state + "_subject.txt", email_context)
|
||||||
email_body = render_to_string(email_template, email_context)
|
email_body = render_to_string(email_template, email_context)
|
||||||
|
|
||||||
print("initiated by: ", instance.trade_offer.initiated_by, ", accepted by: ", instance.accepted_by, ", acting user: ", acting_user, ", recipient user: ", recipient_user, ", state: ", state)
|
|
||||||
|
|
||||||
send_mail(
|
send_mail(
|
||||||
email_subject,
|
email_subject,
|
||||||
email_body,
|
email_body,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue