198 lines
6.2 KiB
TypeScript
198 lines
6.2 KiB
TypeScript
import {useCallback, useState} from 'react'
|
|
import {useMutation, useQuery, useQueryClient} from '@tanstack/react-query'
|
|
|
|
import {addressAdd, addressDelete, addressEdit, addressList} from '@/api/address.ts'
|
|
import {validateAddressFormSubmission} from '@/features/addressBook/addressValidation'
|
|
import {notifyError} from '@/features/notifications'
|
|
import {ensureSessionForAction} from '@/lib/authGuard'
|
|
import {queryKeys} from '@/lib/queryKeys.ts'
|
|
import {useUserStore} from '@/store/user.ts'
|
|
import type {AddressListItem} from '@/types/address.type.ts'
|
|
import type {AddAddressForm, AddressOption} from '@/types'
|
|
|
|
type UseAddressBookOptions = {
|
|
autoLoad?: boolean
|
|
}
|
|
|
|
const emptyAddressForm: AddAddressForm = {
|
|
name: '',
|
|
phone: '',
|
|
detailedAddress: '',
|
|
isDefault: false,
|
|
}
|
|
|
|
export function getAddressText(item: AddressListItem) {
|
|
const regionText = item.region_text || item.region.map((part) => part.trim()).join(', ')
|
|
return [regionText, item.detail_address].filter(Boolean).join(', ')
|
|
}
|
|
|
|
export function mapAddressToOption(item: AddressListItem): AddressOption {
|
|
return {
|
|
id: String(item.id),
|
|
name: item.receiver_name,
|
|
phone: item.phone,
|
|
address: getAddressText(item),
|
|
isDefault: item.default_setting === 1,
|
|
}
|
|
}
|
|
|
|
export function mapAddressToForm(item: AddressListItem): AddAddressForm {
|
|
return {
|
|
name: item.receiver_name,
|
|
phone: item.phone,
|
|
detailedAddress: item.detail_address,
|
|
isDefault: item.default_setting === 1,
|
|
}
|
|
}
|
|
|
|
export function useAddressBook(options?: UseAddressBookOptions) {
|
|
const queryClient = useQueryClient()
|
|
const sessionId = useUserStore((state) => state.authInfo?.session_id ?? '')
|
|
const [addressForm, setAddressForm] = useState<AddAddressForm>(emptyAddressForm)
|
|
const [loadAddressesLoading, setLoadAddressesLoading] = useState(false)
|
|
|
|
const addressListQuery = useQuery({
|
|
queryKey: queryKeys.addressList(sessionId),
|
|
enabled: Boolean(sessionId) && Boolean(options?.autoLoad),
|
|
queryFn: async () => {
|
|
const response = await addressList({session_id: sessionId!})
|
|
return response.data.list
|
|
},
|
|
})
|
|
|
|
const saveAddressMutation = useMutation({
|
|
mutationFn: async (editingAddress?: AddressListItem | null) => {
|
|
const payload = {
|
|
session_id: sessionId!,
|
|
receiver_name: addressForm.name.trim(),
|
|
phone: addressForm.phone.trim(),
|
|
detail_address: addressForm.detailedAddress.trim(),
|
|
default_setting: addressForm.isDefault ? '1' : '0',
|
|
} as const
|
|
|
|
if (editingAddress) {
|
|
return await addressEdit({
|
|
...payload,
|
|
id: String(editingAddress.id),
|
|
})
|
|
}
|
|
|
|
return await addressAdd(payload)
|
|
},
|
|
})
|
|
|
|
const deleteAddressMutation = useMutation({
|
|
mutationFn: async (addressId: string) => {
|
|
return await addressDelete({
|
|
id: addressId,
|
|
session_id: sessionId!,
|
|
})
|
|
},
|
|
})
|
|
|
|
const addresses = addressListQuery.data ?? []
|
|
const addressOptions = addresses.map(mapAddressToOption)
|
|
|
|
const isAddressFormValid = validateAddressFormSubmission(addressForm).valid
|
|
|
|
const loadAddresses = useCallback(async () => {
|
|
if (!sessionId) {
|
|
queryClient.removeQueries({queryKey: queryKeys.addressList(sessionId)})
|
|
return []
|
|
}
|
|
|
|
setLoadAddressesLoading(true)
|
|
|
|
try {
|
|
const result = await queryClient.fetchQuery({
|
|
queryKey: queryKeys.addressList(sessionId),
|
|
queryFn: async () => {
|
|
const response = await addressList({session_id: sessionId})
|
|
return response.data.list
|
|
},
|
|
})
|
|
|
|
return result
|
|
} catch {
|
|
return []
|
|
} finally {
|
|
setLoadAddressesLoading(false)
|
|
}
|
|
}, [queryClient, sessionId])
|
|
|
|
const resetAddressForm = () => {
|
|
setAddressForm(emptyAddressForm)
|
|
saveAddressMutation.reset()
|
|
}
|
|
|
|
const fillAddressForm = (address?: AddressListItem | null) => {
|
|
setAddressForm(address ? mapAddressToForm(address) : emptyAddressForm)
|
|
saveAddressMutation.reset()
|
|
}
|
|
|
|
const changeAddressForm = (field: keyof AddAddressForm, value: AddAddressForm[keyof AddAddressForm]) => {
|
|
setAddressForm((previous) => ({
|
|
...previous,
|
|
[field]: value,
|
|
}))
|
|
}
|
|
|
|
const saveAddress = async (editingAddress?: AddressListItem | null) => {
|
|
if (!ensureSessionForAction() || !sessionId) {
|
|
return null
|
|
}
|
|
|
|
const validation = validateAddressFormSubmission(addressForm)
|
|
if (!validation.valid) {
|
|
notifyError(validation.message)
|
|
return null
|
|
}
|
|
|
|
try {
|
|
const response = await saveAddressMutation.mutateAsync(editingAddress)
|
|
await queryClient.invalidateQueries({
|
|
queryKey: queryKeys.addressList(sessionId),
|
|
})
|
|
return {
|
|
addresses: await loadAddresses(),
|
|
response,
|
|
}
|
|
} catch {
|
|
return null
|
|
}
|
|
}
|
|
|
|
const removeAddress = async (addressId: string) => {
|
|
if (!ensureSessionForAction() || !sessionId) {
|
|
return null
|
|
}
|
|
|
|
try {
|
|
const response = await deleteAddressMutation.mutateAsync(addressId)
|
|
await queryClient.invalidateQueries({
|
|
queryKey: queryKeys.addressList(sessionId),
|
|
})
|
|
return response
|
|
} catch {
|
|
return null
|
|
}
|
|
}
|
|
|
|
return {
|
|
sessionId,
|
|
addresses,
|
|
addressOptions,
|
|
loading: Boolean(sessionId) && (addressListQuery.isPending || loadAddressesLoading),
|
|
addressForm,
|
|
isAddressFormValid,
|
|
submitLoading: saveAddressMutation.isPending,
|
|
deleteLoading: deleteAddressMutation.isPending,
|
|
loadAddresses,
|
|
resetAddressForm,
|
|
fillAddressForm,
|
|
changeAddressForm,
|
|
saveAddress,
|
|
removeAddress,
|
|
}
|
|
}
|