complete add new card

This commit is contained in:
Alihaghighattalab
2024-08-08 18:13:57 +03:30
parent 7dfe6f8b8c
commit 66e52e079a
14 changed files with 519 additions and 50 deletions
+7
View File
@@ -19,6 +19,7 @@
"@types/react-dom": "^18.3.0",
"clsx": "^2.1.1",
"iconsax-react": "^0.0.8",
"iranianbanklogos": "^3.1.2",
"jalaali-js": "^1.2.7",
"react": "^18.3.1",
"react-dom": "^18.3.1",
@@ -10278,6 +10279,12 @@
"node": ">= 10"
}
},
"node_modules/iranianbanklogos": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/iranianbanklogos/-/iranianbanklogos-3.1.2.tgz",
"integrity": "sha512-F6GnrhrC9UO1c4CW8uJloXAkg7pgx/Y8ok6qixb6L0knwIanG6Idudi8/Ebuu3c9PVd59wDTtTZQvtszr+Yyvw==",
"license": "ISC"
},
"node_modules/is-arguments": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz",
+1
View File
@@ -14,6 +14,7 @@
"@types/react-dom": "^18.3.0",
"clsx": "^2.1.1",
"iconsax-react": "^0.0.8",
"iranianbanklogos": "^3.1.2",
"jalaali-js": "^1.2.7",
"react": "^18.3.1",
"react-dom": "^18.3.1",
+40
View File
@@ -0,0 +1,40 @@
import { Dialog, DialogBackdrop, DialogPanel, DialogTitle } from '@headlessui/react'
import React, { FC } from 'react'
type Props = {
isOpen: boolean,
close: () => void,
children: React.ReactNode,
title: string,
subTitle: string,
icon: React.ReactNode
}
export const DialogComponent: FC<Props> = ({ isOpen, children, close, title, subTitle, icon }) => {
return (
<>
<Dialog open={isOpen} as="div" className="relative z-10 focus:outline-none" onClose={close}>
<DialogBackdrop className="fixed inset-0 bg-black/30" />
<div className="fixed inset-0 z-10 w-screen overflow-y-auto">
<div className="flex min-h-full items-center justify-center p-4">
<DialogPanel
transition
className="w-full max-w-md rounded-[40px] bg-white shadow-sm border border-secondary-text-color/10 p-6 backdrop-blur-2xl duration-300 ease-out data-[closed]:transform-[scale(95%)] data-[closed]:opacity-0"
>
<DialogTitle as="h3" className="text-center flex flex-col gap-[18px] items-center justify-center">
<div className="size-[72px] border border-border-color rounded-full flex justify-center items-center">
{icon}
</div>
<div className="flex flex-col gap-[6px]">
<p className="font-medium text-[20px] text-primary-text-color">{title}</p>
<p className="font-normal text-sm text-secondary-text-color">{subTitle}</p>
</div>
</DialogTitle>
{children}
</DialogPanel>
</div>
</div>
</Dialog>
</>
)
}
+11 -5
View File
@@ -1,26 +1,32 @@
import { Eye, EyeSlash } from "iconsax-react"
import React, { FC, useState } from "react"
import { Input as InputComponent } from '@headlessui/react'
import { findBankByPAN } from "../../utility/cart"
import clsx from "clsx"
type Props = {
type?: React.HTMLInputTypeAttribute,
placeholder: string,
icon?: React.ReactNode,
field?: any
ref?: any
field?: any,
ref?: any,
className?: string
}
export const Input: FC<Props> = ({ placeholder, type = "text", icon, field, ref }) => {
export const Input: FC<Props> = ({ placeholder, type = "text", icon, field, ref, className }) => {
const [hidden, setHidden] = useState<boolean>(false)
const handleHidden = () => setHidden(prev => !prev)
return (
<div className={`relative w-full`}>
<div className="absolute top-[18.5px] left-5 grid h-5 w-5 place-items-center text-blue-gray-500">
<div className={clsx("absolute top-[18.5px] left-5 grid h-5 w-5 place-items-center text-blue-gray-500", {
"top-[14px] left-7": placeholder === "شماره کارت" && field.value
})}>
{type === "password" ?
!hidden ? <Eye size="23" color="#777577" onClick={handleHidden} /> : <EyeSlash size="23" color="#777577" onClick={handleHidden} />
: icon}
{placeholder === "شماره کارت" && field.value && findBankByPAN(field.value).icon}
</div>
<InputComponent ref={ref} {...field} type={type === "password" && hidden ? "text" : type} placeholder={placeholder} className="input-style min-w-full" />
<InputComponent ref={ref} {...field} type={type === "password" && hidden ? "text" : type} placeholder={placeholder} className={`input-style min-w-full ${className}`} />
</div>
)
}
@@ -0,0 +1,69 @@
import { CardAdd } from "iconsax-react"
import { DialogComponent } from "../common/dialog"
import { FC } from "react"
import { Input } from "../common/input"
import { Button } from "@headlessui/react"
import { bankCarts, findBankByPAN } from "../../utility/cart"
import { Controller, SubmitHandler, useForm } from "react-hook-form"
import { AddNewCartInterface } from "../../types"
import { yupResolver } from "@hookform/resolvers/yup"
import { AddNewCardSchema } from "../../schema"
import { ErrorComponent } from "../common/error"
type Props = {
isOpen: boolean,
close: () => void
}
export const AddNewCardDialog: FC<Props> = ({ isOpen, close }) => {
const { handleSubmit, formState: { errors }, control } = useForm<AddNewCartInterface>({
resolver: yupResolver(AddNewCardSchema)
})
const createNewCard: SubmitHandler<AddNewCartInterface> = async (data) => {
const finalData: any = {
id: bankCarts?.length + 1,
bankName: findBankByPAN(data.cartNumber).name,
bankPersianName: findBankByPAN(data.cartNumber).name_farsi,
cartNumber: data?.cartNumber.toString(),
cartOwner: `${data.name} ${data?.family}`,
shabaNumber: data?.shabaNumber,
}
bankCarts.push(finalData)
close()
}
return (
<DialogComponent isOpen={isOpen} close={close} title="ایجاد حساب بانکی جدید" subTitle="لطفا اطلاعات را با دقت وارد نمایید" icon={<CardAdd color="#11212D" className="size-[50px]" />}>
<form onSubmit={handleSubmit(createNewCard)} className="flex flex-col gap-[42px] mt-9">
<div className="w-full flex flex-col gap-6">
<Controller control={control} name="name" render={({ field }) => (
<div>
<Input placeholder="نام صاحب کارت" field={field} className="bg-auth-form" />
<ErrorComponent show={errors.name} message={errors.name?.message} />
</div>
)} />
<Controller control={control} name="family" render={({ field }) => (
<div>
<Input field={field} placeholder="نام خانوادگی صاحب کارت" className="bg-auth-form" />
<ErrorComponent show={errors.family} message={errors.family?.message} />
</div>
)} />
<Controller control={control} name="cartNumber" render={({ field }) => (
<div>
<Input field={field} placeholder="شماره کارت" className="bg-auth-form" />
<ErrorComponent show={errors.cartNumber} message={errors.cartNumber?.message} />
</div>
)} />
<Controller control={control} name="shabaNumber" render={({ field }) => (
<div>
<Input field={field} placeholder="شماره شبا" className="bg-auth-form" />
<ErrorComponent show={errors.shabaNumber} message={errors.shabaNumber?.message} />
</div>
)} />
</div>
<div className="flex flex-col gap-[6px]">
<Button type="submit" className="w-full bg-primary-color text-white">ایجاد حساب</Button>
<Button onClick={close} className="w-full bg-transparent text-primary-color">لغو</Button>
</div>
</form>
</DialogComponent>
)
}
@@ -1,18 +1,19 @@
import { Edit, Trash } from "iconsax-react"
import { formatCartNumber } from "../../utility/cart"
import { BankCartInterface } from "../../types"
import { FC } from "react"
import { findBankByPAN, formatCartNumber } from "../../utility/cart"
type Props = {
item: BankCartInterface
}
export const BankCart: FC<Props> = ({ item }) => {
console.log("item=>", item)
return (
<div className="w-full flex flex-col justify-between rounded-[40px] bg-auth-form p-[18px] cursor-pointer bank-bg bg-bank-mehr min-h-[226px]">
<div className="flex flex-row justify-between items-center">
<div className="flex flex-row gap-3 items-center">
<img src={`/svgs/banks/icons/${item?.bankName}.svg`} alt={item?.bankName} />
{findBankByPAN(item.cartNumber).icon}
<p className="text-lg font-medium text-primary-text-color">بانک {item?.bankPersianName}</p>
</div>
<div className="flex flex-row gap-3 items-center">
@@ -20,7 +21,7 @@ export const BankCart: FC<Props> = ({ item }) => {
<Edit color="#777577" />
</div>
</div>
<p className="font-medium text-2xl text-primary-text-color text-center" style={{ direction: "ltr" }}>{formatCartNumber(1234567891011121)}</p>
<p className="font-medium text-2xl text-primary-text-color text-center" style={{ direction: "ltr" }}>{formatCartNumber(item?.cartNumber)}</p>
<div className="flex flex-row items-center justify-between">
<p className="text-sm text-secondary-text-color font-medium">{item?.cartOwner}</p>
<p className="font-medium text-lg text-primary-text-color">IR {item?.shabaNumber}</p>
@@ -1,8 +1,11 @@
import { Add } from "iconsax-react"
export const CreateNewCart = () => {
import { FC } from "react"
type Props = {
handleOpenDialog: () => void
}
export const CreateNewCart: FC<Props> = ({ handleOpenDialog }) => {
return (
<div className="bg-auth-form w-full h-full flex items-center justify-center rounded-[40px] p-[18px] py-10 cursor-pointer min-h-[226px]">
<div className="bg-auth-form w-full h-full flex items-center justify-center rounded-[40px] p-[18px] py-10 cursor-pointer min-h-[226px]" onClick={handleOpenDialog}>
<div className="flex flex-col gap-6 items-center">
<div className="size-16 rounded-full flex justify-center items-center bg-secondary-color">
<Add color="#11212D" className="size-10" />
@@ -1,14 +1,21 @@
import { useState } from "react"
import { BankCartInterface } from "../../types"
import { bankCarts } from "../../utility/cart"
import { BankCart } from "./bank-cart"
import { CreateNewCart } from "./create-new-cart"
import { AddNewCardDialog } from "./add-new-cart-dialog"
export const ManagementBankAccounts = () => {
const [showDialog, setShowDialog] = useState<boolean>(false)
const handleCloseDialog = () => setShowDialog(false)
const handleOpenDialog = () => setShowDialog(true)
return (
<>
<div className="w-full grid grid-cols-1 xl:grid-cols-2 2xl:grid-cols-3 gap-6">
<CreateNewCart />
<CreateNewCart handleOpenDialog={handleOpenDialog} />
{bankCarts?.map((item: BankCartInterface) => <BankCart key={item?.id} item={item} />)}
</div>
<AddNewCardDialog isOpen={showDialog} close={handleCloseDialog} />
</>
)
}
+1
View File
@@ -3,6 +3,7 @@ import ReactDOM from 'react-dom/client';
import { RouterProvider } from 'react-router-dom';
import { router } from './router';
import './index.css'
import "iranianbanklogos/dist/ibl.css";
const root = ReactDOM.createRoot(
+15
View File
@@ -96,3 +96,18 @@ export const MyAccountSchema = yup.object({
.oneOf([yup.ref('newPassword')], "رمز عبور و تکرار آن باید یکسان باشند")
.required("تکرار رمز عبور الزامی میاشد"),
})
export const AddNewCardSchema = yup.object({
name: yup.string().required("وارد کردن نام الزامی میباشد").min(3, "نام نمیتواند کمتر از 3 کاراکتر باشد").max(20, "نام نمیتواند بیشتر از 20 کاراکتر باشد"),
family: yup.string().required("وارد کردن نام خانوادگی الزامی میباشد").min(3, "نام خانوادگی نمیتواند کمتر از 3 کاراکتر باشد").max(25, "نام خانوادگی نمیتواند بیشتر از 25 کاراکتر باشد"),
cartNumber: yup.string()
.required("وارد کردن شماره کارت الزامی میباشد")
.min(16, "شماره کارت نمیتوند کمتر از 16 رقم باشد")
.max(16, " شماره کارت نمیتواند بیشتر از 16 رقم باشد")
.matches(/^[0-9]{16}$/, "شماره کارت نامعتبر"),
shabaNumber: yup.string()
.required("وارد کردن شماره شبا الزامی میباشد")
.min(24, "شماره شبا نمیتوند کمتر از 24 رقم باشد")
.max(24, " شماره شبا نمیتواند بیشتر از 24 رقم باشد")
.matches(/^[0-9]{24}$/, "شماره شبا نامعتبر"),
})
+16 -1
View File
@@ -80,7 +80,22 @@ export interface BankCartInterface {
id: number,
bankName: BankName,
bankPersianName: PersianBankName,
cartNumber: number | string,
cartNumber: string,
cartOwner: string,
shabaNumber: string,
}
export interface BanksInterface {
name: string,
name_farsi: string,
icon: React.ReactNode,
iban_nbc: string,
pan_iin: string[] | []
}
export interface AddNewCartInterface {
name: string
family: string
cartNumber: string
shabaNumber: string
}
-32
View File
@@ -1,32 +0,0 @@
import { BankCartInterface } from "../types";
export const formatCartNumber = (num: number | string) => {
return num.toString().replace(/\B(?=(\d{4})+(?!\d))/g, `          `);
}
export const bankCarts: BankCartInterface[] | [] = [
{
id: 0,
bankName: 'mehr',
bankPersianName: "مهر",
cartNumber: 4682563898563372,
cartOwner: "علی مصلحی",
shabaNumber: "270700001000116102861001"
},
{
id: 1,
bankName: 'saderat',
bankPersianName: "صادرات",
cartNumber: 4682563898563372,
cartOwner: "علی مصلحی",
shabaNumber: "270700001000116102861001"
},
{
id: 2,
bankName: 'pasargad',
bankPersianName: "پاسارگاد",
cartNumber: 4682563898563372,
cartOwner: "علی مصلحی",
shabaNumber: "270700001000116102861001"
},
]
+335
View File
@@ -0,0 +1,335 @@
import { BankCartInterface, BanksInterface } from "../types";
export const formatCartNumber = (num: string) => {
return num?.toString().replace(/\B(?=(\d{4})+(?!\d))/g, `    `);
}
export let bankCarts: BankCartInterface[] | [] | any = []
const banks: BanksInterface[] | [] = [
{
name: "ansar",
name_farsi: "انصار",
icon: <i className="ibl32 ibl-ansar" />,
iban_nbc: "063",
pan_iin: [
"627381"
]
},
{
name: "ayandeh",
name_farsi: "آینده",
icon: <i className="ibl32 ibl-ba" />,
iban_nbc: "062",
pan_iin: [
"636214"
]
},
{
name: "bank_markazi",
name_farsi: "بانک مرکزی",
icon: "Bank_Markazi.svg",
iban_nbc: "010",
pan_iin: [
"636795"
]
},
{
name: "dey",
name_farsi: "دی",
icon: <i className="ibl32 ibl-day" />,
iban_nbc: "066",
pan_iin: [
"502938"
]
},
{
name: "eghtesad_novin",
name_farsi: "اقتصاد نوین",
icon: <i className="ibl32 ibl-en" />,
iban_nbc: "055",
pan_iin: [
"627412",
"627884"
]
},
{
name: "gardeshgari",
name_farsi: "گردشگری",
icon: <i className="ibl32 ibl-tourism" />,
iban_nbc: "064",
pan_iin: [
"505416"
]
},
{
name: "ghavamin",
name_farsi: "قوامین",
icon: <i className="ibl32 ibl-ghbi" />,
iban_nbc: "052",
pan_iin: [
"639599"
]
},
{
name: "hekmat",
name_farsi: "حکمت",
icon: <i className="ibl32 ibl-hi" />,
iban_nbc: "065",
pan_iin: [
"636949"
]
},
{
name: "iran_zamin",
name_farsi: "ایران‌زمین",
icon: <i className="ibl32 ibl-iz" />,
iban_nbc: "069",
pan_iin: [
"505785",
]
},
{
name: "karafarin",
name_farsi: "کارآفرین",
icon: <i className="ibl32 ibl-kar" />,
iban_nbc: "053",
pan_iin: [
"502910",
"627488"
]
},
{
name: "keshavarzi",
name_farsi: "کشاورزی",
icon: <i className="ibl32 ibl-bki" />,
iban_nbc: "016",
pan_iin: [
"603770",
"639217"
]
},
{
name: "khavar_mianeh",
name_farsi: "خاورمیانه",
icon: <i className="ibl32 ibl-me" />,
iban_nbc: "078",
pan_iin: [
"505809"
]
},
{
name: "maskan",
name_farsi: "مسکن",
icon: <i className="ibl32 ibl-maskan" />,
iban_nbc: "014",
pan_iin: [
"628023"
]
},
{
name: "mehr_iran",
name_farsi: "مهر ایران",
icon: <i className="ibl32 ibl-miran" />,
iban_nbc: "060",
pan_iin: [
"606373"
]
},
{
name: "melall",
name_farsi: "ملل",
icon: <i className="ibl32 ibl-melal" />,
iban_nbc: "075",
pan_iin: [
"606256"
]
},
{
name: "mellat",
name_farsi: "ملت",
icon: <i className="ibl32 ibl-mellat" />,
iban_nbc: "012",
pan_iin: [
"610433",
"991975"
]
},
{
name: "melli",
name_farsi: "ملی",
icon: <i className="ibl32 ibl-bmi" />,
iban_nbc: "017",
pan_iin: [
"603799",
"589905",
"170019"
]
},
{
name: "noor",
name_farsi: "نور",
icon: "Noor.svg",
iban_nbc: "080",
pan_iin: [
"507677"
]
},
{
name: "parsian",
name_farsi: "پارسیان",
icon: <i className="ibl32 ibl-parsian" />,
iban_nbc: "054",
pan_iin: [
"622106",
"639194"
]
},
{
name: "pasargad",
name_farsi: "پاسارگاد",
icon: <i className="ibl32 ibl-bpi" />,
iban_nbc: "057",
pan_iin: [
"502229",
"639347"
]
},
{
name: "postbank",
name_farsi: "پست‌بانک",
icon: <i className="ibl32 ibl-post" />,
iban_nbc: "021",
pan_iin: [
"627760"
]
},
{
name: "refah",
name_farsi: "رفاه",
icon: <i className="ibl32 ibl-rb2" />,
iban_nbc: "013",
pan_iin: [
"589463"
]
},
{
name: "resalat",
name_farsi: "رسالت",
icon: <i className="ibl32 ibl-resalat" />,
iban_nbc: "070",
pan_iin: [
"504172"
]
},
{
name: "saderat",
name_farsi: "صادرات",
icon: <i className="ibl32 ibl-bsi" />,
iban_nbc: "019",
pan_iin: [
"603769"
]
},
{
name: "saman",
name_farsi: "سامان",
icon: <i className="ibl32 ibl-sb" />,
iban_nbc: "056",
pan_iin: [
"621986"
]
},
{
name: "sanat_madan",
name_farsi: "صنعت و معدن",
icon: <i className="ibl32 ibl-bim" />,
iban_nbc: "011",
pan_iin: [
"627961"
]
},
{
name: "sarmayeh",
name_farsi: "سرمایه",
icon: <i className="ibl32 ibl-sarmayeh" />,
iban_nbc: "058",
pan_iin: [
"639607"
]
},
{
name: "sepah",
name_farsi: "سپه",
icon: <i className="ibl32 ibl-sepah" />,
iban_nbc: "015",
pan_iin: [
"589210"
]
},
{
name: "shahr",
name_farsi: "شهر",
icon: <i className="ibl32 ibl-shahr" />,
iban_nbc: "061",
pan_iin: [
"502806",
"504706"
]
},
{
name: "sina",
name_farsi: "سینا",
icon: <i className="ibl32 ibl-sina" />,
iban_nbc: "059",
pan_iin: [
"639346"
]
},
{
name: "tejarat",
name_farsi: "تجارت",
icon: <i className="ibl32 ibl-tejarat" />,
iban_nbc: "018",
pan_iin: [
"585983",
"627353"
]
},
{
name: "tosee_saderat",
name_farsi: "توسعه صادرات",
icon: <i className="ibl32 ibl-edbi" />,
iban_nbc: "020",
pan_iin: [
"207177",
"627648"
]
},
{
name: "tosee_taavon",
name_farsi: "توسعه تعاون",
icon: <i className="ibl32 ibl-tt" />,
iban_nbc: "022",
pan_iin: [
"502908"
]
}
]
let cache: any = {};
export const findBankByPAN = (cardNumber: string | number) => {
cardNumber = String(cardNumber);
if (!/^[\d*]+$/.test(cardNumber) || cardNumber.length < 6) return {};
const iin: string = cardNumber.slice(0, 6);
if (cache[iin]) {
return cache[iin];
} else {
const bank: any = banks?.find((bank: any) => bank?.pan_iin.includes(iin))
if (!bank) return {};
cache[iin] = { ...bank };
return bank;
};
};
+2 -1
View File
@@ -10,7 +10,8 @@ module.exports = {
"primary-text-color": "#11212D",
"secondary-text-color": "#777577",
"primary-color": "#015699",
"secondary-color": "#E6E8EA"
"secondary-color": "#E6E8EA",
"border-color": "#D9D9D9"
},
backgroundImage: {
'bank-mehr': "url('/public/svgs/banks/mehr.svg')",