edit address

This commit is contained in:
hamid zarghami
2025-12-01 16:14:59 +03:30
parent 89e9a8c057
commit 4fca678230
7 changed files with 154 additions and 24 deletions
+1 -1
View File
File diff suppressed because one or more lines are too long
@@ -5,6 +5,7 @@ import {
updateAddress,
deleteAddress,
setDefaultAddress,
getAddressById,
} from "../service/AddressService";
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 { AddressForm } from './AddressForm';
import { NominatimReverseGeocodingResponse } from '../../types/Types';
import { Location } from 'iconsax-react';
interface AddressDetailsModalProps {
visible: boolean;
@@ -15,7 +16,9 @@ interface AddressDetailsModalProps {
isDefault: boolean;
};
isPending: boolean;
editMode?: boolean;
onClose: () => void;
onChangePosition?: () => void;
onFormDataChange: (data: Partial<AddressDetailsModalProps['formData']>) => void;
onSubmit: (e: React.FormEvent<HTMLFormElement>) => void;
}
@@ -25,7 +28,9 @@ export const AddressDetailsModal = ({
selectedAddress,
formData,
isPending,
editMode = false,
onClose,
onChangePosition,
onFormDataChange,
onSubmit,
}: AddressDetailsModalProps) => {
@@ -43,6 +48,22 @@ export const AddressDetailsModal = ({
<form onSubmit={onSubmit}>
<div className='px-4'>
<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
formData={formData}
selectedAddress={selectedAddress}
@@ -53,7 +74,7 @@ export const AddressDetailsModal = ({
type='submit'
disabled={isPending}
>
{isPending ? 'در حال ثبت...' : 'تایید'}
{isPending ? (editMode ? 'در حال به‌روزرسانی...' : 'در حال ثبت...') : (editMode ? 'به‌روزرسانی' : 'تایید')}
</Button>
<Button
type='button'
@@ -1,22 +1,28 @@
import { useState } from 'react';
import { useState, useEffect } from 'react';
import { useRouter } from 'next/navigation';
import { useCreateAddress } from '../../hooks/useAddressData';
import { useCreateAddress, useUpdateAddress } from '../../hooks/useAddressData';
import { toast } from '@/components/Toast';
import { CreateAddressType, NominatimReverseGeocodingResponse } from '../../types/Types';
import { CreateAddressType, UpdateAddressType, NominatimReverseGeocodingResponse, Address } from '../../types/Types';
interface UseAddressFormProps {
selectedPosition: [number, number] | null;
selectedAddress: NominatimReverseGeocodingResponse | null;
initialPhone?: string;
editMode?: boolean;
addressData?: Address | null;
}
export const useAddressForm = ({
selectedPosition,
selectedAddress,
initialPhone = '',
editMode = false,
addressData = null,
}: UseAddressFormProps) => {
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({
title: '',
@@ -26,6 +32,18 @@ export const useAddressForm = ({
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>) => {
setFormData((prev) => ({ ...prev, ...data }));
};
@@ -53,7 +71,7 @@ export const useAddressForm = ({
return;
}
const addressData: CreateAddressType = {
const baseAddressData = {
title: formData.title,
address: formData.addressDetails,
city: selectedAddress.address.city || selectedAddress.address.district || '',
@@ -65,16 +83,36 @@ export const useAddressForm = ({
isDefault: formData.isDefault,
};
createAddress(addressData, {
onSuccess: () => {
toast('آدرس با موفقیت ثبت شد', 'success');
router.back();
},
onError: (error) => {
const errorMessage = error instanceof Error ? error.message : 'خطا در ثبت آدرس';
toast(errorMessage, 'error');
},
});
if (editMode && addressData) {
const updateData: UpdateAddressType = {
...baseAddressData,
id: addressData.id,
};
updateAddress(updateData, {
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 {
@@ -10,6 +10,7 @@ import { ConfirmPositionModal } from './components/ConfirmPositionModal';
import { AddressDetailsModal } from './components/AddressDetailsModal';
import { useAddressForm } from './hooks/useAddressForm';
import { fetchAddressFromCoordinates } from './utils/fetchAddress';
import { useGetAddressById } from '../hooks/useAddressData';
const CustomMap = dynamic(
() => import('@/components/map/CustomMap'),
@@ -17,15 +18,30 @@ const CustomMap = dynamic(
);
function OrderTrackingPage() {
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 [selectedPosition, setSelectedPosition] = useState<[number, number] | null>(null);
const [selectedAddress, setSelectedAddress] = useState<NominatimReverseGeocodingResponse | null>(null);
const [markers, setMarkers] = useState<MarkerData[]>([]);
const [mapCenter] = useState<[number, number]>([
Number(params?.get('lat')) || 35.6892,
Number(params?.get('lon')) || 51.3890
]);
const [initialPositionSet, setInitialPositionSet] = useState(false);
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: detailsModal, toggle: _toggleDetailsModal, set: setDetailsModal } = useToggle();
@@ -33,8 +49,26 @@ function OrderTrackingPage() {
selectedPosition,
selectedAddress,
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(() => {
const initializeMarkers = async () => {
const L = (await import('leaflet')).default;
@@ -65,14 +99,16 @@ function OrderTrackingPage() {
const handlePositionSelect = (position: [number, number]) => {
setSelectedPosition((prev) => {
if (prev === position) {
if (prev && prev[0] === position[0] && prev[1] === position[1]) {
return prev;
}
setSelectedAddress(null);
return position;
});
if (editMode && detailsModal) {
setDetailsModal(false);
}
setConfirmModal(position && true);
setDetailsModal(false);
};
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 (
<div className="fixed inset-0 bg-gray-50">
<div className="absolute inset-0">
@@ -126,7 +176,9 @@ function OrderTrackingPage() {
selectedAddress={selectedAddress}
formData={formData}
isPending={isPending}
editMode={editMode}
onClose={toggleDetailsModal}
onChangePosition={handleChangePosition}
onFormDataChange={handleFormDataChange}
onSubmit={submitAddress}
/>
@@ -2,6 +2,7 @@ import { api } from "@/lib/api/axiosInstance";
import {
CreateAddressType,
AddressesResponse,
AddressResponse,
UpdateAddressType,
} from "../types/Types";
@@ -17,7 +18,7 @@ export const createAddress = async (params: CreateAddressType) => {
export const updateAddress = async (params: UpdateAddressType) => {
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;
};
@@ -30,3 +31,10 @@ export const setDefaultAddress = async (id: string) => {
const { data } = await api.patch(`/public/user/addresses/${id}/default`);
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 AddressResponse = BaseResponse<Address>;
export type CreateAddressType = {
title: string;
address: string;