replace imperativ fetch with tanstack query

This commit is contained in:
thierryverse 2025-11-19 08:41:14 +02:00
parent d2fddd5ea5
commit 666b797772

View file

@ -1,7 +1,7 @@
"use client"; "use client";
import { format } from "date-fns"; import { format } from "date-fns";
import { useAtom } from "jotai"; import { useAtom, useAtomValue } from "jotai";
import { import {
Calendar, Calendar,
MoreHorizontal, MoreHorizontal,
@ -21,6 +21,7 @@ import Image from "next/image";
import { useEffect, useRef, useState } from "react"; import { useEffect, useRef, useState } from "react";
import { toast } from "sonner"; import { toast } from "sonner";
import { deletePodcastMutationAtom } from "@/atoms/podcasts/podcast-mutation.atoms"; import { deletePodcastMutationAtom } from "@/atoms/podcasts/podcast-mutation.atoms";
import { podcastsAtom } from "@/atoms/podcasts/podcast-query.atoms";
// UI Components // UI Components
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Card } from "@/components/ui/card"; import { Card } from "@/components/ui/card";
@ -79,10 +80,7 @@ const podcastCardVariants: Variants = {
const MotionCard = motion(Card); const MotionCard = motion(Card);
export default function PodcastsPageClient({ searchSpaceId }: PodcastsPageClientProps) { export default function PodcastsPageClient({ searchSpaceId }: PodcastsPageClientProps) {
const [podcasts, setPodcasts] = useState<Podcast[]>([]);
const [filteredPodcasts, setFilteredPodcasts] = useState<Podcast[]>([]); const [filteredPodcasts, setFilteredPodcasts] = useState<Podcast[]>([]);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [searchQuery, setSearchQuery] = useState(""); const [searchQuery, setSearchQuery] = useState("");
const [sortOrder, setSortOrder] = useState<string>("newest"); const [sortOrder, setSortOrder] = useState<string>("newest");
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false); const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
@ -104,64 +102,37 @@ export default function PodcastsPageClient({ searchSpaceId }: PodcastsPageClient
const currentObjectUrlRef = useRef<string | null>(null); const currentObjectUrlRef = useRef<string | null>(null);
const [{ isPending: isDeletingPodcast, mutateAsync: deletePodcast, error: deleteError }] = const [{ isPending: isDeletingPodcast, mutateAsync: deletePodcast, error: deleteError }] =
useAtom(deletePodcastMutationAtom); useAtom(deletePodcastMutationAtom);
const {
data: podcasts,
isLoading: isFetchingPodcasts,
error: fetchError,
} = useAtomValue(podcastsAtom);
// Add podcast image URL constant // Add podcast image URL constant
const PODCAST_IMAGE_URL = const PODCAST_IMAGE_URL =
"https://static.vecteezy.com/system/resources/thumbnails/002/157/611/small_2x/illustrations-concept-design-podcast-channel-free-vector.jpg"; "https://static.vecteezy.com/system/resources/thumbnails/002/157/611/small_2x/illustrations-concept-design-podcast-channel-free-vector.jpg";
// Fetch podcasts from API
useEffect(() => { useEffect(() => {
const fetchPodcasts = async () => { if (isFetchingPodcasts) return;
try {
setIsLoading(true);
// Get token from localStorage if (fetchError) {
const token = localStorage.getItem("surfsense_bearer_token"); console.error("Error fetching podcasts:", fetchError);
setFilteredPodcasts([]);
return;
}
if (!token) { if (!podcasts) {
setError("Authentication token not found. Please log in again."); setFilteredPodcasts([]);
setIsLoading(false); return;
return; }
}
// Fetch all podcasts for this search space setFilteredPodcasts(podcasts);
const response = await fetch(
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/podcasts`,
{
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
},
cache: "no-store",
}
);
if (!response.ok) {
const errorData = await response.json().catch(() => null);
throw new Error(
`Failed to fetch podcasts: ${response.status} ${errorData?.detail || ""}`
);
}
const data: Podcast[] = await response.json();
setPodcasts(data);
setFilteredPodcasts(data);
setError(null);
} catch (error) {
console.error("Error fetching podcasts:", error);
setError(error instanceof Error ? error.message : "Unknown error occurred");
setPodcasts([]);
setFilteredPodcasts([]);
} finally {
setIsLoading(false);
}
};
fetchPodcasts();
}, []); }, []);
// Filter and sort podcasts based on search query and sort order // Filter and sort podcasts based on search query and sort order
useEffect(() => { useEffect(() => {
if (!podcasts) return;
let result = [...podcasts]; let result = [...podcasts];
// Filter by search term // Filter by search term
@ -433,7 +404,7 @@ export default function PodcastsPageClient({ searchSpaceId }: PodcastsPageClient
</div> </div>
{/* Status Messages */} {/* Status Messages */}
{isLoading && ( {isFetchingPodcasts && (
<div className="flex items-center justify-center h-40"> <div className="flex items-center justify-center h-40">
<div className="flex flex-col items-center gap-2"> <div className="flex flex-col items-center gap-2">
<div className="h-8 w-8 animate-spin rounded-full border-4 border-primary border-t-transparent"></div> <div className="h-8 w-8 animate-spin rounded-full border-4 border-primary border-t-transparent"></div>
@ -442,14 +413,14 @@ export default function PodcastsPageClient({ searchSpaceId }: PodcastsPageClient
</div> </div>
)} )}
{error && !isLoading && ( {fetchError && !isFetchingPodcasts && (
<div className="border border-destructive/50 text-destructive p-4 rounded-md"> <div className="border border-destructive/50 text-destructive p-4 rounded-md">
<h3 className="font-medium">Error loading podcasts</h3> <h3 className="font-medium">Error loading podcasts</h3>
<p className="text-sm">{error}</p> <p className="text-sm">{fetchError.message ?? "Failed to load podcasts"}</p>
</div> </div>
)} )}
{!isLoading && !error && filteredPodcasts.length === 0 && ( {!isFetchingPodcasts && !fetchError && filteredPodcasts.length === 0 && (
<div className="flex flex-col items-center justify-center h-40 gap-2 text-center"> <div className="flex flex-col items-center justify-center h-40 gap-2 text-center">
<PodcastIcon className="h-8 w-8 text-muted-foreground" /> <PodcastIcon className="h-8 w-8 text-muted-foreground" />
<h3 className="font-medium">No podcasts found</h3> <h3 className="font-medium">No podcasts found</h3>
@ -462,7 +433,7 @@ export default function PodcastsPageClient({ searchSpaceId }: PodcastsPageClient
)} )}
{/* Podcast Grid */} {/* Podcast Grid */}
{!isLoading && !error && filteredPodcasts.length > 0 && ( {!isFetchingPodcasts && !fetchError && filteredPodcasts.length > 0 && (
<AnimatePresence mode="wait"> <AnimatePresence mode="wait">
<motion.div <motion.div
className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6" className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6"