Feat: Rating score display redesign, Rating choosing redesign, added avg rating on the game page

This commit is contained in:
Moyasee
2025-10-04 20:12:01 +03:00
parent 52d3750acc
commit 1f05dc8f78
6 changed files with 95 additions and 16 deletions

View File

@@ -200,6 +200,7 @@
"stats": "Stats",
"download_count": "Downloads",
"player_count": "Active players",
"rating_count": "Rating",
"download_error": "This download option is not available",
"download": "Download",
"executable_path_in_use": "Executable already in use by \"{{game}}\"",
@@ -220,6 +221,8 @@
"sort_lowest_score": "Lowest Score",
"sort_most_voted": "Most Voted",
"rating": "Rating",
"rating_stats": "Rating",
"select_rating": "Select Rating",
"submit_review": "Submit Review",
"submitting": "Submitting...",
"loading_reviews": "Loading reviews...",

View File

@@ -24,6 +24,14 @@ import { useUserDetails, useLibrary, useDate } from "@renderer/hooks";
import { useSubscription } from "@renderer/hooks/use-subscription";
import "./game-details.scss";
// Helper function to get score color class
const getScoreColorClass = (score: number): string => {
if (score >= 0 && score <= 3) return "game-details__review-score--red";
if (score >= 4 && score <= 6) return "game-details__review-score--yellow";
if (score >= 7 && score <= 10) return "game-details__review-score--green";
return "";
};
export function GameDetailsContent() {
const heroRef = useRef<HTMLDivElement | null>(null);
const navigate = useNavigate();
@@ -103,7 +111,7 @@ export function GameDetailsContent() {
// Reviews state management
const [reviews, setReviews] = useState<GameReview[]>([]);
const [reviewsLoading, setReviewsLoading] = useState(false);
const [reviewScore, setReviewScore] = useState(5);
const [reviewScore, setReviewScore] = useState<number | null>(null);
const [submittingReview, setSubmittingReview] = useState(false);
const [reviewCharCount, setReviewCharCount] = useState(0);
const MAX_REVIEW_CHARS = 1000;
@@ -302,6 +310,7 @@ export function GameDetailsContent() {
if (
!objectId ||
!reviewHtml.trim() ||
reviewScore === null ||
submittingReview ||
reviewCharCount > MAX_REVIEW_CHARS
) {
@@ -322,7 +331,7 @@ export function GameDetailsContent() {
console.log("Review submitted successfully");
editor?.commands.clearContent();
setReviewScore(5);
setReviewScore(null);
await loadReviews(true); // Reload reviews after submission
setShowReviewForm(false); // Hide the review form after successful submission
setShowReviewPrompt(false); // Hide the prompt banner
@@ -606,10 +615,14 @@ export function GameDetailsContent() {
</span>
</div>
</div>
<EditorContent
editor={editor}
<div
className="game-details__review-input"
/>
onClick={() => editor?.commands.focus()}
>
<EditorContent
editor={editor}
/>
</div>
</div>
<div className="game-details__review-form-bottom">
@@ -618,12 +631,23 @@ export function GameDetailsContent() {
{t("rating")}
</label>
<select
className="game-details__review-score-select"
value={reviewScore}
className={`game-details__review-score-select ${
reviewScore
? reviewScore <= 3
? 'game-details__review-score-select--red'
: reviewScore <= 7
? 'game-details__review-score-select--yellow'
: 'game-details__review-score-select--green'
: ''
}`}
value={reviewScore || ""}
onChange={(e) =>
setReviewScore(Number(e.target.value))
setReviewScore(e.target.value ? Number(e.target.value) : null)
}
>
<option value="" disabled>
{t("select_rating")}
</option>
<option value={1}>1/10</option>
<option value={2}>2/10</option>
<option value={3}>3/10</option>
@@ -642,6 +666,7 @@ export function GameDetailsContent() {
onClick={handleSubmitReview}
disabled={
!editor?.getHTML().trim() ||
reviewScore === null ||
submittingReview ||
reviewCharCount > MAX_REVIEW_CHARS
}
@@ -737,7 +762,7 @@ export function GameDetailsContent() {
</div>
</div>
</div>
<div className="game-details__review-score">
<div className={`game-details__review-score ${getScoreColorClass(review.score)}`}>
{review.score}/10
</div>
</div>

View File

@@ -75,10 +75,30 @@ $hero-height: 300px;
padding: 6px 12px;
font-size: 14px;
cursor: pointer;
transition: border-color 0.2s ease, background-color 0.2s ease;
&:focus {
outline: none;
border-color: #0078d4;
}
&--red {
border-color: #e74c3c;
background-color: rgba(231, 76, 60, 0.1);
}
&--yellow {
border-color: #f39c12;
background-color: rgba(243, 156, 18, 0.1);
}
&--green {
border-color: #27ae60;
background-color: rgba(39, 174, 96, 0.1);
}
option {
background-color: #2a2a2a;
color: #ffffff;
}
}
@@ -373,6 +393,25 @@ $hero-height: 300px;
font-size: globals.$small-font-size;
font-weight: 600;
border: 1px solid rgba(255, 255, 255, 0.15);
// Color variants based on score
&--red {
background: rgba(239, 68, 68, 0.2);
color: #fca5a5;
border-color: rgba(239, 68, 68, 0.4);
}
&--yellow {
background: rgba(245, 158, 11, 0.2);
color: #fcd34d;
border-color: rgba(245, 158, 11, 0.4);
}
&--green {
background: rgba(34, 197, 94, 0.2);
color: #86efac;
border-color: rgba(34, 197, 94, 0.4);
}
}
&__review-date {
@@ -953,12 +992,16 @@ $hero-height: 300px;
&__review-input {
min-height: 120px;
padding: 12px;
cursor: text;
.ProseMirror {
outline: none;
color: #ffffff;
font-size: 14px;
line-height: 1.5;
min-height: 96px; // 120px - 24px padding
width: 100%;
cursor: text;
&:focus {
outline: none;

View File

@@ -106,7 +106,7 @@
.stats {
&__section {
display: flex;
gap: calc(globals.$spacing-unit * 2);
gap: calc(globals.$spacing-unit * 1);
padding: calc(globals.$spacing-unit * 2);
justify-content: space-between;
transition: max-height ease 0.5s;
@@ -116,9 +116,6 @@
flex-direction: column;
}
@media (min-width: 1280px) {
flex-direction: row;
}
}
&__category-title {

View File

@@ -14,6 +14,7 @@ import {
DownloadIcon,
LockIcon,
PeopleIcon,
StarIcon,
} from "@primer/octicons-react";
import { HowLongToBeatSection } from "./how-long-to-beat-section";
import { howLongToBeatEntriesTable } from "@renderer/dexie";
@@ -225,6 +226,16 @@ export function Sidebar() {
</p>
<p>{numberFormatter.format(stats?.playerCount)}</p>
</div>
{stats?.averageScore && (
<div className="stats__category">
<p className="stats__category-title">
<StarIcon size={18} />
{t("rating_count")}
</p>
<p>{stats.averageScore.toFixed(1)}/10</p>
</div>
)}
</div>
</SidebarSection>
)}

View File

@@ -24,7 +24,7 @@ function decodeHtmlEntities(text: string): string {
function removeHtmlTags(html: string): string {
let result = "";
let inTag = false;
for (const char of html) {
if (char === "<") {
inTag = true;
@@ -34,7 +34,7 @@ function removeHtmlTags(html: string): string {
result += char;
}
}
return result;
}