Dissolve/frontend/src/hooks/useApi.ts
2026-07-15 20:36:02 +05:30

139 lines
4.5 KiB
TypeScript

import { useMutation, useQuery, useInfiniteQuery, QueryClient } from '@tanstack/react-query';
import { api, API_URL } from '../api';
import { useAnonymousId } from '@/context/AnonymousIdContext';
import type { EmotionTag, Post, PostSummary, PostContentType } from '@/types';
// A wrapper to inject the anonymous ID header
const callApi = <T,>(method: 'get' | 'post' | 'delete', url: string, anonymousId: string, data?: any): Promise<{ data: T }> => {
const headers = { 'X-Anonymous-User-ID': anonymousId };
if (method === 'post' || method === 'delete') {
return api[method]<T>(url, data, { headers });
}
return api.get<T>(url, { headers });
}
export const useApi = () => {
const { anonymousId } = useAnonymousId();
// Stream
const useGetPosts = (emotion?: EmotionTag) => {
return useInfiniteQuery(
['posts', emotion],
async ({ pageParam: cursor }) => {
if (!anonymousId) throw new Error('Anonymous ID not available');
let url = '/api/posts?';
if (emotion) url += `emotion=${emotion}&`;
if (cursor) url += `cursor=${cursor}`;
const res = await callApi<{posts: Post[], nextCursor: string | null}>('get', url, anonymousId);
return res.data;
},
{
getNextPageParam: (lastPage) => lastPage.nextCursor,
enabled: !!anonymousId,
}
);
};
// Create
const useCreatePost = (queryClient: QueryClient) => {
return useMutation(
({ contentType, contentData, emotionTag }: { contentType: PostContentType, contentData: string, emotionTag: EmotionTag }) => {
if (!anonymousId) throw new Error('Anonymous ID not available');
return callApi('post', '/api/posts', anonymousId, { contentType, contentData, emotionTag });
},
{
onSuccess: () => {
queryClient.invalidateQueries(['my-posts']);
queryClient.invalidateQueries(['posts']);
},
}
);
};
const useUploadFile = () => {
return useMutation(
(file: File) => {
if (!anonymousId) throw new Error('Anonymous ID not available');
const formData = new FormData();
formData.append('file', file);
return callApi<{url: string}>('post', '/api/upload', anonymousId, formData);
},
);
}
// My Dissolves
const useGetMyPosts = () => {
return useQuery(
['my-posts'],
() => {
if (!anonymousId) throw new Error('Anonymous ID not available');
return callApi<{posts: Post[]}>('get', '/api/posts/me', anonymousId).then(res => res.data);
},
{ enabled: !!anonymousId }
);
};
const useGetSummaries = () => {
return useQuery(
['summaries'],
() => {
if (!anonymousId) throw new Error('Anonymous ID not available');
return callApi<{summaries: PostSummary[]}>('get', '/api/posts/summaries', anonymousId).then(res => res.data);
},
{ enabled: !!anonymousId }
);
};
const useClearSummaries = (queryClient: QueryClient) => {
return useMutation(
(summaryIds: string[]) => {
if (!anonymousId) throw new Error('Anonymous ID not available');
return callApi('post', '/api/posts/summaries/clear', anonymousId, { summaryIds });
},
{
onSuccess: () => {
queryClient.invalidateQueries(['summaries']);
},
}
);
};
// Interactions
const useHoldPost = (queryClient: QueryClient) => {
return useMutation(
(postId: string) => {
if (!anonymousId) throw new Error('Anonymous ID not available');
return callApi('post', `/api/posts/${postId}/hold`, anonymousId);
},
{
onSuccess: (_data, postId) => {
queryClient.setQueryData(['held-posts', postId], true);
}
}
);
};
const useReportPost = () => {
return useMutation(
({ postId, reason }: { postId: string, reason: string }) => {
if (!anonymousId) throw new Error('Anonymous ID not available');
return callApi('post', `/api/posts/${postId}/report`, anonymousId, { reason });
},
);
};
const useIsPostHeld = (postId: string) => {
const {data} = useQuery(['held-posts', postId], () => false, { staleTime: Infinity, cacheTime: Infinity });
return data;
}
const getMediaUrl = (path: string) => {
if (path.startsWith('/api/media/')) {
return `${API_URL}${path}`
}
return path;
}
return { useGetPosts, useCreatePost, useUploadFile, useGetMyPosts, useGetSummaries, useClearSummaries, useHoldPost, useReportPost, useIsPostHeld, getMediaUrl };
};