Untitled
unknown
plain_text
a year ago
26 kB
8
Indexable
import { atomIsDetectFooterInViewport } from '@src/atoms/routes/buy/single-book/isDetectFooterInViewport';
import IncrementAndDecrementBuyCartItemQuantity from '@src/components/routes/buy/global/incrementAndDecrementBuyCartItemQunatity';
import TOASTMSG from '@src/constants/global/toastMessages';
import convertNumber from '@src/functions/global/convertNumber';
import { generateUniqueId } from '@src/functions/global/generate-unique-id';
import { rialToToman } from '@src/functions/global/rialToToman';
import toast from '@src/functions/global/toast';
import useLocalStorage from '@src/hooks/global/localStorage';
import { APIaddToFavirateOrChangeInformmeStatus } from '@src/services/routes/global/addToFavirateOrChangeInformmeStatus';
import { APIfetchFavirateList } from '@src/services/routes/global/fetchFavirateList';
import { BuyCartItemType } from '@src/types/global/buyCartItem.type';
import { UserAuthType } from '@src/types/global/userAuth.type';
import { BookType } from '@src/types/routes/global/book.type';
import { FavoriteBookType } from '@src/types/routes/global/favirateBook.type';
import IconChevron from '@src/utils/icons/global/chevron';
import IconCircleDollar from '@src/utils/icons/routes/buy/global/circleDollar';
import IconGuardTick from '@src/utils/icons/routes/buy/single-book/guardTick';
import IconShoppingCartWithEye from '@src/utils/icons/routes/buy/single-book/shoppingCartWithEye';
import IconStarSpeed from '@src/utils/icons/routes/buy/single-book/starSpeed';
import IconCheckBoxTick from '@src/utils/icons/routes/global/checkBoxTick';
import { useAtom } from 'jotai';
import Link from 'next/link';
import { useRouter } from 'next/router';
import { FC, useEffect, useState } from 'react';
import { ThreeDots } from 'react-loader-spinner';
import useSWR, { mutate } from 'swr';
interface IProps {
singleBookData: BookType;
}
const BuySingleBook: FC<IProps> = ({ ...props }): JSX.Element => {
// local-storage state status : undefined (loading before set local-storage data), [] (initial value and not found key in local storage), buyCartItemsData
const [localStorageBuyCartItems, setLocalStorageBuyCartItems] =
useLocalStorage<undefined | [] | BuyCartItemType[]>('buyCartItems', []);
// get phone number and token from local storage for api
const [localStorageUserAuthData] = useLocalStorage<
undefined | null | UserAuthType
>('userAuth', null);
// for fetch favirate books list from api
async function fetcherFetchFavirateList() {
try {
const { data } = await APIfetchFavirateList({
phoneNumber: localStorageUserAuthData!.loginid,
});
return data;
} catch (error: any) {
if (!error.response) {
toast(TOASTMSG.routes.global.pleaseCheckNetworkConnection);
} else {
toast(TOASTMSG.routes.global.sorryUnexpectedError);
}
}
return null;
}
const { data: favirateBooksData } = useSWR<FavoriteBookType[] | null>(
localStorageUserAuthData ? `favirateList` : null,
fetcherFetchFavirateList,
);
// for hide mobile-bottom-buyBook in /buy/single-book/:slug after detect footer in view-port
const [atomStateIsDetectFooterInViewport] = useAtom(
atomIsDetectFooterInViewport,
);
// for show loader after disabled informme button
const [disabledInformmeBtn, setDisabledInformmeBtn] =
useState<boolean>(false);
// router for detect current route and navigate user to signin page
const router = useRouter();
// check is book in favirate list
const [findedBookInFavirateList, setFindedBookInFavirateList] =
useState<null | FavoriteBookType>(null);
useEffect((): void => {
if (localStorageUserAuthData && favirateBooksData) {
// check is book in favirate list
setFindedBookInFavirateList(
favirateBooksData.find(
(item) => item.isbn === String(props.singleBookData.isbn),
) ?? null,
);
}
}, [localStorageUserAuthData, favirateBooksData]);
// book quality option for select option
const bookQualityOption: {
bookId: number;
label: 'در حد نو' | 'خوب' | 'قابل قبول' | 'نو';
value: 'high' | 'medium' | 'low' | 'super-high';
status: 1 | 2 | 3 | 4;
advantages: string[];
quantity: number;
discount: number;
orginalPrice: number;
priceWithoutDiscount: number;
priceWithDiscount: number;
}[] = [
{
bookId: props.singleBookData.inventoryid,
label: 'در حد نو',
value: 'high',
status: 1,
advantages: [
'خوانده نشده و یا با احتیاط خوانده شده',
'عدم / حداقل مصرف شدگی',
'دوستدار محیط زیست',
'بدون خط خوردگی و یا هرگونه یادداشت شدگی',
'مناسب برای هدیه دادن',
],
quantity:
props.singleBookData.buy_status1 - props.singleBookData.sell_status1,
discount: props.singleBookData.current_discount,
orginalPrice: rialToToman({
price: props.singleBookData.current_price,
}),
priceWithoutDiscount: rialToToman({
price: props.singleBookData.sell_price,
}),
priceWithDiscount: rialToToman({
price:
props.singleBookData.sell_price *
(1 - props.singleBookData.current_discount / 100),
}),
},
{
bookId: props.singleBookData.inventoryid,
label: 'خوب',
value: 'medium',
status: 2,
advantages: [
'وضعیت کاملا سالم با کمی اثرات سطحی مصرف شدگی',
'مناسب برای محیط زیست',
'خوانا و تمیز',
'نوشته های کم و اثر ورق خوردگی',
'مناسب برای هدیه دادن',
],
quantity:
props.singleBookData.buy_status2 - props.singleBookData.sell_status2,
discount: props.singleBookData.current_discount,
orginalPrice: rialToToman({
price: props.singleBookData.current_price,
}),
priceWithoutDiscount: rialToToman({
price: (props.singleBookData.sell_price * 85) / 100,
}),
priceWithDiscount: rialToToman({
price:
(props.singleBookData.sell_price *
(1 - props.singleBookData.current_discount / 100) *
85) /
100,
}),
},
{
bookId: props.singleBookData.inventoryid,
label: 'قابل قبول',
value: 'low',
status: 3,
advantages: [
'دوستدار محیط زیست',
'اثرات مصرف شدگی بر روی بعضی از صفحات قابل مشاهده است.',
'گوشه بعضی از صفحات برای علامتگذاری تا شده است.',
'ارزان',
'مناسب برای استفاده شخصی',
],
quantity:
props.singleBookData.buy_status3 - props.singleBookData.sell_status3,
discount: props.singleBookData.current_discount,
orginalPrice: rialToToman({
price: props.singleBookData.current_price,
}),
priceWithoutDiscount: rialToToman({
price: (props.singleBookData.sell_price * 70) / 100,
}),
priceWithDiscount: rialToToman({
price:
(props.singleBookData.sell_price *
(1 - props.singleBookData.current_discount / 100) *
70) /
100,
}),
},
{
bookId: props.singleBookData.inventoryid,
label: 'نو',
value: 'super-high',
status: 4,
advantages: [
'این کتاب به طور مستقیم از ناشر آن تهیه شده و کاملا نو میباشد.',
],
quantity:
props.singleBookData.buy_status4 - props.singleBookData.sell_status4,
discount: Number(process.env['discountForBookWithQualitySuperHigh']),
orginalPrice: rialToToman({
price: props.singleBookData.current_price,
}),
priceWithoutDiscount: rialToToman({
price: props.singleBookData.current_price,
}),
priceWithDiscount: rialToToman({
price:
props.singleBookData.current_price *
(1 -
Number(process.env['discountForBookWithQualitySuperHigh']) / 100),
}),
},
];
// selected quality (default active from high quality to low quality, if not quantity active high quality)
// default active 'high quantity' and update state with useEffect dependencies
const [selectedQuality, setSelectedQuality] = useState(bookQualityOption[0]);
useEffect((): void => {
setSelectedQuality(
bookQualityOption[0]?.quantity
? bookQualityOption[0]
: bookQualityOption[1]?.quantity
? bookQualityOption[1]
: bookQualityOption[2]?.quantity
? bookQualityOption[2]
: bookQualityOption[3]?.quantity
? bookQualityOption[3]
: bookQualityOption[0],
);
}, [props.singleBookData]);
// add book to buy cart handler
const addBookToBuyCartHandler = (): void => {
// show toast add book to buy cart successfully
toast({
type: 'success',
text: `کتاب "${props.singleBookData.title}" با کیفیت "${
selectedQuality!.label
}" به سبد خرید اضافه شد.`,
});
// add book to buyCartItems (localStorage)
localStorageBuyCartItems &&
setLocalStorageBuyCartItems([
{
bookDataForCheckout: {
status: selectedQuality!.status,
quantity: 1,
isbn: props.singleBookData.isbn,
sell_price: Math.round(selectedQuality!.priceWithoutDiscount * 10),
current_discount: selectedQuality!.discount,
weight: props.singleBookData.weight,
modifyPrice: Math.round(selectedQuality!.priceWithDiscount * 10),
},
id: props.singleBookData.inventoryid,
name: props.singleBookData.title,
isbn: props.singleBookData.isbn,
url: props.singleBookData.bookurl,
image: props.singleBookData.image_address,
author: props.singleBookData.authors,
translator: props.singleBookData.translator,
publisher: props.singleBookData.publisher,
weight: props.singleBookData.weight,
qualityType: selectedQuality!.label,
quantity: 1,
maxQuantity: selectedQuality!.quantity,
discount: selectedQuality!.discount,
orginalPrice: selectedQuality!.orginalPrice,
priceWithoutDiscount: selectedQuality!.priceWithoutDiscount,
priceWithDiscount: selectedQuality!.priceWithDiscount,
},
...localStorageBuyCartItems,
]);
};
// find book with current selected qualityType in buyCartItems (localStorage)
// finded ? cartItem : null
const [
findedBookWithCurrentQualityTypeInBuyCartItems,
setFindedBookWithCurrentQualityTypeInBuyCartItems,
] = useState<null | BuyCartItemType>(null);
useEffect((): void => {
if (localStorageBuyCartItems !== undefined) {
const findedBook = localStorageBuyCartItems.find(
(item) =>
item.id === selectedQuality!.bookId &&
item.qualityType === selectedQuality!.label,
);
setFindedBookWithCurrentQualityTypeInBuyCartItems(
findedBook ? findedBook : null,
);
}
}, [localStorageBuyCartItems, selectedQuality]);
// for add book to favirate list or change informme status
async function fetcherAddToFavirateOrChangeInformmeStatus() {
try {
const { msg } = await APIaddToFavirateOrChangeInformmeStatus({
phoneNumber: localStorageUserAuthData!.loginid,
token: localStorageUserAuthData!.token,
isbn: String(props.singleBookData.isbn),
informmeStatus:
findedBookInFavirateList?.informme === '1' ? false : true,
});
toast({ text: msg, type: 'success' });
// if is book in favirate list change informme status and book is not in favirate list add book to favirate list
let updatedData = [];
findedBookInFavirateList
? (updatedData = favirateBooksData!.map((item) => {
if (item.isbn === findedBookInFavirateList.isbn) {
return {
...item,
informme: findedBookInFavirateList.informme === '1' ? '0' : '1',
};
} else {
return item;
}
}))
: (updatedData = favirateBooksData!.concat({
isbn: String(props.singleBookData.isbn),
informme: '1',
title: props.singleBookData.title,
authors: props.singleBookData.authors,
translator: props.singleBookData.translator,
publisher: props.singleBookData.publisher,
image_address: props.singleBookData.image_address,
bookurl: props.singleBookData.bookurl,
}));
mutate(`favirateList`, updatedData, false);
} catch (error: any) {
if (!error.response) {
toast(TOASTMSG.routes.global.pleaseCheckNetworkConnection);
} else {
toast(TOASTMSG.routes.global.sorryUnexpectedError);
}
} finally {
setDisabledInformmeBtn(false);
}
}
const addToFavirateListOrChangeInformmeStatusHandler = (): void => {
// if user is login ? add book to favirate list or change informme status : redirect to login page
if (localStorageUserAuthData) {
setDisabledInformmeBtn(true);
fetcherAddToFavirateOrChangeInformmeStatus();
} else {
toast(TOASTMSG.routes.global.pleaseLogin);
router.push(`/auth/sign-in/?navigate=${router.asPath}`);
}
};
return (
<section className="h-fit rounded-xl border border-[#e3e3e3] bg-[#fafafa] px-4">
{localStorageBuyCartItems ? (
<div className="pt-3">
{/* only render if book is in stock */}
{!!selectedQuality?.quantity && (
<>
{/* select quality */}
<div className="bo flex w-full flex-col gap-2 pb-2.5 pt-1">
<span className="whitespace-nowrap text-sm font-semibold text-[#5B5B63]">
انتخاب کیفیت کتاب
</span>
<div className="grid w-full grid-cols-2 gap-1.5">
{bookQualityOption.map((item) => (
<button
key={generateUniqueId()}
disabled={!item.quantity}
className={`t relative rounded-[10px] border pb-0.5 pt-1.5 text-sm text-gray-800 ${
selectedQuality?.label === item.label
? 'border-c-purple bg-c-purple/5'
: 'border-[#adadad]'
}
${
!item.quantity
? 'pointer-events-none border-opacity-75 opacity-50'
: 'pointer-events-auto border-opacity-100 opacity-100'
}`}
onClick={() => {
setSelectedQuality(item);
}}
>
<div
className={`absolute right-[5px] top-[5px] flex size-[18px] items-center justify-center rounded-md bg-c-purple ${
selectedQuality?.label === item.label
? 'block'
: 'hidden'
}`}
>
<IconCheckBoxTick color="#fff" />
</div>
<p className="font-medium">{item.label}</p>
{
// is quality in stock ? show priceWithDiscount : show 'ناموجود'
item.quantity ? (
<span className="text-[13px] font-normal">
{Math.round(
Number(item.priceWithDiscount),
).toLocaleString('fa-IR')}{' '}
<span className="text-[13px] font-normal">
تومان
</span>
</span>
) : (
<span className="text-[13px] font-normal">
ناموجود
</span>
)
}
</button>
))}
</div>
</div>
{/* advantages quality */}
<div className="space-y-2 border-t border-[#E3E3E3] py-4">
{selectedQuality?.advantages.map((item) => {
return (
<div
key={item}
className="flex items-center gap-1 text-sm text-[#535660]"
>
<IconChevron size={12} color="#666974" position="left" />
<span>{item}</span>
</div>
);
})}
</div>
{/* quantity */}
<div className="flex items-center gap-2 border-t border-[#e3e3e3] py-4">
<IconStarSpeed />
<span className="text-sm font-semibold text-c-purple">
تعداد موجود در انبار :{' '}
{convertNumber({
number: String(selectedQuality.quantity),
type: 'to-persian',
})}{' '}
عدد
</span>
</div>
</>
)}
{/* orginal-price */}
{!!(selectedQuality?.status !== 4) && (
<div
className={`flex items-center gap-2 border-t ${
selectedQuality?.quantity
? 'border-[#e3e3e3] py-4'
: 'border-transparent pb-4 pt-2'
}`}
>
<IconCircleDollar />
<div className="flex items-center gap-1 text-sm text-[#535660]">
<span> قیمت نو این کتاب : </span>
{selectedQuality!.orginalPrice >
selectedQuality!.priceWithDiscount ? (
<p>
{Math.round(selectedQuality!.orginalPrice).toLocaleString(
'fa-IR',
)}{' '}
تومان!
</p>
) : (
<hr className="h-[2px] w-[40px] bg-[#535660]" />
)}
</div>
</div>
)}
{/* only render if book is in stock */}
{!!selectedQuality?.quantity && (
<>
{/* quality guarante */}
<div className="flex items-center gap-2 border-t py-4">
<IconGuardTick />
<p className="text-sm text-[#535660]">
تایید کیفیت توسط ریباکس!
</p>
</div>
</>
)}
{/* add to cart or informme */}
<div
className={`container fixed inset-x-0 z-30 flex w-full justify-between border-t border-[#e3e3e3] bg-white py-3 transition-all duration-500 lg:static lg:mb-3 lg:flex-col lg:space-y-2 lg:bg-transparent lg:pb-0 lg:pt-4 ${
atomStateIsDetectFooterInViewport ? 'bottom-[-300px]' : 'bottom-0'
}`}
>
{/* price-with-discount, price-without-discount, discount */}
<div
className={`flex-col justify-center lg:items-center lg:justify-start ${
selectedQuality?.quantity ? 'flex' : 'hidden'
}`}
>
{selectedQuality?.discount ? (
<del className="ml-3 text-sm text-[#898D98] lg:text-[14.5px]">
{Math.round(
selectedQuality!.priceWithoutDiscount,
).toLocaleString('fa-IR')}{' '}
تومان
</del>
) : null}
<div>
<div className="flex justify-center gap-2 pt-1 lg:pt-0">
<div className="flex items-center gap-1.5 text-[#535660]">
<span className="whitespace-nowrap text-[17px] font-semibold">
{Math.round(
Number(selectedQuality!.priceWithDiscount),
).toLocaleString('fa-IR')}{' '}
<span className="text-sm font-medium">تومان</span>
</span>
</div>
{/* discount [optinal] */}
{selectedQuality?.discount ? (
<span className="h-[23px] rounded-[13px] bg-c-red px-3 pt-1 text-[13px] text-white">
{convertNumber({
number: String(selectedQuality.discount),
type: 'to-persian',
})}
%
</span>
) : null}
</div>
<div className="flex items-center gap-1.5 font-medium text-[#535660] lg:hidden">
<span className="text-sm">کیفیت : </span>
<span className="pb-0.5 text-[13.5px] font-medium">
{selectedQuality?.label}
</span>
</div>
</div>
</div>
{/* check bookId+qualityType is in BuyCartItems ? show [increment and decrement quantity] : (is book qunatity ? show [add book to cart] : show informme) */}
<div className="flex w-fit items-center justify-end lg:block lg:w-full">
{findedBookWithCurrentQualityTypeInBuyCartItems ? (
<>
{/* increment and decrement quantity + go to /buy/cart */}
<div className="flex items-center justify-center gap-4">
<div>
<IncrementAndDecrementBuyCartItemQuantity
buyCartItem={
findedBookWithCurrentQualityTypeInBuyCartItems
}
/>
</div>
<div className="hidden justify-center lg:flex">
<Link
href="/buy/cart"
className="flex items-center gap-1"
>
<IconShoppingCartWithEye />
<span className="pt-1 text-[12.5px] font-medium text-c-royal-blue">
مشاهده سبد خرید
</span>
</Link>
</div>
</div>
</>
) : (
<>
{selectedQuality?.quantity ? (
<>
{/* add to cart btn */}
<div className="flex justify-center">
<button
onClick={addBookToBuyCartHandler}
className="btn-purple w-full rounded-lg px-3 pb-2 pt-3 text-[12.5px] font-medium text-white transition-all duration-300 lg:mb-1.5 lg:max-w-[260px] lg:rounded-[10px] lg:py-[11px] lg:text-sm"
>
افزودن به سبد خرید
</button>
</div>
</>
) : (
<>
{/* add book to imformme or delete book from informe */}
<div className="flex w-full justify-center">
<button
disabled={
disabledInformmeBtn ||
(localStorageUserAuthData
? !favirateBooksData
: false)
} // if user not login or favirateBooksData not fetched or disableld is true ===> disable button
onClick={
addToFavirateListOrChangeInformmeStatusHandler
}
className="btn-orange w-full max-w-xs rounded-[10px] py-3 text-sm font-medium text-white transition-all duration-300 lg:mb-1.5 lg:max-w-[260px] lg:py-[11px]"
>
{/* if disabledInformmeBtn is true ? show loader : (is book in favirates book and informme === '1' ? موجود شد خبرم کنید' : 'دیگر نیازی نیست خبرم کنید') */}
{disabledInformmeBtn ? (
<div className="flex w-full justify-center">
<ThreeDots color="#fff" width={50} height={21} />
</div>
) : Number(findedBookInFavirateList?.informme) ? (
'دیگر نیازی نیست خبرم کنید'
) : (
'موجود شد خبرم کنید'
)}
</button>
</div>
</>
)}
</>
)}
</div>
</div>
</div>
) : (
<div className="flex justify-center py-4">
<ThreeDots color="#5036BB" width={70} height={50} />
</div>
)}
</section>
);
};
export default BuySingleBook;
Editor is loading...
Leave a Comment