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 && (
-
@@ -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 —
-
toggleBlockedReview(review.id)}
>
@@ -619,22 +652,38 @@ export function GameDetailsContent() {
{review.user?.profileImageUrl && (
-

)}
-
review.user?.id && navigate(`/profile/${review.user.id}`)}
+ onClick={() =>
+ review.user?.id &&
+ navigate(`/profile/${review.user.id}`)
+ }
+ onKeyDown={(e) => {
+ if (e.key === 'Enter' || e.key === ' ') {
+ e.preventDefault();
+ review.user?.id &&
+ navigate(`/profile/${review.user.id}`);
+ }
+ }}
+ role="button"
+ tabIndex={0}
>
- {review.user?.displayName || 'Anonymous'}
+ {review.user?.displayName || "Anonymous"}
- {formatDistance(new Date(review.createdAt), new Date(), { addSuffix: true })}
+ {formatDistance(
+ new Date(review.createdAt),
+ new Date(),
+ { addSuffix: true }
+ )}
@@ -642,29 +691,35 @@ export function GameDetailsContent() {
{review.score}/10
-
- handleVoteReview(review.id, 'upvote')}
+
+ handleVoteReview(review.id, "upvote")
+ }
>
{review.upvotes || 0}
- handleVoteReview(review.id, 'downvote')}
+
+ handleVoteReview(review.id, "downvote")
+ }
>
{review.downvotes || 0}
{userDetails?.id === review.user?.id && (
-
handleDeleteReview(review.id)}
title={t("delete_review")}
@@ -672,20 +727,21 @@ export function GameDetailsContent() {
)}
- {review.isBlocked && visibleBlockedReviews.has(review.id) && (
-
toggleBlockedReview(review.id)}
- >
- Hide
-
- )}
+ {review.isBlocked &&
+ visibleBlockedReviews.has(review.id) && (
+
toggleBlockedReview(review.id)}
+ >
+ Hide
+
+ )}
>
)}
))}
-
+
{hasMoreReviews && !reviewsLoading && (
)}
-
+
{reviewsLoading && reviews.length > 0 && (
{t("loading_more_reviews")}
diff --git a/src/renderer/src/pages/game-details/game-details-skeleton.tsx b/src/renderer/src/pages/game-details/game-details-skeleton.tsx
index adaf4ab2..750f92e1 100644
--- a/src/renderer/src/pages/game-details/game-details-skeleton.tsx
+++ b/src/renderer/src/pages/game-details/game-details-skeleton.tsx
@@ -35,7 +35,11 @@ export function GameDetailsSkeleton() {
))}
-
+
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")}
-
+
{t("yes")}
-
+
{t("maybe_later")}
);
-}
\ 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
+}