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
+
+
+ If you plan on updating your theme / snippet frequently, consider using an @import
instead!
+