diff --git a/src/app/[name]/(Dialogs)/layout.tsx b/src/app/[name]/(Dialogs)/layout.tsx index d194a54..587c831 100644 --- a/src/app/[name]/(Dialogs)/layout.tsx +++ b/src/app/[name]/(Dialogs)/layout.tsx @@ -6,7 +6,7 @@ import React from 'react' function DialogsLayout({ children }: { children: React.ReactNode }) { return ( -
+
{children}
diff --git a/src/app/[name]/(Dialogs)/order/checkout/[id]/page.tsx b/src/app/[name]/(Dialogs)/order/checkout/[id]/page.tsx new file mode 100644 index 0000000..2211f8d --- /dev/null +++ b/src/app/[name]/(Dialogs)/order/checkout/[id]/page.tsx @@ -0,0 +1,316 @@ +'use client'; + +import AnimatedBottomSheet from '@/components/bottomsheet/AnimatedBottomSheet'; +import Button from '@/components/button/PrimaryButton'; +import Combobox, { ComboboxOption } from '@/components/combobox/Combobox'; +import InputField from '@/components/input/InputField'; +import clsx from 'clsx'; +import { motion } from 'framer-motion'; +import { ArrowLeft, Box, Card, Cup, Icon, Shop, Ticket, TicketDiscount, TruckTick, Wallet2 } from 'iconsax-react'; +import { ChevronLeft, MinusIcon, PlusIcon } from 'lucide-react'; +import { useParams, useRouter } from 'next/navigation'; +import React, { useCallback, useState } from 'react' +import { useTranslation } from 'react-i18next'; + +type Params = { + id: string +} + + +function OrderDetailInex() { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const params = useParams(); + const { t } = useTranslation('parallels', { keyPrefix: 'OrderDetail' }); + const router = useRouter(); + const [deliveryType, setDeliveryType] = React.useState('0'); + const [paymentType, setPaymentType] = React.useState('0'); + const [couponType, setCouponType] = React.useState('0'); + const [couponCode, setCouponCode] = React.useState(''); + const [cartModal, setCartModal] = React.useState(false); + const [couponCodeError, setCouponCodeError] = React.useState(''); // TODO: handle coupon code error + const [tableNumber, setTableNumber] = useState(0) // TODO: must be set to table number + const incrementTableNumber = () => setTableNumber((prev) => prev + 1); // TODO: must be set to table number + const decrementTableNumber = () => setTableNumber((prev) => prev - 1); // TODO: must be set to table number + const address = "اراک، مصطفی خمینی، خیابان سینا کوچه چمران ساختمان شهرزاد" + + const deliveryOptions: Array = [ + { id: '0', title: t("SectionShipping.InputDeliveryType.Options.Delivery"), icon: TruckTick }, + { id: '1', title: t("SectionShipping.InputDeliveryType.Options.Serve"), icon: Shop }, + { id: '2', title: t("SectionShipping.InputDeliveryType.Options.Pickup"), icon: Box }, + ]; + + const paymentOptions: Array<{ id: string, label: string, icon: Icon }> = [ + { id: '0', label: t("SectionPayment.InputPaymentType.Options.Online"), icon: Card }, + { id: '1', label: t("SectionPayment.InputPaymentType.Options.CashOnDelivery"), icon: Wallet2 }, + ]; + + const couponOptions: Array<{ id: string, label: string, icon: Icon }> = [ + { id: '0', label: t("SectionCoupon.InputCouponType.Options.Discount"), icon: Cup }, + { id: '1', label: t("SectionCoupon.InputCouponType.Options.GiftCard"), icon: Ticket }, + ]; + + const couponDropdownOptions: Array = [ + { id: '0', title: t("SectionCoupon.InputCouponCode.Options.Default") }, + { id: '1', title: "Something" }, + ]; + + const changeDeliveryType = useCallback((id: string) => { + setDeliveryType(id); + }, []) + + const changePaymentType = useCallback((id: string) => { + setPaymentType(id); + }, []); + + const changeCouponType = useCallback((id: string) => { + setCouponCode(''); + setCouponCodeError(''); + setCouponType(id); + }, []); + + const changeCouponCode = useCallback((id: string) => { + if (id === '0') { + setCouponCode(''); + return; + } + setCouponCode(id); + }, []); + + const toggleCartModal = useCallback(() => { + setCartModal((prev) => !prev); + }, []); + + const addressSection = () => { + if (deliveryType !== '0') { + return null; + } + return ( +
+
+
+

{t("SectionAddress.Title")}

+ +
+

+ {address} +

+
+
+ ) + } + + const tableSection = () => { + if (deliveryType !== '1') { + return null; + } + return ( +
+
+
+

{t("SectionTable.Title")}

+ +
+ + + + {tableNumber} + + + +
+
+
+
+ ) + } + + return ( +
+
+ +

{t("Heading")}

+ { router.back() }} + /> +
+ +
+
+

{t("SectionShipping.Title")}

+ changeDeliveryType(String(i))} + /> +
+
+ + {addressSection()} + {tableSection()} + +
+
+

{t("SectionPayment.Title")}

+ +
+ {paymentOptions.map(({ id, label, icon: Icon }) => ( +
+ + + changePaymentType(id)} + type='radio' + name='paymentMethod' + id={`paymentMethod${id}`} + className='size-4 accent-primary' /> +
+ ))} +
+ +
+
+ +
+
+

{t("SectionCoupon.Title")}

+ +
+ {couponOptions.map(({ id, label, icon: Icon }) => ( +
+ + + changeCouponType(id)} + type='radio' + name='couponType' + id={`couponType${id}`} + className='size-4 accent-primary' /> +
+ ))} +
+ + changeCouponCode(String(i))} + /> + + changeCouponCode(e.target.value)} + value={couponCode} + /> + +
+
+ +
+
+
+

{t("SectionSummary.Title")}

+ +
+
+ {t("SectionSummary.SumOfItemsLabel")} + 100,000 T + {t("SectionSummary.DeliveryLabel")} + 20,000 T + {t("SectionSummary.DiscountLabel")} + 10,000 T + {t("SectionSummary.TotalLabel")} + 110,000 T +
+ +
+
+ + +
+
+
+ ) +} + +export default OrderDetailInex \ No newline at end of file diff --git a/src/components/combobox/Combobox.tsx b/src/components/combobox/Combobox.tsx index f54549b..0486e58 100644 --- a/src/components/combobox/Combobox.tsx +++ b/src/components/combobox/Combobox.tsx @@ -1,11 +1,13 @@ import React, { useState, useEffect, useRef } from 'react'; -import ArrowDownIcon from '../icons/ArrowDownIcon'; import { motion, Variants } from 'framer-motion'; -import { SearchNormal } from 'iconsax-react'; +import { Icon, SearchNormal } from 'iconsax-react'; +import clsx from 'clsx'; +import { ChevronDown } from 'lucide-react'; export interface ComboboxOption { id: string; title: string; + icon?: Icon; label?: string; } @@ -15,10 +17,12 @@ type Props = { expanded?: boolean; selectedId: string; searchable?: boolean; + placeholder?: string; + icon?: React.ElementType; onSelectionChange: (e: React.MouseEvent, index: number) => void, } & React.HTMLAttributes -function Combobox({ title, options, expanded, selectedId, searchable = true, onSelectionChange, ...props }: Props) { +function Combobox({ title, options, placeholder, icon: Icon, expanded, selectedId, searchable = true, onSelectionChange, ...props }: Props) { const [expand, setExpand] = useState(expanded); const [searchValue, setSearchValue] = useState(''); const boxRef = useRef(null); @@ -54,6 +58,13 @@ function Combobox({ title, options, expanded, selectedId, searchable = true, onS setExpand(false); }; + const selectedOption = options.find((x) => x.id === selectedId); + const activeIcon = selectedOption?.icon && React.createElement(selectedOption.icon, { + size: 16, + className: "inline-block mr-1 stroke-primary" + }) + + const variants: Variants = { collapse: { opacity: 0, @@ -77,7 +88,12 @@ function Combobox({ title, options, expanded, selectedId, searchable = true, onS return ( -
+
-
- {options.find((x) => x.id === selectedId)?.title ?? ''} +
+ { + activeIcon ? ( + activeIcon + ) : ( + Icon && + ) + } + + {selectedOption?.title ?? placeholder} +
- +
e.stopPropagation()} @@ -135,12 +163,18 @@ function Combobox({ title, options, expanded, selectedId, searchable = true, onS onClick={(e) => setSelection(e, i)} key={v.id} data-selected={v.id === selectedId} - className="flex items-center justify-end px-2 py-1.5 cursor-pointer hover:bg-gray-50 rounded-md last:rounded-b-xl" + className="flex gap-3 items-center justify-end px-2 py-1 cursor-pointer hover:bg-gray-50 rounded-md last:rounded-b-xl" tabIndex={0} role="option" aria-selected > - {v.title} + { + v?.icon && React.createElement(v.icon, { + size: 16, + className: "inline-block mr-1 stroke-primary" + }) + } + {v.title}
))}
diff --git a/src/components/input/InputField.tsx b/src/components/input/InputField.tsx index 26ea54b..989db27 100644 --- a/src/components/input/InputField.tsx +++ b/src/components/input/InputField.tsx @@ -13,7 +13,9 @@ type Props = { function InputField({ onChange, htmlFor, labelText, children, valid = true, className, ...inputProps }: Props) { return ( -
+