loyalty & reward: initial commit

This commit is contained in:
2026-01-13 06:24:25 +03:00
parent 69bc9d4dee
commit a06147dfa4
14 changed files with 873 additions and 25 deletions

View File

@@ -0,0 +1,472 @@
import { Button, Card, Flex, Layout, Progress, Timeline, Tooltip } from "antd";
import ProHeader from "components/ProHeader/ProHeader";
import ProText from "components/ProText";
import { useTranslation } from "react-i18next";
import { useNavigate, useParams } from "react-router-dom";
import styles from "./rewardsAndLoyalty.module.css";
import { OrderType } from "pages/checkout/hooks/types.ts";
import { useAppSelector } from "redux/hooks";
import { selectCart } from "features/order/orderSlice";
import CoinsIcon from "components/Icons/CoinsIcon";
import ArabicPrice from "components/ArabicPrice";
import RaiseIcon from "components/Icons/RaiseIcon";
import CupIcon from "components/Icons/CupIcon";
import ProInputCard from "components/ProInputCard/ProInputCard";
import { useGetLoyaltyHistoryQuery } from "redux/api/others";
import dayjs from "dayjs";
import PopularIcon from "components/Icons/PopularIcon";
export default function RewardsAndLoyalityPage() {
const { t } = useTranslation();
const navigate = useNavigate();
const { subdomain } = useParams();
const { restaurant } = useAppSelector(selectCart);
const loyaltyStamps = restaurant?.loyalty_stamps ?? 0;
const customerLoyaltyPoints = restaurant?.customer_loyalty_points ?? 0;
const { data: loyaltyHistory } = useGetLoyaltyHistoryQuery();
console.log(loyaltyHistory);
// Calculate percentage: (customer_loyalty_points / loyalty_stamps) * 100
const progressPercent =
loyaltyStamps > 0
? Math.min((customerLoyaltyPoints / loyaltyStamps) * 100, 100)
: 0;
const handleCheckout = () => {
navigate(`/${subdomain}/menu?orderType=${OrderType.Redeem}`);
};
return (
<>
<Layout>
<ProHeader>{t("rewardsAndLoyalty.title")}</ProHeader>
<Layout.Content className={styles.redeemContainer}>
<Flex gap="small" wrap justify="center">
<Tooltip title="3 done / 3 in progress / 4 to do">
<div style={{ margin: "47px 47px 20px 47px" }}>
<Progress
percent={70}
format={() => (
<div
style={{
display: "flex",
flexDirection: "column",
alignItems: "center",
justifyContent: "center",
gap: 4,
}}
>
<ProText
style={{
fontWeight: 600,
fontStyle: "SemiBold",
fontSize: 48,
lineHeight: "100%",
letterSpacing: "0%",
textAlign: "center",
}}
>
{Number(customerLoyaltyPoints / loyaltyStamps).toFixed(
0,
)}
</ProText>
<ProText
style={{
fontWeight: 400,
fontStyle: "Regular",
fontSize: 16,
lineHeight: "140%",
letterSpacing: "0%",
textAlign: "center",
color: "#E8B400",
marginTop: 4,
}}
>
{t("rewardsAndLoyalty.completedPurchases")}
</ProText>
</div>
)}
strokeColor="#FFB700"
size={250}
type="circle"
/>
</div>
</Tooltip>
</Flex>
<Flex
gap="small"
wrap={false}
justify="center"
style={{ width: "100%" }}
>
<div className={styles.orderNotes}>
<div
style={{
height: 32,
width: 32,
minHeight: 32,
minWidth: 32,
backgroundColor: "var(--background)",
borderRadius: "50%",
display: "flex",
alignItems: "center",
justifyContent: "center",
}}
>
<CoinsIcon />
</div>
<div
style={{
display: "flex",
flexDirection: "column",
gap: 4,
width: "100%",
}}
>
<ProText
style={{
fontWeight: 400,
fontStyle: "Regular",
fontSize: 12,
lineHeight: "140%",
letterSpacing: "0%",
color: "#777580",
}}
>
{t("rewardsAndLoyalty.saved")}
</ProText>
<ProText
style={{
fontWeight: 500,
fontStyle: "Medium",
fontSize: 14,
lineHeight: "140%",
letterSpacing: "0%",
color: "#333333",
}}
>
<ArabicPrice price={customerLoyaltyPoints || 0} />
</ProText>
</div>
</div>
<div className={styles.orderNotes}>
<div
style={{
height: 32,
width: 32,
minHeight: 32,
minWidth: 32,
backgroundColor: "var(--background)",
borderRadius: "50%",
display: "flex",
alignItems: "center",
justifyContent: "center",
}}
>
<RaiseIcon />
</div>
<div
style={{
display: "flex",
flexDirection: "column",
gap: 4,
width: "100%",
}}
>
<ProText
style={{
fontWeight: 400,
fontStyle: "Regular",
fontSize: 12,
lineHeight: "140%",
letterSpacing: "0%",
color: "#777580",
}}
>
{t("rewardsAndLoyalty.totalPurchased")}
</ProText>
<ProText
style={{
fontWeight: 500,
fontStyle: "Medium",
fontSize: 14,
lineHeight: "140%",
letterSpacing: "0%",
color: "#333333",
}}
>
{loyaltyStamps}
</ProText>
</div>
</div>
</Flex>
<div className={styles.nextRewards}>
<div
style={{
height: 42,
width: 42,
minHeight: 42,
minWidth: 42,
backgroundColor: "#FFF5D4",
borderRadius: "50%",
display: "flex",
alignItems: "center",
justifyContent: "center",
}}
>
<CupIcon />
</div>
<div
style={{
display: "flex",
flexDirection: "column",
gap: 4,
width: "100%",
}}
>
<ProText
style={{
fontWeight: 500,
fontStyle: "Medium",
fontSize: 16,
lineHeight: "140%",
letterSpacing: "0%",
}}
>
{t("rewardsAndLoyalty.almosthere")}
</ProText>
<ProText
style={{
fontWeight: 400,
fontStyle: "Regular",
fontSize: 14,
lineHeight: "140%",
letterSpacing: "0%",
color: "#777580",
}}
>
{t("rewardsAndLoyalty.youreJustXCupsAwayFromYourNextReward", {
cups: loyaltyStamps - (customerLoyaltyPoints % loyaltyStamps),
})}
</ProText>
</div>
</div>
<ProInputCard title={t("rewardsAndLoyalty.yourAvailableRewards")}>
<div
style={{
display: "flex",
flexDirection: "column",
gap: 10,
alignItems: "center",
placeItems: "center",
}}
>
<div
style={{
height: 42,
width: 42,
minHeight: 42,
minWidth: 42,
backgroundColor: "#FFF5D4",
borderRadius: "50%",
display: "flex",
alignItems: "center",
justifyContent: "center",
}}
>
<CupIcon />
</div>
<ProText
style={{
fontWeight: 600,
fontStyle: "SemiBold",
fontSize: 18,
lineHeight: "140%",
letterSpacing: "0%",
textAlign: "center",
}}
>
{t("rewardsAndLoyalty.youCurrentlyHave")}
</ProText>
<ProText
style={{
fontWeight: 600,
fontStyle: "SemiBold",
fontSize: 20,
lineHeight: "140%",
letterSpacing: "0%",
textAlign: "center",
color: "#E8B400",
}}
>
<span style={{ padding: "0 4px" }}>
{Math.floor(customerLoyaltyPoints / loyaltyStamps)}
</span>
{t("rewardsAndLoyalty.freeItems")}
</ProText>
<ProText
style={{
fontWeight: 400,
fontStyle: "Regular",
fontSize: 12,
lineHeight: "140%",
letterSpacing: "0%",
textAlign: "center",
color: "#777580",
}}
>
{t("rewardsAndLoyalty.youCanRedeemDuringTheCheckout")}
</ProText>
<Button
type="primary"
shape="round"
className={styles.checkoutButton}
onClick={handleCheckout}
style={{
width: "100%",
height: 40,
borderRadius: 888,
backgroundColor: "#FFB700",
color: "white",
}}
>
{t("rewardsAndLoyalty.redeemNow")}
</Button>
</div>
</ProInputCard>
<ProInputCard title={t("rewardsAndLoyalty.loyaltyHistory")}>
<Timeline
items={
loyaltyHistory && loyaltyHistory.length > 0
? loyaltyHistory.map((item: any, index: number) => ({
content: (
<div key={index}>
{/* <ProText
style={{
fontWeight: 500,
fontStyle: "Medium",
fontSize: 14,
lineHeight: "140%",
letterSpacing: "0%",
color: "#333333",
}}
>
{item.restaurant_name ||
item.name ||
item.restaurantName ||
t("rewardsAndLoyalty.order")}
</ProText> */}
{(item.created_at ||
item.date ||
item.order_date ||
true) && (
<ProText
style={{
fontWeight: 600,
fontStyle: "SemiBold",
fontSize: 16,
lineHeight: "140%",
letterSpacing: "0%",
color: "#777580",
}}
>
{dayjs(
item.created_at || item.date || item.order_date,
).format("DD MMMM YYYY")}
</ProText>
)}
<div className={styles.loyaltyHistoryItem}>
<div
style={{
height: 30,
width: 20,
minHeight: 30,
minWidth: 20,
borderRadius: "50%",
display: "flex",
alignItems: "center",
justifyContent: "center",
}}
>
<PopularIcon />
</div>
<div
style={{
display: "flex",
flexDirection: "column",
gap: 4,
width: "100%",
}}
>
<ProText
style={{
fontWeight: 500,
fontStyle: "Medium",
fontSize: 16,
lineHeight: "140%",
letterSpacing: "0%",
}}
>
{t("rewardsAndLoyalty.earned")}{" "}
<ProText
style={{
fontWeight: 500,
fontStyle: "Medium",
fontSize: 16,
lineHeight: "140%",
letterSpacing: "0%",
color: "#E8B400",
}}
>
{t("rewardsAndLoyalty.xPoints", {
points:
item.total_points ||
item.points ||
item.loyalty_stamps,
})}
</ProText>
</ProText>
<ProText
style={{
fontWeight: 400,
fontStyle: "Regular",
fontSize: 14,
lineHeight: "140%",
letterSpacing: "0%",
color: "#777580",
}}
>
{t("rewardsAndLoyalty.yourOrderFrom", {
restaurantName:
item.restaurant_name ||
item.name ||
item.restaurantName,
})}
</ProText>
</div>
</div>
</div>
),
}))
: []
}
/>
</ProInputCard>
</Layout.Content>
</Layout>
</>
);
}

View File

@@ -0,0 +1,45 @@
.redeemContainer {
display: flex;
flex-direction: column;
padding: 16px;
gap: 16px;
overflow: auto;
scrollbar-width: none;
}
.orderNotes {
gap: 20px;
border-radius: 16px;
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
background-color: var(--secondary-background);
padding: 16px;
width: 100%;
}
.nextRewards {
gap: 20px;
border-radius: 16px;
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
background-color: #fefbf2;
padding: 16px;
width: 100%;
border: 1px solid #ffedb0;
}
.loyaltyHistoryItem {
gap: 20px;
border-radius: 16px;
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
background-color: var(--secondary-background);
padding: 16px;
width: 100%;
}

View File

@@ -0,0 +1,94 @@
export interface RedeemResponse {
gift: Gift
working_hours: WorkingHours
notes: Notes
is_restaurant_closed: boolean
}
export interface Gift {
id: number
voucher_amount: string
amount: string
restorant_id: number
voucher_code: string
used_amount: string
show_sender_info: number
remaining_amount: string
is_used: number
sender_phone: string
gift_type: string
sender_name: string
recipient_phone: string
recipient_name: string
message: any
created_at: string
card_url: string
gr_url: string
restaurant: string
global_currency: string
lat: string
lng: string
local_currency: string
restaurant_iimage: string
order_id: number
items: Item[]
itemsImagePrefix: string
itemsImagePrefixOld: string
}
export interface Item {
id: number
is_loyalty_used: number
no_of_stamps_give: number
isHasLoyalty: boolean
is_vat_disabled: number
name: string
price: number
qty: number
variant_price: string
image: string
imageName: string
variantName: string
variantLocalName: string
extras: any[]
descriptionEN: string
descriptionAR: string
itemline: string
itemlineAR: string
itemlineAREN: string
extrasgroups: any[]
extragroupnew: any[]
itemComment: string
variant: any
itemExtras: any[]
AvaiilableVariantExtras: any[]
isPrinted: number
category_id: number
pos_order_id: string
updated_at: string
created_at: string
old_qty: number
new_qty: number
last_printed_qty: number
deleted_qty: number
discount_value: any
discount_type_id: any
original_price: number
pricing_method: string
is_already_paid: number
hash_item: string
}
export interface WorkingHours {
opening_time: string
closing_time: string
opening_time_2: any
closing_time_2: any
is_open: boolean
}
export interface Notes {
en: string
ar: string
}