gift: working on UI ans styles

This commit is contained in:
2025-12-31 14:28:11 +03:00
parent 38c83d5143
commit 71e1d71c96
22 changed files with 720 additions and 93 deletions

View File

@@ -0,0 +1,76 @@
.checkoutContainer {
display: flex;
flex-direction: column;
padding: 16px;
gap: 16px;
overflow: auto;
scrollbar-width: none;
}
.carouselContainer {
display: flex;
align-items: center;
justify-content: center;
gap: 16px;
width: 100%;
margin-bottom: 24px;
}
.cardWrapper {
display: flex;
align-items: center;
justify-content: center;
}
.cardImage {
width: 205px;
height: 134px;
object-fit: cover;
border-radius: 8px;
}
.arrowButton {
display: flex;
align-items: center;
justify-content: center;
min-width: 40px;
height: 40px;
padding: 0;
border: none;
background: transparent;
cursor: pointer;
}
.arrowButton:hover {
background: rgba(0, 0, 0, 0.04);
border-radius: 50%;
}
/* CheckoutButton Styles */
.checkoutButtonContainer {
width: 100%;
padding: 16px 16px 0;
position: sticky;
bottom: 0;
left: 0;
height: 80px;
display: flex;
flex-direction: row;
justify-content: space-around;
gap: 1rem;
background-color: var(--secondary-background);
box-shadow: none;
z-index: 999;
}
/* Dark theme styles for checkout */
:global(.darkApp) .checkoutButtonContainer {
background-color: #000000 !important;
}
.checkoutButton {
width: 100%;
height: 48px;
margin-bottom: 16px;
box-shadow: none;
}

View File

@@ -0,0 +1,153 @@
import { Layout, Image, Button, Form, Skeleton } from "antd";
import ProHeader from "components/ProHeader/ProHeader";
import { useTranslation } from "react-i18next";
import styles from "./CardDetails.module.css";
import ProText from "components/ProText";
import { useGetEGiftCardsQuery } from "redux/api/others";
import { selectCart } from "features/order/orderSlice";
import { useAppSelector } from "redux/hooks";
import { useState, useEffect, useCallback } from "react";
import BackIcon from "components/Icons/BackIcon";
import NextIcon from "components/Icons/NextIcon";
import cardStyles from "./CardDetails.module.css";
import ReceivernformationCard from "./components/ReceivernformationCard/ReceivernformationCard";
import SenderformationCard from "./components/SenderformationCard/SenderformationCard";
import TimeEstimateCard from "pages/cart/components/timeEstimate/TimeEstimateCard";
import { useNavigate, useParams } from "react-router-dom";
import GiftAmountCard from "./components/GiftAmountCard/GiftAmountCard";
export default function CardDetailsPage() {
const { t } = useTranslation();
const { data: cards, isLoading } = useGetEGiftCardsQuery();
const { giftDetails } = useAppSelector(selectCart);
const { isRTL } = useAppSelector((state) => state.locale);
const [currentIndex, setCurrentIndex] = useState(0);
const { subdomain } = useParams();
const navigate = useNavigate();
// Find the initial index based on selected cardId from gift details
useEffect(() => {
if (cards && giftDetails?.cardId) {
const index = cards.findIndex(
(card) => card.id.toString() === giftDetails.cardId,
);
if (index !== -1) {
setCurrentIndex(index);
}
}
}, [cards, giftDetails?.cardId]);
const handlePrevious = () => {
if (cards && cards.length > 0) {
setCurrentIndex((prev) => (prev === 0 ? cards.length - 1 : prev - 1));
}
};
const handleNext = () => {
if (cards && cards.length > 0) {
setCurrentIndex((prev) => (prev === cards.length - 1 ? 0 : prev + 1));
}
};
const currentCard = cards && cards.length > 0 ? cards[currentIndex] : null;
const handleCheckout = useCallback(() => {
navigate(`/${subdomain}/checkout`);
}, [subdomain]);
return (
<Layout>
<ProHeader>{t("cardDetails.title")}</ProHeader>
<Layout.Content className={styles.checkoutContainer}>
<div
style={{
textAlign: "center",
display: "flex",
flexDirection: "column",
alignItems: "center",
justifyContent: "center",
margin: "50px 28px 35px 28px",
gap: 8,
}}
>
<ProText style={{ fontSize: 16, fontWeight: 600, color: "#333333" }}>
{t("cardDetails.addGiftDetails")}
</ProText>
<ProText style={{ fontSize: 14, fontWeight: 400, color: "#95949C" }}>
{t("cardDetails.description")}
</ProText>
</div>
{isLoading || !currentCard ? (
<div className={cardStyles.carouselContainer}>
<Skeleton.Avatar
active
size={40}
shape="circle"
style={{ flexShrink: 0 }}
/>
<div className={cardStyles.cardWrapper}>
<Skeleton.Image
active
style={{
width: 205,
height: 134,
borderRadius: 8,
}}
/>
</div>
<Skeleton.Avatar
active
size={40}
shape="circle"
style={{ flexShrink: 0 }}
/>
</div>
) : (
<div className={cardStyles.carouselContainer}>
<Button
type="text"
className={cardStyles.arrowButton}
onClick={isRTL ? handleNext : handlePrevious}
icon={<BackIcon iconSize={24} />}
/>
<div className={cardStyles.cardWrapper}>
<Image
src={currentCard.imageURL}
alt={currentCard.image}
width={205}
height={134}
className={cardStyles.cardImage}
/>
</div>
<Button
type="text"
className={cardStyles.arrowButton}
onClick={isRTL ? handlePrevious : handleNext}
icon={<NextIcon iconSize={24} />}
/>
</div>
)}
<Form
layout="vertical"
style={{ display: "flex", flexDirection: "column", gap: 16 }}
>
<GiftAmountCard />
<ReceivernformationCard />
<SenderformationCard />
<TimeEstimateCard />
</Form>
</Layout.Content>
<Layout.Footer className={styles.checkoutButtonContainer}>
<Button
type="primary"
shape="round"
className={styles.checkoutButton}
onClick={handleCheckout}
>
{t("cardDetails.checkout")}
</Button>
</Layout.Footer>
</Layout>
);
}

View File

@@ -0,0 +1,81 @@
import { Card, Divider, Image } from "antd";
import { EGiftCard } from "../type";
import { useGetEGiftCardsQuery } from "redux/api/others";
import LoadingSpinner from "components/LoadingSpinner/LoadingSpinner";
import ProText from "components/ProText";
import { useTranslation } from "react-i18next";
import { updateGiftDetails } from "features/order/orderSlice";
import { useAppDispatch } from "redux/hooks";
import { useNavigate, useParams } from "react-router-dom";
export default function ECardList() {
const dispatch = useAppDispatch();
const navigate = useNavigate();
const { subdomain } = useParams();
const { data: eGiftCards, isLoading } = useGetEGiftCardsQuery();
const { t } = useTranslation();
console.log(eGiftCards);
const handleCardClick = (id: number) => {
dispatch(updateGiftDetails({ cardId: id.toString() }));
navigate(`/${subdomain}/card-details`);
};
if (isLoading) {
return <LoadingSpinner />;
}
return (
<Card style={{ textAlign: "center" }}>
<div style={{ paddingBottom: 12 }}>
<ProText
style={{
fontSize: 16,
fontWeight: 600,
color: "#333333",
}}
>
{t("eGiftCards.pickCardForYourGift")}
</ProText>
</div>
<ProText
style={{
fontFamily: "Outfit",
fontWeight: 400,
fontStyle: "Regular",
fontSize: 14,
lineHeight: "140%",
letterSpacing: 0,
textAlign: "center",
color: "#95949C",
}}
>
{t("eGiftCards.chooseDesignToMatchTheOccasion")}
</ProText>
<Divider style={{ margin: "12px 0 16px 0" }} />
<div
style={{
display: "grid",
gridTemplateColumns: "1fr 1fr",
padding: "8px",
}}
>
{eGiftCards?.map((card: EGiftCard) => (
<Image
src={card.imageURL}
alt={card.image}
width={135}
height={88}
preview={false}
onClick={() => handleCardClick(card.id)}
style={{
width: "100%",
height: "100%",
objectFit: "cover",
borderRadius: 8,
}}
/>
))}
</div>
</Card>
);
}

View File

@@ -0,0 +1,18 @@
.customerInformationCard {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 16px;
width: 100%;
}
.customerInformationCard > * {
min-width: 0;
width: 100%;
}
/* Reduce gap on small screens to prevent overflow */
@media (max-width: 480px) {
.customerInformationCard {
gap: 8px;
}
}

View File

@@ -0,0 +1,106 @@
import ProInputCard from "components/ProInputCard/ProInputCard.tsx";
import { useTranslation } from "react-i18next";
import { Button } from "antd";
import styles from "./GiftAmountCard.module.css";
import ArabicPrice from "components/ArabicPrice";
import ProText from "components/ProText";
import EditIcon from "components/Icons/EditIcon";
export default function GiftAmountCard() {
const { t } = useTranslation();
return (
<ProInputCard title={t("cardDetails.eGiftCardAmount")}>
<div className={styles.customerInformationCard}>
<Button
style={{
borderRadius: 100,
height: 40,
border: "none",
backgroundColor: "#5F6C7B0D",
}}
>
<ArabicPrice
price={10.00}
textStyle={{
fontWeight: 500,
fontStyle: "Medium",
fontSize: 14,
lineHeight: "140%",
letterSpacing: "0%",
textAlign: "center",
color: "#5F6C7B",
}}
/>
</Button>
<Button
style={{
borderRadius: 100,
height: 40,
border: "none",
backgroundColor: "#5F6C7B0D",
}}
>
<ArabicPrice
price={20.00}
textStyle={{
fontWeight: 500,
fontStyle: "Medium",
fontSize: 14,
lineHeight: "140%",
letterSpacing: "0%",
textAlign: "center",
color: "#5F6C7B",
}}
/>
</Button>
<Button
style={{
borderRadius: 100,
height: 40,
border: "none",
backgroundColor: "#5F6C7B0D",
}}
>
<ArabicPrice
price={30.00}
textStyle={{
fontWeight: 500,
fontStyle: "Medium",
fontSize: 14,
lineHeight: "140%",
letterSpacing: "0%",
textAlign: "center",
color: "#5F6C7B",
}}
/>
</Button>
</div>
<Button
style={{
marginTop: 16,
borderRadius: 100,
width: "100%",
height: 40,
border: "none",
backgroundColor: "#030014",
}}
icon={<EditIcon className={styles.editIcon} color="#FFF" />}
iconPlacement="start"
>
<ProText
style={{
color: "#FFF",
fontWeight: 500,
fontStyle: "Medium",
fontSize: 14,
lineHeight: "140%",
letterSpacing: "0%",
}}
>
{t("cardDetails.costumeAmount")}
</ProText>
</Button>
</ProInputCard>
);
}

View File

@@ -0,0 +1,5 @@
.customerInformationCard {
display: flex;
flex-direction: column;
gap: 16px;
}

View File

@@ -0,0 +1,56 @@
import ProInputCard from "components/ProInputCard/ProInputCard.tsx";
import ProPhoneInput from "components/ProPhoneInput";
import { selectCart, updateGiftDetails } from "features/order/orderSlice";
import { useTranslation } from "react-i18next";
import { useAppDispatch, useAppSelector } from "redux/hooks";
import { Form, Input } from "antd";
import styles from "./ReceivernformationCard.module.css";
import TextArea from "antd/es/input/TextArea";
export default function ReceivernformationCard() {
const { t } = useTranslation();
const dispatch = useAppDispatch();
const { giftDetails } = useAppSelector(selectCart);
return (
<ProInputCard title={t("cardDetails.receiverInformation")}>
<div className={styles.customerInformationCard}>
<Form.Item name="receiverName">
<Input
placeholder={t("cardDetails.receiverName")}
size="large"
autoFocus={false}
style={{ padding: "7px 11px", height: 48, borderRadius: 888 }}
value={giftDetails?.receiverName}
onChange={(e) => {
dispatch(updateGiftDetails({ receiverName: e.target.value }));
}}
/>
</Form.Item>
<ProPhoneInput
propName="receiverPhone"
value={giftDetails?.receiverPhone}
onChange={(e) => {
dispatch(updateGiftDetails({ receiverPhone: e }));
}}
/>
<Form.Item name="message">
<TextArea
placeholder={t("address.message")}
size="large"
style={{
fontSize: 14,
borderRadius: 10,
}}
rows={2}
autoFocus={false}
value={giftDetails?.message}
onChange={(e) => {
dispatch(updateGiftDetails({ message: e.target.value }));
}}
/>
</Form.Item>
</div>
</ProInputCard>
);
}

View File

@@ -0,0 +1,5 @@
.customerInformationCard {
display: flex;
flex-direction: column;
gap: 16px;
}

View File

@@ -0,0 +1,55 @@
import ProInputCard from "components/ProInputCard/ProInputCard.tsx";
import ProPhoneInput from "components/ProPhoneInput";
import { selectCart, updateGiftDetails } from "features/order/orderSlice";
import { useTranslation } from "react-i18next";
import { useAppDispatch, useAppSelector } from "redux/hooks";
import { Checkbox, Form, Input } from "antd";
import styles from "./SenderformationCard.module.css";
import TextArea from "antd/es/input/TextArea";
import ProText from "components/ProText";
export default function SenderformationCard() {
const { t } = useTranslation();
const dispatch = useAppDispatch();
const { giftDetails } = useAppSelector(selectCart);
return (
<ProInputCard title={t("cardDetails.receiverInformation")}>
<div className={styles.customerInformationCard}>
<Form.Item name="receiverName">
<Input
placeholder={t("cardDetails.yourName")}
size="large"
autoFocus={false}
style={{ padding: "7px 11px", height: 48, borderRadius: 888 }}
value={giftDetails?.senderName}
onChange={(e) => {
dispatch(updateGiftDetails({ senderName: e.target.value }));
}}
/>
</Form.Item>
<ProPhoneInput
propName="yourPhone"
value={giftDetails?.senderPhone}
onChange={(e) => {
dispatch(updateGiftDetails({ senderPhone: e }));
}}
/>
<Form.Item name="isSecret" colon={false}>
<Checkbox
style={{
fontSize: 14,
}}
autoFocus={false}
checked={giftDetails?.isSecret}
onChange={(e) => {
dispatch(updateGiftDetails({ isSecret: e.target.checked }));
}}
>
<ProText>{t("cardDetails.keepMyNameSecret")}</ProText>
</Checkbox>
</Form.Item>
</div>
</ProInputCard>
);
}

View File

@@ -0,0 +1,9 @@
export interface EGiftCard {
id: number;
image: string;
restorant_id: number | null;
is_available: number;
created_at: string;
updated_at: string;
imageURL: string;
}