diff --git a/app/assets/v2/js/cart-data.js b/app/assets/v2/js/cart-data.js index 61a0328cd96..c017bff66ca 100644 --- a/app/assets/v2/js/cart-data.js +++ b/app/assets/v2/js/cart-data.js @@ -56,6 +56,8 @@ class CartData { accptedTokenName = 'DAI'; } + grantData.uuid = get_UUID(); + if (acceptsAllTokens || 'DAI' == accptedTokenName) { grantData.grant_donation_amount = 5; grantData.grant_donation_currency = 'DAI'; @@ -71,14 +73,22 @@ class CartData { cartList.push(grantData); this.setCart(cartList); + + fetchData(`/grants/${grantData.grant_id}/activity`, 'POST', { + action: 'ADD_ITEM', + metadata: JSON.stringify(cartList) + }, {'X-CSRFToken': $("input[name='csrfmiddlewaretoken']").val()}); } static removeIdFromCart(grantId) { let cartList = this.loadCart(); - const newList = cartList.filter(grant => { - return (grant.grant_id !== grantId); - }); + const newList = cartList.filter(grant => grant.grant_id !== grantId); + + fetchData(`/grants/${grantId}/activity`, 'POST', { + action: 'REMOVE_ITEM', + metadata: JSON.stringify(newList) + }, {'X-CSRFToken': $("input[name='csrfmiddlewaretoken']").val()}); this.setCart(newList); } @@ -106,6 +116,21 @@ class CartData { this.setCart(cartList); } + static clearCart() { + let cartList = this.loadCart(); + + cartList.map(grant => { + fetchData('/grants/activity', 'POST', { + action: 'REMOVE_ITEM', + metadata: JSON.stringify(cartList), + bulk: true + }, {'X-CSRFToken': $("input[name='csrfmiddlewaretoken']").val()}); + }); + + localStorage.setItem('grants_cart', JSON.stringify([])); + applyCartMenuStyles(); + } + static loadCart() { const cartList = localStorage.getItem('grants_cart'); diff --git a/app/assets/v2/js/cart.js b/app/assets/v2/js/cart.js index 5e7fedbbd2a..8071f5fe972 100644 --- a/app/assets/v2/js/cart.js +++ b/app/assets/v2/js/cart.js @@ -345,7 +345,7 @@ Vue.component('grants-cart', { }, clearCart() { - CartData.setCart([]); + CartData.clearCart(); this.grantData = []; update_cart_title(); }, diff --git a/app/assets/v2/js/shared.js b/app/assets/v2/js/shared.js index 348537d0a08..dd9fd2a3853 100644 --- a/app/assets/v2/js/shared.js +++ b/app/assets/v2/js/shared.js @@ -1244,3 +1244,15 @@ const fetchIssueDetailsFromGithub = issue_url => { }); }); }; + +const get_UUID = () => { + var dt = new Date().getTime(); + const uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { + const r = (dt + Math.random() * 16) % 16 | 0; + + dt = Math.floor(dt / 16); + return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16); + }); + + return uuid; +}; diff --git a/app/grants/admin.py b/app/grants/admin.py index 1601112f807..a639ef122bd 100644 --- a/app/grants/admin.py +++ b/app/grants/admin.py @@ -22,7 +22,7 @@ from django.utils.html import format_html from django.utils.safestring import mark_safe -from grants.models import CLRMatch, Contribution, Flag, Grant, MatchPledge, PhantomFunding, Subscription +from grants.models import CLRMatch, Contribution, Flag, Grant, MatchPledge, PhantomFunding, Subscription, CartActivity class GeneralAdmin(admin.ModelAdmin): @@ -147,7 +147,7 @@ def subscriptions_links(self, instance): def contributions_links(self, instance): """Define the logo image tag to be displayed in the admin.""" - eles = [] + eles = [] for i in [True, False]: html = f"

Success {i}

" @@ -308,6 +308,12 @@ def from_ip_address(self, instance): return " , ".join(visits) +class CartActivityAdmin(admin.ModelAdmin): + list_display = ['id', 'grant', 'profile', 'action', 'bulk', 'created_on'] + raw_id_fields = ['grant', 'profile'] + search_fields = ['bulk', 'action', 'grant'] + + admin.site.register(PhantomFunding, PhantomFundingAdmin) admin.site.register(MatchPledge, MatchPledgeAdmin) admin.site.register(Grant, GrantAdmin) @@ -315,3 +321,4 @@ def from_ip_address(self, instance): admin.site.register(CLRMatch, CLRMatchAdmin) admin.site.register(Subscription, SubscriptionAdmin) admin.site.register(Contribution, ContributionAdmin) +admin.site.register(CartActivity, CartActivityAdmin) diff --git a/app/grants/migrations/0059_auto_20200617_1508.py b/app/grants/migrations/0059_auto_20200617_1508.py new file mode 100644 index 00000000000..3fefba4a745 --- /dev/null +++ b/app/grants/migrations/0059_auto_20200617_1508.py @@ -0,0 +1,48 @@ +# Generated by Django 2.2.4 on 2020-06-17 15:08 + +import django.contrib.postgres.fields.jsonb +from django.db import migrations, models +import django.db.models.deletion +import economy.models + + +class Migration(migrations.Migration): + + dependencies = [ + ('dashboard', '0122_auto_20200615_1510'), + ('grants', '0058_auto_20200615_1226'), + ] + + operations = [ + migrations.AlterField( + model_name='contribution', + name='tx_id', + field=models.CharField(blank=True, default='0x0', help_text='The transaction ID of the Contribution.', max_length=255), + ), + migrations.AlterField( + model_name='grant', + name='grant_type', + field=models.CharField(choices=[('tech', 'tech'), ('health', 'health'), ('Community', 'media'), ('change', 'change'), ('matic', 'matic')], default='tech', help_text='Grant CLR category', max_length=15), + ), + migrations.AlterField( + model_name='matchpledge', + name='pledge_type', + field=models.CharField(choices=[('tech', 'tech'), ('media', 'media'), ('health', 'health'), ('change', 'change')], default='tech', help_text='CLR pledge type', max_length=15), + ), + migrations.CreateModel( + name='CartActivity', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created_on', models.DateTimeField(db_index=True, default=economy.models.get_time)), + ('modified_on', models.DateTimeField(default=economy.models.get_time)), + ('action', models.CharField(choices=[('ADD_ITEM', 'Add item to cart'), ('REMOVE_ITEM', 'Remove item to cart')], help_text='Type of activity', max_length=20)), + ('metadata', django.contrib.postgres.fields.jsonb.JSONField(blank=True, default=dict, help_text='Related data to the action')), + ('bulk', models.BooleanField(default=False)), + ('grant', models.ForeignKey(help_text='Related Grant Activity ', null=True, on_delete=django.db.models.deletion.CASCADE, related_name='cart_actions', to='grants.Grant')), + ('profile', models.ForeignKey(help_text='User Cart Activity', on_delete=django.db.models.deletion.CASCADE, related_name='cart_activity', to='dashboard.Profile')), + ], + options={ + 'abstract': False, + }, + ), + ] diff --git a/app/grants/models.py b/app/grants/models.py index d5331c24719..9d4a9cfb726 100644 --- a/app/grants/models.py +++ b/app/grants/models.py @@ -1401,3 +1401,20 @@ def to_mock_contribution(self): context['tx_cleared'] = True context['success'] = True return context + + +class CartActivity(SuperModel): + ACTIONS = ( + ('ADD_ITEM', 'Add item to cart'), + ('REMOVE_ITEM', 'Remove item to cart') + ) + grant = models.ForeignKey(Grant, null=True, on_delete=models.CASCADE, related_name='cart_actions', + help_text=_('Related Grant Activity ')) + profile = models.ForeignKey('dashboard.Profile', on_delete=models.CASCADE, related_name='cart_activity', + help_text=_('User Cart Activity')) + action = models.CharField(max_length=20, choices=ACTIONS, help_text=_('Type of activity')) + metadata = JSONField(default=dict, blank=True, help_text=_('Related data to the action')) + bulk = models.BooleanField(default=False) + + def __str__(self): + return f'{self.action} {self.grant.id if self.grant else "bulk"} from the cart {self.profile.handle}' diff --git a/app/grants/templates/grants/index.html b/app/grants/templates/grants/index.html index d3d0f005472..22d4b5477f0 100644 --- a/app/grants/templates/grants/index.html +++ b/app/grants/templates/grants/index.html @@ -47,7 +47,7 @@ + Add to Cart

Want to contribute to stability of ecosystem funding? You funded {{prev_grants.count}} grant{{ prev_grants.count|pluralize }} last time. Fund - them again?

+ them again?

{% for prev_grant in prev_grants %} {{prev_grant.title}} {% if not forloop.last %}{% ifequal forloop.revcounter 2 %} and {% else %}, {% endifequal %}{% else %}{% endif %} @@ -89,6 +89,7 @@ + {% csrf_token %}