931 lines
27 KiB
TypeScript
931 lines
27 KiB
TypeScript
/**
|
|
* React Query hooks for API calls
|
|
*/
|
|
import { keepPreviousData, useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
|
import { useDebouncedValue } from "@/hooks/useDebouncedValue";
|
|
import {
|
|
authApi,
|
|
categoryApi,
|
|
websiteApi,
|
|
productApi,
|
|
bountyApi,
|
|
favoriteApi,
|
|
notificationApi,
|
|
paymentApi,
|
|
adminApi,
|
|
friendApi,
|
|
setAccessToken,
|
|
setRefreshToken,
|
|
clearRefreshToken,
|
|
searchApi,
|
|
type User,
|
|
type Bounty,
|
|
type BountyApplication,
|
|
type BountyComment,
|
|
type BountyDelivery,
|
|
type BountyDispute,
|
|
type BountyReview,
|
|
type BountyExtensionRequest,
|
|
type Favorite,
|
|
type FavoriteTag,
|
|
type PriceMonitor,
|
|
type Notification,
|
|
type NotificationPreference,
|
|
} from '@/lib/api';
|
|
|
|
const shortStaleTime = 30 * 1000;
|
|
const staticStaleTime = 5 * 60 * 1000;
|
|
|
|
// ==================== Auth Hooks ====================
|
|
|
|
export function useMe() {
|
|
return useQuery({
|
|
queryKey: ['auth', 'me'],
|
|
queryFn: authApi.me,
|
|
retry: false,
|
|
refetchOnWindowFocus: false,
|
|
staleTime: shortStaleTime,
|
|
});
|
|
}
|
|
|
|
export function useLogin() {
|
|
const queryClient = useQueryClient();
|
|
|
|
return useMutation({
|
|
mutationFn: ({ openId, password }: { openId: string; password: string }) =>
|
|
authApi.login({ open_id: openId, password }),
|
|
onSuccess: (data) => {
|
|
// Store tokens
|
|
setAccessToken(data.access_token);
|
|
setRefreshToken(data.refresh_token);
|
|
// Refetch user data
|
|
queryClient.invalidateQueries({ queryKey: ['auth', 'me'] });
|
|
},
|
|
});
|
|
}
|
|
|
|
export function useRegister() {
|
|
const queryClient = useQueryClient();
|
|
|
|
return useMutation({
|
|
mutationFn: ({ openId, password, name, email }: { openId: string; password: string; name?: string; email?: string }) =>
|
|
authApi.register({ open_id: openId, password, name, email }),
|
|
onSuccess: (data) => {
|
|
setAccessToken(data.access_token);
|
|
setRefreshToken(data.refresh_token);
|
|
queryClient.invalidateQueries({ queryKey: ['auth', 'me'] });
|
|
},
|
|
});
|
|
}
|
|
|
|
export function useLogout() {
|
|
const queryClient = useQueryClient();
|
|
|
|
return useMutation({
|
|
mutationFn: authApi.logout,
|
|
onSuccess: () => {
|
|
setAccessToken(null);
|
|
clearRefreshToken();
|
|
queryClient.setQueryData(['auth', 'me'], null);
|
|
queryClient.invalidateQueries({ queryKey: ['auth'] });
|
|
},
|
|
});
|
|
}
|
|
|
|
export function useUpdateMe() {
|
|
const queryClient = useQueryClient();
|
|
|
|
return useMutation({
|
|
mutationFn: authApi.updateMe,
|
|
onSuccess: (data) => {
|
|
queryClient.setQueryData(['auth', 'me'], data);
|
|
},
|
|
});
|
|
}
|
|
|
|
export function useChangePassword() {
|
|
return useMutation({
|
|
mutationFn: authApi.changePassword,
|
|
});
|
|
}
|
|
|
|
// ==================== Friends Hooks ====================
|
|
|
|
export function useFriends(options?: { enabled?: boolean }) {
|
|
return useQuery({
|
|
queryKey: ['friends'],
|
|
queryFn: friendApi.list,
|
|
staleTime: shortStaleTime,
|
|
enabled: options?.enabled !== false,
|
|
});
|
|
}
|
|
|
|
export function useIncomingFriendRequests(options?: { enabled?: boolean }) {
|
|
return useQuery({
|
|
queryKey: ['friends', 'requests', 'incoming'],
|
|
queryFn: friendApi.incoming,
|
|
staleTime: shortStaleTime,
|
|
enabled: options?.enabled !== false,
|
|
});
|
|
}
|
|
|
|
export function useOutgoingFriendRequests(options?: { enabled?: boolean }) {
|
|
return useQuery({
|
|
queryKey: ['friends', 'requests', 'outgoing'],
|
|
queryFn: friendApi.outgoing,
|
|
staleTime: shortStaleTime,
|
|
enabled: options?.enabled !== false,
|
|
});
|
|
}
|
|
|
|
/** 仅请求待处理好友数量,用于角标(不拉完整列表) */
|
|
export function useIncomingFriendRequestCount(options?: { enabled?: boolean }) {
|
|
return useQuery({
|
|
queryKey: ['friends', 'requests', 'incoming', 'count'],
|
|
queryFn: () => friendApi.incomingCount(),
|
|
staleTime: shortStaleTime,
|
|
enabled: options?.enabled !== false,
|
|
});
|
|
}
|
|
|
|
export function useSendFriendRequest() {
|
|
const queryClient = useQueryClient();
|
|
|
|
return useMutation({
|
|
mutationFn: (data: { receiver_id: number }) => friendApi.sendRequest(data),
|
|
onSuccess: () => {
|
|
queryClient.invalidateQueries({ queryKey: ['friends', 'requests'] });
|
|
},
|
|
});
|
|
}
|
|
|
|
export function useAcceptFriendRequest() {
|
|
const queryClient = useQueryClient();
|
|
|
|
return useMutation({
|
|
mutationFn: (requestId: number) => friendApi.acceptRequest(requestId),
|
|
onSuccess: () => {
|
|
queryClient.invalidateQueries({ queryKey: ['friends'] });
|
|
queryClient.invalidateQueries({ queryKey: ['friends', 'requests'] });
|
|
},
|
|
});
|
|
}
|
|
|
|
export function useRejectFriendRequest() {
|
|
const queryClient = useQueryClient();
|
|
|
|
return useMutation({
|
|
mutationFn: (requestId: number) => friendApi.rejectRequest(requestId),
|
|
onSuccess: () => {
|
|
queryClient.invalidateQueries({ queryKey: ['friends', 'requests'] });
|
|
},
|
|
});
|
|
}
|
|
|
|
export function useCancelFriendRequest() {
|
|
const queryClient = useQueryClient();
|
|
|
|
return useMutation({
|
|
mutationFn: (requestId: number) => friendApi.cancelRequest(requestId),
|
|
onSuccess: () => {
|
|
queryClient.invalidateQueries({ queryKey: ['friends', 'requests'] });
|
|
},
|
|
});
|
|
}
|
|
|
|
export function useSearchUsers(q: string, limit?: number) {
|
|
const debouncedQuery = useDebouncedValue(q, 300);
|
|
return useQuery({
|
|
queryKey: ['friends', 'search', debouncedQuery, limit],
|
|
queryFn: () => friendApi.searchUsers(debouncedQuery, limit),
|
|
enabled: !!debouncedQuery.trim(),
|
|
staleTime: shortStaleTime,
|
|
});
|
|
}
|
|
|
|
// ==================== Category Hooks ====================
|
|
|
|
export function useCategories(options?: { enabled?: boolean }) {
|
|
return useQuery({
|
|
queryKey: ['categories'],
|
|
queryFn: categoryApi.list,
|
|
staleTime: staticStaleTime,
|
|
enabled: options?.enabled !== false,
|
|
});
|
|
}
|
|
|
|
export function useCategoryBySlug(slug: string) {
|
|
return useQuery({
|
|
queryKey: ['categories', slug],
|
|
queryFn: () => categoryApi.getBySlug(slug),
|
|
enabled: !!slug,
|
|
staleTime: staticStaleTime,
|
|
});
|
|
}
|
|
|
|
// ==================== Website Hooks ====================
|
|
|
|
export function useWebsites(params?: { category_id?: number; is_verified?: boolean; page?: number }) {
|
|
return useQuery({
|
|
queryKey: ['websites', params],
|
|
queryFn: () => websiteApi.list(params),
|
|
staleTime: shortStaleTime,
|
|
placeholderData: keepPreviousData,
|
|
});
|
|
}
|
|
|
|
export function useWebsite(id: number) {
|
|
return useQuery({
|
|
queryKey: ['websites', id],
|
|
queryFn: () => websiteApi.get(id),
|
|
enabled: !!id,
|
|
staleTime: shortStaleTime,
|
|
});
|
|
}
|
|
|
|
// ==================== Product Hooks ====================
|
|
|
|
export function useProducts(params?: { category_id?: number; search?: string; page?: number; min_price?: number; max_price?: number; sort_by?: string }) {
|
|
return useQuery({
|
|
queryKey: ['products', params],
|
|
queryFn: () => productApi.list(params),
|
|
staleTime: shortStaleTime,
|
|
placeholderData: keepPreviousData,
|
|
});
|
|
}
|
|
|
|
export function useRecommendedProducts(limit = 8) {
|
|
return useQuery({
|
|
queryKey: ['products', 'recommendations', limit],
|
|
queryFn: () => productApi.recommendations(limit),
|
|
staleTime: shortStaleTime,
|
|
});
|
|
}
|
|
|
|
export function useProduct(id: number) {
|
|
return useQuery({
|
|
queryKey: ['products', id],
|
|
queryFn: () => productApi.get(id),
|
|
enabled: !!id,
|
|
staleTime: shortStaleTime,
|
|
});
|
|
}
|
|
|
|
export function useProductWithPrices(id: number) {
|
|
return useQuery({
|
|
queryKey: ['products', id, 'prices'],
|
|
queryFn: () => productApi.getWithPrices(id),
|
|
enabled: !!id,
|
|
staleTime: shortStaleTime,
|
|
});
|
|
}
|
|
|
|
export function useProductSearch(q: string, params?: { page?: number; category_id?: number; user_id?: string; min_price?: number; max_price?: number; sort_by?: string }) {
|
|
const debouncedQuery = useDebouncedValue(q, 300);
|
|
return useQuery({
|
|
queryKey: ['products', 'search', debouncedQuery, params],
|
|
queryFn: () => productApi.search({ q: debouncedQuery, ...params }),
|
|
enabled: !!debouncedQuery.trim(),
|
|
staleTime: shortStaleTime,
|
|
placeholderData: keepPreviousData,
|
|
});
|
|
}
|
|
|
|
// ==================== Bounty Hooks ====================
|
|
|
|
export function useBounties(params?: { status?: string; publisher_id?: number; acceptor_id?: number; page?: number }) {
|
|
return useQuery({
|
|
queryKey: ['bounties', params],
|
|
queryFn: () => bountyApi.list(params),
|
|
staleTime: shortStaleTime,
|
|
placeholderData: keepPreviousData,
|
|
});
|
|
}
|
|
|
|
export function useBounty(id: number) {
|
|
return useQuery({
|
|
queryKey: ['bounties', id],
|
|
queryFn: () => bountyApi.get(id),
|
|
enabled: !!id,
|
|
staleTime: shortStaleTime,
|
|
});
|
|
}
|
|
|
|
export function useBountySearch(q: string, page?: number) {
|
|
const debouncedQuery = useDebouncedValue(q, 300);
|
|
return useQuery({
|
|
queryKey: ['bounties', 'search', debouncedQuery, page],
|
|
queryFn: () => bountyApi.search(debouncedQuery, page),
|
|
enabled: !!debouncedQuery.trim(),
|
|
staleTime: shortStaleTime,
|
|
placeholderData: keepPreviousData,
|
|
});
|
|
}
|
|
|
|
export function useMyPublishedBounties(page?: number) {
|
|
return useQuery({
|
|
queryKey: ['bounties', 'my-published', page],
|
|
queryFn: () => bountyApi.myPublished(page),
|
|
staleTime: shortStaleTime,
|
|
placeholderData: keepPreviousData,
|
|
});
|
|
}
|
|
|
|
export function useMyAcceptedBounties(page?: number) {
|
|
return useQuery({
|
|
queryKey: ['bounties', 'my-accepted', page],
|
|
queryFn: () => bountyApi.myAccepted(page),
|
|
staleTime: shortStaleTime,
|
|
placeholderData: keepPreviousData,
|
|
});
|
|
}
|
|
|
|
export function useCreateBounty() {
|
|
const queryClient = useQueryClient();
|
|
|
|
return useMutation({
|
|
mutationFn: bountyApi.create,
|
|
onSuccess: () => {
|
|
queryClient.invalidateQueries({ queryKey: ['bounties'] });
|
|
},
|
|
});
|
|
}
|
|
|
|
export function useUpdateBounty() {
|
|
const queryClient = useQueryClient();
|
|
|
|
return useMutation({
|
|
mutationFn: ({ id, data }: { id: number; data: Parameters<typeof bountyApi.update>[1] }) =>
|
|
bountyApi.update(id, data),
|
|
onSuccess: (_, { id }) => {
|
|
queryClient.invalidateQueries({ queryKey: ['bounties', id] });
|
|
queryClient.invalidateQueries({ queryKey: ['bounties'] });
|
|
},
|
|
});
|
|
}
|
|
|
|
export function useCancelBounty() {
|
|
const queryClient = useQueryClient();
|
|
|
|
return useMutation({
|
|
mutationFn: bountyApi.cancel,
|
|
onSuccess: (_, id) => {
|
|
queryClient.invalidateQueries({ queryKey: ['bounties', id] });
|
|
queryClient.invalidateQueries({ queryKey: ['bounties'] });
|
|
},
|
|
});
|
|
}
|
|
|
|
export function useCompleteBounty() {
|
|
const queryClient = useQueryClient();
|
|
|
|
return useMutation({
|
|
mutationFn: bountyApi.complete,
|
|
onSuccess: (_, id) => {
|
|
queryClient.invalidateQueries({ queryKey: ['bounties', id] });
|
|
queryClient.invalidateQueries({ queryKey: ['bounties'] });
|
|
},
|
|
});
|
|
}
|
|
|
|
// Bounty Applications
|
|
export function useBountyApplications(bountyId: number) {
|
|
return useQuery({
|
|
queryKey: ['bounties', bountyId, 'applications'],
|
|
queryFn: () => bountyApi.listApplications(bountyId),
|
|
enabled: !!bountyId,
|
|
});
|
|
}
|
|
|
|
export function useMyBountyApplication(bountyId: number) {
|
|
return useQuery({
|
|
queryKey: ['bounties', bountyId, 'my-application'],
|
|
queryFn: () => bountyApi.myApplication(bountyId),
|
|
enabled: !!bountyId,
|
|
});
|
|
}
|
|
|
|
export function useSubmitApplication() {
|
|
const queryClient = useQueryClient();
|
|
|
|
return useMutation({
|
|
mutationFn: ({ bountyId, data }: { bountyId: number; data: { message?: string } }) =>
|
|
bountyApi.submitApplication(bountyId, data),
|
|
onSuccess: (_, { bountyId }) => {
|
|
queryClient.invalidateQueries({ queryKey: ['bounties', bountyId, 'applications'] });
|
|
queryClient.invalidateQueries({ queryKey: ['bounties', bountyId, 'my-application'] });
|
|
},
|
|
});
|
|
}
|
|
|
|
export function useAcceptApplication() {
|
|
const queryClient = useQueryClient();
|
|
|
|
return useMutation({
|
|
mutationFn: ({ bountyId, applicationId }: { bountyId: number; applicationId: number }) =>
|
|
bountyApi.acceptApplication(bountyId, applicationId),
|
|
onSuccess: (_, { bountyId }) => {
|
|
queryClient.invalidateQueries({ queryKey: ['bounties', bountyId] });
|
|
queryClient.invalidateQueries({ queryKey: ['bounties', bountyId, 'applications'] });
|
|
},
|
|
});
|
|
}
|
|
|
|
// Bounty Comments
|
|
export function useBountyComments(bountyId: number) {
|
|
return useQuery({
|
|
queryKey: ['bounties', bountyId, 'comments'],
|
|
queryFn: () => bountyApi.listComments(bountyId),
|
|
enabled: !!bountyId,
|
|
});
|
|
}
|
|
|
|
export function useCreateComment() {
|
|
const queryClient = useQueryClient();
|
|
|
|
return useMutation({
|
|
mutationFn: ({ bountyId, data }: { bountyId: number; data: { content: string; parent_id?: number } }) =>
|
|
bountyApi.createComment(bountyId, data),
|
|
onSuccess: (_, { bountyId }) => {
|
|
queryClient.invalidateQueries({ queryKey: ['bounties', bountyId, 'comments'] });
|
|
},
|
|
});
|
|
}
|
|
|
|
// Bounty Deliveries
|
|
export function useDeliveries(bountyId: number, enabled = true) {
|
|
return useQuery({
|
|
queryKey: ['bounties', bountyId, 'deliveries'],
|
|
queryFn: () => bountyApi.listDeliveries(bountyId),
|
|
enabled: !!bountyId && enabled,
|
|
});
|
|
}
|
|
|
|
export function useSubmitDelivery() {
|
|
const queryClient = useQueryClient();
|
|
return useMutation({
|
|
mutationFn: ({ bountyId, data }: { bountyId: number; data: { content: string; attachment_url?: string } }) =>
|
|
bountyApi.submitDelivery(bountyId, data),
|
|
onSuccess: (_, { bountyId }) => {
|
|
queryClient.invalidateQueries({ queryKey: ['bounties', bountyId, 'deliveries'] });
|
|
},
|
|
});
|
|
}
|
|
|
|
export function useReviewDelivery() {
|
|
const queryClient = useQueryClient();
|
|
return useMutation({
|
|
mutationFn: ({ bountyId, deliveryId, accept }: { bountyId: number; deliveryId: number; accept: boolean }) =>
|
|
bountyApi.reviewDelivery(bountyId, deliveryId, accept),
|
|
onSuccess: (_, { bountyId }) => {
|
|
queryClient.invalidateQueries({ queryKey: ['bounties', bountyId, 'deliveries'] });
|
|
},
|
|
});
|
|
}
|
|
|
|
// Bounty Disputes
|
|
export function useDisputes(bountyId: number, enabled = true) {
|
|
return useQuery({
|
|
queryKey: ['bounties', bountyId, 'disputes'],
|
|
queryFn: () => bountyApi.listDisputes(bountyId),
|
|
enabled: !!bountyId && enabled,
|
|
});
|
|
}
|
|
|
|
export function useCreateDispute() {
|
|
const queryClient = useQueryClient();
|
|
return useMutation({
|
|
mutationFn: ({ bountyId, data }: { bountyId: number; data: { reason: string; evidence_url?: string } }) =>
|
|
bountyApi.createDispute(bountyId, data),
|
|
onSuccess: (_, { bountyId }) => {
|
|
queryClient.invalidateQueries({ queryKey: ['bounties', bountyId, 'disputes'] });
|
|
},
|
|
});
|
|
}
|
|
|
|
export function useResolveDispute() {
|
|
const queryClient = useQueryClient();
|
|
return useMutation({
|
|
mutationFn: ({ bountyId, disputeId, data }: { bountyId: number; disputeId: number; data: { resolution: string; accepted: boolean } }) =>
|
|
bountyApi.resolveDispute(bountyId, disputeId, data),
|
|
onSuccess: (_, { bountyId }) => {
|
|
queryClient.invalidateQueries({ queryKey: ['bounties', bountyId, 'disputes'] });
|
|
},
|
|
});
|
|
}
|
|
|
|
// Bounty Reviews
|
|
export function useBountyReviews(bountyId: number, enabled = true) {
|
|
return useQuery({
|
|
queryKey: ['bounties', bountyId, 'reviews'],
|
|
queryFn: () => bountyApi.listReviews(bountyId),
|
|
enabled: !!bountyId && enabled,
|
|
});
|
|
}
|
|
|
|
export function useCreateReview() {
|
|
const queryClient = useQueryClient();
|
|
return useMutation({
|
|
mutationFn: ({ bountyId, data }: { bountyId: number; data: { reviewee_id: number; rating: number; comment?: string } }) =>
|
|
bountyApi.createReview(bountyId, data),
|
|
onSuccess: (_, { bountyId }) => {
|
|
queryClient.invalidateQueries({ queryKey: ['bounties', bountyId, 'reviews'] });
|
|
},
|
|
});
|
|
}
|
|
|
|
// Bounty Extensions
|
|
export function useExtensionRequests(bountyId: number, enabled = true) {
|
|
return useQuery({
|
|
queryKey: ['bounties', bountyId, 'extensions'],
|
|
queryFn: () => bountyApi.listExtensions(bountyId),
|
|
enabled: !!bountyId && enabled,
|
|
});
|
|
}
|
|
|
|
export function useCreateExtensionRequest() {
|
|
const queryClient = useQueryClient();
|
|
return useMutation({
|
|
mutationFn: ({ bountyId, data }: { bountyId: number; data: { proposed_deadline: string; reason?: string } }) =>
|
|
bountyApi.createExtension(bountyId, data),
|
|
onSuccess: (_, { bountyId }) => {
|
|
queryClient.invalidateQueries({ queryKey: ['bounties', bountyId, 'extensions'] });
|
|
},
|
|
});
|
|
}
|
|
|
|
export function useReviewExtensionRequest() {
|
|
const queryClient = useQueryClient();
|
|
return useMutation({
|
|
mutationFn: ({ bountyId, requestId, approve }: { bountyId: number; requestId: number; approve: boolean }) =>
|
|
bountyApi.reviewExtension(bountyId, requestId, approve),
|
|
onSuccess: (_, { bountyId }) => {
|
|
queryClient.invalidateQueries({ queryKey: ['bounties', bountyId, 'extensions'] });
|
|
},
|
|
});
|
|
}
|
|
|
|
// ==================== Favorites Hooks ====================
|
|
|
|
export function useFavorites(params?: { tag_id?: number; page?: number }, options?: { enabled?: boolean }) {
|
|
return useQuery({
|
|
queryKey: ['favorites', params],
|
|
queryFn: () => favoriteApi.list(params),
|
|
staleTime: shortStaleTime,
|
|
placeholderData: keepPreviousData,
|
|
enabled: options?.enabled !== false,
|
|
});
|
|
}
|
|
|
|
export function useFavorite(id: number) {
|
|
return useQuery({
|
|
queryKey: ['favorites', id],
|
|
queryFn: () => favoriteApi.get(id),
|
|
enabled: !!id,
|
|
staleTime: shortStaleTime,
|
|
});
|
|
}
|
|
|
|
export function useCheckFavorite(productId: number, websiteId: number) {
|
|
return useQuery({
|
|
queryKey: ['favorites', 'check', productId, websiteId],
|
|
queryFn: () => favoriteApi.check(productId, websiteId),
|
|
enabled: !!productId && !!websiteId,
|
|
staleTime: shortStaleTime,
|
|
});
|
|
}
|
|
|
|
export function useAddFavorite() {
|
|
const queryClient = useQueryClient();
|
|
|
|
return useMutation({
|
|
mutationFn: favoriteApi.add,
|
|
onSuccess: (_, data) => {
|
|
queryClient.invalidateQueries({ queryKey: ['favorites'] });
|
|
queryClient.invalidateQueries({ queryKey: ['favorites', 'check', data.product_id, data.website_id] });
|
|
},
|
|
});
|
|
}
|
|
|
|
export function useRemoveFavorite() {
|
|
const queryClient = useQueryClient();
|
|
|
|
return useMutation({
|
|
mutationFn: favoriteApi.remove,
|
|
onSuccess: () => {
|
|
queryClient.invalidateQueries({ queryKey: ['favorites'] });
|
|
},
|
|
});
|
|
}
|
|
|
|
// Favorite Tags
|
|
export function useFavoriteTags() {
|
|
return useQuery({
|
|
queryKey: ['favorites', 'tags'],
|
|
queryFn: favoriteApi.listTags,
|
|
staleTime: staticStaleTime,
|
|
});
|
|
}
|
|
|
|
export function useCreateFavoriteTag() {
|
|
const queryClient = useQueryClient();
|
|
|
|
return useMutation({
|
|
mutationFn: favoriteApi.createTag,
|
|
onSuccess: () => {
|
|
queryClient.invalidateQueries({ queryKey: ['favorites', 'tags'] });
|
|
},
|
|
});
|
|
}
|
|
|
|
export function useUpdateFavoriteTag() {
|
|
const queryClient = useQueryClient();
|
|
|
|
return useMutation({
|
|
mutationFn: ({ id, data }: { id: number; data: Parameters<typeof favoriteApi.updateTag>[1] }) =>
|
|
favoriteApi.updateTag(id, data),
|
|
onSuccess: () => {
|
|
queryClient.invalidateQueries({ queryKey: ['favorites', 'tags'] });
|
|
},
|
|
});
|
|
}
|
|
|
|
export function useDeleteFavoriteTag() {
|
|
const queryClient = useQueryClient();
|
|
|
|
return useMutation({
|
|
mutationFn: favoriteApi.deleteTag,
|
|
onSuccess: () => {
|
|
queryClient.invalidateQueries({ queryKey: ['favorites', 'tags'] });
|
|
queryClient.invalidateQueries({ queryKey: ['favorites'] });
|
|
},
|
|
});
|
|
}
|
|
|
|
// Price Monitor
|
|
export function usePriceMonitor(favoriteId: number) {
|
|
return useQuery({
|
|
queryKey: ['favorites', favoriteId, 'monitor'],
|
|
queryFn: () => favoriteApi.getMonitor(favoriteId),
|
|
enabled: !!favoriteId,
|
|
});
|
|
}
|
|
|
|
export function usePriceHistory(favoriteId: number, page?: number) {
|
|
return useQuery({
|
|
queryKey: ['favorites', favoriteId, 'monitor', 'history', page],
|
|
queryFn: () => favoriteApi.getMonitorHistory(favoriteId, page),
|
|
enabled: !!favoriteId,
|
|
staleTime: shortStaleTime,
|
|
placeholderData: keepPreviousData,
|
|
});
|
|
}
|
|
|
|
export function useCreatePriceMonitor() {
|
|
const queryClient = useQueryClient();
|
|
|
|
return useMutation({
|
|
mutationFn: ({ favoriteId, data }: { favoriteId: number; data: Parameters<typeof favoriteApi.createMonitor>[1] }) =>
|
|
favoriteApi.createMonitor(favoriteId, data),
|
|
onSuccess: (_, { favoriteId }) => {
|
|
queryClient.invalidateQueries({ queryKey: ['favorites', favoriteId, 'monitor'] });
|
|
},
|
|
});
|
|
}
|
|
|
|
export function useUpdatePriceMonitor() {
|
|
const queryClient = useQueryClient();
|
|
return useMutation({
|
|
mutationFn: ({ favoriteId, data }: { favoriteId: number; data: Parameters<typeof favoriteApi.updateMonitor>[1] }) =>
|
|
favoriteApi.updateMonitor(favoriteId, data),
|
|
onSuccess: (_, { favoriteId }) => {
|
|
queryClient.invalidateQueries({ queryKey: ['favorites', favoriteId, 'monitor'] });
|
|
},
|
|
});
|
|
}
|
|
|
|
export function useRefreshPriceMonitor() {
|
|
const queryClient = useQueryClient();
|
|
return useMutation({
|
|
mutationFn: (favoriteId: number) => favoriteApi.refreshMonitor(favoriteId),
|
|
onSuccess: (_, favoriteId) => {
|
|
queryClient.invalidateQueries({ queryKey: ['favorites', favoriteId, 'monitor'] });
|
|
queryClient.invalidateQueries({ queryKey: ['favorites', favoriteId, 'monitor', 'history'] });
|
|
},
|
|
});
|
|
}
|
|
|
|
export function useAllPriceMonitors(page?: number) {
|
|
return useQuery({
|
|
queryKey: ['favorites', 'monitors', 'all', page],
|
|
queryFn: () => favoriteApi.listAllMonitors(page),
|
|
staleTime: shortStaleTime,
|
|
placeholderData: keepPreviousData,
|
|
});
|
|
}
|
|
|
|
// ==================== Notification Hooks ====================
|
|
|
|
export function useNotifications(params?: { is_read?: boolean; type?: string; start?: string; end?: string; page?: number }, options?: { enabled?: boolean }) {
|
|
return useQuery({
|
|
queryKey: ['notifications', params],
|
|
queryFn: () => notificationApi.list(params),
|
|
staleTime: shortStaleTime,
|
|
placeholderData: keepPreviousData,
|
|
enabled: options?.enabled !== false,
|
|
});
|
|
}
|
|
|
|
export function useGlobalSearch(q: string, limit = 10) {
|
|
const debouncedQuery = useDebouncedValue(q, 300);
|
|
return useQuery({
|
|
queryKey: ['search', debouncedQuery, limit],
|
|
queryFn: () => searchApi.global(debouncedQuery, limit),
|
|
enabled: !!debouncedQuery.trim(),
|
|
staleTime: shortStaleTime,
|
|
});
|
|
}
|
|
|
|
export function useUnreadNotificationCount(options?: { enabled?: boolean }) {
|
|
return useQuery({
|
|
queryKey: ['notifications', 'unread-count'],
|
|
queryFn: notificationApi.unreadCount,
|
|
staleTime: shortStaleTime,
|
|
enabled: options?.enabled !== false,
|
|
});
|
|
}
|
|
|
|
export function useMarkNotificationAsRead() {
|
|
const queryClient = useQueryClient();
|
|
|
|
return useMutation({
|
|
mutationFn: notificationApi.markAsRead,
|
|
onSuccess: () => {
|
|
queryClient.invalidateQueries({ queryKey: ['notifications'] });
|
|
},
|
|
});
|
|
}
|
|
|
|
export function useMarkAllNotificationsAsRead() {
|
|
const queryClient = useQueryClient();
|
|
|
|
return useMutation({
|
|
mutationFn: notificationApi.markAllAsRead,
|
|
onSuccess: () => {
|
|
queryClient.invalidateQueries({ queryKey: ['notifications'] });
|
|
},
|
|
});
|
|
}
|
|
|
|
export function useNotificationPreferences(options?: { enabled?: boolean }) {
|
|
return useQuery({
|
|
queryKey: ['notifications', 'preferences'],
|
|
queryFn: notificationApi.getPreferences,
|
|
staleTime: shortStaleTime,
|
|
enabled: options?.enabled !== false,
|
|
});
|
|
}
|
|
|
|
export function useUpdateNotificationPreferences() {
|
|
const queryClient = useQueryClient();
|
|
return useMutation({
|
|
mutationFn: (data: { enable_bounty?: boolean; enable_price_alert?: boolean; enable_system?: boolean }) =>
|
|
notificationApi.updatePreferences(data),
|
|
onSuccess: () => {
|
|
queryClient.invalidateQueries({ queryKey: ['notifications', 'preferences'] });
|
|
},
|
|
});
|
|
}
|
|
|
|
// ==================== Payment Hooks ====================
|
|
|
|
export function useConnectStatus() {
|
|
return useQuery({
|
|
queryKey: ['payments', 'connect', 'status'],
|
|
queryFn: paymentApi.getConnectStatus,
|
|
});
|
|
}
|
|
|
|
export function useCreateEscrow() {
|
|
return useMutation({
|
|
mutationFn: paymentApi.createEscrow,
|
|
});
|
|
}
|
|
|
|
export function useSetupConnectAccount() {
|
|
const queryClient = useQueryClient();
|
|
|
|
return useMutation({
|
|
mutationFn: ({ returnUrl, refreshUrl }: { returnUrl: string; refreshUrl: string }) =>
|
|
paymentApi.setupConnectAccount(returnUrl, refreshUrl),
|
|
onSuccess: () => {
|
|
queryClient.invalidateQueries({ queryKey: ['payments', 'connect'] });
|
|
},
|
|
});
|
|
}
|
|
|
|
export function useReleasePayout() {
|
|
const queryClient = useQueryClient();
|
|
|
|
return useMutation({
|
|
mutationFn: paymentApi.releasePayout,
|
|
onSuccess: (_, bountyId) => {
|
|
queryClient.invalidateQueries({ queryKey: ['bounties', bountyId] });
|
|
},
|
|
});
|
|
}
|
|
|
|
// ==================== Admin Hooks ====================
|
|
|
|
export function useAdminUsers() {
|
|
return useQuery({
|
|
queryKey: ['admin', 'users'],
|
|
queryFn: adminApi.listUsers,
|
|
staleTime: shortStaleTime,
|
|
});
|
|
}
|
|
|
|
export function useUpdateAdminUser() {
|
|
const queryClient = useQueryClient();
|
|
return useMutation({
|
|
mutationFn: ({ id, data }: { id: number; data: { role?: string; is_active?: boolean } }) =>
|
|
adminApi.updateUser(id, data),
|
|
onSuccess: () => {
|
|
queryClient.invalidateQueries({ queryKey: ['admin', 'users'] });
|
|
},
|
|
});
|
|
}
|
|
|
|
export function useAdminBounties(status?: string) {
|
|
return useQuery({
|
|
queryKey: ['admin', 'bounties', status],
|
|
queryFn: () => adminApi.listBounties(status),
|
|
staleTime: shortStaleTime,
|
|
});
|
|
}
|
|
|
|
export function useAdminPayments() {
|
|
return useQuery({
|
|
queryKey: ['admin', 'payments'],
|
|
queryFn: adminApi.listPayments,
|
|
staleTime: shortStaleTime,
|
|
});
|
|
}
|
|
|
|
export function useAdminDisputes(status?: string) {
|
|
return useQuery({
|
|
queryKey: ['admin', 'disputes', status],
|
|
queryFn: () => adminApi.listDisputes(status),
|
|
staleTime: shortStaleTime,
|
|
});
|
|
}
|
|
|
|
export function useAdminPendingProducts() {
|
|
return useQuery({
|
|
queryKey: ['admin', 'products', 'pending'],
|
|
queryFn: adminApi.listPendingProducts,
|
|
staleTime: shortStaleTime,
|
|
});
|
|
}
|
|
|
|
export function useAdminAllProducts(status?: string) {
|
|
return useQuery({
|
|
queryKey: ['admin', 'products', 'all', status],
|
|
queryFn: () => adminApi.listAllProducts(status),
|
|
staleTime: shortStaleTime,
|
|
});
|
|
}
|
|
|
|
export function useReviewProduct() {
|
|
const queryClient = useQueryClient();
|
|
return useMutation({
|
|
mutationFn: ({ productId, data }: { productId: number; data: { approved: boolean; reject_reason?: string } }) =>
|
|
adminApi.reviewProduct(productId, data),
|
|
onSuccess: () => {
|
|
queryClient.invalidateQueries({ queryKey: ['admin', 'products'] });
|
|
},
|
|
});
|
|
}
|
|
|
|
export function useUpdateAdminProductImages() {
|
|
const queryClient = useQueryClient();
|
|
return useMutation({
|
|
mutationFn: ({ productId, data }: { productId: number; data: { images: string[]; image?: string } }) =>
|
|
adminApi.updateProductImages(productId, data),
|
|
onSuccess: () => {
|
|
queryClient.invalidateQueries({ queryKey: ['admin', 'products'] });
|
|
},
|
|
});
|
|
}
|
|
|
|
// ==================== My Products Hooks ====================
|
|
|
|
export function useMyProducts(status?: string, options?: { enabled?: boolean }) {
|
|
return useQuery({
|
|
queryKey: ['products', 'my', status],
|
|
queryFn: () => productApi.myProducts(status),
|
|
staleTime: shortStaleTime,
|
|
enabled: options?.enabled !== false,
|
|
});
|
|
}
|