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:
parent
4f64d1c7f2
commit
96cc5068b2
@ -3,8 +3,52 @@
|
|||||||
# For license information, please see license.txt
|
# For license information, please see license.txt
|
||||||
|
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
# import frappe
|
import frappe
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
|
|
||||||
class Wishlist(Document):
|
class Wishlist(Document):
|
||||||
pass
|
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)))
|
@ -12,6 +12,8 @@
|
|||||||
"item_details_section",
|
"item_details_section",
|
||||||
"description",
|
"description",
|
||||||
"column_break_7",
|
"column_break_7",
|
||||||
|
"section_break_8",
|
||||||
|
"price",
|
||||||
"image",
|
"image",
|
||||||
"image_view",
|
"image_view",
|
||||||
"warehouse_section",
|
"warehouse_section",
|
||||||
@ -52,6 +54,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fetch_from": "item_code.description",
|
"fetch_from": "item_code.description",
|
||||||
|
"fetch_if_empty": 1,
|
||||||
"fieldname": "description",
|
"fieldname": "description",
|
||||||
"fieldtype": "Text Editor",
|
"fieldtype": "Text Editor",
|
||||||
"label": "Description"
|
"label": "Description"
|
||||||
@ -62,6 +65,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fetch_from": "item_code.image",
|
"fetch_from": "item_code.image",
|
||||||
|
"fetch_if_empty": 1,
|
||||||
"fieldname": "image",
|
"fieldname": "image",
|
||||||
"fieldtype": "Attach",
|
"fieldtype": "Attach",
|
||||||
"hidden": 1,
|
"hidden": 1,
|
||||||
@ -69,6 +73,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fetch_from": "item_code.image",
|
"fetch_from": "item_code.image",
|
||||||
|
"fetch_if_empty": 1,
|
||||||
"fieldname": "image_view",
|
"fieldname": "image_view",
|
||||||
"fieldtype": "Image",
|
"fieldtype": "Image",
|
||||||
"hidden": 1,
|
"hidden": 1,
|
||||||
@ -87,12 +92,21 @@
|
|||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"label": "Warehouse",
|
"label": "Warehouse",
|
||||||
"options": "Warehouse"
|
"options": "Warehouse"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "section_break_8",
|
||||||
|
"fieldtype": "Section Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "price",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"label": "Price"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2021-03-10 19:13:41.310816",
|
"modified": "2021-03-12 18:23:03.487891",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "E-commerce",
|
"module": "E-commerce",
|
||||||
"name": "Wishlist Items",
|
"name": "Wishlist Items",
|
||||||
|
@ -67,6 +67,7 @@ class ProductQuery:
|
|||||||
product_info = get_product_info_for_website(item.item_code, skip_quotation_creation=True).get('product_info')
|
product_info = get_product_info_for_website(item.item_code, skip_quotation_creation=True).get('product_info')
|
||||||
if product_info:
|
if product_info:
|
||||||
item.formatted_price = (product_info.get('price') or {}).get('formatted_price')
|
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"):
|
if self.settings.show_stock_availability and item.get("website_warehouse"):
|
||||||
stock_qty = frappe.utils.flt(
|
stock_qty = frappe.utils.flt(
|
||||||
@ -78,6 +79,11 @@ class ProductQuery:
|
|||||||
"actual_qty")
|
"actual_qty")
|
||||||
)
|
)
|
||||||
item.in_stock = "green" if stock_qty else "red"
|
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
|
return result
|
||||||
|
|
||||||
def query_items(self, conditions, or_conditions, substitutions, start=0):
|
def query_items(self, conditions, or_conditions, substitutions, start=0):
|
||||||
|
@ -138,7 +138,7 @@ def update_cart(item_code, qty, additional_notes=None, with_items=False):
|
|||||||
"additional_notes": additional_notes
|
"additional_notes": additional_notes
|
||||||
})
|
})
|
||||||
else:
|
else:
|
||||||
quotation_items[0].qty = qty + 1
|
quotation_items[0].qty = qty
|
||||||
quotation_items[0].additional_notes = additional_notes
|
quotation_items[0].additional_notes = additional_notes
|
||||||
|
|
||||||
apply_cart_settings(quotation=quotation)
|
apply_cart_settings(quotation=quotation)
|
||||||
|
@ -11,7 +11,8 @@
|
|||||||
],
|
],
|
||||||
"js/erpnext-web.min.js": [
|
"js/erpnext-web.min.js": [
|
||||||
"public/js/website_utils.js",
|
"public/js/website_utils.js",
|
||||||
"public/js/shopping_cart.js"
|
"public/js/shopping_cart.js",
|
||||||
|
"public/js/wishlist.js"
|
||||||
],
|
],
|
||||||
"css/erpnext-web.css": [
|
"css/erpnext-web.css": [
|
||||||
"public/scss/website.scss",
|
"public/scss/website.scss",
|
||||||
|
39
erpnext/public/js/wishlist.js
Normal file
39
erpnext/public/js/wishlist.js
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
@ -349,20 +349,20 @@ body.product-page {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.cart-icon {
|
|
||||||
.cart-badge {
|
.shopping-badge {
|
||||||
position: relative;
|
position: relative;
|
||||||
top: -10px;
|
top: -10px;
|
||||||
left: -12px;
|
left: -12px;
|
||||||
background: var(--red-600);
|
background: var(--red-600);
|
||||||
width: 16px;
|
width: 16px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
height: 16px;
|
height: 16px;
|
||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.cart-animate {
|
.cart-animate {
|
||||||
animation: wiggle 0.5s linear;
|
animation: wiggle 0.5s linear;
|
||||||
}
|
}
|
||||||
@ -555,7 +555,28 @@ body.product-page {
|
|||||||
margin-left: 12px;
|
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;
|
cursor: pointer;
|
||||||
stroke: #F47A7A !important;
|
stroke: #F47A7A !important;
|
||||||
|
|
||||||
@ -565,10 +586,8 @@ body.product-page {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.wished {
|
.wished {
|
||||||
.wish-icon {
|
stroke: none;
|
||||||
stroke: none;
|
fill: #F47A7A !important;
|
||||||
fill: #F47A7A !important;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.list-row-checkbox {
|
.list-row-checkbox {
|
||||||
|
@ -127,12 +127,11 @@
|
|||||||
<span class="indicator {{ item.in_stock }} card-indicator"></span>
|
<span class="indicator {{ item.in_stock }} card-indicator"></span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if not item.has_variants %}
|
{% 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"
|
<div class="like-action"
|
||||||
data-name="{{ title }}" data-doctype="Item">
|
data-item-code="{{ item.item_code }}" data-price="{{ item.price }}">
|
||||||
<svg class="icon sm">
|
<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>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -6,7 +6,15 @@
|
|||||||
<svg class="icon icon-lg">
|
<svg class="icon icon-lg">
|
||||||
<use href="#icon-assets"></use>
|
<use href="#icon-assets"></use>
|
||||||
</svg>
|
</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>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -73,6 +73,11 @@ $(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bind_card_actions() {
|
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) => {
|
$('.page_content').on('click', '.btn-add-to-cart-list', (e) => {
|
||||||
const $btn = $(e.currentTarget);
|
const $btn = $(e.currentTarget);
|
||||||
$btn.prop('disabled', true);
|
$btn.prop('disabled', true);
|
||||||
@ -91,18 +96,77 @@ $(() => {
|
|||||||
animate_add_to_cart(button) {
|
animate_add_to_cart(button) {
|
||||||
// Create 'added to cart' animation
|
// Create 'added to cart' animation
|
||||||
let btn_id = "#" + button[0].id;
|
let btn_id = "#" + button[0].id;
|
||||||
button.removeClass('not-added');
|
this.toggle_button_class(button, 'not-added', 'added-to-cart');
|
||||||
button.addClass('added-to-cart');
|
|
||||||
$(btn_id).text('Added to Cart');
|
$(btn_id).text('Added to Cart');
|
||||||
|
|
||||||
// undo
|
// undo
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
button.removeClass('added-to-cart');
|
this.toggle_button_class(button, 'added-to-cart', 'not-added');
|
||||||
button.addClass('not-added');
|
|
||||||
$(btn_id).text('Add to Cart');
|
$(btn_id).text('Add to Cart');
|
||||||
}, 2000);
|
}, 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() {
|
bind_search() {
|
||||||
$('input[type=search]').on('keydown', (e) => {
|
$('input[type=search]').on('keydown', (e) => {
|
||||||
if (e.keyCode === 13) {
|
if (e.keyCode === 13) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user