Fix: marked props read-only and catch error

This commit is contained in:
Moyasee
2025-10-05 20:36:20 +03:00
parent 6667e00c91
commit 063e97e0ec
8 changed files with 79 additions and 46 deletions

View File

@@ -223,7 +223,7 @@
"rating": "Rating",
"rating_stats": "Rating",
"rating_very_negative": "Very Negative",
"rating_negative": "Negative",
"rating_negative": "Negative",
"rating_neutral": "Neutral",
"rating_positive": "Positive",
"rating_very_positive": "Very Positive",

View File

@@ -73,7 +73,7 @@
color: globals.$muted-color;
font-size: 12px;
align-items: center;
// Ensure star rating is properly aligned
.star-rating {
align-items: center;

View File

@@ -109,7 +109,7 @@ export function GameCard({ game, ...props }: GameCardProps) {
</span>
</div>
<div className="game-card__specifics-item">
<StarRating
<StarRating
rating={stats?.averageScore || null}
size={14}
showCalculating={!!(stats && stats.averageScore === null)}

View File

@@ -1 +1 @@
export * from "./star-rating";
export * from "./star-rating";

View File

@@ -51,4 +51,4 @@
color: globals.$muted-color;
}
}
}
}

View File

@@ -9,13 +9,13 @@ export interface StarRatingProps {
calculatingText?: string;
}
export function StarRating({
rating,
maxStars = 5,
export function StarRating({
rating,
maxStars = 5,
size = 12,
showCalculating = false,
calculatingText = "Calculating"
}: StarRatingProps) {
calculatingText = "Calculating",
}: Readonly<StarRatingProps>) {
if (rating === null && showCalculating) {
return (
<div className="star-rating star-rating--calculating">
@@ -40,25 +40,36 @@ export function StarRating({
return (
<div className="star-rating">
{Array.from({ length: filledStars }, (_, index) => (
<StarFillIcon key={`filled-${index}`} size={size} className="star-rating__star star-rating__star--filled" />
<StarFillIcon
key={`filled-${index}`}
size={size}
className="star-rating__star star-rating__star--filled"
/>
))}
{hasHalfStar && (
<div className="star-rating__half-star" key="half-star">
<StarIcon size={size} className="star-rating__star star-rating__star--empty" />
<StarFillIcon size={size} className="star-rating__star star-rating__star--half" />
<StarIcon
size={size}
className="star-rating__star star-rating__star--empty"
/>
<StarFillIcon
size={size}
className="star-rating__star star-rating__star--half"
/>
</div>
)}
{Array.from({ length: emptyStars }, (_, index) => (
<StarIcon key={`empty-${index}`} size={size} className="star-rating__star star-rating__star--empty" />
<StarIcon
key={`empty-${index}`}
size={size}
className="star-rating__star star-rating__star--empty"
/>
))}
<span className="star-rating__value">{rating.toFixed(1)}</span>
</div>
);
}
}

View File

@@ -1,5 +1,10 @@
import { useContext, useEffect, useMemo, useRef, useState } from "react";
import { PencilIcon, TrashIcon, ClockIcon, NoteIcon } from "@primer/octicons-react";
import {
PencilIcon,
TrashIcon,
ClockIcon,
NoteIcon,
} from "@primer/octicons-react";
import { ThumbsUp, ThumbsDown, Star } from "lucide-react";
import { useNavigate } from "react-router-dom";
import { useEditor, EditorContent } from "@tiptap/react";
@@ -68,12 +73,18 @@ const getSelectScoreColorClass = (score: number): string => {
const getRatingText = (score: number, t: (key: string) => string): string => {
switch (score) {
case 1: return t("rating_very_negative");
case 2: return t("rating_negative");
case 3: return t("rating_neutral");
case 4: return t("rating_positive");
case 5: return t("rating_very_positive");
default: return "";
case 1:
return t("rating_very_negative");
case 2:
return t("rating_negative");
case 3:
return t("rating_neutral");
case 4:
return t("rating_positive");
case 5:
return t("rating_very_positive");
default:
return "";
}
};
@@ -163,23 +174,23 @@ export function GameDetailsContent() {
handlePaste: (view, event) => {
const htmlContent = event.clipboardData?.getData("text/html") || "";
const plainText = event.clipboardData?.getData("text/plain") || "";
const currentText = view.state.doc.textContent;
const remainingChars = MAX_REVIEW_CHARS - currentText.length;
if ((htmlContent || plainText) && remainingChars > 0) {
event.preventDefault();
if (htmlContent) {
const tempDiv = document.createElement("div");
tempDiv.innerHTML = htmlContent;
const textLength = tempDiv.textContent?.length || 0;
if (textLength <= remainingChars) {
return false;
return false;
}
}
const truncatedText = plainText.slice(0, remainingChars);
view.dispatch(view.state.tr.insertText(truncatedText));
return true;
@@ -343,7 +354,7 @@ export function GameDetailsContent() {
}
setSubmittingReview(true);
try {
await window.electron.createGameReview(
shop,
@@ -355,12 +366,13 @@ export function GameDetailsContent() {
editor?.commands.clearContent();
setReviewScore(null);
showSuccessToast(t("review_submitted_successfully"));
await loadReviews(true);
setShowReviewForm(false);
setShowReviewPrompt(false);
await loadReviews(true);
setShowReviewForm(false);
setShowReviewPrompt(false);
setHasUserReviewed(true);
} catch (error) {
console.error("Failed to submit review:", error);
showErrorToast(t("review_submission_failed"));
} finally {
setSubmittingReview(false);
@@ -387,7 +399,7 @@ export function GameDetailsContent() {
const handleReviewPromptLater = () => {
setShowReviewPrompt(false);
if (objectId) {
sessionStorage.setItem(`reviewPromptDismissed_${objectId}`, 'true');
sessionStorage.setItem(`reviewPromptDismissed_${objectId}`, "true");
}
};
@@ -422,7 +434,7 @@ export function GameDetailsContent() {
useEffect(() => {
if (objectId && (game || shop)) {
loadReviews(true);
checkUserReview();
checkUserReview();
}
}, [game, shop, objectId, reviewsSortBy, userDetails]);
@@ -661,9 +673,13 @@ export function GameDetailsContent() {
onClick={() => setReviewScore(starValue)}
title={getRatingText(starValue, t)}
>
<Star
size={24}
fill={reviewScore && starValue <= reviewScore ? "currentColor" : "none"}
<Star
size={24}
fill={
reviewScore && starValue <= reviewScore
? "currentColor"
: "none"
}
/>
</button>
))}
@@ -684,7 +700,6 @@ export function GameDetailsContent() {
? t("submitting")
: t("submit_review")}
</button>
</div>
</div>
</>
@@ -774,12 +789,19 @@ export function GameDetailsContent() {
</div>
</div>
</div>
<div className="game-details__review-score-stars" title={getRatingText(review.score, t)}>
<div
className="game-details__review-score-stars"
title={getRatingText(review.score, t)}
>
{[1, 2, 3, 4, 5].map((starValue) => (
<Star
key={starValue}
size={20}
fill={starValue <= review.score ? "currentColor" : "none"}
fill={
starValue <= review.score
? "currentColor"
: "none"
}
className={`game-details__review-star ${
starValue <= review.score
? "game-details__review-star--filled"

View File

@@ -62,13 +62,13 @@ $hero-height: 300px;
font-weight: 500;
margin-top: calc(globals.$spacing-unit * 1);
border: 1px solid;
&--success {
background: rgba(34, 197, 94, 0.1);
color: #86efac;
border-color: rgba(34, 197, 94, 0.3);
}
&--error {
background: rgba(239, 68, 68, 0.1);
color: #fca5a5;