allow to mark episodes as completed
This commit is contained in:
41
web/src/api/episodes.ts
Normal file
41
web/src/api/episodes.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { useMutation, useQuery } from "@tanstack/react-query";
|
||||
|
||||
export type EpisodeDetail = {
|
||||
id: number;
|
||||
title: string;
|
||||
pubDate: string;
|
||||
guid: string;
|
||||
url: string;
|
||||
podcastId: number;
|
||||
number: number;
|
||||
listened: boolean;
|
||||
createdAt: string;
|
||||
};
|
||||
|
||||
export const usePodcastEpisodesQuery = (
|
||||
id: number | string | null | undefined,
|
||||
) => {
|
||||
return useQuery({
|
||||
queryKey: ["podcasts", id, "episodes"],
|
||||
enabled: typeof id !== "undefined" && id !== null,
|
||||
queryFn: async () => {
|
||||
const resp = await fetch(`/api/podcasts/${id}/episodes`);
|
||||
return (await resp.json()) as EpisodeDetail[];
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const useUpdateEpisodeMutation = () => {
|
||||
return useMutation({
|
||||
mutationFn: async ({
|
||||
id,
|
||||
...data
|
||||
}: Partial<EpisodeDetail> & { id: number }) => {
|
||||
const resp = await fetch(`/api/episodes/${id}`, {
|
||||
method: "PATCH",
|
||||
body: JSON.stringify(data),
|
||||
});
|
||||
return await resp.json();
|
||||
},
|
||||
});
|
||||
};
|
||||
@@ -11,17 +11,6 @@ export type PodcastDetail = {
|
||||
createdAt: string;
|
||||
};
|
||||
|
||||
export type EpisodeDetail = {
|
||||
id: number;
|
||||
title: string;
|
||||
pubDate: string;
|
||||
guid: string;
|
||||
url: string;
|
||||
podcastId: number;
|
||||
number: number;
|
||||
createdAt: string;
|
||||
};
|
||||
|
||||
export const usePodcastsQuery = () => {
|
||||
return useQuery({
|
||||
queryKey: ["podcasts"],
|
||||
@@ -43,19 +32,6 @@ export const usePodcastQuery = (id: number | string | null | undefined) => {
|
||||
});
|
||||
};
|
||||
|
||||
export const usePodcastEpisodesQuery = (
|
||||
id: number | string | null | undefined,
|
||||
) => {
|
||||
return useQuery({
|
||||
queryKey: ["podcasts", id, "episodes"],
|
||||
enabled: typeof id !== "undefined" && id !== null,
|
||||
queryFn: async () => {
|
||||
const resp = await fetch(`/api/podcasts/${id}/episodes`);
|
||||
return (await resp.json()) as EpisodeDetail[];
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export type CreatePodcastData = {
|
||||
feed: string;
|
||||
};
|
||||
|
||||
@@ -1,11 +1,17 @@
|
||||
import { useParams } from "react-router";
|
||||
import { usePodcastEpisodesQuery, usePodcastQuery } from "../api/podcasts";
|
||||
import { usePodcastQuery } from "../api/podcasts";
|
||||
import {
|
||||
usePodcastEpisodesQuery,
|
||||
useUpdateEpisodeMutation,
|
||||
} from "../api/episodes";
|
||||
import { Link } from "react-router";
|
||||
import { usePlayerContext } from "../player/context";
|
||||
import { PlayIcon } from "../icons/play";
|
||||
import { PauseIcon } from "../icons/pause";
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
|
||||
export const PodcastPage = () => {
|
||||
const queryClient = useQueryClient();
|
||||
const { id } = useParams<{ id: string }>();
|
||||
|
||||
const { data: podcast } = usePodcastQuery(id);
|
||||
@@ -13,6 +19,40 @@ export const PodcastPage = () => {
|
||||
|
||||
const player = usePlayerContext();
|
||||
|
||||
const updateEpisodeMutation = useUpdateEpisodeMutation();
|
||||
|
||||
const handleMarkCompleted = (episodeId: number) => {
|
||||
updateEpisodeMutation.mutate(
|
||||
{
|
||||
id: episodeId,
|
||||
listened: true,
|
||||
},
|
||||
{
|
||||
onSuccess() {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: ["podcasts", id, "episodes"],
|
||||
});
|
||||
},
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
const handleUnmarkCompleted = (episodeId: number) => {
|
||||
updateEpisodeMutation.mutate(
|
||||
{
|
||||
id: episodeId,
|
||||
listened: false,
|
||||
},
|
||||
{
|
||||
onSuccess() {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: ["podcasts", id, "episodes"],
|
||||
});
|
||||
},
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Link to="/">
|
||||
@@ -52,7 +92,31 @@ export const PodcastPage = () => {
|
||||
)}
|
||||
<span>{episode.title}</span>
|
||||
</div>
|
||||
<span>{new Date(episode.createdAt).toLocaleString()}</span>
|
||||
<div className="flex items-center gap-2">
|
||||
{episode.listened ? (
|
||||
<button
|
||||
className="text-green-700"
|
||||
disabled={
|
||||
updateEpisodeMutation.isPending &&
|
||||
updateEpisodeMutation.variables.id === episode.id
|
||||
}
|
||||
onClick={() => handleUnmarkCompleted(episode.id)}
|
||||
>
|
||||
Unmark completed
|
||||
</button>
|
||||
) : (
|
||||
<button
|
||||
disabled={
|
||||
updateEpisodeMutation.isPending &&
|
||||
updateEpisodeMutation.variables.id === episode.id
|
||||
}
|
||||
onClick={() => handleMarkCompleted(episode.id)}
|
||||
>
|
||||
Mark completed
|
||||
</button>
|
||||
)}
|
||||
<span>{new Date(episode.createdAt).toLocaleString()}</span>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { createContext, useContext } from "react";
|
||||
import type { EpisodeDetail } from "../api/podcasts";
|
||||
import type { EpisodeDetail } from "../api/episodes";
|
||||
|
||||
export type PlayerStatus = "stopped" | "playing" | "paused";
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import {
|
||||
type PlayerStatus,
|
||||
playerContext,
|
||||
} from "./context";
|
||||
import type { EpisodeDetail } from "../api/podcasts";
|
||||
import type { EpisodeDetail } from "../api/episodes";
|
||||
|
||||
export const PlayerProvider = ({ children }: { children: ReactNode }) => {
|
||||
const [status, setStatus] = useState<PlayerStatus>("stopped");
|
||||
|
||||
Reference in New Issue
Block a user