Merge branch 'main' into feat/displaying-new-game-update

This commit is contained in:
Chubby Granny Chaser
2025-11-02 20:45:56 +00:00
committed by GitHub
4 changed files with 91 additions and 49 deletions

View File

@@ -224,6 +224,7 @@
"show_more": "Show more", "show_more": "Show more",
"show_less": "Show less", "show_less": "Show less",
"reviews": "Reviews", "reviews": "Reviews",
"review_played_for": "Played for",
"leave_a_review": "Leave a Review", "leave_a_review": "Leave a Review",
"write_review_placeholder": "Share your thoughts about this game...", "write_review_placeholder": "Share your thoughts about this game...",
"sort_newest": "Newest", "sort_newest": "Newest",

View File

@@ -22,7 +22,13 @@
&__review-user-info { &__review-user-info {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: calc(globals.$spacing-unit * 0.25); gap: calc(globals.$spacing-unit * 0.45);
}
&__review-meta-row {
display: flex;
align-items: center;
gap: calc(globals.$spacing-unit * 0.75);
} }
&__review-display-name { &__review-display-name {
@@ -157,28 +163,28 @@
&__review-score-stars { &__review-score-stars {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 2px; gap: 4px;
background: rgba(255, 255, 255, 0.05);
border-radius: 8px;
padding: 2px 6px;
border: 1px solid rgba(255, 255, 255, 0.1);
font-size: 11px;
font-weight: 500;
}
&__review-right {
display: flex;
flex-direction: column;
align-items: flex-end;
} }
&__review-star { &__review-star {
color: #666666; color: rgba(255, 255, 255, 0.7);
transition: color 0.2s ease; transition: color 0.2s ease;
cursor: default; cursor: default;
&--filled { &--filled {
color: #ffffff; color: rgba(255, 255, 255, 0.7);
&.game-details__review-score--red {
color: #fca5a5;
}
&.game-details__review-score--yellow {
color: #fcd34d;
}
&.game-details__review-score--green {
color: #86efac;
}
} }
&--empty { &--empty {
@@ -198,6 +204,24 @@
font-size: globals.$small-font-size; font-size: globals.$small-font-size;
} }
&__review-playtime {
display: flex;
align-items: center;
gap: 4px;
color: rgba(255, 255, 255, 0.7);
font-size: 11px;
font-weight: 500;
background: rgba(255, 255, 255, 0.05);
border-radius: 8px;
padding: 2px 6px;
border: 1px solid rgba(255, 255, 255, 0.1);
margin-top: 0;
svg {
color: rgba(255, 255, 255, 0.6);
}
}
&__review-content { &__review-content {
color: globals.$body-color; color: globals.$body-color;
line-height: 1.5; line-height: 1.5;

View File

@@ -7,9 +7,10 @@ import { useState } from "react";
import type { GameReview } from "@types"; import type { GameReview } from "@types";
import { sanitizeHtml } from "@shared"; import { sanitizeHtml } from "@shared";
import { useDate } from "@renderer/hooks"; import { useDate, useFormat } from "@renderer/hooks";
import { formatNumber } from "@renderer/helpers"; import { formatNumber } from "@renderer/helpers";
import { Avatar } from "@renderer/components"; import { Avatar } from "@renderer/components";
import { MAX_MINUTES_TO_SHOW_IN_PLAYTIME } from "@renderer/constants";
import "./review-item.scss"; import "./review-item.scss";
@@ -29,13 +30,6 @@ interface ReviewItemProps {
) => void; ) => void;
} }
const getScoreColorClass = (score: number): string => {
if (score >= 1 && score <= 2) return "game-details__review-score--red";
if (score >= 3 && score <= 3) return "game-details__review-score--yellow";
if (score >= 4 && score <= 5) return "game-details__review-score--green";
return "";
};
const getRatingText = (score: number, t: (key: string) => string): string => { const getRatingText = (score: number, t: (key: string) => string): string => {
switch (score) { switch (score) {
case 1: case 1:
@@ -68,6 +62,7 @@ export function ReviewItem({
const navigate = useNavigate(); const navigate = useNavigate();
const { t, i18n } = useTranslation("game_details"); const { t, i18n } = useTranslation("game_details");
const { formatDistance } = useDate(); const { formatDistance } = useDate();
const { numberFormatter } = useFormat();
const [showOriginal, setShowOriginal] = useState(false); const [showOriginal, setShowOriginal] = useState(false);
@@ -93,6 +88,21 @@ export function ReviewItem({
} }
}; };
// Format playtime similar to hero panel
const formatPlayTime = (playTimeInSeconds: number) => {
const minutes = playTimeInSeconds / 60;
if (minutes < MAX_MINUTES_TO_SHOW_IN_PLAYTIME) {
return t("amount_minutes", {
amount: minutes.toFixed(0),
});
}
const hours = minutes / 60;
return t("amount_hours", { amount: numberFormatter.format(hours) });
};
// Determine which content to show - always show original for own reviews
const displayContent = needsTranslation const displayContent = needsTranslation
? review.translations[i18n.language] ? review.translations[i18n.language]
: review.reviewHtml; : review.reviewHtml;
@@ -136,34 +146,40 @@ export function ReviewItem({
> >
{review.user.displayName || "Anonymous"} {review.user.displayName || "Anonymous"}
</button> </button>
<div className="game-details__review-date"> <div className="game-details__review-meta-row">
<ClockIcon size={12} /> <div
{formatDistance(new Date(review.createdAt), new Date(), { className="game-details__review-score-stars"
addSuffix: true, title={getRatingText(review.score, t)}
})} >
<Star
size={12}
className="game-details__review-star game-details__review-star--filled"
/>
<span className="game-details__review-score-text">
{review.score}/5
</span>
</div>
{Boolean(
review.playTimeInSeconds && review.playTimeInSeconds > 0
) && (
<div className="game-details__review-playtime">
<ClockIcon size={12} />
<span>
{t("review_played_for")}{" "}
{formatPlayTime(review.playTimeInSeconds || 0)}
</span>
</div>
)}
</div> </div>
</div> </div>
</div> </div>
<div <div className="game-details__review-right">
className="game-details__review-score-stars" <div className="game-details__review-date">
title={getRatingText(review.score, t)} <ClockIcon size={12} />
> {formatDistance(new Date(review.createdAt), new Date(), {
{[1, 2, 3, 4, 5].map((starValue) => ( addSuffix: true,
<Star })}
key={starValue} </div>
size={20}
fill={starValue <= review.score ? "currentColor" : "none"}
className={`game-details__review-star ${
starValue <= review.score
? "game-details__review-star--filled"
: "game-details__review-star--empty"
} ${
starValue <= review.score
? getScoreColorClass(review.score)
: ""
}`}
/>
))}
</div> </div>
</div> </div>
<div> <div>

View File

@@ -245,6 +245,7 @@ export interface GameReview {
isBlocked: boolean; isBlocked: boolean;
hasUpvoted: boolean; hasUpvoted: boolean;
hasDownvoted: boolean; hasDownvoted: boolean;
playTimeInSeconds?: number;
user: { user: {
id: string; id: string;
displayName: string; displayName: string;