diff --git a/README.md b/README.md index a8d8796..954ed05 100644 --- a/README.md +++ b/README.md @@ -6,10 +6,10 @@ AniX is an unofficial web client for the Android application Anixart. It allows ## Changelog [RU] -- [3.4.0](./public/changelog/3.4.0.md) - [3.3.0](./public/changelog/3.3.0.md) - [3.2.3](./public/changelog/3.2.3.md) - [3.2.2](./public/changelog/3.2.2.md) +- [3.2.1](./public/changelog/3.2.1.md) [other versions](./public/changelog) diff --git a/app/App.tsx b/app/App.tsx index 69d1288..e8c949c 100644 --- a/app/App.tsx +++ b/app/App.tsx @@ -8,7 +8,6 @@ import { Button, Modal } from "flowbite-react"; import { Spinner } from "./components/Spinner/Spinner"; import { ChangelogModal } from "#/components/ChangelogModal/ChangelogModal"; import PlausibleProvider from "next-plausible"; -import { Bounce, ToastContainer } from "react-toastify"; const inter = Inter({ subsets: ["latin"] }); @@ -112,20 +111,6 @@ export const App = (props) => { enabled={true} /> )} - ); }; diff --git a/app/api/config.ts b/app/api/config.ts index b0b7b9c..e4aeda4 100644 --- a/app/api/config.ts +++ b/app/api/config.ts @@ -1,4 +1,4 @@ -export const CURRENT_APP_VERSION = "3.4.0"; +export const CURRENT_APP_VERSION = "3.3.0"; export const API_URL = "https://api.anixart.tv"; export const API_PREFIX = "/api/proxy"; @@ -13,7 +13,6 @@ export const ENDPOINTS = { licensed: `${API_PREFIX}/release/streaming/platform`, }, user: { - auth: `${API_PREFIX}/auth/signIn`, profile: `${API_PREFIX}/profile`, bookmark: `${API_PREFIX}/profile/list`, history: `${API_PREFIX}/history`, diff --git a/app/api/profile/login/route.ts b/app/api/profile/login/route.ts new file mode 100644 index 0000000..21d5a51 --- /dev/null +++ b/app/api/profile/login/route.ts @@ -0,0 +1,14 @@ +import { NextResponse, NextRequest } from "next/server"; +import { authorize } from "#/api/utils"; +import { API_URL } from "#/api/config"; + +export async function POST(request: NextRequest) { + const response = await authorize(`${API_URL}/auth/signIn`, await request.json()); + if (!response) { + return NextResponse.json({ message: "Server Error" }, { status: 500 }); + } + if (!response.profile) { + return NextResponse.json({ message: "Profile not found" }, { status: 404 }); + } + return NextResponse.json(response); +} diff --git a/app/api/search/route.ts b/app/api/search/route.ts index d07dea9..ce7f73d 100644 --- a/app/api/search/route.ts +++ b/app/api/search/route.ts @@ -49,26 +49,16 @@ export async function GET(request: NextRequest) { if (token) { url.searchParams.set("token", token); } - const body = { query, searchBy }; + const data = { query, searchBy }; - const { data, error } = await fetchDataViaPost( + const response = await fetchDataViaPost( url.toString(), - JSON.stringify(body), + JSON.stringify(data), true ); - if (error) { - return new Response(JSON.stringify(error), { - status: 500, - headers: { - "Content-Type": "application/json", - }, - }); + if (!response) { + return NextResponse.json({ message: "Bad request" }, { status: 400 }); } - return new Response(JSON.stringify(data), { - status: 200, - headers: { - "Content-Type": "application/json", - }, - }); + return NextResponse.json(response); } diff --git a/app/api/utils.ts b/app/api/utils.ts index af3a4c3..53b48ae 100644 --- a/app/api/utils.ts +++ b/app/api/utils.ts @@ -4,159 +4,79 @@ export const HEADERS = { "Content-Type": "application/json; charset=UTF-8", }; -type Success = { - data: T; - error: null; -}; - -type Failure = { - data: null; - error: E; -}; - -type Result = Success | Failure; - -export async function tryCatch( - promise: Promise -): Promise> { - try { - const data = await promise; - return { data, error: null }; - } catch (error) { - return { data: null, error: error as E }; - } -} - -export async function tryCatchPlayer( - promise: Promise -): Promise> { - try { - const res: Awaited = await promise; - const data = await res.json(); - if (!res.ok) { - if (data.message) { - return { - data: null, - error: { - message: data.message, - code: res.status, - }, - }; - } else if (data.detail) { - return { - data: null, - error: { - message: data.detail, - code: res.status, - }, - }; - } else { - return { - data: null, - error: { - message: res.statusText, - code: res.status, - }, - }; - } - } - - return { data, error: null }; - } catch (error) { - return { data: null, error: error as E }; - } -} - -export async function tryCatchAPI( - promise: Promise -): Promise> { - try { - const res: Awaited = await promise; - // if (!res.ok) { - // return { - // data: null, - // error: { - // message: res.statusText, - // code: res.status, - // }, - // }; - // } - - if ( - res.headers.get("content-length") && - Number(res.headers.get("content-length")) == 0 - ) { - return { - data: null, - error: { - message: "Not Found", - code: 404, - }, - }; - } - - const data: Awaited = await res.json(); - if (data.code != 0) { - return { - data: null, - error: { - message: "API Returned an Error", - code: data.code || 500, - }, - }; - } - - return { data, error: null }; - } catch (error) { - return { data: null, error: error }; - } -} - -export const useSWRfetcher = async (url: string) => { - const { data, error } = await tryCatchAPI(fetch(url)); - if (error) { - throw error; - } - return data; -}; - export const fetchDataViaGet = async ( url: string, - API_V2: string | boolean = false, - addHeaders?: Record + API_V2: string | boolean = false ) => { if (API_V2) { HEADERS["API-Version"] = "v2"; } - - const { data, error } = await tryCatchAPI( - fetch(url, { - headers: { ...HEADERS, ...addHeaders }, - }) - ); - - return { data, error }; + try { + const response = await fetch(url, { + headers: HEADERS, + }); + if (response.status !== 200) { + return null; + } + const data = await response.json(); + return data; + } catch (error) { + console.log(error); + } }; export const fetchDataViaPost = async ( url: string, body: string, API_V2: string | boolean = false, - addHeaders?: Record + contentType: string = "" ) => { if (API_V2) { HEADERS["API-Version"] = "v2"; } + if (contentType != "") { + HEADERS["Content-Type"] = contentType; + } - const { data, error } = await tryCatchAPI( - fetch(url, { + try { + const response = await fetch(url, { method: "POST", + headers: HEADERS, body: body, - headers: { ...HEADERS, ...addHeaders }, - }) - ); + }); + if (response.status !== 200) { + return null; + } + const data = await response.json(); + return data; + } catch (error) { + console.log(error); + } +}; - return { data, error }; +export const authorize = async ( + url: string, + data: { login: string; password: string } +) => { + try { + const response = await fetch( + `${url}?login=${data.login}&password=${data.password}`, + { + method: "POST", + headers: { + "User-Agent": USER_AGENT, + Sign: "9aa5c7af74e8cd70c86f7f9587bde23d", + "Content-Type": "application/x-www-form-urlencoded", + }, + } + ); + if (response.status !== 200) { + throw new Error("Error authorizing user"); + } + return await response.json(); + } catch (error) { + return error; + } }; export function setJWT(user_id: number | string, jwt: string) { diff --git a/app/collection/[id]/page.tsx b/app/collection/[id]/page.tsx index f70d285..a8b8b1c 100644 --- a/app/collection/[id]/page.tsx +++ b/app/collection/[id]/page.tsx @@ -1,36 +1,28 @@ import { ViewCollectionPage } from "#/pages/ViewCollection"; import { fetchDataViaGet } from "#/api/utils"; import type { Metadata, ResolvingMetadata } from "next"; -export const dynamic = "force-static"; +export const dynamic = 'force-static'; export async function generateMetadata( { params }, parent: ResolvingMetadata ): Promise { const id = params.id; - const { data, error } = await fetchDataViaGet( + const collection = await fetchDataViaGet( `https://api.anixart.tv/collection/${id}` ); const previousOG = (await parent).openGraph; - if (error) { - return { - title: "Приватная коллекция", - description: "Приватная коллекция", - }; - } - return { - title: - data.collection ? - "коллекция - " + data.collection.title + title: collection.collection + ? "коллекция - " + collection.collection.title : "Приватная коллекция", - description: data.collection && data.collection.description, + description: collection.collection && collection.collection.description, openGraph: { ...previousOG, images: [ { - url: data.collection && data.collection.image, // Must be an absolute URL + url: collection.collection && collection.collection.image, // Must be an absolute URL width: 600, height: 800, }, diff --git a/app/components/ChangelogModal/ChangelogModal.tsx b/app/components/ChangelogModal/ChangelogModal.tsx index 294ef8f..0d12d07 100644 --- a/app/components/ChangelogModal/ChangelogModal.tsx +++ b/app/components/ChangelogModal/ChangelogModal.tsx @@ -4,7 +4,6 @@ import { Modal, Accordion } from "flowbite-react"; import Markdown from "markdown-to-jsx"; import { useEffect, useState } from "react"; import Styles from "./ChangelogModal.module.css"; -import { tryCatch } from "#/api/utils"; export const ChangelogModal = (props: { isOpen: boolean; @@ -18,20 +17,29 @@ export const ChangelogModal = (props: { >({}); async function _fetchVersionChangelog(version: string) { - const { data, error } = await tryCatch(fetch(`/changelog/${version}.md`)); - if (error) { - return "Нет списка изменений"; - } - return await data.text(); + const res = await fetch(`/changelog/${version}.md`); + return await res.text(); } useEffect(() => { if (props.version != "" && currentVersionChangelog == "") { - setCurrentVersionChangelog("Загрузка ..."); _fetchVersionChangelog(props.version).then((data) => { setCurrentVersionChangelog(data); }); } + + if (props.previousVersions.length > 0) { + props.previousVersions.forEach((version) => { + _fetchVersionChangelog(version).then((data) => { + setPreviousVersionsChangelog((prev) => { + return { + ...prev, + [version]: data, + }; + }); + }); + }); + } // eslint-disable-next-line react-hooks/exhaustive-deps }, [props.version]); @@ -42,38 +50,20 @@ export const ChangelogModal = (props: { {currentVersionChangelog} - - {props.previousVersions.length > 0 && - props.previousVersions.map((version) => { - return ( + {Object.keys(previousVersionsChangelog).length == props.previousVersions.length && ( + + {props.previousVersions.map( + (version) => ( - { - if (!previousVersionsChangelog.hasOwnProperty(version)) { - _fetchVersionChangelog(version).then((data) => { - setPreviousVersionsChangelog((prev) => { - return { - ...prev, - [version]: data, - }; - }); - }); - } - }} - > - Список изменений v{version} - + Список изменений v{version} - {previousVersionsChangelog.hasOwnProperty(version) ? - - {previousVersionsChangelog[version]} - - :
Загрузка ...
} + {previousVersionsChangelog[version]}
- ); - })} -
+ ) + )} +
+ )} ); diff --git a/app/components/CollectionInfo/CollectionInfoControls.tsx b/app/components/CollectionInfo/CollectionInfoControls.tsx index d14c39c..ccbe41f 100644 --- a/app/components/CollectionInfo/CollectionInfoControls.tsx +++ b/app/components/CollectionInfo/CollectionInfoControls.tsx @@ -1,11 +1,9 @@ "use client"; -import { Card, Button, useThemeMode } from "flowbite-react"; +import { Card, Button } from "flowbite-react"; import { useState } from "react"; import { useUserStore } from "#/store/auth"; import { ENDPOINTS } from "#/api/config"; import { useRouter } from "next/navigation"; -import { tryCatchAPI } from "#/api/utils"; -import { toast } from "react-toastify"; export const CollectionInfoControls = (props: { isFavorite: boolean; @@ -14,124 +12,36 @@ export const CollectionInfoControls = (props: { isPrivate: boolean; }) => { const [isFavorite, setIsFavorite] = useState(props.isFavorite); - const [isUpdating, setIsUpdating] = useState(false); - const theme = useThemeMode(); - const userStore = useUserStore(); const router = useRouter(); async function _addToFavorite() { - async function _FavCol(url: string) { - setIsUpdating(true); - const tid = toast.loading( - isFavorite ? - "Удаляем коллекцию из избранного..." - : "Добавляем коллекцию в избранное...", - { - position: "bottom-center", - 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: - isFavorite ? - "Ошибка удаления коллекции из избранного" - : "Ошибка добавления коллекции в избранное", - type: "error", - autoClose: 2500, - isLoading: false, - closeOnClick: true, - draggable: true, - }); - setIsUpdating(false); - return; - } - - toast.update(tid, { - render: - isFavorite ? - "Коллекция удалена из избранного" - : "Коллекция добавлена в избранное", - type: "success", - autoClose: 2500, - isLoading: false, - closeOnClick: true, - draggable: true, - }); - - setIsUpdating(false); + if (userStore.user) { setIsFavorite(!isFavorite); - } - - if (userStore.token) { - let url = `${ENDPOINTS.collection.favoriteCollections}/add/${props.id}?token=${userStore.token}`; if (isFavorite) { - url = `${ENDPOINTS.collection.favoriteCollections}/delete/${props.id}?token=${userStore.token}`; + fetch( + `${ENDPOINTS.collection.favoriteCollections}/delete/${props.id}?token=${userStore.token}` + ); + } else { + fetch( + `${ENDPOINTS.collection.favoriteCollections}/add/${props.id}?token=${userStore.token}` + ); } - _FavCol(url); } } async function _deleteCollection() { - async function _DelCol(url: string) { - setIsUpdating(true); - const tid = toast.loading("Удаляем коллекцию...", { - position: "bottom-center", - 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: "Ошибка удаления коллекции", - type: "error", - autoClose: 2500, - isLoading: false, - closeOnClick: true, - draggable: true, - }); - setIsUpdating(false); - return; - } - - toast.update(tid, { - render: `Коллекция удалена`, - type: "success", - autoClose: 2500, - isLoading: false, - closeOnClick: true, - draggable: true, - }); - - setIsUpdating(false); - router.push("/collections"); - } - - if (userStore.token) { - _DelCol( + if (userStore.user) { + fetch( `${ENDPOINTS.collection.delete}/${props.id}?token=${userStore.token}` ); + router.push("/collections"); } } return ( - diff --git a/app/components/CollectionLink/CollectionLink.tsx b/app/components/CollectionLink/CollectionLink.tsx index 4b45fe8..a3ae98e 100644 --- a/app/components/CollectionLink/CollectionLink.tsx +++ b/app/components/CollectionLink/CollectionLink.tsx @@ -15,7 +15,7 @@ export const CollectionLink = (props: any) => { {props.title { + const res = await fetch(url); + + if (!res.ok) { + const error = new Error( + `An error occurred while fetching the data. status: ${res.status}` + ); + error.message = await res.json(); + throw error; + } + + return res.json(); +}; + const CommentsAllModal = (props: { isOpen: boolean; setIsOpen: any; @@ -90,6 +103,7 @@ const CommentsAllModal = (props: { token: string | null; type?: "release" | "collection"; }) => { + const [isLoadingEnd, setIsLoadingEnd] = useState(false); const [currentRef, setCurrentRef] = useState(null); const modalRef = useCallback((ref) => { setCurrentRef(ref); @@ -113,7 +127,7 @@ const CommentsAllModal = (props: { const { data, error, isLoading, size, setSize } = useSWRInfinite( getKey, - useSWRfetcher, + fetcher, { initialSize: 2 } ); @@ -125,6 +139,7 @@ const CommentsAllModal = (props: { allReleases.push(...data[i].content); } setContent(allReleases); + setIsLoadingEnd(true); } }, [data]); @@ -155,7 +170,7 @@ const CommentsAllModal = (props: { Все комментарии

- всего: {isLoading ? "загрузка..." : data[0].total_count} + всего: {!isLoadingEnd ? "загрузка..." : data[0].total_count}

@@ -164,7 +179,7 @@ const CommentsAllModal = (props: { onScroll={handleScroll} ref={modalRef} > - {isLoading ? ( + {!isLoadingEnd ? ( ) : content ? ( content.map((comment: any) => ( diff --git a/app/components/CropModal/CropModal.tsx b/app/components/CropModal/CropModal.tsx index 7f42690..893da73 100644 --- a/app/components/CropModal/CropModal.tsx +++ b/app/components/CropModal/CropModal.tsx @@ -3,86 +3,56 @@ import Cropper, { ReactCropperElement } from "react-cropper"; import "cropperjs/dist/cropper.css"; import { Button, Modal } from "flowbite-react"; -type CropModalProps = { +type Props = { + src: string; + setSrc: (src: string) => void; + setTempSrc: (src: string) => void; isOpen: boolean; - isActionsDisabled: boolean; - selectedImage: any | null; - croppedImage: any | null; - setCropModalProps: (props: { - isOpen: boolean; - isActionsDisabled: boolean; - selectedImage: any | null; - croppedImage: any | null; - }) => void; - cropParams: { - guides?: boolean; - width?: number; - height?: number; - quality?: number; - aspectRatio?: number; - forceAspect?: boolean; - }; + setIsOpen: (isOpen: boolean) => void; + height: number; + width: number; + aspectRatio: number; + guides: boolean; + quality: number; + forceAspect?: boolean; }; -export const CropModal: React.FC = ({ - isOpen, - setCropModalProps, - cropParams, - selectedImage, - croppedImage, - isActionsDisabled, -}) => { +export const CropModal: React.FC = (props) => { const cropperRef = useRef(null); const getCropData = () => { if (typeof cropperRef.current?.cropper !== "undefined") { - const croppedImage = cropperRef.current?.cropper - .getCroppedCanvas({ - width: cropParams.width, - height: cropParams.height, - maxWidth: cropParams.width, - maxHeight: cropParams.height, - }) - .toDataURL( - "image/jpeg", - cropParams.quality || false ? cropParams.quality : 100 - ); - - setCropModalProps({ - isOpen: true, - isActionsDisabled: false, - selectedImage: selectedImage, - croppedImage: croppedImage, - }); + props.setSrc( + cropperRef.current?.cropper + .getCroppedCanvas({ + width: props.width, + height: props.height, + maxWidth: props.width, + maxHeight: props.height, + }) + .toDataURL("image/jpeg", props.quality) + ); + props.setTempSrc(""); } }; return ( { - setCropModalProps({ - isOpen: false, - isActionsDisabled: false, - selectedImage: null, - croppedImage: null, - }); - }} + show={props.isOpen} + onClose={() => props.setIsOpen(false)} size={"7xl"} > Обрезать изображение @@ -99,26 +69,23 @@ export const CropModal: React.FC = ({ diff --git a/app/components/Navbar/Navbar.tsx b/app/components/Navbar/Navbar.tsx new file mode 100644 index 0000000..20bce24 --- /dev/null +++ b/app/components/Navbar/Navbar.tsx @@ -0,0 +1,290 @@ +"use client"; +import Link from "next/link"; +import { usePathname } from "next/navigation"; +import { useUserStore } from "#/store/auth"; +import { Dropdown } from "flowbite-react"; +import { useState } from "react"; +import Image from "next/image"; +import { SettingsModal } from "#/components/SettingsModal/SettingsModal"; + +export const Navbar = () => { + const pathname = usePathname(); + const userStore: any = useUserStore((state) => state); + const [isSettingModalOpen, setIsSettingModalOpen] = useState(false); + + const navLinks = [ + { + id: 1, + icon: "material-symbols--home-outline", + iconActive: "material-symbols--home", + title: "Домашняя", + href: "/", + categoryHref: "/home", + withAuthOnly: false, + mobileMenu: false, + }, + { + id: 2, + icon: "material-symbols--search", + iconActive: "material-symbols--search", + title: "Поиск", + href: "/search", + withAuthOnly: false, + mobileMenu: false, + }, + { + id: 3, + icon: "material-symbols--bookmarks-outline", + iconActive: "material-symbols--bookmarks", + title: "Закладки", + href: "/bookmarks", + withAuthOnly: true, + mobileMenu: false, + }, + { + id: 4, + icon: "material-symbols--favorite-outline", + iconActive: "material-symbols--favorite", + title: "Избранное", + href: "/favorites", + withAuthOnly: true, + mobileMenu: true, + }, + { + id: 5, + icon: "material-symbols--collections-bookmark-outline", + iconActive: "material-symbols--collections-bookmark", + title: "Коллекции", + href: "/collections", + withAuthOnly: true, + mobileMenu: true, + }, + { + id: 6, + icon: "material-symbols--history", + iconActive: "material-symbols--history", + title: "История", + href: "/history", + withAuthOnly: true, + mobileMenu: true, + }, + ]; + + return ( + <> +
+
+ + {userStore.isAuth ? ( + <> +
+ + + + + + Профиль + + + {navLinks.map((link) => { + return ( + + + + + {link.title} + + + + ); + })} + { + setIsSettingModalOpen(true); + }} + className="flex items-center gap-1 text-sm md:text-base" + > + + Настройки + + { + userStore.logout(); + }} + className="flex items-center gap-1 text-sm md:text-base" + > + + Выйти + + +
+
+ + +

{userStore.user.login}

+ +
+ + ) : ( + ( +
+ + Меню +
+ )} + inline={true} + dismissOnClick={true} + theme={{ + arrowIcon: + "ml-1 w-4 h-4 [transform:rotateX(180deg)] sm:transform-none", + }} + > + + + + + Войти + + + + { + setIsSettingModalOpen(true); + }} + className="flex items-center gap-1 text-sm md:text-base" + > + + Настройки + +
+ )} +
+
+ + + ); +}; diff --git a/app/components/Navbar/NavbarUpdate.tsx b/app/components/Navbar/NavbarUpdate.tsx index 71b74d0..9ee4d76 100644 --- a/app/components/Navbar/NavbarUpdate.tsx +++ b/app/components/Navbar/NavbarUpdate.tsx @@ -87,8 +87,8 @@ export const Navbar = () => { return ( <>
-
-
+
+
{menuItems.map((item) => { return ( { ); })}
-
+
{!userStore.isAuth ? { + const router = useRouter(); const profileIdIsSmaller = props.my_profile_id < props.profile_id; - const theme = useThemeMode(); - + const [friendRequestDisabled, setFriendRequestDisabled] = useState(false); + const [blockRequestDisabled, setBlockRequestDisabled] = useState(false); const { mutate } = useSWRConfig(); - const [actionsDisabled, setActionsDisabled] = useState(false); - function _getFriendStatus() { const num = props.friendStatus; @@ -57,119 +54,53 @@ export const ProfileActions = (props: { } const FriendStatus = _getFriendStatus(); const isRequestedStatus = - FriendStatus != null ? - profileIdIsSmaller ? profileIdIsSmaller && FriendStatus != 0 - : !profileIdIsSmaller && FriendStatus == 2 - : null; + FriendStatus != null + ? profileIdIsSmaller + ? profileIdIsSmaller && FriendStatus != 0 + : !profileIdIsSmaller && FriendStatus == 2 + : null; // ^ This is some messed up shit - async function _addToFriends() { - setActionsDisabled(true); - - const tid = toast.loading("Добавляем в друзья...", { - position: "bottom-center", - hideProgressBar: true, - closeOnClick: false, - pauseOnHover: false, - draggable: false, - theme: theme.mode == "light" ? "light" : "dark", - }); - + function _addToFriends() { let url = `${ENDPOINTS.user.profile}/friend/request`; - FriendStatus == 1 ? (url += "/remove/") - : isRequestedStatus ? (url += "/remove/") - : (url += "/send/"); + setFriendRequestDisabled(true); + setBlockRequestDisabled(true); + + FriendStatus == 1 + ? (url += "/remove/") + : isRequestedStatus + ? (url += "/remove/") + : (url += "/send/"); + url += `${props.profile_id}?token=${props.token}`; - - const { data, error } = await tryCatchAPI(fetch(url)); - - if (error) { - toast.update(tid, { - render: - FriendStatus == 1 || isRequestedStatus ? - "Ошибка удаления из друзей" - : "Ошибка добавления в друзья", - type: "error", - autoClose: 2500, - isLoading: false, - closeOnClick: true, - draggable: true, - }); - setActionsDisabled(false); - return; - } - - mutate( - `${ENDPOINTS.user.profile}/${props.profile_id}?token=${props.token}` - ); - - toast.update(tid, { - render: - FriendStatus == 1 || isRequestedStatus ? - "Удален из друзей" - : "Добавлен в друзья", - type: "success", - autoClose: 2500, - isLoading: false, - closeOnClick: true, - draggable: true, + fetch(url).then((res) => { + mutate( + `${ENDPOINTS.user.profile}/${props.profile_id}?token=${props.token}` + ); + setTimeout(() => { + setBlockRequestDisabled(false); + setFriendRequestDisabled(false); + }, 100); }); - - setActionsDisabled(false); } - async function _addToBlocklist() { - setActionsDisabled(true); - - const tid = toast.loading( - !props.is_blocked ? - "Блокируем пользователя..." - : "Разблокируем пользователя...", - { - position: "bottom-center", - hideProgressBar: true, - closeOnClick: false, - pauseOnHover: false, - draggable: false, - theme: theme.mode == "light" ? "light" : "dark", - } - ); - + function _addToBlocklist() { let url = `${ENDPOINTS.user.profile}/blocklist`; + setBlockRequestDisabled(true); + setFriendRequestDisabled(true); + !props.is_blocked ? (url += "/add/") : (url += "/remove/"); + url += `${props.profile_id}?token=${props.token}`; - - const { data, error } = await tryCatchAPI(fetch(url)); - if (error) { - toast.update(tid, { - render: !props.is_blocked ? "Ошибка блокировки" : "Ошибка разблокировки", - type: "error", - autoClose: 2500, - isLoading: false, - closeOnClick: true, - draggable: true, - }); - setActionsDisabled(false); - return; - } - - mutate( - `${ENDPOINTS.user.profile}/${props.profile_id}?token=${props.token}` - ); - - toast.update(tid, { - render: - !props.is_blocked ? - "Пользователь заблокирован" - : "Пользователь разблокирован", - type: "success", - autoClose: 2500, - isLoading: false, - closeOnClick: true, - draggable: true, + fetch(url).then((res) => { + mutate( + `${ENDPOINTS.user.profile}/${props.profile_id}?token=${props.token}` + ); + setTimeout(() => { + setBlockRequestDisabled(false); + setFriendRequestDisabled(false); + }, 100); }); - - setActionsDisabled(false); } return ( @@ -178,14 +109,7 @@ export const ProfileActions = (props: {

Отправил(-а) вам заявку в друзья

)}
- {props.isMyProfile && ( - - )} + {props.isMyProfile && } {!props.isMyProfile && ( <> {(!props.isFriendRequestsDisallowed || @@ -194,25 +118,26 @@ export const ProfileActions = (props: { !props.is_me_blocked && !props.is_blocked && ( )}