list of addresses
This commit is contained in:
@@ -1,5 +1,11 @@
|
|||||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
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 = () => {
|
export const useGetAddresses = () => {
|
||||||
return useQuery({
|
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 Button from '@/components/button/PrimaryButton';
|
||||||
import { ef } from '@/lib/helpers/utfNumbers';
|
import { ef } from '@/lib/helpers/utfNumbers';
|
||||||
// import { useProfile } from '@/hooks/auth/useProfile';
|
import { ArrowLeft, Edit2, TickCircle, Trash } from 'iconsax-react';
|
||||||
// import { useAuthStore } from '@/zustand/authStore';
|
|
||||||
import { ArrowLeft, Edit2, TickCircle } from 'iconsax-react';
|
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { useRouter, useSearchParams } from 'next/navigation';
|
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
|
type Props = object
|
||||||
|
|
||||||
function UserAddressesPage({ }: Props) {
|
function UserAddressesPage({ }: Props) {
|
||||||
|
const { data: addressesResponse, isLoading } = useGetAddresses();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const searchParams = useSearchParams();
|
const searchParams = useSearchParams();
|
||||||
const redirect = searchParams.get('redirect');
|
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 = () => {
|
const addresses = addressesResponse?.data || [];
|
||||||
if(redirect) {
|
|
||||||
|
const handleSetDefault = (addressId: string) => {
|
||||||
|
setDefault(addressId, {
|
||||||
|
onSuccess: () => {
|
||||||
|
toast('آدرس به عنوان پیشفرض تنظیم شد', 'success');
|
||||||
|
if (redirect) {
|
||||||
router.replace(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 (
|
return (
|
||||||
<div className='overflow-y-auto h-full noscrollbar flex flex-col'>
|
<div className='overflow-y-auto h-full noscrollbar flex flex-col'>
|
||||||
@@ -36,45 +85,71 @@ function UserAddressesPage({ }: Props) {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mt-8 flex-1 w-full py-0 flex flex-col gap-4">
|
<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'>
|
{isLoading ? (
|
||||||
<Danger className='stroke-disabled-text size-9' />
|
<div className='flex-1 flex flex-col items-center justify-center gap-4'>
|
||||||
<p className='text-center text-sm2 text-disabled-text'>
|
<p className='text-center text-sm2 text-disabled-text'>
|
||||||
شما هیچ آدرسی ثبت نکرده اید.برای ثبت آدرس دکمه افزودن را بزنید
|
در حال بارگذاری...
|
||||||
</p>
|
</p>
|
||||||
</div> */}
|
</div>
|
||||||
<div className="bg-container rounded-container w-full shadow-xl px-4 py-4 flex flex-col justify-between">
|
) : addresses.length === 0 ? (
|
||||||
<h2 className='text-sm font-medium'>منزل</h2>
|
<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'>
|
<p className='text-sm2 mt-2'>
|
||||||
لورم ایپسوم متن ساختگی با تولید سادگی نامفهوم از صنعت چاپ، و با استفاده از طراحان گرافیک است
|
{formatAddress(address)}
|
||||||
</p>
|
</p>
|
||||||
<p className='text-xs mt-3'>
|
<p className='text-xs mt-2 text-disabled-text'>
|
||||||
{ef('ارسال: 10,000 تومان')}
|
کد پستی: {ef(address.postalCode || 'ثبت نشده')}
|
||||||
</p>
|
</p>
|
||||||
<div className="flex justify-between items-center mt-4">
|
<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
|
<Button
|
||||||
onClick={onSelected}
|
onClick={() => handleSetDefault(address.id)}
|
||||||
className='!bg-background !text-foreground flex items-center gap-2 text-xs h-8'
|
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' />
|
<TickCircle variant='Bold' className='fill-foreground stroke-background size-4.5 mb-0.5' />
|
||||||
تنظیم به عنوان فعال
|
تنظیم به عنوان فعال
|
||||||
</Button>
|
</Button>
|
||||||
<Link href={'address/new'}>
|
)}
|
||||||
|
<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!'>
|
<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' />
|
<Edit2 className='stroke-foreground size-5 mb-0.5' />
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</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="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>
|
</div>
|
||||||
|
))
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='w-full text-center mt-6'>
|
<div className='w-full text-center mt-6'>
|
||||||
@@ -82,6 +157,17 @@ function UserAddressesPage({ }: Props) {
|
|||||||
<Button>افزودن آدرس</Button>
|
<Button>افزودن آدرس</Button>
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<Prompt
|
||||||
|
title='حذف آدرس'
|
||||||
|
description='آیا از حذف این آدرس اطمینان دارید؟'
|
||||||
|
textConfirm='بله، حذف کن'
|
||||||
|
textCancel='انصراف'
|
||||||
|
visible={deleteModalVisible}
|
||||||
|
onConfirm={handleDeleteConfirm}
|
||||||
|
onCancel={handleDeleteCancel}
|
||||||
|
onClick={handleDeleteCancel}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,12 @@
|
|||||||
import { api } from "@/lib/api/axiosInstance";
|
import { api } from "@/lib/api/axiosInstance";
|
||||||
import { CreateAddressType } from "../types/Types";
|
import {
|
||||||
|
CreateAddressType,
|
||||||
|
AddressesResponse,
|
||||||
|
UpdateAddressType,
|
||||||
|
} from "../types/Types";
|
||||||
|
|
||||||
export const getAddresses = async () => {
|
export const getAddresses = async (): Promise<AddressesResponse> => {
|
||||||
const { data } = await api.get("/public/user/addresses");
|
const { data } = await api.get<AddressesResponse>("/public/user/addresses");
|
||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -10,3 +14,19 @@ export const createAddress = async (params: CreateAddressType) => {
|
|||||||
const { data } = await api.post("/public/user/addresses", params);
|
const { data } = await api.post("/public/user/addresses", params);
|
||||||
return data;
|
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 { 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 = {
|
export type CreateAddressType = {
|
||||||
title: string;
|
title: string;
|
||||||
|
|||||||
Reference in New Issue
Block a user