diff --git a/src/main/events/catalogue/check-game-review.ts b/src/main/events/catalogue/check-game-review.ts index c46ede07..5fa71e29 100644 --- a/src/main/events/catalogue/check-game-review.ts +++ b/src/main/events/catalogue/check-game-review.ts @@ -7,11 +7,9 @@ const checkGameReview = async ( shop: GameShop, objectId: string ) => { - return HydraApi.get( - `/games/${shop}/${objectId}/reviews/check`, - null, - { needsAuth: true } - ); + return HydraApi.get(`/games/${shop}/${objectId}/reviews/check`, null, { + needsAuth: true, + }); }; -registerEvent("checkGameReview", checkGameReview); \ No newline at end of file +registerEvent("checkGameReview", checkGameReview); diff --git a/src/main/events/catalogue/create-game-review.ts b/src/main/events/catalogue/create-game-review.ts index 7f29b639..57c74d45 100644 --- a/src/main/events/catalogue/create-game-review.ts +++ b/src/main/events/catalogue/create-game-review.ts @@ -15,4 +15,4 @@ const createGameReview = async ( }); }; -registerEvent("createGameReview", createGameReview); \ No newline at end of file +registerEvent("createGameReview", createGameReview); diff --git a/src/main/events/catalogue/delete-review.ts b/src/main/events/catalogue/delete-review.ts index 2048b3e7..e617a288 100644 --- a/src/main/events/catalogue/delete-review.ts +++ b/src/main/events/catalogue/delete-review.ts @@ -11,4 +11,4 @@ const deleteReview = async ( return HydraApi.delete(`/games/${shop}/${objectId}/reviews/${reviewId}`); }; -registerEvent("deleteReview", deleteReview); \ No newline at end of file +registerEvent("deleteReview", deleteReview); diff --git a/src/main/events/catalogue/get-game-reviews.ts b/src/main/events/catalogue/get-game-reviews.ts index d3c31780..8f29db3f 100644 --- a/src/main/events/catalogue/get-game-reviews.ts +++ b/src/main/events/catalogue/get-game-reviews.ts @@ -23,4 +23,4 @@ const getGameReviews = async ( ); }; -registerEvent("getGameReviews", getGameReviews); \ No newline at end of file +registerEvent("getGameReviews", getGameReviews); diff --git a/src/main/events/catalogue/vote-review.ts b/src/main/events/catalogue/vote-review.ts index b60062c3..a562eada 100644 --- a/src/main/events/catalogue/vote-review.ts +++ b/src/main/events/catalogue/vote-review.ts @@ -7,9 +7,12 @@ const voteReview = async ( shop: GameShop, objectId: string, reviewId: string, - voteType: 'upvote' | 'downvote' + voteType: "upvote" | "downvote" ) => { - return HydraApi.put(`/games/${shop}/${objectId}/reviews/${reviewId}/${voteType}`, {}); + return HydraApi.put( + `/games/${shop}/${objectId}/reviews/${reviewId}/${voteType}`, + {} + ); }; -registerEvent("voteReview", voteReview); \ No newline at end of file +registerEvent("voteReview", voteReview); diff --git a/src/preload/index.ts b/src/preload/index.ts index eda43369..7596fd11 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -82,7 +82,8 @@ contextBridge.exposeInMainWorld("electron", { objectId: string, reviewHtml: string, score: number - ) => ipcRenderer.invoke("createGameReview", shop, objectId, reviewHtml, score), + ) => + ipcRenderer.invoke("createGameReview", shop, objectId, reviewHtml, score), getGameReviews: ( shop: GameShop, objectId: string, @@ -96,11 +97,8 @@ contextBridge.exposeInMainWorld("electron", { reviewId: string, voteType: "upvote" | "downvote" ) => ipcRenderer.invoke("voteReview", shop, objectId, reviewId, voteType), - deleteReview: ( - shop: GameShop, - objectId: string, - reviewId: string - ) => ipcRenderer.invoke("deleteReview", shop, objectId, reviewId), + deleteReview: (shop: GameShop, objectId: string, reviewId: string) => + ipcRenderer.invoke("deleteReview", shop, objectId, reviewId), checkGameReview: (shop: GameShop, objectId: string) => ipcRenderer.invoke("checkGameReview", shop, objectId), onUpdateAchievements: ( diff --git a/src/renderer/src/components/game-card/game-card.tsx b/src/renderer/src/components/game-card/game-card.tsx index cb9a060c..15b5439b 100644 --- a/src/renderer/src/components/game-card/game-card.tsx +++ b/src/renderer/src/components/game-card/game-card.tsx @@ -110,9 +110,7 @@ export function GameCard({ game, ...props }: GameCardProps) { {stats?.averageScore && (
- - {stats.averageScore.toFixed(1)} - + {stats.averageScore.toFixed(1)}
)} diff --git a/src/renderer/src/declaration.d.ts b/src/renderer/src/declaration.d.ts index 752a1115..c1e06a89 100644 --- a/src/renderer/src/declaration.d.ts +++ b/src/renderer/src/declaration.d.ts @@ -110,7 +110,7 @@ declare global { shop: GameShop, objectId: string, reviewId: string, - voteType: 'upvote' | 'downvote' + voteType: "upvote" | "downvote" ) => Promise; deleteReview: ( shop: GameShop, diff --git a/src/renderer/src/pages/game-details/game-details-content.tsx b/src/renderer/src/pages/game-details/game-details-content.tsx index 48228e8e..0c53177f 100644 --- a/src/renderer/src/pages/game-details/game-details-content.tsx +++ b/src/renderer/src/pages/game-details/game-details-content.tsx @@ -2,11 +2,11 @@ import { useContext, useEffect, useMemo, useRef, useState } from "react"; import { PencilIcon, TrashIcon, ClockIcon } from "@primer/octicons-react"; import { ThumbsUp, ThumbsDown } from "lucide-react"; import { useNavigate } from "react-router-dom"; -import { useEditor, EditorContent } from '@tiptap/react'; -import StarterKit from '@tiptap/starter-kit'; -import Bold from '@tiptap/extension-bold'; -import Italic from '@tiptap/extension-italic'; -import Underline from '@tiptap/extension-underline'; +import { useEditor, EditorContent } from "@tiptap/react"; +import StarterKit from "@tiptap/starter-kit"; +import Bold from "@tiptap/extension-bold"; +import Italic from "@tiptap/extension-italic"; +import Underline from "@tiptap/extension-underline"; import type { GameReview } from "@types"; import { HeroPanel } from "./hero"; @@ -32,8 +32,14 @@ export function GameDetailsContent() { const { t } = useTranslation("game_details"); - const { objectId, shopDetails, game, hasNSFWContentBlocked, updateGame, shop } = - useContext(gameDetailsContext); + const { + objectId, + shopDetails, + game, + hasNSFWContentBlocked, + updateGame, + shop, + } = useContext(gameDetailsContext); const { showHydraCloudModal } = useSubscription(); @@ -93,7 +99,7 @@ export function GameDetailsContent() { const [backdropOpacity, setBackdropOpacity] = useState(1); const [showEditGameModal, setShowEditGameModal] = useState(false); const [isDescriptionExpanded, setIsDescriptionExpanded] = useState(false); - + // Reviews state management const [reviews, setReviews] = useState([]); const [reviewsLoading, setReviewsLoading] = useState(false); @@ -102,10 +108,12 @@ export function GameDetailsContent() { const [reviewsSortBy, setReviewsSortBy] = useState("newest"); const [reviewsPage, setReviewsPage] = useState(0); const [hasMoreReviews, setHasMoreReviews] = useState(true); - const [visibleBlockedReviews, setVisibleBlockedReviews] = useState>(new Set()); + const [visibleBlockedReviews, setVisibleBlockedReviews] = useState< + Set + >(new Set()); const [totalReviewCount, setTotalReviewCount] = useState(0); const [showReviewForm, setShowReviewForm] = useState(false); - + // Review prompt banner state const [showReviewPrompt, setShowReviewPrompt] = useState(false); const [hasUserReviewed, setHasUserReviewed] = useState(false); @@ -113,17 +121,12 @@ export function GameDetailsContent() { // Tiptap editor for review input const editor = useEditor({ - extensions: [ - StarterKit, - Bold, - Italic, - Underline, - ], - content: '', + extensions: [StarterKit, Bold, Italic, Underline], + content: "", editorProps: { attributes: { - class: 'game-details__review-editor', - 'data-placeholder': t("write_review_placeholder"), + class: "game-details__review-editor", + "data-placeholder": t("write_review_placeholder"), }, }, }); @@ -164,15 +167,19 @@ export function GameDetailsContent() { // Reviews functions const checkUserReview = async () => { if (!objectId || !userDetails) return; - + setReviewCheckLoading(true); try { const response = await window.electron.checkGameReview(shop, objectId); const hasReviewed = (response as any)?.hasReviewed || false; setHasUserReviewed(hasReviewed); - + // Show prompt only if user hasn't reviewed and has played the game - if (!hasReviewed && game?.playTimeInMilliseconds && game.playTimeInMilliseconds > 0) { + if ( + !hasReviewed && + game?.playTimeInMilliseconds && + game.playTimeInMilliseconds > 0 + ) { setShowReviewPrompt(true); } } catch (error) { @@ -184,7 +191,7 @@ export function GameDetailsContent() { const loadReviews = async (reset = false) => { if (!objectId) return; - + setReviewsLoading(true); try { const skip = reset ? 0 : reviewsPage * 20; @@ -195,19 +202,19 @@ export function GameDetailsContent() { skip, reviewsSortBy ); - + // Handle the response structure: { totalCount: number, reviews: Review[] } const reviewsData = (response as any)?.reviews || []; const reviewCount = (response as any)?.totalCount || 0; - + if (reset) { setReviews(reviewsData); setReviewsPage(0); setTotalReviewCount(reviewCount); } else { - setReviews(prev => [...prev, ...reviewsData]); + setReviews((prev) => [...prev, ...reviewsData]); } - + setHasMoreReviews(reviewsData.length === 20); } catch (error) { console.error("Failed to load reviews:", error); @@ -216,9 +223,12 @@ export function GameDetailsContent() { } }; - const handleVoteReview = async (reviewId: string, voteType: 'upvote' | 'downvote') => { + const handleVoteReview = async ( + reviewId: string, + voteType: "upvote" | "downvote" + ) => { if (!objectId) return; - + try { await window.electron.voteReview(shop, objectId, reviewId, voteType); // Reload reviews to get updated vote counts @@ -230,13 +240,13 @@ export function GameDetailsContent() { const handleDeleteReview = async (reviewId: string) => { if (!objectId) return; - + try { await window.electron.deleteReview(shop, objectId, reviewId); // Reload reviews after deletion loadReviews(true); } catch (error) { - console.error('Failed to delete review:', error); + console.error("Failed to delete review:", error); } }; @@ -244,17 +254,17 @@ export function GameDetailsContent() { console.log("handleSubmitReview called"); console.log("game:", game); console.log("objectId:", objectId); - - const reviewHtml = editor?.getHTML() || ''; + + const reviewHtml = editor?.getHTML() || ""; console.log("reviewHtml:", reviewHtml); console.log("reviewScore:", reviewScore); console.log("submittingReview:", submittingReview); - + if (!objectId || !reviewHtml.trim() || submittingReview) { console.log("Early return - validation failed"); return; } - + console.log("Starting review submission..."); setSubmittingReview(true); try { @@ -265,7 +275,7 @@ export function GameDetailsContent() { reviewHtml, reviewScore ); - + console.log("Review submitted successfully"); editor?.commands.clearContent(); setReviewScore(5); @@ -285,14 +295,16 @@ export function GameDetailsContent() { const handleReviewPromptYes = () => { setShowReviewPrompt(false); setShowReviewForm(true); - + // Scroll to review form setTimeout(() => { - const reviewFormElement = document.querySelector('.game-details__review-form'); + const reviewFormElement = document.querySelector( + ".game-details__review-form" + ); if (reviewFormElement) { - reviewFormElement.scrollIntoView({ - behavior: 'smooth', - block: 'start' + reviewFormElement.scrollIntoView({ + behavior: "smooth", + block: "start", }); } }, 100); @@ -310,7 +322,7 @@ export function GameDetailsContent() { }; const toggleBlockedReview = (reviewId: string) => { - setVisibleBlockedReviews(prev => { + setVisibleBlockedReviews((prev) => { const newSet = new Set(prev); if (newSet.has(reviewId)) { newSet.delete(reviewId); @@ -323,7 +335,7 @@ export function GameDetailsContent() { const loadMoreReviews = () => { if (!reviewsLoading && hasMoreReviews) { - setReviewsPage(prev => prev + 1); + setReviewsPage((prev) => prev + 1); loadReviews(false); } }; @@ -457,13 +469,17 @@ export function GameDetailsContent() {
{/* Review Prompt Banner */} - {showReviewPrompt && userDetails && game?.playTimeInMilliseconds && !hasUserReviewed && !reviewCheckLoading && ( - - )} - + {showReviewPrompt && + userDetails && + game?.playTimeInMilliseconds && + !hasUserReviewed && + !reviewCheckLoading && ( + + )} + @@ -472,10 +488,12 @@ export function GameDetailsContent() { __html: aboutTheGame, }} className={`game-details__description ${ - isDescriptionExpanded ? 'game-details__description--expanded' : 'game-details__description--collapsed' + isDescriptionExpanded + ? "game-details__description--expanded" + : "game-details__description--collapsed" }`} /> - + {aboutTheGame && aboutTheGame.length > 500 && (
- +
- +
- +
@@ -578,7 +610,7 @@ export function GameDetailsContent() { {totalReviewCount} - @@ -589,7 +621,7 @@ export function GameDetailsContent() { {t("loading_reviews")} )} - + {!reviewsLoading && reviews.length === 0 && (
📝
@@ -601,13 +633,14 @@ export function GameDetailsContent() {

)} - + {reviews.map((review, index) => (
- {review.isBlocked && !visibleBlockedReviews.has(review.id) ? ( + {review.isBlocked && + !visibleBlockedReviews.has(review.id) ? (
- Review from blocked user — -
-
- -
{userDetails?.id === review.user?.id && ( - )} - {review.isBlocked && visibleBlockedReviews.has(review.id) && ( - - )} + {review.isBlocked && + visibleBlockedReviews.has(review.id) && ( + + )}
)}
))} - + {hasMoreReviews && !reviewsLoading && (
diff --git a/src/renderer/src/pages/game-details/game-details.scss b/src/renderer/src/pages/game-details/game-details.scss index b82bd6b1..f6b724ab 100644 --- a/src/renderer/src/pages/game-details/game-details.scss +++ b/src/renderer/src/pages/game-details/game-details.scss @@ -86,7 +86,9 @@ $hero-height: 300px; font-size: globals.$body-font-size; font-family: inherit; cursor: pointer; - transition: border-color 0.2s ease, background-color 0.2s ease; + transition: + border-color 0.2s ease, + background-color 0.2s ease; &:focus { outline: none; @@ -126,7 +128,9 @@ $hero-height: 300px; font-size: globals.$body-font-size; font-family: inherit; cursor: pointer; - transition: border-color 0.2s ease, background-color 0.2s ease; + transition: + border-color 0.2s ease, + background-color 0.2s ease; &:focus { outline: none; @@ -148,7 +152,8 @@ $hero-height: 300px; background-color: rgba(255, 255, 255, 0.05); border: 1px solid globals.$border-color; color: globals.$body-color; - padding: calc(globals.$spacing-unit * 0.75) calc(globals.$spacing-unit * 1.5); + padding: calc(globals.$spacing-unit * 0.75) + calc(globals.$spacing-unit * 1.5); border-radius: 6px; cursor: pointer; font-size: globals.$small-font-size; @@ -631,9 +636,9 @@ $hero-height: 300px; max-height: 300px; overflow: hidden; position: relative; - + &::after { - content: ''; + content: ""; position: absolute; bottom: 0; left: 0; @@ -677,7 +682,8 @@ $hero-height: 300px; background: none; border: 1px solid globals.$border-color; color: globals.$body-color; - padding: calc(globals.$spacing-unit * 0.75) calc(globals.$spacing-unit * 1.5); + padding: calc(globals.$spacing-unit * 0.75) + calc(globals.$spacing-unit * 1.5); border-radius: 4px; cursor: pointer; font-size: globals.$body-font-size; @@ -883,8 +889,13 @@ $hero-height: 300px; display: flex; align-items: center; gap: calc(globals.$spacing-unit * 0.5); - padding: calc(globals.$spacing-unit * 0.75) calc(globals.$spacing-unit * 1.5); - background: linear-gradient(135deg, globals.$brand-teal, globals.$brand-blue); + padding: calc(globals.$spacing-unit * 0.75) + calc(globals.$spacing-unit * 1.5); + background: linear-gradient( + 135deg, + globals.$brand-teal, + globals.$brand-blue + ); color: white; border: none; border-radius: 8px; @@ -980,7 +991,9 @@ $hero-height: 300px; font-family: inherit; line-height: 1.5; min-height: 100px; - transition: border-color 0.2s ease, background-color 0.2s ease; + transition: + border-color 0.2s ease, + background-color 0.2s ease; &:focus-within { outline: none; @@ -995,7 +1008,7 @@ $hero-height: 300px; .ProseMirror { outline: none; min-height: 80px; - + &:empty:before { content: attr(data-placeholder); color: rgba(208, 209, 215, 0.6); @@ -1004,7 +1017,7 @@ $hero-height: 300px; p { margin: 0 0 8px 0; - + &:last-child { margin-bottom: 0; } diff --git a/src/renderer/src/pages/game-details/review-prompt-banner.scss b/src/renderer/src/pages/game-details/review-prompt-banner.scss index 28ba1e47..b8f7557b 100644 --- a/src/renderer/src/pages/game-details/review-prompt-banner.scss +++ b/src/renderer/src/pages/game-details/review-prompt-banner.scss @@ -43,4 +43,4 @@ gap: globals.$spacing-unit; align-items: center; } -} \ No newline at end of file +} diff --git a/src/renderer/src/pages/game-details/review-prompt-banner.tsx b/src/renderer/src/pages/game-details/review-prompt-banner.tsx index 87c1b170..7bd96613 100644 --- a/src/renderer/src/pages/game-details/review-prompt-banner.tsx +++ b/src/renderer/src/pages/game-details/review-prompt-banner.tsx @@ -18,27 +18,21 @@ export function ReviewPromptBanner({
- You've seemed to enjoy this game + You've seemed to enjoy this game {t("would_you_recommend_this_game")}
- -
); -} \ No newline at end of file +} diff --git a/src/renderer/src/pages/game-details/review-sort-options.scss b/src/renderer/src/pages/game-details/review-sort-options.scss index e982cb24..5b374728 100644 --- a/src/renderer/src/pages/game-details/review-sort-options.scss +++ b/src/renderer/src/pages/game-details/review-sort-options.scss @@ -50,7 +50,7 @@ span { display: inline-block; - + @media (max-width: 480px) { display: none; } @@ -69,4 +69,4 @@ display: none; } } -} \ No newline at end of file +} diff --git a/src/renderer/src/pages/game-details/review-sort-options.tsx b/src/renderer/src/pages/game-details/review-sort-options.tsx index 5ec25c31..858faefd 100644 --- a/src/renderer/src/pages/game-details/review-sort-options.tsx +++ b/src/renderer/src/pages/game-details/review-sort-options.tsx @@ -1,15 +1,28 @@ -import { CalendarIcon, StarIcon, ThumbsupIcon, ClockIcon } from "@primer/octicons-react"; +import { + CalendarIcon, + StarIcon, + ThumbsupIcon, + ClockIcon, +} from "@primer/octicons-react"; import { useTranslation } from "react-i18next"; import "./review-sort-options.scss"; -type ReviewSortOption = "newest" | "oldest" | "score_high" | "score_low" | "most_voted"; +type ReviewSortOption = + | "newest" + | "oldest" + | "score_high" + | "score_low" + | "most_voted"; interface ReviewSortOptionsProps { sortBy: ReviewSortOption; onSortChange: (sortBy: ReviewSortOption) => void; } -export function ReviewSortOptions({ sortBy, onSortChange }: ReviewSortOptionsProps) { +export function ReviewSortOptions({ + sortBy, + onSortChange, +}: ReviewSortOptionsProps) { const { t } = useTranslation("game_details"); return ( @@ -57,4 +70,4 @@ export function ReviewSortOptions({ sortBy, onSortChange }: ReviewSortOptionsPro ); -} \ No newline at end of file +}