diff --git a/src/equicordplugins/themeLibrary/components/LikesComponent.tsx b/src/equicordplugins/themeLibrary/components/LikesComponent.tsx new file mode 100644 index 00000000..d097e0dc --- /dev/null +++ b/src/equicordplugins/themeLibrary/components/LikesComponent.tsx @@ -0,0 +1,100 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2024 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { Logger } from "@utils/Logger"; +import { Button, useEffect, UserStore, useState } from "@webpack/common"; +import type { User } from "discord-types/general"; + +import type { Theme, ThemeLikeProps } from "../types"; +import { themeRequest } from "./ThemeTab"; + +const logger = new Logger("ThemeLibrary", "#e5c890"); + +const fetchLikes = async () => { + try { + const response = await themeRequest("/likes/get"); + const data = await response.json(); + return data; + } catch (err) { + logger.error(err); + } +}; + +export const LikesComponent = ({ themeId, likedThemes: initialLikedThemes }: { themeId: Theme["id"], likedThemes: ThemeLikeProps | undefined; }) => { + const [likesCount, setLikesCount] = useState(0); + const [likedThemes, setLikedThemes] = useState(initialLikedThemes); + + useEffect(() => { + const likes = getThemeLikes(themeId); + logger.debug("likes", likes, "for:", themeId); + setLikesCount(likes); + }, [likedThemes, themeId]); + + function getThemeLikes(themeId: Theme["id"]): number { + const themeLike = likedThemes?.likes.find(like => like.themeId === themeId as unknown as Number); + return themeLike ? themeLike.userIds.length : 0; + } + + const likeIcon = (isLiked: boolean) => ( + + ); + + const handleLikeClick = async (themeId: Theme["id"]) => { + const theme = likedThemes?.likes.find(like => like.themeId === themeId as unknown as Number); + const currentUser: User = UserStore.getCurrentUser(); + const hasLiked: boolean = theme?.userIds.includes(currentUser.id) ?? false; + const endpoint = hasLiked ? "/likes/remove" : "/likes/add"; + + try { + const response = await themeRequest(endpoint, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + cache: "no-store", + body: JSON.stringify({ + userId: currentUser.id, + themeId: themeId, + }), + }); + + if (!response.ok) return logger.error("Couldnt update likes, res:", response.statusText); + + const fetchLikes = async () => { + try { + const response = await themeRequest("/likes/get"); + const data = await response.json(); + setLikedThemes(data); + } catch (err) { + logger.error(err); + } + }; + + fetchLikes(); + // doing it locally isnt the best way probably, but it does the same + setLikesCount(likesCount + (hasLiked ? -1 : 1)); + } catch (err) { + logger.error(err); + } + }; + + const hasLiked = likedThemes?.likes.some(like => like.themeId === themeId as unknown as Number && like.userIds.includes(UserStore.getCurrentUser().id)) ?? false; + + return ( +
+ +
+ ); +}; diff --git a/src/equicordplugins/themeLibrary/components/ThemeTab.tsx b/src/equicordplugins/themeLibrary/components/ThemeTab.tsx index 3bb7ace2..6bab6ad7 100644 --- a/src/equicordplugins/themeLibrary/components/ThemeTab.tsx +++ b/src/equicordplugins/themeLibrary/components/ThemeTab.tsx @@ -15,6 +15,7 @@ import { ErrorCard } from "@components/ErrorCard"; import { OpenExternalIcon } from "@components/Icons"; import { SettingsTab, wrapTab } from "@components/VencordSettings/shared"; import { proxyLazy } from "@utils/lazy"; +import { Logger } from "@utils/Logger"; import { Margins } from "@utils/margins"; import { classes } from "@utils/misc"; import { openModal } from "@utils/modal"; @@ -23,7 +24,8 @@ import { Button, Card, FluxDispatcher, Forms, React, Select, showToast, TabBar, import { User } from "discord-types/general"; import { Constructor } from "type-fest"; -import { SearchStatus, TabItem, Theme } from "../types"; +import { SearchStatus, TabItem, Theme, ThemeLikeProps } from "../types"; +import { LikesComponent } from "./LikesComponent"; import { ThemeInfoModal } from "./ThemeInfoModal"; const cl = classNameFactory("vc-plugins-"); @@ -33,6 +35,8 @@ const TextAreaProps = findLazy(m => typeof m.textarea === "string"); const API_URL = "https://themes-delta.vercel.app/api"; +const logger = new Logger("ThemeLibrary", "#e5c890"); + async function fetchThemes(url: string): Promise { const response = await fetch(url); const data = await response.json(); @@ -47,7 +51,7 @@ async function fetchThemes(url: string): Promise { return themes.sort((a, b) => new Date(b.release_date).getTime() - new Date(a.release_date).getTime()); } -function API_TYPE(theme, returnAll?: boolean) { +function API_TYPE(theme: Theme | Object, returnAll?: boolean) { if (!theme) return; const settings = Settings.plugins.ThemeLibrary.domain ?? false; @@ -55,11 +59,12 @@ function API_TYPE(theme, returnAll?: boolean) { const url = settings ? "https://raw.githubusercontent.com/Faf4a/plugins/main/assets/meta.json" : `${API_URL}/themes`; return fetchThemes(url); } else { + // @ts-ignore return settings ? theme.source : `${API_URL}/${theme.name}`; } } -async function themeRequest(path: string, options: RequestInit = {}) { +export async function themeRequest(path: string, options: RequestInit = {}) { return fetch(API_URL + path, { ...options, headers: { @@ -100,7 +105,9 @@ function ThemeTab() { const [themes, setThemes] = useState([]); const [filteredThemes, setFilteredThemes] = useState([]); const [themeLinks, setThemeLinks] = useState(Vencord.Settings.themeLinks); + const [likedThemes, setLikedThemes] = useState(); const [searchValue, setSearchValue] = useState({ value: "", status: SearchStatus.ALL }); + const [hideWarningCard, setHideWarningCard] = useState(Settings.plugins.ThemeLibrary.hideWarningCard); const [loading, setLoading] = useState(true); const getUser = (id: string, username: string) => UserUtils.getUser(id) ?? makeDummyUser({ username, id }); @@ -126,22 +133,38 @@ function ThemeTab() { ); }; + const fetchLikes = async () => { + try { + const response = await themeRequest("/likes/get"); + const data = await response.json(); + return data; + } catch (err) { + logger.error(err); + } + }; + useEffect(() => { - Promise.resolve(API_TYPE({}, true)).then(themes => { - setThemes(themes); - setFilteredThemes(themes); - setLoading(false); - }).catch(err => { - console.error("Failed to load 'ThemeLibrary'", err); - setLoading(true); - }); + const fetchThemes = async () => { + try { + const themes = await API_TYPE({}, true); + // fetch likes + setThemes(themes); + const likes = await fetchLikes(); + setLikedThemes(likes); + setFilteredThemes(themes); + } catch (err) { + logger.error(err); + } finally { + setLoading(false); + } + }; + fetchThemes(); }, []); useEffect(() => { setThemeLinks(Vencord.Settings.themeLinks); }, [Vencord.Settings.themeLinks]); - useEffect(() => { const filteredThemes = themes.filter(themeFilter); setFilteredThemes(filteredThemes); @@ -160,15 +183,27 @@ function ThemeTab() { height: "100%", fontSize: "1.5em", color: "var(--text-muted)" - }}>Loading Themes... + }}>Loading themes... ) : ( <> - - Want your theme removed? - - If you want your theme(s) permanently removed, please open an issue on GitHub - - + {hideWarningCard ? null : ( + + Want your theme removed? + + If you want your theme(s) permanently removed, please open an issue on GitHub + + + + )}
Theme Info +