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

+
+ + ); + })} +
+ ))} +
)}