feat: Wishlist from card actions

- Add remove items from wishlist
- Wishlist icon at nav bar
- Animate wishlist icon in card and navbar
- Remember wished state after refresh as well
This commit is contained in:
marination 2021-03-14 17:28:49 +05:30
parent 4f64d1c7f2
commit 96cc5068b2
10 changed files with 224 additions and 30 deletions

View File

@ -3,8 +3,52 @@
# For license information, please see license.txt
from __future__ import unicode_literals
# import frappe
import frappe
from frappe.model.document import Document
class Wishlist(Document):
pass
@frappe.whitelist()
def add_to_wishlist(item_code, price):
"""Insert Item into wishlist."""
web_item_data = frappe.db.get_value("Website Item", {"item_code": item_code},
["image", "website_warehouse", "name", "item_name"], as_dict=1)
wished_item_dict = {
"item_code": item_code,
"item_name": web_item_data.get("item_name"),
"website_item": web_item_data.get("name"),
"price": frappe.utils.flt(price),
"image": web_item_data.get("image"),
"website_warehouse": web_item_data.get("website_warehouse")
}
if not frappe.db.exists("Wishlist", frappe.session.user):
# initialise wishlist
wishlist = frappe.get_doc({"doctype": "Wishlist"})
wishlist.user = frappe.session.user
wishlist.append("items", wished_item_dict)
wishlist.save(ignore_permissions=True)
else:
wishlist = frappe.get_doc("Wishlist", frappe.session.user)
item = wishlist.append('items', wished_item_dict)
item.db_insert()
if hasattr(frappe.local, "cookie_manager"):
frappe.local.cookie_manager.set_cookie("wish_count", str(len(wishlist.items)))
@frappe.whitelist()
def remove_from_wishlist(item_code):
if frappe.db.exists("Wishlist Items", {"item_code": item_code}):
frappe.db.sql("""
delete
from `tabWishlist Items`
where item_code=%(item_code)s
"""%{"item_code": frappe.db.escape(item_code)})
frappe.db.commit()
wishlist = frappe.get_doc("Wishlist", frappe.session.user)
if hasattr(frappe.local, "cookie_manager"):
frappe.local.cookie_manager.set_cookie("wish_count", str(len(wishlist.items)))

View File

@ -12,6 +12,8 @@
"item_details_section",
"description",
"column_break_7",
"section_break_8",
"price",
"image",
"image_view",
"warehouse_section",
@ -52,6 +54,7 @@
},
{
"fetch_from": "item_code.description",
"fetch_if_empty": 1,
"fieldname": "description",
"fieldtype": "Text Editor",
"label": "Description"
@ -62,6 +65,7 @@
},
{
"fetch_from": "item_code.image",
"fetch_if_empty": 1,
"fieldname": "image",
"fieldtype": "Attach",
"hidden": 1,
@ -69,6 +73,7 @@
},
{
"fetch_from": "item_code.image",
"fetch_if_empty": 1,
"fieldname": "image_view",
"fieldtype": "Image",
"hidden": 1,
@ -87,12 +92,21 @@
"in_list_view": 1,
"label": "Warehouse",
"options": "Warehouse"
},
{
"fieldname": "section_break_8",
"fieldtype": "Section Break"
},
{
"fieldname": "price",
"fieldtype": "Float",
"label": "Price"
}
],
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2021-03-10 19:13:41.310816",
"modified": "2021-03-12 18:23:03.487891",
"modified_by": "Administrator",
"module": "E-commerce",
"name": "Wishlist Items",

View File

@ -67,6 +67,7 @@ class ProductQuery:
product_info = get_product_info_for_website(item.item_code, skip_quotation_creation=True).get('product_info')
if product_info:
item.formatted_price = (product_info.get('price') or {}).get('formatted_price')
item.price = product_info['price'].get('price_list_rate')
if self.settings.show_stock_availability and item.get("website_warehouse"):
stock_qty = frappe.utils.flt(
@ -78,6 +79,11 @@ class ProductQuery:
"actual_qty")
)
item.in_stock = "green" if stock_qty else "red"
item.wished = False
if frappe.db.exists("Wishlist Items", {"item_code": item.item_code}):
item.wished = True
return result
def query_items(self, conditions, or_conditions, substitutions, start=0):

View File

@ -138,7 +138,7 @@ def update_cart(item_code, qty, additional_notes=None, with_items=False):
"additional_notes": additional_notes
})
else:
quotation_items[0].qty = qty + 1
quotation_items[0].qty = qty
quotation_items[0].additional_notes = additional_notes
apply_cart_settings(quotation=quotation)

View File

@ -11,7 +11,8 @@
],
"js/erpnext-web.min.js": [
"public/js/website_utils.js",
"public/js/shopping_cart.js"
"public/js/shopping_cart.js",
"public/js/wishlist.js"
],
"css/erpnext-web.css": [
"public/scss/website.scss",

View File

@ -0,0 +1,39 @@
frappe.provide("erpnext.e_commerce");
var wishlist = erpnext.e_commerce;
frappe.ready(function() {
$(".wishlist").toggleClass('hidden', true);
wishlist.set_wishlist_count();
});
$.extend(wishlist, {
set_wishlist_count: function() {
var wish_count = frappe.get_cookie("wish_count");
if(frappe.session.user==="Guest") {
wish_count = 0;
}
if(wish_count) {
$(".wishlist").toggleClass('hidden', false);
}
var $wishlist = $('.wishlist-icon');
var $badge = $wishlist.find("#wish-count");
if(parseInt(wish_count) === 0 || wish_count === undefined) {
$wishlist.css("display", "none");
}
else {
$wishlist.css("display", "inline");
}
if(wish_count) {
$badge.html(wish_count);
$wishlist.addClass('cart-animate');
setTimeout(() => {
$wishlist.removeClass('cart-animate');
}, 500);
} else {
$badge.remove();
}
}
});

View File

@ -349,20 +349,20 @@ body.product-page {
}
}
.cart-icon {
.cart-badge {
position: relative;
top: -10px;
left: -12px;
background: var(--red-600);
width: 16px;
align-items: center;
height: 16px;
font-size: 10px;
border-radius: 50%;
}
.shopping-badge {
position: relative;
top: -10px;
left: -12px;
background: var(--red-600);
width: 16px;
align-items: center;
height: 16px;
font-size: 10px;
border-radius: 50%;
}
.cart-animate {
animation: wiggle 0.5s linear;
}
@ -555,7 +555,28 @@ body.product-page {
margin-left: 12px;
}
.wish-icon {
.like-animate {
animation: expand cubic-bezier(0.04, 0.4, 0.5, 0.95) 1.6s forwards 1;
}
@keyframes expand {
30% {
transform: scale(1.6);
}
50% {
transform: scale(0.8);
}
70% {
transform: scale(1.3);
}
100% {
transform: scale(1);
}
}
@keyframes heart { 0%, 17.5% { font-size: 0; } }
.not-wished {
cursor: pointer;
stroke: #F47A7A !important;
@ -565,10 +586,8 @@ body.product-page {
}
.wished {
.wish-icon {
stroke: none;
fill: #F47A7A !important;
}
stroke: none;
fill: #F47A7A !important;
}
.list-row-checkbox {

View File

@ -127,12 +127,11 @@
<span class="indicator {{ item.in_stock }} card-indicator"></span>
{% endif %}
{% if not item.has_variants %}
<input class="level-item list-row-checkbox hidden-xs"
type="checkbox" data-name="{{ title }}" style="display: none !important;">
<div class="like-action"
data-name="{{ title }}" data-doctype="Item">
data-item-code="{{ item.item_code }}" data-price="{{ item.price }}">
<svg class="icon sm">
<use class="wish-icon" href="#icon-heart"></use>
{%- set icon_class = "wished" if item.wished else "not-wished"-%}
<use class="{{ icon_class }} wish-icon" href="#icon-heart"></use>
</svg>
</div>
{% endif %}

View File

@ -6,7 +6,15 @@
<svg class="icon icon-lg">
<use href="#icon-assets"></use>
</svg>
<span class="badge badge-primary cart-badge" id="cart-count"></span>
<span class="badge badge-primary shopping-badge" id="cart-count"></span>
</a>
</li>
<li class="wishlist wishlist-icon hidden">
<a class="nav-link" href="/cart">
<svg class="icon icon-lg">
<use href="#icon-heart"></use>
</svg>
<span class="badge badge-primary shopping-badge" id="wish-count"></span>
</a>
</li>
{% endblock %}

View File

@ -73,6 +73,11 @@ $(() => {
}
bind_card_actions() {
this.bind_add_to_cart_action();
this.bind_wishlist_action();
}
bind_add_to_cart_action() {
$('.page_content').on('click', '.btn-add-to-cart-list', (e) => {
const $btn = $(e.currentTarget);
$btn.prop('disabled', true);
@ -91,18 +96,77 @@ $(() => {
animate_add_to_cart(button) {
// Create 'added to cart' animation
let btn_id = "#" + button[0].id;
button.removeClass('not-added');
button.addClass('added-to-cart');
this.toggle_button_class(button, 'not-added', 'added-to-cart');
$(btn_id).text('Added to Cart');
// undo
setTimeout(() => {
button.removeClass('added-to-cart');
button.addClass('not-added');
this.toggle_button_class(button, 'added-to-cart', 'not-added');
$(btn_id).text('Add to Cart');
}, 2000);
}
bind_wishlist_action() {
$('.page_content').on('click', '.like-action', (e) => {
const $btn = $(e.currentTarget);
const $wish_icon = $btn.find('.wish-icon');
let me = this;
if ($wish_icon.hasClass('wished')) {
// un-wish item
$btn.removeClass("like-animate");
this.toggle_button_class($wish_icon, 'wished', 'not-wished');
frappe.call({
type: "POST",
method: "erpnext.e_commerce.doctype.wishlist.wishlist.remove_from_wishlist",
args: {
item_code: $btn.data('item-code')
},
callback: function (r) {
if (r.exc) {
me.toggle_button_class($wish_icon, 'wished', 'not-wished');
frappe.msgprint({
message: __("Sorry, something went wrong. Please refresh."),
indicator: "red",
title: __("Note")}
);
} else {
erpnext.e_commerce.set_wishlist_count();
}
}
});
} else {
$btn.addClass("like-animate");
this.toggle_button_class($wish_icon, 'not-wished', 'wished');
frappe.call({
type: "POST",
method: "erpnext.e_commerce.doctype.wishlist.wishlist.add_to_wishlist",
args: {
item_code: $btn.data('item-code'),
price: $btn.data('price')
},
callback: function (r) {
if (r.exc) {
me.toggle_button_class($wish_icon, 'wished', 'not-wished');
frappe.msgprint({
message: __("Sorry, something went wrong. Please refresh."),
indicator: "red",
title: __("Note")}
);
} else {
erpnext.e_commerce.set_wishlist_count();
}
}
});
}
});
}
toggle_button_class(button, remove, add) {
button.removeClass(remove);
button.addClass(add);
}
bind_search() {
$('input[type=search]').on('keydown', (e) => {
if (e.keyCode === 13) {