mirror of
https://github.com/LaCasemate/fab-manager.git
synced 2025-02-26 20:54:21 +01:00
(wip) cart items components
This commit is contained in:
parent
c24673fefa
commit
05a6f517cd
@ -0,0 +1,79 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import noImage from '../../../../images/no_image.png';
|
||||||
|
import FormatLib from '../../lib/format';
|
||||||
|
import OrderLib from '../../lib/order';
|
||||||
|
import { FabButton } from '../base/fab-button';
|
||||||
|
import Switch from 'react-switch';
|
||||||
|
import type { OrderItem } from '../../models/order';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { ReactNode } from 'react';
|
||||||
|
|
||||||
|
interface AbstractItemProps {
|
||||||
|
item: OrderItem,
|
||||||
|
hasError: boolean,
|
||||||
|
className?: string,
|
||||||
|
removeItemFromCart: (item: OrderItem) => void,
|
||||||
|
toggleItemOffer: (item: OrderItem, checked: boolean) => void,
|
||||||
|
privilegedOperator: boolean,
|
||||||
|
actions?: ReactNode
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This component shares the common code for items in the cart (product, cart-item, etc)
|
||||||
|
*/
|
||||||
|
export const AbstractItem: React.FC<AbstractItemProps> = ({ item, hasError, className, removeItemFromCart, toggleItemOffer, privilegedOperator, actions, children }) => {
|
||||||
|
const { t } = useTranslation('public');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the callback triggered when then user remove the given item from the cart
|
||||||
|
*/
|
||||||
|
const handleRemoveItem = (item: OrderItem) => {
|
||||||
|
return (e: React.BaseSyntheticEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
|
||||||
|
removeItemFromCart(item);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the callback triggered when the privileged user enable/disable the offered attribute for the given item
|
||||||
|
*/
|
||||||
|
const handleToggleOffer = (item: OrderItem) => {
|
||||||
|
return (checked: boolean) => toggleItemOffer(item, checked);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<article className={`item ${className || ''} ${hasError ? 'error' : ''}`}>
|
||||||
|
<div className='picture'>
|
||||||
|
<img alt='' src={item.orderable_main_image_url || noImage} />
|
||||||
|
</div>
|
||||||
|
{children}
|
||||||
|
<div className="actions">
|
||||||
|
{actions}
|
||||||
|
<div className='total'>
|
||||||
|
<span>{t('app.public.abstract_item.total')}</span>
|
||||||
|
<p>{FormatLib.price(OrderLib.itemAmount(item))}</p>
|
||||||
|
</div>
|
||||||
|
<FabButton className="main-action-btn" onClick={handleRemoveItem(item)}>
|
||||||
|
<i className="fa fa-trash" />
|
||||||
|
</FabButton>
|
||||||
|
</div>
|
||||||
|
{privilegedOperator &&
|
||||||
|
<div className='offer'>
|
||||||
|
<label>
|
||||||
|
<span>{t('app.public.abstract_item.offer_product')}</span>
|
||||||
|
<Switch
|
||||||
|
checked={item.is_offered || false}
|
||||||
|
onChange={handleToggleOffer(item)}
|
||||||
|
width={40}
|
||||||
|
height={19}
|
||||||
|
uncheckedIcon={false}
|
||||||
|
checkedIcon={false}
|
||||||
|
handleDiameter={15} />
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</article>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,141 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import FormatLib from '../../lib/format';
|
||||||
|
import { CaretDown, CaretUp } from 'phosphor-react';
|
||||||
|
import type { OrderProduct, OrderErrors, Order } from '../../models/order';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import _ from 'lodash';
|
||||||
|
import CartAPI from '../../api/cart';
|
||||||
|
import { AbstractItem } from './abstract-item';
|
||||||
|
import { ReactNode } from 'react';
|
||||||
|
|
||||||
|
interface CartOrderProductProps {
|
||||||
|
item: OrderProduct,
|
||||||
|
cartErrors: OrderErrors,
|
||||||
|
className?: string,
|
||||||
|
cart: Order,
|
||||||
|
setCart: (cart: Order) => void,
|
||||||
|
onError: (message: string) => void,
|
||||||
|
removeProductFromCart: (item: OrderProduct) => void,
|
||||||
|
toggleProductOffer: (item: OrderProduct, checked: boolean) => void,
|
||||||
|
privilegedOperator: boolean,
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This component shows a product in the cart
|
||||||
|
*/
|
||||||
|
export const CartOrderProduct: React.FC<CartOrderProductProps> = ({ item, cartErrors, className, cart, setCart, onError, removeProductFromCart, toggleProductOffer, privilegedOperator }) => {
|
||||||
|
const { t } = useTranslation('public');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the given item's errors
|
||||||
|
*/
|
||||||
|
const getItemErrors = (item: OrderProduct) => {
|
||||||
|
if (!cartErrors) return [];
|
||||||
|
const errors = _.find(cartErrors.details, (e) => e.item_id === item.id);
|
||||||
|
return errors?.errors || [{ error: 'not_found' }];
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show an human-readable styled error for the given item's error
|
||||||
|
*/
|
||||||
|
const itemError = (item: OrderProduct, error) => {
|
||||||
|
if (error.error === 'is_active' || error.error === 'not_found') {
|
||||||
|
return <div className='error'><p>{t('app.public.cart_order_product.errors.product_not_found')}</p></div>;
|
||||||
|
}
|
||||||
|
if (error.error === 'stock' && error.value === 0) {
|
||||||
|
return <div className='error'><p>{t('app.public.cart_order_product.errors.out_of_stock')}</p></div>;
|
||||||
|
}
|
||||||
|
if (error.error === 'stock' && error.value > 0) {
|
||||||
|
return <div className='error'><p>{t('app.public.cart_order_product.errors.stock_limit_QUANTITY', { QUANTITY: error.value })}</p></div>;
|
||||||
|
}
|
||||||
|
if (error.error === 'quantity_min') {
|
||||||
|
return <div className='error'><p>{t('app.public.cart_order_product.errors.quantity_min_QUANTITY', { QUANTITY: error.value })}</p></div>;
|
||||||
|
}
|
||||||
|
if (error.error === 'amount') {
|
||||||
|
return <div className='error'>
|
||||||
|
<p>{t('app.public.cart_order_product.errors.price_changed_PRICE', { PRICE: `${FormatLib.price(error.value)} / ${t('app.public.cart_order_product.unit')}` })}</p>
|
||||||
|
<span className='refresh-btn' onClick={refreshItem(item)}>{t('app.public.cart_order_product.update_item')}</span>
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Refresh product amount
|
||||||
|
*/
|
||||||
|
const refreshItem = (item: OrderProduct) => {
|
||||||
|
return (e: React.BaseSyntheticEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
CartAPI.refreshItem(cart, item.orderable_id, item.orderable_type).then(data => {
|
||||||
|
setCart(data);
|
||||||
|
}).catch(onError);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Change product quantity
|
||||||
|
*/
|
||||||
|
const changeProductQuantity = (e: React.BaseSyntheticEvent, item: OrderProduct) => {
|
||||||
|
CartAPI.setQuantity(cart, item.orderable_id, item.orderable_type, e.target.value)
|
||||||
|
.then(data => {
|
||||||
|
setCart(data);
|
||||||
|
})
|
||||||
|
.catch(() => onError(t('app.public.cart_order_product.stock_limit')));
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Increment/decrement product quantity
|
||||||
|
*/
|
||||||
|
const increaseOrDecreaseProductQuantity = (item: OrderProduct, direction: 'up' | 'down') => {
|
||||||
|
CartAPI.setQuantity(cart, item.orderable_id, item.orderable_type, direction === 'up' ? item.quantity + 1 : item.quantity - 1)
|
||||||
|
.then(data => {
|
||||||
|
setCart(data);
|
||||||
|
})
|
||||||
|
.catch(() => onError(t('app.public.cart_order_product.stock_limit')));
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the components in the "actions" section of the item
|
||||||
|
*/
|
||||||
|
const buildActions = (): ReactNode => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className='price'>
|
||||||
|
<p>{FormatLib.price(item.amount)}</p>
|
||||||
|
<span>/ {t('app.public.cart_order_product.unit')}</span>
|
||||||
|
</div>
|
||||||
|
<div className='quantity'>
|
||||||
|
<input type='number'
|
||||||
|
onChange={e => changeProductQuantity(e, item)}
|
||||||
|
min={item.quantity_min}
|
||||||
|
max={item.orderable_external_stock}
|
||||||
|
value={item.quantity}
|
||||||
|
/>
|
||||||
|
<button onClick={() => increaseOrDecreaseProductQuantity(item, 'up')}><CaretUp size={12} weight="fill" /></button>
|
||||||
|
<button onClick={() => increaseOrDecreaseProductQuantity(item, 'down')}><CaretDown size={12} weight="fill" /></button>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AbstractItem className={`cart-order-product ${className || ''}`}
|
||||||
|
hasError={getItemErrors(item).length > 0}
|
||||||
|
item={item}
|
||||||
|
removeItemFromCart={removeProductFromCart}
|
||||||
|
privilegedOperator={privilegedOperator}
|
||||||
|
toggleItemOffer={toggleProductOffer}
|
||||||
|
actions={buildActions()}>
|
||||||
|
<div className="ref">
|
||||||
|
<span>{t('app.public.cart_order_product.reference_short')} {item.orderable_ref || ''}</span>
|
||||||
|
<p><a className="text-black" href={`/#!/store/p/${item.orderable_slug}`}>{item.orderable_name}</a></p>
|
||||||
|
{item.quantity_min > 1 &&
|
||||||
|
<span className='min'>{t('app.public.cart_order_product.minimum_purchase')}{item.quantity_min}</span>
|
||||||
|
}
|
||||||
|
{getItemErrors(item).map(e => {
|
||||||
|
return itemError(item, e);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</AbstractItem>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,48 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import type { OrderErrors, Order } from '../../models/order';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import _ from 'lodash';
|
||||||
|
import { AbstractItem } from './abstract-item';
|
||||||
|
import { OrderCartItem } from '../../models/order';
|
||||||
|
|
||||||
|
interface CartOrderReservationProps {
|
||||||
|
item: OrderCartItem,
|
||||||
|
cartErrors: OrderErrors,
|
||||||
|
className?: string,
|
||||||
|
cart: Order,
|
||||||
|
setCart: (cart: Order) => void,
|
||||||
|
onError: (message: string) => void,
|
||||||
|
removeProductFromCart: (item: OrderCartItem) => void,
|
||||||
|
toggleProductOffer: (item: OrderCartItem, checked: boolean) => void,
|
||||||
|
privilegedOperator: boolean,
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This component shows a product in the cart
|
||||||
|
*/
|
||||||
|
export const CartOrderReservation: React.FC<CartOrderReservationProps> = ({ item, cartErrors, className, cart, setCart, onError, removeProductFromCart, toggleProductOffer, privilegedOperator }) => {
|
||||||
|
const { t } = useTranslation('public');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the given item's errors
|
||||||
|
*/
|
||||||
|
const getItemErrors = (item: OrderCartItem) => {
|
||||||
|
if (!cartErrors) return [];
|
||||||
|
const errors = _.find(cartErrors.details, (e) => e.item_id === item.id);
|
||||||
|
return errors?.errors || [{ error: 'not_found' }];
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AbstractItem className={`cart-order-reservation ${className || ''}`}
|
||||||
|
hasError={getItemErrors(item).length > 0}
|
||||||
|
item={item}
|
||||||
|
removeItemFromCart={removeProductFromCart}
|
||||||
|
privilegedOperator={privilegedOperator}
|
||||||
|
toggleItemOffer={toggleProductOffer}>
|
||||||
|
<div className="ref">
|
||||||
|
<p>Réservation {item.orderable_name}</p>
|
||||||
|
{getItemErrors(item)}
|
||||||
|
</div>
|
||||||
|
</AbstractItem>
|
||||||
|
);
|
||||||
|
};
|
@ -3,24 +3,23 @@ import * as React from 'react';
|
|||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { react2angular } from 'react2angular';
|
import { react2angular } from 'react2angular';
|
||||||
import { Loader } from '../base/loader';
|
import { Loader } from '../base/loader';
|
||||||
import { IApplication } from '../../models/application';
|
import type { IApplication } from '../../models/application';
|
||||||
import { FabButton } from '../base/fab-button';
|
import { FabButton } from '../base/fab-button';
|
||||||
import useCart from '../../hooks/use-cart';
|
import useCart from '../../hooks/use-cart';
|
||||||
import FormatLib from '../../lib/format';
|
import FormatLib from '../../lib/format';
|
||||||
import CartAPI from '../../api/cart';
|
import CartAPI from '../../api/cart';
|
||||||
import { User } from '../../models/user';
|
import type { User } from '../../models/user';
|
||||||
import { PaymentModal } from '../payment/stripe/payment-modal';
|
import { PaymentModal } from '../payment/stripe/payment-modal';
|
||||||
import { PaymentMethod } from '../../models/payment';
|
import { PaymentMethod } from '../../models/payment';
|
||||||
import { Order, OrderErrors } from '../../models/order';
|
import type { Order, OrderCartItem, OrderErrors, OrderItem, OrderProduct } from '../../models/order';
|
||||||
import { MemberSelect } from '../user/member-select';
|
import { MemberSelect } from '../user/member-select';
|
||||||
import { CouponInput } from '../coupon/coupon-input';
|
import { CouponInput } from '../coupon/coupon-input';
|
||||||
import { Coupon } from '../../models/coupon';
|
import type { Coupon } from '../../models/coupon';
|
||||||
import noImage from '../../../../images/no_image.png';
|
|
||||||
import Switch from 'react-switch';
|
|
||||||
import OrderLib from '../../lib/order';
|
import OrderLib from '../../lib/order';
|
||||||
import { CaretDown, CaretUp } from 'phosphor-react';
|
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import OrderAPI from '../../api/order';
|
import OrderAPI from '../../api/order';
|
||||||
|
import { CartOrderProduct } from './cart-order-product';
|
||||||
|
import { CartOrderReservation } from './cart-order-reservation';
|
||||||
|
|
||||||
declare const Application: IApplication;
|
declare const Application: IApplication;
|
||||||
|
|
||||||
@ -58,9 +57,6 @@ const StoreCart: React.FC<StoreCartProps> = ({ onSuccess, onError, currentUser,
|
|||||||
* Remove the product from cart
|
* Remove the product from cart
|
||||||
*/
|
*/
|
||||||
const removeProductFromCart = (item) => {
|
const removeProductFromCart = (item) => {
|
||||||
return (e: React.BaseSyntheticEvent) => {
|
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
const errors = getItemErrors(item);
|
const errors = getItemErrors(item);
|
||||||
if (errors.length === 1 && errors[0].error === 'not_found') {
|
if (errors.length === 1 && errors[0].error === 'not_found') {
|
||||||
reloadCart().catch(onError);
|
reloadCart().catch(onError);
|
||||||
@ -70,42 +66,6 @@ const StoreCart: React.FC<StoreCartProps> = ({ onSuccess, onError, currentUser,
|
|||||||
}).catch(onError);
|
}).catch(onError);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Change product quantity
|
|
||||||
*/
|
|
||||||
const changeProductQuantity = (e: React.BaseSyntheticEvent, item) => {
|
|
||||||
CartAPI.setQuantity(cart, item.orderable_id, item.orderable_type, e.target.value)
|
|
||||||
.then(data => {
|
|
||||||
setCart(data);
|
|
||||||
})
|
|
||||||
.catch(() => onError(t('app.public.store_cart.stock_limit')));
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Increment/decrement product quantity
|
|
||||||
*/
|
|
||||||
const increaseOrDecreaseProductQuantity = (item, direction: 'up' | 'down') => {
|
|
||||||
CartAPI.setQuantity(cart, item.orderable_id, item.orderable_type, direction === 'up' ? item.quantity + 1 : item.quantity - 1)
|
|
||||||
.then(data => {
|
|
||||||
setCart(data);
|
|
||||||
})
|
|
||||||
.catch(() => onError(t('app.public.store_cart.stock_limit')));
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Refresh product amount
|
|
||||||
*/
|
|
||||||
const refreshItem = (item) => {
|
|
||||||
return (e: React.BaseSyntheticEvent) => {
|
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
CartAPI.refreshItem(cart, item.orderable_id, item.orderable_type).then(data => {
|
|
||||||
setCart(data);
|
|
||||||
}).catch(onError);
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check the current cart's items (available, price, stock, quantity_min)
|
* Check the current cart's items (available, price, stock, quantity_min)
|
||||||
@ -202,8 +162,7 @@ const StoreCart: React.FC<StoreCartProps> = ({ onSuccess, onError, currentUser,
|
|||||||
/**
|
/**
|
||||||
* Toggle product offer
|
* Toggle product offer
|
||||||
*/
|
*/
|
||||||
const toggleProductOffer = (item) => {
|
const toggleProductOffer = (item: OrderItem, checked: boolean) => {
|
||||||
return (checked: boolean) => {
|
|
||||||
CartAPI.setOffer(cart, item.orderable_id, item.orderable_type, checked).then(data => {
|
CartAPI.setOffer(cart, item.orderable_id, item.orderable_type, checked).then(data => {
|
||||||
setCart(data);
|
setCart(data);
|
||||||
}).catch(e => {
|
}).catch(e => {
|
||||||
@ -214,7 +173,6 @@ const StoreCart: React.FC<StoreCartProps> = ({ onSuccess, onError, currentUser,
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Apply coupon to current cart
|
* Apply coupon to current cart
|
||||||
@ -225,90 +183,39 @@ const StoreCart: React.FC<StoreCartProps> = ({ onSuccess, onError, currentUser,
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Show item error
|
|
||||||
*/
|
|
||||||
const itemError = (item, error) => {
|
|
||||||
if (error.error === 'is_active' || error.error === 'not_found') {
|
|
||||||
return <div className='error'><p>{t('app.public.store_cart.errors.product_not_found')}</p></div>;
|
|
||||||
}
|
|
||||||
if (error.error === 'stock' && error.value === 0) {
|
|
||||||
return <div className='error'><p>{t('app.public.store_cart.errors.out_of_stock')}</p></div>;
|
|
||||||
}
|
|
||||||
if (error.error === 'stock' && error.value > 0) {
|
|
||||||
return <div className='error'><p>{t('app.public.store_cart.errors.stock_limit_QUANTITY', { QUANTITY: error.value })}</p></div>;
|
|
||||||
}
|
|
||||||
if (error.error === 'quantity_min') {
|
|
||||||
return <div className='error'><p>{t('app.public.store_cart.errors.quantity_min_QUANTITY', { QUANTITY: error.value })}</p></div>;
|
|
||||||
}
|
|
||||||
if (error.error === 'amount') {
|
|
||||||
return <div className='error'>
|
|
||||||
<p>{t('app.public.store_cart.errors.price_changed_PRICE', { PRICE: `${FormatLib.price(error.value)} / ${t('app.public.store_cart.unit')}` })}</p>
|
|
||||||
<span className='refresh-btn' onClick={refreshItem(item)}>{t('app.public.store_cart.update_item')}</span>
|
|
||||||
</div>;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='store-cart'>
|
<div className='store-cart'>
|
||||||
<div className="store-cart-list">
|
<div className="store-cart-list">
|
||||||
{cart && cartIsEmpty() && <p>{t('app.public.store_cart.cart_is_empty')}</p>}
|
{cart && cartIsEmpty() && <p>{t('app.public.store_cart.cart_is_empty')}</p>}
|
||||||
{cart && cart.order_items_attributes.map(item => (
|
{cart && cart.order_items_attributes.map(item => {
|
||||||
<article key={item.id} className={`store-cart-list-item ${getItemErrors(item).length > 0 ? 'error' : ''}`}>
|
if (item.orderable_type === 'Product') {
|
||||||
<div className='picture'>
|
return (
|
||||||
<img alt='' src={item.orderable_main_image_url || noImage} />
|
<CartOrderProduct item={item as OrderProduct}
|
||||||
</div>
|
key={item.id}
|
||||||
<div className="ref">
|
className="store-cart-list-item"
|
||||||
<span>{t('app.public.store_cart.reference_short')} {item.orderable_ref || ''}</span>
|
cartErrors={cartErrors}
|
||||||
<p><a className="text-black" href={`/#!/store/p/${item.orderable_slug}`}>{item.orderable_name}</a></p>
|
cart={cart}
|
||||||
{item.quantity_min > 1 &&
|
setCart={setCart}
|
||||||
<span className='min'>{t('app.public.store_cart.minimum_purchase')}{item.quantity_min}</span>
|
onError={onError}
|
||||||
|
removeProductFromCart={removeProductFromCart}
|
||||||
|
toggleProductOffer={toggleProductOffer}
|
||||||
|
privilegedOperator={isPrivileged()} />
|
||||||
|
);
|
||||||
}
|
}
|
||||||
{getItemErrors(item).map(e => {
|
return (
|
||||||
return itemError(item, e);
|
<CartOrderReservation item={item as OrderCartItem}
|
||||||
|
key={item.id}
|
||||||
|
className="store-cart-list-item"
|
||||||
|
cartErrors={cartErrors}
|
||||||
|
cart={cart}
|
||||||
|
setCart={setCart}
|
||||||
|
onError={onError}
|
||||||
|
removeProductFromCart={removeProductFromCart}
|
||||||
|
toggleProductOffer={toggleProductOffer}
|
||||||
|
privilegedOperator={isPrivileged()} />
|
||||||
|
);
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
<div className="actions">
|
|
||||||
<div className='price'>
|
|
||||||
<p>{FormatLib.price(item.amount)}</p>
|
|
||||||
<span>/ {t('app.public.store_cart.unit')}</span>
|
|
||||||
</div>
|
|
||||||
<div className='quantity'>
|
|
||||||
<input type='number'
|
|
||||||
onChange={e => changeProductQuantity(e, item)}
|
|
||||||
min={item.quantity_min}
|
|
||||||
max={item.orderable_external_stock}
|
|
||||||
value={item.quantity}
|
|
||||||
/>
|
|
||||||
<button onClick={() => increaseOrDecreaseProductQuantity(item, 'up')}><CaretUp size={12} weight="fill" /></button>
|
|
||||||
<button onClick={() => increaseOrDecreaseProductQuantity(item, 'down')}><CaretDown size={12} weight="fill" /></button>
|
|
||||||
</div>
|
|
||||||
<div className='total'>
|
|
||||||
<span>{t('app.public.store_cart.total')}</span>
|
|
||||||
<p>{FormatLib.price(OrderLib.itemAmount(item))}</p>
|
|
||||||
</div>
|
|
||||||
<FabButton className="main-action-btn" onClick={removeProductFromCart(item)}>
|
|
||||||
<i className="fa fa-trash" />
|
|
||||||
</FabButton>
|
|
||||||
</div>
|
|
||||||
{isPrivileged() &&
|
|
||||||
<div className='offer'>
|
|
||||||
<label>
|
|
||||||
<span>{t('app.public.store_cart.offer_product')}</span>
|
|
||||||
<Switch
|
|
||||||
checked={item.is_offered || false}
|
|
||||||
onChange={toggleProductOffer(item)}
|
|
||||||
width={40}
|
|
||||||
height={19}
|
|
||||||
uncheckedIcon={false}
|
|
||||||
checkedIcon={false}
|
|
||||||
handleDiameter={15} />
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</article>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="group">
|
<div className="group">
|
||||||
{cart && !cartIsEmpty() &&
|
{cart && !cartIsEmpty() &&
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
import { computePriceWithCoupon } from './coupon';
|
import { computePriceWithCoupon } from './coupon';
|
||||||
import { Order } from '../models/order';
|
import { Order, OrderItem } from '../models/order';
|
||||||
|
|
||||||
export default class OrderLib {
|
export default class OrderLib {
|
||||||
/**
|
/**
|
||||||
* Get the order item total
|
* Get the order item total
|
||||||
*/
|
*/
|
||||||
static itemAmount = (item): number => {
|
static itemAmount = (item: OrderItem): number => {
|
||||||
return item.quantity * Math.round(item.amount * 100) / 100;
|
return item.quantity * Math.round(item.amount * 100) / 100;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -8,6 +8,29 @@ import { CartItemType } from './cart_item';
|
|||||||
|
|
||||||
export type OrderableType = 'Product' | CartItemType;
|
export type OrderableType = 'Product' | CartItemType;
|
||||||
|
|
||||||
|
export interface OrderItem {
|
||||||
|
id: number,
|
||||||
|
orderable_type: OrderableType,
|
||||||
|
orderable_id: number,
|
||||||
|
orderable_name: string,
|
||||||
|
orderable_main_image_url?: string;
|
||||||
|
quantity: number,
|
||||||
|
amount: number,
|
||||||
|
is_offered: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface OrderProduct extends OrderItem {
|
||||||
|
orderable_type: 'Product',
|
||||||
|
orderable_slug: string,
|
||||||
|
orderable_ref?: string,
|
||||||
|
orderable_external_stock: number,
|
||||||
|
quantity_min: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface OrderCartItem extends OrderItem {
|
||||||
|
orderable_type: CartItemType
|
||||||
|
}
|
||||||
|
|
||||||
export interface Order {
|
export interface Order {
|
||||||
id: number,
|
id: number,
|
||||||
token: string,
|
token: string,
|
||||||
@ -29,20 +52,7 @@ export interface Order {
|
|||||||
payment_date?: TDateISO,
|
payment_date?: TDateISO,
|
||||||
wallet_amount?: number,
|
wallet_amount?: number,
|
||||||
paid_total?: number,
|
paid_total?: number,
|
||||||
order_items_attributes: Array<{
|
order_items_attributes: Array<OrderItem>,
|
||||||
id: number,
|
|
||||||
orderable_type: OrderableType,
|
|
||||||
orderable_id: number,
|
|
||||||
orderable_name: string,
|
|
||||||
orderable_slug: string,
|
|
||||||
orderable_ref?: string,
|
|
||||||
orderable_main_image_url?: string,
|
|
||||||
orderable_external_stock: number,
|
|
||||||
quantity: number,
|
|
||||||
quantity_min: number,
|
|
||||||
amount: number,
|
|
||||||
is_offered: boolean
|
|
||||||
}>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface OrderPayment {
|
export interface OrderPayment {
|
||||||
|
@ -434,12 +434,6 @@ en:
|
|||||||
checkout: "Checkout"
|
checkout: "Checkout"
|
||||||
cart_is_empty: "Your cart is empty"
|
cart_is_empty: "Your cart is empty"
|
||||||
pickup: "Pickup your products"
|
pickup: "Pickup your products"
|
||||||
reference_short: "ref:"
|
|
||||||
minimum_purchase: "Minimum purchase: "
|
|
||||||
stock_limit: "You have reached the current stock limit"
|
|
||||||
unit: "Unit"
|
|
||||||
total: "Total"
|
|
||||||
offer_product: "Offer the product"
|
|
||||||
checkout_header: "Total amount for your cart"
|
checkout_header: "Total amount for your cart"
|
||||||
checkout_products_COUNT: "Your cart contains {COUNT} {COUNT, plural, =1{product} other{products}}"
|
checkout_products_COUNT: "Your cart contains {COUNT} {COUNT, plural, =1{product} other{products}}"
|
||||||
checkout_products_total: "Products total"
|
checkout_products_total: "Products total"
|
||||||
@ -449,6 +443,14 @@ en:
|
|||||||
checkout_error: "An unexpected error occurred. Please contact the administrator."
|
checkout_error: "An unexpected error occurred. Please contact the administrator."
|
||||||
checkout_success: "Purchase confirmed. Thanks!"
|
checkout_success: "Purchase confirmed. Thanks!"
|
||||||
select_user: "Please select a user before continuing."
|
select_user: "Please select a user before continuing."
|
||||||
|
abstract_item:
|
||||||
|
offer_product: "Offer the product"
|
||||||
|
total: "Total"
|
||||||
|
cart_order_product:
|
||||||
|
reference_short: "ref:"
|
||||||
|
minimum_purchase: "Minimum purchase: "
|
||||||
|
stock_limit: "You have reached the current stock limit"
|
||||||
|
unit: "Unit"
|
||||||
update_item: "Update"
|
update_item: "Update"
|
||||||
errors:
|
errors:
|
||||||
product_not_found: "This product is no longer available, please remove it from your cart."
|
product_not_found: "This product is no longer available, please remove it from your cart."
|
||||||
|
Loading…
x
Reference in New Issue
Block a user