diff --git a/backend/controller/userController.ts b/backend/controller/userController.ts
index 78e22c4..21d40af 100644
--- a/backend/controller/userController.ts
+++ b/backend/controller/userController.ts
@@ -20,7 +20,7 @@ import { getWeight, lootboxes } from "../../shared/lootboxes";
import { weightedPickRandom } from "../../shared/utils";
import { emit } from "../events";
import { Game } from "../schema";
-import { and, eq, gt } from "drizzle-orm";
+import { and, count, eq, gt, max, not, sum } from "drizzle-orm";
import dayjs from "dayjs";
const secret = process.env.SECRET!;
@@ -185,11 +185,7 @@ export const userController = createController({
}),
async ({ id }, { db }) => {
const now = dayjs();
- const firstOfYear = now
- .set("day", 0)
- .set("month", 0)
- .set("hour", 0)
- .set("minute", 0);
+ const firstOfYear = now.startOf("year");
const gamesOfUser = await db.query.Game.findMany({
where: and(eq(Game.user, id), gt(Game.finished, firstOfYear.valueOf())),
});
@@ -203,4 +199,34 @@ export const userController = createController({
return heat;
},
),
+ getProfile: createEndpoint(
+ z.object({
+ id: z.string(),
+ }),
+ async ({ id }, { db }) => {
+ const [{ value: totalGames }] = await db
+ .select({
+ value: count(),
+ })
+ .from(Game)
+ .where(and(eq(Game.user, id), not(eq(Game.finished, 0))));
+ const [{ value: highestStage }] = await db
+ .select({
+ value: max(Game.stage),
+ })
+ .from(Game)
+ .where(and(eq(Game.user, id), not(eq(Game.finished, 0))));
+ const [{ value: totalStages }] = await db
+ .select({
+ value: sum(Game.stage),
+ })
+ .from(Game)
+ .where(and(eq(Game.user, id), not(eq(Game.finished, 0))));
+ return {
+ totalGames,
+ highestStage,
+ averageStage: Number(totalStages) / totalGames,
+ };
+ },
+ ),
});
diff --git a/bun.lockb b/bun.lockb
index 99f2628..e7afa93 100755
Binary files a/bun.lockb and b/bun.lockb differ
diff --git a/package.json b/package.json
index 72cf3f4..8c4363d 100644
--- a/package.json
+++ b/package.json
@@ -23,6 +23,7 @@
"@radix-ui/react-dropdown-menu": "^2.1.2",
"@radix-ui/react-popover": "^1.1.2",
"@radix-ui/react-switch": "^1.1.1",
+ "@radix-ui/react-tooltip": "^1.2.7",
"@tanstack/react-query": "^5.59.11",
"@tanstack/react-query-devtools": "^5.59.11",
"@tsparticles/engine": "^3.5.0",
diff --git a/src/components/PastMatch.tsx b/src/components/PastMatch.tsx
index 6eafdfe..fb54902 100644
--- a/src/components/PastMatch.tsx
+++ b/src/components/PastMatch.tsx
@@ -9,8 +9,8 @@ interface PastMatchProps {
const PastMatch = ({ game }: PastMatchProps) => {
return (
-
-
+
+
Endless
@@ -24,7 +24,7 @@ const PastMatch = ({ game }: PastMatchProps) => {
{game.minesCount - game.isFlagged.flat().filter((f) => f).length}
-
+
Duration: {formatTimeSpan(game.finished - game.started)}
diff --git a/src/components/Tooltip.tsx b/src/components/Tooltip.tsx
new file mode 100644
index 0000000..b899b2f
--- /dev/null
+++ b/src/components/Tooltip.tsx
@@ -0,0 +1,60 @@
+"use client";
+
+import * as React from "react";
+import * as TooltipPrimitive from "@radix-ui/react-tooltip";
+import { cn } from "../lib/utils";
+
+function TooltipProvider({
+ delayDuration = 0,
+ ...props
+}: React.ComponentProps
) {
+ return (
+
+ );
+}
+
+function Tooltip({
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+
+ );
+}
+
+function TooltipTrigger({
+ ...props
+}: React.ComponentProps) {
+ return ;
+}
+
+function TooltipContent({
+ className,
+ sideOffset = 0,
+ children,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+ {children}
+
+
+
+ );
+}
+
+export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };
diff --git a/src/index.css b/src/index.css
index 31b1d71..cae1f4b 100644
--- a/src/index.css
+++ b/src/index.css
@@ -1,20 +1,36 @@
@import "tailwindcss";
@theme {
- --color-primary: #D9AFD9;
+ --color-primary: #d9afd9;
--color-input: color-mix(in srgb, var(--color-white, #fff) 20%, transparent);
--color-background: black;
--color-common: var(--color-sky-500);
--color-uncommon: var(--color-green-400);
--color-rare: var(--color-red-500);
--color-legendary: var(--color-amber-500);
- --bg-brand: -webkit-linear-gradient(225deg, rgb(251, 175, 21), rgb(251, 21, 242),
- rgb(21, 198, 251)) 0% 0% / 100% 300%;
- --bg-secondary: linear-gradient(90deg, #D9AFD9 0%, #97D9E1 100%) 0% 0% / 100% 300%;
+ --bg-brand: -webkit-linear-gradient(
+ 225deg,
+ rgb(251, 175, 21),
+ rgb(251, 21, 242),
+ rgb(21, 198, 251)
+ )
+ 0% 0% / 100% 300%;
+ --bg-secondary: linear-gradient(90deg, #d9afd9 0%, #97d9e1 100%) 0% 0% / 100%
+ 300%;
--animate-gradientmove: gradientmove 1s ease 0s 1 normal forwards;
@keyframes gradientmove {
- 0%{background-position: 0% 0%}
- 100%{background-position: 0% 100%}
+ 0% {
+ background-position: 0% 0%;
+ }
+ 100% {
+ background-position: 0% 100%;
+ }
+ }
+}
+
+@layer components {
+ .bg-brand {
+ background: var(--bg-brand);
}
}
@@ -22,7 +38,7 @@ button {
cursor: pointer;
}
-.grid-border-b div:not(:nth-last-child(-n+3)) {
+.grid-border-b div:not(:nth-last-child(-n + 3)) {
@apply border-b border-white/10;
}
diff --git a/src/views/profile/Profile.tsx b/src/views/profile/Profile.tsx
index 85beb85..7b8c4a9 100644
--- a/src/views/profile/Profile.tsx
+++ b/src/views/profile/Profile.tsx
@@ -1,35 +1,89 @@
import { useMemo } from "react";
import { useWSQuery } from "../../hooks";
import dayjs from "dayjs";
+import {
+ Tooltip,
+ TooltipContent,
+ TooltipTrigger,
+} from "../../components/Tooltip";
+import PastMatch from "../../components/PastMatch";
const Profile: React.FC = () => {
- const { data: heatmap } = useWSQuery("user.getHeatmap", { id: "Gordon" });
- const now = useMemo(() => dayjs(), []);
- const firstOfYear = useMemo(
- () => now.set("day", 0).set("month", 0).set("hour", 0).set("minute", 0),
- [now],
+ const { data: username } = useWSQuery("user.getSelf", null);
+ const { data: heatmap } = useWSQuery(
+ "user.getHeatmap",
+ { id: username! },
+ !!username,
);
+ const { data: profile } = useWSQuery(
+ "user.getProfile",
+ { id: username! },
+ !!username,
+ );
+ const { data: pastGames } = useWSQuery(
+ "game.getGames",
+ {
+ user: username!,
+ page: 0,
+ },
+ !!username,
+ );
+ const now = useMemo(() => dayjs(), []);
+ const firstOfYear = useMemo(() => now.startOf("year"), [now]);
const weeks = now.diff(firstOfYear, "weeks") + 1;
const maxHeat = heatmap ? Math.max(...heatmap) : 0;
return (
-
+
+
+
{username}
+
+
Total Games: {profile?.totalGames}
+
Highest Stage: {profile?.highestStage}
+
Average Stage: {profile?.averageStage}
+
+
+
+ {pastGames?.data
+ .slice(0, 4)
+ .map((game) =>
)}
+
{heatmap && (
-
- {Array.from({ length: weeks }).map((_, w) => (
-
- {Array.from({ length: 7 }).map((_, d) => (
-
- ))}
-
- ))}
+
+
Activity
+
+ {Array.from({ length: weeks }).map((_, w) => (
+
+ {Array.from({ length: 7 }).map((_, d) => {
+ const index = w * 7 + d;
+ if (index >= heatmap.length) return;
+ return (
+
+
+
+
+
+
+ {firstOfYear
+ .clone()
+ .add(index, "days")
+ .format("DD/MM/YYYY")}
+
+ {heatmap[index]} Games Played
+
+
+ );
+ })}
+
+ ))}
+
)}