profile + update address with location

This commit is contained in:
hamid zarghami
2025-09-17 10:53:52 +03:30
parent 87bc7866fa
commit bc22bbd6d9
8 changed files with 605 additions and 15 deletions
+44
View File
@@ -17,11 +17,13 @@
"@radix-ui/react-slot": "^1.2.3",
"@tanstack/react-query": "^5.84.1",
"@tanstack/react-query-devtools": "^5.85.5",
"@types/leaflet": "^1.9.20",
"axios": "^1.11.0",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"embla-carousel-react": "^8.6.0",
"iconsax-react": "^0.0.8",
"leaflet": "^1.9.4",
"lucide-react": "^0.539.0",
"moment-jalaali": "^0.10.4",
"next": "^15.5.3",
@@ -30,6 +32,7 @@
"react-dom": "19.1.0",
"react-hook-form": "^7.62.0",
"react-infinite-scroll-component": "^6.1.0",
"react-leaflet": "^5.0.0",
"react-otp-input": "^3.1.1",
"react-spinners": "^0.17.0",
"swiper": "^11.2.10",
@@ -1765,6 +1768,16 @@
"integrity": "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==",
"license": "MIT"
},
"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==",
"peerDependencies": {
"leaflet": "^1.9.0",
"react": "^19.0.0",
"react-dom": "^19.0.0"
}
},
"node_modules/@rtsao/scc": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz",
@@ -2141,6 +2154,11 @@
"dev": true,
"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=="
},
"node_modules/@types/json-schema": {
"version": "7.0.15",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
@@ -2155,6 +2173,14 @@
"dev": true,
"license": "MIT"
},
"node_modules/@types/leaflet": {
"version": "1.9.20",
"resolved": "https://registry.npmjs.org/@types/leaflet/-/leaflet-1.9.20.tgz",
"integrity": "sha512-rooalPMlk61LCaLOvBF2VIf9M47HgMQqi5xQ9QRi7c8PkdIe0WrIi5IxXUXQjAdL0c+vcQ01mYWbthzmp9GHWw==",
"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",
@@ -5231,6 +5257,11 @@
"node": ">=0.10"
}
},
"node_modules/leaflet": {
"version": "1.9.4",
"resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.9.4.tgz",
"integrity": "sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA=="
},
"node_modules/levn": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
@@ -6259,6 +6290,19 @@
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
"license": "MIT"
},
"node_modules/react-leaflet": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/react-leaflet/-/react-leaflet-5.0.0.tgz",
"integrity": "sha512-CWbTpr5vcHw5bt9i4zSlPEVQdTVcML390TjeDG0cK59z1ylexpqC6M1PJFjV8jD7CF+ACBFsLIDs6DRMoLEofw==",
"dependencies": {
"@react-leaflet/core": "^3.0.0"
},
"peerDependencies": {
"leaflet": "^1.9.0",
"react": "^19.0.0",
"react-dom": "^19.0.0"
}
},
"node_modules/react-otp-input": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/react-otp-input/-/react-otp-input-3.1.1.tgz",
+3
View File
@@ -18,11 +18,13 @@
"@radix-ui/react-slot": "^1.2.3",
"@tanstack/react-query": "^5.84.1",
"@tanstack/react-query-devtools": "^5.85.5",
"@types/leaflet": "^1.9.20",
"axios": "^1.11.0",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"embla-carousel-react": "^8.6.0",
"iconsax-react": "^0.0.8",
"leaflet": "^1.9.4",
"lucide-react": "^0.539.0",
"moment-jalaali": "^0.10.4",
"next": "^15.5.3",
@@ -31,6 +33,7 @@
"react-dom": "19.1.0",
"react-hook-form": "^7.62.0",
"react-infinite-scroll-component": "^6.1.0",
"react-leaflet": "^5.0.0",
"react-otp-input": "^3.1.1",
"react-spinners": "^0.17.0",
"swiper": "^11.2.10",
+1
View File
@@ -1,5 +1,6 @@
@import "tailwindcss";
@import "tw-animate-css";
@import "leaflet/dist/leaflet.css";
/* بهبود responsive design برای موبایل */
html {
+458
View File
@@ -0,0 +1,458 @@
'use client'
import React, { useState, useEffect, useRef, useCallback } from 'react'
import { useForm } from 'react-hook-form'
import { Button } from '@/components/ui/button'
import Input from '@/components/Input'
import { Location, TickCircle, CloseCircle, ArrowRight2, ArrowLeft2 } from 'iconsax-react'
import DefaulModal from '@/components/DefaulModal'
import { useGetAddressByLocation, useUpdateAddress } from '../hooks/useProfileData'
import { UpdateAddressType } from '../types/ProfileTypes'
import type { Map as LeafletMap, LeafletMouseEvent, Marker } from 'leaflet'
interface AddressModalProps {
isOpen: boolean
onClose: () => void
currentAddress?: string
onAddressChange: (addressData: UpdateAddressType) => void
}
const AddressModal: React.FC<AddressModalProps> = ({
isOpen,
onClose,
onAddressChange
}) => {
const [currentStep, setCurrentStep] = useState(1)
const [selectedLocation, setSelectedLocation] = useState<{ lat: number; lng: number } | null>(null)
const [isMapLoaded, setIsMapLoaded] = useState(false)
const mapRef = useRef<HTMLDivElement>(null)
const leafletMapRef = useRef<LeafletMap | null>(null)
const currentMarkerRef = useRef<Marker | null>(null)
// React Hook Form
const { register, handleSubmit, setValue, formState: { errors, isSubmitting } } = useForm<UpdateAddressType>({
defaultValues: {
lat: '',
lon: '',
provinceId: 0,
cityId: 0,
postalCode: '',
address: '',
plaque: ''
}
})
// Get address by location
const { data: addressData, isLoading: isLoadingAddress } = useGetAddressByLocation(
selectedLocation?.lat,
selectedLocation?.lng
)
// Update address mutation
const updateAddressMutation = useUpdateAddress()
const loadMap = useCallback(async () => {
try {
const L = await import('leaflet')
// Fix default icon issue
delete (L.Icon.Default.prototype as unknown as { _getIconUrl: unknown })._getIconUrl
L.Icon.Default.mergeOptions({
iconRetinaUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.7.1/images/marker-icon-2x.png',
iconUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.7.1/images/marker-icon.png',
shadowUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.7.1/images/marker-shadow.png',
})
if (mapRef.current && !leafletMapRef.current) {
// Get initial coordinates (user's location or default to Tehran)
const initialLat = selectedLocation?.lat || 35.6892
const initialLng = selectedLocation?.lng || 51.3890
// Create map
leafletMapRef.current = L.map(mapRef.current).setView([initialLat, initialLng], 13)
// Add tile layer
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
}).addTo(leafletMapRef.current)
// Add click handler
leafletMapRef.current.on('click', (e: LeafletMouseEvent) => {
const { lat, lng } = e.latlng
handleMapClick(lat, lng)
})
setIsMapLoaded(true)
}
} catch (error) {
console.error('Error loading map:', error)
}
}, [selectedLocation?.lat, selectedLocation?.lng])
useEffect(() => {
if (isOpen && typeof window !== 'undefined' && !isMapLoaded) {
loadMap()
}
}, [isOpen, isMapLoaded, loadMap])
// Get user's current location on mount
useEffect(() => {
if (isOpen && navigator.geolocation) {
navigator.geolocation.getCurrentPosition(
(position) => {
const { latitude, longitude } = position.coords
setSelectedLocation({ lat: latitude, lng: longitude })
},
(error) => {
console.error('Error getting current location:', error)
// Fallback to Tehran coordinates
setSelectedLocation({ lat: 35.6892, lng: 51.3890 })
}
)
}
}, [isOpen])
// Update map view when selectedLocation changes
useEffect(() => {
if (leafletMapRef.current && selectedLocation) {
leafletMapRef.current.setView([selectedLocation.lat, selectedLocation.lng], 13)
}
}, [selectedLocation])
const handleMapClick = async (lat: number, lng: number) => {
// Add marker if map exists
if (leafletMapRef.current) {
const L = await import('leaflet')
// Remove existing marker
if (currentMarkerRef.current) {
leafletMapRef.current.removeLayer(currentMarkerRef.current)
}
// Add new marker
currentMarkerRef.current = L.marker([lat, lng]).addTo(leafletMapRef.current)
currentMarkerRef.current.bindPopup(`
<div style="text-align: center; padding: 8px;">
<div style="display: flex; align-items: center; justify-content: center; margin-bottom: 8px;">
<span style="color: #16a34a; font-weight: 500;">آدرس شما اینجاست!</span>
</div>
</div>
`).openPopup()
}
// Set selected location
setSelectedLocation({ lat, lng })
}
// Update form data when address data is received
useEffect(() => {
if (addressData?.results) {
setValue('address', addressData.results.address)
setValue('provinceId', addressData.results.provinceId)
setValue('cityId', addressData.results.cityId)
}
}, [addressData, setValue])
const handleNextStep = () => {
if (selectedLocation) {
setCurrentStep(2)
}
}
const handlePrevStep = () => {
setCurrentStep(1)
}
const onSubmit = async (data: UpdateAddressType) => {
if (selectedLocation) {
try {
// Prepare data for API
const updateData: UpdateAddressType = {
...data,
lat: selectedLocation.lat.toString(),
lon: selectedLocation.lng.toString()
}
// Call update address API
await updateAddressMutation.mutateAsync(updateData)
// Call parent callback with form data
onAddressChange(updateData)
handleClose()
} catch (error) {
console.error('Error updating address:', error)
// You can add toast notification here
}
}
}
const handleClose = () => {
// Reset state when closing
setCurrentStep(1)
setSelectedLocation(null)
// Reset form
setValue('lat', '')
setValue('lon', '')
setValue('provinceId', 0)
setValue('cityId', 0)
setValue('postalCode', '')
setValue('address', '')
setValue('plaque', '')
onClose()
}
const handleCurrentLocation = () => {
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(
(position) => {
const { latitude, longitude } = position.coords
handleMapClick(latitude, longitude)
// Pan map to current location
if (leafletMapRef.current) {
leafletMapRef.current.setView([latitude, longitude], 16)
}
},
(error) => {
console.error('Error getting location:', error)
alert('خطا در دریافت موقعیت فعلی')
}
)
} else {
alert('مرورگر شما از موقعیت‌یابی پشتیبانی نمی‌کند')
}
}
const handleClearSelection = () => {
setSelectedLocation(null)
// Remove marker from map
if (leafletMapRef.current && currentMarkerRef.current) {
leafletMapRef.current.removeLayer(currentMarkerRef.current)
currentMarkerRef.current = null
}
}
// Cleanup on unmount
useEffect(() => {
return () => {
if (leafletMapRef.current) {
leafletMapRef.current.remove()
leafletMapRef.current = null
}
}
}, [])
const renderStep1 = () => (
<div className="flex flex-col h-full">
{/* Subtitle */}
<p className="text-sm text-gray-600 mb-4 text-center">
موقعیت خود را روی نقشه انتخاب کنید
</p>
{/* Selected Location Display */}
{selectedLocation && (
<div className="mt-4 p-4 bg-gradient-to-r from-green-50 to-blue-50 rounded-lg border border-green-200">
<div className="flex items-start gap-3">
<div className="flex-shrink-0 mt-0.5">
<TickCircle size={20} className="text-green-600" />
</div>
<div className="flex-1">
<div className="flex items-center justify-between mb-2">
<span className="text-sm font-medium text-green-800">موقعیت انتخاب شده:</span>
<button
onClick={handleClearSelection}
className="text-red-400 hover:text-red-600 transition-colors"
>
<CloseCircle size={16} />
</button>
</div>
<p className="text-sm text-gray-700 leading-relaxed">
{isLoadingAddress ? 'در حال دریافت آدرس...' :
addressData?.results?.address ||
`عرض جغرافیایی: ${selectedLocation.lat.toFixed(6)}, طول جغرافیایی: ${selectedLocation.lng.toFixed(6)}`}
</p>
</div>
</div>
</div>
)}
{/* Action Buttons */}
<div className="flex flex-col sm:flex-row gap-3 justify-end mt-6">
<Button
onClick={handleClose}
variant="outline"
className="w-full sm:w-auto order-2 sm:order-1"
>
انصراف
</Button>
<Button
onClick={handleNextStep}
className="w-full sm:w-auto bg-blue-600 hover:bg-blue-700 order-1 sm:order-2 disabled:opacity-50 disabled:cursor-not-allowed"
disabled={!selectedLocation}
>
مرحله بعد
<ArrowLeft2 size={20} color='white' className="mr-1" />
</Button>
</div>
</div>
)
const renderStep2 = () => (
<form onSubmit={handleSubmit(onSubmit)} className="flex flex-col h-full">
{/* Subtitle */}
<p className="text-sm text-gray-600 mb-4 text-center">
آدرس جدید خود را وارد کنید
</p>
{/* Address Form */}
<div className="flex-1 space-y-4">
{/* Main Address Field */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
آدرس کامل
</label>
<textarea
{...register('address', { required: 'آدرس کامل الزامی است' })}
className="w-full p-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent resize-none"
rows={4}
placeholder="آدرس کامل خود را وارد کنید"
/>
{errors.address && (
<p className="text-red-500 text-sm mt-1">{errors.address.message}</p>
)}
</div>
{/* Province and City Row - Readonly */}
<div className="grid grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
شهر
</label>
<Input
value={addressData?.results?.city || ''}
readOnly
className="w-full bg-gray-50 cursor-not-allowed"
placeholder="شهر"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
استان
</label>
<Input
value={addressData?.results?.province || ''}
readOnly
className="w-full bg-gray-50 cursor-not-allowed"
placeholder="استان"
/>
</div>
</div>
{/* Postal Code and Plaque Row */}
<div className="grid grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
کد پستی
</label>
<Input
{...register('postalCode', { required: 'کد پستی الزامی است' })}
placeholder="کد پستی"
className="w-full"
/>
{errors.postalCode && (
<p className="text-red-500 text-sm mt-1">{errors.postalCode.message}</p>
)}
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
پلاک
</label>
<Input
{...register('plaque', { required: 'پلاک الزامی است' })}
placeholder="پلاک"
className="w-full"
/>
{errors.plaque && (
<p className="text-red-500 text-sm mt-1">{errors.plaque.message}</p>
)}
</div>
</div>
</div>
{/* Action Buttons */}
<div className="flex flex-col sm:flex-row gap-3 justify-end mt-6">
<Button
type="button"
onClick={handlePrevStep}
variant="outline"
className="w-full sm:w-auto order-2 sm:order-1"
>
<ArrowRight2 color='black' size={16} className="ml-1" />
مرحله قبل
</Button>
<Button
type="button"
onClick={handleClose}
variant="outline"
className="w-full sm:w-auto order-3 sm:order-2"
>
انصراف
</Button>
<Button
type="submit"
className="w-full sm:w-auto bg-blue-600 hover:bg-blue-700 order-1 sm:order-3 disabled:opacity-50 disabled:cursor-not-allowed"
disabled={isSubmitting || updateAddressMutation.isPending}
>
<TickCircle color='white' size={16} className="ml-1" />
{isSubmitting || updateAddressMutation.isPending ? 'در حال ذخیره...' : 'تایید'}
</Button>
</div>
</form>
)
return (
<DefaulModal
open={isOpen}
close={handleClose}
isHeader={true}
title_header="تغییر آدرس"
width={500}
>
{/* Map Container - Only show in step 1 */}
{currentStep === 1 && (
<div className="flex-1 relative rounded-lg overflow-hidden border border-gray-200 bg-gray-100 min-h-[300px]">
<div
ref={mapRef}
className="h-full w-full rounded-lg"
style={{ minHeight: '300px' }}
/>
{!isMapLoaded && (
<div className="absolute inset-0 flex items-center justify-center bg-gray-100">
<div className="text-center text-gray-500">
<Location size={48} color="#015699" className="mx-auto mb-4" />
<p className="text-lg">در حال بارگذاری نقشه...</p>
</div>
</div>
)}
{/* Current Location Button Overlay */}
<div className="absolute bottom-4 left-4 z-[1000]">
<Button
onClick={handleCurrentLocation}
variant="outline"
size="sm"
>
<Location color={'black'} size={20} className="ml-0.5" />
موقعیت فعلی
</Button>
</div>
</div>
)}
{currentStep === 1 ? renderStep1() : renderStep2()}
</DefaulModal>
)
}
export default AddressModal
+16 -4
View File
@@ -1,7 +1,7 @@
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
import * as api from "../service/Service";
import { useSharedStore } from "@/share/store/sharedStore";
import { UpdateProfileRequest } from "../types/ProfileTypes";
import { UpdateAddressType, UpdateProfileRequest } from "../types/ProfileTypes";
export const useGetProfile = () => {
const { isLogin } = useSharedStore();
@@ -21,8 +21,20 @@ export const useUpdateProfile = () => {
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["profile"] });
},
onError: (error: any) => {
console.error("خطا در به‌روزرسانی پروفایل:", error);
},
});
};
export const useGetAddressByLocation = (lat?: number, lon?: number) => {
return useQuery({
queryKey: ["address-by-location", lat, lon],
queryFn: () => api.getAddressByLocation(lat!, lon!),
enabled: lat !== undefined && lon !== undefined,
});
};
export const useUpdateAddress = () => {
return useMutation({
mutationFn: (addressData: UpdateAddressType) =>
api.updateAddress(addressData),
});
};
+37 -11
View File
@@ -7,9 +7,11 @@ import { Button } from '@/components/ui/button'
import Input from '@/components/Input'
import { Edit2 } from 'lucide-react'
import Layout from '@/hoc/Layout'
import { Heart, ShoppingCart } from 'iconsax-react'
import { Heart, ShoppingCart, Edit } from 'iconsax-react'
import Menu from './components/Menu'
import AddressModal from './components/AddressModal'
import { useGetProfile, useUpdateProfile } from './hooks/useProfileData'
import { UpdateAddressType } from './types/ProfileTypes'
// Schema validation
const profileSchema = yup.object({
@@ -30,6 +32,7 @@ const ProfilePage = () => {
const { data: profile, isLoading } = useGetProfile()
const updateProfileMutation = useUpdateProfile()
const [isEditing, setIsEditing] = useState(false)
const [isAddressModalOpen, setIsAddressModalOpen] = useState(false)
const {
register,
@@ -76,6 +79,11 @@ const ProfilePage = () => {
setIsEditing(false)
}
const handleAddressChange = async (addressData: UpdateAddressType) => {
setValue('address', addressData.address)
await updateProfileMutation.mutateAsync({ address: addressData.address })
}
if (isLoading) {
return (
<div className="p-3 sm:p-4 md:p-6">
@@ -103,7 +111,7 @@ const ProfilePage = () => {
variant="outline"
className="flex items-center gap-2 text-sm sm:text-base"
>
<Edit2 size={16} />
<Edit2 color='black' size={16} />
<span className="hidden sm:inline">ویرایش</span>
</Button>
</div>
@@ -147,15 +155,6 @@ const ProfilePage = () => {
</div>
</div>
<div className="mt-3 sm:mt-4">
<Input
label="آدرس"
{...register('address')}
readOnly={!isEditing}
error_text={errors.address?.message}
/>
</div>
{isEditing && (
<div className="mt-4 sm:mt-6 flex flex-col sm:flex-row gap-3">
<Button
@@ -177,6 +176,25 @@ const ProfilePage = () => {
)}
</form>
</div>
{/* Address Section */}
<div className="bg-[#FAFAFA] rounded-lg p-3 sm:p-4 md:p-6 mt-4 sm:mt-6">
<div className="flex flex-col sm:flex-row items-start sm:items-center justify-between mb-4 sm:mb-6 gap-3 sm:gap-0">
<h2 className="text-base sm:text-lg font-bold text-gray-900">آدرس</h2>
<Button
onClick={() => setIsAddressModalOpen(true)}
variant="outline"
className="flex items-center gap-2 text-sm sm:text-base"
>
<Edit2 color='black' size={16} />
<span className="hidden sm:inline">ویرایش</span>
</Button>
</div>
<div className="bg-gray-50 rounded-lg p-3 text-sm text-gray-600 min-h-[60px] flex items-center">
{profile?.results?.info?.userAddress?.address || 'آدرسی وارد نشده است'}
</div>
</div>
</div>
<div className='flex-1 flex lg:flex-row flex-col gap-6'>
@@ -205,6 +223,14 @@ const ProfilePage = () => {
</div>
</div>
</div>
{/* Address Modal */}
<AddressModal
isOpen={isAddressModalOpen}
onClose={() => setIsAddressModalOpen(false)}
currentAddress={profile?.results?.info?.userAddress?.address || ''}
onAddressChange={handleAddressChange}
/>
</div>
)
}
+14
View File
@@ -3,6 +3,8 @@ import { UserMeResponseType } from "@/share/types/SharedTypes";
import {
UpdateProfileRequest,
UpdateProfileResponse,
AddressLocationResponse,
UpdateAddressType,
} from "../types/ProfileTypes";
export const getProfile = async () => {
@@ -17,3 +19,15 @@ export const updateProfile = async (profileData: UpdateProfileRequest) => {
);
return data;
};
export const getAddressByLocation = async (lat: number, lon: number) => {
const { data } = await axios.get<AddressLocationResponse>(
`/address/location/reverse?lat=${lat}&lon=${lon}`
);
return data;
};
export const updateAddress = async (addressData: UpdateAddressType) => {
const { data } = await axios.post("/address/user/save", addressData);
return data;
};
+32
View File
@@ -14,6 +14,28 @@ export interface UpdateProfileRequest {
address?: string;
}
export interface AddressFormData {
address: string;
mobileNumber: string;
province: string;
city: string;
postalCode: string;
plaque: string;
}
export interface AddressLocationResponse {
status: number;
success: boolean;
results: {
province: string;
provinceId: number;
city: string;
cityId: number;
address: string;
region: string;
};
}
export interface UpdateProfileResponse {
status: number;
success: boolean;
@@ -58,3 +80,13 @@ export interface UpdateProfileResponse {
};
};
}
export type UpdateAddressType = {
lat: string;
lon: string;
provinceId: number;
cityId: number;
postalCode: string;
address: string;
plaque: string;
};