mirror of
https://github.com/hydralauncher/hydra.git
synced 2026-01-19 17:23:57 +00:00
feat: ui improvement
This commit is contained in:
@@ -4,13 +4,25 @@ import { gameDetailsContext } from "@renderer/context";
|
||||
import * as styles from "./achievement-panel.css";
|
||||
|
||||
import HydraIcon from "@renderer/assets/icons/hydra.svg?react";
|
||||
import { UserAchievement } from "@types";
|
||||
|
||||
export interface HeroPanelProps {
|
||||
isHeaderStuck: boolean;
|
||||
export interface AchievementPanelProps {
|
||||
achievements: UserAchievement[];
|
||||
}
|
||||
|
||||
export function AchievementPanel({ isHeaderStuck }: HeroPanelProps) {
|
||||
const { t } = useTranslation("game_details");
|
||||
export function AchievementPanel({ achievements }: AchievementPanelProps) {
|
||||
const { t } = useTranslation("achievement");
|
||||
|
||||
const achievementsPointsTotal = achievements.reduce(
|
||||
(acc, achievement) => acc + (achievement.points ?? 0),
|
||||
0
|
||||
);
|
||||
|
||||
const achievementsPointsEarnedSum = achievements.reduce(
|
||||
(acc, achievement) =>
|
||||
acc + (achievement.unlocked ? (achievement.points ?? 0) : 0),
|
||||
0
|
||||
);
|
||||
|
||||
const {} = useContext(gameDetailsContext);
|
||||
|
||||
@@ -18,7 +30,8 @@ export function AchievementPanel({ isHeaderStuck }: HeroPanelProps) {
|
||||
<>
|
||||
<div className={styles.panel}>
|
||||
<div className={styles.content}>
|
||||
Pontos desbloqueados: <HydraIcon width={20} height={20} /> 69/420
|
||||
{t("earned_points")} <HydraIcon width={20} height={20} />
|
||||
{achievementsPointsEarnedSum} / {achievementsPointsTotal}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
|
||||
@@ -329,7 +329,7 @@ export function AchievementsContent({
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<AchievementPanel isHeaderStuck={false} />
|
||||
<AchievementPanel achievements={achievements!} />
|
||||
<AchievementList achievements={achievements!} />
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
import type { ComparedAchievements } from "@types";
|
||||
import * as styles from "./achievements.css";
|
||||
import { CheckCircleIcon, LockIcon } from "@primer/octicons-react";
|
||||
import {
|
||||
CheckCircleIcon,
|
||||
EyeClosedIcon,
|
||||
LockIcon,
|
||||
} from "@primer/octicons-react";
|
||||
import { useDate } from "@renderer/hooks";
|
||||
import { SPACING_UNIT } from "@renderer/theme.css";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export interface ComparedAchievementListProps {
|
||||
achievements: ComparedAchievements;
|
||||
@@ -11,6 +16,7 @@ export interface ComparedAchievementListProps {
|
||||
export function ComparedAchievementList({
|
||||
achievements,
|
||||
}: ComparedAchievementListProps) {
|
||||
const { t } = useTranslation("achievement");
|
||||
const { formatDateTime } = useDate();
|
||||
|
||||
return (
|
||||
@@ -43,7 +49,17 @@ export function ComparedAchievementList({
|
||||
loading="lazy"
|
||||
/>
|
||||
<div>
|
||||
<h4>{achievement.displayName}</h4>
|
||||
<h4 style={{ display: "flex", alignItems: "center", gap: "4px" }}>
|
||||
{achievement.hidden && (
|
||||
<span
|
||||
style={{ display: "flex" }}
|
||||
title={t("hidden_achievement_tooltip")}
|
||||
>
|
||||
<EyeClosedIcon size={12} />
|
||||
</span>
|
||||
)}
|
||||
{achievement.displayName}
|
||||
</h4>
|
||||
<p>{achievement.description}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -14,7 +14,7 @@ export interface ComparedAchievementPanelProps {
|
||||
export function ComparedAchievementPanel({
|
||||
achievements,
|
||||
}: ComparedAchievementPanelProps) {
|
||||
const { t } = useTranslation("game_details");
|
||||
const { t } = useTranslation("achievement");
|
||||
|
||||
const {} = useContext(gameDetailsContext);
|
||||
|
||||
|
||||
@@ -41,9 +41,7 @@ export function FriendsBox() {
|
||||
{friend.displayName}
|
||||
</span>
|
||||
{friend.currentGame && (
|
||||
<Link to={buildGameDetailsPath({ ...friend.currentGame })}>
|
||||
<p>{t("playing", { game: friend.currentGame.title })}</p>
|
||||
</Link>
|
||||
<p>{t("playing", { game: friend.currentGame.title })}</p>
|
||||
)}
|
||||
</div>
|
||||
</Link>
|
||||
|
||||
@@ -105,6 +105,22 @@ export const listItem = style({
|
||||
},
|
||||
});
|
||||
|
||||
export const statsListItem = style({
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
transition: "all ease 0.1s",
|
||||
color: vars.color.muted,
|
||||
width: "100%",
|
||||
overflow: "hidden",
|
||||
borderRadius: "4px",
|
||||
padding: `${SPACING_UNIT}px ${SPACING_UNIT}px`,
|
||||
gap: `${SPACING_UNIT}px`,
|
||||
":hover": {
|
||||
backgroundColor: "rgba(255, 255, 255, 0.15)",
|
||||
textDecoration: "none",
|
||||
},
|
||||
});
|
||||
|
||||
export const gamesGrid = style({
|
||||
listStyle: "none",
|
||||
margin: "0",
|
||||
|
||||
@@ -22,6 +22,7 @@ import {
|
||||
} from "@renderer/helpers";
|
||||
import { MAX_MINUTES_TO_SHOW_IN_PLAYTIME } from "@renderer/constants";
|
||||
import { UserStatsBox } from "./user-stats-box";
|
||||
import HydraIcon from "@renderer/assets/icons/hydra.svg?react";
|
||||
|
||||
export function ProfileContent() {
|
||||
const { userProfile, isMe, userStats } = useContext(userProfileContext);
|
||||
@@ -157,7 +158,7 @@ export function ProfileContent() {
|
||||
height: "100%",
|
||||
width: "100%",
|
||||
background:
|
||||
"linear-gradient(0deg, rgba(0, 0, 0, 0.7) 20%, transparent 100%)",
|
||||
"linear-gradient(0deg, rgba(0, 0, 0, 0.75) 25%, transparent 100%)",
|
||||
padding: 8,
|
||||
}}
|
||||
>
|
||||
@@ -187,6 +188,22 @@ export function ProfileContent() {
|
||||
flexDirection: "column",
|
||||
}}
|
||||
>
|
||||
{game.achievementsPointsEarnedSum > 0 && (
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
justifyContent: "start",
|
||||
gap: 8,
|
||||
marginBottom: 4,
|
||||
color: vars.color.muted,
|
||||
}}
|
||||
>
|
||||
<HydraIcon width={16} height={16} />
|
||||
{numberFormatter.format(
|
||||
game.achievementsPointsEarnedSum
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
|
||||
@@ -6,14 +6,13 @@ import { useFormat } from "@renderer/hooks";
|
||||
import { MAX_MINUTES_TO_SHOW_IN_PLAYTIME } from "@renderer/constants";
|
||||
import HydraIcon from "@renderer/assets/icons/hydra.svg?react";
|
||||
import { useSubscription } from "@renderer/hooks/use-subscription";
|
||||
import { ClockIcon, TrophyIcon } from "@primer/octicons-react";
|
||||
import { vars } from "@renderer/theme.css";
|
||||
|
||||
export function UserStatsBox() {
|
||||
const { showHydraCloudModal } = useSubscription();
|
||||
|
||||
const { userStats } = useContext(userProfileContext);
|
||||
|
||||
const { userStats, isMe } = useContext(userProfileContext);
|
||||
const { t } = useTranslation("user_profile");
|
||||
|
||||
const { numberFormatter } = useFormat();
|
||||
|
||||
const formatPlayTime = useCallback(
|
||||
@@ -43,22 +42,46 @@ export function UserStatsBox() {
|
||||
|
||||
<div className={styles.box}>
|
||||
<ul className={styles.list}>
|
||||
<li>
|
||||
<h3 className={styles.listItemTitle}>{t("achievements")}</h3>
|
||||
{userStats.achievementsPointsEarnedSum !== undefined ? (
|
||||
<>
|
||||
{(isMe || userStats.unlockedAchievementSum !== undefined) && (
|
||||
<li className={styles.statsListItem}>
|
||||
<h3 className={styles.listItemTitle}>
|
||||
{t("achievements_unlocked")}
|
||||
</h3>
|
||||
{userStats.unlockedAchievementSum !== undefined ? (
|
||||
<div
|
||||
style={{ display: "flex", justifyContent: "space-between" }}
|
||||
>
|
||||
<p
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: "4px",
|
||||
}}
|
||||
>
|
||||
<p className={styles.listItemDescription}>
|
||||
<TrophyIcon /> {userStats.unlockedAchievementSum}{" "}
|
||||
{t("achievements")}
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<button
|
||||
type="button"
|
||||
onClick={showHydraCloudModal}
|
||||
className={styles.link}
|
||||
>
|
||||
<small style={{ color: vars.color.warning }}>
|
||||
{t("show_achievements_on_profile")}
|
||||
</small>
|
||||
</button>
|
||||
)}
|
||||
</li>
|
||||
)}
|
||||
|
||||
{(isMe || userStats.achievementsPointsEarnedSum !== undefined) && (
|
||||
<li className={styles.statsListItem}>
|
||||
<h3 className={styles.listItemTitle}>{t("earned_points")}</h3>
|
||||
{userStats.achievementsPointsEarnedSum !== undefined ? (
|
||||
<div
|
||||
style={{ display: "flex", justifyContent: "space-between" }}
|
||||
>
|
||||
<p className={styles.listItemDescription}>
|
||||
<HydraIcon width={20} height={20} />
|
||||
{userStats.achievementsPointsEarnedSum.value}
|
||||
{numberFormatter.format(
|
||||
userStats.achievementsPointsEarnedSum.value
|
||||
)}
|
||||
</p>
|
||||
<p title={t("ranking_updated_weekly")}>
|
||||
{t("top_percentile", {
|
||||
@@ -67,25 +90,27 @@ export function UserStatsBox() {
|
||||
})}
|
||||
</p>
|
||||
</div>
|
||||
<p>Unlock count: {userStats.unlockedAchievementSum}</p>
|
||||
</>
|
||||
) : (
|
||||
<button
|
||||
type="button"
|
||||
onClick={showHydraCloudModal}
|
||||
className={styles.link}
|
||||
>
|
||||
<small>
|
||||
Saiba como exibir suas conquistas e pontos no perfil
|
||||
</small>
|
||||
</button>
|
||||
)}
|
||||
</li>
|
||||
) : (
|
||||
<button
|
||||
type="button"
|
||||
onClick={showHydraCloudModal}
|
||||
className={styles.link}
|
||||
>
|
||||
<small style={{ color: vars.color.warning }}>
|
||||
{t("show_achievements_on_profile")}
|
||||
</small>
|
||||
</button>
|
||||
)}
|
||||
</li>
|
||||
)}
|
||||
|
||||
<li>
|
||||
<li className={styles.statsListItem}>
|
||||
<h3 className={styles.listItemTitle}>{t("total_play_time")}</h3>
|
||||
<div style={{ display: "flex", justifyContent: "space-between" }}>
|
||||
<p>{formatPlayTime(userStats.totalPlayTimeInSeconds.value)}</p>
|
||||
<p className={styles.listItemDescription}>
|
||||
<ClockIcon />
|
||||
{formatPlayTime(userStats.totalPlayTimeInSeconds.value)}
|
||||
</p>
|
||||
<p title={t("ranking_updated_weekly")}>
|
||||
{t("top_percentile", {
|
||||
percentile: userStats.totalPlayTimeInSeconds.topPercentile,
|
||||
|
||||
Reference in New Issue
Block a user