Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 16 additions & 2 deletions app/(website)/user/[id]/components/Tabs/UserTabGeneral.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { FolderKanbanIcon, Trophy, User2 } from "lucide-react";
import { FolderKanbanIcon, HistoryIcon, Trophy, User2 } from "lucide-react";
import { useState } from "react";

import UserGrades from "@/app/(website)/user/[id]/components/UserGrades";
import { UserLevelProgress } from "@/app/(website)/user/[id]/components/UserLevelProgress";
import UserPlayHistoryChart from "@/app/(website)/user/[id]/components/UserPlayHistoryChart";
import UserStatsChart from "@/app/(website)/user/[id]/components/UserStatsChart";
import BBCodeTextField from "@/components/BBCode/BBCodeTextField";
import PrettyHeader from "@/components/General/PrettyHeader";
Expand All @@ -11,6 +12,7 @@ import { Button } from "@/components/ui/button";
import { Skeleton } from "@/components/ui/skeleton";
import { useUserGrades } from "@/lib/hooks/api/user/useUserGrades";
import { useUserGraph } from "@/lib/hooks/api/user/useUserGraph";
import { useUserPlayHistoryGraph } from "@/lib/hooks/api/user/useUserPlayHistoryGraph";
import { useT } from "@/lib/i18n/utils";
import type { GameMode, UserResponse, UserStatsResponse } from "@/lib/types/api";
import NumberWith from "@/lib/utils/numberWith";
Expand All @@ -33,8 +35,11 @@ export default function UserTabGeneral({
const userGradesQuery = useUserGrades(user.user_id, gameMode);
const userGraphQuery = useUserGraph(user.user_id, gameMode);

const userPlayHistoryGraphQuery = useUserPlayHistoryGraph(user.user_id);

const userGrades = userGradesQuery.data;
const userGraph = userGraphQuery.data;
const userPlayHistoryGraph = userPlayHistoryGraphQuery.data;

return (
<div className="flex flex-col">
Expand Down Expand Up @@ -169,7 +174,7 @@ export default function UserTabGeneral({
</div>

{user.description && user.description.length > 0 && (
<div className="md:col-span-2 lg:col-span-3 ">
<div className="col-span-2 lg:col-span-3 ">
<PrettyHeader text={t("aboutMe")} icon={<User2 />} />
<RoundedContent className="h-fit min-h-0">
<div className="max-h-96 overflow-y-auto">
Expand All @@ -178,6 +183,15 @@ export default function UserTabGeneral({
</RoundedContent>
</div>
)}

{userPlayHistoryGraph && userPlayHistoryGraph.snapshots.length > 0 && (
<div className="col-span-2 lg:col-span-3">
<PrettyHeader text={t("playHistory")} icon={<HistoryIcon />} />
<RoundedContent className="h-fit min-h-0">
<UserPlayHistoryChart data={userPlayHistoryGraph} />
</RoundedContent>
</div>
)}
</div>
</div>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export default function UserGeneralInformation({
const friendsData = friendsQuery.data;

const localizedPlaystyle = metadata
? metadata.playstyle.map(p => tPlaystyle(`options.${p}`)).join(", ")
? metadata.playstyle.filter(p => p !== UserPlaystyle.NONE).map(p => tPlaystyle(`options.${p}`)).join(", ")
: null;

return (
Expand Down
111 changes: 111 additions & 0 deletions app/(website)/user/[id]/components/UserPlayHistoryChart.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import Cookies from "js-cookie";
import {
CartesianGrid,
Line,
LineChart,
ResponsiveContainer,
Tooltip,
XAxis,
YAxis,
} from "recharts";

import { useT } from "@/lib/i18n/utils";
import type {
GetUserByUserIdPlayHistoryGraphResponse,
} from "@/lib/types/api";

interface Props {
data: GetUserByUserIdPlayHistoryGraphResponse;
}

export default function UserPlayHistoryChart({ data }: Props) {
const t = useT("pages.user.components.statsChart");

const locale = Cookies.get("locale") || "en";

if (data.snapshots.length === 0)
return null;

const firstDate = new Date(data.snapshots[0].saved_at);
const lastDate = new Date(data.snapshots.at(-1)!.saved_at);

let { snapshots } = data;

for (let date = firstDate; date <= lastDate; date.setMonth(date.getMonth() + 1)) {
const dateString = date.toLocaleString(locale, { year: "numeric", month: "short" });
if (!snapshots.some(s => new Date(s.saved_at).toLocaleString(locale, { year: "numeric", month: "short" }) === dateString)) {
snapshots.push({
saved_at: date.toISOString(),
play_count: 0,
});
}
}

snapshots = snapshots.sort((b, a) => new Date(b.saved_at).getTime() - new Date(a.saved_at).getTime());

const chartData = snapshots.map((s) => {
return {
date: new Date(s.saved_at).toLocaleString(locale, { year: "numeric", month: "short" }),
play_count: s.play_count,
};
});

const leewayForDomain = 10;

return (
<ResponsiveContainer
width="100%"
height="100%"
className="h-52 max-h-52 min-h-52"
>
<LineChart data={chartData} margin={{ top: 5, right: 20, bottom: 40, left: 0 }}>
<CartesianGrid stroke="#e0e0e0" strokeOpacity={0.3} vertical={false} />
<XAxis
dataKey="date"
tick={{ fontSize: 12 }}
angle={-45}
textAnchor="end"
height={60}
stroke="#666"
/>
<YAxis
type="number"
tickFormatter={(value: number) => {
return (Math.round(value / 10) * 10).toString();
}}
domain={[
(dataMin: number) => Math.floor(Math.max(0, dataMin - leewayForDomain) / 10) * 10,
(dataMax: number) => Math.ceil((dataMax + leewayForDomain) / 10) * 10,
]}
stroke="#666"
tick={{ fontSize: 12 }}
/>

<Line
dataKey="play_count"
stroke="#E0C097"
strokeWidth={2}
dot={{ fill: "#E0C097", strokeWidth: 0, r: 0 }}
activeDot={{ r: 6 }}
isAnimationActive={false}
/>

<Tooltip
formatter={value => [
t("tooltip", {
value: Math.round(value as number),
type: t("types.plays"),
}),
]}
contentStyle={{
backgroundColor: "#fff",
border: "1px solid #ccc",
borderRadius: "4px",
color: "#333",
}}
labelStyle={{ color: "#666", fontWeight: "bold" }}
/>
</LineChart>
</ResponsiveContainer>
);
}
6 changes: 6 additions & 0 deletions components/Header/LanguageSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,12 @@ export function LanguageSelector() {
}));
}, [getLanguageName]);

const isEnglishOnlyPossibleLanguage = languages.every(language => language.code === "en");

if (isEnglishOnlyPossibleLanguage) {
return null;
}

return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
Expand Down
9 changes: 9 additions & 0 deletions lib/hooks/api/user/useUserPlayHistoryGraph.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
"use client";

import useSWR from "swr";

import type { GetUserByUserIdPlayHistoryGraphResponse } from "@/lib/types/api";

export function useUserPlayHistoryGraph(userId: number) {
return useSWR<GetUserByUserIdPlayHistoryGraphResponse>(`user/${userId}/play-history-graph`);
}
6 changes: 4 additions & 2 deletions lib/i18n/messages/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -826,7 +826,8 @@
"performance": "Performance",
"showByRank": "Nach Rang anzeigen",
"showByPp": "Nach PP anzeigen",
"aboutMe": "Über mich"
"aboutMe": "Über mich",
"playHistory": "Spielverlauf"
},
"scoresTab": {
"bestScores": "Beste Scores",
Expand Down Expand Up @@ -890,7 +891,8 @@
"date": "Datum",
"types": {
"pp": "pp",
"rank": "rang"
"rank": "rang",
"plays": "Spiele"
},
"tooltip": "{value} {type}"
}
Expand Down
6 changes: 4 additions & 2 deletions lib/i18n/messages/en-GB.json
Original file line number Diff line number Diff line change
Expand Up @@ -830,7 +830,8 @@
"performance": "Pewfowmance",
"showByRank": "Show by rank",
"showByPp": "Show by pp",
"aboutMe": "Abouwt me"
"aboutMe": "Abouwt me",
"playHistory": "Play histowy"
},
"scoresTab": {
"bestScores": "Best scowes",
Expand Down Expand Up @@ -894,7 +895,8 @@
"date": "Date",
"types": {
"pp": "pp",
"rank": "rank"
"rank": "rank",
"plays": "pways"
},
"tooltip": "{value} {type}"
}
Expand Down
8 changes: 8 additions & 0 deletions lib/i18n/messages/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -2174,6 +2174,10 @@
"aboutMe": {
"text": "About me",
"context": "Section header for user description/about section"
},
"playHistory": {
"text": "Play history",
"context": "Section header for user play history"
}
},
"scoresTab": {
Expand Down Expand Up @@ -2349,6 +2353,10 @@
"rank": {
"text": "rank",
"context": "Word for rank in chart tooltip"
},
"plays": {
"text": "plays",
"context": "Word for plays in chart tooltip"
}
},
"tooltip": {
Expand Down
6 changes: 4 additions & 2 deletions lib/i18n/messages/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -826,7 +826,8 @@
"performance": "Rendimiento",
"showByRank": "Mostrar por rango",
"showByPp": "Mostrar por pp",
"aboutMe": "Sobre mí"
"aboutMe": "Sobre mí",
"playHistory": "Historial de juego"
},
"scoresTab": {
"bestScores": "Mejores puntuaciones",
Expand Down Expand Up @@ -890,7 +891,8 @@
"date": "Fecha",
"types": {
"pp": "pp",
"rank": "rank"
"rank": "rank",
"plays": "jugadas"
},
"tooltip": "{value} {type}"
}
Expand Down
6 changes: 4 additions & 2 deletions lib/i18n/messages/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -826,7 +826,8 @@
"performance": "Performance",
"showByRank": "Afficher par rang",
"showByPp": "Afficher par pp",
"aboutMe": "À propos de moi"
"aboutMe": "À propos de moi",
"playHistory": "Historique de jeu"
},
"scoresTab": {
"bestScores": "Meilleures performances",
Expand Down Expand Up @@ -890,7 +891,8 @@
"date": "Date",
"types": {
"pp": "pp",
"rank": "rank"
"rank": "rank",
"plays": "parties"
},
"tooltip": "{value} {type}"
}
Expand Down
6 changes: 4 additions & 2 deletions lib/i18n/messages/ja.json
Original file line number Diff line number Diff line change
Expand Up @@ -826,7 +826,8 @@
"performance": "パフォーマンス",
"showByRank": "順位で表示",
"showByPp": "PPで表示",
"aboutMe": "自己紹介"
"aboutMe": "自己紹介",
"playHistory": "プレイ履歴"
},
"scoresTab": {
"bestScores": "ベストパフォーマンス",
Expand Down Expand Up @@ -890,7 +891,8 @@
"date": "日付",
"types": {
"pp": "pp",
"rank": "rank"
"rank": "rank",
"plays": "プレイ"
},
"tooltip": "{value} {type}"
}
Expand Down
6 changes: 4 additions & 2 deletions lib/i18n/messages/ru.json
Original file line number Diff line number Diff line change
Expand Up @@ -823,7 +823,8 @@
"performance": "Производительность",
"showByRank": "Показать по рангу",
"showByPp": "Показать по пп",
"aboutMe": "Обо мне"
"aboutMe": "Обо мне",
"playHistory": "История игры"
},
"scoresTab": {
"bestScores": "Лучшие результаты",
Expand Down Expand Up @@ -887,7 +888,8 @@
"date": "Дата",
"types": {
"pp": "пп",
"rank": "ранг"
"rank": "ранг",
"plays": "игры"
},
"tooltip": "{value} {type}"
}
Expand Down
6 changes: 4 additions & 2 deletions lib/i18n/messages/uk.json
Original file line number Diff line number Diff line change
Expand Up @@ -826,7 +826,8 @@
"performance": "Продуктивність",
"showByRank": "Показати за рейтингом",
"showByPp": "Показати за pp",
"aboutMe": "Про мене"
"aboutMe": "Про мене",
"playHistory": "Історія гри"
},
"scoresTab": {
"bestScores": "Кращі результати",
Expand Down Expand Up @@ -890,7 +891,8 @@
"date": "Дата",
"types": {
"pp": "pp",
"rank": "рейтинг"
"rank": "рейтинг",
"plays": "ігри"
},
"tooltip": "{value} {type}"
}
Expand Down
6 changes: 4 additions & 2 deletions lib/i18n/messages/zh-CN.json
Original file line number Diff line number Diff line change
Expand Up @@ -826,7 +826,8 @@
"performance": "表现",
"showByRank": "按排名显示",
"showByPp": "按 PP 显示",
"aboutMe": "关于我"
"aboutMe": "关于我",
"playHistory": "游玩历史"
},
"scoresTab": {
"bestScores": "最好成绩",
Expand Down Expand Up @@ -890,7 +891,8 @@
"date": "日期",
"types": {
"pp": "pp",
"rank": "rank"
"rank": "rank",
"plays": "游玩次数"
},
"tooltip": "{value} {type}"
}
Expand Down
Loading