Compare commits

..

2 Commits

Author SHA1 Message Date
hamid zarghami 7c12b19511 multi card
Build and Deploy Docker Images / build_and_deploy (push) Has been cancelled
2026-06-29 12:19:44 +03:30
hamid zarghami a14be45b76 multi phones 2026-06-29 12:15:52 +03:30
4 changed files with 101 additions and 13 deletions
@@ -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="space-y-3">
<p className="text-xs text-gray-500 dark:text-gray-400">
{`تعداد کارت های مقصد: ${creditCardOptions.length}`}
</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>
)}
{selectedCard && (
<div className="flex items-start justify-between gap-4"> <div className="flex items-start justify-between gap-4">
{selectedCreditCard.cardOwner && ( {selectedCard.cardOwner && (
<div> <div>
<span className="text-xs text-gray-500 dark:text-gray-400">{tOrderDetail("SectionPayment.CreditCard.CardOwnerLabel")}</span> <span className="text-xs text-gray-500 dark:text-gray-400">{tOrderDetail("SectionPayment.CreditCard.CardOwnerLabel")}</span>
<p className="text-sm font-medium mt-1">{selectedCreditCard.cardOwner}</p> <p className="text-sm font-medium mt-1">{selectedCard.cardOwner}</p>
</div> </div>
)} )}
{selectedCreditCard.cardNumber && ( {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;
+11 -5
View File
@@ -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} />
+2 -1
View File
@@ -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;