👔 feat: add optimistic update for bookmark button
All checks were successful
Integration Tests / integration-tests (pull_request) Successful in 52s
All checks were successful
Integration Tests / integration-tests (pull_request) Successful in 52s
This commit is contained in:
21
features/home/actions/Hero/removeHeroBannerMediaFromSaved.ts
Normal file
21
features/home/actions/Hero/removeHeroBannerMediaFromSaved.ts
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
"use server";
|
||||||
|
|
||||||
|
import { backendFetch } from "@/shared/helpers/backendFetch";
|
||||||
|
|
||||||
|
export const removeHeroBannerMediaFromSaved = async (mediaId: string) => {
|
||||||
|
try {
|
||||||
|
return await backendFetch("collections/sys", {
|
||||||
|
method: "DELETE",
|
||||||
|
body: JSON.stringify({
|
||||||
|
name: "Saved",
|
||||||
|
itemId: mediaId,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error removing media from saved list:", error);
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message: "Failed to remove media from saved list.",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
@ -1,29 +1,63 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import { addHeroBannerMediaToSaved } from "@/features/home/actions/Hero/addHeroBannerMediaToSaved";
|
import { addHeroBannerMediaToSaved } from "@/features/home/actions/Hero/addHeroBannerMediaToSaved";
|
||||||
|
import { removeHeroBannerMediaFromSaved } from "@/features/home/actions/Hero/removeHeroBannerMediaFromSaved";
|
||||||
import { useAuth } from "@/shared/contexts/AuthContext";
|
import { useAuth } from "@/shared/contexts/AuthContext";
|
||||||
|
import { BackendResponse } from "@/shared/helpers/backendFetch";
|
||||||
import { Button } from "@/shared/libs/shadcn/ui/button";
|
import { Button } from "@/shared/libs/shadcn/ui/button";
|
||||||
import { Icon } from "@iconify/react";
|
import { Icon } from "@iconify/react";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
const AddToList = ({ mediaId }: { mediaId: string }) => {
|
const AddToList = ({
|
||||||
|
mediaId,
|
||||||
|
isInCollection,
|
||||||
|
}: {
|
||||||
|
mediaId: string;
|
||||||
|
isInCollection: boolean;
|
||||||
|
}) => {
|
||||||
const { session } = useAuth();
|
const { session } = useAuth();
|
||||||
|
const [isSaved, setIsSaved] = React.useState<boolean>(isInCollection);
|
||||||
|
|
||||||
const handleAddToList = async () => {
|
const handleAddToList = async () => {
|
||||||
const result = await addHeroBannerMediaToSaved(mediaId);
|
setIsSaved(!isSaved);
|
||||||
console.log("Hasil dari fungsi server:", result);
|
const result = (await addHeroBannerMediaToSaved(mediaId).catch(
|
||||||
|
(_) => void _,
|
||||||
|
)) as BackendResponse<undefined>;
|
||||||
|
if (!result || !result.success) {
|
||||||
|
setIsSaved((prev) => !prev);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const handleRemoveFromList = async () => {
|
||||||
|
setIsSaved(!isSaved);
|
||||||
|
const result = (await removeHeroBannerMediaFromSaved(mediaId).catch(
|
||||||
|
(_) => void _,
|
||||||
|
)) as BackendResponse<undefined>;
|
||||||
|
if (!result || !result.success) {
|
||||||
|
setIsSaved((prev) => !prev);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{session?.user && (
|
{session?.user &&
|
||||||
<Button
|
(isSaved ? (
|
||||||
onClick={handleAddToList}
|
<Button
|
||||||
variant="secondary"
|
onClick={handleRemoveFromList}
|
||||||
className="h-full flex gap-1 px-4 rounded-xl border border-neutral-400/10 bg-neutral-950/20 hover:bg-neutral-950/40 backdrop-blur-lg text-neutral-200"
|
variant="secondary"
|
||||||
>
|
className="h-full flex gap-1 px-4 rounded-xl border border-neutral-400/10 bg-neutral-950/20 hover:bg-neutral-950/40 backdrop-blur-lg text-neutral-200"
|
||||||
<Icon icon="boxicons:bookmark" className="size-5.5" />
|
>
|
||||||
<span>Add to List</span>
|
<Icon icon="boxicons:bookmark-filled" className="size-5.5" />
|
||||||
</Button>
|
<span>Remove from List</span>
|
||||||
)}
|
</Button>
|
||||||
|
) : (
|
||||||
|
<Button
|
||||||
|
onClick={handleAddToList}
|
||||||
|
variant="secondary"
|
||||||
|
className="h-full flex gap-1 px-4 rounded-xl border border-neutral-400/10 bg-neutral-950/20 hover:bg-neutral-950/40 backdrop-blur-lg text-neutral-200"
|
||||||
|
>
|
||||||
|
<Icon icon="boxicons:bookmark" className="size-5.5" />
|
||||||
|
<span>Add to List</span>
|
||||||
|
</Button>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -19,6 +19,7 @@ export interface HeroSwiperProps {
|
|||||||
slug: string;
|
slug: string;
|
||||||
name: string;
|
name: string;
|
||||||
}[];
|
}[];
|
||||||
|
isInCollection: boolean;
|
||||||
}[];
|
}[];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -81,7 +82,10 @@ const HeroSwiper = (props: HeroSwiperProps) => {
|
|||||||
</span>
|
</span>
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
<AddToList mediaId={slide.id} />
|
<AddToList
|
||||||
|
mediaId={slide.id}
|
||||||
|
isInCollection={slide.isInCollection}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</SwiperSlide>
|
</SwiperSlide>
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { RecommendationAnime } from "@/features/home/actions/getRecommenationAnime";
|
import { RecommendationAnime } from "@/features/home/actions/Hero/getRecommenationAnime";
|
||||||
import { Icon } from "@iconify/react";
|
import { Icon } from "@iconify/react";
|
||||||
|
|
||||||
const AnimeRecommendationCard = ({ data }: { data: RecommendationAnime }) => {
|
const AnimeRecommendationCard = ({ data }: { data: RecommendationAnime }) => {
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useRef } from "react";
|
import { useRef } from "react";
|
||||||
import { RecommendationAnime } from "../../actions/getRecommenationAnime";
|
import { RecommendationAnime } from "../../actions/Hero/getRecommenationAnime";
|
||||||
import AnimeRecommendationCard from "./components/Card";
|
import AnimeRecommendationCard from "./components/Card";
|
||||||
import ScrollingButton from "./components/ScrollingButton";
|
import ScrollingButton from "./components/ScrollingButton";
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { getRecommendationAnimeAction } from "../../actions/getRecommenationAnime";
|
import { getRecommendationAnimeAction } from "../../actions/Hero/getRecommenationAnime";
|
||||||
import RecommendationClient from "./main.client";
|
import RecommendationClient from "./main.client";
|
||||||
|
|
||||||
const RecommendationMain = async () => {
|
const RecommendationMain = async () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user