change name logind all component
This commit is contained in:
Generated
+55
-3
@@ -18,12 +18,14 @@
|
||||
"formik": "^2.4.6",
|
||||
"i18next": "^25.4.2",
|
||||
"iconsax-react": "^0.0.8",
|
||||
"leaflet": "^1.9.4",
|
||||
"moment-jalaali": "^0.10.4",
|
||||
"rc-rate": "^2.13.1",
|
||||
"react": "^19.1.1",
|
||||
"react-dom": "^19.1.1",
|
||||
"react-dropzone": "^14.3.8",
|
||||
"react-i18next": "^15.7.2",
|
||||
"react-leaflet": "^5.0.0-rc.2",
|
||||
"react-loading-skeleton": "^3.5.0",
|
||||
"react-multi-date-picker": "^4.5.2",
|
||||
"react-otp-input": "^3.1.1",
|
||||
@@ -37,6 +39,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.33.0",
|
||||
"@types/leaflet": "^1.9.21",
|
||||
"@types/moment-jalaali": "^0.7.9",
|
||||
"@types/node": "^24.10.1",
|
||||
"@types/react": "^19.1.10",
|
||||
@@ -1456,6 +1459,17 @@
|
||||
"react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@react-leaflet/core": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@react-leaflet/core/-/core-3.0.0.tgz",
|
||||
"integrity": "sha512-3EWmekh4Nz+pGcr+xjf0KNyYfC3U2JjnkWsh0zcqaexYqmmB5ZhH37kz41JXGmKzpaMZCnPofBBm64i+YrEvGQ==",
|
||||
"license": "Hippocratic-2.1",
|
||||
"peerDependencies": {
|
||||
"leaflet": "^1.9.0",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@react-stately/flags": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@react-stately/flags/-/flags-3.1.2.tgz",
|
||||
@@ -2128,6 +2142,13 @@
|
||||
"integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/geojson": {
|
||||
"version": "7946.0.16",
|
||||
"resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz",
|
||||
"integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/hoist-non-react-statics": {
|
||||
"version": "3.3.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.7.tgz",
|
||||
@@ -2147,6 +2168,16 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/leaflet": {
|
||||
"version": "1.9.21",
|
||||
"resolved": "https://registry.npmjs.org/@types/leaflet/-/leaflet-1.9.21.tgz",
|
||||
"integrity": "sha512-TbAd9DaPGSnzp6QvtYngntMZgcRk+igFELwR2N99XZn7RXUdKgsXMR+28bUO0rPsWp8MIu/f47luLIQuSLYv/w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/geojson": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/moment-jalaali": {
|
||||
"version": "0.7.9",
|
||||
"resolved": "https://registry.npmjs.org/@types/moment-jalaali/-/moment-jalaali-0.7.9.tgz",
|
||||
@@ -3759,9 +3790,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/js-yaml": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
|
||||
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
|
||||
"integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -3828,6 +3859,13 @@
|
||||
"json-buffer": "3.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/leaflet": {
|
||||
"version": "1.9.4",
|
||||
"resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.9.4.tgz",
|
||||
"integrity": "sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA==",
|
||||
"license": "BSD-2-Clause",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/levn": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
|
||||
@@ -4663,6 +4701,20 @@
|
||||
"integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/react-leaflet": {
|
||||
"version": "5.0.0-rc.2",
|
||||
"resolved": "https://registry.npmjs.org/react-leaflet/-/react-leaflet-5.0.0-rc.2.tgz",
|
||||
"integrity": "sha512-1xQGYG9mEIW+nfkQhqgHImwUuB1UDlnzYFSzv6PrBFDBeYrFmv0BbpwpNAFdJg/UQ2yz5UZSL7ZwlUxjwb8MZw==",
|
||||
"license": "Hippocratic-2.1",
|
||||
"dependencies": {
|
||||
"@react-leaflet/core": "^3.0.0-rc.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"leaflet": "^1.9.0",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-loading-skeleton": {
|
||||
"version": "3.5.0",
|
||||
"resolved": "https://registry.npmjs.org/react-loading-skeleton/-/react-loading-skeleton-3.5.0.tgz",
|
||||
|
||||
@@ -20,12 +20,14 @@
|
||||
"formik": "^2.4.6",
|
||||
"i18next": "^25.4.2",
|
||||
"iconsax-react": "^0.0.8",
|
||||
"leaflet": "^1.9.4",
|
||||
"moment-jalaali": "^0.10.4",
|
||||
"rc-rate": "^2.13.1",
|
||||
"react": "^19.1.1",
|
||||
"react-dom": "^19.1.1",
|
||||
"react-dropzone": "^14.3.8",
|
||||
"react-i18next": "^15.7.2",
|
||||
"react-leaflet": "^5.0.0-rc.2",
|
||||
"react-loading-skeleton": "^3.5.0",
|
||||
"react-multi-date-picker": "^4.5.2",
|
||||
"react-otp-input": "^3.1.1",
|
||||
@@ -39,6 +41,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.33.0",
|
||||
"@types/leaflet": "^1.9.21",
|
||||
"@types/moment-jalaali": "^0.7.9",
|
||||
"@types/node": "^24.10.1",
|
||||
"@types/react": "^19.1.10",
|
||||
|
||||
@@ -3,6 +3,7 @@ import { BrowserRouter } from 'react-router-dom'
|
||||
import 'swiper/swiper-bundle.css';
|
||||
// import 'rc-rate/assets/index.css';
|
||||
// import "react-multi-date-picker/styles/layouts/mobile.css"
|
||||
import 'leaflet/dist/leaflet.css';
|
||||
import './assets/fonts/irancell/style.css'
|
||||
import i18next from 'i18next'
|
||||
import { I18nextProvider } from 'react-i18next'
|
||||
|
||||
@@ -5,7 +5,7 @@ import { clx } from '../helpers/utils'
|
||||
|
||||
type Props = {
|
||||
className?: string;
|
||||
isLoading?: boolean,
|
||||
isloading?: boolean,
|
||||
} & ButtonHTMLAttributes<HTMLButtonElement> &
|
||||
XOR<{ children: ReactNode }, { label: string }>;
|
||||
|
||||
@@ -18,9 +18,9 @@ const Button: FC<Props> = memo((props: Props) => {
|
||||
);
|
||||
|
||||
return (
|
||||
<button disabled={props.isLoading} {...props} className={`${buttonClass} ${props.className}`} >
|
||||
<button disabled={props.isloading} {...props} className={`${buttonClass} ${props.className}`} >
|
||||
{
|
||||
props.isLoading ?
|
||||
props.isloading ?
|
||||
<MoonLoader color="white" size={16} />
|
||||
:
|
||||
props.label || props.children
|
||||
|
||||
@@ -5,7 +5,7 @@ import Button from './Button'
|
||||
import Textarea from './Textarea'
|
||||
|
||||
type Props = {
|
||||
isLoading?: boolean,
|
||||
isloading?: boolean,
|
||||
close: () => void,
|
||||
isOpen: boolean,
|
||||
onConfrim: (text?: string) => void,
|
||||
@@ -51,7 +51,7 @@ const ModalConfrim: FC<Props> = (props: Props) => {
|
||||
<Button
|
||||
label={t('confrim.yes')}
|
||||
onClick={() => props.onConfrim(props.isHasDescription ? description : undefined)}
|
||||
isLoading={props.isLoading}
|
||||
isloading={props.isloading}
|
||||
/>
|
||||
<Button
|
||||
label={t('confrim.cancel')}
|
||||
|
||||
@@ -9,7 +9,7 @@ import Pagination from '../components/Pagination';
|
||||
const Table = <T extends RowDataType>({
|
||||
columns,
|
||||
data,
|
||||
isLoading = false,
|
||||
isloading = false,
|
||||
onRowClick,
|
||||
className = '',
|
||||
rowClassName = '',
|
||||
@@ -74,7 +74,7 @@ const Table = <T extends RowDataType>({
|
||||
const hasActions = (actionsPosition === 'top' || actionsPosition === 'above-header') && (actions || (selectable && selectedRows.length > 0 && selectedActions));
|
||||
const roundedClass = hasActions ? 'rounded-b-2xl' : 'rounded-2xl';
|
||||
|
||||
if (isLoading) {
|
||||
if (isloading) {
|
||||
return (
|
||||
<div className={`bg-white ${roundedClass} overflow-hidden`}>
|
||||
{Array.from({ length: emptyRowsCount }).map((_, index) => (
|
||||
@@ -145,7 +145,7 @@ const Table = <T extends RowDataType>({
|
||||
};
|
||||
|
||||
const renderTableBody = () => {
|
||||
if (isLoading) {
|
||||
if (isloading) {
|
||||
return (
|
||||
<DefaultTableSkeleton tdCount={columns.length + (selectable ? 1 : 0) + (rowActions ? 1 : 0)} trCount={emptyRowsCount} />
|
||||
);
|
||||
|
||||
@@ -4,10 +4,10 @@ import ModalConfrim from './ModalConfrim'
|
||||
|
||||
interface Props {
|
||||
onDelete: () => void,
|
||||
isLoading?: boolean
|
||||
isloading?: boolean
|
||||
}
|
||||
|
||||
const TrashWithConfrim: FC<Props> = ({ onDelete, isLoading = false }) => {
|
||||
const TrashWithConfrim: FC<Props> = ({ onDelete, isloading = false }) => {
|
||||
const [isConfirm, setIsConfirm] = useState(false)
|
||||
return (
|
||||
<div>
|
||||
@@ -21,7 +21,7 @@ const TrashWithConfrim: FC<Props> = ({ onDelete, isLoading = false }) => {
|
||||
isOpen={isConfirm}
|
||||
close={() => setIsConfirm(false)}
|
||||
onConfrim={() => onDelete()}
|
||||
isLoading={isLoading}
|
||||
isloading={isloading}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -29,7 +29,7 @@ export interface ActionType {
|
||||
export interface TableProps<T extends RowDataType> {
|
||||
columns: ColumnType<T>[];
|
||||
data?: T[];
|
||||
isLoading?: boolean;
|
||||
isloading?: boolean;
|
||||
onRowClick?: (id: string | number, item: T) => void;
|
||||
className?: string;
|
||||
rowClassName?: string | ((item: T, index: number) => string);
|
||||
|
||||
@@ -103,7 +103,7 @@ const LoginStep1: FC = () => {
|
||||
label={t('auth.next')}
|
||||
className='mt-8'
|
||||
onClick={() => formik.handleSubmit()}
|
||||
isLoading={loginWithOtp.isPending}
|
||||
isloading={loginWithOtp.isPending}
|
||||
/>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -109,7 +109,7 @@ const LoginStep2: FC = () => {
|
||||
label={t('auth.login')}
|
||||
className='mt-8'
|
||||
onClick={() => formik.handleSubmit()}
|
||||
isLoading={otpVerify.isPending}
|
||||
isloading={otpVerify.isPending}
|
||||
/>
|
||||
|
||||
{/* <div onClick={() => setStepLogin(3)} className='mt-6 cursor-pointer flex gap-2 items-center text-sm'>
|
||||
|
||||
@@ -68,7 +68,7 @@ const RegisterStep1: FC = () => {
|
||||
label={t('auth.next')}
|
||||
className='mt-8'
|
||||
onClick={() => formik.handleSubmit()}
|
||||
isLoading={loginWithOtp.isPending || checkHasAccount.isPending}
|
||||
isloading={loginWithOtp.isPending || checkHasAccount.isPending}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -180,7 +180,7 @@ const CategoryFood: FC = () => {
|
||||
<Table
|
||||
columns={columns}
|
||||
data={filteredCategories}
|
||||
isLoading={isLoading}
|
||||
isloading={isLoading}
|
||||
selectable
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -116,7 +116,7 @@ const CreateFood: FC = () => {
|
||||
<Button
|
||||
className='px-5'
|
||||
onClick={() => formik.handleSubmit()}
|
||||
isLoading={isPending || isUploading}
|
||||
isloading={isPending || isUploading}
|
||||
>
|
||||
<div className='flex gap-2 items-center'>
|
||||
<TickCircle className='size-5' color='white' />
|
||||
|
||||
@@ -60,7 +60,7 @@ const FoodsList: FC = () => {
|
||||
<Table
|
||||
columns={columns}
|
||||
data={foods}
|
||||
isLoading={isLoading}
|
||||
isloading={isLoading}
|
||||
pagination={{
|
||||
currentPage,
|
||||
totalPages,
|
||||
|
||||
@@ -182,7 +182,7 @@ const UpdateFood: FC = () => {
|
||||
<Button
|
||||
className='px-5'
|
||||
onClick={() => formik.handleSubmit()}
|
||||
isLoading={isPending || isUploading}
|
||||
isloading={isPending || isUploading}
|
||||
>
|
||||
<div className='flex gap-2 items-center'>
|
||||
<TickCircle className='size-5' color='white' />
|
||||
|
||||
@@ -66,7 +66,7 @@ const CategoryForm: FC<Props> = ({
|
||||
<Button
|
||||
onClick={() => formik.handleSubmit()}
|
||||
className="w-full"
|
||||
isLoading={isSubmitting}
|
||||
isloading={isSubmitting}
|
||||
>
|
||||
<Add color='#fff' size={20} className="ml-2" />
|
||||
ذخیره
|
||||
|
||||
@@ -59,7 +59,7 @@ const CategoryTableColumns = ({ onStatusChange, onEdit, onDelete, isDeleting = f
|
||||
/>
|
||||
<TrashWithConfrim
|
||||
onDelete={() => onDelete(item.id)}
|
||||
isLoading={isDeleting}
|
||||
isloading={isDeleting}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -80,7 +80,7 @@ export const getFoodTableColumns = ({ onDelete, isDeleting }: GetFoodTableColumn
|
||||
</Link>
|
||||
<TrashWithConfrim
|
||||
onDelete={() => onDelete(item.id)}
|
||||
isLoading={isDeleting}
|
||||
isloading={isDeleting}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -49,7 +49,7 @@ const CreateRole: FC = () => {
|
||||
<Button
|
||||
className='px-5'
|
||||
onClick={() => formik.handleSubmit()}
|
||||
isLoading={isPending}
|
||||
isloading={isPending}
|
||||
>
|
||||
<div className='flex gap-2 items-center'>
|
||||
<TickCircle className='size-5' color='white' />
|
||||
|
||||
@@ -58,7 +58,7 @@ const RolesList: FC = () => {
|
||||
<Table
|
||||
columns={columns}
|
||||
data={roles}
|
||||
isLoading={isLoading}
|
||||
isloading={isLoading}
|
||||
pagination={{
|
||||
currentPage,
|
||||
totalPages,
|
||||
|
||||
@@ -81,7 +81,7 @@ const UpdateRole: FC = () => {
|
||||
<Button
|
||||
className='px-5'
|
||||
onClick={() => formik.handleSubmit()}
|
||||
isLoading={isPending}
|
||||
isloading={isPending}
|
||||
>
|
||||
<div className='flex gap-2 items-center'>
|
||||
<TickCircle className='size-5' color='white' />
|
||||
|
||||
@@ -46,7 +46,7 @@ export const getRoleTableColumns = ({ onDelete, isDeleting }: GetRoleTableColumn
|
||||
{onDelete && (
|
||||
<TrashWithConfrim
|
||||
onDelete={() => onDelete(item.id)}
|
||||
isLoading={isDeleting}
|
||||
isloading={isDeleting}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -56,7 +56,7 @@ const CreateSchedule: FC = () => {
|
||||
<Button
|
||||
className='px-5'
|
||||
onClick={() => formik.handleSubmit()}
|
||||
isLoading={isPending}
|
||||
isloading={isPending}
|
||||
>
|
||||
<div className='flex gap-2 items-center'>
|
||||
<TickCircle className='size-5' color='white' />
|
||||
|
||||
@@ -61,7 +61,7 @@ const ScheduleList: FC = () => {
|
||||
<Table
|
||||
columns={columns}
|
||||
data={schedules}
|
||||
isLoading={isLoading}
|
||||
isloading={isLoading}
|
||||
pagination={{
|
||||
currentPage,
|
||||
totalPages,
|
||||
|
||||
@@ -83,7 +83,7 @@ const UpdateSchedule: FC = () => {
|
||||
<Button
|
||||
className='px-5'
|
||||
onClick={() => formik.handleSubmit()}
|
||||
isLoading={isPending}
|
||||
isloading={isPending}
|
||||
>
|
||||
<div className='flex gap-2 items-center'>
|
||||
<TickCircle className='size-5' color='white' />
|
||||
|
||||
@@ -62,7 +62,7 @@ export const getScheduleTableColumns = ({ onDelete, isDeleting, t }: GetSchedule
|
||||
{onDelete && (
|
||||
<TrashWithConfrim
|
||||
onDelete={() => onDelete(item.id)}
|
||||
isLoading={isDeleting}
|
||||
isloading={isDeleting}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -1,24 +1,253 @@
|
||||
import Button from '@/components/Button'
|
||||
import ColorPicker from '@/components/ColorPicker'
|
||||
import Input from '@/components/Input'
|
||||
import { type FC } from 'react'
|
||||
import Textarea from '@/components/Textarea'
|
||||
import UploadBox from '@/components/UploadBox'
|
||||
import DefaulModal from '@/components/DefaulModal'
|
||||
import LocationPicker from './components/LocationPicker'
|
||||
import LocationInput from './components/LocationInput'
|
||||
import { TickCircle } from 'iconsax-react'
|
||||
import { type FC, useEffect, useState } from 'react'
|
||||
import { useGetRestaurant, useUpdateRestaurant } from './hooks/useSettingData'
|
||||
import { useSingleUpload } from '../uploader/hooks/useUploaderData'
|
||||
import type { UpdateRestaurantType } from './types/Types'
|
||||
import type { ErrorType } from '@/helpers/types'
|
||||
import { useFormik } from 'formik'
|
||||
import * as yup from 'yup'
|
||||
import { toast } from 'react-toastify'
|
||||
import { extractErrorMessage } from '@/config/func'
|
||||
|
||||
const validationSchema = yup.object({
|
||||
name: yup.string().required('نام رستوران الزامی است'),
|
||||
menuColor: yup.string().required('رنگ منو الزامی است'),
|
||||
phone: yup.string().required('شماره تماس الزامی است'),
|
||||
instagram: yup.string().url('لینک اینستاگرام معتبر نیست').nullable(),
|
||||
address: yup.string().required('آدرس الزامی است'),
|
||||
latitude: yup.number().required('انتخاب موقعیت روی نقشه الزامی است'),
|
||||
longitude: yup.number().required('انتخاب موقعیت روی نقشه الزامی است'),
|
||||
description: yup.string().nullable(),
|
||||
})
|
||||
|
||||
const GeneralSettings: FC = () => {
|
||||
const { data: restaurant, refetch } = useGetRestaurant()
|
||||
const { mutate: updateRestaurant, isPending } = useUpdateRestaurant()
|
||||
const { mutate: singleUpload, isPending: isUploading } = useSingleUpload()
|
||||
const [isLocationModalOpen, setIsLocationModalOpen] = useState(false)
|
||||
const [logoFile, setLogoFile] = useState<File[]>([])
|
||||
|
||||
const formik = useFormik<UpdateRestaurantType>({
|
||||
initialValues: {
|
||||
name: '',
|
||||
menuColor: '#000000',
|
||||
phone: '',
|
||||
instagram: '',
|
||||
address: '',
|
||||
latitude: 0,
|
||||
longitude: 0,
|
||||
description: '',
|
||||
logo: '',
|
||||
},
|
||||
validationSchema,
|
||||
onSubmit: (values) => {
|
||||
const submitSettings = (logoUrl?: string) => {
|
||||
const settingsData: UpdateRestaurantType = {
|
||||
...values,
|
||||
logo: logoUrl || values.logo || ''
|
||||
}
|
||||
|
||||
updateRestaurant(settingsData, {
|
||||
onSuccess: () => {
|
||||
toast.success('تنظیمات با موفقیت ذخیره شد')
|
||||
refetch()
|
||||
setLogoFile([])
|
||||
},
|
||||
onError: (error: unknown) => {
|
||||
toast.error('خطا در ذخیره تنظیمات')
|
||||
console.error(error)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
if (logoFile.length > 0) {
|
||||
singleUpload(logoFile[0], {
|
||||
onSuccess: (response) => {
|
||||
const logoUrl = response?.data?.url || ''
|
||||
submitSettings(logoUrl)
|
||||
},
|
||||
onError: (error: ErrorType) => {
|
||||
toast.error(extractErrorMessage(error, 'خطا در آپلود لوگو'))
|
||||
},
|
||||
})
|
||||
} else {
|
||||
submitSettings()
|
||||
}
|
||||
},
|
||||
enableReinitialize: true,
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
if (restaurant?.data) {
|
||||
const restaurantData = restaurant.data
|
||||
formik.setValues({
|
||||
name: restaurantData.name || '',
|
||||
menuColor: restaurantData.menuColor || '#000000',
|
||||
phone: restaurantData.phoneNumber || '',
|
||||
instagram: restaurantData.instagram || '',
|
||||
address: restaurantData.address || '',
|
||||
latitude: restaurantData.latitude || 0,
|
||||
longitude: restaurantData.longitude || 0,
|
||||
description: restaurantData.description || '',
|
||||
logo: restaurantData.logo || '',
|
||||
})
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [restaurant])
|
||||
|
||||
const handleLocationSelect = (lat: number, lng: number) => {
|
||||
formik.setFieldValue('latitude', lat)
|
||||
formik.setFieldValue('longitude', lng)
|
||||
setIsLocationModalOpen(false)
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<div className='bg-white rounded-4xl p-8'>
|
||||
<div className='text-lg font-light'>
|
||||
تنظیمات عمومی
|
||||
<>
|
||||
<div className='bg-white rounded-4xl p-8'>
|
||||
<div className='text-lg font-light'>
|
||||
تنظیمات عمومی
|
||||
</div>
|
||||
|
||||
<form onSubmit={formik.handleSubmit}>
|
||||
<div className='mt-8 rowTwoInput'>
|
||||
<Input
|
||||
label='نام رستوران'
|
||||
name='name'
|
||||
value={formik.values.name}
|
||||
onChange={formik.handleChange}
|
||||
onBlur={formik.handleBlur}
|
||||
error_text={formik.touched.name && formik.errors.name ? formik.errors.name : undefined}
|
||||
/>
|
||||
|
||||
<ColorPicker
|
||||
label='رنگ منو'
|
||||
value={formik.values.menuColor}
|
||||
onChange={(value) => formik.setFieldValue('menuColor', value)}
|
||||
error_text={formik.touched.menuColor && formik.errors.menuColor ? formik.errors.menuColor : undefined}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='mt-8'>
|
||||
<UploadBox
|
||||
label='لوگو'
|
||||
onChange={(files) => setLogoFile(files)}
|
||||
isReset={logoFile.length === 0 && !formik.values.logo}
|
||||
/>
|
||||
|
||||
{
|
||||
formik.values.logo && (
|
||||
<div className='mt-3'>
|
||||
<img src={formik.values.logo} alt='logo' className='w-20 h-20 rounded-full' />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
|
||||
<div className='mt-8 rowTwoInput'>
|
||||
<Input
|
||||
label='شماره تماس'
|
||||
name='phone'
|
||||
value={formik.values.phone}
|
||||
onChange={formik.handleChange}
|
||||
onBlur={formik.handleBlur}
|
||||
error_text={formik.touched.phone && formik.errors.phone ? formik.errors.phone : undefined}
|
||||
/>
|
||||
|
||||
<Input
|
||||
label='اینستاگرام'
|
||||
name='instagram'
|
||||
value={formik.values.instagram}
|
||||
onChange={formik.handleChange}
|
||||
onBlur={formik.handleBlur}
|
||||
error_text={formik.touched.instagram && formik.errors.instagram ? formik.errors.instagram : undefined}
|
||||
placeholder='https://instagram.com/...'
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='mt-8'>
|
||||
<Textarea
|
||||
label='آدرس'
|
||||
name='address'
|
||||
value={formik.values.address}
|
||||
onChange={formik.handleChange}
|
||||
onBlur={formik.handleBlur}
|
||||
error_text={formik.touched.address && formik.errors.address ? formik.errors.address : undefined}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<LocationInput
|
||||
label='موقعیت روی نقشه'
|
||||
latitude={formik.values.latitude}
|
||||
longitude={formik.values.longitude}
|
||||
onClick={() => setIsLocationModalOpen(true)}
|
||||
error_text={
|
||||
(formik.touched.latitude || formik.touched.longitude) && (formik.errors.latitude || formik.errors.longitude)
|
||||
? formik.errors.latitude || formik.errors.longitude
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
|
||||
<div className='mt-8'>
|
||||
<Textarea
|
||||
label='توضیحات'
|
||||
name='description'
|
||||
value={formik.values.description || ''}
|
||||
onChange={formik.handleChange}
|
||||
onBlur={formik.handleBlur}
|
||||
error_text={formik.touched.description && formik.errors.description ? formik.errors.description : undefined}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='mt-8 flex justify-end'>
|
||||
<Button
|
||||
type='submit'
|
||||
className='w-fit px-6'
|
||||
isloading={isPending || isUploading}
|
||||
disabled={isPending || isUploading}
|
||||
>
|
||||
<div className='flex gap-2 items-center'>
|
||||
<TickCircle
|
||||
size={20}
|
||||
color='#fff'
|
||||
/>
|
||||
<span>ذخیره</span>
|
||||
</div>
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div className='mt-8 rowTwoInput'>
|
||||
<Input
|
||||
label='نام رستوران'
|
||||
/>
|
||||
|
||||
<ColorPicker
|
||||
label='رنگ منو'
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<DefaulModal
|
||||
open={isLocationModalOpen}
|
||||
close={() => setIsLocationModalOpen(false)}
|
||||
isHeader={true}
|
||||
title_header='انتخاب موقعیت روی نقشه'
|
||||
width={800}
|
||||
>
|
||||
<div className='p-4'>
|
||||
<LocationPicker
|
||||
initialPosition={
|
||||
formik.values.latitude && formik.values.longitude
|
||||
? [formik.values.latitude, formik.values.longitude]
|
||||
: undefined
|
||||
}
|
||||
onLocationSelect={handleLocationSelect}
|
||||
/>
|
||||
<div className='mt-4 text-xs text-gray-600 text-center'>
|
||||
روی نقشه کلیک کنید تا موقعیت را انتخاب کنید
|
||||
</div>
|
||||
</div>
|
||||
</DefaulModal>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
import { type FC } from 'react'
|
||||
import { Location } from 'iconsax-react'
|
||||
import Error from '@/components/Error'
|
||||
|
||||
type LocationInputProps = {
|
||||
label?: string
|
||||
latitude?: number
|
||||
longitude?: number
|
||||
onClick: () => void
|
||||
error_text?: string
|
||||
}
|
||||
|
||||
const LocationInput: FC<LocationInputProps> = ({
|
||||
label,
|
||||
latitude,
|
||||
longitude,
|
||||
onClick,
|
||||
error_text,
|
||||
}) => {
|
||||
const getLocationDisplay = () => {
|
||||
if (latitude && longitude) {
|
||||
return `${latitude.toFixed(6)}, ${longitude.toFixed(6)}`
|
||||
}
|
||||
return 'روی نقشه کلیک کنید'
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='mt-8'>
|
||||
{label && <div className='text-sm mb-1'>{label}</div>}
|
||||
<div
|
||||
onClick={onClick}
|
||||
className='w-full bg-white h-10 text-black px-4 text-xs rounded-xl border border-border flex items-center gap-2 cursor-pointer mt-1'
|
||||
>
|
||||
<Location size={20} color='#8C90A3' />
|
||||
<span className='flex-1 text-right'>{getLocationDisplay()}</span>
|
||||
</div>
|
||||
{error_text && (
|
||||
<Error errorText={error_text} />
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default LocationInput
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
import { type FC, useState, useEffect } from 'react'
|
||||
import { MapContainer, TileLayer, Marker, useMapEvents, useMap } from 'react-leaflet'
|
||||
import { Icon } from 'leaflet'
|
||||
import type { LatLngTuple } from 'leaflet'
|
||||
|
||||
interface LocationPickerProps {
|
||||
initialPosition?: [number, number]
|
||||
onLocationSelect: (lat: number, lng: number) => void
|
||||
}
|
||||
|
||||
const ChangeView = ({ center, zoom }: { center: LatLngTuple; zoom: number }) => {
|
||||
const map = useMap()
|
||||
useEffect(() => {
|
||||
map.setView(center, zoom)
|
||||
}, [map, center, zoom])
|
||||
return null
|
||||
}
|
||||
|
||||
const LocationPicker: FC<LocationPickerProps> = ({ initialPosition, onLocationSelect }) => {
|
||||
const [position, setPosition] = useState<LatLngTuple>(
|
||||
initialPosition || [35.6892, 51.3890] // تهران به عنوان پیشفرض
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
if (initialPosition) {
|
||||
setPosition(initialPosition)
|
||||
}
|
||||
}, [initialPosition])
|
||||
|
||||
const LocationMarker = () => {
|
||||
useMapEvents({
|
||||
click(e) {
|
||||
const newPosition: LatLngTuple = [e.latlng.lat, e.latlng.lng]
|
||||
setPosition(newPosition)
|
||||
onLocationSelect(newPosition[0], newPosition[1])
|
||||
},
|
||||
})
|
||||
|
||||
const customIcon = new Icon({
|
||||
iconUrl: 'https://unpkg.com/leaflet@1.9.4/dist/images/marker-icon.png',
|
||||
iconRetinaUrl: 'https://unpkg.com/leaflet@1.9.4/dist/images/marker-icon-2x.png',
|
||||
shadowUrl: 'https://unpkg.com/leaflet@1.9.4/dist/images/marker-shadow.png',
|
||||
iconSize: [25, 41],
|
||||
iconAnchor: [12, 41],
|
||||
popupAnchor: [1, -34],
|
||||
shadowSize: [41, 41],
|
||||
})
|
||||
|
||||
return position ? <Marker position={position} icon={customIcon} /> : null
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="w-full h-[400px] rounded-xl overflow-hidden">
|
||||
<MapContainer
|
||||
center={position}
|
||||
zoom={13}
|
||||
style={{ height: '100%', width: '100%' }}
|
||||
scrollWheelZoom={true}
|
||||
>
|
||||
<ChangeView center={position} zoom={13} />
|
||||
<TileLayer
|
||||
attribution='© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
|
||||
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
|
||||
/>
|
||||
<LocationMarker />
|
||||
</MapContainer>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default LocationPicker
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
import { useMutation, useQuery } from "@tanstack/react-query";
|
||||
import * as api from "../service/SettingService";
|
||||
|
||||
export const useGetRestaurant = () => {
|
||||
return useQuery({
|
||||
queryKey: ["restaurant"],
|
||||
queryFn: api.getRestaurant,
|
||||
});
|
||||
};
|
||||
|
||||
export const useUpdateRestaurant = () => {
|
||||
return useMutation({
|
||||
mutationFn: api.updateRestaurant,
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,20 @@
|
||||
import axios from "@/config/axios";
|
||||
import type {
|
||||
GetRestaurantResponse,
|
||||
UpdateRestaurantType,
|
||||
} from "../types/Types";
|
||||
|
||||
export const getRestaurant = async (): Promise<GetRestaurantResponse> => {
|
||||
const { data } = await axios.get<GetRestaurantResponse>(
|
||||
"/admin/restaurants/my-restaurant"
|
||||
);
|
||||
return data;
|
||||
};
|
||||
|
||||
export const updateRestaurant = async (params: UpdateRestaurantType) => {
|
||||
const { data } = await axios.patch(
|
||||
"/admin/restaurants/my-restaurant",
|
||||
params
|
||||
);
|
||||
return data;
|
||||
};
|
||||
@@ -0,0 +1,42 @@
|
||||
import type { IResponse } from "@/types/response.types";
|
||||
|
||||
export type UpdateRestaurantType = {
|
||||
name: string;
|
||||
menuColor: string;
|
||||
phone: string;
|
||||
instagram: string;
|
||||
address: string;
|
||||
logo?: string;
|
||||
latitude: number;
|
||||
longitude: number;
|
||||
description: string;
|
||||
};
|
||||
|
||||
export type Restaurant = {
|
||||
id: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
deletedAt: string | null;
|
||||
name: string;
|
||||
slug: string;
|
||||
logo: string | null;
|
||||
address: string | null;
|
||||
menuColor: string | null;
|
||||
latitude: number | null;
|
||||
longitude: number | null;
|
||||
serviceArea: unknown | null;
|
||||
isActive: boolean;
|
||||
establishedYear: number | null;
|
||||
phoneNumber: string | null;
|
||||
instagram: string | null;
|
||||
telegram: string | null;
|
||||
whatsapp: string | null;
|
||||
description: string | null;
|
||||
seoTitle: string | null;
|
||||
seoDescription: string | null;
|
||||
tagNames: unknown | null;
|
||||
images: unknown | null;
|
||||
vat: number;
|
||||
};
|
||||
|
||||
export type GetRestaurantResponse = IResponse<Restaurant>;
|
||||
Reference in New Issue
Block a user