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",
+6 -30
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"
/>
<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>
<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>
<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.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</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>
</div>
<ButtonComponent title="ثبت کد" className="w-full min-w-full xl:min-w-min xl:max-w-60" pending={isPending} type="submit" />
</div>
</div>
</form>
)
}
+119 -67
View File
@@ -1,24 +1,102 @@
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="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>
@@ -28,42 +106,43 @@ export const MyAccountPage = () => {
</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 }) => (
<Controller name="mobile_number" control={control} render={({ field }) => (
<div>
<Input placeholder="شماره تماس" type="number" field={field} />
<ErrorComponent show={errors.phoneNumber} message={errors.phoneNumber?.message} />
<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="marketName" control={control} render={({ field }) => (
<Controller name="shopName" control={control} render={({ field }) => (
<div>
<Input placeholder="نام شرکت" field={field} />
<ErrorComponent show={errors.marketName} message={errors.marketName?.message} />
<Input placeholder={user?.shopName ? user?.shopName : "نام شرکت"} field={field} />
<ErrorComponent show={errors.shopName} message={errors.shopName?.message} />
</div>
)} />
<Controller name="name" control={control} render={({ field }) => (
<Controller name="first_name" control={control} render={({ field }) => (
<div>
<Input placeholder="نام" field={field} />
<ErrorComponent show={errors.name} message={errors.name?.message} />
<Input placeholder={user?.first_name ? user?.first_name : "نام"} field={field} />
<ErrorComponent show={errors.first_name} message={errors.first_name?.message} />
</div>
)} />
<Controller name="family" control={control} render={({ field }) => (
<Controller name="last_name" control={control} render={({ field }) => (
<div>
<Input placeholder="نام خانوادگی" field={field} />
<ErrorComponent show={errors.family} message={errors.family?.message} />
<Input placeholder={user?.last_name ? user?.last_name : "نام خانوادگی"} field={field} />
<ErrorComponent show={errors.last_name} message={errors.last_name?.message} />
</div>
)} />
<Controller name="capCity" control={control} render={({ field }) => (
<Controller name="province_name" control={control} render={({ field }) => (
<div>
<ComboBox field={field} />
<ErrorComponent show={errors.capCity} message={errors.capCity?.message} />
<ComboBox field={field} data={provinceData} placeholder={user?.province_name} />
<ErrorComponent show={errors.province_name} message={errors.province_name?.message} />
</div>
)} />
<Controller name="city" control={control} render={({ field }) => (
<Controller name="city_name" control={control} render={({ field }) => (
<div>
<ComboBox field={field} />
<ErrorComponent show={errors.city} message={errors.city?.message} />
<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>
@@ -79,64 +158,37 @@ export const MyAccountPage = () => {
<div className="grid grid-cols-1 md:grid-cols-2 gap-[18px]">
<Controller name="address" control={control} render={({ field }) => (
<div>
<Input placeholder="آدرس" field={field} />
<Input placeholder={user?.address ? user?.address : "آدرس"} field={field} />
<ErrorComponent show={errors.address} message={errors.address?.message} />
</div>
)} />
<Controller name="phoneNumber1" control={control} render={({ field }) => (
<Controller name="national_code" control={control} render={({ field }) => (
<div>
<Input placeholder="شماره تماس" type="number" field={field} />
<ErrorComponent show={errors.phoneNumber1} message={errors.phoneNumber1?.message} />
<Input placeholder={user?.national_code} type="number" field={field} disabled />
<ErrorComponent show={errors.national_code} message={errors.national_code?.message} />
</div>
)} />
<Controller name="staticNumber" control={control} render={({ field }) => (
<Controller name="cell_number" control={control} render={({ field }) => (
<div>
<Input placeholder="شماره ثابت" type="number" field={field} />
<ErrorComponent show={errors.staticNumber} message={errors.staticNumber?.message} />
<Input placeholder={user?.cell_number} type="number" field={field} />
<ErrorComponent show={errors.cell_number} message={errors.cell_number?.message} />
</div>
)} />
<Controller name="date" control={control} render={({ field }) => (
<Controller name="birthDate" control={control} render={({ field }) => (
<div className="flex flex-col gap-y-9">
<DatepickerComponent field={field} />
<ErrorComponent show={errors.date} message={errors.date?.message} />
<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="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="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>
</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 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>
</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>
</div>
<ButtonComponent className="w-full min-w-full xl:min-w-min xl:max-w-60" pending={isPending} title={pending ? "لغو درخواست" : "ثبت درخواست"} type="submit" />
</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({
@@ -120,3 +109,22 @@ export const AddNewCardSchema = yup.object({
.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
}
)
);
+64 -23
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
req: {
userId: string,
desc: string,
expireDate: string,
created_at: string,
updated_at: string,
__v: number,
id: string
metaData: null | any,
status: number
},
status: number,
expireDate: string
}
interface TransactionsData {
@@ -313,3 +311,46 @@ 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> },
];