feat: add toast notification on favorite button click

This commit is contained in:
Kentai Radiquum 2025-03-21 00:01:46 +05:00
parent f609de25f9
commit 60ece79df3
Signed by: Radiquum
GPG key ID: 858E8EE696525EED
4 changed files with 119 additions and 35 deletions

View file

@ -8,6 +8,7 @@ import { Button, Modal } from "flowbite-react";
import { Spinner } from "./components/Spinner/Spinner"; import { Spinner } from "./components/Spinner/Spinner";
import { ChangelogModal } from "#/components/ChangelogModal/ChangelogModal"; import { ChangelogModal } from "#/components/ChangelogModal/ChangelogModal";
import PlausibleProvider from "next-plausible"; import PlausibleProvider from "next-plausible";
import { Bounce, ToastContainer } from "react-toastify";
const inter = Inter({ subsets: ["latin"] }); const inter = Inter({ subsets: ["latin"] });
@ -111,6 +112,19 @@ export const App = (props) => {
enabled={true} enabled={true}
/> />
)} )}
<ToastContainer
position="bottom-center"
autoClose={5000}
hideProgressBar={false}
newestOnTop={true}
closeOnClick={true}
rtl={false}
pauseOnFocusLoss={false}
draggable={true}
pauseOnHover={true}
theme="colored"
transition={Bounce}
/>
</body> </body>
); );
}; };

View file

@ -3,7 +3,9 @@ import { ENDPOINTS } from "#/api/config";
import Link from "next/link"; import Link from "next/link";
import useSWRInfinite from "swr/infinite"; import useSWRInfinite from "swr/infinite";
import { useCallback, useEffect, useState } from "react"; import { useCallback, useEffect, useState } from "react";
import { useSWRfetcher } from "#/api/utils"; import { tryCatchAPI, useSWRfetcher } from "#/api/utils";
import { toast } from "react-toastify";
import { useThemeMode } from "flowbite-react";
const lists = [ const lists = [
{ list: 0, name: "Не смотрю" }, { list: 0, name: "Не смотрю" },
@ -32,18 +34,64 @@ export const ReleaseInfoUserList = (props: {
}) => { }) => {
const [AddReleaseToCollectionModalOpen, setAddReleaseToCollectionModalOpen] = const [AddReleaseToCollectionModalOpen, setAddReleaseToCollectionModalOpen] =
useState(false); useState(false);
const [favButtonDisabled, setFavButtonDisabled] = useState(false);
const [listEventDisabledd, setListEventDisabled] = useState(false);
const theme = useThemeMode();
function _addToFavorite() { function _addToFavorite() {
if (props.token) { async function _setFav(url: string) {
props.setIsFavorite(!props.isFavorite); setFavButtonDisabled(true);
if (props.isFavorite) { const tid = toast.loading(
fetch( !props.isFavorite ?
`${ENDPOINTS.user.favorite}/delete/${props.release_id}?token=${props.token}` "Добавляем в избранное..."
); : "Удаляем из избранное...",
} else { {
fetch( position: "bottom-center",
`${ENDPOINTS.user.favorite}/add/${props.release_id}?token=${props.token}` hideProgressBar: true,
); closeOnClick: false,
pauseOnHover: false,
draggable: false,
theme: theme.mode == "light" ? "light" : "dark",
}
);
const { data, error } = await tryCatchAPI(fetch(url));
if (error) {
toast.update(tid, {
render:
!props.isFavorite ?
"Ошибка добавления в избранное"
: "Ошибка удаления из избранного",
type: "error",
autoClose: 2500,
isLoading: false,
closeOnClick: true,
draggable: true,
});
setFavButtonDisabled(false);
return;
} }
toast.update(tid, {
render:
!props.isFavorite ? "Добавлено в избранное" : "Удалено из избранного",
type: "success",
autoClose: 2500,
isLoading: false,
closeOnClick: true,
draggable: true,
});
props.setIsFavorite(!props.isFavorite);
setFavButtonDisabled(false);
}
if (props.token) {
let url = `${ENDPOINTS.user.favorite}/add/${props.release_id}?token=${props.token}`;
if (props.isFavorite) {
url = `${ENDPOINTS.user.favorite}/delete/${props.release_id}?token=${props.token}`;
}
_setFav(url);
} }
} }
@ -78,7 +126,7 @@ export const ReleaseInfoUserList = (props: {
<span className="w-6 h-6 iconify mdi--bookmark-add "></span> <span className="w-6 h-6 iconify mdi--bookmark-add "></span>
</Button> </Button>
)} )}
{props.token ? ( {props.token ?
<> <>
<Dropdown <Dropdown
label={lists[props.userList].name} label={lists[props.userList].name}
@ -102,6 +150,7 @@ export const ReleaseInfoUserList = (props: {
_addToFavorite(); _addToFavorite();
}} }}
size="sm" size="sm"
disabled={favButtonDisabled}
> >
<span <span
className={`iconify w-6 h-6 ${ className={`iconify w-6 h-6 ${
@ -110,9 +159,7 @@ export const ReleaseInfoUserList = (props: {
></span> ></span>
</Button> </Button>
</> </>
) : ( : <p>Войдите что-бы добавить в список, избранное или коллекцию</p>}
<p>Войдите что-бы добавить в список, избранное или коллекцию</p>
)}
</div> </div>
<AddReleaseToCollectionModal <AddReleaseToCollectionModal
isOpen={AddReleaseToCollectionModalOpen} isOpen={AddReleaseToCollectionModalOpen}
@ -175,7 +222,6 @@ const AddReleaseToCollectionModal = (props: {
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [scrollPosition]); }, [scrollPosition]);
function _addToCollection(collection_id: number) { function _addToCollection(collection_id: number) {
if (props.token) { if (props.token) {
fetch( fetch(
@ -212,25 +258,25 @@ const AddReleaseToCollectionModal = (props: {
onScroll={handleScroll} onScroll={handleScroll}
ref={modalRef} ref={modalRef}
> >
{content && content.length > 0 {content && content.length > 0 ?
? content.map((collection) => ( content.map((collection) => (
<button <button
className="relative w-full h-64 overflow-hidden bg-center bg-no-repeat bg-cover rounded-sm group-hover:animate-bg_zoom animate-bg_zoom_rev group-hover:[background-size:110%] " className="relative w-full h-64 overflow-hidden bg-center bg-no-repeat bg-cover rounded-sm group-hover:animate-bg_zoom animate-bg_zoom_rev group-hover:[background-size:110%] "
style={{ style={{
backgroundImage: `linear-gradient(to bottom, rgba(0, 0, 0, 0.1) 0%, rgba(0, 0, 0, 0.9) 100%), url(${collection.image})`, backgroundImage: `linear-gradient(to bottom, rgba(0, 0, 0, 0.1) 0%, rgba(0, 0, 0, 0.9) 100%), url(${collection.image})`,
}} }}
key={`collection_${collection.id}`} key={`collection_${collection.id}`}
onClick={() => _addToCollection(collection.id)} onClick={() => _addToCollection(collection.id)}
> >
<div className="absolute bottom-0 left-0 gap-1 p-2"> <div className="absolute bottom-0 left-0 gap-1 p-2">
<p className="text-xl font-bold text-white"> <p className="text-xl font-bold text-white">
{collection.title} {collection.title}
</p> </p>
<p className="text-gray-400">{collection.description}</p> <p className="text-gray-400">{collection.description}</p>
</div> </div>
</button> </button>
)) ))
: "коллекций не найдено"} : "коллекций не найдено"}
</div> </div>
</Modal> </Modal>
); );

23
package-lock.json generated
View file

@ -20,6 +20,7 @@
"react": "^18", "react": "^18",
"react-cropper": "^2.3.3", "react-cropper": "^2.3.3",
"react-dom": "^18", "react-dom": "^18",
"react-toastify": "^11.0.5",
"swiper": "^11.1.4", "swiper": "^11.1.4",
"swr": "^2.2.5", "swr": "^2.2.5",
"videojs-video-element": "^1.4.1", "videojs-video-element": "^1.4.1",
@ -1671,6 +1672,15 @@
"resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz",
"integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==" "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA=="
}, },
"node_modules/clsx": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
"integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/color-convert": { "node_modules/color-convert": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
@ -4765,6 +4775,19 @@
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
"dev": true "dev": true
}, },
"node_modules/react-toastify": {
"version": "11.0.5",
"resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-11.0.5.tgz",
"integrity": "sha512-EpqHBGvnSTtHYhCPLxML05NLY2ZX0JURbAdNYa6BUkk+amz4wbKBQvoKQAB0ardvSarUBuY4Q4s1sluAzZwkmA==",
"license": "MIT",
"dependencies": {
"clsx": "^2.1.1"
},
"peerDependencies": {
"react": "^18 || ^19",
"react-dom": "^18 || ^19"
}
},
"node_modules/read-cache": { "node_modules/read-cache": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",

View file

@ -21,6 +21,7 @@
"react": "^18", "react": "^18",
"react-cropper": "^2.3.3", "react-cropper": "^2.3.3",
"react-dom": "^18", "react-dom": "^18",
"react-toastify": "^11.0.5",
"swiper": "^11.1.4", "swiper": "^11.1.4",
"swr": "^2.2.5", "swr": "^2.2.5",
"videojs-video-element": "^1.4.1", "videojs-video-element": "^1.4.1",