enhance loyalty section UI

This commit is contained in:
2026-01-13 23:09:37 +03:00
parent a06147dfa4
commit 1d9ae7190e
9 changed files with 162 additions and 76 deletions

View File

@@ -92,7 +92,7 @@
"scheduledOrder": "طلب مجدول" "scheduledOrder": "طلب مجدول"
}, },
"promotion": { "promotion": {
"title": "الترويجات", "title": "عرض التفاصيل",
"description": "احصل على خصم 10% على طلبك الأول" "description": "احصل على خصم 10% على طلبك الأول"
} }
}, },

View File

@@ -108,7 +108,7 @@
"scheduledOrder": "Scheduled Order" "scheduledOrder": "Scheduled Order"
}, },
"promotion": { "promotion": {
"title": "Promotions", "title": "Show details",
"description": "Get 10% off your first order" "description": "Get 10% off your first order"
} }
}, },
@@ -282,7 +282,9 @@
"am": "AM", "am": "AM",
"pm": "PM", "pm": "PM",
"cannotSelectPastDate": "You cannot select a past date. Please select today or a future date.", "cannotSelectPastDate": "You cannot select a past date. Please select today or a future date.",
"checkRequiredFields": "Please check required fields" "checkRequiredFields": "Please check required fields",
"applyYourAvailableRewardsToGetDiscountsOnItemsInYourCart": "Apply your available rewards to get discounts on items in your cart",
"loyalty": "Loyalty"
}, },
"checkout": { "checkout": {
"addCarDetails": "Add Car Details", "addCarDetails": "Add Car Details",

View File

@@ -11,6 +11,7 @@
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
font-size: 16px; font-size: 16px;
min-height: 24px;
} }
.summaryDivider { .summaryDivider {

View File

@@ -1,4 +1,4 @@
import { Card, Checkbox, Divider, Space } from "antd"; import { Card, Checkbox, Divider, Space, Tag } from "antd";
import ArabicPrice from "components/ArabicPrice"; import ArabicPrice from "components/ArabicPrice";
import { import {
selectCart, selectCart,
@@ -69,7 +69,7 @@ export default function OrderSummary() {
<ProText type="secondary" style={titlesStyle}> <ProText type="secondary" style={titlesStyle}>
{t("cart.basketTotal")} {t("cart.basketTotal")}
</ProText> </ProText>
<ArabicPrice price={subtotal} style={titlesStyle} /> <ArabicPrice price={subtotal} textStyle={titlesStyle} />
</div> </div>
{orderType === OrderType.Delivery && ( {orderType === OrderType.Delivery && (
<div className={styles.summaryRow}> <div className={styles.summaryRow}>
@@ -78,7 +78,7 @@ export default function OrderSummary() {
</ProText> </ProText>
<ArabicPrice <ArabicPrice
price={Number(restaurant?.delivery_fees || 0)} price={Number(restaurant?.delivery_fees || 0)}
style={{ ...titlesStyle, color: "#434E5C" }} textStyle={{ ...titlesStyle, color: "#434E5C" }}
/> />
</div> </div>
)} )}
@@ -87,11 +87,43 @@ export default function OrderSummary() {
<ProText type="secondary" style={titlesStyle}> <ProText type="secondary" style={titlesStyle}>
{t("cart.discount")} {t("cart.discount")}
</ProText> </ProText>
<div
style={{
display: "flex",
alignItems: "center",
minHeight: "24px",
}}
>
{isHasLoyaltyGift &&
useLoyaltyPoints &&
highestLoyaltyItem &&
restaurant?.is_loyalty_enabled === 1 ? (
<Tag
color="green"
style={{
backgroundColor: "#EDFEF5",
borderRadius: "4px",
padding: "3px 10px",
fontWeight: 500,
fontStyle: "Medium",
fontSize: 12,
lineHeight: "140%",
letterSpacing: "0%",
placeItems: "center",
margin: "0 10px",
position: "relative",
top: -1,
}}
>
{t("cart.loyalty")}
</Tag>
) : null}
<ArabicPrice <ArabicPrice
price={discountAmount} price={discountAmount}
style={{ ...titlesStyle, color: "#434E5C" }} textStyle={{ ...titlesStyle, color: "#434E5C" }}
/> />
</div> </div>
</div>
)} )}
{orderType !== OrderType.Redeem && ( {orderType !== OrderType.Redeem && (
<div className={styles.summaryRow}> <div className={styles.summaryRow}>
@@ -100,7 +132,7 @@ export default function OrderSummary() {
</ProText> </ProText>
<ArabicPrice <ArabicPrice
price={tip || 0} price={tip || 0}
style={{ ...titlesStyle, color: "#434E5C" }} textStyle={{ ...titlesStyle, color: "#434E5C" }}
/> />
</div> </div>
)} )}
@@ -111,7 +143,7 @@ export default function OrderSummary() {
</ProText> </ProText>
<ArabicPrice <ArabicPrice
price={0} price={0}
style={{ ...titlesStyle, color: "#434E5C" }} textStyle={{ ...titlesStyle, color: "#434E5C" }}
/> />
</div> </div>
)} )}
@@ -122,7 +154,7 @@ export default function OrderSummary() {
</ProText> </ProText>
<ArabicPrice <ArabicPrice
price={0} price={0}
style={{ ...titlesStyle, color: "#32AD6D" }} textStyle={{ ...titlesStyle, color: "#32AD6D" }}
/> />
</div> </div>
)} )}
@@ -133,7 +165,7 @@ export default function OrderSummary() {
</ProText> </ProText>
<ArabicPrice <ArabicPrice
price={taxAmount || 0} price={taxAmount || 0}
style={{ ...titlesStyle, color: "#434E5C" }} textStyle={{ ...titlesStyle, color: "#434E5C" }}
/> />
</div> </div>
)} )}
@@ -144,7 +176,7 @@ export default function OrderSummary() {
</ProText> </ProText>
<ArabicPrice <ArabicPrice
price={splitBillAmount} price={splitBillAmount}
style={{ ...titlesStyle, color: "#434E5C" }} textStyle={{ ...titlesStyle, color: "#434E5C" }}
/> />
</div> </div>
)} )}
@@ -167,7 +199,7 @@ export default function OrderSummary() {
</ProText> </ProText>
<ArabicPrice <ArabicPrice
price={grandTotal} price={grandTotal}
style={{ textStyle={{
fontWeight: 600, fontWeight: 600,
fontStyle: "SemiBold", fontStyle: "SemiBold",
fontSize: 18, fontSize: 18,
@@ -178,43 +210,6 @@ export default function OrderSummary() {
/> />
</div> </div>
</Space> </Space>
{isHasLoyaltyGift &&
restaurant?.is_loyalty_enabled === 1 &&
orderType !== OrderType.Redeem && (
<>
<Checkbox
checked={useLoyaltyPoints}
onChange={(value) => {
dispatch(updateUseLoyaltyPoints(value.target.checked));
}}
style={{ marginTop: 8 }}
>
{t("cart.useLoyaltyPoints")}
</Checkbox>
</>
)}
{isHasLoyaltyGift &&
loyaltyValidation.errorMessage &&
orderType !== OrderType.Redeem && (
<div style={{ marginTop: 8, color: "red", fontSize: "12px" }}>
{t(loyaltyValidation.errorMessage)}
</div>
)}
{isHasLoyaltyGift &&
orderType !== OrderType.Redeem &&
useLoyaltyPoints &&
highestLoyaltyItem &&
restaurant?.is_loyalty_enabled === 1 && (
<div style={{ marginTop: 8, color: "green", fontSize: "12px" }}>
{t("cart.loyaltyDiscountApplied", {
itemName: highestLoyaltyItem.name,
amount: Math.round(highestLoyaltyItem.price || 0).toFixed(2),
})}
</div>
)}
</Card> </Card>
</> </>
); );

View File

@@ -0,0 +1,10 @@
.useLoyaltyPointsContainer {
gap: 20px;
border-radius: 16px;
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
background-color: var(--secondary-background);
width: 100%;
}

View File

@@ -0,0 +1,67 @@
import { Button, Form, Switch } from "antd";
import ProInputCard from "components/ProInputCard/ProInputCard.tsx";
import { useTranslation } from "react-i18next";
import { useAppDispatch, useAppSelector } from "redux/hooks.ts";
import { selectCart, updateUseLoyaltyPoints } from "features/order/orderSlice";
import ProText from "components/ProText";
import styles from "./EarnLoyaltyPointsCard.module.css";
export default function EarnLoyaltyPointsCard() {
const { t } = useTranslation();
const dispatch = useAppDispatch();
const { useLoyaltyPoints } = useAppSelector(selectCart);
return (
<>
<ProInputCard title={t("cart.loyalty")}>
<Form.Item name="useLoyaltyPoints">
<div className={styles.useLoyaltyPointsContainer}>
<div
style={{
display: "flex",
flexDirection: "column",
gap: 4,
width: "100%",
}}
>
<ProText
style={{
fontWeight: 500,
fontStyle: "Medium",
fontSize: 14,
lineHeight: "140%",
letterSpacing: "0%",
color: "#333333",
}}
>
{t("cart.useLoyaltyPoints")}
</ProText>
<ProText
style={{
fontWeight: 400,
fontStyle: "Regular",
fontSize: 12,
lineHeight: "140%",
letterSpacing: "0%",
color: "#777580",
}}
>
{t(
"cart.applyYourAvailableRewardsToGetDiscountsOnItemsInYourCart",
)}
</ProText>
</div>
<Switch
autoFocus={false}
checked={useLoyaltyPoints}
onClick={() => {
dispatch(updateUseLoyaltyPoints(!useLoyaltyPoints));
}}
/>
</div>
</Form.Item>
</ProInputCard>
</>
);
}

View File

@@ -23,6 +23,7 @@ import { CarCard } from "./components/CarCard";
import { CollectWay } from "./components/CollectWay/CollectWay"; import { CollectWay } from "./components/CollectWay/CollectWay";
import PickupTimeCard from "./components/pickupEstimate/TimeEstimateCard"; import PickupTimeCard from "./components/pickupEstimate/TimeEstimateCard";
import VoucherSummary from "pages/redeem/components/VoucherSummary/VoucherSummary"; import VoucherSummary from "pages/redeem/components/VoucherSummary/VoucherSummary";
import EarnLoyaltyPointsCard from "pages/cart/components/earnLoyaltyPointsCard/EarnLoyaltyPointsCard";
export default function CheckoutPage() { export default function CheckoutPage() {
const { t } = useTranslation(); const { t } = useTranslation();
@@ -85,7 +86,9 @@ export default function CheckoutPage() {
<OfficeDetails /> */} <OfficeDetails /> */}
{/* <GiftDetails /> */} {/* <GiftDetails /> */}
{/* <BriefMenu /> */} {/* <BriefMenu /> */}
{orderType !== OrderType.Redeem && orderType !== OrderType.Gift && <CouponCard />} {orderType !== OrderType.Redeem && orderType !== OrderType.Gift && (
<CouponCard />
)}
{/* Collection Method */} {/* Collection Method */}
{orderType === OrderType.Pickup && ( {orderType === OrderType.Pickup && (
@@ -123,7 +126,12 @@ export default function CheckoutPage() {
)} )}
{/* Reward Your Waiter */} {/* Reward Your Waiter */}
{orderType !== OrderType.Redeem && orderType !== OrderType.Gift && <RewardWaiterCard />} {orderType !== OrderType.Redeem && orderType !== OrderType.Gift && (
<RewardWaiterCard />
)}
{orderType !== OrderType.Redeem && orderType !== OrderType.Gift && (
<EarnLoyaltyPointsCard />
)}
<BriefMenuCard /> <BriefMenuCard />
<Ads1 /> <Ads1 />
<OrderSummary /> <OrderSummary />

View File

@@ -7,8 +7,8 @@ import { useNavigate, useParams } from "react-router-dom";
import styles from "./rewardsAndLoyalty.module.css"; import styles from "./rewardsAndLoyalty.module.css";
import { OrderType } from "pages/checkout/hooks/types.ts"; import { OrderType } from "pages/checkout/hooks/types.ts";
import { useAppSelector } from "redux/hooks"; import { useAppDispatch, useAppSelector } from "redux/hooks";
import { selectCart } from "features/order/orderSlice"; import { selectCart, updateUseLoyaltyPoints } from "features/order/orderSlice";
import CoinsIcon from "components/Icons/CoinsIcon"; import CoinsIcon from "components/Icons/CoinsIcon";
import ArabicPrice from "components/ArabicPrice"; import ArabicPrice from "components/ArabicPrice";
import RaiseIcon from "components/Icons/RaiseIcon"; import RaiseIcon from "components/Icons/RaiseIcon";
@@ -19,6 +19,7 @@ import dayjs from "dayjs";
import PopularIcon from "components/Icons/PopularIcon"; import PopularIcon from "components/Icons/PopularIcon";
export default function RewardsAndLoyalityPage() { export default function RewardsAndLoyalityPage() {
const dispatch = useAppDispatch();
const { t } = useTranslation(); const { t } = useTranslation();
const navigate = useNavigate(); const navigate = useNavigate();
const { subdomain } = useParams(); const { subdomain } = useParams();
@@ -27,19 +28,17 @@ export default function RewardsAndLoyalityPage() {
const loyaltyStamps = restaurant?.loyalty_stamps ?? 0; const loyaltyStamps = restaurant?.loyalty_stamps ?? 0;
const customerLoyaltyPoints = restaurant?.customer_loyalty_points ?? 0; const customerLoyaltyPoints = restaurant?.customer_loyalty_points ?? 0;
const { data: loyaltyHistory } = useGetLoyaltyHistoryQuery(); const { data: loyaltyHistory } = useGetLoyaltyHistoryQuery();
const loyaltyHistories = loyaltyHistory?.filter(
(LH) => Number(LH.restaurant_id) === Number(restaurant.restautantId),
);
console.log(loyaltyHistory); const handleRedeem = () => {
navigate(`/${subdomain}/menu?orderType=${OrderType.DineIn}`);
// Calculate percentage: (customer_loyalty_points / loyalty_stamps) * 100 dispatch(updateUseLoyaltyPoints(true));
const progressPercent =
loyaltyStamps > 0
? Math.min((customerLoyaltyPoints / loyaltyStamps) * 100, 100)
: 0;
const handleCheckout = () => {
navigate(`/${subdomain}/menu?orderType=${OrderType.Redeem}`);
}; };
const currentStampRound = customerLoyaltyPoints % loyaltyStamps;
return ( return (
<> <>
<Layout> <Layout>
@@ -49,7 +48,7 @@ export default function RewardsAndLoyalityPage() {
<Tooltip title="3 done / 3 in progress / 4 to do"> <Tooltip title="3 done / 3 in progress / 4 to do">
<div style={{ margin: "47px 47px 20px 47px" }}> <div style={{ margin: "47px 47px 20px 47px" }}>
<Progress <Progress
percent={70} percent={(currentStampRound / loyaltyStamps) * 100}
format={() => ( format={() => (
<div <div
style={{ style={{
@@ -70,9 +69,7 @@ export default function RewardsAndLoyalityPage() {
textAlign: "center", textAlign: "center",
}} }}
> >
{Number(customerLoyaltyPoints / loyaltyStamps).toFixed( {currentStampRound} / {loyaltyStamps}
0,
)}
</ProText> </ProText>
<ProText <ProText
style={{ style={{
@@ -332,7 +329,7 @@ export default function RewardsAndLoyalityPage() {
type="primary" type="primary"
shape="round" shape="round"
className={styles.checkoutButton} className={styles.checkoutButton}
onClick={handleCheckout} onClick={handleRedeem}
style={{ style={{
width: "100%", width: "100%",
height: 40, height: 40,
@@ -349,8 +346,8 @@ export default function RewardsAndLoyalityPage() {
<ProInputCard title={t("rewardsAndLoyalty.loyaltyHistory")}> <ProInputCard title={t("rewardsAndLoyalty.loyaltyHistory")}>
<Timeline <Timeline
items={ items={
loyaltyHistory && loyaltyHistory.length > 0 loyaltyHistories && loyaltyHistories.length > 0
? loyaltyHistory.map((item: any, index: number) => ({ ? loyaltyHistories.map((item: any, index: number) => ({
content: ( content: (
<div key={index}> <div key={index}>
{/* <ProText {/* <ProText

View File

@@ -39,7 +39,13 @@
flex-direction: row; flex-direction: row;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
background-color: var(--secondary-background); background-color: var(--background);
padding: 16px;
width: 100%; width: 100%;
width: 275;
opacity: 1;
padding-top: 14px;
padding-right: 12px;
padding-bottom: 14px;
padding-left: 12px;
margin-top: 12px;
} }