list of addresses
This commit is contained in:
@@ -1,5 +1,11 @@
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import { createAddress, getAddresses } from "../service/AddressService";
|
||||
import {
|
||||
createAddress,
|
||||
getAddresses,
|
||||
updateAddress,
|
||||
deleteAddress,
|
||||
setDefaultAddress,
|
||||
} from "../service/AddressService";
|
||||
|
||||
export const useGetAddresses = () => {
|
||||
return useQuery({
|
||||
@@ -17,3 +23,33 @@ export const useCreateAddress = () => {
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const useUpdateAddress = () => {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: updateAddress,
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ["addresses"] });
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const useDeleteAddress = () => {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: deleteAddress,
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ["addresses"] });
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const useSetDefaultAddress = () => {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: setDefaultAddress,
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ["addresses"] });
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
@@ -2,25 +2,74 @@
|
||||
|
||||
import Button from '@/components/button/PrimaryButton';
|
||||
import { ef } from '@/lib/helpers/utfNumbers';
|
||||
// import { useProfile } from '@/hooks/auth/useProfile';
|
||||
// import { useAuthStore } from '@/zustand/authStore';
|
||||
import { ArrowLeft, Edit2, TickCircle } from 'iconsax-react';
|
||||
import { ArrowLeft, Edit2, TickCircle, Trash } from 'iconsax-react';
|
||||
import Link from 'next/link';
|
||||
import { useRouter, useSearchParams } from 'next/navigation';
|
||||
import React from 'react'
|
||||
import React, { useState } from 'react';
|
||||
import { useGetAddresses, useSetDefaultAddress, useDeleteAddress } from './hooks/useAddressData';
|
||||
import { Address } from './types/Types';
|
||||
import Prompt from '@/components/utils/Prompt';
|
||||
import { toast } from '@/components/Toast';
|
||||
import useToggle from '@/hooks/helpers/useToggle';
|
||||
|
||||
type Props = object
|
||||
|
||||
function UserAddressesPage({ }: Props) {
|
||||
const { data: addressesResponse, isLoading } = useGetAddresses();
|
||||
const router = useRouter();
|
||||
const searchParams = useSearchParams();
|
||||
const redirect = searchParams.get('redirect');
|
||||
const { mutate: setDefault, isPending: isSettingDefault } = useSetDefaultAddress();
|
||||
const { mutate: deleteAddressMutation, isPending: isDeleting } = useDeleteAddress();
|
||||
const { state: deleteModalVisible, set: setDeleteModal } = useToggle();
|
||||
const [selectedAddressId, setSelectedAddressId] = useState<string | null>(null);
|
||||
|
||||
const onSelected = () => {
|
||||
if(redirect) {
|
||||
router.replace(redirect);
|
||||
}
|
||||
}
|
||||
const addresses = addressesResponse?.data || [];
|
||||
|
||||
const handleSetDefault = (addressId: string) => {
|
||||
setDefault(addressId, {
|
||||
onSuccess: () => {
|
||||
toast('آدرس به عنوان پیشفرض تنظیم شد', 'success');
|
||||
if (redirect) {
|
||||
router.replace(redirect);
|
||||
}
|
||||
},
|
||||
onError: (error) => {
|
||||
const errorMessage = error instanceof Error ? error.message : 'خطا در تنظیم آدرس پیشفرض';
|
||||
toast(errorMessage, 'error');
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const handleDeleteClick = (addressId: string) => {
|
||||
setSelectedAddressId(addressId);
|
||||
setDeleteModal(true);
|
||||
};
|
||||
|
||||
const handleDeleteConfirm = () => {
|
||||
if (!selectedAddressId) return;
|
||||
|
||||
deleteAddressMutation(selectedAddressId, {
|
||||
onSuccess: () => {
|
||||
toast('آدرس با موفقیت حذف شد', 'success');
|
||||
setDeleteModal(false);
|
||||
setSelectedAddressId(null);
|
||||
},
|
||||
onError: (error) => {
|
||||
const errorMessage = error instanceof Error ? error.message : 'خطا در حذف آدرس';
|
||||
toast(errorMessage, 'error');
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const handleDeleteCancel = () => {
|
||||
setDeleteModal(false);
|
||||
setSelectedAddressId(null);
|
||||
};
|
||||
|
||||
const formatAddress = (address: Address) => {
|
||||
return `${address.address}، ${address.city}، ${address.province}`;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className='overflow-y-auto h-full noscrollbar flex flex-col'>
|
||||
@@ -36,45 +85,71 @@ function UserAddressesPage({ }: Props) {
|
||||
</div>
|
||||
|
||||
<div className="mt-8 flex-1 w-full py-0 flex flex-col gap-4">
|
||||
{/* <div className='flex-1 flex flex-col items-center justify-center gap-4'>
|
||||
<Danger className='stroke-disabled-text size-9' />
|
||||
<p className='text-center text-sm2 text-disabled-text'>
|
||||
شما هیچ آدرسی ثبت نکرده اید.برای ثبت آدرس دکمه افزودن را بزنید
|
||||
</p>
|
||||
</div> */}
|
||||
<div className="bg-container rounded-container w-full shadow-xl px-4 py-4 flex flex-col justify-between">
|
||||
<h2 className='text-sm font-medium'>منزل</h2>
|
||||
<p className='text-sm2 mt-2'>
|
||||
لورم ایپسوم متن ساختگی با تولید سادگی نامفهوم از صنعت چاپ، و با استفاده از طراحان گرافیک است
|
||||
</p>
|
||||
<p className='text-xs mt-3'>
|
||||
{ef('ارسال: 10,000 تومان')}
|
||||
</p>
|
||||
<div className="flex justify-between items-center mt-4">
|
||||
<Button
|
||||
onClick={onSelected}
|
||||
className='!bg-background !text-foreground flex items-center gap-2 text-xs h-8'
|
||||
>
|
||||
<TickCircle variant='Bold' className='fill-foreground stroke-background size-4.5 mb-0.5' />
|
||||
تنظیم به عنوان فعال
|
||||
</Button>
|
||||
<Link href={'address/new'}>
|
||||
<Button className='!bg-background !text-foreground flex justify-center items-center gap-2 text-xs p-0! size-8!'>
|
||||
<Edit2 className='stroke-foreground size-5 mb-0.5' />
|
||||
</Button>
|
||||
</Link>
|
||||
{isLoading ? (
|
||||
<div className='flex-1 flex flex-col items-center justify-center gap-4'>
|
||||
<p className='text-center text-sm2 text-disabled-text'>
|
||||
در حال بارگذاری...
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="bg-container rounded-container w-full shadow-xl px-4 py-4 flex flex-col justify-between">
|
||||
<h2 className='text-sm font-medium'>شرکت</h2>
|
||||
<p className='text-sm2 mt-2'>
|
||||
لورم ایپسوم متن ساختگی با تولید سادگی نامفهوم از صنعت چاپ، و با استفاده از طراحان گرافیک است
|
||||
</p>
|
||||
<p className='text-xs mt-3'>
|
||||
{ef('ارسال: 21,000 تومان')}
|
||||
</p>
|
||||
</div>
|
||||
) : addresses.length === 0 ? (
|
||||
<div className='flex-1 flex flex-col items-center justify-center gap-4'>
|
||||
<p className='text-center text-sm2 text-disabled-text'>
|
||||
شما هیچ آدرسی ثبت نکرده اید. برای ثبت آدرس دکمه افزودن را بزنید
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
addresses.map((address) => (
|
||||
<div
|
||||
key={address.id}
|
||||
className={`bg-container rounded-container w-full shadow-xl px-4 py-4 flex flex-col justify-between ${address.isDefault ? 'ring-2 ring-primary' : ''}`}
|
||||
>
|
||||
<div className="flex justify-between items-start mb-2">
|
||||
<h2 className='text-sm font-medium'>{address.title}</h2>
|
||||
{address.isDefault && (
|
||||
<span className='text-xs bg-primary/10 text-primary px-2 py-1 rounded-full'>
|
||||
پیشفرض
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<p className='text-sm2 mt-2'>
|
||||
{formatAddress(address)}
|
||||
</p>
|
||||
<p className='text-xs mt-2 text-disabled-text'>
|
||||
کد پستی: {ef(address.postalCode || 'ثبت نشده')}
|
||||
</p>
|
||||
<p className='text-xs mt-2 text-disabled-text'>
|
||||
تلفن: {ef(address.phone)}
|
||||
</p>
|
||||
<div className="flex justify-between items-center mt-4 gap-2">
|
||||
{!address.isDefault && (
|
||||
<Button
|
||||
onClick={() => handleSetDefault(address.id)}
|
||||
disabled={isSettingDefault}
|
||||
pending={isSettingDefault}
|
||||
className='!bg-background !text-foreground flex items-center gap-2 text-xs h-8 flex-1'
|
||||
>
|
||||
<TickCircle variant='Bold' className='fill-foreground stroke-background size-4.5 mb-0.5' />
|
||||
تنظیم به عنوان فعال
|
||||
</Button>
|
||||
)}
|
||||
<div className="flex gap-2">
|
||||
<Link href={`address/new?id=${address.id}`}>
|
||||
<Button className='!bg-background !text-foreground flex justify-center items-center gap-2 text-xs p-0! size-8!'>
|
||||
<Edit2 className='stroke-foreground size-5 mb-0.5' />
|
||||
</Button>
|
||||
</Link>
|
||||
<Button
|
||||
onClick={() => handleDeleteClick(address.id)}
|
||||
disabled={isDeleting}
|
||||
className='!bg-background !text-foreground flex justify-center items-center gap-2 text-xs p-0! size-8!'
|
||||
>
|
||||
<Trash className='stroke-foreground size-5 mb-0.5' />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className='w-full text-center mt-6'>
|
||||
@@ -82,6 +157,17 @@ function UserAddressesPage({ }: Props) {
|
||||
<Button>افزودن آدرس</Button>
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<Prompt
|
||||
title='حذف آدرس'
|
||||
description='آیا از حذف این آدرس اطمینان دارید؟'
|
||||
textConfirm='بله، حذف کن'
|
||||
textCancel='انصراف'
|
||||
visible={deleteModalVisible}
|
||||
onConfirm={handleDeleteConfirm}
|
||||
onCancel={handleDeleteCancel}
|
||||
onClick={handleDeleteCancel}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
import { api } from "@/lib/api/axiosInstance";
|
||||
import { CreateAddressType } from "../types/Types";
|
||||
import {
|
||||
CreateAddressType,
|
||||
AddressesResponse,
|
||||
UpdateAddressType,
|
||||
} from "../types/Types";
|
||||
|
||||
export const getAddresses = async () => {
|
||||
const { data } = await api.get("/public/user/addresses");
|
||||
export const getAddresses = async (): Promise<AddressesResponse> => {
|
||||
const { data } = await api.get<AddressesResponse>("/public/user/addresses");
|
||||
return data;
|
||||
};
|
||||
|
||||
@@ -10,3 +14,19 @@ export const createAddress = async (params: CreateAddressType) => {
|
||||
const { data } = await api.post("/public/user/addresses", params);
|
||||
return data;
|
||||
};
|
||||
|
||||
export const updateAddress = async (params: UpdateAddressType) => {
|
||||
const { id, ...rest } = params;
|
||||
const { data } = await api.put(`/public/user/addresses/${id}`, rest);
|
||||
return data;
|
||||
};
|
||||
|
||||
export const deleteAddress = async (id: string) => {
|
||||
const { data } = await api.delete(`/public/user/addresses/${id}`);
|
||||
return data;
|
||||
};
|
||||
|
||||
export const setDefaultAddress = async (id: string) => {
|
||||
const { data } = await api.patch(`/public/user/addresses/${id}/set-default`);
|
||||
return data;
|
||||
};
|
||||
|
||||
@@ -1,4 +1,24 @@
|
||||
import type { Icon, DivIcon } from "leaflet";
|
||||
import type { BaseResponse } from "@/app/[name]/(Main)/types/Types";
|
||||
|
||||
export interface Address {
|
||||
id: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
deletedAt: string | null;
|
||||
user: string;
|
||||
title: string;
|
||||
address: string;
|
||||
city: string;
|
||||
province: string;
|
||||
postalCode: string;
|
||||
latitude: number;
|
||||
longitude: number;
|
||||
phone: string;
|
||||
isDefault: boolean;
|
||||
}
|
||||
|
||||
export type AddressesResponse = BaseResponse<Address[]>;
|
||||
|
||||
export type CreateAddressType = {
|
||||
title: string;
|
||||
|
||||
Reference in New Issue
Block a user