edit address
This commit is contained in:
+1
-1
File diff suppressed because one or more lines are too long
@@ -5,6 +5,7 @@ import {
|
|||||||
updateAddress,
|
updateAddress,
|
||||||
deleteAddress,
|
deleteAddress,
|
||||||
setDefaultAddress,
|
setDefaultAddress,
|
||||||
|
getAddressById,
|
||||||
} from "../service/AddressService";
|
} from "../service/AddressService";
|
||||||
|
|
||||||
export const useGetAddresses = () => {
|
export const useGetAddresses = () => {
|
||||||
@@ -53,3 +54,11 @@ export const useSetDefaultAddress = () => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const useGetAddressById = (id: string | null) => {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: ["address", id],
|
||||||
|
queryFn: () => getAddressById(id!),
|
||||||
|
enabled: !!id,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import Button from '@/components/button/PrimaryButton';
|
|||||||
import { AddressDisplay } from './AddressDisplay';
|
import { AddressDisplay } from './AddressDisplay';
|
||||||
import { AddressForm } from './AddressForm';
|
import { AddressForm } from './AddressForm';
|
||||||
import { NominatimReverseGeocodingResponse } from '../../types/Types';
|
import { NominatimReverseGeocodingResponse } from '../../types/Types';
|
||||||
|
import { Location } from 'iconsax-react';
|
||||||
|
|
||||||
interface AddressDetailsModalProps {
|
interface AddressDetailsModalProps {
|
||||||
visible: boolean;
|
visible: boolean;
|
||||||
@@ -15,7 +16,9 @@ interface AddressDetailsModalProps {
|
|||||||
isDefault: boolean;
|
isDefault: boolean;
|
||||||
};
|
};
|
||||||
isPending: boolean;
|
isPending: boolean;
|
||||||
|
editMode?: boolean;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
|
onChangePosition?: () => void;
|
||||||
onFormDataChange: (data: Partial<AddressDetailsModalProps['formData']>) => void;
|
onFormDataChange: (data: Partial<AddressDetailsModalProps['formData']>) => void;
|
||||||
onSubmit: (e: React.FormEvent<HTMLFormElement>) => void;
|
onSubmit: (e: React.FormEvent<HTMLFormElement>) => void;
|
||||||
}
|
}
|
||||||
@@ -25,7 +28,9 @@ export const AddressDetailsModal = ({
|
|||||||
selectedAddress,
|
selectedAddress,
|
||||||
formData,
|
formData,
|
||||||
isPending,
|
isPending,
|
||||||
|
editMode = false,
|
||||||
onClose,
|
onClose,
|
||||||
|
onChangePosition,
|
||||||
onFormDataChange,
|
onFormDataChange,
|
||||||
onSubmit,
|
onSubmit,
|
||||||
}: AddressDetailsModalProps) => {
|
}: AddressDetailsModalProps) => {
|
||||||
@@ -43,6 +48,22 @@ export const AddressDetailsModal = ({
|
|||||||
<form onSubmit={onSubmit}>
|
<form onSubmit={onSubmit}>
|
||||||
<div className='px-4'>
|
<div className='px-4'>
|
||||||
<AddressDisplay selectedAddress={selectedAddress} />
|
<AddressDisplay selectedAddress={selectedAddress} />
|
||||||
|
{editMode && onChangePosition && (
|
||||||
|
<div className='mt-4 mb-6 flex justify-end'>
|
||||||
|
<Button
|
||||||
|
type='button'
|
||||||
|
onClick={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
onChangePosition();
|
||||||
|
}}
|
||||||
|
className='!bg-background !w-fit !text-foreground flex items-center gap-2 text-xs'
|
||||||
|
disabled={isPending}
|
||||||
|
>
|
||||||
|
<Location color='#6b7280' variant='Bold' className='size-4' />
|
||||||
|
تغییر موقعیت روی نقشه
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<AddressForm
|
<AddressForm
|
||||||
formData={formData}
|
formData={formData}
|
||||||
selectedAddress={selectedAddress}
|
selectedAddress={selectedAddress}
|
||||||
@@ -53,7 +74,7 @@ export const AddressDetailsModal = ({
|
|||||||
type='submit'
|
type='submit'
|
||||||
disabled={isPending}
|
disabled={isPending}
|
||||||
>
|
>
|
||||||
{isPending ? 'در حال ثبت...' : 'تایید'}
|
{isPending ? (editMode ? 'در حال بهروزرسانی...' : 'در حال ثبت...') : (editMode ? 'بهروزرسانی' : 'تایید')}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
type='button'
|
type='button'
|
||||||
|
|||||||
@@ -1,22 +1,28 @@
|
|||||||
import { useState } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useCreateAddress } from '../../hooks/useAddressData';
|
import { useCreateAddress, useUpdateAddress } from '../../hooks/useAddressData';
|
||||||
import { toast } from '@/components/Toast';
|
import { toast } from '@/components/Toast';
|
||||||
import { CreateAddressType, NominatimReverseGeocodingResponse } from '../../types/Types';
|
import { CreateAddressType, UpdateAddressType, NominatimReverseGeocodingResponse, Address } from '../../types/Types';
|
||||||
|
|
||||||
interface UseAddressFormProps {
|
interface UseAddressFormProps {
|
||||||
selectedPosition: [number, number] | null;
|
selectedPosition: [number, number] | null;
|
||||||
selectedAddress: NominatimReverseGeocodingResponse | null;
|
selectedAddress: NominatimReverseGeocodingResponse | null;
|
||||||
initialPhone?: string;
|
initialPhone?: string;
|
||||||
|
editMode?: boolean;
|
||||||
|
addressData?: Address | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useAddressForm = ({
|
export const useAddressForm = ({
|
||||||
selectedPosition,
|
selectedPosition,
|
||||||
selectedAddress,
|
selectedAddress,
|
||||||
initialPhone = '',
|
initialPhone = '',
|
||||||
|
editMode = false,
|
||||||
|
addressData = null,
|
||||||
}: UseAddressFormProps) => {
|
}: UseAddressFormProps) => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { mutate: createAddress, isPending } = useCreateAddress();
|
const { mutate: createAddress, isPending: isCreating } = useCreateAddress();
|
||||||
|
const { mutate: updateAddress, isPending: isUpdating } = useUpdateAddress();
|
||||||
|
const isPending = isCreating || isUpdating;
|
||||||
|
|
||||||
const [formData, setFormData] = useState({
|
const [formData, setFormData] = useState({
|
||||||
title: '',
|
title: '',
|
||||||
@@ -26,6 +32,18 @@ export const useAddressForm = ({
|
|||||||
isDefault: false,
|
isDefault: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (editMode && addressData) {
|
||||||
|
setFormData({
|
||||||
|
title: addressData.title || '',
|
||||||
|
addressDetails: addressData.address || '',
|
||||||
|
phone: addressData.phone || initialPhone,
|
||||||
|
postalCode: addressData.postalCode || '',
|
||||||
|
isDefault: addressData.isDefault || false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [editMode, addressData, initialPhone]);
|
||||||
|
|
||||||
const handleFormDataChange = (data: Partial<typeof formData>) => {
|
const handleFormDataChange = (data: Partial<typeof formData>) => {
|
||||||
setFormData((prev) => ({ ...prev, ...data }));
|
setFormData((prev) => ({ ...prev, ...data }));
|
||||||
};
|
};
|
||||||
@@ -53,7 +71,7 @@ export const useAddressForm = ({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const addressData: CreateAddressType = {
|
const baseAddressData = {
|
||||||
title: formData.title,
|
title: formData.title,
|
||||||
address: formData.addressDetails,
|
address: formData.addressDetails,
|
||||||
city: selectedAddress.address.city || selectedAddress.address.district || '',
|
city: selectedAddress.address.city || selectedAddress.address.district || '',
|
||||||
@@ -65,16 +83,36 @@ export const useAddressForm = ({
|
|||||||
isDefault: formData.isDefault,
|
isDefault: formData.isDefault,
|
||||||
};
|
};
|
||||||
|
|
||||||
createAddress(addressData, {
|
if (editMode && addressData) {
|
||||||
onSuccess: () => {
|
const updateData: UpdateAddressType = {
|
||||||
toast('آدرس با موفقیت ثبت شد', 'success');
|
...baseAddressData,
|
||||||
router.back();
|
id: addressData.id,
|
||||||
},
|
};
|
||||||
onError: (error) => {
|
|
||||||
const errorMessage = error instanceof Error ? error.message : 'خطا در ثبت آدرس';
|
updateAddress(updateData, {
|
||||||
toast(errorMessage, 'error');
|
onSuccess: () => {
|
||||||
},
|
toast('آدرس با موفقیت بهروزرسانی شد', 'success');
|
||||||
});
|
router.back();
|
||||||
|
},
|
||||||
|
onError: (error) => {
|
||||||
|
const errorMessage = error instanceof Error ? error.message : 'خطا در بهروزرسانی آدرس';
|
||||||
|
toast(errorMessage, 'error');
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
const createData: CreateAddressType = baseAddressData;
|
||||||
|
|
||||||
|
createAddress(createData, {
|
||||||
|
onSuccess: () => {
|
||||||
|
toast('آدرس با موفقیت ثبت شد', 'success');
|
||||||
|
router.back();
|
||||||
|
},
|
||||||
|
onError: (error) => {
|
||||||
|
const errorMessage = error instanceof Error ? error.message : 'خطا در ثبت آدرس';
|
||||||
|
toast(errorMessage, 'error');
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import { ConfirmPositionModal } from './components/ConfirmPositionModal';
|
|||||||
import { AddressDetailsModal } from './components/AddressDetailsModal';
|
import { AddressDetailsModal } from './components/AddressDetailsModal';
|
||||||
import { useAddressForm } from './hooks/useAddressForm';
|
import { useAddressForm } from './hooks/useAddressForm';
|
||||||
import { fetchAddressFromCoordinates } from './utils/fetchAddress';
|
import { fetchAddressFromCoordinates } from './utils/fetchAddress';
|
||||||
|
import { useGetAddressById } from '../hooks/useAddressData';
|
||||||
|
|
||||||
const CustomMap = dynamic(
|
const CustomMap = dynamic(
|
||||||
() => import('@/components/map/CustomMap'),
|
() => import('@/components/map/CustomMap'),
|
||||||
@@ -17,15 +18,30 @@ const CustomMap = dynamic(
|
|||||||
);
|
);
|
||||||
|
|
||||||
function OrderTrackingPage() {
|
function OrderTrackingPage() {
|
||||||
|
|
||||||
const params = useSearchParams();
|
const params = useSearchParams();
|
||||||
|
const id = params.get('id');
|
||||||
|
const editMode = !!id;
|
||||||
|
|
||||||
|
const { data: addressResponse, isLoading: isLoadingAddress } = useGetAddressById(id);
|
||||||
|
const address = addressResponse?.data;
|
||||||
const user = useAuthStore((state) => state.user);
|
const user = useAuthStore((state) => state.user);
|
||||||
const [selectedPosition, setSelectedPosition] = useState<[number, number] | null>(null);
|
const [selectedPosition, setSelectedPosition] = useState<[number, number] | null>(null);
|
||||||
const [selectedAddress, setSelectedAddress] = useState<NominatimReverseGeocodingResponse | null>(null);
|
const [selectedAddress, setSelectedAddress] = useState<NominatimReverseGeocodingResponse | null>(null);
|
||||||
const [markers, setMarkers] = useState<MarkerData[]>([]);
|
const [markers, setMarkers] = useState<MarkerData[]>([]);
|
||||||
const [mapCenter] = useState<[number, number]>([
|
const [initialPositionSet, setInitialPositionSet] = useState(false);
|
||||||
Number(params?.get('lat')) || 35.6892,
|
|
||||||
Number(params?.get('lon')) || 51.3890
|
const getInitialMapCenter = (): [number, number] => {
|
||||||
]);
|
if (editMode && address) {
|
||||||
|
return [address.latitude, address.longitude];
|
||||||
|
}
|
||||||
|
return [
|
||||||
|
Number(params?.get('lat')) || 35.6892,
|
||||||
|
Number(params?.get('lon')) || 51.3890
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
const [mapCenter, setMapCenter] = useState<[number, number]>(getInitialMapCenter());
|
||||||
const { state: confirmModal, toggle: toggleConfirmModal, set: setConfirmModal } = useToggle();
|
const { state: confirmModal, toggle: toggleConfirmModal, set: setConfirmModal } = useToggle();
|
||||||
const { state: detailsModal, toggle: _toggleDetailsModal, set: setDetailsModal } = useToggle();
|
const { state: detailsModal, toggle: _toggleDetailsModal, set: setDetailsModal } = useToggle();
|
||||||
|
|
||||||
@@ -33,8 +49,26 @@ function OrderTrackingPage() {
|
|||||||
selectedPosition,
|
selectedPosition,
|
||||||
selectedAddress,
|
selectedAddress,
|
||||||
initialPhone: user?.number || '',
|
initialPhone: user?.number || '',
|
||||||
|
editMode,
|
||||||
|
addressData: address || null,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (editMode && address && !initialPositionSet) {
|
||||||
|
const position: [number, number] = [address.latitude, address.longitude];
|
||||||
|
setMapCenter(position);
|
||||||
|
setSelectedPosition(position);
|
||||||
|
setInitialPositionSet(true);
|
||||||
|
|
||||||
|
fetchAddressFromCoordinates(address.latitude, address.longitude).then((addr) => {
|
||||||
|
if (addr) {
|
||||||
|
setSelectedAddress(addr);
|
||||||
|
setDetailsModal(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [editMode, address, initialPositionSet, setDetailsModal]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const initializeMarkers = async () => {
|
const initializeMarkers = async () => {
|
||||||
const L = (await import('leaflet')).default;
|
const L = (await import('leaflet')).default;
|
||||||
@@ -65,14 +99,16 @@ function OrderTrackingPage() {
|
|||||||
|
|
||||||
const handlePositionSelect = (position: [number, number]) => {
|
const handlePositionSelect = (position: [number, number]) => {
|
||||||
setSelectedPosition((prev) => {
|
setSelectedPosition((prev) => {
|
||||||
if (prev === position) {
|
if (prev && prev[0] === position[0] && prev[1] === position[1]) {
|
||||||
return prev;
|
return prev;
|
||||||
}
|
}
|
||||||
setSelectedAddress(null);
|
setSelectedAddress(null);
|
||||||
return position;
|
return position;
|
||||||
});
|
});
|
||||||
|
if (editMode && detailsModal) {
|
||||||
|
setDetailsModal(false);
|
||||||
|
}
|
||||||
setConfirmModal(position && true);
|
setConfirmModal(position && true);
|
||||||
setDetailsModal(false);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleZoomChange = (zoom: number) => {
|
const handleZoomChange = (zoom: number) => {
|
||||||
@@ -100,6 +136,20 @@ function OrderTrackingPage() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleChangePosition = () => {
|
||||||
|
setDetailsModal(false);
|
||||||
|
setSelectedPosition(null);
|
||||||
|
setSelectedAddress(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (editMode && isLoadingAddress) {
|
||||||
|
return (
|
||||||
|
<div className="fixed inset-0 bg-gray-50 flex items-center justify-center">
|
||||||
|
<p className="text-sm2 text-disabled-text">در حال بارگذاری...</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="fixed inset-0 bg-gray-50">
|
<div className="fixed inset-0 bg-gray-50">
|
||||||
<div className="absolute inset-0">
|
<div className="absolute inset-0">
|
||||||
@@ -126,7 +176,9 @@ function OrderTrackingPage() {
|
|||||||
selectedAddress={selectedAddress}
|
selectedAddress={selectedAddress}
|
||||||
formData={formData}
|
formData={formData}
|
||||||
isPending={isPending}
|
isPending={isPending}
|
||||||
|
editMode={editMode}
|
||||||
onClose={toggleDetailsModal}
|
onClose={toggleDetailsModal}
|
||||||
|
onChangePosition={handleChangePosition}
|
||||||
onFormDataChange={handleFormDataChange}
|
onFormDataChange={handleFormDataChange}
|
||||||
onSubmit={submitAddress}
|
onSubmit={submitAddress}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { api } from "@/lib/api/axiosInstance";
|
|||||||
import {
|
import {
|
||||||
CreateAddressType,
|
CreateAddressType,
|
||||||
AddressesResponse,
|
AddressesResponse,
|
||||||
|
AddressResponse,
|
||||||
UpdateAddressType,
|
UpdateAddressType,
|
||||||
} from "../types/Types";
|
} from "../types/Types";
|
||||||
|
|
||||||
@@ -17,7 +18,7 @@ export const createAddress = async (params: CreateAddressType) => {
|
|||||||
|
|
||||||
export const updateAddress = async (params: UpdateAddressType) => {
|
export const updateAddress = async (params: UpdateAddressType) => {
|
||||||
const { id, ...rest } = params;
|
const { id, ...rest } = params;
|
||||||
const { data } = await api.put(`/public/user/addresses/${id}`, rest);
|
const { data } = await api.patch(`/public/user/addresses/${id}`, rest);
|
||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -30,3 +31,10 @@ export const setDefaultAddress = async (id: string) => {
|
|||||||
const { data } = await api.patch(`/public/user/addresses/${id}/default`);
|
const { data } = await api.patch(`/public/user/addresses/${id}/default`);
|
||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getAddressById = async (id: string): Promise<AddressResponse> => {
|
||||||
|
const { data } = await api.get<AddressResponse>(
|
||||||
|
`/public/user/addresses/${id}`
|
||||||
|
);
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ export interface Address {
|
|||||||
|
|
||||||
export type AddressesResponse = BaseResponse<Address[]>;
|
export type AddressesResponse = BaseResponse<Address[]>;
|
||||||
|
|
||||||
|
export type AddressResponse = BaseResponse<Address>;
|
||||||
|
|
||||||
export type CreateAddressType = {
|
export type CreateAddressType = {
|
||||||
title: string;
|
title: string;
|
||||||
address: string;
|
address: string;
|
||||||
|
|||||||
Reference in New Issue
Block a user