diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json index b0fee465..c42f000e 100755 --- a/src/locales/en/translation.json +++ b/src/locales/en/translation.json @@ -317,7 +317,11 @@ "caption": "Caption", "audio": "Audio", "filter_by_source": "Filter by source", - "no_repacks_found": "No sources found for this game" + "no_repacks_found": "No sources found for this game", +"delete_review": "Delete review", + "delete_review_modal_title": "Delete Review", + "delete_review_modal_description": "Are you sure you want to delete your review? This action cannot be undone.", + "delete_review_karma_warning": "You will lose any karma points earned from this review." }, "activation": { "title": "Activate Hydra", @@ -624,7 +628,10 @@ "error_adding_friend": "Could not send friend request. Please check friend code", "friend_code_length_error": "Friend code must have 8 characters", "game_removed_from_pinned": "Game removed from pinned", - "game_added_to_pinned": "Game added to pinned" + "game_added_to_pinned": "Game added to pinned", + "karma": "Karma", + "karma_count": "karma", + "karma_description": "Earned from positive likes on your reviews" }, "achievement": { "achievement_unlocked": "Achievement unlocked", 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 6f2558ef..de66e5a6 100644 --- a/src/renderer/src/pages/game-details/game-details-content.tsx +++ b/src/renderer/src/pages/game-details/game-details-content.tsx @@ -4,16 +4,13 @@ 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 type { GameReview } from "@types"; import { HeroPanel } from "./hero"; import { DescriptionHeader } from "./description-header/description-header"; import { GallerySlider } from "./gallery-slider/gallery-slider"; import { Sidebar } from "./sidebar/sidebar"; -import { EditGameModal } from "./modals"; +import { EditGameModal, DeleteReviewModal } from "./modals"; import { ReviewSortOptions } from "./review-sort-options"; import { ReviewPromptBanner } from "./review-prompt-banner"; @@ -98,6 +95,8 @@ export function GameDetailsContent() { const [backdropOpacity, setBackdropOpacity] = useState(1); const [showEditGameModal, setShowEditGameModal] = useState(false); + const [showDeleteReviewModal, setShowDeleteReviewModal] = useState(false); + const [reviewToDelete, setReviewToDelete] = useState(null); const [isDescriptionExpanded, setIsDescriptionExpanded] = useState(false); // Reviews state management @@ -121,7 +120,7 @@ export function GameDetailsContent() { // Tiptap editor for review input const editor = useEditor({ - extensions: [StarterKit, Bold, Italic, Underline], + extensions: [StarterKit], content: "", editorProps: { attributes: { @@ -239,12 +238,19 @@ export function GameDetailsContent() { }; const handleDeleteReview = async (reviewId: string) => { - if (!objectId) return; + setReviewToDelete(reviewId); + setShowDeleteReviewModal(true); + }; + + const confirmDeleteReview = async () => { + if (!objectId || !reviewToDelete) return; try { - await window.electron.deleteReview(shop, objectId, reviewId); + await window.electron.deleteReview(shop, objectId, reviewToDelete); // Reload reviews after deletion loadReviews(true); + setShowDeleteReviewModal(false); + setReviewToDelete(null); } catch (error) { console.error("Failed to delete review:", error); } @@ -469,7 +475,8 @@ export function GameDetailsContent() {
{/* Review Prompt Banner */} - {showReviewPrompt && + {game?.shop !== "custom" && + showReviewPrompt && userDetails && game?.playTimeInMilliseconds && !hasUserReviewed && @@ -504,260 +511,262 @@ export function GameDetailsContent() { )} -
- {showReviewForm && ( - <> -
-

- {t("leave_a_review")} -

-
+ {game?.shop !== "custom" && ( +
+ {showReviewForm && ( + <> +
+

+ {t("leave_a_review")} +

+
+ +
+
+ +
+
+ + + +
-
-
- -
-
- -
+
- +
+
+ + +
+ + )} -
-
- - -
+ {showReviewForm && ( +
+ )} + +
+
+
+

+ {t("reviews")} +

+ + {totalReviewCount} +
- - )} - - {showReviewForm && ( -
- )} - -
-
-
-

- {t("reviews")} -

- - {totalReviewCount} - -
-
- {reviewsLoading && reviews.length === 0 && ( -
- {t("loading_reviews")} -
- )} + {reviewsLoading && reviews.length === 0 && ( +
+ {t("loading_reviews")} +
+ )} - {!reviewsLoading && reviews.length === 0 && ( -
-
📝
-

- {t("no_reviews_yet")} -

-

- {t("be_first_to_review")} -

-
- )} + {!reviewsLoading && reviews.length === 0 && ( +
+
📝
+

+ {t("no_reviews_yet")} +

+

+ {t("be_first_to_review")} +

+
+ )} - {reviews.map((review, index) => ( -
- {review.isBlocked && - !visibleBlockedReviews.has(review.id) ? ( -
- Review from blocked user — - -
- ) : ( - <> -
-
- {review.user?.profileImageUrl && ( - {review.user.displayName - )} -
-
- review.user?.id && - navigate(`/profile/${review.user.id}`) - } - onKeyDown={(e) => { - if (e.key === "Enter" || e.key === " ") { - e.preventDefault(); + {reviews.map((review, index) => ( +
+ {review.isBlocked && + !visibleBlockedReviews.has(review.id) ? ( +
+ Review from blocked user — + +
+ ) : ( + <> +
+
+ {review.user?.profileImageUrl && ( + {review.user.displayName + )} +
+
review.user?.id && - navigate(`/profile/${review.user.id}`); + navigate(`/profile/${review.user.id}`) } - }} - role="button" - tabIndex={0} - > - {review.user?.displayName || "Anonymous"} -
-
- - {formatDistance( - new Date(review.createdAt), - new Date(), - { addSuffix: true } - )} + 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"} +
+
+ + {formatDistance( + new Date(review.createdAt), + new Date(), + { addSuffix: true } + )} +
+
+ {review.score}/10 +
-
- {review.score}/10 -
-
-
-
-
- - -
- {userDetails?.id === review.user?.id && ( - - )} - {review.isBlocked && - visibleBlockedReviews.has(review.id) && ( +
+
+
+ +
+ {userDetails?.id === review.user?.id && ( + )} -
- - )} -
- ))} + {review.isBlocked && + visibleBlockedReviews.has(review.id) && ( + + )} +
+ + )} +
+ ))} - {hasMoreReviews && !reviewsLoading && ( - - )} + {hasMoreReviews && !reviewsLoading && ( + + )} - {reviewsLoading && reviews.length > 0 && ( -
- {t("loading_more_reviews")} -
- )} + {reviewsLoading && reviews.length > 0 && ( +
+ {t("loading_more_reviews")} +
+ )} +
-
+ )}
{game?.shop !== "custom" && } @@ -773,6 +782,15 @@ export function GameDetailsContent() { onGameUpdated={handleGameUpdated} /> )} + + { + setShowDeleteReviewModal(false); + setReviewToDelete(null); + }} + onConfirm={confirmDeleteReview} + />
); } diff --git a/src/renderer/src/pages/game-details/game-details.scss b/src/renderer/src/pages/game-details/game-details.scss index fe097839..da3745e9 100644 --- a/src/renderer/src/pages/game-details/game-details.scss +++ b/src/renderer/src/pages/game-details/game-details.scss @@ -196,7 +196,6 @@ $hero-height: 300px; display: flex; justify-content: space-between; align-items: center; - margin-bottom: calc(globals.$spacing-unit * 2); padding-bottom: calc(globals.$spacing-unit * 1); } @@ -850,7 +849,6 @@ $hero-height: 300px; justify-content: space-between; align-items: center; margin-bottom: calc(globals.$spacing-unit * 2); - gap: calc(globals.$spacing-unit * 2); @media (max-width: 768px) { flex-direction: column; diff --git a/src/renderer/src/pages/game-details/modals/delete-review-modal.scss b/src/renderer/src/pages/game-details/modals/delete-review-modal.scss new file mode 100644 index 00000000..40ad6e59 --- /dev/null +++ b/src/renderer/src/pages/game-details/modals/delete-review-modal.scss @@ -0,0 +1,19 @@ +@use "../../../scss/globals.scss"; + +.delete-review-modal { + &__karma-warning { + background-color: rgba(255, 193, 7, 0.1); + border: 1px solid rgba(255, 193, 7, 0.3); + border-radius: 4px; + padding: 12px; + margin-bottom: 16px; + color: #ffc107; + font-size: 14px; + font-weight: 500; + } + + &__actions { + display: flex; + justify-content: flex-end; + } +} \ No newline at end of file diff --git a/src/renderer/src/pages/game-details/modals/delete-review-modal.tsx b/src/renderer/src/pages/game-details/modals/delete-review-modal.tsx new file mode 100644 index 00000000..45501b88 --- /dev/null +++ b/src/renderer/src/pages/game-details/modals/delete-review-modal.tsx @@ -0,0 +1,45 @@ +import { useTranslation } from "react-i18next"; +import { Button, Modal } from "@renderer/components"; +import "./delete-review-modal.scss"; + +interface DeleteReviewModalProps { + visible: boolean; + onClose: () => void; + onConfirm: () => void; +} + +export function DeleteReviewModal({ + visible, + onClose, + onConfirm, +}: DeleteReviewModalProps) { + const { t } = useTranslation("game_details"); + + const handleDeleteReview = () => { + onConfirm(); + onClose(); + }; + + return ( + +
+ {t("delete_review_karma_warning")} +
+ +
+ + + +
+
+ ); +} \ No newline at end of file diff --git a/src/renderer/src/pages/game-details/modals/index.ts b/src/renderer/src/pages/game-details/modals/index.ts index 724e0003..d0f27b0f 100644 --- a/src/renderer/src/pages/game-details/modals/index.ts +++ b/src/renderer/src/pages/game-details/modals/index.ts @@ -2,3 +2,4 @@ export * from "./repacks-modal"; export * from "./download-settings-modal"; export * from "./game-options-modal"; export * from "./edit-game-modal"; +export * from "./delete-review-modal"; 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 eafe9972..fba6c50f 100644 --- a/src/renderer/src/pages/game-details/review-sort-options.scss +++ b/src/renderer/src/pages/game-details/review-sort-options.scss @@ -6,6 +6,7 @@ flex-direction: column; align-items: flex-start; gap: calc(globals.$spacing-unit); + margin-bottom: calc(globals.$spacing-unit * 3); } &__label { @@ -37,7 +38,7 @@ transition: all ease 0.2s; display: flex; align-items: center; - gap: 6px; + gap: 4px; &:hover:not(:disabled) { color: rgba(255, 255, 255, 0.6); @@ -61,21 +62,6 @@ } } - &__toggle-option { - &.active { - background: rgba(255, 255, 255, 0.05); - border-radius: 4px; - padding: 6px 8px; - border: 1px solid rgba(255, 255, 255, 0.1); - } - - &:hover:not(:disabled) { - background: rgba(255, 255, 255, 0.03); - border-radius: 4px; - padding: 6px 8px; - } - } - &__separator { color: rgba(255, 255, 255, 0.3); font-size: 14px; 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 fc7f431a..ca11d056 100644 --- a/src/renderer/src/pages/game-details/review-sort-options.tsx +++ b/src/renderer/src/pages/game-details/review-sort-options.tsx @@ -49,16 +49,30 @@ export function ReviewSortOptions({ className={`review-sort-options__option review-sort-options__toggle-option ${isDateActive ? "active" : ""}`} onClick={handleDateToggle} > - {sortBy === "newest" ? : } - {sortBy === "oldest" ? t("sort_oldest") : t("sort_newest")} + {sortBy === "newest" ? ( + + ) : ( + + )} + + {sortBy === "oldest" ? t("sort_oldest") : t("sort_newest")} + | |