complete asan service gps

This commit is contained in:
Alihaghighattalab
2024-09-03 16:29:35 +03:30
parent 10a6a746ed
commit 00835e3733
28 changed files with 693 additions and 284 deletions
+12 -8
View File
@@ -24,7 +24,6 @@
"clsx": "^2.1.1",
"framer-motion": "^11.3.28",
"iconsax-react": "^0.0.8",
"iranianbanklogos": "^3.1.2",
"moment-jalaali": "^0.10.1",
"react": "^18.3.1",
"react-dom": "^18.3.1",
@@ -37,7 +36,8 @@
"typescript": "^4.9.5",
"web-vitals": "^2.1.4",
"yup": "^1.4.0",
"zustand": "^4.5.5"
"zustand": "^4.5.5",
"zustand-persist": "^0.4.0"
},
"devDependencies": {
"@tanstack/eslint-plugin-query": "^5.51.15",
@@ -10587,12 +10587,6 @@
"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",
@@ -20313,6 +20307,16 @@
"optional": true
}
}
},
"node_modules/zustand-persist": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/zustand-persist/-/zustand-persist-0.4.0.tgz",
"integrity": "sha512-u6bBIc4yZRpSKBKuTNhoqvoIb09gGHk2NkiPg4K7MPIWTYZg70PlpBn48QEDnKZwfNurnf58TaW5BuMGIMf5hw==",
"license": "MIT",
"peerDependencies": {
"react": ">=16.8.0",
"zustand": ">=3.6.3"
}
}
}
}
+2 -2
View File
@@ -19,7 +19,6 @@
"clsx": "^2.1.1",
"framer-motion": "^11.3.28",
"iconsax-react": "^0.0.8",
"iranianbanklogos": "^3.1.2",
"moment-jalaali": "^0.10.1",
"react": "^18.3.1",
"react-dom": "^18.3.1",
@@ -32,7 +31,8 @@
"typescript": "^4.9.5",
"web-vitals": "^2.1.4",
"yup": "^1.4.0",
"zustand": "^4.5.5"
"zustand": "^4.5.5",
"zustand-persist": "^0.4.0"
},
"scripts": {
"start": "react-scripts start",
+14 -38
View File
@@ -1,43 +1,19 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using create-react-app"
/>
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>React App</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta name="description" content="Web site created using create-react-app" />
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<link rel="icon" type="image/svg+xml" href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABwAAAAcBAMAAACAI8KnAAAAMFBMVEVHcEwHUJAHUpIGUpMGUpMNR4MHUpMGVJUIUZEIUZEIUZELTIoMTYwKT44GU5QHUpLtfyqAAAAAEHRSTlMAUJPA2BKu/4BebyM0QO6fto/c2wAAARNJREFUeAFF0QFEQ1EUh/FvtbdVe2uTCKDCCmSIBYgUgYYYUAFDMNvaf7MwYBoYEINSqIAHAhAwgAeMQFEglMiA5Lg9+4Cfc91zuWAtbO6sEOWVGxeDcqOIq3sK8Fh1irfx73shRy9Yb6QkKUsZG27jG/skbNyl5/jMAXjnsZbjKCwFJPLJVtqxksmTfOr+c1j1KiyhiOKVXGrC8Jut2Qk7t+zPyN2cHWp5EaNnDEdGO8xYqjGywzlfdWLSNQWFq7ZIH0zvEpctSgZrUgfmJHtG4iQp1YsUJHuk145Jat7ZqlIAG1zKUpM9YD6fduxnKgA1pkxfjIvY+B3/Zj3k8Ays8Q/AcQNXqvD5MPitBURdRZ/yB8Qed1faOTlWAAAAAElFTkSuQmCC" />
<title>آسان سرویس</title>
</head>
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
<body>
<div id="root"></div>
</body>
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>
</html>
+21
View File
@@ -1,8 +1,29 @@
import { useMutation } from "@tanstack/react-query";
import { Imei } from "./components/home/imei";
import { StatusBox } from "./components/home/status-box";
import { getUsersInformation } from "./services/api/user";
import { TransitionPages } from "./transition";
import { useUserStore } from "./store/user";
import { handleError } from "./utility/error-handle";
import { useEffect } from "react";
function App() {
const { updateUser } = useUserStore()
const { mutate: userMutate } = useMutation({
mutationFn: getUsersInformation,
mutationKey: ["user"]
})
const handleGetUsersInformation = async () => {
await userMutate({}, {
onSuccess: (res: any) => {
updateUser(res?.data)
},
onError: (err: any) => handleError(err)
})
}
useEffect(() => {
handleGetUsersInformation()
}, [])
return (
<TransitionPages>
<div className="flex flex-col gap-y-[30px] w-full">
+1 -1
View File
@@ -21,8 +21,8 @@ export const AwardNotComplete: FC<Props> = ({ award }) => {
<div className="flex flex-col items-start gap-3">
<p className="font-normal text-base text-primary-text-color" style={{ fontFamily: "vazirmatn" }}>{award?.desc?.length >= 70 ? `${award?.desc?.slice(0, 70)} ...` : award?.desc}</p>
<div className="flex flex-row items-center gap-2">
<p className="font-normal text-sm text-secondary-text-color">{formatDateToPersian(award?.expireDate, "jYYYY/jMM/jD")}</p>
<Calendar2 color="#777577" className="size-5" />
<p className="font-normal text-sm text-secondary-text-color">{formatDateToPersian(award?.expireDate, "jYYYY/jMM/jD")}</p>
</div>
</div>
</div>
+11 -6
View File
@@ -1,16 +1,21 @@
import { Cup, Transmit } from "iconsax-react"
import { AwardsResponse } from "../../types"
import { FC } from "react"
import { formatDateToPersian } from "../../utility/getDate"
export const AwardComplete = () => {
type Props = {
award: AwardsResponse
}
export const AwardComplete: FC<Props> = ({ award }) => {
return (
<div className="flex flex-col gap-10 bg-auth-form p-[18px] rounded-[40px]">
<div className="flex flex-col w-full h-full justify-between gap-10 bg-auth-form p-[18px] rounded-[40px]">
<div className="flex flex-col gap-3">
<p className="font-normal text-lg text-secondary-text-color">۲۵۰ هزار تومان تخفیف برای اقامت در تمام ویلاها و کلبه های جاجیگا با حداقل رزرو ۲ میلیون تومان.</p>
<p className="font-normal text-base text-secondary-text-color">1403/06/12</p>
<p className="font-normal text-lg text-secondary-text-color">{award?.req?.desc}</p>
<p className="font-normal text-base text-secondary-text-color">{formatDateToPersian(award?.expireDate, "jYYYY/jMM/jD")}</p>
</div>
<div className="flex flex-row justify-between items-center">
<div className="flex flex-row gap-x-[6px] items-center">
<p className="font-normal text-lg text-secondary-text-color">75</p>
<p className="font-normal text-lg text-secondary-text-color">{award?.score}</p>
<Cup className="size-4" color="#777577" />
</div>
<div className="size-10 flex items-center justify-center bg-white rounded-full cursor-pointer">
+33 -16
View File
@@ -1,31 +1,48 @@
import { useQuery } from "@tanstack/react-query"
import { AwardNotComplete } from "./award-not-complete"
import { getAwardsList } from "../../services/api/awards"
import { AwardsResponse } from "../../types"
import { AwardSkeleton } from "./award-skeleton"
import { handleError } from "../../utility/error-handle"
import { useEffect } from "react"
import { useQuery } from "@tanstack/react-query";
import { AwardNotComplete } from "./award-not-complete";
import { getAwardsList } from "../../services/api/awards";
import { AwardsResponse, AwardStatus } from "../../types";
import { AwardSkeleton } from "./award-skeleton";
import { handleError } from "../../utility/error-handle";
import { useEffect, useState } from "react";
import { AwardComplete } from "./awards-complete";
import { useUserStore } from "../../store/user";
export const Awards = () => {
const { user } = useUserStore()
const [status, setStatus] = useState<AwardStatus>({
awards: 0,
my_score: 0
});
const { data, isLoading, error } = useQuery({
queryKey: ["awards"],
queryFn: getAwardsList
})
});
useEffect(() => {
handleError(error)
}, [error])
handleError(error);
}, [error]);
if (isLoading) return <AwardSkeleton />
useEffect(() => {
const awardsTemp: number = data?.data?.length;
const tempScores: any = data?.data?.filter((award: AwardsResponse) => award?.status === 1);
const myScore = tempScores?.reduce((totalScore: number, award: AwardsResponse) => totalScore + award?.score, 0);
setStatus({
awards: awardsTemp,
my_score: myScore
});
}, [data]);
if (isLoading) return <AwardSkeleton />;
return (
<div className="flex flex-col gap-10">
<div className="flex flex-row justify-between items-center">
<p className="font-medium text-xl text-primary-text-color">جوایز (5)</p>
<p className="font-normal text-base text-primary-text-color">امتیازهای من: 65</p>
<p className="font-medium text-xl text-primary-text-color">جوایز ({status?.awards})</p>
<p className="font-normal text-base text-primary-text-color">امتیازهای من: {status?.my_score}</p>
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 xl:grid-cols-3 2xl:grid-cols-4 gap-6">
{data?.data?.map((award: AwardsResponse) => <AwardNotComplete key={award.id} award={award} />)}
{data?.data?.map((award: AwardsResponse, index: number) => award?.status === 0 ? <AwardNotComplete key={index} award={award} /> : <AwardComplete key={index} award={award} />)}
</div>
</div>
)
}
);
};
+3 -3
View File
@@ -32,7 +32,7 @@ export const ComboBox: FC<Props> = ({ field, placeholder, className, data = [],
<div className="relative w-full">
<ComboboxInput
placeholder={placeholder}
className={`input-style min-w-full ${className}`}
className={`input-style min-w-full ${className} ${placeholder && "placeholder:text-primary-text-color"}`}
displayValue={(person: any) => person?.name}
onChange={(event) => setQuery(event.target.value)}
/>
@@ -45,9 +45,9 @@ export const ComboBox: FC<Props> = ({ field, placeholder, className, data = [],
transition
className="bg-white shadow min-w-[220px] !max-h-[300px] mt-2 rounded-xl p-2"
>
{filteredValue.map((value) => (
{filteredValue.map((value, index: number) => (
<ComboboxOption
key={value?.id}
key={index}
value={value}
className="group flex cursor-default items-center gap-2 rounded-lg py-1.5 px-3 select-none data-[focus]:bg-secondary-text-color/10"
>
+7 -5
View File
@@ -6,13 +6,14 @@ import persian_fa from "react-date-object/locales/persian_fa"
import { getJalaaliDate } from '../../utility/getDate';
type Props = {
field: any
field: any,
placeholder?: string
}
export const DatepickerComponent: FC<Props> = ({ field }) => {
export const DatepickerComponent: FC<Props> = ({ field, placeholder }) => {
return (
<div className='relative w-full min-w-max cursor-pointer mb-10 xl:mb-0'>
<div className="absolute top-[18.5px] left-5 grid h-5 w-5 place-items-center !z-50">
<div className="absolute top-[18.5px] left-5 grid h-5 w-5 place-items-center">
<Calendar2 color='#777577' />
</div>
<DatePicker
@@ -20,11 +21,12 @@ export const DatepickerComponent: FC<Props> = ({ field }) => {
value={field.value}
onChange={field.onChange}
showOtherDays
placeholder={getJalaaliDate()}
placeholder={placeholder ? placeholder : getJalaaliDate()}
calendar={persian}
locale={persian_fa}
calendarPosition="bottom-right"
inputClass='w-full !min-w-full absolute input-style top-0 z-10 cursor-pointer'
arrow={false}
inputClass={`w-full !min-w-full absolute input-style top-0 cursor-pointer`}
/>
</div>
)
+4 -3
View File
@@ -11,10 +11,11 @@ type Props = {
field?: any,
className?: string,
value?: string,
onChange?: (value: string) => void
onChange?: (value: string) => void,
disabled?: boolean
}
export const Input: FC<Props> = ({ placeholder, type = "text", icon, field, className, value, onChange }) => {
export const Input: FC<Props> = ({ placeholder, type = "text", icon, field, className, value, onChange, disabled = false }) => {
const [hidden, setHidden] = useState<boolean>(false)
const handleHidden = () => setHidden(prev => !prev)
return (
@@ -27,7 +28,7 @@ export const Input: FC<Props> = ({ placeholder, type = "text", icon, field, clas
: icon}
{placeholder === "شماره کارت" && field.value && findBankByPAN(field.value).icon}
</div>
<InputComponent ref={field?.ref} {...field} value={value} onChange={onChange || field?.onChange} type={type === "password" && hidden ? "text" : type} placeholder={placeholder} className={`input-style min-w-full ${className}`} />
<InputComponent ref={field?.ref} {...field} value={value} onChange={onChange || field?.onChange} type={type === "password" && hidden ? "text" : type} placeholder={placeholder} className={`input-style min-w-full ${className}`} disabled={disabled} />
</div>
)
}
+8 -9
View File
@@ -1,4 +1,4 @@
import { flexRender, getCoreRowModel, getPaginationRowModel, useReactTable } from "@tanstack/react-table"
import { flexRender, getCoreRowModel, useReactTable } from "@tanstack/react-table"
import { ArrowDown2, ArrowLeft2, ArrowRight2, ArrowUp2, SearchNormal1 } from "iconsax-react"
import { FC, useEffect, useState } from "react"
import { Input } from "./input"
@@ -24,20 +24,17 @@ type ADSortType = {
export const Table: FC<Props> = ({ columns, data, tableStatus = false, tableScore = 0, isLoading = false, totalData = 0 }) => {
const [aDSort, setADSort] = useState<ADSortType>({
key: "",
value: 1
value: -1
})
const { updateParams, params, clearParams } = useParamsStore()
const totalPages = Math.ceil(totalData / params?.rows)
const table = useReactTable({
data: data ? data : [],
columns: columns ? columns : [],
debugTable: true,
getCoreRowModel: getCoreRowModel(),
enableColumnResizing: true,
columnResizeMode: 'onChange',
debugHeaders: true,
debugColumns: true,
getPaginationRowModel: getPaginationRowModel()
pageCount: params?.rows,
})
const handleInputValue = (value: string) => {
@@ -71,14 +68,16 @@ export const Table: FC<Props> = ({ columns, data, tableStatus = false, tableScor
<div className="flex flex-col gap-10">
<div className="flex flex-col sm:flex-row justify-between items-center gap-2.5 md:gap-10">
<div className="sm:max-w-[300px] xl:max-w-[450px] w-full">
<Input placeholder="جستجو" value={params?.search} onChange={(e: any) => handleInputValue(e?.target?.value)} className="bg-auth-form placeholder:text-[#777577]" icon={<SearchNormal1 color="#777577" />} />
<Input placeholder="جستجو" value={params?.search} onChange={(e: any) => {
handleInputValue(e?.target?.value)
}} className="bg-auth-form placeholder:text-[#777577]" icon={<SearchNormal1 color="#777577" />} />
</div>
<div className="w-full sm:max-w-[100px]">
<ComboBox placeholder={params?.rows.toString()} className="bg-auth-form" data={tableRows} mode="table" />
</div>
</div>
<div className="w-full flex justify-start items-end text-sm lg:text-lg text-primary-text-color font-normal -mb-7 pl-2">{tableStatus && `امتیاز من : ${tableScore}`}</div>
<div className="block max-w-full overflow-x-auto overflow-y-hidden bg-auth-form rounded-[40px] shadow-sm p-4 pb-0 xl:p-9 xl:pb-0 relative">
<div className="block max-w-full min-h-max overflow-x-auto overflow-y-hidden bg-auth-form rounded-[40px] shadow-sm p-4 pb-0 xl:p-9 xl:pb-0 relative">
<table>
<thead className="border-b border-b-secondary-color">
{table.getHeaderGroups().map((headerGroup: any, index: number) => (
@@ -92,7 +91,7 @@ export const Table: FC<Props> = ({ columns, data, tableStatus = false, tableScor
}}>
<div {...{ className: header.column.getCanSort() ? "text-secondary-text-color text-base font-normal flex flex-row items-center justify-center gap-1 px-4 py-2" : "", onClick: header.column.getToggleSortingHandler() }}>
{flexRender(header.column.columnDef.header, header.getContext())}
{{ asc: <ArrowDown2 color="#777577" className="size-4" />, desc: <ArrowUp2 color="#777577" className="size-4" /> }[header.column.getIsSorted() as string] ?? null}
{aDSort?.value === 1 && aDSort?.key === header.getContext()?.column?.id ? <ArrowDown2 color="#777577" className="size-4" /> : <ArrowUp2 color="#777577" className="size-4" />}
</div>
</th>
))}
+48
View File
@@ -0,0 +1,48 @@
import { Trash } from 'iconsax-react';
import { FC } from 'react';
type Props = {
setSelectedImage: (file: any) => any,
selectedImage: any
}
export const ImageUpload: FC<Props> = ({ selectedImage, setSelectedImage }) => {
const handleImageChange = (event: any) => {
const file = event.target.files[0];
if (file) {
const reader = new FileReader();
reader.onload = () => {
setSelectedImage(file);
};
reader.readAsDataURL(file);
}
};
const handleImageDelete = () => {
setSelectedImage(null);
};
return (
<div className="flex flex-col items-left">
<input
type="file"
accept="image/*"
onChange={handleImageChange}
className="hidden"
id="imageUpload"
/>
<label htmlFor="imageUpload" className="cursor-pointer">
{selectedImage ? (
<div className="relative">
<img src={URL.createObjectURL(selectedImage)} alt="Selected image" className="w-full min-w-max max-h-60 object-fill rounded-[40px]" />
<Trash color='#fff' size={25} onClick={handleImageDelete} className='absolute bottom-2 right-4' />
</div>
) : (
<div className="bg-white w-full min-w-max h-fit min-h-60 flex items-center justify-center text-secondary-text-color rounded-[40px]">
عکس خود را انتخاب کنید
</div>
)}
</label>
</div>
);
}
+6 -3
View File
@@ -15,8 +15,10 @@ export const LoginForm = () => {
const navigate = useNavigate()
const { mutate, isPending } = useMutation({
mutationFn: loginMutation
mutationFn: loginMutation,
mutationKey: ["login"]
})
const {
handleSubmit,
formState: { errors },
@@ -24,12 +26,13 @@ export const LoginForm = () => {
} = useForm<LoginFormInterface>({
resolver: yupResolver(loginSchema)
})
const handleLoginSubmit: SubmitHandler<LoginFormInterface> = async (data) => {
mutate({
await mutate({
...data,
remember_me: true
}, {
onSuccess: (data: any) => {
onSuccess: async (data: any) => {
window.localStorage.setItem("token", data?.data?.token)
toast.success("با موفقیت وارد شدین")
navigate("/")
+38 -4
View File
@@ -1,21 +1,55 @@
import { Button } from "@headlessui/react"
import { Input } from "../common/input"
import { useMutation } from "@tanstack/react-query"
import { checkIMEI } from "../../services/api/imei"
import { ButtonComponent } from "../common/button"
import { Controller, SubmitHandler, useForm } from "react-hook-form"
import { CheckIMEI } from "../../types"
import { yupResolver } from "@hookform/resolvers/yup"
import { checkIMEISchema } from "../../schema"
import { ErrorComponent } from "../common/error"
import toast from "react-hot-toast"
import { handleError } from "../../utility/error-handle"
export const Imei = () => {
const {
handleSubmit,
formState: { errors },
control
} = useForm<CheckIMEI>({
resolver: yupResolver(checkIMEISchema)
})
const { mutate, isPending } = useMutation({
mutationFn: checkIMEI,
mutationKey: ["IMEI"]
})
const handleCheckIMEI: SubmitHandler<CheckIMEI> = async (data) => {
await mutate(data, {
onSuccess: (res: any) => {
toast.success("کد IMEI معتبر است")
},
onError: (err: any) => handleError(err)
})
}
return (
<div className="w-full flex flex-col lg:flex-row bg-auth-form p-[30px] rounded-[40px] gap-8">
<form onSubmit={handleSubmit(handleCheckIMEI)} className="w-full flex flex-col lg:flex-row bg-auth-form p-[30px] rounded-[40px] gap-8">
<div className="w-full h-full flex items-center justify-center">
<img src="/svgs/dashboard/binary.svg" alt="binary" className="size-full max-w-[500px] max-h-[500px]" />
</div>
<div className="flex flex-col w-full gap-10 justify-center lg:justify-between">
<Input placeholder="کد IMEI" />
<Controller control={control} name="IMEI" render={({ field }) => (
<div className="flex flex-col !gap-0">
<Input placeholder="کد IMEI" type="text" field={field} />
<ErrorComponent show={errors?.IMEI} message={errors?.IMEI?.message} />
</div>
)} />
<p className="font-normal text-base text-primary-text-color text-justify lg:text-wrap">
لورم ایپسوم متن ساختگی با تولید سادگی نامفهوم از صنعت چاپ و با استفاده از طراحان گرافیک است چاپگرها و متون بلکه روزنامه و مجله در ستون و سطرآنچنان که لازم است و برای شرایط فعلی تکنولوژی مورد نیاز
</p>
<div className="w-full flex justify-end">
<Button className="w-full min-w-full xl:min-w-min xl:max-w-60">ثبت کد</Button>
<ButtonComponent title="ثبت کد" className="w-full min-w-full xl:min-w-min xl:max-w-60" pending={isPending} type="submit" />
</div>
</div>
</div>
</form>
)
}
+174 -122
View File
@@ -1,142 +1,194 @@
import { Button } from "@headlessui/react"
import { Input } from "../common/input"
import { DatepickerComponent } from "../common/datepicker"
import { Controller, SubmitHandler, useForm } from "react-hook-form"
import { MyAccountInterface } from "../../types"
import { CitiesType, ComboBoxItems, ProvinceType, UpdateUserInterface } from "../../types"
import { yupResolver } from "@hookform/resolvers/yup"
import { MyAccountSchema } from "../../schema"
import { UpdateUserSchema } from "../../schema"
import { ErrorComponent } from "../common/error"
import { ComboBox } from "../common/combo-box"
import { ResetPasswordBox } from "./reset-password-box"
import { useEffect, useState } from "react"
import { getProvincesList } from "../../services/api/province"
import { useMutation, useQuery } from "@tanstack/react-query"
import { getCitiesList } from "../../services/api/cities"
import { useUserStore } from "../../store/user"
import { ButtonComponent } from "../common/button"
import { updateUSerInformations } from "../../services/api/user"
import toast from "react-hot-toast"
import { handleError } from "../../utility/error-handle"
import { ImageUpload } from "../common/upload"
export const MyAccountPage = () => {
const { handleSubmit, formState: { errors }, control } = useForm<MyAccountInterface>({
resolver: yupResolver(MyAccountSchema)
const [selectedImage, setSelectedImage] = useState<any>(null);
const { user, updateUser } = useUserStore()
const { mutate, isPending } = useMutation({
mutationFn: updateUSerInformations,
mutationKey: ["update-user"]
})
const { handleSubmit, formState: { errors }, control, watch, reset } = useForm<UpdateUserInterface>({
resolver: yupResolver(UpdateUserSchema),
})
const provinceName: any = watch("province_name");
const [provinceData, setProvinceData] = useState<ComboBoxItems[] | []>([])
const [citiesData, setCitiesData] = useState<ComboBoxItems[] | []>([])
const { data: provinces } = useQuery({
queryFn: getProvincesList,
queryKey: ["province"]
})
const { data: cities } = useQuery({
queryFn: getCitiesList,
queryKey: ["cities"]
})
const handleMyAccountForm: SubmitHandler<MyAccountInterface> = async (data) => {
console.log("data =>", data)
const handleUpdateUser: SubmitHandler<UpdateUserInterface> = async (data) => {
let formData = new FormData()
formData.append("first_name", data?.first_name)
formData.append("last_name", data?.last_name)
formData.append("province_name", data?.province_name?.name)
formData.append("city_name", data?.city_name?.name)
formData.append("shopName", data?.shopName)
formData.append("birthDate", data?.birthDate)
formData.append("address", data?.address)
formData.append("cell_number", data?.cell_number)
formData.append("image", selectedImage)
await mutate({ formData, id: user?.id }, {
onSuccess: (res: any) => {
toast?.success("اطلاعات کاربر با موفقیت بروز رسانی شد")
updateUser(res?.data?.user)
},
onError: (err: any) => handleError(err)
})
}
useEffect(() => {
const tempArray: ComboBoxItems[] = [...provinceData]
provinces?.data?.data?.map((province: ProvinceType) => tempArray.push({
id: province?.ProvinceID,
name: province?.ProvinceName
}))
setProvinceData(tempArray)
}, [provinces])
useEffect(() => {
const tempCities: any = []
const newCities = cities?.data?.data?.filter((city: CitiesType) => city?.ProvinceName?.toLocaleLowerCase()?.includes(provinceName?.name?.toLowerCase()))
newCities?.map((city: CitiesType) => tempCities?.push({
id: city?.CityID,
name: city?.CityName
}))
setCitiesData(tempCities)
}, [provinceName]);
useEffect(() => {
reset({
address: user?.address,
birthDate: user?.birthDate,
city_name: { id: 1, name: user?.city_name },
province_name: { id: 1, name: user?.province_name },
mobile_number: user?.mobile_number,
first_name: user?.first_name,
last_name: user?.last_name,
national_code: user?.national_code,
shopName: user?.shopName,
cell_number: user?.cell_number,
})
}, [reset])
return (
<form onSubmit={handleSubmit(handleMyAccountForm)} className="w-full h-full flex flex-col gap-10 bg-auth-form rounded-[40px] p-[30px]">
<div className="flex flex-col gap-5 xl:gap-0 xl:flex-row xl:gap-x-10 xl:justify-between items-start">
<div className="flex flex-col gap-y-5 w-full xl:max-w-fit">
<p className="font-medium text-xl text-primary-text-color">اطلاعات اولیه</p>
<p className="font-normal text-base text-secondary-text-color text-wrap xl:max-w-xs">
لورم ایپسوم متن ساختگی با تولید سادگی نامفهوم از صنعت چاپ و با استفاده از طراحان گرافیک است چاپگرها و متون
</p>
</div>
<div className="flex flex-col gap-10 w-full">
<div className="grid grid-cols-1 md:grid-cols-2 gap-[18px]">
<Controller name="phoneNumber" control={control} render={({ field }) => (
<div>
<Input placeholder="شماره تماس" type="number" field={field} />
<ErrorComponent show={errors.phoneNumber} message={errors.phoneNumber?.message} />
</div>
)} />
<Controller name="marketName" control={control} render={({ field }) => (
<div>
<Input placeholder="نام شرکت" field={field} />
<ErrorComponent show={errors.marketName} message={errors.marketName?.message} />
</div>
)} />
<Controller name="name" control={control} render={({ field }) => (
<div>
<Input placeholder="نام" field={field} />
<ErrorComponent show={errors.name} message={errors.name?.message} />
</div>
)} />
<Controller name="family" control={control} render={({ field }) => (
<div>
<Input placeholder="نام خانوادگی" field={field} />
<ErrorComponent show={errors.family} message={errors.family?.message} />
</div>
)} />
<Controller name="capCity" control={control} render={({ field }) => (
<div>
<ComboBox field={field} />
<ErrorComponent show={errors.capCity} message={errors.capCity?.message} />
</div>
)} />
<Controller name="city" control={control} render={({ field }) => (
<div>
<ComboBox field={field} />
<ErrorComponent show={errors.city} message={errors.city?.message} />
</div>
)} />
<div className="w-full h-full flex flex-col gap-10 bg-auth-form rounded-[40px] p-[30px]">
<form onSubmit={handleSubmit(handleUpdateUser)} className="w-full h-full flex flex-col gap-10 bg-auth-form rounded-[40px]">
<div className="flex flex-col gap-5 xl:gap-0 xl:flex-row xl:gap-x-10 xl:justify-between items-start">
<div className="flex flex-col gap-y-5 w-full xl:max-w-fit">
<p className="font-medium text-xl text-primary-text-color">اطلاعات اولیه</p>
<p className="font-normal text-base text-secondary-text-color text-wrap xl:max-w-xs">
لورم ایپسوم متن ساختگی با تولید سادگی نامفهوم از صنعت چاپ و با استفاده از طراحان گرافیک است چاپگرها و متون
</p>
</div>
<div className="divide-y-2 border" />
</div>
</div>
<div className="flex flex-col gap-5 xl:gap-0 xl:flex-row xl:gap-x-10 xl:justify-between items-start">
<div className="flex flex-col gap-y-5 w-full xl:max-w-fit">
<p className="font-medium text-xl text-primary-text-color">اطلاعات تکمیلی</p>
<p className="font-normal text-base text-secondary-text-color text-wrap xl:max-w-xs">
لورم ایپسوم متن ساختگی با تولید سادگی نامفهوم از صنعت چاپ و با استفاده از طراحان گرافیک است چاپگرها و متون
</p>
</div>
<div className="flex flex-col gap-10 w-full">
<div className="grid grid-cols-1 md:grid-cols-2 gap-[18px]">
<Controller name="address" control={control} render={({ field }) => (
<div>
<Input placeholder="آدرس" field={field} />
<ErrorComponent show={errors.address} message={errors.address?.message} />
</div>
)} />
<Controller name="phoneNumber1" control={control} render={({ field }) => (
<div>
<Input placeholder="شماره تماس" type="number" field={field} />
<ErrorComponent show={errors.phoneNumber1} message={errors.phoneNumber1?.message} />
</div>
)} />
<Controller name="staticNumber" control={control} render={({ field }) => (
<div>
<Input placeholder="شماره ثابت" type="number" field={field} />
<ErrorComponent show={errors.staticNumber} message={errors.staticNumber?.message} />
</div>
)} />
<Controller name="date" control={control} render={({ field }) => (
<div className="flex flex-col gap-y-9">
<DatepickerComponent field={field} />
<ErrorComponent show={errors.date} message={errors.date?.message} />
</div>
)} />
<div className="flex flex-col gap-10 w-full">
<div className="grid grid-cols-1 md:grid-cols-2 gap-[18px]">
<Controller name="mobile_number" control={control} render={({ field }) => (
<div>
<Input placeholder={user?.mobile_number ? user?.mobile_number : "شماره تماس"} type="number" disabled field={field} />
<ErrorComponent show={errors.mobile_number} message={errors.mobile_number?.message} />
</div>
)} />
<Controller name="shopName" control={control} render={({ field }) => (
<div>
<Input placeholder={user?.shopName ? user?.shopName : "نام شرکت"} field={field} />
<ErrorComponent show={errors.shopName} message={errors.shopName?.message} />
</div>
)} />
<Controller name="first_name" control={control} render={({ field }) => (
<div>
<Input placeholder={user?.first_name ? user?.first_name : "نام"} field={field} />
<ErrorComponent show={errors.first_name} message={errors.first_name?.message} />
</div>
)} />
<Controller name="last_name" control={control} render={({ field }) => (
<div>
<Input placeholder={user?.last_name ? user?.last_name : "نام خانوادگی"} field={field} />
<ErrorComponent show={errors.last_name} message={errors.last_name?.message} />
</div>
)} />
<Controller name="province_name" control={control} render={({ field }) => (
<div>
<ComboBox field={field} data={provinceData} placeholder={user?.province_name} />
<ErrorComponent show={errors.province_name} message={errors.province_name?.message} />
</div>
)} />
<Controller name="city_name" control={control} render={({ field }) => (
<div>
<ComboBox field={field} data={citiesData} placeholder={user?.city_name} />
<ErrorComponent show={errors.city_name} message={errors.city_name?.message} />
</div>
)} />
<ImageUpload setSelectedImage={setSelectedImage} selectedImage={selectedImage} />
</div>
<div className="divide-y-2 border" />
</div>
<div className="divide-y-2 border" />
</div>
</div>
<div className="flex flex-col gap-5 xl:gap-0 xl:flex-row xl:gap-x-10 xl:justify-between items-start">
<div className="flex flex-col gap-y-5 w-full xl:max-w-fit">
<p className="font-medium text-xl text-primary-text-color">تغییر رمز عبور</p>
<p className="font-normal text-base text-secondary-text-color text-wrap xl:max-w-xs">
لورم ایپسوم متن ساختگی با تولید سادگی نامفهوم از صنعت چاپ و با استفاده از طراحان گرافیک است چاپگرها و متون
</p>
<div className="flex flex-col gap-5 xl:gap-0 xl:flex-row xl:gap-x-10 xl:justify-between items-start">
<div className="flex flex-col gap-y-5 w-full xl:max-w-fit">
<p className="font-medium text-xl text-primary-text-color">اطلاعات تکمیلی</p>
<p className="font-normal text-base text-secondary-text-color text-wrap xl:max-w-xs">
لورم ایپسوم متن ساختگی با تولید سادگی نامفهوم از صنعت چاپ و با استفاده از طراحان گرافیک است چاپگرها و متون
</p>
</div>
<div className="flex flex-col gap-10 w-full">
<div className="grid grid-cols-1 md:grid-cols-2 gap-[18px]">
<Controller name="address" control={control} render={({ field }) => (
<div>
<Input placeholder={user?.address ? user?.address : "آدرس"} field={field} />
<ErrorComponent show={errors.address} message={errors.address?.message} />
</div>
)} />
<Controller name="national_code" control={control} render={({ field }) => (
<div>
<Input placeholder={user?.national_code} type="number" field={field} disabled />
<ErrorComponent show={errors.national_code} message={errors.national_code?.message} />
</div>
)} />
<Controller name="cell_number" control={control} render={({ field }) => (
<div>
<Input placeholder={user?.cell_number} type="number" field={field} />
<ErrorComponent show={errors.cell_number} message={errors.cell_number?.message} />
</div>
)} />
<Controller name="birthDate" control={control} render={({ field }) => (
<div className="flex flex-col gap-y-9">
<DatepickerComponent field={field} placeholder={user?.birthDate} />
<ErrorComponent show={errors.birthDate} message={errors.birthDate?.message} />
</div>
)} />
</div>
<div className="divide-y-2 border" />
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-[18px] w-full">
<Controller name="currentPassword" control={control} render={({ field }) => (
<div>
<Input placeholder="رمز عبور فعلی" type="password" field={field} />
<ErrorComponent show={errors.currentPassword} message={errors.currentPassword?.message} />
</div>
)} />
<div />
<Controller name="newPassword" control={control} render={({ field }) => (
<div>
<Input placeholder="رمز عبور جدید" type="password" field={field} />
<ErrorComponent show={errors.newPassword} message={errors.newPassword?.message} />
</div>
)} />
<Controller name="repeatPassword" control={control} render={({ field }) => (
<div>
<Input placeholder="تکرار رمز عبور" type="password" field={field} />
<ErrorComponent show={errors.repeatPassword} message={errors.repeatPassword?.message} />
</div>
)} />
<div className="flex w-full min-w-max lg:justify-end mt-10">
<ButtonComponent type="submit" title="ذخیره" pending={isPending} className="w-full lg:max-w-fit lg:min-w-72" />
</div>
</div>
<div className="flex w-full xl:justify-end mt-10">
<Button type="submit" className="w-full xl:max-w-fit xl:min-w-72">ذخیره</Button>
</div>
</form>
</form>
<ResetPasswordBox />
</div >
)
}
@@ -0,0 +1,71 @@
import { Controller, SubmitHandler, useForm } from "react-hook-form"
import { ResetPasswordUserSchema } from "../../schema"
import { ResetPasswordUser } from "../../types"
import { yupResolver } from "@hookform/resolvers/yup"
import { ErrorComponent } from "../common/error"
import { Input } from "../common/input"
import { ButtonComponent } from "../common/button"
import { useMutation } from "@tanstack/react-query"
import { changeUserPassword } from "../../services/api/user"
import toast from "react-hot-toast"
import { handleError } from "../../utility/error-handle"
export const ResetPasswordBox = () => {
const { mutate, isPending } = useMutation({
mutationFn: changeUserPassword,
mutationKey: ["user-password"]
})
const { handleSubmit, formState: { errors }, control } = useForm<ResetPasswordUser>({
resolver: yupResolver(ResetPasswordUserSchema)
})
const handleResetUserPassword: SubmitHandler<ResetPasswordUser> = async (data) => {
await mutate({
...data
},
{
onSuccess: () => {
toast.success("تغییر رمز عبور با موفقیت انجام شد")
},
onError: (err: any) => {
handleError(err)
}
})
}
return (
<form className="w-full h-full flex flex-col gap-10" onSubmit={handleSubmit(handleResetUserPassword)}>
<div className="flex flex-col gap-5 xl:gap-0 xl:flex-row xl:gap-x-10 xl:justify-between items-start">
<div className="flex flex-col gap-y-5 w-full xl:max-w-fit">
<p className="font-medium text-xl text-primary-text-color">تغییر رمز عبور</p>
<p className="font-normal text-base text-secondary-text-color text-wrap xl:max-w-xs">
لورم ایپسوم متن ساختگی با تولید سادگی نامفهوم از صنعت چاپ و با استفاده از طراحان گرافیک است چاپگرها و متون
</p>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-[18px] w-full">
<Controller name="password" control={control} render={({ field }) => (
<div>
<Input placeholder="رمز عبور فعلی" type="password" field={field} />
<ErrorComponent show={errors.password} message={errors.password?.message} />
</div>
)} />
<div />
<Controller name="newPassword" control={control} render={({ field }) => (
<div>
<Input placeholder="رمز عبور جدید" type="password" field={field} />
<ErrorComponent show={errors.newPassword} message={errors.newPassword?.message} />
</div>
)} />
<Controller name="password_confirmation" control={control} render={({ field }) => (
<div>
<Input placeholder="تکرار رمز عبور" type="password" field={field} />
<ErrorComponent show={errors.password_confirmation} message={errors.password_confirmation?.message} />
</div>
)} />
</div>
</div>
<div className="flex w-full lg:justify-end mt-10">
<ButtonComponent type="submit" title="تغییر رمز عبور" pending={isPending} className="w-full lg:max-w-fit lg:min-w-72" />
</div>
</form>
)
}
+11 -5
View File
@@ -1,20 +1,26 @@
import { Call } from "iconsax-react"
import React, { FC } from "react"
import { Call, Profile } from "iconsax-react"
import { FC } from "react"
import { useUserStore } from "../../../store/user"
type Props = {
setShow: () => void
}
export const Header: FC<Props> = ({ setShow }) => {
const { user } = useUserStore()
return (
<header className="flex flex-row w-full justify-between items-center mb-10 lg:mb-[60px]">
<div className="flex flex-row gap-x-5 lg:gap-x-3 items-center">
<img src="/svgs/header/burger-menu.svg" alt="burger menu" className="lg:hidden size-[27px]" onClick={setShow} />
<img src="/svgs/header/person.svg" alt="person" className="size-[60px]" />
<p className="hidden font-normal text-sm lg:block lg:text-xl">علی مصلحی</p>
{user?.profilePic !== "noPic" ? <img src={`https://asan-service.com/${user?.profilePic}`} alt="person" className="size-[60px] rounded-full" /> : <div className="size-[60px] flex flex-row justify-center items-center bg-secondary-color rounded-full">
<Profile
size="30"
color="#777577"
/></div>}
<p className="hidden font-normal text-sm lg:block lg:text-xl">{user?.first_name} {user?.last_name}</p>
</div>
<div className="flex flex-row gap-x-3 items-center">
<p className="font-normal text-sm lg:text-xl">021-45597000</p>
<p className="font-normal text-sm lg:text-xl"><a href={`tel:${user?.mobile_number}`}>{user?.mobile_number}</a></p>
<Call color="#11212D" variant="Bold" className="size-[27px]" />
</div>
</header>
@@ -4,6 +4,7 @@ import { sidebarItems } from "../../../utility/sidebar";
import { SidebarItem } from "./sidebar-item";
import { CloseCircle, LogoutCurve } from "iconsax-react";
import { useNavigate } from "react-router-dom";
import { useUserStore } from "../../../store/user";
type Props = {
setShow: (value: boolean) => void,
@@ -11,11 +12,13 @@ type Props = {
};
export const SidebarMobile: FC<Props> = ({ setShow, show }) => {
const { clearUser } = useUserStore()
const navigate = useNavigate()
const handleCloseSidebar = () => setShow(false);
const handleExit = () => {
window.localStorage.clear()
navigate("/auth/login")
clearUser()
}
return (
<>
+57 -6
View File
@@ -1,21 +1,72 @@
import { Button } from "@headlessui/react"
import { ComboBox } from "../common/combo-box"
import { FC } from "react"
import { FC, useEffect, useState } from "react"
import { useUserStore } from "../../store/user"
import { ButtonComponent } from "../common/button"
import { Controller, SubmitHandler, useForm } from "react-hook-form"
import { CardBank, SendWithdraw } from "../../types"
import { ErrorComponent } from "../common/error"
import { useMutation } from "@tanstack/react-query"
import { sendwithdraw } from "../../services/api/wallet"
import toast from "react-hot-toast"
import { handleError } from "../../utility/error-handle"
type Props = {
pending: boolean
}
export const WalletAddress: FC<Props> = ({ pending }) => {
const [selectedCard, setSelectedCard] = useState<CardBank | null>(null)
const { mutate, isPending } = useMutation({
mutationFn: sendwithdraw,
mutationKey: ["withdraw"]
})
const { handleSubmit, formState: { errors }, control, watch } = useForm<any>({
})
const cardDetail: any = watch("card")
const [userBanks, setUserBanks] = useState<any>([])
const { user } = useUserStore()
useEffect(() => {
const tempUserBanks: any = [...userBanks]
user?.cardBank?.map((cards: any) => tempUserBanks.push({
id: cards?.IBAN,
name: cards?.PAN,
}))
setUserBanks(tempUserBanks)
}, [user])
useEffect(() => {
const selectedCard: CardBank | CardBank[] | [] = user?.cardBank?.filter((cardBanks: any) => cardBanks?.IBAN === cardDetail?.id && cardBanks?.PAN === cardDetail?.name)
setSelectedCard(selectedCard?.[0])
}, [cardDetail])
const handleSendWithdraw: SubmitHandler<SendWithdraw> = async () => {
await mutate({
card: {
holderName: selectedCard?.holderName,
PAN: selectedCard?.PAN,
IBAN: selectedCard?.IBAN
}
}, {
onSuccess: (res: any) => {
toast.success("درخواست شما با موفقیت ثبت شد")
},
onError: (err: any) => handleError(err)
})
}
return (
<div className="w-full flex flex-col lg:flex-row bg-auth-form p-[30px] rounded-[40px] gap-8">
<form onSubmit={handleSubmit(handleSendWithdraw)} className="w-full flex flex-col lg:flex-row bg-auth-form p-[30px] rounded-[40px] gap-8">
<div className="w-full h-full flex items-center justify-center">
<img src="/svgs/dashboard/wallet.svg" alt="wallet" className="size-full max-w-[500px] max-h-[500px]" />
</div>
<div className="flex flex-col w-full gap-10 justify-center lg:justify-between">
<div className="flex flex-col gap-10">
<p className="text-2xl font-medium text-primary-text-color">ثبت درخواست برداشت از حساب</p>
{!pending && <ComboBox placeholder="حساب بانکی مورد نظر خود را انتخاب کنید" />}
{!pending && <Controller control={control} name="card" render={({ field }) => (
<>
<ComboBox placeholder="حساب بانکی مورد نظر خود را انتخاب کنید" data={userBanks} field={field} />
<ErrorComponent show={errors?.card} message={errors?.card?.message} />
</>
)} />}
</div>
{!pending && <p className="font-normal text-base text-primary-text-color text-justify lg:text-wrap">
لورم ایپسوم متن ساختگی با تولید سادگی نامفهوم از صنعت چاپ و با استفاده از طراحان گرافیک است چاپگرها و متون بلکه روزنامه و مجله در ستون و سطرآنچنان که لازم است و برای شرایط فعلی تکنولوژی مورد نیاز
@@ -24,9 +75,9 @@ export const WalletAddress: FC<Props> = ({ pending }) => {
{`همکار گرامی
طبق درخواست قبلی، عملیات واریز مبلغ 765,975 تومان به حساب شما در حال اجراست. متاسفانه پیش از تکمیل درخواست، امکان شروع عملیات جدید برای شما وجود ندارد.`}</p>}
<div className="w-full flex justify-end">
<Button className="w-full min-w-full xl:min-w-min xl:max-w-60">{pending ? "لغو درخواست" : "ثبت درخواست"}</Button>
<ButtonComponent className="w-full min-w-full xl:min-w-min xl:max-w-60" pending={isPending} title={pending ? "لغو درخواست" : "ثبت درخواست"} type="submit" />
</div>
</div>
</div>
</form >
)
}
+1 -1
View File
@@ -36,7 +36,7 @@
}
button {
@apply text-base font-medium flex w-full justify-center bg-primary-color max-w-[442px] max-h-[55px] rounded-[20px] p-[18px] text-white
@apply text-base font-medium flex w-full justify-center bg-primary-color max-h-[55px] rounded-[20px] p-[18px] text-white
}
table {
-1
View File
@@ -3,7 +3,6 @@ import ReactDOM from 'react-dom/client';
import { RouterProvider } from 'react-router-dom';
import { router } from './router';
import './index.css'
import "iranianbanklogos/dist/ibl.css";
import { Toaster } from 'react-hot-toast';
import { RQProvider } from './rq-provider';
+33 -25
View File
@@ -76,34 +76,23 @@ export const RegisterSchema = yup.object({
.required("تکرار رمز عبور الزامی میباشد"),
})
export const MyAccountSchema = yup.object({
phoneNumber: yup.string()
.required("وارد کردن شماره تماس الزامی میباشد")
export const UpdateUserSchema = yup.object({
first_name: yup.string().required("وارد کردن نام الزامی میباشد").min(3, "نام نمیتواند کمتر از 3 کاراکتر باشد").max(20, "نام نمیتواند بیشتر از 20 کاراکتر باشد"),
last_name: yup.string().required("وارد کردن نام خانوادگی الزامی میباشد").min(3, "نام خانوادگی نمیتواند کمتر از 3 کاراکتر باشد").max(25, "نام خانوادگی نمیتواند بیشتر از 25 کاراکتر باشد"),
city_name: yup.object().required("وارد کردن شهر الزامی مباشد"),
province_name: yup.object().required("وارد کردن استان الزامی مباشد"),
shopName: yup.string().required("وارد کردن نام فروشگاه الزامی میباشد").min(2, "نام فروشگاه نمیتواند کمتر از 3 کاراکتر باشد").max(30, "نام فروشگاه نمیتواند بیشتر از 30 کاراکتر باشد"),
birthDate: yup.string().required("وارد کردن تاریخ تولد الزامی میباشد"),
address: yup.string().required("وارد کردن آدرس الزامی میباشد").min(5, "آدرس نمیتواند کمتر از 5 کاراکتر باشد").max(50, "آدرس نمیتواند بیشتر از از 50 کاراکتر باشد"),
cell_number: yup.string()
.min(11, "شماره تلفن ثابت نمیتوند کمتر از 11 رقم باشد")
.max(11, "شماره ثابت نمیتواند بیشتر از 11 رقم باشد")
.matches(/^[0-9]{11}$/, "شماره تماس نامعتبر").required("وارد کردن شماره تلفن ثابت الزامی میباشد"),
national_code: yup.string().min(10, "کد ملی نمیتواند کمتر از 10 کاراکتر باشد").max(10, "کد ملی نمیتونه بیشتر از 10 کاراکتر باشه").matches(/^[0-9]{10}$/, "کد نامعتبر"),
mobile_number: yup.string()
.min(11, "شماره تماس نمیتوند کمتر از 11 رقم باشد")
.max(11, "شماره تماس نمیتواند بیشتر از 11 رقم باشد")
.matches(/^[0-9]{11}$/, "شماره تماس نامعتبر"),
marketName: yup.string().required("وارد کردن نام فروشگاه الزامی میباشد").min(2, "نام فروشگاه نمیتواند کمتر از 3 کاراکتر باشد").max(30, "نام فروشگاه نمیتواند بیشتر از 30 کاراکتر باشد"),
name: yup.string().required("وارد کردن نام الزامی میباشد").min(3, "نام نمیتواند کمتر از 3 کاراکتر باشد").max(20, "نام نمیتواند بیشتر از 20 کاراکتر باشد"),
family: yup.string().required("وارد کردن نام خانوادگی الزامی میباشد").min(3, "نام خانوادگی نمیتواند کمتر از 3 کاراکتر باشد").max(25, "نام خانوادگی نمیتواند بیشتر از 25 کاراکتر باشد"),
city: yup.object().required("وارد کردن شهر الزامی مباشد"),
capCity: yup.object().required("وارد کردن استان الزامی مباشد"),
address: yup.string().required("وارد کردن آدرس الزامی میباشد"),
phoneNumber1: yup.string()
.required("وارد کردن شماره تماس الزامی میباشد")
.min(11, "شماره تماس نمیتوند کمتر از 11 رقم باشد")
.max(11, "شماره تماس نمیتواند بیشتر از 11 رقم باشد")
.matches(/^[0-9]{11}$/, "شماره تماس نامعتبر"),
staticNumber: yup.string()
.required("وارد کردن شماره تماس الزامی میباشد")
.min(8, "شماره تماس نمیتوند کمتر از 8 رقم باشد")
.max(8, "شماره تماس نمیتواند بیشتر از 8 رقم باشد")
.matches(/^[0-9]{8}$/, "شماره تماس نامعتبر"),
date: yup.string().required("وارد کردن تاریخ الزامی میباشد"),
currentPassword: yup.string().required("وارد کردن رمز عبور فعلی الزامی میباشد"),
newPassword: yup.string().min(8, "رمز عبور نمیتواند کمتر از 8 کاراکتر باشد").required("رمز عبور الزامی میباشد"),
repeatPassword: yup.string()
.oneOf([yup.ref('newPassword')], "رمز عبور و تکرار آن باید یکسان باشند")
.required("تکرار رمز عبور الزامی میاشد"),
})
export const AddNewCardSchema = yup.object({
@@ -119,4 +108,23 @@ export const AddNewCardSchema = yup.object({
.min(26, "شماره شبا نمیتوند کمتر از 26 رقم باشد")
.max(26, " شماره شبا نمیتواند بیشتر از 26 رقم باشد")
.matches(/^IR[0-9]{24}$/, "شماره شبا نامعتبر"),
})
export const ResetPasswordUserSchema = yup.object({
password: yup.string().min(8, "رمز عبور نمی‌تواند کمتر از 8 کاراکتر باشد").required("رمز عبور الزامی می‌باشد"),
newPassword: yup.string().min(8, "رمز عبور جدید نمی‌تواند کمتر از 8 کاراکتر باشد")
.notOneOf([yup.ref('password')], "رمز عبور جدید نباید با رمز عبور فعلی یکسان باشد")
.required("رمز عبور جدید الزامی می‌باشد"),
password_confirmation: yup.string()
.oneOf([yup.ref('newPassword')], "رمز عبور جدید و تکرار آن باید یکسان باشند")
.required("تکرار رمز عبور الزامی می‌باشد"),
});
export const SendWithdrawSchema = yup.object({
holderName: yup.string().required("وارد کردن اطلاعات صاحب کارت الزامی میباشد"),
card: yup.object(),
})
export const checkIMEISchema = yup.object({
IMEI: yup.string().required("وارد کردن اطلاعات IMEI الزامی میباشد").min(15,"شناسه IMEI نمیتواند کمتر از 15 کاراکتر باشد").max(15,"شناسه IMEI نمیتواند بیشتر از 15 کاراکتر باشد"),
})
+5
View File
@@ -0,0 +1,5 @@
import { CheckIMEI } from "../../types";
import axiosInstance from "../axios";
export const checkIMEI = (payload: CheckIMEI) => axiosInstance.post<any>("gps/register-device", payload)
+6
View File
@@ -0,0 +1,6 @@
import { ResetPasswordUser } from "../../types";
import axiosInstance from "../axios";
export const getUsersInformation = ({ }: any) => axiosInstance.get("/gps/user/me")
export const changeUserPassword = (payload: ResetPasswordUser) => axiosInstance.post<any>("/gps/user/reset-pass", payload)
export const updateUSerInformations = (payload: any) => axiosInstance.put<any>(`/gps/user/me/${payload?.id}`, payload?.formData)
+2 -1
View File
@@ -1,5 +1,6 @@
import axiosInstance from "../axios";
import { OverviewResponse } from "./overview";
export const getWalletDetails = () => axiosInstance.get<OverviewResponse[] | OverviewResponse | [], any>("/gps/overview/wallet")
export const sendwithdraw = (payload: any) => axiosInstance.post("/gps/withdraw", payload)
+56
View File
@@ -0,0 +1,56 @@
import { create } from 'zustand';
import { UserInterface } from '../types';
import { persist } from "zustand/middleware";
type ZustandState = {
user: UserInterface;
updateUser: (userData: Partial<UserInterface>) => void;
clearUser: () => void;
};
const defaultParams: UserInterface = {
cardBank: [],
city_name: "",
created_at: "",
dollarBalance: "",
first_name: "",
id: "",
last_name: "",
mobile_number: "",
national_code: "",
profilePic: "",
province_name: "",
score: 0,
shopName: "",
updated_at: "",
walletBalance: 0,
active: true,
_id: "",
address: "",
birthDate: "",
cell_number: "",
}
export const useUserStore = create<ZustandState>()(
persist(
(set) => ({
user: defaultParams,
updateUser: (userData: Partial<UserInterface>) =>
set((state) => ({
...state,
user: {
...state.user,
...userData,
},
})),
clearUser: () =>
set((state) => ({
...state,
user: defaultParams,
})),
}),
{
name: 'user-store',
getStorage: () => localStorage
}
)
);
+65 -24
View File
@@ -67,25 +67,21 @@ export interface StatusBoxItemInterface {
detail?: boolean
}
export interface MyAccountInterface {
phoneNumber: string,
marketName: string,
name: string,
family: string,
city: object,
capCity: object,
address: string,
phoneNumber1: string,
staticNumber: string,
date: string,
currentPassword: string,
newPassword: string,
repeatPassword: string
export interface UpdateUserInterface {
id?: any,
first_name: string
last_name: string
city_name: object | any
province_name: object | any
shopName: string
birthDate: string
address: string
cell_number: string,
national_code?: string,
mobile_number?: string,
image?: any
}
type BankName = "saderat" | "mehr" | "pasargad"
type PersianBankName = "مهر" | "صادرات" | "پاسارگاد"
export interface CardBank {
_id: string,
holderName: string,
@@ -207,15 +203,17 @@ export interface RegisterDeviceId {
}
export interface AwardsResponse {
_id: string,
score: number,
title: string,
desc: string,
expireDate: string,
created_at: string,
updated_at: string,
__v: number,
id: string
desc: string
req: {
userId: string,
desc: string,
metaData: null | any,
status: number
},
status: number,
expireDate: string
}
interface TransactionsData {
@@ -312,4 +310,47 @@ export type CitiesType = {
export interface CitiesResponse {
data: CitiesType[] | [],
error: string | null | any
}
export interface UserInterface {
cardBank: CardBank[] | [],
city_name: string,
created_at: string,
dollarBalance: string,
first_name: string,
id: string,
last_name: string,
mobile_number: string,
national_code: string,
profilePic: string,
province_name: string
score: number
shopName: string
updated_at: string
walletBalance: number
active: boolean,
_id: string,
address: string,
birthDate: string,
cell_number: string
}
export interface ResetPasswordUser {
password: string,
newPassword: string,
password_confirmation: string
}
export interface AwardStatus {
awards: number,
my_score: number
}
export interface SendWithdraw {
holderName: string,
card: any
}
export interface CheckIMEI {
IMEI: string
}
+1 -1
View File
@@ -19,7 +19,7 @@ const columnConfigs: colConfig[] | [] = [
</div>
},
{ accessor: "created_at", header: "تاریخ درخواست", cellRenderer: (info) => info.getValue() && <p style={{ direction: "ltr" }}>{formatDateToPersian(info.getValue())}</p> },
{ accessor: "id", header: "شماره پیگیری" },
{ accessor: "trackingNumber", header: "شماره پیگیری" },
{ accessor: "updated_at", header: "تاریخ واریز", cellRenderer: (info) => info.getValue() && <p style={{ direction: "ltr" }}>{formatDateToPersian(info.getValue())}</p> },
];