From b67bb009fcf6c47be975428f3dbe5befbce0fb73 Mon Sep 17 00:00:00 2001 From: Vendicated Date: Sun, 15 Mar 2026 02:00:53 +0100 Subject: [PATCH] ReviewDB: make profile review button like game collection button --- .../reviewDB/components/ReviewModal.tsx | 4 +- .../reviewDB/components/ReviewsView.tsx | 4 +- src/plugins/reviewDB/index.tsx | 77 ++++++++++++++----- src/plugins/reviewDB/reviewDbApi.ts | 19 ++--- 4 files changed, 71 insertions(+), 33 deletions(-) diff --git a/src/plugins/reviewDB/components/ReviewModal.tsx b/src/plugins/reviewDB/components/ReviewModal.tsx index 9cc1e577d..c4da56642 100644 --- a/src/plugins/reviewDB/components/ReviewModal.tsx +++ b/src/plugins/reviewDB/components/ReviewModal.tsx @@ -19,7 +19,7 @@ import ErrorBoundary from "@components/ErrorBoundary"; import { Auth } from "@plugins/reviewDB/auth"; import { ReviewType } from "@plugins/reviewDB/entities"; -import { Response, REVIEWS_PER_PAGE } from "@plugins/reviewDB/reviewDbApi"; +import { REVIEWS_PER_PAGE, UserReviewsData } from "@plugins/reviewDB/reviewDbApi"; import { cl } from "@plugins/reviewDB/utils"; import { ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalRoot, ModalSize, openModal } from "@utils/modal"; import { useForceUpdater } from "@utils/react"; @@ -29,7 +29,7 @@ import ReviewComponent from "./ReviewComponent"; import ReviewsView, { ReviewsInputComponent } from "./ReviewsView"; function Modal({ modalProps, modalKey, discordId, name, type }: { modalProps: any; modalKey: string, discordId: string; name: string; type: ReviewType; }) { - const [data, setData] = useState(); + const [data, setData] = useState(); const [signal, refetch] = useForceUpdater(true); const [page, setPage] = useState(1); diff --git a/src/plugins/reviewDB/components/ReviewsView.tsx b/src/plugins/reviewDB/components/ReviewsView.tsx index 675bfe614..558db9621 100644 --- a/src/plugins/reviewDB/components/ReviewsView.tsx +++ b/src/plugins/reviewDB/components/ReviewsView.tsx @@ -18,7 +18,7 @@ import { Auth, authorize } from "@plugins/reviewDB/auth"; import { Review, ReviewType } from "@plugins/reviewDB/entities"; -import { addReview, getReviews, Response, REVIEWS_PER_PAGE } from "@plugins/reviewDB/reviewDbApi"; +import { addReview, getReviews, REVIEWS_PER_PAGE, UserReviewsData } from "@plugins/reviewDB/reviewDbApi"; import { settings } from "@plugins/reviewDB/settings"; import { cl, showToast } from "@plugins/reviewDB/utils"; import { useAwaiter, useForceUpdater } from "@utils/react"; @@ -39,7 +39,7 @@ interface UserProps { } interface Props extends UserProps { - onFetchReviews(data: Response): void; + onFetchReviews(data: UserReviewsData): void; refetchSignal?: unknown; showInput?: boolean; page?: number; diff --git a/src/plugins/reviewDB/index.tsx b/src/plugins/reviewDB/index.tsx index 1b395f69c..51e0a3f3e 100644 --- a/src/plugins/reviewDB/index.tsx +++ b/src/plugins/reviewDB/index.tsx @@ -20,9 +20,12 @@ import "./style.css"; import { NavContextMenuPatchCallback } from "@api/ContextMenu"; import ErrorBoundary from "@components/ErrorBoundary"; -import { NotesIcon, OpenExternalIcon } from "@components/Icons"; -import { TooltipContainer } from "@components/TooltipContainer"; +import { OpenExternalIcon } from "@components/Icons"; +import { Paragraph } from "@components/Paragraph"; +import { Span } from "@components/Span"; import { Devs } from "@utils/constants"; +import { classes } from "@utils/misc"; +import { useAwaiter } from "@utils/react"; import definePlugin from "@utils/types"; import { Guild, User } from "@vencord/discord-types"; import { findCssClassesLazy } from "@webpack"; @@ -31,11 +34,14 @@ import { Alerts, Clickable, Menu, Parser } from "@webpack/common"; import { Auth, initAuth, updateAuth } from "./auth"; import { openReviewsModal } from "./components/ReviewModal"; import { NotificationType, ReviewType } from "./entities"; -import { getCurrentUserInfo, readNotification } from "./reviewDbApi"; +import { getCurrentUserInfo, getReviews, readNotification } from "./reviewDbApi"; import { settings } from "./settings"; import { showToast } from "./utils"; -const BannerButtonClasses = findCssClassesLazy("bannerButton"); +const DMSideBarClasses = findCssClassesLazy("widgetPreviews"); +const ProfileCardClasses = findCssClassesLazy("cardsList", "firstCardContainer", "card", "container"); +const ProfileCardContainerClasses = findCssClassesLazy("innerContainer", "icons", "icon", "displayCount", "displayCountText", "displayCountTextColor", "breadcrumb"); +const ProfileCardOverlayClasses = findCssClassesLazy("overlay", "isPrivate", "outer"); const guildPopoutPatch: NavContextMenuPatchCallback = (children, { guild }: { guild: Guild, onClose(): void; }) => { if (!guild) return; @@ -77,17 +83,19 @@ export default definePlugin({ patches: [ { - find: ".POPOUT,user:", + // DM profile sidebar + find: ".SIDEBAR,disableToolbar:", replacement: { - match: /children:\[(?=[^[]+?shouldShowTooltip:)/, - replace: "$&$self.BiteSizeReviewsButton({user:arguments[0].user})," + match: /user:(\i),widgets:.{0,100}?\}\),/, + replace: "$&$self.renderProfileComponent({user:$1,isSideBar:true})," } }, { - find: ".SIDEBAR,disableToolbar:", + // User popout + find: /\.POPOUT,onClose:\i}\),nicknameIcons:.+?\.isProvisional/, replacement: { - match: /children:\[(?=[^[]+?\.SIDEBAR}\),\i\.bot)/, - replace: "$&$self.BiteSizeReviewsButton({user:arguments[0].user})," + match: /user:(\i),widgets:.{0,100}?\}\),/, + replace: "$&$self.renderProfileComponent({user:$1})," } } ], @@ -146,16 +154,45 @@ export default definePlugin({ }, 4000); }, - BiteSizeReviewsButton: ErrorBoundary.wrap(({ user }: { user: User; }) => { - return ( - - openReviewsModal(user.id, user.username, ReviewType.User)} - className={BannerButtonClasses.bannerButton} - > - - - + renderProfileComponent: ErrorBoundary.wrap(({ user, isSideBar = false }: { user: User; isSideBar?: boolean; }) => { + const [reviewData] = useAwaiter(() => getReviews(user.id), { deps: [user.id], fallbackValue: null }); + + // Discord are masters at using a crap ton of html elements and css classes to create a simple ui that could have + // been made with less than half of the number of elements, so we have to do this insanity to replicate their ui + const reviewsSection = ( +
+
    +
  • + openReviewsModal(user.id, user.username, ReviewType.User)}> +
    + User Reviews + {!!reviewData?.reviewCount && ( +
    + {reviewData.reviews.slice(0, 4).map((review, idx) => { + const showCount = idx === 3 && reviewData.reviewCount > 4; + + return ( +
    + {review.sender.username} + {showCount && ( +
    + +{reviewData.reviewCount - 3} +
    + )} +
    + ); + })} +
    + )} +
    +
    +
  • +
+
); + + return isSideBar + ?
{reviewsSection}
+ : reviewsSection; }, { noop: true }) }); diff --git a/src/plugins/reviewDB/reviewDbApi.ts b/src/plugins/reviewDB/reviewDbApi.ts index 3fe6dd061..e899216a3 100644 --- a/src/plugins/reviewDB/reviewDbApi.ts +++ b/src/plugins/reviewDB/reviewDbApi.ts @@ -27,7 +27,7 @@ const API_URL = "https://manti.vendicated.dev/api/reviewdb"; export const REVIEWS_PER_PAGE = 50; -export interface Response { +export interface UserReviewsData { message: string; reviews: Review[]; updated: boolean; @@ -47,18 +47,19 @@ async function rdbRequest(path: string, options: RequestInit = {}) { }); } -export async function getReviews(id: string, offset = 0): Promise { +export async function getReviews(id: string, offset = 0): Promise { let flags = 0; if (!settings.store.showWarning) flags |= WarningFlag; const params = new URLSearchParams({ flags: String(flags), - offset: String(offset) + offset: String(offset), + limit: "4" }); const req = await fetch(`${API_URL}/users/${id}/reviews?${params}`); const res = (req.ok) - ? await req.json() as Response + ? await req.json() as UserReviewsData : { message: req.status === 429 ? "You are sending requests too fast. Wait a few seconds and try again." : "An Error occured while fetching reviews. Please try again later.", reviews: [], @@ -93,7 +94,7 @@ export async function getReviews(id: string, offset = 0): Promise { return res; } -export async function addReview(review: any): Promise { +export async function addReview(review: any): Promise { const token = await getToken(); if (!token) { @@ -109,13 +110,13 @@ export async function addReview(review: any): Promise { "Content-Type": "application/json", } }).then(async r => { - const data = await r.json() as Response; + const data = await r.json() as UserReviewsData; showToast(data.message); return r.ok ? data : null; }); } -export async function deleteReview(id: number): Promise { +export async function deleteReview(id: number): Promise { return await rdbRequest(`/users/${id}/reviews`, { method: "DELETE", headers: { @@ -126,7 +127,7 @@ export async function deleteReview(id: number): Promise { reviewid: id }) }).then(async r => { - const data = await r.json() as Response; + const data = await r.json() as UserReviewsData; showToast(data.message); return r.ok ? data : null; }); @@ -142,7 +143,7 @@ export async function reportReview(id: number) { body: JSON.stringify({ reviewid: id, }) - }).then(r => r.json()) as Response; + }).then(r => r.json()) as UserReviewsData; showToast(res.message); }