Compare commits

..

19 Commits

Author SHA1 Message Date
e86966062a update loyalty discount calculations 2026-01-17 21:22:24 +03:00
3fe0c68526 clean code 2026-01-17 20:03:50 +03:00
5cb681c4a8 apply call waiter api 2026-01-17 12:14:19 +03:00
f97b83062c apply restaurant decimails formating & fix tax calcualtions 2026-01-17 11:44:04 +03:00
69425580d6 apply "thawani" way payment 2026-01-17 11:05:00 +03:00
8083e9ec96 apply items validation and fix taxes list viewing 2026-01-17 10:04:34 +03:00
4dfa08d26c room number value sending 2026-01-15 14:28:00 +03:00
788b05d6f4 enhance openening times UI styles 2026-01-15 11:51:19 +03:00
0ce2d320a8 intergrate openening times & show extras 2026-01-15 11:43:36 +03:00
046773cb8b fixes 2026-01-15 11:08:54 +03:00
e50d2dfd4c delivery: fix map updating and enhance UI 2026-01-15 07:49:58 +03:00
ab5867b0cb implment send amount gift api 2026-01-15 07:22:48 +03:00
68a12a4796 fix time range 2026-01-15 06:36:21 +03:00
5d08498f8c transaltiosn & add clear coupon discount 2026-01-15 06:34:20 +03:00
d0de05cfb0 Revert "If token exists: browser back navigates to /${subdomain}"
This reverts commit 093d4279d9.
2026-01-15 06:26:50 +03:00
8a4e6691d4 When a user with a token navigates to /:subdomain/login or /:subdomain/otp, they are redirected to /${subdomain}. The redirect uses replace to avoid adding a history entry. 2026-01-15 06:26:35 +03:00
adc98200cb room & office number fix sending 2026-01-15 06:22:09 +03:00
26a16fa8d7 remove title 2026-01-15 06:15:56 +03:00
093d4279d9 If token exists: browser back navigates to /${subdomain} 2026-01-15 06:15:48 +03:00
43 changed files with 888 additions and 648 deletions

BIN
public/thawani.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -170,7 +170,17 @@
"justXMorePurchasesToUnlockYourFREEItem": "فقط {{cups}} أكثر للفتح الوجبة المجانية!",
"youreJustXCupsAwayFromYourNextReward": "🎉 أنت فقط {{cups}} أكثر للحصول على المكافأة التالية!",
"callWaiter": "اتصل بالرادير",
"balance": "الرصيد"
"balance": "الرصيد",
"closed": "مغلق",
"sunday": "الأحد",
"monday": "الإثنين",
"tuesday": "الثلاثاء",
"wednesday": "الأربعاء",
"thursday": "الخميس",
"friday": "الجمعة",
"saturday": "السبت",
"selectYourTable": "اختر طاولتك",
"calledWaiterSuccess": "تم اتصال الرادير بنجاح"
},
"cart": {
"addSpecialRequestOptional": "إضافة طلب خاص (اختياري)",
@@ -279,8 +289,8 @@
"cannotSelectPastDate": "لا يمكنك اختيار تاريخ سابق. يرجى اختيار اليوم أو تاريخ مستقبلي.",
"checkRequiredFields": "يرجى التحقق من الحقول المطلوبة",
"loyalty": "ولاء",
"vatTax": "ضريبة (القيمة المضافة)",
"otherTaxes": "ضريبة (أخرى)"
"vat": "ضريبة القيمة المضافة ({{value}}%))",
"otherTaxes": "{{name}} ({{value}}%)"
},
"checkout": {
"addCarDetails": "إضافة تفاصيل السيارة",
@@ -325,7 +335,9 @@
"change": "تغيير",
"pickup": "استلام",
"setPickupTime": "تحديد وقت الاستلام",
"carPlateNumber": "رقم لوحة السيارة"
"carPlateNumber": "رقم لوحة السيارة",
"noItems": "لا يوجد عناصر في السلة",
"thawani": "ثواني"
},
"address": {
"title": "العنوان",
@@ -377,7 +389,9 @@
"gotIt": "فهمت",
"howItWorksDescription": "يمكنك إرسال هدية إلى أي شخص عبر التطبيق. يمكنك إرسال هدية إلى أي شخص عبر التطبيق. يمكنك إرسال هدية إلى أي شخص عبر التطبيق. يمكنك إرسال هدية إلى أي شخص عبر التطبيق. يمكنك إرسال هدية إلى أي شخص عبر التطبيق.",
"senderEmail": "البريد الإلكتروني المرسل",
"save": "حفظ"
"save": "حفظ",
"pleaseEnterRoomNumber": "يرجى إدخال رقم الغرفة",
"pleaseEnterOfficeNumber": "يرجى إدخال رقم المكتب"
},
"login": {
"singup/Login": "الدخول / التسجيل",

View File

@@ -182,7 +182,17 @@
"justXMorePurchasesToUnlockYourFREEItem": "Just {{cups}} more purchases to unlock your FREE item!",
"youreJustXCupsAwayFromYourNextReward": "🎉 You're just {{cups}} stamps away from your next reward!",
"callWaiter": "Call Waiter",
"balance": "Balance"
"balance": "Balance",
"closed": "Closed",
"sunday": "Sunday",
"monday": "Monday",
"tuesday": "Tuesday",
"wednesday": "Wednesday",
"thursday": "Thursday",
"friday": "Friday",
"saturday": "Saturday",
"selectYourTable": "Select your table",
"calledWaiterSuccess": "Waiter has been called successfully"
},
"cart": {
"remainingToPay": "Remaining to Pay",
@@ -296,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",
@@ -344,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",
@@ -397,7 +409,9 @@
"gotIt": "Got It",
"howItWorksDescription": "The gifted amount will be credited directly to your friend's wallet in the app. The recipient can use the amount to book a session of their choice within the app. The gifted amount is non-refundable and can only be used for booking sessions.",
"senderEmail": "Sender Email",
"save": "Save"
"save": "Save",
"pleaseEnterRoomNumber": "Please enter room number",
"pleaseEnterOfficeNumber": "Please enter office number"
},
"login": {
"singup/Login": "Sing up / Login",

View File

@@ -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)

View File

@@ -1,4 +1,3 @@
import { Status, Wrapper } from "@googlemaps/react-wrapper";
import { useEffect, useRef, useState } from "react";
@@ -74,7 +73,7 @@ function MapComponent({
});
setMap(newMap);
console.log('Map initialized successfully');
console.log("Map initialized successfully");
// If we have an initial location, add a marker
if (initialLocation) {
@@ -148,16 +147,14 @@ function MapComponent({
geocoder.geocode({ location: { lat, lng } }, (results, status) => {
if (status === "OK" && results && results[0]) {
const address = results[0].formatted_address;
console.log(
"Initial marker drag - calling onLocationSelect:",
{ lat, lng, address },
);
console.log("Initial marker drag - calling onLocationSelect:", {
lat,
lng,
address,
});
onLocationSelect?.(lat, lng, address);
} else {
console.log(
"Geocoding failed on initial marker drag:",
status,
);
console.log("Geocoding failed on initial marker drag:", status);
// Fallback: use coordinates as address if geocoding fails
const fallbackAddress = `Location: ${lat.toFixed(6)}, ${lng.toFixed(6)}`;
console.log(
@@ -201,19 +198,29 @@ function MapComponent({
// Get address from coordinates
const geocoder = new google.maps.Geocoder();
geocoder.geocode({ location: { lat, lng } }, (results, status) => {
if (status === "OK" && results && results[0]) {
const address = results[0].formatted_address;
console.log('Marker drag - calling onLocationSelect:', { lat, lng, address });
onLocationSelect?.(lat, lng, address);
} else {
console.log('Geocoding failed on drag:', status);
// Fallback: use coordinates as address if geocoding fails
const fallbackAddress = `Location: ${lat.toFixed(6)}, ${lng.toFixed(6)}`;
console.log('Using fallback address for drag:', fallbackAddress);
onLocationSelect?.(lat, lng, fallbackAddress);
}
});
geocoder.geocode(
{ location: { lat, lng } },
(results, status) => {
if (status === "OK" && results && results[0]) {
const address = results[0].formatted_address;
console.log("Marker drag - calling onLocationSelect:", {
lat,
lng,
address,
});
onLocationSelect?.(lat, lng, address);
} else {
console.log("Geocoding failed on drag:", status);
// Fallback: use coordinates as address if geocoding fails
const fallbackAddress = `Location: ${lat.toFixed(6)}, ${lng.toFixed(6)}`;
console.log(
"Using fallback address for drag:",
fallbackAddress,
);
onLocationSelect?.(lat, lng, fallbackAddress);
}
},
);
}
});
@@ -222,13 +229,17 @@ function MapComponent({
geocoder.geocode({ location: { lat, lng } }, (results, status) => {
if (status === "OK" && results && results[0]) {
const address = results[0].formatted_address;
console.log('Map click - calling onLocationSelect:', { lat, lng, address });
console.log("Map click - calling onLocationSelect:", {
lat,
lng,
address,
});
onLocationSelect?.(lat, lng, address);
} else {
console.log('Geocoding failed on click:', status);
console.log("Geocoding failed on click:", status);
// Fallback: use coordinates as address if geocoding fails
const fallbackAddress = `Location: ${lat.toFixed(6)}, ${lng.toFixed(6)}`;
console.log('Using fallback address:', fallbackAddress);
console.log("Using fallback address:", fallbackAddress);
onLocationSelect?.(lat, lng, fallbackAddress);
}
});
@@ -238,6 +249,57 @@ function MapComponent({
}
}, [map, onLocationSelect, readOnly, initialLocation]);
// Update map center and marker when initialLocation changes (after map is initialized)
useEffect(() => {
if (map && initialLocation) {
// Update map center
map.setCenter({
lat: initialLocation.lat,
lng: initialLocation.lng,
});
map.setZoom(15);
// Update or create marker
if (markerRef.current) {
// Update existing marker position
markerRef.current.setPosition({
lat: initialLocation.lat,
lng: initialLocation.lng,
});
} else {
// Create new marker if it doesn't exist
const newMarker = new google.maps.Marker({
position: { lat: initialLocation.lat, lng: initialLocation.lng },
map: map,
draggable: !readOnly,
});
markerRef.current = newMarker;
// Add drag end listener if not readOnly
if (!readOnly && onLocationSelect) {
newMarker.addListener("dragend", () => {
const position = newMarker.getPosition();
if (position) {
const lat = position.lat();
const lng = position.lng();
const geocoder = new google.maps.Geocoder();
geocoder.geocode({ location: { lat, lng } }, (results, status) => {
if (status === "OK" && results && results[0]) {
const address = results[0].formatted_address;
onLocationSelect(lat, lng, address);
} else {
const fallbackAddress = `Location: ${lat.toFixed(6)}, ${lng.toFixed(6)}`;
onLocationSelect(lat, lng, fallbackAddress);
}
});
}
});
}
}
}
}, [map, initialLocation, readOnly, onLocationSelect]);
return (
<div
ref={mapRef}

View File

@@ -1,22 +1,58 @@
import { Button } from "antd";
import { Card } from "antd";
import { useTranslation } from "react-i18next";
import { ProBottomSheet } from "../ProBottomSheet/ProBottomSheet";
import ProText from "components/ProText";
import ProTitle from "components/ProTitle";
import { useAppSelector } from "redux/hooks";
import { useGetOpeningTimesQuery } from "redux/api/others";
import { useMemo } from "react";
import TimeIcon from "components/Icons/order/TimeIcon";
interface OpeningTimesBottomSheetProps {
isOpen: boolean;
onClose: () => void;
}
const textStyle: React.CSSProperties = {
fontWeight: 400,
fontStyle: "Regular",
fontSize: 14,
lineHeight: "140%",
letterSpacing: "0%",
marginBottom: 4,
// Helper function to format time (HH:mm to 12h format)
const formatTime = (time: string | null | undefined): string => {
if (!time) return "";
// If already in 12h format (contains AM/PM), return as is
if (time.includes("AM") || time.includes("PM")) {
return time;
}
// Parse 24h format (HH:mm)
const [hours, minutes] = time.split(":");
const hour24 = parseInt(hours, 10);
if (isNaN(hour24)) return time;
const hour12 = hour24 % 12 || 12;
const ampm = hour24 >= 12 ? "PM" : "AM";
return `${hour12}:${minutes} ${ampm}`;
};
// Helper function to get time ranges for a day
const getDayTimes = (
openingTimes: any,
dayIndex: number,
): { shift1: string | null; shift2: string | null } => {
if (!openingTimes) {
return { shift1: null, shift2: null };
}
const from1 = openingTimes[`${dayIndex}_from` as keyof typeof openingTimes];
const to1 = openingTimes[`${dayIndex}_to` as keyof typeof openingTimes];
const from2 = openingTimes[`2_${dayIndex}_from` as keyof typeof openingTimes];
const to2 = openingTimes[`2_${dayIndex}_to` as keyof typeof openingTimes];
const shift1 =
from1 && to1 ? `${formatTime(from1)} - ${formatTime(to1)}` : null;
const shift2 =
from2 && to2 ? `${formatTime(from2)} - ${formatTime(to2)}` : null;
return { shift1, shift2 };
};
export function OpeningTimesBottomSheet({
@@ -26,6 +62,12 @@ export function OpeningTimesBottomSheet({
const { t } = useTranslation();
const { isRTL } = useAppSelector((state) => state.locale);
const { restaurant } = useAppSelector((state) => state.order);
const { data: openingTimes } = useGetOpeningTimesQuery(
restaurant?.restautantId,
{
skip: !restaurant?.restautantId,
},
);
const days = [
"sunday",
@@ -37,7 +79,11 @@ export function OpeningTimesBottomSheet({
"saturday",
];
const todayIndex = new Date().getDay();
const todayDay = days[todayIndex];
// Memoize day times to avoid recalculating on every render
const dayTimes = useMemo(() => {
return days.map((_, index) => getDayTimes(openingTimes, index));
}, [openingTimes]);
return (
<ProBottomSheet
@@ -46,170 +92,205 @@ export function OpeningTimesBottomSheet({
title={t("menu.openingTimes")}
showCloseButton={false}
initialSnap={1}
height={445}
snapPoints={[445]}
height={600}
snapPoints={[600]}
>
<div
style={{
display: "flex",
flexDirection: "column",
padding: 20,
padding: "20px 0",
gap: 20,
maxHeight: "calc(600px - 120px)",
overflowY: "auto",
}}
>
<ProTitle level={5}>{t("menu.address")}</ProTitle>
<ProText type="secondary">
{isRTL ? restaurant?.addressAR : restaurant?.address}
</ProText>
{/* Address Section */}
<Card
bordered={false}
style={{
backgroundColor: "#f8f9fa",
borderRadius: 12,
boxShadow: "none",
}}
bodyStyle={{ padding: "16px" }}
>
<div
style={{
display: "flex",
flexDirection: "column",
gap: 8,
}}
>
<ProTitle
level={5}
style={{
marginBottom: 0,
fontSize: 16,
fontWeight: 600,
color: "#333",
}}
>
{t("menu.address")}
</ProTitle>
<ProText
type="secondary"
style={{
fontSize: 14,
lineHeight: "20px",
color: "#666",
}}
>
{isRTL ? restaurant?.addressAR : restaurant?.address}
</ProText>
</div>
</Card>
<ProTitle level={5}>{t("menu.openingTimes")}</ProTitle>
<div style={{ display: "flex", justifyContent: "space-between" }}>
<ProText
type="secondary"
{/* Opening Times Section */}
<div style={{ display: "flex", flexDirection: "column", gap: 16 }}>
<div
style={{
...textStyle,
fontWeight: todayDay === "sunday" ? 700 : 400,
display: "flex",
alignItems: "center",
gap: 8,
marginBottom: 4,
}}
>
sunday
</ProText>
<ProText
type="secondary"
<TimeIcon color="#333" />
<ProTitle
level={5}
style={{
marginBottom: 0,
fontSize: 16,
fontWeight: 600,
color: "#333",
}}
>
{t("menu.openingTimes")}
</ProTitle>
</div>
<div
style={{
...textStyle,
fontWeight: todayDay === "sunday" ? 700 : 400,
display: "flex",
flexDirection: "column",
gap: 8,
}}
>
10:00 AM to 10:00 PM
</ProText>
</div>
<div style={{ display: "flex", justifyContent: "space-between" }}>
<ProText
type="secondary"
style={{
...textStyle,
fontWeight: todayDay === "monday" ? 700 : 400,
}}
>
monday
</ProText>
<ProText
type="secondary"
style={{
...textStyle,
fontWeight: todayDay === "monday" ? 700 : 400,
}}
>
10:00 AM to 10:00 PM
</ProText>
</div>
<div style={{ display: "flex", justifyContent: "space-between"}}>
<ProText
type="secondary"
style={{
...textStyle,
fontWeight: todayDay === "tuesday" ? 700 : 400,
}}
>
tuesday
</ProText>
<ProText
type="secondary"
style={{
...textStyle,
fontWeight: todayDay === "tuesday" ? 700 : 400,
}}
>
10:00 AM to 10:00 PM
</ProText>
</div>
<div style={{ display: "flex", justifyContent: "space-between" }}>
<ProText
type="secondary"
style={{
...textStyle,
fontWeight: todayDay === "wednesday" ? 700 : 400,
}}
>
wednesday
</ProText>
<ProText
type="secondary"
style={{
...textStyle,
fontWeight: todayDay === "wednesday" ? 700 : 400,
}}
>
10:00 AM to 10:00 PM
</ProText>
</div>
<div style={{ display: "flex", justifyContent: "space-between" }}>
<ProText
type="secondary"
style={{
...textStyle,
fontWeight: todayDay === "thursday" ? 700 : 400,
}}
>
thursday
</ProText>
<ProText
type="secondary"
style={{
...textStyle,
fontWeight: todayDay === "thursday" ? 700 : 400,
}}
>
10:00 AM to 10:00 PM
</ProText>
</div>
<div style={{ display: "flex", justifyContent: "space-between" }}>
<ProText
type="secondary"
style={{
...textStyle,
fontWeight: todayDay === "friday" ? 700 : 400,
}}
>
friday
</ProText>
<ProText
type="secondary"
style={{
...textStyle,
fontWeight: todayDay === "friday" ? 700 : 400,
}}
>
10:00 AM to 10:00 PM
</ProText>
</div>
<div style={{ display: "flex", justifyContent: "space-between" }}>
<ProText
type="secondary"
style={{
...textStyle,
fontWeight: todayDay === "saturday" ? 700 : 400,
}}
>
saturday
</ProText>
<ProText
type="secondary"
style={{
...textStyle,
fontWeight: todayDay === "saturday" ? 700 : 400,
}}
>
10:00 AM to 10:00 PM
</ProText>
{days.map((day, index) => {
const isToday = index === todayIndex;
const { shift1, shift2 } = dayTimes[index];
const hasShifts = shift1 || shift2;
return (
<Card
key={day}
bordered
style={{
borderRadius: 12,
borderColor: isToday ? "#1890ff" : "#e8e8e8",
borderWidth: isToday ? 2 : 1,
backgroundColor: isToday ? "#f0f8ff" : "#ffffff",
boxShadow: isToday
? "0 2px 8px rgba(24, 144, 255, 0.15)"
: "0 1px 2px rgba(0, 0, 0, 0.05)",
transition: "all 0.2s ease",
}}
bodyStyle={{ padding: "14px 16px" }}
>
<div
style={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
gap: 16,
}}
>
<div
style={{
display: "flex",
alignItems: "center",
gap: 12,
flex: 1,
}}
>
{isToday && (
<div
style={{
width: 6,
height: 6,
borderRadius: "50%",
backgroundColor: "#1890ff",
flexShrink: 0,
}}
/>
)}
<ProText
style={{
fontSize: 15,
fontWeight: isToday ? 600 : 500,
color: isToday ? "#1890ff" : "#333",
textTransform: "capitalize",
}}
>
{t(`menu.${day}`)}
</ProText>
</div>
<div
style={{
display: "flex",
flexDirection: "column",
alignItems: isRTL ? "flex-start" : "flex-end",
gap: shift2 ? 4 : 0,
flex: 1,
}}
>
{hasShifts ? (
<>
<ProText
style={{
fontSize: 14,
fontWeight: isToday ? 500 : 400,
color: isToday ? "#1890ff" : "#666",
lineHeight: "20px",
}}
>
{shift1}
</ProText>
{shift2 && (
<ProText
style={{
fontSize: 14,
fontWeight: isToday ? 500 : 400,
color: isToday ? "#1890ff" : "#666",
lineHeight: "20px",
}}
>
{shift2}
</ProText>
)}
</>
) : (
<ProText
type="secondary"
style={{
fontSize: 14,
color: "#999",
fontStyle: "italic",
}}
>
{t("menu.closed")}
</ProText>
)}
</div>
</div>
</Card>
);
})}
</div>
</div>
</div>
<Button
type="primary"
style={{ width: "100%", height: 48 }}
onClick={onClose}
>
{t("menu.close")}
</Button>
</ProBottomSheet>
);
}

View File

@@ -17,10 +17,10 @@ const NoteIcon = ({ className, onClick }: NoteIconType) => {
<path
d="M12 16H12.008M12 8V13M3.23005 7.913L7.91005 3.23C8.06005 3.08 8.26005 3 8.48005 3H15.53C15.74 3 15.95 3.08 16.1 3.23L20.77 7.903C20.92 8.053 21 8.253 21 8.473V15.527C21 15.737 20.92 15.947 20.77 16.097L16.1 20.77C15.95 20.92 15.75 21 15.53 21H8.47005C8.36456 21.0011 8.2599 20.9814 8.16208 20.9419C8.06425 20.9025 7.9752 20.844 7.90005 20.77L3.23005 16.097C3.15602 16.0218 3.09759 15.9328 3.05812 15.835C3.01865 15.7371 2.99891 15.6325 3.00005 15.527V8.473C3.00005 8.263 3.08005 8.053 3.23005 7.903V7.913Z"
stroke="#3D3B4A"
stroke-width="1.5"
stroke-miterlimit="10"
stroke-linecap="round"
stroke-linejoin="round"
strokeWidth="1.5"
strokeMiterlimit="10"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);

View File

@@ -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>
);

View File

@@ -17,7 +17,7 @@ const RateIcon = ({ className, onClick }: RateIconType) => {
<path
d="M26.4351 131.263C18.2009 129.563 9.45442 127.066 4.20797 120.496C-0.552335 114.535 -1.36302 105.7 2.23265 98.9722C7.4747 89.1632 20.3229 84.8319 24.4402 74.5003C27.401 67.0706 24.9679 58.7427 24.4263 50.7633C23.852 42.2992 25.5683 33.5602 30.0859 26.379C34.6035 19.1981 42.0585 13.7282 50.4398 12.4131C62.6515 10.4973 74.6822 17.2329 87.0409 17.0031C101.49 16.7343 113.941 7.17211 127.741 2.8807C135.732 0.395575 144.272 -0.303884 152.561 0.847569C160.267 1.91822 168.283 4.99429 172.479 11.5457C176.514 17.8476 176.178 25.8983 178.116 33.1264C180.159 40.7481 184.913 47.6136 191.329 52.2072C195.925 55.498 201.301 57.6369 205.675 61.218C209.591 64.4246 212.539 68.6589 215.048 73.0551C218.27 78.7035 220.892 84.8722 221.249 91.3654C221.937 103.879 213.897 115.647 203.429 122.538C192.961 129.429 180.37 132.237 167.99 134.188C158.175 135.734 148.289 136.826 138.373 137.458C128.55 138.083 118.471 138.613 108.638 137.94C105.646 137.735 102.889 136.912 99.9902 136.244C96.2772 135.389 92.5503 135.76 88.7856 135.959C78.9527 136.48 69.0879 136.405 59.2637 135.738C49.4395 135.072 39.6552 133.813 29.9831 131.967C28.7985 131.741 27.616 131.506 26.4351 131.263Z"
fill="#FFB700"
fill-opacity="0.12"
fillOpacity="0.12"
/>
<path
d="M149.572 150H69.6144C67.3832 150 65.5576 148.174 65.5576 145.943V145.546C65.5576 143.315 67.3832 141.489 69.6144 141.489H149.572C151.803 141.489 153.629 143.315 153.629 145.546V145.943C153.629 148.174 151.803 150 149.572 150Z"
@@ -42,22 +42,22 @@ const RateIcon = ({ className, onClick }: RateIconType) => {
<path
d="M142.231 13.9854C142.231 10.7734 139.603 8.14526 136.391 8.14526H134.134C114.797 23.8846 96.1683 40.5151 79.0111 58.5438C78.5128 59.0675 78.0168 59.5935 77.5195 60.1188V89.5343C80.7173 86.54 83.9063 83.5363 87.0804 80.5174C88.8163 78.8666 90.5472 77.2104 92.2788 75.5549C108.944 60.8335 125.605 46.108 142.231 31.3423L142.231 13.9854Z"
fill="#FFB700"
fill-opacity="0.12"
fillOpacity="0.12"
/>
<path
d="M95.8987 119.961C93.5809 122.338 91.2756 124.728 88.9967 127.149C85.3554 131.016 81.8398 134.99 78.3311 138.969C79.3493 140.693 81.2222 141.859 83.3584 141.859H99.6052C101.571 139.656 103.547 137.461 105.543 135.286C111.156 129.168 116.897 123.176 122.743 117.288C129.246 111.064 135.741 104.833 142.23 98.5942V76.4097C137.105 80.4905 132.061 84.6713 127.162 89.03C116.203 98.7802 105.84 109.178 95.8987 119.961Z"
fill="#FFB700"
fill-opacity="0.12"
fillOpacity="0.12"
/>
<path
d="M142.231 116.867C136.361 122.2 130.579 127.631 124.896 133.164C121.948 136.035 119.027 138.934 116.13 141.858H130.067C132.121 139.96 134.17 138.057 136.206 136.14C138.217 134.247 140.225 132.35 142.231 130.453V116.867Z"
fill="#FFB700"
fill-opacity="0.12"
fillOpacity="0.12"
/>
<path
d="M114.568 8.14502H100.676C92.7524 14.6636 85.003 21.3827 77.5195 28.407V37.3576C83.8437 32.2819 90.1625 27.196 96.5533 22.2048C102.559 17.5142 108.55 12.811 114.568 8.14502Z"
fill="#FFB700"
fill-opacity="0.12"
fillOpacity="0.12"
/>
<path
d="M111.138 11.8819C111.138 12.6409 110.523 13.2561 109.764 13.2561C109.005 13.2561 108.39 12.6409 108.39 11.8819C108.39 11.1229 109.005 10.5073 109.764 10.5073C110.523 10.5073 111.138 11.1226 111.138 11.8819Z"

View File

@@ -9,6 +9,7 @@ interface InputCardProps {
placeholder: string;
value: string;
required?: boolean;
reuireqMessage?: string;
}
export default function InputCard({
@@ -17,6 +18,7 @@ export default function InputCard({
placeholder,
value,
required = false,
reuireqMessage = "",
}: InputCardProps) {
const dispatch = useAppDispatch();
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
@@ -24,10 +26,10 @@ export default function InputCard({
};
return (
<>
<ProInputCard title={title} dividerStyle={{ margin: "5px 0 0 0" }}>
<ProInputCard title={title}>
<Form.Item
name={name}
rules={[{ required }]}
rules={[{ required, message: reuireqMessage }]}
>
<Input
placeholder={placeholder}

View File

@@ -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;

View File

@@ -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,13 +26,13 @@ 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);
const isHasLoyaltyGift =
(restaurant?.loyalty_stamps ?? 0) -
(restaurant?.customer_loyalty_points ?? 0) <=
(restaurant?.customer_loyalty_points ?? 0) <=
0;
const titlesStyle: CSSProperties = {
@@ -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 (
<>
@@ -93,9 +113,9 @@ export default function OrderSummary() {
}}
>
{isHasLoyaltyGift &&
useLoyaltyPoints &&
highestLoyaltyItem &&
restaurant?.is_loyalty_enabled === 1 ? (
useLoyaltyPoints &&
highestLoyaltyItem &&
restaurant?.is_loyalty_enabled === 1 ? (
<Tag
color="green"
style={{
@@ -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}>
<ProText type="secondary" style={titlesStyle}>
{t("cart.otherTaxes")}
</ProText>
<ArabicPrice
price={taxAmount - vat || 0}
textStyle={{ ...titlesStyle, color: "#434E5C" }}
/>
</div>
)}
{orderType !== OrderType.Redeem &&
taxesList.map((tax) => (
<div key={tax.id} className={styles.summaryRow}>
<ProText type="secondary" style={titlesStyle}>
{t("cart.otherTaxes", {
name: tax.name || tax.name_local,
value: tax.percentage,
})}
</ProText>
<ArabicPrice
price={tax.amount}
textStyle={{ ...titlesStyle, color: "#434E5C" }}
/>
</div>
))}
{orderType !== OrderType.Redeem && splitBillAmount > 0 && (
<div className={styles.summaryRow}>
<ProText type="secondary" style={titlesStyle}>

View File

@@ -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,

View File

@@ -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;
};

View File

@@ -0,0 +1,22 @@
import { Loader } from "components/Loader/Loader";
import { Navigate, useParams } from "react-router-dom";
import { useAppSelector } from "redux/hooks";
export const PublicRoute = ({
children,
}: {
children: JSX.Element;
}) => {
const { token, loading } = useAppSelector((state) => state.auth);
const { subdomain } = useParams();
if (loading) {
return <Loader />;
}
if (token) {
return <Navigate to={`/${subdomain}`} replace />;
}
return children;
};

View File

@@ -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);
});
@@ -702,7 +706,8 @@ const orderSlice = createSlice({
}
},
updateOrder(state, action: PayloadAction<any>) {
state.order = action.payload;
state.order = { ...(state.order || {}), ...action.payload };
if (typeof window !== "undefined") {
localStorage.setItem(
CART_STORAGE_KEYS.ORDER,
@@ -766,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,
);
};
@@ -821,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) +
(state.order.useLoyaltyPoints &&
state.order.restaurant?.is_loyalty_enabled === 1
? selectHighestPricedLoyaltyItem(state)?.price || 0
: 0);
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))
: 0);
}
export const selectLoyaltyValidation = createSelector(
[(state: RootState) => state.order.useLoyaltyPoints, selectLoyaltyItems],
@@ -850,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
@@ -869,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)
);
};

View File

@@ -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;
}

View File

@@ -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,
};
}

View File

@@ -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 ;
}

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -64,30 +64,70 @@ export default function AddressPage() {
name="loginForm"
form={form}
>
<div style={{ display: "flex", gap: 10 }}>
<div style={{ display: "flex", flexDirection: "column", gap: 10 }}>
<div style={{ display: "flex", gap: 10 }}>
<Form.Item
name="floor"
rules={[{ required: true, message: "" }]}
style={{ width: "100%" }}
>
<Input
placeholder={t("address.floor")}
style={{
fontSize: 14,
height: 50,
}}
autoFocus={false}
/>
</Form.Item>{" "}
<Form.Item
name="apt"
rules={[{ required: true, message: "" }]}
style={{ width: "100%" }}
>
<Input
placeholder={t("address.aptNumber")}
style={{
fontSize: 14,
height: 50,
}}
autoFocus={false}
/>
</Form.Item>
</div>
<Form.Item
label={t("floor")}
name="floor"
name="street"
rules={[{ required: true, message: "" }]}
style={{ width: "100%" }}
>
<Input
placeholder={t("address.floor")}
placeholder={t("address.street")}
style={{
fontSize: 14,
height: 50,
}}
autoFocus={false}
/>
</Form.Item>{" "}
</Form.Item>
<Form.Item
label={t("address.aptNumber")}
name="apt"
name="additional"
rules={[{ required: true, message: "" }]}
style={{ width: "100%" }}
>
<Input
placeholder={t("address.aptNumber")}
placeholder={t("address.additionalDirection")}
style={{
fontSize: 14,
height: 50,
}}
autoFocus={false}
/>
</Form.Item>
<Form.Item
name="addressLabel"
rules={[{ required: true, message: "" }]}
>
<Input
placeholder={t("address.addressLabel")}
style={{
fontSize: 14,
height: 50,
@@ -96,51 +136,6 @@ export default function AddressPage() {
/>
</Form.Item>
</div>
<Form.Item
label={t("address.street")}
name="street"
rules={[{ required: true, message: "" }]}
>
<Input
placeholder={t("address.street")}
style={{
fontSize: 14,
height: 50,
}}
autoFocus={false}
/>
</Form.Item>
<Form.Item
label={t("address.additionalDirection")}
name="additional"
rules={[{ required: true, message: "" }]}
>
<Input
placeholder={t("address.additionalDirection")}
style={{
fontSize: 14,
height: 50,
}}
autoFocus={false}
/>
</Form.Item>
<ProPhoneInput label={t("address.mobileNumber")} propName="phone" />
<Form.Item
label={t("address.addressLabel")}
name="addressLabel"
rules={[{ required: true, message: "" }]}
>
<Input
placeholder={t("address.addressLabel")}
style={{
fontSize: 14,
height: 50,
}}
autoFocus={false}
/>
</Form.Item>
</Form>
</Card>
</div>

View File

@@ -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();

View File

@@ -1,4 +1,5 @@
import { Button, Form, Input, message } from "antd";
import { CloseCircleOutlined } from "@ant-design/icons";
import { CouponBottomSheet } from "components/CustomBottomSheet/CouponBottomSheet";
import { CouponDialog } from "components/CustomBottomSheet/CouponDialog";
import CouponHeartIcon from "components/Icons/cart/CouponHeart.tsx";
@@ -21,12 +22,14 @@ import { colors } from "ThemeConstants";
export default function CouponCard() {
const { t } = useTranslation();
const dispatch = useAppDispatch();
const { restaurant } = useAppSelector((state) => state.order);
const { restaurant, discount } = useAppSelector((state) => state.order);
const { coupon } = useAppSelector(selectCart);
const { isDesktop } = useBreakPoint();
const [getDiscount] = useGetDiscountMutation();
const [isCouponOpen, setIsCouponOpen] = useState(false);
const isDiscountApplied = discount.value > 0 || discount.isDiscount || discount.isGift;
const handleCouponSave = (value: string) => {
getDiscount({
discountCode: value,
@@ -52,6 +55,18 @@ export default function CouponCard() {
setIsCouponOpen(false);
};
const handleClearDiscount = () => {
dispatch(updateCoupon(""));
dispatch(
updateDiscount({
value: 0,
isGift: false,
isDiscount: false,
}),
);
message.success(t("cart.couponRemoved") || "Coupon removed");
};
return (
<>
<ProInputCard
@@ -85,33 +100,45 @@ export default function CouponCard() {
size="large"
autoFocus={false}
style={{ padding: "7px 11px", height: 48 }}
value={coupon}
onChange={(e) => {
dispatch(updateCoupon(e.target.value));
}}
suffix={
<Button
style={{
width: 100,
height: 32,
borderRadius: 100,
backgroundColor: "#333333",
color: "white",
fontWeight: 500,
fontStyle: "Medium",
fontSize: 14,
lineHeight: "140%",
letterSpacing: "0%"
}}
onClick={() => handleCouponSave(coupon)}
icon={<CouponHeartIcon className={styles.couponApplyIcon} />}
iconPlacement="end"
>
<ProText
style={{ position: "relative", top: -1, color: "white" }}
isDiscountApplied ? (
<CloseCircleOutlined
onClick={handleClearDiscount}
style={{
fontSize: 18,
color: "#999",
cursor: "pointer",
}}
/>
) : (
<Button
style={{
width: 100,
height: 32,
borderRadius: 100,
backgroundColor: "#333333",
color: "white",
fontWeight: 500,
fontStyle: "Medium",
fontSize: 14,
lineHeight: "140%",
letterSpacing: "0%"
}}
onClick={() => handleCouponSave(coupon)}
icon={<CouponHeartIcon className={styles.couponApplyIcon} />}
iconPlacement="end"
>
{t("cart.apply")}
</ProText>
</Button>
<ProText
style={{ position: "relative", top: -1, color: "white" }}
>
{t("cart.apply")}
</ProText>
</Button>
)
}
/>
</Form.Item>

View File

@@ -106,6 +106,8 @@ export const AddressSummary = () => {
return null;
}
console.log(location);
return (
<>
<Card

View File

@@ -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";
@@ -10,14 +10,17 @@ import { EqualltyChoiceBottomSheet } from "pages/pay/components/splitBill/Equall
import { SplitBillChoiceBottomSheet } from "pages/pay/components/splitBill/SplitBillChoiceBottomSheet";
import { CustomAmountChoiceBottomSheet } from "pages/pay/components/splitBill/CustomAmountChoiceBottomSheet";
import { PayForYourItemsChoiceBottomSheet } from "pages/pay/components/splitBill/PayForYourItemsChoiceBottomSheet";
import useGidtAmount from "../hooks/useGidtAmount";
import { GiftType } from "components/CustomBottomSheet/GiftTypeBottomSheet";
type SplitWay = "customAmount" | "equality" | "payForItems" | null;
export default function CheckoutButton({ form }: { form: FormInstance }) {
const dispatch = useAppDispatch();
const { t } = useTranslation();
const { orderType } = useAppSelector(selectCart);
const { orderType, giftDetails, items } = useAppSelector(selectCart);
const { handleCreateOrder } = useOrder();
const { handleCreateGiftAmount } = useGidtAmount();
const [selectedSplitWay, setSelectedSplitWay] = useState<SplitWay>(null);
const [
isSplitBillChoiceBottomSheetOpen,
@@ -47,11 +50,22 @@ export default function CheckoutButton({ form }: { form: FormInstance }) {
const handlePlaceOrderClick = useCallback(async () => {
try {
await form.validateFields();
handleCreateOrder();
if (
orderType === OrderType.Gift &&
giftDetails?.giftType === GiftType.Vouchers
) {
handleCreateGiftAmount();
} else {
if (items.length > 0) {
handleCreateOrder();
} else {
message.error(t("checkout.noItems"));
}
}
} catch (error) {
console.log(error);
}
}, [handleCreateOrder, form]);
}, [handleCreateOrder, handleCreateGiftAmount, form, orderType, giftDetails]);
const shouldShowSplitBill = useMemo(
() => orderType === OrderType.DineIn,

View File

@@ -82,7 +82,7 @@ export default function PickupEstimateContent({
let hour = nextHour;
let minute = nextMinute;
for (let i = 0; i <= 96; i++) {
for (let i = 0; i < 96; i++) {
const time = dayjs().hour(hour).minute(minute).second(0);
const formatted = time.format("h:mm A");

View File

@@ -0,0 +1,82 @@
import { message } from "antd";
import {
clearCart,
selectCart,
selectGrandTotal,
} from "features/order/orderSlice";
import { useCallback } from "react";
import { useTranslation } from "react-i18next";
import { useNavigate, useParams } from "react-router-dom";
import { useCreateGiftAmountMutation } from "redux/api/others";
import { useAppDispatch, useAppSelector } from "redux/hooks";
import { PAYMENT_CONFIRMATION_URL } from "utils/constants";
export default function useGidtAmount() {
const dispatch = useAppDispatch();
const navigate = useNavigate();
const { t } = useTranslation();
const { subdomain } = useParams();
const restaurantID = localStorage.getItem("restaurantID");
const { giftDetails } = useAppSelector(selectCart);
const orderPrice = useAppSelector(selectGrandTotal);
const [createGiftAmount] = useCreateGiftAmountMutation();
const handleCreateGiftAmount = useCallback(() => {
const loadingMessageKey = "create-order-loader";
message.loading({
content: t("order.creatingOrder", {
defaultValue: "Creating order...",
}),
key: loadingMessageKey,
duration: 0,
});
createGiftAmount({
sender_name: giftDetails?.senderName,
sender_phone: giftDetails?.senderPhone,
sender_email: giftDetails?.senderEmail,
receiver_name: giftDetails?.receiverName,
receiver_phone: giftDetails?.receiverPhone,
special_message: giftDetails?.message,
keep_name_secret: giftDetails?.isSecret,
restaurant_id: restaurantID,
order_amount: orderPrice,
gift_card_id: giftDetails?.cardId,
}).then((res: unknown) => {
message.destroy(loadingMessageKey);
const mutationResult = res as {
data?: { result?: { orderID?: string } };
error?: { data?: { message?: string } };
};
if (mutationResult.error)
message.error(
mutationResult.error.data?.message || t("order.createOrderFailed"),
);
else {
const redirectMessageKey = "order-redirect-loader";
message.loading({
content: t("order.redirectingToPayment", {
defaultValue: "Redirecting to payment...",
}),
key: redirectMessageKey,
duration: 0,
});
window.location.href = `${PAYMENT_CONFIRMATION_URL}/${mutationResult.data?.result?.orderID}`;
dispatch(clearCart());
}
});
}, [
createGiftAmount,
orderPrice,
giftDetails,
restaurantID,
t,
navigate,
subdomain,
dispatch,
]);
return { handleCreateGiftAmount };
}

View File

@@ -44,7 +44,7 @@ export default function useOrder() {
giftDetails,
order,
restaurant,
} = useAppSelector(selectCart);
} = useAppSelector((state) => state.order);
const highestLoyaltyItem = useAppSelector(selectHighestPricedLoyaltyItem);
const { useLoyaltyPoints } = useAppSelector(selectCart);
@@ -93,8 +93,8 @@ export default function useOrder() {
order_item_comment: i.comment || "",
variant: (i.variant as Variant)?.id || "",
})),
office_no: order?.officeNumber || "",
room_no: order?.roomNumber || "",
office_no: order?.officeNumber,
room_no: order?.roomNumber,
...(discount.isDiscount ? { couponID: coupon } : {}),
...(discount.isGift ? { discountGiftCode: coupon } : {}),
discountAmount: discountAmount || 0,
@@ -150,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({
@@ -202,6 +203,14 @@ export default function useOrder() {
location?.lat,
location?.lng,
location?.address,
order,
pickupTime,
pickupDate,
plateCar,
pickupType,
discountAmount,
subtotal,
restaurant,
t,
navigate,
subdomain,

View File

@@ -32,10 +32,19 @@ export default function CheckoutPage() {
coupon,
customerName,
tip,
restaurant,
} = useAppSelector(selectCart);
const { token } = useAppSelector((state) => state.auth);
useEffect(() => {
form.setFieldsValue({ coupon, collectionMethod, phone, customerName, tip });
form.setFieldsValue({
coupon,
collectionMethod,
phone,
customerName,
tip,
officeNumber: order?.officeNumber,
roomNumber: order?.roomNumber,
});
}, [form, phone, coupon, collectionMethod, customerName, tip]);
return (
@@ -66,6 +75,7 @@ export default function CheckoutPage() {
placeholder={t("address.roomNo")}
value={order?.roomNumber}
required
reuireqMessage={t("address.pleaseEnterRoomNumber")}
/>
)}
{orderType === OrderType.ToOffice && (
@@ -75,6 +85,7 @@ export default function CheckoutPage() {
placeholder={t("address.officeNo")}
value={order?.officeNumber}
required
reuireqMessage={t("address.pleaseEnterOfficeNumber")}
/>
)}
{orderType === OrderType.Redeem && <VoucherSummary />}
@@ -126,9 +137,13 @@ export default function CheckoutPage() {
{/* {orderType !== OrderType.Redeem && orderType !== OrderType.Gift && (
<RewardWaiterCard />
)} */}
{orderType !== OrderType.Redeem && orderType !== OrderType.Gift && (
{orderType !== OrderType.Redeem &&
orderType !== OrderType.Gift &&
restaurant?.is_loyalty_enabled === 1 &&
(restaurant?.customer_loyalty_points ?? 0) >
(restaurant?.loyalty_stamps ?? 0) ? (
<EarnLoyaltyPointsCard />
)}
) : null}
<BriefMenuCard />
{/* <Ads1 /> */}
<OrderSummary />

View File

@@ -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>

View File

@@ -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={{

View File

@@ -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

View File

@@ -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 && (
@@ -32,7 +33,7 @@ export default function Extra({
}}
>
<ProText style={{ fontSize: "1.25rem" }}>
{t("menu.youMightAlsoLike")}
{t("menu.choose1")}
</ProText>
<ProText strong style={{ fontSize: "0.75rem" }}>
@@ -40,17 +41,13 @@ export default function Extra({
</ProText>
</div>
<ProText strong style={{ fontSize: "0.75rem" }}>
{t("menu.choose1")}
</ProText>
<div className={styles.productContainer}>
<ProCheckboxGroups
options={extrasList.map((value) => {
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())}

View File

@@ -1,7 +1,5 @@
import { Divider } from "antd";
import ProText from "components/ProText";
import { Dispatch, SetStateAction } from "react";
import { useTranslation } from "react-i18next";
import { TheExtrasGroup } from "utils/types/appTypes";
@@ -16,15 +14,13 @@ export default function ExtraGroupsContainer({
selectedExtrasByGroup: Record<number, string[]>;
setSelectedExtrasByGroup: Dispatch<SetStateAction<Record<number, string[]>>>;
}) {
const { t } = useTranslation();
return (
<>
{groupsList.length > 0 && (
<div>
<Divider style={{ margin: "0 0 16px 0" }} />
<div
{/* <div
style={{
display: "flex",
justifyContent: "space-between",
@@ -38,7 +34,7 @@ export default function ExtraGroupsContainer({
<ProText strong style={{ fontSize: "0.75rem" }}>
{t("menu.optional")}
</ProText>
</div>
</div> */}
{/* <ProText strong style={{ fontSize: "0.75rem" }}>
{t("menu.choose1")}

View File

@@ -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)}`
: "",
};
})}

View File

@@ -214,6 +214,8 @@ export default function ProductDetailPage({
);
}
console.log(product.theExtrasGroups);
return (
<div
style={{
@@ -367,14 +369,13 @@ export default function ProductDetailPage({
/>
)}
{product.theExtrasGroups.length === 0 &&
getExtras()?.length > 0 && (
<ExtraComponent
extrasList={getExtras()}
selectedExtras={selectedExtras}
setSelectedExtras={setSelectedExtras}
/>
)}
{getExtras()?.length > 0 && (
<ExtraComponent
extrasList={getExtras()}
selectedExtras={selectedExtras}
setSelectedExtras={setSelectedExtras}
/>
)}
</Space>
</div>
)}

View File

@@ -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";
@@ -41,9 +38,7 @@ export default function RestaurantPage() {
const param = useParams();
const [searchParams] = useSearchParams();
const { pathname } = useLocation();
const { orderType } = useAppSelector(
(state) => state.order,
);
const { orderType } = useAppSelector((state) => state.order);
const { isRTL } = useAppSelector((state) => state.locale);
const { data: restaurant, isLoading } = useGetRestaurantDetailsQuery(
param.subdomain,
@@ -119,7 +114,7 @@ export default function RestaurantPage() {
<Link to={`https://www.instagram.com/${restaurant?.instagram}`}>
<InstagramIcon className={styles.socialIcon} />
</Link>
<Link to="https://x.com/">
{/* <Link to="https://x.com/">
<XIcon className={styles.socialIcon} />
</Link>
<Link to="https://www.snapchat.com/">
@@ -127,7 +122,7 @@ export default function RestaurantPage() {
</Link>
<Link to="https://www.jordan.com/">
<JIcon className={styles.socialIcon} />
</Link>
</Link> */}
</div>
</div>
</div>

View File

@@ -12,6 +12,9 @@ import {
EGIFT_CARDS_URL,
REDEEM_DETAILS_URL,
LOYALTY_HISTORY_URL,
CREATE_GIFT_AMOUNT_URL,
CALL_WAITER_URL,
OPENING_TIMES_URL,
} from "utils/constants";
import { OrderDetails } from "pages/checkout/hooks/types";
@@ -24,6 +27,7 @@ import {
import { baseApi } from "./apiSlice";
import { EGiftCard } from "pages/EGiftCards/type";
import { RedeemResponse } from "pages/redeem/types";
import { OpeningTimeResponse } from "./types";
export const branchApi = baseApi.injectEndpoints({
endpoints: (builder) => ({
@@ -202,6 +206,29 @@ export const branchApi = baseApi.injectEndpoints({
return response.result.data.orders;
},
}),
createGiftAmount: builder.mutation({
query: (body: any) => ({
url: CREATE_GIFT_AMOUNT_URL,
method: "POST",
body,
}),
}),
getOpeningTimes: builder.query<OpeningTimeResponse, string | void>({
query: (restaurantId: string) => ({
url: OPENING_TIMES_URL +"/"+ restaurantId,
method: "GET",
}),
transformResponse: (response: any) => {
return response.result;
},
}),
callWaiter: builder.mutation({
query: (body: any) => ({
url: CALL_WAITER_URL,
method: "POST",
body,
}),
}),
}),
});
export const {
@@ -218,4 +245,7 @@ export const {
useGetEGiftCardsQuery,
useGetRedeemDetailsQuery,
useGetLoyaltyHistoryQuery,
useCreateGiftAmountMutation,
useGetOpeningTimesQuery,
useCallWaiterMutation
} = branchApi;

34
src/redux/api/types.ts Normal file
View File

@@ -0,0 +1,34 @@
export interface OpeningTimeResponse {
id: number;
created_at: string;
updated_at: string;
"0_from": string;
"0_to": string;
"1_from": string;
"1_to": string;
"2_from": string;
"2_to": string;
"3_from": string;
"3_to": string;
"4_from": string;
"4_to": string;
"5_from": string;
"5_to": string;
"6_from": string;
"6_to": string;
restorant_id: number;
"2_0_from": any;
"2_0_to": any;
"2_1_from": any;
"2_1_to": any;
"2_2_from": any;
"2_2_to": any;
"2_3_from": any;
"2_3_to": any;
"2_4_from": any;
"2_4_to": any;
"2_5_from": any;
"2_5_to": any;
"2_6_from": any;
"2_6_to": any;
}

View File

@@ -1,6 +1,6 @@
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";
import AddressPage from "pages/address/page";
@@ -8,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";
@@ -122,9 +122,9 @@ export const router = createHashRouter([
element: (
<PageWrapper
children={
<PrivateRoute>
<PublicRoute>
<OrdersPage />
</PrivateRoute>
</PublicRoute>
}
/>
),
@@ -132,13 +132,29 @@ export const router = createHashRouter([
},
{
path: "login",
element: <PageWrapper children={<LoginPage />} />,
element: (
<PageWrapper
children={
<PublicRoute>
<LoginPage />
</PublicRoute>
}
/>
),
errorElement: <ErrorPage />,
},
{
path: "otp",
element: <PageWrapper children={<OtpPage />} />,
element: (
<PageWrapper
children={
<PublicRoute>
<OtpPage />
</PublicRoute>
}
/>
),
errorElement: <ErrorPage />,
},
{
@@ -178,20 +194,6 @@ export const router = createHashRouter([
},
],
},
{
path: "errors",
errorElement: <ErrorPage />,
children: [
{
path: "400",
element: (
<PrivateRoute>
<Error400Page />
</PrivateRoute>
),
},
],
},
]);
export default router;

View File

@@ -111,3 +111,6 @@ export const DISCOUNT_URL = `${BASE_URL}getDiscount`;
export const EGIFT_CARDS_URL = `${BASE_URL}gift/cards`;
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`;

View File

@@ -534,6 +534,7 @@ export interface RestaurantDetails {
closingTime: string;
isOpened: boolean;
isFav: boolean;
currency_decimals: number;
}
export interface Banner {