Compare commits
6 Commits
4dfa08d26c
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| e86966062a | |||
| 3fe0c68526 | |||
| 5cb681c4a8 | |||
| f97b83062c | |||
| 69425580d6 | |||
| 8083e9ec96 |
BIN
public/thawani.png
Normal file
BIN
public/thawani.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.2 KiB |
@@ -178,7 +178,9 @@
|
||||
"wednesday": "الأربعاء",
|
||||
"thursday": "الخميس",
|
||||
"friday": "الجمعة",
|
||||
"saturday": "السبت"
|
||||
"saturday": "السبت",
|
||||
"selectYourTable": "اختر طاولتك",
|
||||
"calledWaiterSuccess": "تم اتصال الرادير بنجاح"
|
||||
},
|
||||
"cart": {
|
||||
"addSpecialRequestOptional": "إضافة طلب خاص (اختياري)",
|
||||
@@ -287,8 +289,8 @@
|
||||
"cannotSelectPastDate": "لا يمكنك اختيار تاريخ سابق. يرجى اختيار اليوم أو تاريخ مستقبلي.",
|
||||
"checkRequiredFields": "يرجى التحقق من الحقول المطلوبة",
|
||||
"loyalty": "ولاء",
|
||||
"vatTax": "ضريبة (القيمة المضافة)",
|
||||
"otherTaxes": "ضريبة (أخرى)"
|
||||
"vat": "ضريبة القيمة المضافة ({{value}}%))",
|
||||
"otherTaxes": "{{name}} ({{value}}%)"
|
||||
},
|
||||
"checkout": {
|
||||
"addCarDetails": "إضافة تفاصيل السيارة",
|
||||
@@ -333,7 +335,9 @@
|
||||
"change": "تغيير",
|
||||
"pickup": "استلام",
|
||||
"setPickupTime": "تحديد وقت الاستلام",
|
||||
"carPlateNumber": "رقم لوحة السيارة"
|
||||
"carPlateNumber": "رقم لوحة السيارة",
|
||||
"noItems": "لا يوجد عناصر في السلة",
|
||||
"thawani": "ثواني"
|
||||
},
|
||||
"address": {
|
||||
"title": "العنوان",
|
||||
|
||||
@@ -190,7 +190,9 @@
|
||||
"wednesday": "Wednesday",
|
||||
"thursday": "Thursday",
|
||||
"friday": "Friday",
|
||||
"saturday": "Saturday"
|
||||
"saturday": "Saturday",
|
||||
"selectYourTable": "Select your table",
|
||||
"calledWaiterSuccess": "Waiter has been called successfully"
|
||||
},
|
||||
"cart": {
|
||||
"remainingToPay": "Remaining to Pay",
|
||||
@@ -304,8 +306,8 @@
|
||||
"checkRequiredFields": "Please check required fields",
|
||||
"applyYourAvailableRewardsToGetDiscountsOnItemsInYourCart": "Apply your available rewards to get discounts on items in your cart",
|
||||
"loyalty": "Loyalty",
|
||||
"vatTax": "Tax (Vat)",
|
||||
"otherTaxes": "Tax (Other)"
|
||||
"vat": "Vat ({{value}}%)",
|
||||
"otherTaxes": "{{name}} ({{value}}%)"
|
||||
},
|
||||
"checkout": {
|
||||
"addCarDetails": "Add Car Details",
|
||||
@@ -352,7 +354,9 @@
|
||||
"change": "Change",
|
||||
"pickup": "Pickup",
|
||||
"setPickupTime": "Set Pickup Time",
|
||||
"carPlateNumber": "Car Plate Number"
|
||||
"carPlateNumber": "Car Plate Number",
|
||||
"noItems": "No items in cart",
|
||||
"thawani": "Thawani"
|
||||
},
|
||||
"address": {
|
||||
"title": "Address",
|
||||
|
||||
@@ -27,7 +27,7 @@ const ArabicPrice: React.FC<ArabicPriceProps> = ({
|
||||
|
||||
// Format the price to ensure it has 2 decimal places
|
||||
const formattedPrice =
|
||||
typeof price === "number" ? formatPriceUi(price, 3) : price;
|
||||
typeof price === "number" ? formatPriceUi(price, restaurant?.currency_decimals ?? 3) : price;
|
||||
const { textDecoration, ...restStyle } = style;
|
||||
const decorationStyle = textDecoration
|
||||
? ({ textDecoration } as React.CSSProperties)
|
||||
|
||||
@@ -18,9 +18,9 @@ const RefershIcon = ({ className, onClick, dimension }: RefershIconType) => {
|
||||
<path
|
||||
d="M1.5 7.5C1.5 7.5 1.59099 6.86307 4.22703 4.22703C6.86307 1.59099 11.1369 1.59099 13.773 4.22703C14.7069 5.16099 15.31 6.30054 15.5821 7.5M1.5 7.5V3M1.5 7.5H6M16.5 10.5C16.5 10.5 16.409 11.1369 13.773 13.773C11.1369 16.409 6.86307 16.409 4.22703 13.773C3.29307 12.839 2.69002 11.6995 2.41787 10.5M16.5 10.5V15M16.5 10.5H12"
|
||||
stroke="#5F6C7B"
|
||||
stroke-width="1.5"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
strokeWidth="1.5"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import { Card, Col, Image, Row } from "antd";
|
||||
import PresentIcon from "components/Icons/cart/PresentIcon";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Link, useParams } from "react-router-dom";
|
||||
import { useGetRestaurantDetailsQuery } from "redux/api/others";
|
||||
import { ACCESS_TOKEN } from "utils/constants";
|
||||
import { colors } from "ThemeConstants.ts";
|
||||
import ProText from "../ProText";
|
||||
@@ -12,7 +10,7 @@ import { useAppSelector } from "redux/hooks";
|
||||
const LoyaltyCard = () => {
|
||||
const { t } = useTranslation();
|
||||
const { subdomain } = useParams();
|
||||
const { data: restaurant } = useGetRestaurantDetailsQuery(subdomain);
|
||||
const { restaurant } = useAppSelector((state) => state.order);
|
||||
const { isRTL } = useAppSelector((state) => state.locale);
|
||||
const token = localStorage.getItem(ACCESS_TOKEN);
|
||||
const loyaltyStamps = restaurant?.loyalty_stamps ?? 0;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Card, Checkbox, Divider, Space, Tag } from "antd";
|
||||
import { Card, Divider, Space, Tag } from "antd";
|
||||
import ArabicPrice from "components/ArabicPrice";
|
||||
import {
|
||||
selectCart,
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
selectDiscountTotal,
|
||||
selectGrandTotal,
|
||||
selectHighestPricedLoyaltyItem,
|
||||
selectTaxAmount,
|
||||
totalTaxes,
|
||||
} from "features/order/orderSlice";
|
||||
import { OrderType } from "pages/checkout/hooks/types";
|
||||
import { useTranslation } from "react-i18next";
|
||||
@@ -16,7 +16,7 @@ import { useAppSelector } from "redux/hooks";
|
||||
import ProText from "../ProText";
|
||||
import ProTitle from "../ProTitle";
|
||||
import styles from "./OrderSummary.module.css";
|
||||
import { CSSProperties } from "react";
|
||||
import { CSSProperties, useMemo } from "react";
|
||||
|
||||
export default function OrderSummary() {
|
||||
const { t } = useTranslation();
|
||||
@@ -26,7 +26,7 @@ export default function OrderSummary() {
|
||||
const { orderType } = useAppSelector(selectCart);
|
||||
const subtotal = useAppSelector(selectCartTotal);
|
||||
const highestLoyaltyItem = useAppSelector(selectHighestPricedLoyaltyItem);
|
||||
const taxAmount = useAppSelector(selectTaxAmount);
|
||||
const totalTaxesAmount = useAppSelector(totalTaxes);
|
||||
const grandTotal = useAppSelector(selectGrandTotal);
|
||||
const discountAmount = useAppSelector(selectDiscountTotal);
|
||||
|
||||
@@ -44,7 +44,27 @@ export default function OrderSummary() {
|
||||
textAlign: "center",
|
||||
};
|
||||
|
||||
const vat = ((restaurant?.vat ?? 0) / 100) * subtotal || 0;
|
||||
const vat = ((restaurant?.vat ?? 0) / 100) * (subtotal - discountAmount) || 0;
|
||||
|
||||
// Calculate individual taxes
|
||||
const taxesList = useMemo(() => {
|
||||
if (!restaurant?.taxes) return [];
|
||||
|
||||
const subtotalAfterDiscount = subtotal - discountAmount;
|
||||
|
||||
return restaurant.taxes
|
||||
.filter((tax) => tax.is_active === 1)
|
||||
.map((tax) => {
|
||||
const amount = ((Number(tax.percentage) || 0) / 100) * subtotalAfterDiscount;
|
||||
return {
|
||||
id: tax.id,
|
||||
name: tax.name,
|
||||
name_local: tax.name_local,
|
||||
percentage: tax.percentage,
|
||||
amount,
|
||||
};
|
||||
});
|
||||
}, [restaurant?.taxes, subtotal, discountAmount]);
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -117,7 +137,7 @@ export default function OrderSummary() {
|
||||
</Tag>
|
||||
) : null}
|
||||
<ArabicPrice
|
||||
price={discountAmount}
|
||||
price={discountAmount + (useLoyaltyPoints && restaurant?.is_loyalty_enabled === 1 ? ((highestLoyaltyItem?.price || 0) * (totalTaxesAmount / 100)) : 0)}
|
||||
textStyle={{ ...titlesStyle, color: "#434E5C" }}
|
||||
/>
|
||||
</div>
|
||||
@@ -156,10 +176,10 @@ export default function OrderSummary() {
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{orderType !== OrderType.Redeem && (
|
||||
{orderType !== OrderType.Redeem && restaurant?.vat && (
|
||||
<div className={styles.summaryRow}>
|
||||
<ProText type="secondary" style={titlesStyle}>
|
||||
{t("cart.vatTax")}
|
||||
{t("cart.vat", { value: restaurant.vat })}
|
||||
</ProText>
|
||||
<ArabicPrice
|
||||
price={vat}
|
||||
@@ -167,17 +187,21 @@ export default function OrderSummary() {
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{orderType !== OrderType.Redeem && (
|
||||
<div className={styles.summaryRow}>
|
||||
{orderType !== OrderType.Redeem &&
|
||||
taxesList.map((tax) => (
|
||||
<div key={tax.id} className={styles.summaryRow}>
|
||||
<ProText type="secondary" style={titlesStyle}>
|
||||
{t("cart.otherTaxes")}
|
||||
{t("cart.otherTaxes", {
|
||||
name: tax.name || tax.name_local,
|
||||
value: tax.percentage,
|
||||
})}
|
||||
</ProText>
|
||||
<ArabicPrice
|
||||
price={taxAmount - vat || 0}
|
||||
price={tax.amount}
|
||||
textStyle={{ ...titlesStyle, color: "#434E5C" }}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
))}
|
||||
{orderType !== OrderType.Redeem && splitBillAmount > 0 && (
|
||||
<div className={styles.summaryRow}>
|
||||
<ProText type="secondary" style={titlesStyle}>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Form, Radio, Space } from "antd";
|
||||
import { Form, Image, Radio, Space } from "antd";
|
||||
import { Group } from "antd/es/radio";
|
||||
import ArabicPrice from "components/ArabicPrice";
|
||||
import DifferentCardIcon from "components/Icons/paymentMethods/DifferentCardIcon";
|
||||
// import DifferentCardIcon from "components/Icons/paymentMethods/DifferentCardIcon";
|
||||
import ProText from "components/ProText";
|
||||
import {
|
||||
selectCart,
|
||||
@@ -14,7 +14,6 @@ import { colors, ProGray1 } from "../../ThemeConstants";
|
||||
import ProInputCard from "../ProInputCard/ProInputCard";
|
||||
import styles from "./PaymentMethods.module.css";
|
||||
import { OrderType } from "pages/checkout/hooks/types.ts";
|
||||
import RCardIcon from "components/Icons/RCardIcon";
|
||||
import { formatPriceUi } from "utils/helpers";
|
||||
|
||||
const PaymentMethods = () => {
|
||||
@@ -22,7 +21,8 @@ const PaymentMethods = () => {
|
||||
const { paymentMethod, orderType } = useAppSelector(selectCart);
|
||||
const dispatch = useAppDispatch();
|
||||
const grandTotal = useAppSelector(selectGrandTotal);
|
||||
const { isRTL } = useAppSelector((state) => state.locale);
|
||||
const { restaurant } = useAppSelector((state) => state.order);
|
||||
// const { isRTL } = useAppSelector((state) => state.locale);
|
||||
|
||||
const options: {
|
||||
label: React.ReactNode;
|
||||
@@ -40,16 +40,18 @@ const PaymentMethods = () => {
|
||||
<ProText
|
||||
style={{
|
||||
color: "#E8B400",
|
||||
[isRTL ? "marginLeft" : "marginRight"]: 4,
|
||||
// [isRTL ? "marginLeft" : "marginRight"]: 4,
|
||||
}}
|
||||
>
|
||||
$
|
||||
{/* $ */}
|
||||
</ProText>
|
||||
<ProText style={{ color: "#5F6C7B" }}>
|
||||
{t("checkout.cash")}
|
||||
</ProText>
|
||||
<ProText style={{color: '#5F6C7B'}}>{t("checkout.cash")}</ProText>
|
||||
</>
|
||||
),
|
||||
value: "cash",
|
||||
price: formatPriceUi(grandTotal, 3),
|
||||
price: formatPriceUi(grandTotal, restaurant.currency_decimals ?? 3),
|
||||
style: {
|
||||
color: colors.primary,
|
||||
},
|
||||
@@ -65,14 +67,26 @@ const PaymentMethods = () => {
|
||||
{
|
||||
label: (
|
||||
<>
|
||||
<RCardIcon className={styles.eCardIcon} />
|
||||
<ProText style={{color: '#5F6C7B'}}>{t("checkout.differentCard")}</ProText>
|
||||
{/* <RCardIcon className={styles.eCardIcon} /> */}
|
||||
<ProText style={{ color: "#5F6C7B" }}>
|
||||
{t("checkout.thawani")}
|
||||
</ProText>
|
||||
</>
|
||||
),
|
||||
value: "differentCard",
|
||||
value: "thawani",
|
||||
icon: (
|
||||
<div className={styles.differentCardIcon}>
|
||||
<DifferentCardIcon />
|
||||
<Image
|
||||
preview={false}
|
||||
src={"thawani.png"}
|
||||
alt="thawani"
|
||||
width={24}
|
||||
height={24}
|
||||
style={{
|
||||
position: "relative",
|
||||
bottom: 3,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
),
|
||||
hideCurrency: true,
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
import { Loader } from "components/Loader/Loader";
|
||||
import { Navigate, useParams } from "react-router-dom";
|
||||
import { useAppSelector } from "redux/hooks";
|
||||
|
||||
export const PrivateRoute = ({
|
||||
children,
|
||||
}: // permission,
|
||||
{
|
||||
children: JSX.Element;
|
||||
permission?: string;
|
||||
}) => {
|
||||
const { token, loading } = useAppSelector((state) => state.auth);
|
||||
const { subdomain } = useParams();
|
||||
|
||||
// const { data: user, isLoading: loadingUser } = useGetSignedUserInfoQuery();
|
||||
|
||||
// useEffect(() => {
|
||||
// if (user) dispatch(setUser(user));
|
||||
// }, [dispatch, user]);
|
||||
|
||||
if (loading) {
|
||||
return <Loader />;
|
||||
}
|
||||
|
||||
// let newPermissions: any[] = [];
|
||||
// // aggregate the rules from multi
|
||||
// user?.roles?.forEach(
|
||||
// (r) => (newPermissions = [...newPermissions, ...r.permissions])
|
||||
// );
|
||||
|
||||
// const userHasRequiredPermission = permission
|
||||
// ? !!newPermissions.find((p) => p.name === permission)
|
||||
// : true;
|
||||
|
||||
console.log(token);
|
||||
|
||||
|
||||
if (!token) {
|
||||
return <Navigate to={`/${subdomain}/login`} />;
|
||||
}
|
||||
|
||||
// if (token && !userHasRequiredPermission) {
|
||||
// return <AccessDenied />; // build your won access denied page (sth like 404)
|
||||
// }
|
||||
|
||||
return children;
|
||||
};
|
||||
@@ -380,7 +380,11 @@ const orderSlice = createSlice({
|
||||
// Clear all cart data from localStorage
|
||||
if (typeof window !== "undefined") {
|
||||
Object.values(CART_STORAGE_KEYS)
|
||||
.filter((key) => key !== CART_STORAGE_KEYS.ORDER_TYPE)
|
||||
.filter(
|
||||
(key) =>
|
||||
key !== CART_STORAGE_KEYS.ORDER_TYPE &&
|
||||
key !== CART_STORAGE_KEYS.PAYMENT_METHOD,
|
||||
)
|
||||
.forEach((key) => {
|
||||
localStorage.removeItem(key);
|
||||
});
|
||||
@@ -767,23 +771,21 @@ export const {
|
||||
|
||||
// Tax calculation helper functions
|
||||
const calculateTaxAmount = (
|
||||
state: RootState,
|
||||
amount: number,
|
||||
tax: Tax,
|
||||
): number => {
|
||||
const percentage = parseFloat(tax.percentage);
|
||||
return (((state.order.restaurant?.vat || 0) + percentage) * amount) / 100;
|
||||
return (percentage * amount) / 100;
|
||||
};
|
||||
|
||||
const calculateTotalTax = (
|
||||
state: RootState,
|
||||
subtotal: number,
|
||||
taxes: Tax[],
|
||||
): number => {
|
||||
return taxes
|
||||
.filter((tax) => tax.is_active === 1)
|
||||
.reduce(
|
||||
(total, tax) => total + calculateTaxAmount(state, subtotal, tax),
|
||||
(total, tax) => total + calculateTaxAmount(subtotal, tax),
|
||||
0,
|
||||
);
|
||||
};
|
||||
@@ -822,21 +824,31 @@ export const selectLoyaltyItems = createSelector([selectOrderItems], (items) =>
|
||||
items.filter((item) => item.isHasLoyalty),
|
||||
);
|
||||
|
||||
// Tax selectors
|
||||
export const selectTaxes = (state: RootState) => state.order.restaurant.taxes;
|
||||
export const totalTaxes = (state: RootState) => {
|
||||
const taxes = selectTaxes(state);
|
||||
const totalTaxes = taxes?.reduce((total, tax) => total + parseFloat(tax.percentage), 0) || 0;
|
||||
const vat = (state.order.restaurant?.vat || 0);
|
||||
return totalTaxes + vat;
|
||||
};
|
||||
|
||||
export const selectHighestPricedLoyaltyItem = (state: RootState) => {
|
||||
const loyaltyItems = selectLoyaltyItems(state);
|
||||
if (loyaltyItems.length === 0) return null;
|
||||
|
||||
return loyaltyItems.reduce((highest, current) =>
|
||||
const highestItem = loyaltyItems.reduce((highest, current) =>
|
||||
current.price > highest.price ? current : highest,
|
||||
);
|
||||
)
|
||||
return highestItem;
|
||||
};
|
||||
|
||||
export const selectDiscountTotal = (state: RootState) =>
|
||||
(state.order.discount.value / 100) * selectCartTotal(state) +
|
||||
export const selectDiscountTotal = (state: RootState) => {
|
||||
return (state.order.discount.value / 100) * selectCartTotal(state) +
|
||||
(state.order.useLoyaltyPoints &&
|
||||
state.order.restaurant?.is_loyalty_enabled === 1
|
||||
? selectHighestPricedLoyaltyItem(state)?.price || 0
|
||||
? ((selectHighestPricedLoyaltyItem(state)?.price || 0))
|
||||
: 0);
|
||||
}
|
||||
|
||||
export const selectLoyaltyValidation = createSelector(
|
||||
[(state: RootState) => state.order.useLoyaltyPoints, selectLoyaltyItems],
|
||||
@@ -851,18 +863,18 @@ export const selectLoyaltyValidation = createSelector(
|
||||
}),
|
||||
);
|
||||
|
||||
// Tax selectors
|
||||
export const selectTaxes = (state: RootState) => state.order.restaurant.taxes;
|
||||
|
||||
|
||||
export const selectTaxAmount = (state: RootState) => {
|
||||
const subtotal = selectCartTotal(state) - selectDiscountTotal(state);
|
||||
const taxes = selectTaxes(state);
|
||||
return calculateTotalTax(state, subtotal, taxes || []);
|
||||
return calculateTotalTax(subtotal, taxes || []);
|
||||
};
|
||||
|
||||
export const selectGrandTotal = (state: RootState) => {
|
||||
const totalDiscount = selectDiscountTotal(state);
|
||||
const taxAmount = selectTaxAmount(state);
|
||||
const vatAmount = ((state.order.restaurant?.vat || 0) / 100) * (selectCartTotal(state) - selectDiscountTotal(state));
|
||||
const subtotal = selectCartTotal(state);
|
||||
const deliveryFee =
|
||||
state.order.orderType === OrderType.Delivery
|
||||
@@ -870,12 +882,13 @@ export const selectGrandTotal = (state: RootState) => {
|
||||
: 0;
|
||||
|
||||
return (
|
||||
subtotal +
|
||||
taxAmount -
|
||||
subtotal -
|
||||
totalDiscount +
|
||||
deliveryFee -
|
||||
state.order.splitBillAmount +
|
||||
Number(state.order.tip)
|
||||
taxAmount +
|
||||
vatAmount -
|
||||
deliveryFee
|
||||
// state.order.splitBillAmount +
|
||||
// Number(state.order.tip)
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
export function useDetectBarcode() {
|
||||
const [barcode, setBarcode] = useState<string>("");
|
||||
const [lastBarcode, setLastBarcode] = useState<string>("");
|
||||
|
||||
useEffect(() => {
|
||||
let interval: any;
|
||||
|
||||
const handleKeydown = (evt: any) => {
|
||||
if (interval) clearInterval(interval);
|
||||
|
||||
if (evt.code === "Enter") {
|
||||
if (barcode) handleBarcode(barcode);
|
||||
setBarcode("");
|
||||
return;
|
||||
}
|
||||
|
||||
if (evt.key !== "Shift") setBarcode((prev) => prev + evt.key);
|
||||
|
||||
interval = setInterval(() => setBarcode(""), 20);
|
||||
};
|
||||
|
||||
// Adding the event listener when the component mounts
|
||||
document.addEventListener("keydown", handleKeydown);
|
||||
|
||||
// Cleanup the event listener when the component unmounts
|
||||
return () => {
|
||||
document.removeEventListener("keydown", handleKeydown);
|
||||
if (interval) clearInterval(interval);
|
||||
};
|
||||
}, [barcode]);
|
||||
|
||||
const handleBarcode = (scannedBarcode: string) => {
|
||||
setLastBarcode(scannedBarcode);
|
||||
};
|
||||
|
||||
return lastBarcode;
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
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,
|
||||
};
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
import { useAppSelector } from "redux/hooks";
|
||||
|
||||
export function useTranslations() {
|
||||
const { isRTL } = useAppSelector((state) => state.locale);
|
||||
const nameProp = isRTL ? "arabic_name" : "name";
|
||||
return [nameProp] as const ;
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
import { Layout } from 'antd';
|
||||
|
||||
const { Footer } = Layout;
|
||||
|
||||
type FooterNavProps = React.HTMLAttributes<HTMLDivElement>;
|
||||
|
||||
const FooterNav = ({ ...others }: FooterNavProps) => {
|
||||
return (
|
||||
<Footer {...others}>AntD Dashboard © 2023 Created by Design Sparx</Footer>
|
||||
);
|
||||
};
|
||||
|
||||
export default FooterNav;
|
||||
@@ -1,70 +0,0 @@
|
||||
import { BugFilled } from "@ant-design/icons";
|
||||
import { MenuProps } from "antd";
|
||||
import { PATHS } from "utils/constants";
|
||||
|
||||
// import WarehouseIcon from "components/Icons/WarehouseIcon";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Link } from "react-router-dom";
|
||||
|
||||
export default function useSidebarItems() {
|
||||
type MenuItem = Required<MenuProps>["items"][number] & {
|
||||
permission: string;
|
||||
children?: MenuItem[];
|
||||
};
|
||||
const { t } = useTranslation();
|
||||
// const [isAuth] = useAuth();
|
||||
const getItem = (
|
||||
label: React.ReactNode,
|
||||
key: React.Key,
|
||||
permission?: string,
|
||||
icon?: React.ReactNode,
|
||||
children?: MenuItem[],
|
||||
type?: "group"
|
||||
): MenuItem => {
|
||||
return {
|
||||
key,
|
||||
icon,
|
||||
children,
|
||||
label,
|
||||
type,
|
||||
permission,
|
||||
} as MenuItem;
|
||||
};
|
||||
|
||||
// Recursive function to filter items based on permissions
|
||||
const getGrantedItems = (items: any[]): MenuItem[] => {
|
||||
return items
|
||||
.filter(() => true)// Filter out items without permission
|
||||
.map((item: any) => {
|
||||
if (item.children) {
|
||||
// Recursively filter children
|
||||
return {
|
||||
...item,
|
||||
children: getGrantedItems(item.children),
|
||||
};
|
||||
}
|
||||
return item;
|
||||
});
|
||||
};
|
||||
|
||||
const items: MenuProps["items"] = [
|
||||
getItem(
|
||||
t("menu"),
|
||||
PATHS.menu,
|
||||
undefined,
|
||||
<div style={{ marginTop: 10 }}>
|
||||
<Link style={{}} to={PATHS.menu}>
|
||||
<BugFilled className="icon-container pos-icon" />
|
||||
</Link>
|
||||
</div>
|
||||
),
|
||||
];
|
||||
|
||||
// if we have a menu with empty children after applying "getGrantedItems"
|
||||
// we going to remove it
|
||||
const grantedItems = getGrantedItems(items).filter(
|
||||
(i) => (i.children && i.children.length !== 0) || !i.children
|
||||
);
|
||||
|
||||
return grantedItems;
|
||||
}
|
||||
@@ -33,7 +33,6 @@ export default function CartMobileTabletLayout({
|
||||
const { items, orderType } = useAppSelector(selectCart);
|
||||
const { isRTL } = useAppSelector((state) => state.locale);
|
||||
const { subdomain } = useParams();
|
||||
const { pickup_type } = useAppSelector((state) => state.order.restaurant);
|
||||
const { isMobile, isTablet } = useBreakPoint();
|
||||
const getResponsiveClass = () => (isTablet ? "tablet" : "mobile");
|
||||
const navigate = useNavigate();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Button, FormInstance, Layout } from "antd";
|
||||
import { Button, FormInstance, Layout, message } from "antd";
|
||||
import { selectCart, updateSplitBillAmount } from "features/order/orderSlice";
|
||||
import { OrderType } from "pages/checkout/hooks/types.ts";
|
||||
import { useCallback, useMemo, useState } from "react";
|
||||
@@ -18,7 +18,7 @@ type SplitWay = "customAmount" | "equality" | "payForItems" | null;
|
||||
export default function CheckoutButton({ form }: { form: FormInstance }) {
|
||||
const dispatch = useAppDispatch();
|
||||
const { t } = useTranslation();
|
||||
const { orderType, giftDetails } = useAppSelector(selectCart);
|
||||
const { orderType, giftDetails, items } = useAppSelector(selectCart);
|
||||
const { handleCreateOrder } = useOrder();
|
||||
const { handleCreateGiftAmount } = useGidtAmount();
|
||||
const [selectedSplitWay, setSelectedSplitWay] = useState<SplitWay>(null);
|
||||
@@ -56,7 +56,11 @@ export default function CheckoutButton({ form }: { form: FormInstance }) {
|
||||
) {
|
||||
handleCreateGiftAmount();
|
||||
} else {
|
||||
if (items.length > 0) {
|
||||
handleCreateOrder();
|
||||
} else {
|
||||
message.error(t("checkout.noItems"));
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
|
||||
@@ -75,8 +75,6 @@ export default function useOrder() {
|
||||
duration: 0,
|
||||
});
|
||||
|
||||
console.log(order?.roomNumber);
|
||||
|
||||
createOrder({
|
||||
phone: mobilenumber || phone || giftDetails?.senderPhone,
|
||||
comment: specialRequest,
|
||||
@@ -152,8 +150,9 @@ export default function useOrder() {
|
||||
);
|
||||
else {
|
||||
const redirectMessageKey = "order-redirect-loader";
|
||||
|
||||
if (
|
||||
orderType === OrderType.Gift &&
|
||||
localStorage.getItem("fascano_payment_method")?.toString() === '"thawani"' &&
|
||||
mutationResult.data?.result?.orderID
|
||||
) {
|
||||
message.loading({
|
||||
|
||||
@@ -13,13 +13,14 @@ import "react-phone-input-2/lib/style.css";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import { useSendOtpMutation } from "redux/api/auth";
|
||||
import { useGetRestaurantDetailsQuery } from "redux/api/others";
|
||||
import { useAppSelector } from "redux/hooks";
|
||||
import { useAppDispatch, useAppSelector } from "redux/hooks";
|
||||
import { colors, DisabledColor, ProGray1 } from "ThemeConstants";
|
||||
import { default_image } from "utils/constants";
|
||||
import styles from "./login.module.css";
|
||||
import { Layout } from "antd";
|
||||
import ProHeader from "components/ProHeader/ProHeader";
|
||||
import useBreakPoint from "hooks/useBreakPoint";
|
||||
import { updateCustomerName } from "features/order/orderSlice";
|
||||
|
||||
export default function LoginPage() {
|
||||
const { t } = useTranslation();
|
||||
@@ -32,7 +33,7 @@ export default function LoginPage() {
|
||||
skip: !subdomain,
|
||||
});
|
||||
const { isTablet } = useBreakPoint();
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
// const [phone, setPhone] = useState<string>("");
|
||||
const [selectedDate, setSelectedDate] = useState<string>("");
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
@@ -113,6 +114,10 @@ export default function LoginPage() {
|
||||
height: 50,
|
||||
fontSize: 14,
|
||||
}}
|
||||
onBlur={(e) => {
|
||||
dispatch(updateCustomerName(e.target.value));
|
||||
}}
|
||||
disabled={isLoading}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { StarFilled } from "@ant-design/icons";
|
||||
import { Button, Image, Select, Space } from "antd";
|
||||
import { Button, Image, message, Select, Space } from "antd";
|
||||
import { FloatingButton } from "components/FloatingButton/FloatingButton";
|
||||
import LogoContainerIcon from "components/Icons/LogoContainerIcon";
|
||||
import ImageWithFallback from "components/ImageWithFallback";
|
||||
@@ -12,6 +12,7 @@ import { OrderType } from "pages/checkout/hooks/types.ts";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useParams } from "react-router-dom";
|
||||
import {
|
||||
useCallWaiterMutation,
|
||||
useGetMenuQuery,
|
||||
useGetRestaurantDetailsQuery,
|
||||
} from "redux/api/others";
|
||||
@@ -37,7 +38,7 @@ import { OrderTypesBottomSheet } from "components/CustomBottomSheet/OrderTypesBo
|
||||
function MenuPage() {
|
||||
const { subdomain } = useParams();
|
||||
const { isRTL } = useAppSelector((state) => state.locale);
|
||||
const { orderType } = useAppSelector((state) => state.order);
|
||||
const { orderType, table } = useAppSelector((state) => state.order);
|
||||
const { token } = useAppSelector((state) => state.auth);
|
||||
const { t } = useTranslation();
|
||||
const { data: restaurant, isLoading: isLoadingRestaurant } =
|
||||
@@ -56,6 +57,7 @@ function MenuPage() {
|
||||
const [isOpeningTimesOpen, setIsOpeningTimesOpen] = useState(false);
|
||||
const [isOrderTypesOpen, setIsOrderTypesOpen] = useState(false);
|
||||
const orderTypeOptions = enumToSelectOptions(OrderType, t, "orderTypes");
|
||||
const [callWaiter] = useCallWaiterMutation()
|
||||
|
||||
// Automatically load restaurant taxes when restaurant data is available
|
||||
useRestaurant(restaurant);
|
||||
@@ -203,6 +205,19 @@ function MenuPage() {
|
||||
backgroundColor: "#EBEBEC",
|
||||
border: "none",
|
||||
}}
|
||||
onClick={() => {
|
||||
if (table) callWaiter({
|
||||
table_id: table,
|
||||
call_reason: "call_waiter",
|
||||
}).unwrap().then(() => {
|
||||
message.success(t("menu.calledWaiterSuccess"));
|
||||
}).catch((err) => {
|
||||
message.error(err.data.message);
|
||||
})
|
||||
else
|
||||
message.info(t("menu.selectYourTable"));
|
||||
}
|
||||
}
|
||||
>
|
||||
<ProText
|
||||
style={{
|
||||
|
||||
@@ -37,13 +37,14 @@ import NewRateIcon from "components/Icons/order/NewRateIcon";
|
||||
import NoteIcon from "components/Icons/NoteIcon";
|
||||
import SuccessIcon from "components/Icons/SuccessIcon";
|
||||
import { SplitBillParticipantsBottomSheet } from "./components/SplitBillParticipantsBottomSheet";
|
||||
import { OrderType } from "pages/checkout/hooks/types";
|
||||
|
||||
export default function OrderPage() {
|
||||
const { t } = useTranslation();
|
||||
const { orderId } = useParams();
|
||||
const navigate = useNavigate();
|
||||
const { isRTL } = useAppSelector((state) => state.locale);
|
||||
const { restaurant } = useAppSelector((state) => state.order);
|
||||
const { restaurant, orderType } = useAppSelector((state) => state.order);
|
||||
const hasRefetchedRef = useRef(false);
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [isRateOrderOpen, setIsRateOrderOpen] = useState(false);
|
||||
@@ -429,7 +430,7 @@ export default function OrderPage() {
|
||||
<Stepper statuses={orderDetails?.status} />
|
||||
</div>
|
||||
|
||||
{!hasClosedStatus && (
|
||||
{!hasClosedStatus && orderType === OrderType.DineIn && (
|
||||
<div className={styles.orderNotes}>
|
||||
<NoteIcon className={styles.noteIcon} />
|
||||
<div
|
||||
@@ -464,7 +465,7 @@ export default function OrderPage() {
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{hasClosedStatus && (
|
||||
{hasClosedStatus && orderType === OrderType.DineIn && (
|
||||
<div className={styles.orderNotesClosed}>
|
||||
<SuccessIcon className={styles.noteIcon} />
|
||||
<ProText
|
||||
|
||||
@@ -6,6 +6,7 @@ import { useTranslation } from "react-i18next";
|
||||
import { Extra as ExtraType } from "utils/types/appTypes";
|
||||
import styles from "../product.module.css";
|
||||
import { formatPriceUi } from "utils/helpers";
|
||||
import { useAppSelector } from "redux/hooks";
|
||||
|
||||
export default function Extra({
|
||||
extrasList,
|
||||
@@ -17,7 +18,7 @@ export default function Extra({
|
||||
setSelectedExtras: Dispatch<SetStateAction<ExtraType[]>>;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const { restaurant } = useAppSelector((state) => state.order);
|
||||
return (
|
||||
<>
|
||||
{extrasList.length > 0 && (
|
||||
@@ -46,7 +47,7 @@ export default function Extra({
|
||||
return {
|
||||
value: value.id.toString(),
|
||||
label: value.name,
|
||||
price: `+${formatPriceUi(value.price, 3)}`,
|
||||
price: `+${formatPriceUi(value.price, restaurant.currency_decimals ?? 3)}`,
|
||||
};
|
||||
})}
|
||||
value={selectedExtras.map((ex) => ex.id.toString())}
|
||||
|
||||
@@ -21,7 +21,7 @@ export default function Variants({
|
||||
const { isRTL } = useAppSelector((state) => state.locale);
|
||||
const { t } = useTranslation();
|
||||
const { isDesktop } = useBreakPoint();
|
||||
|
||||
const { restaurant } = useAppSelector((state) => state.order);
|
||||
// Determine variant levels based on options array length
|
||||
const variantLevels = useMemo(() => {
|
||||
if (!variantsList || variantsList.length === 0) return [];
|
||||
@@ -170,7 +170,7 @@ export default function Variants({
|
||||
value: value,
|
||||
label: value,
|
||||
price: variant
|
||||
? `+${formatPriceUi(variant.price, 3)}`
|
||||
? `+${formatPriceUi(variant.price, restaurant.currency_decimals ?? 3)}`
|
||||
: "",
|
||||
};
|
||||
})}
|
||||
|
||||
@@ -10,16 +10,13 @@ import styles from "./restaurant.module.css";
|
||||
import RestaurantServices from "./RestaurantServices";
|
||||
|
||||
// Import the Client Component for localStorage handling
|
||||
import Ads1 from "components/Ads/Ads1";
|
||||
import { OrderDetailsBottomSheet } from "components/CustomBottomSheet/orderDetailsSheet/OrderDetailsBottomSheet.tsx";
|
||||
import { Loader } from "components/Loader/Loader";
|
||||
import {
|
||||
CART_STORAGE_KEYS,
|
||||
updateOrderType,
|
||||
} from "features/order/orderSlice.ts";
|
||||
import useBreakPoint from "hooks/useBreakPoint";
|
||||
import { useRestaurant } from "hooks/useRestaurant";
|
||||
import useSwipeUp from "hooks/useSwipeUp";
|
||||
import { OrderType } from "pages/checkout/hooks/types.ts";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
REDEEM_DETAILS_URL,
|
||||
LOYALTY_HISTORY_URL,
|
||||
CREATE_GIFT_AMOUNT_URL,
|
||||
CALL_WAITER_URL,
|
||||
OPENING_TIMES_URL,
|
||||
} from "utils/constants";
|
||||
|
||||
@@ -221,6 +222,13 @@ export const branchApi = baseApi.injectEndpoints({
|
||||
return response.result;
|
||||
},
|
||||
}),
|
||||
callWaiter: builder.mutation({
|
||||
query: (body: any) => ({
|
||||
url: CALL_WAITER_URL,
|
||||
method: "POST",
|
||||
body,
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
});
|
||||
export const {
|
||||
@@ -239,4 +247,5 @@ export const {
|
||||
useGetLoyaltyHistoryQuery,
|
||||
useCreateGiftAmountMutation,
|
||||
useGetOpeningTimesQuery,
|
||||
useCallWaiterMutation
|
||||
} = branchApi;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { Grid } from "antd";
|
||||
import { Loader } from "components/Loader/Loader";
|
||||
import { PrivateRoute } from "components/privateRoute/PrivateRoute";
|
||||
import { PublicRoute } from "components/publicRoute/PublicRoute";
|
||||
import { AppLayout } from "layouts";
|
||||
import HeaderMenuDrawer from "layouts/app/HeaderMenuDrawer";
|
||||
@@ -9,7 +8,7 @@ import CardDetailsPage from "pages/CardDetails/CardDetails";
|
||||
import CartPage from "pages/cart/page";
|
||||
import CheckoutPage from "pages/checkout/page";
|
||||
import EGiftCardsPage from "pages/EGiftCards/EGiftCards";
|
||||
import { Error400Page, ErrorPage } from "pages/errors";
|
||||
import { ErrorPage } from "pages/errors";
|
||||
import LoginPage from "pages/login/page";
|
||||
import MenuPage from "pages/menu/page";
|
||||
import OrderDetails from "pages/order/components/OrderDetails";
|
||||
@@ -123,9 +122,9 @@ export const router = createHashRouter([
|
||||
element: (
|
||||
<PageWrapper
|
||||
children={
|
||||
<PrivateRoute>
|
||||
<PublicRoute>
|
||||
<OrdersPage />
|
||||
</PrivateRoute>
|
||||
</PublicRoute>
|
||||
}
|
||||
/>
|
||||
),
|
||||
@@ -195,20 +194,6 @@ export const router = createHashRouter([
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: "errors",
|
||||
errorElement: <ErrorPage />,
|
||||
children: [
|
||||
{
|
||||
path: "400",
|
||||
element: (
|
||||
<PrivateRoute>
|
||||
<Error400Page />
|
||||
</PrivateRoute>
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
|
||||
export default router;
|
||||
|
||||
@@ -113,3 +113,4 @@ export const REDEEM_DETAILS_URL = `${BASE_URL}gift/getGiftOrderByVoucherCode`;
|
||||
export const LOYALTY_HISTORY_URL = `${BASE_URL}loyaltyHistory`;
|
||||
export const CREATE_GIFT_AMOUNT_URL = `${BASE_URL}gift/addGiftAmount`;
|
||||
export const OPENING_TIMES_URL = `${BASE_URL}restaurant/getWorkingHours`;
|
||||
export const CALL_WAITER_URL = `${BASE_URL}call_waiter`;
|
||||
@@ -534,6 +534,7 @@ export interface RestaurantDetails {
|
||||
closingTime: string;
|
||||
isOpened: boolean;
|
||||
isFav: boolean;
|
||||
currency_decimals: number;
|
||||
}
|
||||
|
||||
export interface Banner {
|
||||
|
||||
Reference in New Issue
Block a user