Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7c12b19511 | |||
| a14be45b76 |
@@ -9,7 +9,7 @@ import { extractErrorMessage } from "@/lib/func";
|
|||||||
import { Card, Icon, Wallet2 } from "iconsax-react";
|
import { Card, Icon, Wallet2 } from "iconsax-react";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import { glassSurfaceCard } from "@/lib/styles/glassSurface";
|
import { glassSurfaceCard } from "@/lib/styles/glassSurface";
|
||||||
import { useCallback, useMemo, useRef } from "react";
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { PaymentMethodEnum } from "../../enum/Enum";
|
import { PaymentMethodEnum } from "../../enum/Enum";
|
||||||
import { useGetPaymentMethod } from "../../hooks/useOrderData";
|
import { useGetPaymentMethod } from "../../hooks/useOrderData";
|
||||||
@@ -20,6 +20,11 @@ type PaymentSectionProps = {
|
|||||||
onPaymentTypeChange: (id: string) => void;
|
onPaymentTypeChange: (id: string) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type CreditCardOption = {
|
||||||
|
cardNumber: string;
|
||||||
|
cardOwner: string;
|
||||||
|
};
|
||||||
|
|
||||||
const getIconByMethod = (method: PaymentMethodEnum): Icon => {
|
const getIconByMethod = (method: PaymentMethodEnum): Icon => {
|
||||||
if (method === PaymentMethodEnum.Online || method === PaymentMethodEnum.CreditCard) {
|
if (method === PaymentMethodEnum.Online || method === PaymentMethodEnum.CreditCard) {
|
||||||
return Card;
|
return Card;
|
||||||
@@ -40,6 +45,7 @@ export const PaymentSection = ({ paymentType, onPaymentTypeChange }: PaymentSect
|
|||||||
const { data: cartData } = useGetCartItems(isSuccess);
|
const { data: cartData } = useGetCartItems(isSuccess);
|
||||||
const { mutateAsync: singleUpload, isPending: isUploading } = useSingleUpload();
|
const { mutateAsync: singleUpload, isPending: isUploading } = useSingleUpload();
|
||||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||||
|
const [selectedCardNumber, setSelectedCardNumber] = useState<string>("");
|
||||||
|
|
||||||
const walletAmount = userWallet?.data?.balance ?? 0;
|
const walletAmount = userWallet?.data?.balance ?? 0;
|
||||||
const totalAmount = cartData?.data?.total ?? 0;
|
const totalAmount = cartData?.data?.total ?? 0;
|
||||||
@@ -69,6 +75,11 @@ export const PaymentSection = ({ paymentType, onPaymentTypeChange }: PaymentSect
|
|||||||
method: item.method,
|
method: item.method,
|
||||||
cardNumber: item.cardNumber,
|
cardNumber: item.cardNumber,
|
||||||
cardOwner: item.cardOwner,
|
cardOwner: item.cardOwner,
|
||||||
|
cards:
|
||||||
|
item.cards?.map((card) => ({
|
||||||
|
cardNumber: card.cardNumber,
|
||||||
|
cardOwner: card.owner,
|
||||||
|
})) ?? [],
|
||||||
description: item.description,
|
description: item.description,
|
||||||
isWallet: item.method === PaymentMethodEnum.Wallet,
|
isWallet: item.method === PaymentMethodEnum.Wallet,
|
||||||
}));
|
}));
|
||||||
@@ -85,11 +96,45 @@ export const PaymentSection = ({ paymentType, onPaymentTypeChange }: PaymentSect
|
|||||||
if (selected?.method !== PaymentMethodEnum.CreditCard) {
|
if (selected?.method !== PaymentMethodEnum.CreditCard) {
|
||||||
setPaymentDesc("");
|
setPaymentDesc("");
|
||||||
setPaymentAttachments([]);
|
setPaymentAttachments([]);
|
||||||
|
setSelectedCardNumber("");
|
||||||
}
|
}
|
||||||
onPaymentTypeChange(value);
|
onPaymentTypeChange(value);
|
||||||
setIsSelectedPayment(true);
|
setIsSelectedPayment(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const creditCardOptions = useMemo<CreditCardOption[]>(() => {
|
||||||
|
if (!selectedCreditCard) return [];
|
||||||
|
|
||||||
|
const normalizedCards = selectedCreditCard.cards.filter(
|
||||||
|
(card): card is CreditCardOption => !!card.cardNumber && !!card.cardOwner,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (normalizedCards.length > 0) return normalizedCards;
|
||||||
|
|
||||||
|
if (selectedCreditCard.cardNumber && selectedCreditCard.cardOwner) {
|
||||||
|
return [{ cardNumber: selectedCreditCard.cardNumber, cardOwner: selectedCreditCard.cardOwner }];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [];
|
||||||
|
}, [selectedCreditCard]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (creditCardOptions.length === 0) {
|
||||||
|
setSelectedCardNumber("");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const hasSelectedCard = creditCardOptions.some((item) => item.cardNumber === selectedCardNumber);
|
||||||
|
if (!hasSelectedCard) {
|
||||||
|
setSelectedCardNumber(creditCardOptions[0].cardNumber);
|
||||||
|
}
|
||||||
|
}, [creditCardOptions, selectedCardNumber]);
|
||||||
|
|
||||||
|
const selectedCard = useMemo(
|
||||||
|
() => creditCardOptions.find((item) => item.cardNumber === selectedCardNumber) ?? null,
|
||||||
|
[creditCardOptions, selectedCardNumber],
|
||||||
|
);
|
||||||
|
|
||||||
const handleCopyCardNumber = useCallback(
|
const handleCopyCardNumber = useCallback(
|
||||||
async (cardNumber: string) => {
|
async (cardNumber: string) => {
|
||||||
try {
|
try {
|
||||||
@@ -147,24 +192,49 @@ export const PaymentSection = ({ paymentType, onPaymentTypeChange }: PaymentSect
|
|||||||
|
|
||||||
{selectedCreditCard && (
|
{selectedCreditCard && (
|
||||||
<div className="mt-6 pt-4 border-t border-border flex flex-col gap-4">
|
<div className="mt-6 pt-4 border-t border-border flex flex-col gap-4">
|
||||||
{(selectedCreditCard.cardOwner || selectedCreditCard.cardNumber) && (
|
{creditCardOptions.length > 0 && (
|
||||||
<div className="flex items-start justify-between gap-4">
|
<div className="space-y-3">
|
||||||
{selectedCreditCard.cardOwner && (
|
<p className="text-xs text-gray-500 dark:text-gray-400">
|
||||||
<div>
|
{`تعداد کارت های مقصد: ${creditCardOptions.length}`}
|
||||||
<span className="text-xs text-gray-500 dark:text-gray-400">{tOrderDetail("SectionPayment.CreditCard.CardOwnerLabel")}</span>
|
</p>
|
||||||
<p className="text-sm font-medium mt-1">{selectedCreditCard.cardOwner}</p>
|
{creditCardOptions.map((card) => {
|
||||||
|
const isActiveCard = selectedCardNumber === card.cardNumber;
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
key={card.cardNumber}
|
||||||
|
type="button"
|
||||||
|
onClick={() => setSelectedCardNumber(card.cardNumber)}
|
||||||
|
className={`w-full rounded-normal border px-3 py-2 text-right transition-colors ${
|
||||||
|
isActiveCard ? "border-primary bg-primary/5" : "border-border hover:border-primary/50"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<span className="block text-xs text-gray-500 dark:text-gray-400">{card.cardOwner}</span>
|
||||||
|
<span className="block text-sm font-medium mt-1" dir="ltr">
|
||||||
|
{card.cardNumber}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{selectedCreditCard.cardNumber && (
|
{selectedCard && (
|
||||||
|
<div className="flex items-start justify-between gap-4">
|
||||||
|
{selectedCard.cardOwner && (
|
||||||
|
<div>
|
||||||
|
<span className="text-xs text-gray-500 dark:text-gray-400">{tOrderDetail("SectionPayment.CreditCard.CardOwnerLabel")}</span>
|
||||||
|
<p className="text-sm font-medium mt-1">{selectedCard.cardOwner}</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{selectedCard.cardNumber && (
|
||||||
<div className="text-left">
|
<div className="text-left">
|
||||||
<span className="text-xs text-gray-500 dark:text-gray-400">{tOrderDetail("SectionPayment.CreditCard.CardNumberLabel")}</span>
|
<span className="text-xs text-gray-500 dark:text-gray-400">{tOrderDetail("SectionPayment.CreditCard.CardNumberLabel")}</span>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="block text-sm font-medium mt-1 cursor-pointer hover:text-primary transition-colors"
|
className="block text-sm font-medium mt-1 cursor-pointer hover:text-primary transition-colors"
|
||||||
dir="ltr"
|
dir="ltr"
|
||||||
onClick={() => handleCopyCardNumber(selectedCreditCard.cardNumber!)}
|
onClick={() => handleCopyCardNumber(selectedCard.cardNumber)}
|
||||||
>
|
>
|
||||||
{selectedCreditCard.cardNumber}
|
{selectedCard.cardNumber}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -116,10 +116,21 @@ export interface PaymentMethodResponse {
|
|||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
order: number;
|
order: number;
|
||||||
merchantId: string | null;
|
merchantId: string | null;
|
||||||
|
cards?: PaymentMethodCard[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export type PaymentMethodsResponse = BaseResponse<PaymentMethodResponse[]>;
|
export type PaymentMethodsResponse = BaseResponse<PaymentMethodResponse[]>;
|
||||||
|
|
||||||
|
export interface PaymentMethodCard {
|
||||||
|
id: string;
|
||||||
|
createdAt: string;
|
||||||
|
updatedAt: string;
|
||||||
|
deletedAt: string | null;
|
||||||
|
paymentMethod: string;
|
||||||
|
cardNumber: string;
|
||||||
|
owner: string;
|
||||||
|
}
|
||||||
|
|
||||||
export type CheckoutStoreType = {
|
export type CheckoutStoreType = {
|
||||||
isSelectedAddress: boolean;
|
isSelectedAddress: boolean;
|
||||||
setIsSelectedAddress: (value: boolean) => void;
|
setIsSelectedAddress: (value: boolean) => void;
|
||||||
|
|||||||
@@ -30,6 +30,11 @@ function AboutPage() {
|
|||||||
|
|
||||||
const restaurant = aboutData?.data;
|
const restaurant = aboutData?.data;
|
||||||
const schedules = schedulesData?.data || [];
|
const schedules = schedulesData?.data || [];
|
||||||
|
const phoneList = Array.isArray(restaurant?.phones)
|
||||||
|
? restaurant.phones.filter((phone): phone is string => Boolean(phone))
|
||||||
|
: restaurant?.phone
|
||||||
|
? [restaurant.phone]
|
||||||
|
: [];
|
||||||
|
|
||||||
const isLoading = aboutLoading || reviewsLoading || schedulesLoading;
|
const isLoading = aboutLoading || reviewsLoading || schedulesLoading;
|
||||||
|
|
||||||
@@ -122,16 +127,17 @@ function AboutPage() {
|
|||||||
)}
|
)}
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{(restaurant.phone || restaurant.telegram || restaurant.whatsapp || restaurant.instagram) && (
|
{(phoneList.length > 0 || restaurant.telegram || restaurant.whatsapp || restaurant.instagram) && (
|
||||||
<section
|
<section
|
||||||
className={glassSurfaceCard('pt-3 pb-3.5 px-[16px] mt-4 grid grid-cols-3 items-center')}>
|
className={glassSurfaceCard('pt-3 pb-3.5 px-[16px] mt-4 grid grid-cols-3 items-center')}>
|
||||||
<h2 className='text-sm2 font-medium leading-5'>ارتباط</h2>
|
<h2 className='text-sm2 font-medium leading-5'>ارتباط</h2>
|
||||||
<div className='col-span-1 text-center flex justify-center gap-4'>
|
<div className='col-span-2 text-center flex justify-center flex-wrap gap-2'>
|
||||||
{restaurant.phone &&
|
{phoneList.map((phone) => (
|
||||||
<a href={`tel://${restaurant.phone}`} className='bg-[#EAEDF5] dark:bg-neutral-700 p-2 rounded-normal'>
|
<a key={phone} href={`tel://${phone}`} className='bg-[#EAEDF5] dark:bg-neutral-700 px-2 py-1.5 rounded-normal inline-flex items-center gap-1.5'>
|
||||||
<CallCalling className='stroke-foreground' size={24} />
|
<CallCalling className='stroke-foreground' size={24} />
|
||||||
|
<span dir='ltr' className='text-xs leading-5'>{phone}</span>
|
||||||
</a>
|
</a>
|
||||||
}
|
))}
|
||||||
{restaurant.telegram &&
|
{restaurant.telegram &&
|
||||||
<a href={`https://t.me/${restaurant.telegram}`} className='bg-[#EAEDF5] dark:bg-neutral-700 p-2 rounded-normal'>
|
<a href={`https://t.me/${restaurant.telegram}`} className='bg-[#EAEDF5] dark:bg-neutral-700 p-2 rounded-normal'>
|
||||||
<TelegramIcon className='stroke-foreground' width={24} height={24} />
|
<TelegramIcon className='stroke-foreground' width={24} height={24} />
|
||||||
|
|||||||
@@ -32,7 +32,8 @@ export interface Restaurant {
|
|||||||
isActive: boolean;
|
isActive: boolean;
|
||||||
establishedYear: number | null;
|
establishedYear: number | null;
|
||||||
phoneNumber: string | null;
|
phoneNumber: string | null;
|
||||||
phone: string;
|
phone?: string | null;
|
||||||
|
phones?: string[];
|
||||||
instagram: string | null;
|
instagram: string | null;
|
||||||
telegram: string | null;
|
telegram: string | null;
|
||||||
whatsapp: string | null;
|
whatsapp: string | null;
|
||||||
|
|||||||
Reference in New Issue
Block a user