working on gift flow

This commit is contained in:
2025-10-20 00:57:07 +03:00
parent 6214e2c0f5
commit a131c9147a
19 changed files with 137 additions and 62 deletions

View File

@@ -45,8 +45,8 @@ export function GiftBottomSheet({
title={t("address.giftDetails")}
showCloseButton={false}
initialSnap={1}
height={"90vh"}
snapPoints={["90vh"]}
height={755}
snapPoints={[755]}
>
<Form
layout="vertical"
@@ -114,6 +114,7 @@ export function GiftBottomSheet({
autoFocus={false}
/>
</Form.Item>
<Form.Item
name="senderPhone"
label={t("address.senderPhone")}
@@ -130,6 +131,22 @@ export function GiftBottomSheet({
/>
</Form.Item>
<Form.Item
name="senderEmail"
label={t("address.senderEmail")}
rules={[{ required: true, message: "" }]}
colon={false}
>
<Input
placeholder={t("address.senderEmail")}
size="large"
style={{
fontSize: 14,
}}
autoFocus={false}
/>
</Form.Item>
<Form.Item
name="isSecret"
rules={[{ required: true, message: "" }]}

View File

@@ -17,8 +17,8 @@ const CancelIcon = ({ className, onClick }: CancelIconType) => {
<path
d="M13.6666 5.68425L7.99998 8.83239M7.99998 8.83239L2.33331 5.68425M7.99998 8.83239L8 15.1657M14 9.16575L14 6.12671C14 5.89828 14 5.78407 13.9663 5.6822C13.9366 5.59208 13.8879 5.50936 13.8236 5.43957C13.7509 5.36068 13.651 5.30521 13.4514 5.19428L8.51802 2.45353C8.32895 2.3485 8.23442 2.29598 8.1343 2.27539C8.0457 2.25716 7.95431 2.25716 7.8657 2.27539C7.76559 2.29598 7.67105 2.3485 7.48198 2.45353L2.54865 5.19428C2.34897 5.30521 2.24912 5.36068 2.17642 5.43957C2.11211 5.50936 2.06343 5.59208 2.03366 5.6822C2 5.78407 2 5.89828 2 6.12671V11.5381C2 11.7665 2 11.8808 2.03366 11.9826C2.06343 12.0727 2.11211 12.1555 2.17642 12.2253C2.24912 12.3041 2.34897 12.3596 2.54865 12.4706L7.48198 15.2113C7.67105 15.3163 7.76559 15.3688 7.8657 15.3894C7.95431 15.4077 8.0457 15.4077 8.1343 15.3894C8.23442 15.3688 8.32895 15.3163 8.51802 15.2113L8.66667 15.1287M5 3.83241L11 7.16575M11 11.4991L14.3333 14.8324M14.3333 11.4991L11 14.8324"
stroke="#FF6F59"
stroke-linecap="round"
stroke-linejoin="round"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);

View File

@@ -10,10 +10,9 @@ import { colors, ProGray1 } from "../../ThemeConstants";
import ProInputCard from "../ProInputCard/ProInputCard";
import styles from "./PaymentMethods.module.css";
const PaymentMethods = () => {
const { t } = useTranslation();
const { paymentMethod } = useAppSelector(selectCart);
const { paymentMethod, orderType } = useAppSelector(selectCart);
const dispatch = useAppDispatch();
const options: {
@@ -23,11 +22,15 @@ const PaymentMethods = () => {
icon?: React.ReactNode;
style?: React.CSSProperties;
}[] = [
{
label: t("checkout.creditDebitCard"),
value: "creditDebitCard",
price: t("checkout.expiresIn") + ":12/26",
},
...(orderType !== "gift"
? [
{
label: t("checkout.creditDebitCard"),
value: "creditDebitCard",
price: t("checkout.expiresIn") + ":12/26",
},
]
: []),
{
label: t("checkout.differentCard"),

View File

@@ -218,7 +218,7 @@ export function ProBottomSheet({
const contentStyle: React.CSSProperties = {
flex: 1,
overflow: "auto",
padding: "20px",
padding: "0 20px",
backgroundColor: themeName === "dark" ? "#0a0a0a" : "#ffffff",
color: themeName === "dark" ? "#ffffff" : "#000000",
};

View File

@@ -30,6 +30,7 @@ export interface GiftDetailsType {
message: string;
senderName: string;
senderPhone: string;
senderEmail: string;
isSecret: boolean;
}
@@ -50,6 +51,7 @@ interface CartState {
collectionMethod: string;
phone: string;
paymentMethod: string;
orderType: string;
}
// localStorage keys
@@ -69,6 +71,7 @@ export const CART_STORAGE_KEYS = {
COLLECTION_METHOD: 'fascano_collection_method',
PHONE: 'fascano_phone',
PAYMENT_METHOD: 'fascano_payment_method',
ORDER_TYPE: 'fascano_order_type',
} as const;
// Utility functions for localStorage
@@ -101,6 +104,7 @@ const initialState: CartState = {
collectionMethod: getFromLocalStorage(CART_STORAGE_KEYS.COLLECTION_METHOD, ""),
phone: getFromLocalStorage(CART_STORAGE_KEYS.PHONE, ""),
paymentMethod: getFromLocalStorage(CART_STORAGE_KEYS.PAYMENT_METHOD, ""),
orderType: getFromLocalStorage(CART_STORAGE_KEYS.ORDER_TYPE, ""),
};
const orderSlice = createSlice({
@@ -164,7 +168,7 @@ const orderSlice = createSlice({
state.paymentMethod = "";
// Clear all cart data from localStorage
if (typeof window !== 'undefined') {
Object.values(CART_STORAGE_KEYS).forEach(key => {
Object.values(CART_STORAGE_KEYS).filter(key => key !== CART_STORAGE_KEYS.ORDER_TYPE).forEach(key => {
localStorage.removeItem(key);
});
}
@@ -296,6 +300,14 @@ const orderSlice = createSlice({
localStorage.setItem(CART_STORAGE_KEYS.PAYMENT_METHOD, JSON.stringify(state.paymentMethod));
}
},
updateOrderType(state, action: PayloadAction<string>) {
state.orderType = action.payload;
// Sync to localStorage
if (typeof window !== 'undefined') {
localStorage.setItem(CART_STORAGE_KEYS.ORDER_TYPE, JSON.stringify(state.orderType));
}
},
},
});
@@ -320,6 +332,7 @@ export const {
updateCollectionMethod,
updatePhone,
updatePaymentMethod,
updateOrderType,
reset,
} = orderSlice.actions;

View File

@@ -1,13 +1,13 @@
import { updateSpecialRequest, selectCart } from "features/order/orderSlice.ts";
import { message, Input } from "antd";
import { RightOutlined } from "@ant-design/icons";
import { Input } from "antd";
import ProInputCard from "components/ProInputCard/ProInputCard.tsx";
import { selectCart, updateSpecialRequest } from "features/order/orderSlice.ts";
import useBreakPoint from "hooks/useBreakPoint.ts";
import { BottomSheet } from "pages/cart/components/specialRequest/BottomSheet.tsx";
import { Dialog } from "pages/cart/components/specialRequest/Dialog.tsx";
import { useState } from "react";
import { useTranslation } from "react-i18next";
import { useAppDispatch, useAppSelector } from "redux/hooks.ts";
import ProInputCard from "components/ProInputCard/ProInputCard.tsx";
import { RightOutlined } from "@ant-design/icons";
import { BottomSheet } from "pages/cart/components/specialRequest/BottomSheet.tsx";
import { useState } from "react";
import useBreakPoint from "hooks/useBreakPoint.ts";
import { Dialog } from "pages/cart/components/specialRequest/Dialog.tsx";
import styles from "./SpecialRequestCard.module.css";
export default function SpecialRequestCard() {
@@ -20,7 +20,6 @@ export default function SpecialRequestCard() {
const handleSpecialRequestSave = (value: string) => {
dispatch(updateSpecialRequest(value));
message.success(t("cart.specialRequest") + " " + t("updatedSuccessfully"));
};
const handleSpecialRequestClose = () => {

View File

@@ -19,7 +19,7 @@ export const AddressSummary = () => {
const dispatch = useAppDispatch();
const { location } = useAppSelector(selectCart);
const [isMapBottomSheetOpen, setIsMapBottomSheetOpen] = useState(false);
const orderType = useMemo(() => localStorage.getItem("orderType"), []); // Default to delivery for now
const { orderType } = useAppSelector(selectCart) // Default to delivery for now
const handleLocationSave = useCallback(
(locationString: string) => {

View File

@@ -11,7 +11,7 @@ import styles from "../../address/address.module.css";
export default function BriefMenu() {
const { tables, items } = useAppSelector(selectCart);
const { t } = useTranslation();
const orderType = useMemo(() => localStorage.getItem("orderType"), []);
const { orderType } = useAppSelector(selectCart)
const menuItems = useMemo(
() =>

View File

@@ -1,13 +1,15 @@
import { Button, FormInstance } from "antd";
import { selectCart } from "features/order/orderSlice";
import { useCallback, useMemo } from "react";
import { useTranslation } from "react-i18next";
import { useNavigate, useParams } from "react-router-dom";
import { useAppSelector } from "redux/hooks";
import styles from "../../address/address.module.css";
import useOrder from "../hooks/useOrder";
export default function CheckoutButton({ form }: { form: FormInstance }) {
const { t } = useTranslation();
const orderType = useMemo(() => localStorage.getItem("orderType"), []);
const { orderType } = useAppSelector(selectCart)
const navigate = useNavigate();
const { handleCreateOrder } = useOrder();
const { id } = useParams();

View File

@@ -23,8 +23,7 @@ export const GiftDetails = () => {
const { giftDetails } = useAppSelector(selectCart);
const [isOfficeBottomSheetOpen, setIsOfficeBottomSheetOpen] = useState(false);
const [isInfoButtonSheetOpen, setIsInfoButtonSheetOpen] = useState(false);
const orderType = useMemo(() => localStorage.getItem("orderType"), []);
const { orderType } = useAppSelector(selectCart);
const handleGiftDetailsSave = useCallback(
(giftDetailsData: GiftDetailsType) => {

View File

@@ -20,7 +20,7 @@ export const OfficeDetails = () => {
const { officeDetails } = useAppSelector(selectCart);
const [isOfficeBottomSheetOpen, setIsOfficeBottomSheetOpen] = useState(false);
const orderType = useMemo(() => localStorage.getItem("orderType"), []);
const { orderType } = useAppSelector(selectCart)
const handleOfficeDetailsSave = useCallback(
(officeDetailsData: OfficeDetailsType) => {

View File

@@ -5,9 +5,9 @@ import GoldenHouseIcon from "components/Icons/address/GoldenHouseIcon";
import RoomServiceIcon from "components/Icons/address/RoomServiceIcon";
import ProText from "components/ProText";
import {
RoomDetailsType,
selectCart,
updateRoomDetails,
RoomDetailsType,
selectCart,
updateRoomDetails,
} from "features/order/orderSlice";
import { useCallback, useMemo, useState } from "react";
import { useTranslation } from "react-i18next";
@@ -20,7 +20,7 @@ export const RoomDetails = () => {
const { roomDetails } = useAppSelector(selectCart);
const [isRoomBottomSheetOpen, setIsRoomBottomSheetOpen] = useState(false);
const orderType = useMemo(() => localStorage.getItem("orderType"), []);
const { orderType } = useAppSelector(selectCart)
const handleRoomDetailsSave = useCallback(
(roomDetailsData: RoomDetailsType) => {

View File

@@ -7,21 +7,30 @@ import { useAppDispatch, useAppSelector } from "redux/hooks";
export default function PhoneCard() {
const { t } = useTranslation();
const dispatch = useAppDispatch();
const { phone } = useAppSelector(selectCart);
const { phone, orderType } = useAppSelector(selectCart);
return (
<>
<ProInputCard title={t("checkout.phoneNumber")}>
<Form.Item name="phone" required rules={[{ required: true, message: t("checkout.pleaseEnterPhoneNumber") }]}>
<Input
placeholder={t("checkout.phoneNumber")}
size="large"
autoFocus={false}
style={{ padding: "7px 11px", height: 50, borderRadius: 888 }}
value={phone}
onChange={(e) => dispatch(updatePhone(e.target.value))}
/>
</Form.Item>
</ProInputCard>
</>
orderType !== "gift" && (
<>
<ProInputCard title={t("checkout.phoneNumber")}>
<Form.Item
name="phone"
required
rules={[
{ required: true, message: t("checkout.pleaseEnterPhoneNumber") },
]}
>
<Input
placeholder={t("checkout.phoneNumber")}
size="large"
autoFocus={false}
style={{ padding: "7px 11px", height: 50, borderRadius: 888 }}
value={phone}
onChange={(e) => dispatch(updatePhone(e.target.value))}
/>
</Form.Item>
</ProInputCard>
</>
)
);
}

View File

@@ -5,11 +5,12 @@ import { useTranslation } from "react-i18next";
import { useNavigate, useParams } from "react-router-dom";
import { useCreateOrderMutation } from "redux/api/others";
import { useAppDispatch, useAppSelector } from "redux/hooks";
import { PAYMENT_CONFIRMATION_URL } from "utils/constants";
import { Customer } from "../../otp/types";
export default function useOrder() {
const dispatch = useAppDispatch();
const router = useNavigate();
const navigate = useNavigate();
const { t } = useTranslation();
const { id } = useParams();
const restaurantID = localStorage.getItem("restaurantID");
@@ -25,19 +26,22 @@ export default function useOrder() {
phone,
estimateTime,
officeDetails,
orderType,
giftDetails,
} = useAppSelector(selectCart);
const [createOrder] = useCreateOrderMutation();
const handleCreateOrder = useCallback(() => {
createOrder({
phone: mobilenumber || phone,
phone: mobilenumber || phone || giftDetails?.senderPhone,
couponID: coupon,
discountAmount: 0,
comment: specialRequest,
timeslot: "",
table_id: tables,
deliveryType: "table",
deliveryType: orderType,
dineType: orderType,
type: "table-pickup",
user_id: id,
restorant_id: restaurantID,
@@ -61,13 +65,33 @@ export default function useOrder() {
),
useWallet: 0,
tip,
...(orderType === "gift"
? {
receiverName: giftDetails?.receiverName,
receiverPhone: giftDetails?.receiverPhone,
specialMessage: giftDetails?.message,
keepNameSecret: giftDetails?.isSecret,
senderEmail: giftDetails?.senderEmail,
senderPhone: giftDetails?.senderPhone,
senderName: giftDetails?.senderName,
}
: {}),
})
.then((res: any) => {
if (res.error)
message.error(res.error.data.message || t("order.createOrderFailed"));
else {
if (orderType === "gift")
// navigate(`/${PAYMENT_CONFIRMATION_URL}/${res.data.result.orderID}`, {
// replace: false,
// });
window.location.href = `${PAYMENT_CONFIRMATION_URL}/${res.data.result.orderID}`;
// window.open(
// `${PAYMENT_CONFIRMATION_URL}/${res.data.result.orderID}`,
// );
else navigate(`/${id}/order/${res.data.result.orderID}`);
dispatch(clearCart());
router(`/${id}/order/${res.data.result.orderID}`);
localStorage.setItem("orderID", res.data.result.orderID);
}
})
.catch((error: any) => {
@@ -77,9 +101,17 @@ export default function useOrder() {
createOrder,
mobilenumber,
phone,
giftDetails?.senderPhone,
giftDetails?.receiverName,
giftDetails?.receiverPhone,
giftDetails?.message,
giftDetails?.isSecret,
giftDetails?.senderEmail,
giftDetails?.senderName,
coupon,
specialRequest,
tables,
orderType,
id,
restaurantID,
items,
@@ -88,8 +120,8 @@ export default function useOrder() {
estimateTime,
tip,
t,
navigate,
dispatch,
router,
]);
return { handleCreateOrder };
}

View File

@@ -1,9 +1,10 @@
"use client";
import { Button, Grid } from "antd";
import { Button } from "antd";
import DineInIcon from "components/Icons/DineInIcon";
import DownIcon from "components/Icons/DownIcon";
import PickupIcon from "components/Icons/PickupIcon";
import useBreakPoint from "hooks/useBreakPoint";
import styles from "../menu.module.css";
interface ResponsiveServicesProps {
@@ -17,13 +18,12 @@ interface ResponsiveServicesProps {
};
}
const { useBreakpoint } = Grid;
export default function ResponsiveServices({ orderType, translations }: ResponsiveServicesProps) {
const { xs } = useBreakpoint();
const { isMobile } = useBreakPoint();
// Hide pickup service if screen width is less than 400px (insufficient for 3 services)
const shouldHidePickup = xs;
const shouldHidePickup = isMobile;
return (
<div className={styles.services}>

View File

@@ -33,8 +33,6 @@ export default function OrderPage() {
},
);
console.log(orderDetails);
return (
<>
<ProHeader>{t("order.title")}</ProHeader>

View File

@@ -9,9 +9,10 @@ import SendGiftIcon from "components/Icons/SendGiftIcon";
import ToOfficeIcon from "components/Icons/ToOfficeIcon";
import ToRoomIcon from "components/Icons/ToRoomIcon";
import ProTitle from "components/ProTitle";
import { updateOrderType } from "features/order/orderSlice";
import { useTranslation } from "react-i18next";
import { Link } from "react-router-dom";
import { useAppSelector } from "redux/hooks";
import { useAppDispatch, useAppSelector } from "redux/hooks";
import styles from "./restaurant.module.css";
interface RestaurantServicesProps {
@@ -37,6 +38,7 @@ export default function RestaurantServices({
const { t } = useTranslation();
const { isRTL } = useAppSelector((state) => state.locale);
const id = localStorage.getItem("restaurantName");
const dispatch = useAppDispatch();
const services = [
...((dineIn && [
@@ -161,7 +163,7 @@ export default function RestaurantServices({
to={s?.href}
key={s?.id}
onClick={() => {
localStorage.setItem("orderType", s?.id);
dispatch(updateOrderType(s?.id));
}}
style={{
width: "100%",

View File

@@ -5,7 +5,7 @@ import {
ORDERS_URL,
PRODUCTS_AND_CATEGORIES_URL,
RESTAURANT_DETAILS_URL,
TABLES_URL,
TABLES_URL
} from "utils/constants";
import { OrderDetails } from "pages/checkout/hooks/types";

View File

@@ -102,3 +102,4 @@ export const TABLES_URL = `${BASE_URL}restaurant/getTables`;
export const LOGIN_URL = `${API_BASE_URL}login`;
export const SEND_OTP_URL = `${API_BASE_URL}sendOtp`;
export const CONFIRM_OTP_URL = `${API_BASE_URL}confirmOtp`;
export const PAYMENT_CONFIRMATION_URL = `https://menu.fascano.com/payment/confirmation`;