Compare commits

..

3 Commits

Author SHA1 Message Date
102415fe8b fixes
- clean coupon on success order
- in menu on clicking on the sticky categories make scroll jump to the category title postion exactly
2026-01-04 07:46:21 +03:00
f294138d30 fixes
- change refresh icon & apply refreshing logic
- apply validation in action btn in menu
- preserve on customer info state upon refresh
2026-01-04 07:00:56 +03:00
13cce2f12f show qty 2026-01-02 06:25:57 +03:00
15 changed files with 327 additions and 166 deletions

View File

@@ -83,7 +83,7 @@
color: var(--secondary-background);
}
.minusIcon{
.minusIcon {
color: var(--secondary-foreground);
}

View File

@@ -134,9 +134,10 @@ export default function CartActionsButtons({ item }: { item: CartItem }) {
}),
)
}
disabled={item.quantity >= 99}
className={styles.addButton}
style={{
backgroundColor: colors.primary,
backgroundColor: "#FFC600",
width: 28,
height: 28,
border: "none",

View File

@@ -2,9 +2,10 @@ interface PlusIconType {
className?: string;
onClick?: () => void;
dimesion?: string
color?: string
}
const PlusIcon = ({ className, onClick, dimesion }: PlusIconType) => {
const PlusIcon = ({ className, onClick, dimesion, color }: PlusIconType) => {
return (
<svg
width={dimesion || "16"}
@@ -17,7 +18,7 @@ const PlusIcon = ({ className, onClick, dimesion }: PlusIconType) => {
>
<path
d="M7.99992 3.3335V12.6668M3.33325 8.00016H12.6666"
stroke="#FFD633"
stroke={color || "#FFD633"}
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"

View File

@@ -0,0 +1,29 @@
interface RefershIconType {
className?: string;
onClick?: () => void;
dimension?: number;
}
const RefershIcon = ({ className, onClick, dimension }: RefershIconType) => {
return (
<svg
width={dimension || "18"}
height={dimension || "18"}
viewBox="0 0 18 18"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={className}
onClick={onClick}
>
<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"
/>
</svg>
);
};
export default RefershIcon;

View File

@@ -3,16 +3,25 @@
flex-direction: row;
justify-content: end;
padding: 0 16px;
gap: 10px;
gap: 8px;
position: absolute;
top: 50px;
z-index: 1000;
right: 0;
}
:global(.ant-app-rtl) .languageSwitch {
left: 0;
right: auto;
}
:global(.ant-app-ltr) .languageSwitch {
right: 0;
.refreshIcon {
cursor: pointer;
position: relative;
top: 2px;
margin-right: 3px;
}
:global(.ant-app-rtl) .refreshIcon {
margin-left: 3px;
}

View File

@@ -6,6 +6,7 @@ import { useDispatch } from "react-redux";
import { useAppSelector } from "redux/hooks";
import ProText from "../ProText";
import styles from "./LanguageSwitch.module.css";
import RefershIcon from "components/Icons/RefershIcon";
export function LanguageSwitch() {
const dispatch = useDispatch();
@@ -23,11 +24,15 @@ export function LanguageSwitch() {
});
};
const refreshPage = () => {
window.location.reload();
};
return (
<div className={styles.languageSwitch}>
<GlobalOutlined
style={{
color: themeName === "dark" ? "#fff" : "#434E5C",
color: themeName === "dark" ? "#fff" : "#333333",
fontSize: 15,
marginRight: 3,
cursor: isPending ? "wait" : "pointer",
@@ -37,23 +42,19 @@ export function LanguageSwitch() {
/>
<ProText
style={{
color: themeName === "dark" ? "#fff" : "#434E5C",
color: themeName === "dark" ? "#fff" : "#333333",
fontSize: 14,
marginRight: 3,
opacity: isPending ? 0.7 : 1,
[isRTL ? "marginLeft" : "marginRight"]: 3,
}}
onClick={changeLanguage}
>
{isPending ? "..." : isRTL ? "English" : "Arabic"}
</ProText>
<ShakeOutlined
style={{
color: themeName === "dark" ? "#fff" : "#434E5C",
fontSize: 15,
marginRight: 3,
[isRTL ? "marginLeft" : "marginRight"]: 3,
}}
/>
<div onClick={refreshPage}>
<RefershIcon dimension={18} className={styles.refreshIcon} />
</div>
</div>
);
}

View File

@@ -42,12 +42,22 @@ export function ScrollHandlerProvider({ children }: { children: ReactNode }) {
const scrollToCategory = useCallback((categoryId: number) => {
const categoryRef = categoryRefs.current?.[categoryId];
if (categoryRef) {
categoryRef.scrollIntoView({
// Get the sticky header height (70px when sticky, 0 when not)
const stickyHeaderHeight = isCategoriesSticky && categoriesContainerRef.current
? categoriesContainerRef.current.offsetHeight
: 0;
// Calculate the position of the category element
const elementPosition = categoryRef.getBoundingClientRect().top;
const offsetPosition = elementPosition + window.pageYOffset - stickyHeaderHeight;
// Scroll to the exact position, accounting for sticky header
window.scrollTo({
top: offsetPosition,
behavior: "smooth",
block: "start",
});
}
}, []);
}, [isCategoriesSticky, categoriesContainerRef]);
return (
<ScrollHandlerContext.Provider

View File

@@ -113,6 +113,7 @@ export const CART_STORAGE_KEYS = {
HIDDEN_SERVICES: "fascano_hidden_services",
VISIBLE_SERVICES: "fascano_visible_services",
ESTIMATE_WAY: "fascano_estimate_way",
CUSTOMER_NAME: "fascano_customer_name",
} as const;
// Utility functions for localStorage
@@ -201,7 +202,7 @@ const initialState: CartState = {
estimateWay: getFromLocalStorage(CART_STORAGE_KEYS.ESTIMATE_WAY, ""),
order: getFromLocalStorage(CART_STORAGE_KEYS.ORDER, null),
splitBillAmount: 0,
customerName: "",
customerName: getFromLocalStorage(CART_STORAGE_KEYS.CUSTOMER_NAME, ""),
totalServices: 8,
hiddenServices: 0,
visibleServices: 0,
@@ -363,6 +364,19 @@ const orderSlice = createSlice({
state.collectionMethod = "";
state.paymentMethod = "";
state.loyaltyValidationError = null;
state.discount = {
value: 0,
isGift: false,
isDiscount: false,
};
state.plateCar = "";
state.pickupDate = "";
state.pickupTime = "";
state.pickupType = "";
state.estimateWay = "";
state.order = null;
state.splitBillAmount = 0;
state.customerName = "";
// Clear all cart data from localStorage
if (typeof window !== "undefined") {
Object.values(CART_STORAGE_KEYS)
@@ -701,6 +715,12 @@ const orderSlice = createSlice({
},
updateCustomerName(state, action: PayloadAction<string>) {
state.customerName = action.payload;
if (typeof window !== "undefined") {
localStorage.setItem(
CART_STORAGE_KEYS.CUSTOMER_NAME,
JSON.stringify(state.customerName),
);
}
},
},
});

View File

@@ -1,6 +1,10 @@
import ProInputCard from "components/ProInputCard/ProInputCard.tsx";
import ProPhoneInput from "components/ProPhoneInput";
import { selectCart, updateCustomerName } from "features/order/orderSlice";
import {
selectCart,
updateCustomerName,
updatePhone,
} from "features/order/orderSlice";
import { OrderType } from "pages/checkout/hooks/types.ts";
import { useTranslation } from "react-i18next";
import { useAppDispatch, useAppSelector } from "redux/hooks";
@@ -9,9 +13,12 @@ import styles from "./CustomerInformationCard.module.css";
export default function CustomerInformationCard() {
const { t } = useTranslation();
const { orderType } = useAppSelector(selectCart);
const { orderType, customerName, phone } = useAppSelector(selectCart);
const dispatch = useAppDispatch();
const customerName = useAppSelector((state) => state.order.customerName);
const setPhone = (value: string) => {
dispatch(updatePhone(value));
};
return (
orderType !== OrderType.Gift && (
@@ -30,7 +37,7 @@ export default function CustomerInformationCard() {
}}
/>
</Form.Item>
<ProPhoneInput propName="phone" />
<ProPhoneInput propName="phone" value={phone} onChange={setPhone} />
</div>
</ProInputCard>
</>

View File

@@ -24,13 +24,13 @@ import { useEffect } from "react";
export default function CheckoutPage() {
const { t } = useTranslation();
const [form] = Form.useForm();
const { phone, order, orderType, collectionMethod, coupon } =
const { phone, order, orderType, collectionMethod, coupon, customerName } =
useAppSelector(selectCart);
const { token } = useAppSelector((state) => state.auth);
const dispatch = useAppDispatch();
useEffect(() => {
form.setFieldsValue({ coupon, collectionMethod });
}, [form, phone]);
form.setFieldsValue({ coupon, collectionMethod, phone, customerName });
}, [form, phone, coupon, collectionMethod, customerName]);
return (
<>

View File

@@ -1,25 +1,93 @@
.plusIcon {
position: relative;
top: -1px;
.quantityControls {
display: flex;
align-items: center;
background-color: var(--background);
border-radius: 888px;
width: fit-content;
}
.addButton {
position: absolute;
z-index: 1;
.quantityLabel {
font-size: 14px;
color: var(--secondary-color);
font-weight: 500;
}
.quantityInputContainer {
display: flex;
padding: 0 1px;
align-items: center;
border-radius: 888px;
width: 90px;
height: 32px;
}
.quantityButton {
padding: 0;
width: 28px !important;
height: 28px !important;
display: flex;
align-items: center;
justify-content: center;
color: var(--secondary-background);
background-color: var(--primary);
border-radius: 50%;
transition: all 0.2s ease;
}
.quantityInput {
text-align: center;
border: none;
box-shadow: none;
font-size: 16px;
font-weight: 600;
border: 0;
color: #fff;
font-size: 1rem;
padding: 0;
}
.actionRect {
fill: var(--background) !important;
.removeButton {
padding: 4px 0;
height: 32px;
display: flex;
align-items: center;
gap: 4px;
width: 30px;
}
.addButton svg rect {
fill: var(--background) !important;
.deleteButtonContainer {
position: absolute;
top: 12px;
right: 12px;
border-radius: 50%;
padding: 8px;
cursor: pointer;
transition: all 0.3s ease;
}
:global(.darkApp) .addButton rect {
fill: var(--background) !important;
.deleteIcon {
font-size: 18px;
color: var(--secondary-color);
}
.cartItemActions :global(.ant-input-number-outlined) {
border: none;
width: 40px;
background-color: inherit;
text-align: center;
}
.cartItemActions :global(.ant-input-number-input) {
text-align: center !important;
}
.plusIcon {
margin-bottom: 1px;
color: var(--secondary-background);
}
.minusIcon {
color: var(--secondary-foreground);
}
.deleteIcon {
position: relative;
right: 1px;
}

View File

@@ -1,5 +1,5 @@
import { MinusOutlined, PlusOutlined } from "@ant-design/icons";
import { Button, message } from "antd";
import { Button, InputNumber, message } from "antd";
import { useTranslation } from "react-i18next";
import { useNavigate, useParams } from "react-router-dom";
import { useGetRestaurantDetailsQuery } from "redux/api/others";
@@ -8,7 +8,7 @@ import { useAppSelector, useAppDispatch } from "redux/hooks";
import { Product } from "utils/types/appTypes";
import NextIcon from "components/Icons/NextIcon";
import { addItem, removeItem, updateQuantity } from "features/order/orderSlice";
import ProText from "components/ProText";
import PlusIcon from "components/Icons/PlusIcon";
export function AddToCartButton({ item }: { item: Product }) {
const { t } = useTranslation();
@@ -158,108 +158,102 @@ export function AddToCartButton({ item }: { item: Product }) {
}
};
return isInCart && !hasOptions ? (
<>
<div
className={styles.addButton}
style={{
width: 90,
height: 30,
position: "absolute",
bottom: 3,
[isRTL ? "left" : "right"]: 1,
background: "#FAFAFA",
borderRadius: 888,
}}
>
return (
<div
className={styles.cartItemActions}
onClick={(e) => {
e.stopPropagation();
e.preventDefault;
}}
>
{isInCart && !hasOptions ? (
<>
<div className={styles.quantityControls}>
<div className={styles.quantityInputContainer}>
<Button
shape="circle"
iconPlacement="start"
icon={
<MinusOutlined title="minus" style={{ color: "black" }} />
}
size="small"
onClick={handleMinusClick}
className={styles.addButton}
style={{
backgroundColor: "white",
width: 28,
height: 28,
border: "none",
}}
/>
<InputNumber
min={1}
max={99}
value={totalQuantity}
onClick={(e) => {
e.stopPropagation();
e.preventDefault;
}}
onChange={(value: number | null) =>
dispatch(
updateQuantity({
id: item.id,
uniqueId: basicCartItem?.uniqueId || "",
quantity: value || 1,
}),
)
}
size="small"
controls={false}
className={styles.quantityInput}
name="id"
/>
<Button
shape="circle"
iconPlacement="start"
icon={<PlusIcon color="#FFF" />}
size="small"
onClick={handlePlusClick}
disabled={totalQuantity >= 99}
className={styles.addButton}
style={{
backgroundColor: "#FFC600",
width: 28,
height: 28,
}}
/>
</div>
</div>
</>
) : (
<Button
shape="circle"
iconPlacement="start"
icon={<MinusOutlined title="minus" style={{ color: "black" }} />}
size="small"
onClick={handleMinusClick}
icon={
hasOptions ? (
<NextIcon iconColor="#302E3E" iconSize={16} />
) : (
<PlusOutlined title="add" />
)
}
onClick={handleClick}
disabled={!hasOptions && totalQuantity >= 99}
className={styles.addButton}
style={{
color: "#302E3E",
backgroundColor: "white",
width: 28,
height: 28,
position: "absolute",
bottom: 1,
[isRTL ? "left" : "right"]: 60,
[isRTL ? "left" : "right"]: -87,
bottom: 3,
minWidth: 28,
border: "none",
boxShadow:
"0px 1px 2px 0px #8585851A, 0px 3px 3px 0px #85858517, -1px 7px 4px 0px #8585850D, -1px 13px 5px 0px #85858503, -2px 20px 6px 0px #85858500",
}}
/>
<ProText
style={{
position: "absolute",
bottom: 7,
[isRTL ? "left" : "right"]: 45,
fontSize: 14,
fontWeight: 700,
fontStyle: "Bold",
lineHeight: "100%",
letterSpacing: "0.06px",
textAlign: "center",
verticalAlign: "middle",
}}
>
{totalQuantity}
</ProText>
<Button
shape="circle"
iconPlacement="start"
icon={<PlusOutlined title="plus" />}
size="small"
onClick={handlePlusClick}
className={styles.addButton}
style={{
backgroundColor: "#FFC600",
width: 28,
height: 28,
position: "absolute",
bottom: 1,
[isRTL ? "left" : "right"]: 2,
minWidth: 28,
}}
/>
</div>
</>
) : (
<div
style={{
position: "absolute",
bottom: -11,
[isRTL ? "left" : "right"]: -2,
borderRadius: "50%",
}}
>
<Button
shape="circle"
iconPlacement="start"
size="small"
icon={
hasOptions ? (
<NextIcon iconColor="#302E3E" iconSize={16} />
) : (
<PlusOutlined title="add" />
)
}
onClick={handleClick}
className={styles.addButton}
style={{
color: "#302E3E",
backgroundColor: "white",
width: 28,
height: 28,
position: "absolute",
bottom: 16,
[isRTL ? "left" : "right"]: 7,
minWidth: 28,
boxShadow:
"0px 1px 2px 0px #8585851A, 0px 3px 3px 0px #85858517, -1px 7px 4px 0px #8585850D, -1px 13px 5px 0px #85858503, -2px 20px 6px 0px #85858500",
}}
/>
)}
</div>
);
}

View File

@@ -2,7 +2,6 @@ import styles from "pages/menu/components/MenuList/ProductCard.module.css";
import { Card, Badge } from "antd";
import ProText from "components/ProText.tsx";
import ArabicPrice from "components/ArabicPrice";
import { colors } from "ThemeConstants.ts";
import { ItemDescriptionIcons } from "components/ItemDescriptionIcons/ItemDescriptionIcons.tsx";
import ImageWithFallback from "components/ImageWithFallback";
import { Product } from "utils/types/appTypes.ts";
@@ -207,7 +206,14 @@ export default function ProductCard({ item, setIsBottomSheetOpen }: Props) {
width={91}
height={96}
/>
<AddToCartButton item={item} />
<div
style={{
position: "absolute",
bottom: 3,
}}
>
<AddToCartButton item={item} />
</div>
</Badge>
</div>
</div>

View File

@@ -171,7 +171,7 @@
}
.headerContainer {
margin: 5px 0px;
margin: 5px 0 0 0;
}
/* Enhanced responsive item description */

View File

@@ -51,30 +51,37 @@ export function SplitBillParticipantsBottomSheet({
gap: 20,
}}
>
<div className={styles.inviteToBill}>
<Button
shape="circle"
iconPlacement="start"
size="small"
className={styles.addButton}
style={{
background: "#F9F9FA",
width: 28,
height: 28,
border: "none",
color: "#FFB700",
}}
>
<ProText style={{ color: "#1F1C2E" }}> 1 </ProText>
</Button>
<div
style={{
display: "flex",
flexDirection: "column",
gap: 4,
width: "100%",
}}
>
<div
style={{
display: "flex",
flexDirection: "row",
justifyContent: "space-between",
placeItems: "center",
}}
>
<div style={{
display: "flex",
flexDirection: "row",
justifyContent: "flex-start",
placeItems: "center",
gap: 10,
}}>
<Button
shape="circle"
iconPlacement="start"
size="small"
className={styles.addButton}
style={{
background: "#F9F9FA",
width: 28,
height: 28,
border: "none",
color: "#FFB700",
}}
>
<ProText style={{ color: "#1F1C2E" }}> 1 </ProText>
</Button>
<ProText
style={{
fontWeight: 500,
@@ -88,7 +95,18 @@ export function SplitBillParticipantsBottomSheet({
{t("order.personHasPaid")}
</ProText>
</div>
{isRTL ? <BackIcon iconSize={24} /> : <NextIcon iconSize={24} />}
<ArabicPrice
price={0}
textStyle={{
fontWeight: 400,
fontStyle: "Regular",
fontSize: 12,
lineHeight: "140%",
letterSpacing: "0%",
color: "#3D3B4A",
}}
/>
</div>
</div>
@@ -132,10 +150,7 @@ export function SplitBillParticipantsBottomSheet({
>
{t("splitBill.remainingToPay")}
</ProText>
<ArabicPrice
price={0}
textStyle={taxesChargesStyle}
/>
<ArabicPrice price={0} textStyle={taxesChargesStyle} />
</div>
<ProText style={taxesChargesStyle}>
{t("splitBill.includesAllOfTaxesCharges")}