gift: working on UI ans styles
This commit is contained in:
76
src/pages/CardDetails/CardDetails.module.css
Normal file
76
src/pages/CardDetails/CardDetails.module.css
Normal 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;
|
||||
}
|
||||
153
src/pages/CardDetails/CardDetails.tsx
Normal file
153
src/pages/CardDetails/CardDetails.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
81
src/pages/CardDetails/components/ECardList.tsx
Normal file
81
src/pages/CardDetails/components/ECardList.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
.customerInformationCard {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
.customerInformationCard {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
9
src/pages/CardDetails/type.ts
Normal file
9
src/pages/CardDetails/type.ts
Normal 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;
|
||||
}
|
||||
Reference in New Issue
Block a user