add "order details" bottom sheet
This commit is contained in:
138
src/components/CustomBottomSheet/OrderDetailsBottomSheet.tsx
Normal file
138
src/components/CustomBottomSheet/OrderDetailsBottomSheet.tsx
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { ProBottomSheet } from "../ProBottomSheet/ProBottomSheet";
|
||||||
|
import { useAppSelector } from "redux/hooks";
|
||||||
|
import ProText from "components/ProText";
|
||||||
|
import { OrderType } from "pages/checkout/hooks/types";
|
||||||
|
|
||||||
|
interface OrderDetailsBottomSheetProps {
|
||||||
|
isOpen: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function OrderDetailsBottomSheet({
|
||||||
|
isOpen,
|
||||||
|
onClose,
|
||||||
|
}: OrderDetailsBottomSheetProps) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { orderType, restaurant, specialRequest, items } = useAppSelector(
|
||||||
|
(state) => state.order,
|
||||||
|
);
|
||||||
|
// const { isRTL } = useAppSelector((state) => state.locale);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ProBottomSheet
|
||||||
|
isOpen={isOpen}
|
||||||
|
onClose={onClose}
|
||||||
|
title={t("Order Details")}
|
||||||
|
height={500}
|
||||||
|
snapPoints={["60vh"]}
|
||||||
|
>
|
||||||
|
<div style={{ padding: "16px 0" }}>
|
||||||
|
{/* Order Type */}
|
||||||
|
<div style={{ marginBottom: 16 }}>
|
||||||
|
<ProText style={{ fontSize: 18, fontWeight: 600 }}>
|
||||||
|
{t("Order Type")}: {orderType && t(orderType)}
|
||||||
|
</ProText>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Items List */}
|
||||||
|
<div style={{ marginBottom: 16 }}>
|
||||||
|
<ProText style={{ fontSize: 16, fontWeight: 600, marginBottom: 8 }}>
|
||||||
|
{t("Items")}
|
||||||
|
</ProText>
|
||||||
|
{items.length > 0 ? (
|
||||||
|
<div>
|
||||||
|
{items.map((item) => (
|
||||||
|
<div
|
||||||
|
key={item.id}
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
marginBottom: 8,
|
||||||
|
padding: "8px 0",
|
||||||
|
borderBottom: "1px solid #eee",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<ProText style={{ fontWeight: 500 }}>
|
||||||
|
{item.quantity} x {item.name}
|
||||||
|
</ProText>
|
||||||
|
</div>
|
||||||
|
<ProText style={{ fontWeight: 500 }}>{}</ProText>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<ProText>{t("No items in cart")}</ProText>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Special Request */}
|
||||||
|
{specialRequest && (
|
||||||
|
<div style={{ marginBottom: 16 }}>
|
||||||
|
<ProText style={{ fontSize: 16, fontWeight: 600, marginBottom: 8 }}>
|
||||||
|
{t("Special Request")}
|
||||||
|
</ProText>
|
||||||
|
<ProText>{specialRequest}</ProText>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Order Summary */}
|
||||||
|
<div style={{ marginTop: 24 }}>
|
||||||
|
<ProText style={{ fontSize: 16, fontWeight: 600, marginBottom: 8 }}>
|
||||||
|
{t("Order Summary")}
|
||||||
|
</ProText>
|
||||||
|
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
marginBottom: 8,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ProText>{t("Subtotal")}</ProText>
|
||||||
|
<ProText>{}</ProText>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
marginBottom: 8,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ProText>{t("Tax")}</ProText>
|
||||||
|
<ProText>{}</ProText>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{orderType !== OrderType.DineIn && restaurant?.delivery_fees && (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
marginBottom: 8,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ProText>{t("Delivery Fee")}</ProText>
|
||||||
|
<ProText>{Number(restaurant.delivery_fees)}</ProText>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
marginTop: 16,
|
||||||
|
paddingTop: 16,
|
||||||
|
borderTop: "1px solid #eee",
|
||||||
|
fontWeight: 600,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ProText style={{ fontWeight: 600 }}>{t("Total")}</ProText>
|
||||||
|
<ProText style={{ fontWeight: 600 }}>{}</ProText>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ProBottomSheet>
|
||||||
|
);
|
||||||
|
}
|
||||||
52
src/hooks/useSwipeUp.ts
Normal file
52
src/hooks/useSwipeUp.ts
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
import { useRef, useState, useCallback } from "react";
|
||||||
|
|
||||||
|
type Props = { isEnabled: boolean; swipeAction: () => void };
|
||||||
|
|
||||||
|
export default function useSwipeUp({ isEnabled, swipeAction }: Props) {
|
||||||
|
// Swipe detection
|
||||||
|
const startYRef = useRef(0);
|
||||||
|
const [isSwiping, setIsSwiping] = useState(false);
|
||||||
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
|
// Touch event handlers for swipe detection
|
||||||
|
const handleTouchStart = useCallback(
|
||||||
|
(e: React.TouchEvent) => {
|
||||||
|
if (!isEnabled) return;
|
||||||
|
startYRef.current = e.touches[0].clientY;
|
||||||
|
setIsSwiping(true);
|
||||||
|
},
|
||||||
|
[isEnabled],
|
||||||
|
);
|
||||||
|
|
||||||
|
/*
|
||||||
|
const handleTouchMove = useCallback(
|
||||||
|
(e: React.TouchEvent) => {
|
||||||
|
if (!isSwiping) return;
|
||||||
|
},
|
||||||
|
[isSwiping],
|
||||||
|
);
|
||||||
|
*/
|
||||||
|
|
||||||
|
const handleTouchEnd = useCallback(
|
||||||
|
(e: React.TouchEvent) => {
|
||||||
|
if (!isSwiping || !isEnabled) return;
|
||||||
|
|
||||||
|
const endY = e.changedTouches[0].clientY;
|
||||||
|
const deltaY = startYRef.current - endY;
|
||||||
|
const threshold = 70; // Threshold to detect a swipe up
|
||||||
|
|
||||||
|
if (deltaY > threshold) {
|
||||||
|
// Swipe up detected
|
||||||
|
swipeAction();
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsSwiping(false);
|
||||||
|
},
|
||||||
|
[isSwiping, isEnabled],
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
containerRef,
|
||||||
|
handleTouchStart,
|
||||||
|
handleTouchEnd,
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -21,13 +21,16 @@ import {
|
|||||||
} from "react-router-dom";
|
} from "react-router-dom";
|
||||||
import { useGetRestaurantDetailsQuery } from "redux/api/others";
|
import { useGetRestaurantDetailsQuery } from "redux/api/others";
|
||||||
import LocalStorageHandler from "../menu/components/LocalStorageHandler";
|
import LocalStorageHandler from "../menu/components/LocalStorageHandler";
|
||||||
import { useEffect } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import {
|
import {
|
||||||
updateOrderType,
|
updateOrderType,
|
||||||
CART_STORAGE_KEYS,
|
CART_STORAGE_KEYS,
|
||||||
} from "features/order/orderSlice.ts";
|
} from "features/order/orderSlice.ts";
|
||||||
import { OrderType } from "pages/checkout/hooks/types.ts";
|
import { OrderType } from "pages/checkout/hooks/types.ts";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { OrderDetailsBottomSheet } from "components/CustomBottomSheet/OrderDetailsBottomSheet";
|
||||||
|
import useBreakPoint from "hooks/useBreakPoint.ts";
|
||||||
|
import useSwipeUp from "hooks/useSwipeUp.ts";
|
||||||
|
|
||||||
const storedOrderType = localStorage.getItem(CART_STORAGE_KEYS.ORDER_TYPE);
|
const storedOrderType = localStorage.getItem(CART_STORAGE_KEYS.ORDER_TYPE);
|
||||||
|
|
||||||
@@ -35,13 +38,22 @@ export default function RestaurantPage() {
|
|||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const param = useParams();
|
const param = useParams();
|
||||||
|
const { isMobile } = useBreakPoint();
|
||||||
const [searchParams] = useSearchParams();
|
const [searchParams] = useSearchParams();
|
||||||
const { pathname } = useLocation();
|
const { pathname } = useLocation();
|
||||||
const { orderType } = useAppSelector((state) => state.order);
|
const { orderType, items: cartItems } = useAppSelector(
|
||||||
|
(state) => state.order,
|
||||||
|
);
|
||||||
const { isRTL } = useAppSelector((state) => state.locale);
|
const { isRTL } = useAppSelector((state) => state.locale);
|
||||||
const { data: restaurant, isLoading } = useGetRestaurantDetailsQuery("595", {
|
const { data: restaurant, isLoading } = useGetRestaurantDetailsQuery("595", {
|
||||||
skip: !param.id,
|
skip: !param.id,
|
||||||
});
|
});
|
||||||
|
const [isOrderDetailsOpen, setIsOrderDetailsOpen] = useState(false);
|
||||||
|
|
||||||
|
const { containerRef, handleTouchEnd, handleTouchStart } = useSwipeUp({
|
||||||
|
swipeAction: () => setIsOrderDetailsOpen(true),
|
||||||
|
isEnabled: isMobile && cartItems.length > 0,
|
||||||
|
});
|
||||||
|
|
||||||
// Automatically load restaurant taxes when restaurant data is available
|
// Automatically load restaurant taxes when restaurant data is available
|
||||||
useRestaurant(restaurant);
|
useRestaurant(restaurant);
|
||||||
@@ -102,7 +114,13 @@ export default function RestaurantPage() {
|
|||||||
<div className={styles.promotionContainer}>
|
<div className={styles.promotionContainer}>
|
||||||
<Ads1 />
|
<Ads1 />
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.socialIconsContainer}>
|
|
||||||
|
<div
|
||||||
|
ref={containerRef}
|
||||||
|
onTouchStart={handleTouchStart}
|
||||||
|
onTouchEnd={handleTouchEnd}
|
||||||
|
className={styles.socialIconsContainer}
|
||||||
|
>
|
||||||
<InstagramIcon className={styles.socialIcon} />
|
<InstagramIcon className={styles.socialIcon} />
|
||||||
<XIcon className={styles.socialIcon} />
|
<XIcon className={styles.socialIcon} />
|
||||||
<SnapIcon className={styles.socialIcon} />
|
<SnapIcon className={styles.socialIcon} />
|
||||||
@@ -110,6 +128,12 @@ export default function RestaurantPage() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Order Details Bottom Sheet - Moved outside the container */}
|
||||||
|
<OrderDetailsBottomSheet
|
||||||
|
isOpen={isOrderDetailsOpen}
|
||||||
|
onClose={() => setIsOrderDetailsOpen(false)}
|
||||||
|
/>
|
||||||
|
|
||||||
<LocalStorageHandler
|
<LocalStorageHandler
|
||||||
restaurantID={restaurant.restautantId}
|
restaurantID={restaurant.restautantId}
|
||||||
restaurantName={restaurant.restautantName}
|
restaurantName={restaurant.restautantName}
|
||||||
|
|||||||
Reference in New Issue
Block a user