updated backend to provide gems spend statistic

This commit is contained in:
MasterGordon 2025-06-28 15:05:36 +02:00
parent e8d1a8afde
commit 59ec036380
6 changed files with 75 additions and 43 deletions

View File

@ -17,9 +17,9 @@ import {
upsertCollection, upsertCollection,
} from "../repositories/collectionRepository"; } from "../repositories/collectionRepository";
import { getWeight, lootboxes } from "../../shared/lootboxes"; import { getWeight, lootboxes } from "../../shared/lootboxes";
import { weightedPickRandom } from "../../shared/utils"; import { round, weightedPickRandom } from "../../shared/utils";
import { emit } from "../events"; import { emit } from "../events";
import { Game } from "../schema"; import { Game, Gems } from "../schema";
import { and, count, eq, gt, max, not, sum } from "drizzle-orm"; import { and, count, eq, gt, max, not, sum } from "drizzle-orm";
import dayjs from "dayjs"; import dayjs from "dayjs";
@ -222,10 +222,19 @@ export const userController = createController({
}) })
.from(Game) .from(Game)
.where(and(eq(Game.user, id), not(eq(Game.finished, 0)))); .where(and(eq(Game.user, id), not(eq(Game.finished, 0))));
const [{ totalGems, currentGems }] = await db
.select({
totalGems: Gems.totalCount,
currentGems: Gems.count,
})
.from(Gems)
.where(eq(Gems.user, id));
return { return {
totalGames, totalGames,
highestStage, highestStage,
averageStage: Number(totalStages) / totalGames, averageStage: round(Number(totalStages) / totalGames, 3),
totalGems,
currentGems,
}; };
}, },
), ),

View File

@ -20,3 +20,8 @@ export const weightedPickRandom = <T>(
} }
return arr[arr.length - 1]; return arr[arr.length - 1];
}; };
export const round = (value: number, digits: number) => {
const factor = Math.pow(10, digits);
return Math.round(value * factor) / factor;
};

View File

@ -11,6 +11,7 @@ import {
} from "../../components/DropdownMenu"; } from "../../components/DropdownMenu";
import { cn } from "../../lib/utils"; import { cn } from "../../lib/utils";
import { useWSMutation, useWSQuery } from "../../hooks"; import { useWSMutation, useWSQuery } from "../../hooks";
import { Suspense } from "react";
const Collection = () => { const Collection = () => {
const { data: collection, refetch } = useWSQuery( const { data: collection, refetch } = useWSQuery(
@ -75,17 +76,19 @@ const Collection = () => {
</DropdownMenu> </DropdownMenu>
)} )}
</div> </div>
<Board <Suspense>
game={testBoard(theme.id)} <Board
onLeftClick={() => {}} game={testBoard(theme.id)}
restartGame={() => {}} onLeftClick={() => {}}
onRightClick={() => {}} restartGame={() => {}}
width={11 * 32} onRightClick={() => {}}
height={4 * 32} width={11 * 32}
className={cn( height={4 * 32}
selected && "outline-primary outline-4 rounded-md", className={cn(
)} selected && "outline-primary outline-4 rounded-md",
/> )}
/>
</Suspense>
</div> </div>
); );
})} })}

View File

@ -3,7 +3,7 @@ import { useAtom } from "jotai";
import { gameIdAtom } from "../../atoms"; import { gameIdAtom } from "../../atoms";
import { Button } from "../../components/Button"; import { Button } from "../../components/Button";
import LeaderboardButton from "../../components/LeaderboardButton"; import LeaderboardButton from "../../components/LeaderboardButton";
import { Fragment, useEffect } from "react"; import { Fragment, startTransition, Suspense, useEffect } from "react";
import { Board } from "../../components/LazyBoard"; import { Board } from "../../components/LazyBoard";
interface EndlessProps { interface EndlessProps {
@ -41,31 +41,33 @@ const Endless: React.FC<EndlessProps> = (props) => {
<div className="grow" /> <div className="grow" />
<LeaderboardButton label="View Leaderboard" /> <LeaderboardButton label="View Leaderboard" />
</div> </div>
<Board <Suspense>
game={game} <Board
restartGame={async () => { game={game}
const gameId = await startGame.mutateAsync(null); restartGame={async () => {
setGameId(gameId.uuid); const gameId = await startGame.mutateAsync(null);
}} setGameId(gameId.uuid);
onLeftClick={(x, y) => { }}
reveal.mutateAsync({ x, y }); onLeftClick={(x, y) => {
}} reveal.mutateAsync({ x, y });
onRightClick={(x, y) => { }}
const isFlagged = game.isFlagged[x][y]; onRightClick={(x, y) => {
const isQuestionMark = game.isQuestionMark[x][y]; const isFlagged = game.isFlagged[x][y];
if (!isFlagged && !isQuestionMark) { const isQuestionMark = game.isQuestionMark[x][y];
placeFlag.mutateAsync({ x, y }); if (!isFlagged && !isQuestionMark) {
return; placeFlag.mutateAsync({ x, y });
} return;
if (isFlagged && settings?.placeQuestionMark) { }
placeQuestionMark.mutateAsync({ x, y }); if (isFlagged && settings?.placeQuestionMark) {
return; placeQuestionMark.mutateAsync({ x, y });
} else { return;
clearTile.mutateAsync({ x, y }); } else {
return; clearTile.mutateAsync({ x, y });
} return;
}} }
/> }}
/>
</Suspense>
</> </>
) : ( ) : (
<div className="w-full grid md:grid-cols-[350px_1fr]"> <div className="w-full grid md:grid-cols-[350px_1fr]">
@ -76,7 +78,9 @@ const Endless: React.FC<EndlessProps> = (props) => {
variant="primary" variant="primary"
onClick={async () => { onClick={async () => {
const gameId = await startGame.mutateAsync(null); const gameId = await startGame.mutateAsync(null);
setGameId(gameId.uuid); startTransition(() => {
setGameId(gameId.uuid);
});
}} }}
> >
Start Game Start Game

View File

@ -7,6 +7,7 @@ import {
TooltipTrigger, TooltipTrigger,
} from "../../components/Tooltip"; } from "../../components/Tooltip";
import PastMatch from "../../components/PastMatch"; import PastMatch from "../../components/PastMatch";
import GemsIcon from "../../components/GemIcon";
const Profile: React.FC = () => { const Profile: React.FC = () => {
const { data: username } = useWSQuery("user.getSelf", null); const { data: username } = useWSQuery("user.getSelf", null);
@ -43,6 +44,11 @@ const Profile: React.FC = () => {
<p> <p>
Average Stage: {Math.round(profile?.averageStage ?? 1 * 100) / 100} Average Stage: {Math.round(profile?.averageStage ?? 1 * 100) / 100}
</p> </p>
<p>
Gems Spend:{" "}
{(profile?.totalGems ?? 0) - (profile?.currentGems ?? 0)}{" "}
<GemsIcon />
</p>
</div> </div>
</div> </div>
<div className="flex flex-col gap-4"> <div className="flex flex-col gap-4">

View File

@ -2,12 +2,17 @@ import { defineConfig } from "vite";
import react from "@vitejs/plugin-react-swc"; import react from "@vitejs/plugin-react-swc";
import tailwindcss from "@tailwindcss/vite"; import tailwindcss from "@tailwindcss/vite";
import { imagetools } from "vite-imagetools"; import { imagetools } from "vite-imagetools";
import { analyzer } from "vite-bundle-analyzer"; // import { analyzer } from "vite-bundle-analyzer";
// https://vitejs.dev/config/ // https://vitejs.dev/config/
export default defineConfig({ export default defineConfig({
server: { server: {
port: 3003, port: 3003,
}, },
plugins: [react(), tailwindcss(), imagetools(), analyzer()], plugins: [
react(),
tailwindcss(),
imagetools(),
// analyzer()
],
}); });